2017-04-28 13:29:42 +10:00
|
|
|
/* Copyright 2017 Andrew Dawson
|
|
|
|
*
|
|
|
|
* This file is a part of Tusky.
|
|
|
|
*
|
|
|
|
* This program 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 <http://www.gnu.org/licenses>. */
|
|
|
|
|
2017-05-05 08:55:34 +10:00
|
|
|
package com.keylesspalace.tusky.util;
|
2017-04-28 13:29:42 +10:00
|
|
|
|
2017-10-19 07:18:07 +11:00
|
|
|
import android.app.NotificationChannel;
|
2017-04-28 13:29:42 +10:00
|
|
|
import android.app.PendingIntent;
|
|
|
|
import android.content.Context;
|
|
|
|
import android.content.Intent;
|
|
|
|
import android.content.SharedPreferences;
|
|
|
|
import android.graphics.Bitmap;
|
2017-11-03 20:09:09 +11:00
|
|
|
import android.graphics.BitmapFactory;
|
2017-04-28 13:29:42 +10:00
|
|
|
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;
|
2017-10-19 07:18:07 +11:00
|
|
|
import android.support.v4.content.ContextCompat;
|
2017-05-24 05:34:31 +10:00
|
|
|
import android.util.Log;
|
2017-04-28 13:29:42 +10:00
|
|
|
|
2017-05-05 08:55:34 +10:00
|
|
|
import com.keylesspalace.tusky.MainActivity;
|
2017-11-14 05:05:23 +11:00
|
|
|
import com.keylesspalace.tusky.NotificationPullJobCreator;
|
2017-05-05 08:55:34 +10:00
|
|
|
import com.keylesspalace.tusky.R;
|
2017-04-28 13:29:42 +10:00
|
|
|
import com.keylesspalace.tusky.entity.Notification;
|
2017-05-15 20:05:10 +10:00
|
|
|
import com.keylesspalace.tusky.receiver.NotificationClearBroadcastReceiver;
|
|
|
|
import com.keylesspalace.tusky.view.RoundedTransformation;
|
2017-04-28 13:29:42 +10:00
|
|
|
import com.squareup.picasso.Picasso;
|
|
|
|
|
|
|
|
import org.json.JSONArray;
|
|
|
|
import org.json.JSONException;
|
|
|
|
|
2017-11-03 20:09:09 +11:00
|
|
|
import java.io.IOException;
|
2017-10-19 07:18:07 +11:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
|
|
|
|
2017-11-14 05:05:23 +11:00
|
|
|
public class NotificationManager {
|
|
|
|
private static final String TAG = "NotificationManager";
|
2017-05-24 05:34:31 +10:00
|
|
|
|
2017-11-14 05:05:23 +11:00
|
|
|
/**
|
|
|
|
* notification channels used on Android O+
|
|
|
|
**/
|
2017-10-19 07:18:07 +11:00
|
|
|
private static final String CHANNEL_MENTION = "CHANNEL_MENTION";
|
|
|
|
private static final String CHANNEL_FOLLOW = "CHANNEL_FOLLOW";
|
|
|
|
private static final String CHANNEL_BOOST = "CHANNEL_BOOST";
|
2017-11-14 05:05:23 +11:00
|
|
|
private static final String CHANNEL_FAVOURITE = " CHANNEL_FAVOURITE";
|
2017-10-19 07:18:07 +11:00
|
|
|
|
2017-07-28 12:03:45 +10:00
|
|
|
/**
|
|
|
|
* Takes a given Mastodon notification and either creates a new Android notification or updates
|
|
|
|
* the state of the existing notification to reflect the new interaction.
|
|
|
|
*
|
2017-11-14 05:05:23 +11:00
|
|
|
* @param context to access application preferences and services
|
2017-07-28 12:03:45 +10:00
|
|
|
* @param notifyId an arbitrary number to reference this notification for any future action
|
2017-11-14 05:05:23 +11:00
|
|
|
* @param body a new Mastodon notification
|
2017-07-28 12:03:45 +10:00
|
|
|
*/
|
2017-05-05 08:55:34 +10:00
|
|
|
public static void make(final Context context, final int notifyId, Notification body) {
|
2017-04-28 13:29:42 +10:00
|
|
|
final SharedPreferences preferences =
|
|
|
|
PreferenceManager.getDefaultSharedPreferences(context);
|
|
|
|
final SharedPreferences notificationPreferences = context.getSharedPreferences(
|
|
|
|
"Notifications", Context.MODE_PRIVATE);
|
|
|
|
|
|
|
|
if (!filterNotification(preferences, body)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-10-19 07:18:07 +11:00
|
|
|
createNotificationChannels(context);
|
|
|
|
|
2017-04-28 13:29:42 +10:00
|
|
|
String rawCurrentNotifications = notificationPreferences.getString("current", "[]");
|
|
|
|
JSONArray currentNotifications;
|
|
|
|
|
|
|
|
try {
|
|
|
|
currentNotifications = new JSONArray(rawCurrentNotifications);
|
|
|
|
} catch (JSONException e) {
|
|
|
|
currentNotifications = new JSONArray();
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean alreadyContains = false;
|
|
|
|
|
2017-11-14 05:05:23 +11:00
|
|
|
for (int i = 0; i < currentNotifications.length(); i++) {
|
2017-04-28 13:29:42 +10:00
|
|
|
try {
|
|
|
|
if (currentNotifications.getString(i).equals(body.account.getDisplayName())) {
|
|
|
|
alreadyContains = true;
|
|
|
|
}
|
|
|
|
} catch (JSONException e) {
|
2017-05-24 05:34:31 +10:00
|
|
|
Log.d(TAG, Log.getStackTraceString(e));
|
2017-04-28 13:29:42 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!alreadyContains) {
|
|
|
|
currentNotifications.put(body.account.getDisplayName());
|
|
|
|
}
|
|
|
|
|
|
|
|
notificationPreferences.edit()
|
|
|
|
.putString("current", currentNotifications.toString())
|
2017-11-14 05:05:23 +11:00
|
|
|
.apply();
|
2017-04-28 13:29:42 +10:00
|
|
|
|
|
|
|
Intent resultIntent = new Intent(context, MainActivity.class);
|
|
|
|
resultIntent.putExtra("tab_position", 1);
|
|
|
|
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
|
|
|
stackBuilder.addParentStack(MainActivity.class);
|
|
|
|
stackBuilder.addNextIntent(resultIntent);
|
2017-07-02 10:32:35 +10:00
|
|
|
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
|
|
|
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
2017-04-28 13:29:42 +10:00
|
|
|
|
|
|
|
Intent deleteIntent = new Intent(context, NotificationClearBroadcastReceiver.class);
|
2017-07-02 10:32:35 +10:00
|
|
|
PendingIntent deletePendingIntent = PendingIntent.getBroadcast(context, 0, deleteIntent,
|
|
|
|
PendingIntent.FLAG_CANCEL_CURRENT);
|
2017-04-28 13:29:42 +10:00
|
|
|
|
2017-10-19 07:18:07 +11:00
|
|
|
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, getChannelId(body))
|
2017-04-28 13:29:42 +10:00
|
|
|
.setSmallIcon(R.drawable.ic_notify)
|
|
|
|
.setContentIntent(resultPendingIntent)
|
|
|
|
.setDeleteIntent(deletePendingIntent)
|
2017-10-19 07:18:07 +11:00
|
|
|
.setColor(ContextCompat.getColor(context, (R.color.primary)))
|
2017-04-28 13:29:42 +10:00
|
|
|
.setDefaults(0); // So it doesn't ring twice, notify only in Target callback
|
|
|
|
|
2017-10-19 07:18:07 +11:00
|
|
|
setupPreferences(preferences, builder);
|
|
|
|
|
2017-04-28 13:29:42 +10:00
|
|
|
if (currentNotifications.length() == 1) {
|
|
|
|
builder.setContentTitle(titleForType(context, body))
|
|
|
|
.setContentText(truncateWithEllipses(bodyForType(body), 40));
|
|
|
|
|
2017-11-03 20:09:09 +11:00
|
|
|
//load the avatar synchronously
|
|
|
|
Bitmap accountAvatar;
|
|
|
|
try {
|
|
|
|
accountAvatar = Picasso.with(context)
|
|
|
|
.load(body.account.avatar)
|
|
|
|
.transform(new RoundedTransformation(7, 0))
|
|
|
|
.get();
|
|
|
|
} catch (IOException e) {
|
|
|
|
Log.d(TAG, "error loading account avatar", e);
|
|
|
|
accountAvatar = BitmapFactory.decodeResource(context.getResources(), R.drawable.avatar_default);
|
|
|
|
}
|
2017-04-28 13:29:42 +10:00
|
|
|
|
2017-11-03 20:09:09 +11:00
|
|
|
builder.setLargeIcon(accountAvatar);
|
2017-04-28 13:29:42 +10:00
|
|
|
|
|
|
|
} else {
|
|
|
|
try {
|
2017-07-02 10:32:35 +10:00
|
|
|
String format = context.getString(R.string.notification_title_summary);
|
|
|
|
String title = String.format(format, currentNotifications.length());
|
|
|
|
String text = truncateWithEllipses(joinNames(context, currentNotifications), 40);
|
|
|
|
builder.setContentTitle(title)
|
|
|
|
.setContentText(text);
|
2017-04-28 13:29:42 +10:00
|
|
|
} catch (JSONException e) {
|
2017-05-24 05:34:31 +10:00
|
|
|
Log.d(TAG, Log.getStackTraceString(e));
|
2017-04-28 13:29:42 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
|
|
builder.setVisibility(android.app.Notification.VISIBILITY_PRIVATE);
|
|
|
|
builder.setCategory(android.app.Notification.CATEGORY_SOCIAL);
|
|
|
|
}
|
|
|
|
|
2017-11-14 05:05:23 +11:00
|
|
|
android.app.NotificationManager notificationManager = (android.app.NotificationManager)
|
|
|
|
context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
|
|
//noinspection ConstantConditions
|
2017-07-02 10:32:35 +10:00
|
|
|
notificationManager.notify(notifyId, builder.build());
|
2017-04-28 13:29:42 +10:00
|
|
|
}
|
|
|
|
|
2017-10-22 06:28:05 +11:00
|
|
|
public static void createNotificationChannels(Context context) {
|
2017-11-14 05:05:23 +11:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
2017-10-19 07:18:07 +11:00
|
|
|
|
2017-11-14 05:05:23 +11:00
|
|
|
android.app.NotificationManager mNotificationManager =
|
|
|
|
(android.app.NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
2017-10-19 07:18:07 +11:00
|
|
|
|
|
|
|
String[] channelIds = new String[]{CHANNEL_MENTION, CHANNEL_FOLLOW, CHANNEL_BOOST, CHANNEL_FAVOURITE};
|
|
|
|
int[] channelNames = {
|
|
|
|
R.string.notification_channel_mention_name,
|
|
|
|
R.string.notification_channel_follow_name,
|
|
|
|
R.string.notification_channel_boost_name,
|
|
|
|
R.string.notification_channel_favourite_name
|
|
|
|
};
|
|
|
|
int[] channelDescriptions = {
|
|
|
|
R.string.notification_channel_mention_descriptions,
|
|
|
|
R.string.notification_channel_follow_description,
|
|
|
|
R.string.notification_channel_boost_description,
|
|
|
|
R.string.notification_channel_favourite_description
|
|
|
|
};
|
|
|
|
|
|
|
|
List<NotificationChannel> channels = new ArrayList<>(4);
|
|
|
|
|
2017-11-14 05:05:23 +11:00
|
|
|
for (int i = 0; i < channelIds.length; i++) {
|
2017-10-19 07:18:07 +11:00
|
|
|
String id = channelIds[i];
|
|
|
|
String name = context.getString(channelNames[i]);
|
|
|
|
String description = context.getString(channelDescriptions[i]);
|
2017-11-14 05:05:23 +11:00
|
|
|
int importance = android.app.NotificationManager.IMPORTANCE_DEFAULT;
|
2017-10-19 07:18:07 +11:00
|
|
|
NotificationChannel channel = new NotificationChannel(id, name, importance);
|
|
|
|
|
|
|
|
channel.setDescription(description);
|
|
|
|
channel.enableLights(true);
|
|
|
|
channel.enableVibration(true);
|
|
|
|
channel.setShowBadge(true);
|
|
|
|
channels.add(channel);
|
|
|
|
}
|
|
|
|
|
2017-11-14 05:05:23 +11:00
|
|
|
//noinspection ConstantConditions
|
2017-10-19 07:18:07 +11:00
|
|
|
mNotificationManager.createNotificationChannels(channels);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-14 05:05:23 +11:00
|
|
|
public static void clearNotifications(Context context) {
|
|
|
|
SharedPreferences notificationPreferences =
|
|
|
|
context.getSharedPreferences("Notifications", Context.MODE_PRIVATE);
|
|
|
|
notificationPreferences.edit().putString("current", "[]").apply();
|
|
|
|
|
|
|
|
android.app.NotificationManager manager = (android.app.NotificationManager)
|
|
|
|
context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
|
|
//noinspection ConstantConditions
|
|
|
|
manager.cancel(NotificationPullJobCreator.NOTIFY_ID);
|
|
|
|
}
|
|
|
|
|
2017-04-28 13:29:42 +10:00
|
|
|
private static boolean filterNotification(SharedPreferences preferences,
|
2017-11-14 05:05:23 +11:00
|
|
|
Notification notification) {
|
2017-10-19 07:18:07 +11:00
|
|
|
|
2017-11-14 05:05:23 +11:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
2017-10-19 07:18:07 +11:00
|
|
|
return true; //do not filter on Android O or newer, the system does it for us
|
|
|
|
}
|
|
|
|
|
2017-11-14 05:05:23 +11:00
|
|
|
switch (notification.type) {
|
2017-04-28 13:29:42 +10:00
|
|
|
default:
|
2017-07-02 10:32:35 +10:00
|
|
|
case MENTION:
|
2017-04-28 13:29:42 +10:00
|
|
|
return preferences.getBoolean("notificationFilterMentions", true);
|
2017-07-02 10:32:35 +10:00
|
|
|
case FOLLOW:
|
2017-04-28 13:29:42 +10:00
|
|
|
return preferences.getBoolean("notificationFilterFollows", true);
|
2017-07-02 10:32:35 +10:00
|
|
|
case REBLOG:
|
2017-04-28 13:29:42 +10:00
|
|
|
return preferences.getBoolean("notificationFilterReblogs", true);
|
2017-07-02 10:32:35 +10:00
|
|
|
case FAVOURITE:
|
2017-04-28 13:29:42 +10:00
|
|
|
return preferences.getBoolean("notificationFilterFavourites", true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-19 07:18:07 +11:00
|
|
|
private static String getChannelId(Notification notification) {
|
2017-11-14 05:05:23 +11:00
|
|
|
switch (notification.type) {
|
|
|
|
default:
|
|
|
|
case MENTION:
|
|
|
|
return CHANNEL_MENTION;
|
|
|
|
case FOLLOW:
|
|
|
|
return CHANNEL_FOLLOW;
|
|
|
|
case REBLOG:
|
|
|
|
return CHANNEL_BOOST;
|
|
|
|
case FAVOURITE:
|
|
|
|
return CHANNEL_FAVOURITE;
|
|
|
|
}
|
2017-10-19 07:18:07 +11:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-11-14 05:05:23 +11:00
|
|
|
@SuppressWarnings("SameParameterValue")
|
2017-04-28 13:29:42 +10:00
|
|
|
private static String truncateWithEllipses(String string, int limit) {
|
|
|
|
if (string.length() < limit) {
|
|
|
|
return string;
|
|
|
|
} else {
|
|
|
|
return string.substring(0, limit - 3) + "...";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void setupPreferences(SharedPreferences preferences,
|
2017-11-14 05:05:23 +11:00
|
|
|
NotificationCompat.Builder builder) {
|
2017-10-19 07:18:07 +11:00
|
|
|
|
2017-11-14 05:05:23 +11:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
2017-10-19 07:18:07 +11:00
|
|
|
return; //do nothing on Android O or newer, the system uses the channel settings anyway
|
|
|
|
}
|
|
|
|
|
2017-04-28 13:29:42 +10:00
|
|
|
if (preferences.getBoolean("notificationAlertSound", true)) {
|
|
|
|
builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (preferences.getBoolean("notificationAlertVibrate", false)) {
|
2017-11-14 05:05:23 +11:00
|
|
|
builder.setVibrate(new long[]{500, 500});
|
2017-04-28 13:29:42 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
if (preferences.getBoolean("notificationAlertLight", false)) {
|
|
|
|
builder.setLights(0xFF00FF8F, 300, 1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nullable
|
|
|
|
private static String joinNames(Context context, JSONArray array) throws JSONException {
|
|
|
|
if (array.length() > 3) {
|
2017-07-02 10:32:35 +10:00
|
|
|
return String.format(context.getString(R.string.notification_summary_large),
|
|
|
|
array.get(0), array.get(1), array.get(2), array.length() - 3);
|
2017-04-28 13:29:42 +10:00
|
|
|
} else if (array.length() == 3) {
|
2017-07-02 10:32:35 +10:00
|
|
|
return String.format(context.getString(R.string.notification_summary_medium),
|
|
|
|
array.get(0), array.get(1), array.get(2));
|
2017-04-28 13:29:42 +10:00
|
|
|
} else if (array.length() == 2) {
|
2017-07-02 10:32:35 +10:00
|
|
|
return String.format(context.getString(R.string.notification_summary_small),
|
|
|
|
array.get(0), array.get(1));
|
2017-04-28 13:29:42 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nullable
|
|
|
|
private static String titleForType(Context context, Notification notification) {
|
|
|
|
switch (notification.type) {
|
|
|
|
case MENTION:
|
2017-07-02 10:32:35 +10:00
|
|
|
return String.format(context.getString(R.string.notification_mention_format),
|
|
|
|
notification.account.getDisplayName());
|
2017-04-28 13:29:42 +10:00
|
|
|
case FOLLOW:
|
2017-07-02 10:32:35 +10:00
|
|
|
return String.format(context.getString(R.string.notification_follow_format),
|
|
|
|
notification.account.getDisplayName());
|
2017-04-28 13:29:42 +10:00
|
|
|
case FAVOURITE:
|
2017-07-02 10:32:35 +10:00
|
|
|
return String.format(context.getString(R.string.notification_favourite_format),
|
|
|
|
notification.account.getDisplayName());
|
2017-04-28 13:29:42 +10:00
|
|
|
case REBLOG:
|
2017-07-02 10:32:35 +10:00
|
|
|
return String.format(context.getString(R.string.notification_reblog_format),
|
|
|
|
notification.account.getDisplayName());
|
2017-04-28 13:29:42 +10:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nullable
|
|
|
|
private static String bodyForType(Notification notification) {
|
|
|
|
switch (notification.type) {
|
|
|
|
case FOLLOW:
|
|
|
|
return notification.account.username;
|
|
|
|
case MENTION:
|
|
|
|
case FAVOURITE:
|
|
|
|
case REBLOG:
|
|
|
|
return notification.status.content.toString();
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2017-10-19 07:18:07 +11:00
|
|
|
|
2017-04-28 13:29:42 +10:00
|
|
|
}
|