/* Copyright 2017 Andrew Dawson * * This file is part of Tusky. * * Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along with Tusky. If not, see * . */ package com.keylesspalace.tusky; import android.app.*; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.os.Build; import android.preference.PreferenceManager; import android.provider.Settings; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.support.v4.app.TaskStackBuilder; import com.android.volley.AuthFailureError; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.ImageRequest; import com.android.volley.toolbox.JsonArrayRequest; import org.json.JSONArray; import org.json.JSONException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; public class PullNotificationService extends IntentService { private static final int NOTIFY_ID = 6; // This is an arbitrary number. private static final String TAG = "PullNotifications"; public PullNotificationService() { super("Tusky Pull Notification Service"); } @Override protected void onHandleIntent(Intent intent) { SharedPreferences preferences = getSharedPreferences( getString(R.string.preferences_file_key), Context.MODE_PRIVATE); String domain = preferences.getString("domain", null); String accessToken = preferences.getString("accessToken", null); long date = preferences.getLong("lastUpdate", 0); Date lastUpdate = null; if (date != 0) { lastUpdate = new Date(date); } checkNotifications(domain, accessToken, lastUpdate); } private void checkNotifications(final String domain, final String accessToken, final Date lastUpdate) { String endpoint = getString(R.string.endpoint_notifications); String url = "https://" + domain + endpoint; JsonArrayRequest request = new JsonArrayRequest(url, new Response.Listener() { @Override public void onResponse(JSONArray response) { List notifications; try { notifications = Notification.parse(response); } catch (JSONException e) { onCheckNotificationsFailure(e); return; } onCheckNotificationsSuccess(notifications, lastUpdate); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { onCheckNotificationsFailure(error); } }) { @Override public Map getHeaders() throws AuthFailureError { Map headers = new HashMap<>(); headers.put("Authorization", "Bearer " + accessToken); return headers; } }; VolleySingleton.getInstance(this).addToRequestQueue(request); } private void onCheckNotificationsSuccess(List notifications, Date lastUpdate) { Date newest = null; List mentions = new ArrayList<>(); for (Notification notification : notifications) { if (notification.getType() == Notification.Type.MENTION) { Status status = notification.getStatus(); if (status != null) { Date createdAt = status.getCreatedAt(); if (lastUpdate == null || createdAt.after(lastUpdate)) { MentionResult mention = new MentionResult(); mention.content = status.getContent().toString(); mention.displayName = notification.getDisplayName(); mention.avatarUrl = status.getAvatar(); mentions.add(mention); } if (newest == null || createdAt.after(newest)) { newest = createdAt; } } } } long now = new Date().getTime(); if (mentions.size() > 0) { SharedPreferences preferences = getSharedPreferences( getString(R.string.preferences_file_key), Context.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); editor.putLong("lastUpdate", now); editor.apply(); loadAvatar(mentions, mentions.get(0).avatarUrl); } else if (newest != null) { long hoursAgo = (now - newest.getTime()) / (60 * 60 * 1000); if (hoursAgo >= 1) { dismissStaleNotifications(); } } } private void onCheckNotificationsFailure(Exception exception) { //TODO: not sure if just logging here is enough? Log.e(TAG, "Failed to check notifications. " + exception.getMessage()); } private static class MentionResult { public String displayName; public String content; public String avatarUrl; } private String truncateWithEllipses(String string, int limit) { if (string.length() < limit) { return string; } else { return string.substring(0, limit - 3) + "..."; } } private void loadAvatar(final List mentions, String url) { ImageRequest request = new ImageRequest(url, new Response.Listener() { @Override public void onResponse(Bitmap response) { updateNotification(mentions, response); } }, 0, 0, null, null, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { updateNotification(mentions, null); } }); VolleySingleton.getInstance(this).addToRequestQueue(request); } private void updateNotification(List mentions, @Nullable Bitmap icon) { final int NOTIFICATION_CONTENT_LIMIT = 40; SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); String title; if (mentions.size() > 1) { title = String.format( getString(R.string.notification_service_several_mentions), mentions.size()); } else { title = String.format( getString(R.string.notification_service_one_mention), mentions.get(0).displayName); } NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_notify_mention) .setContentTitle(title); if (icon != null) { builder.setLargeIcon(icon); } if (preferences.getBoolean("notificationAlertSound", true)) { builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI); } if (preferences.getBoolean("notificationStyleVibrate", false)) { builder.setVibrate(new long[] { 500, 500 }); } if (preferences.getBoolean("notificationStyleLight", false)) { builder.setLights(0xFF00FF8F, 300, 1000); } for (int i = 0; i < mentions.size(); i++) { MentionResult mention = mentions.get(i); String text = truncateWithEllipses(mention.content, NOTIFICATION_CONTENT_LIMIT); builder.setContentText(text) .setNumber(i); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { builder.setVisibility(android.app.Notification.VISIBILITY_PRIVATE); builder.setCategory(android.app.Notification.CATEGORY_SOCIAL); } Intent resultIntent = new Intent(this, SplashActivity.class); TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); stackBuilder.addParentStack(SplashActivity.class); stackBuilder.addNextIntent(resultIntent); PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent(resultPendingIntent); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(NOTIFY_ID, builder.build()); } private void dismissStaleNotifications() { NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.cancel(NOTIFY_ID); } }