2018-05-06 19:07:10 +10:00
|
|
|
/* Copyright 2018 Jeremiasz Nelz <remi6397(a)gmail.com>
|
|
|
|
* Copyright 2017 Andrew Dawson
|
2017-04-28 13:29:42 +10:00
|
|
|
*
|
|
|
|
* 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;
|
2018-02-04 08:45:14 +11:00
|
|
|
import android.app.NotificationChannelGroup;
|
2018-02-13 08:03:08 +11:00
|
|
|
import android.app.NotificationManager;
|
2017-04-28 13:29:42 +10:00
|
|
|
import android.app.PendingIntent;
|
|
|
|
import android.content.Context;
|
|
|
|
import android.content.Intent;
|
|
|
|
import android.graphics.Bitmap;
|
2017-11-03 20:09:09 +11:00
|
|
|
import android.graphics.BitmapFactory;
|
2018-05-10 07:31:47 +10:00
|
|
|
import android.graphics.Color;
|
2017-04-28 13:29:42 +10:00
|
|
|
import android.os.Build;
|
|
|
|
import android.provider.Settings;
|
2018-12-18 01:25:35 +11:00
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import androidx.annotation.Nullable;
|
|
|
|
import androidx.core.app.NotificationCompat;
|
|
|
|
import androidx.core.app.NotificationManagerCompat;
|
|
|
|
import androidx.core.app.RemoteInput;
|
|
|
|
import androidx.core.app.TaskStackBuilder;
|
|
|
|
import androidx.core.content.ContextCompat;
|
|
|
|
import androidx.core.text.BidiFormatter;
|
2019-05-03 03:44:35 +10:00
|
|
|
|
|
|
|
import android.text.TextUtils;
|
2017-05-24 05:34:31 +10:00
|
|
|
import android.util.Log;
|
2017-04-28 13:29:42 +10:00
|
|
|
|
2019-04-17 05:39:12 +10:00
|
|
|
import com.bumptech.glide.Glide;
|
|
|
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
|
|
|
import com.bumptech.glide.request.FutureTarget;
|
2018-11-13 07:09:39 +11:00
|
|
|
import com.evernote.android.job.JobManager;
|
|
|
|
import com.evernote.android.job.JobRequest;
|
2018-05-10 07:31:47 +10:00
|
|
|
import com.keylesspalace.tusky.BuildConfig;
|
2017-05-05 08:55:34 +10:00
|
|
|
import com.keylesspalace.tusky.MainActivity;
|
|
|
|
import com.keylesspalace.tusky.R;
|
2018-02-04 08:45:14 +11:00
|
|
|
import com.keylesspalace.tusky.db.AccountEntity;
|
|
|
|
import com.keylesspalace.tusky.db.AccountManager;
|
2017-04-28 13:29:42 +10:00
|
|
|
import com.keylesspalace.tusky.entity.Notification;
|
2019-05-03 03:44:35 +10:00
|
|
|
import com.keylesspalace.tusky.entity.Poll;
|
|
|
|
import com.keylesspalace.tusky.entity.PollOption;
|
2018-05-06 19:07:10 +10:00
|
|
|
import com.keylesspalace.tusky.entity.Status;
|
2017-05-15 20:05:10 +10:00
|
|
|
import com.keylesspalace.tusky.receiver.NotificationClearBroadcastReceiver;
|
2018-05-06 19:07:10 +10:00
|
|
|
import com.keylesspalace.tusky.receiver.SendStatusBroadcastReceiver;
|
2017-04-28 13:29:42 +10:00
|
|
|
|
|
|
|
import org.json.JSONArray;
|
|
|
|
import org.json.JSONException;
|
|
|
|
|
2017-10-19 07:18:07 +11:00
|
|
|
import java.util.ArrayList;
|
2018-05-06 19:07:10 +10:00
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.LinkedHashSet;
|
2017-10-19 07:18:07 +11:00
|
|
|
import java.util.List;
|
2019-04-17 05:39:12 +10:00
|
|
|
import java.util.concurrent.ExecutionException;
|
2017-10-19 07:18:07 +11:00
|
|
|
|
2018-02-13 08:03:08 +11:00
|
|
|
public class NotificationHelper {
|
2018-02-04 08:45:14 +11:00
|
|
|
|
2018-05-06 19:07:10 +10:00
|
|
|
private static int notificationId = 0;
|
|
|
|
|
2018-03-10 08:02:32 +11:00
|
|
|
/**
|
|
|
|
* constants used in Intents
|
|
|
|
*/
|
2018-02-04 08:45:14 +11:00
|
|
|
public static final String ACCOUNT_ID = "account_id";
|
|
|
|
|
2018-02-13 08:03:08 +11:00
|
|
|
private static final String TAG = "NotificationHelper";
|
2017-05-24 05:34:31 +10:00
|
|
|
|
2018-05-06 19:07:10 +10:00
|
|
|
public static final String REPLY_ACTION = "REPLY_ACTION";
|
|
|
|
|
|
|
|
public static final String COMPOSE_ACTION = "COMPOSE_ACTION";
|
|
|
|
|
|
|
|
public static final String KEY_REPLY = "KEY_REPLY";
|
|
|
|
|
|
|
|
public static final String KEY_SENDER_ACCOUNT_ID = "KEY_SENDER_ACCOUNT_ID";
|
|
|
|
|
|
|
|
public static final String KEY_SENDER_ACCOUNT_IDENTIFIER = "KEY_SENDER_ACCOUNT_IDENTIFIER";
|
|
|
|
|
|
|
|
public static final String KEY_SENDER_ACCOUNT_FULL_NAME = "KEY_SENDER_ACCOUNT_FULL_NAME";
|
|
|
|
|
|
|
|
public static final String KEY_NOTIFICATION_ID = "KEY_NOTIFICATION_ID";
|
|
|
|
|
|
|
|
public static final String KEY_CITED_STATUS_ID = "KEY_CITED_STATUS_ID";
|
|
|
|
|
|
|
|
public static final String KEY_VISIBILITY = "KEY_VISIBILITY";
|
|
|
|
|
|
|
|
public static final String KEY_SPOILER = "KEY_SPOILER";
|
|
|
|
|
|
|
|
public static final String KEY_MENTIONS = "KEY_MENTIONS";
|
|
|
|
|
|
|
|
public static final String KEY_CITED_TEXT = "KEY_CITED_TEXT";
|
|
|
|
|
|
|
|
public static final String KEY_CITED_AUTHOR_LOCAL = "KEY_CITED_AUTHOR_LOCAL";
|
|
|
|
|
2018-03-10 08:02:32 +11:00
|
|
|
/**
|
|
|
|
* notification channels used on Android O+
|
|
|
|
**/
|
2018-05-06 19:07:10 +10:00
|
|
|
public static final String CHANNEL_MENTION = "CHANNEL_MENTION";
|
|
|
|
public static final String CHANNEL_FOLLOW = "CHANNEL_FOLLOW";
|
|
|
|
public static final String CHANNEL_BOOST = "CHANNEL_BOOST";
|
|
|
|
public static final String CHANNEL_FAVOURITE = "CHANNEL_FAVOURITE";
|
2019-05-03 03:44:35 +10:00
|
|
|
public static final String CHANNEL_POLL = "CHANNEL_POLL";
|
2017-10-19 07:18:07 +11:00
|
|
|
|
2018-11-13 07:09:39 +11:00
|
|
|
/**
|
|
|
|
* time in minutes between notification checks
|
|
|
|
* note that this cannot be less than 15 minutes due to Android battery saving constraints
|
|
|
|
*/
|
|
|
|
private static final int NOTIFICATION_CHECK_INTERVAL_MINUTES = 15;
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
*
|
2018-03-10 08:02:32 +11:00
|
|
|
* @param context to access application preferences and services
|
|
|
|
* @param body a new Mastodon notification
|
|
|
|
* @param account the account for which the notification should be shown
|
2017-07-28 12:03:45 +10:00
|
|
|
*/
|
2017-04-28 13:29:42 +10:00
|
|
|
|
2018-06-25 22:08:36 +10:00
|
|
|
public static void make(final Context context, Notification body, AccountEntity account, boolean isFirstOfBatch) {
|
2018-02-04 08:45:14 +11:00
|
|
|
|
2018-02-13 08:03:08 +11:00
|
|
|
if (!filterNotification(account, body, context)) {
|
2017-04-28 13:29:42 +10:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-02-04 08:45:14 +11:00
|
|
|
String rawCurrentNotifications = account.getActiveNotifications();
|
2017-04-28 13:29:42 +10:00
|
|
|
JSONArray currentNotifications;
|
2018-05-25 03:00:17 +10:00
|
|
|
BidiFormatter bidiFormatter = BidiFormatter.getInstance();
|
2017-04-28 13:29:42 +10:00
|
|
|
|
|
|
|
try {
|
|
|
|
currentNotifications = new JSONArray(rawCurrentNotifications);
|
|
|
|
} catch (JSONException e) {
|
|
|
|
currentNotifications = new JSONArray();
|
|
|
|
}
|
|
|
|
|
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 {
|
2018-03-03 23:24:03 +11:00
|
|
|
if (currentNotifications.getString(i).equals(body.getAccount().getName())) {
|
2018-04-14 21:56:48 +10:00
|
|
|
currentNotifications.remove(i);
|
|
|
|
break;
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-14 21:56:48 +10:00
|
|
|
currentNotifications.put(body.getAccount().getName());
|
2017-04-28 13:29:42 +10:00
|
|
|
|
2018-02-04 08:45:14 +11:00
|
|
|
account.setActiveNotifications(currentNotifications.toString());
|
|
|
|
|
2018-05-06 19:07:10 +10:00
|
|
|
// Notification group member
|
|
|
|
// =========================
|
|
|
|
final NotificationCompat.Builder builder = newNotification(context, body, account, false);
|
2017-04-28 13:29:42 +10:00
|
|
|
|
2018-05-06 19:07:10 +10:00
|
|
|
notificationId++;
|
2017-04-28 13:29:42 +10:00
|
|
|
|
2019-05-03 03:44:35 +10:00
|
|
|
builder.setContentTitle(titleForType(context, body, bidiFormatter, account))
|
|
|
|
.setContentText(bodyForType(body, context));
|
2017-04-28 13:29:42 +10:00
|
|
|
|
2019-05-03 03:44:35 +10:00
|
|
|
if (body.getType() == Notification.Type.MENTION || body.getType() == Notification.Type.POLL) {
|
2018-05-06 19:07:10 +10:00
|
|
|
builder.setStyle(new NotificationCompat.BigTextStyle()
|
2019-05-03 03:44:35 +10:00
|
|
|
.bigText(bodyForType(body, context)));
|
2018-05-06 19:07:10 +10:00
|
|
|
}
|
2017-04-28 13:29:42 +10:00
|
|
|
|
2018-05-06 19:07:10 +10:00
|
|
|
//load the avatar synchronously
|
|
|
|
Bitmap accountAvatar;
|
|
|
|
try {
|
2019-04-17 05:39:12 +10:00
|
|
|
FutureTarget<Bitmap> target = Glide.with(context)
|
|
|
|
.asBitmap()
|
2018-05-06 19:07:10 +10:00
|
|
|
.load(body.getAccount().getAvatar())
|
2019-04-17 05:39:12 +10:00
|
|
|
.transform(new RoundedCorners(20))
|
|
|
|
.submit();
|
|
|
|
|
|
|
|
accountAvatar = target.get();
|
|
|
|
} catch (ExecutionException | InterruptedException e) {
|
2018-05-06 19:07:10 +10:00
|
|
|
Log.d(TAG, "error loading account avatar", e);
|
|
|
|
accountAvatar = BitmapFactory.decodeResource(context.getResources(), R.drawable.avatar_default);
|
|
|
|
}
|
2017-10-19 07:18:07 +11:00
|
|
|
|
2018-05-06 19:07:10 +10:00
|
|
|
builder.setLargeIcon(accountAvatar);
|
2018-02-04 08:45:14 +11:00
|
|
|
|
2018-05-06 19:07:10 +10:00
|
|
|
// Reply to mention action; RemoteInput is available from KitKat Watch, but buttons are available from Nougat
|
|
|
|
if (body.getType() == Notification.Type.MENTION
|
|
|
|
&& android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
|
|
RemoteInput replyRemoteInput = new RemoteInput.Builder(KEY_REPLY)
|
|
|
|
.setLabel(context.getString(R.string.label_quick_reply))
|
|
|
|
.build();
|
2017-04-28 13:29:42 +10:00
|
|
|
|
2018-05-06 19:07:10 +10:00
|
|
|
PendingIntent quickReplyPendingIntent = getStatusReplyIntent(REPLY_ACTION, context, body, account);
|
2017-04-28 13:29:42 +10:00
|
|
|
|
2018-05-06 19:07:10 +10:00
|
|
|
NotificationCompat.Action quickReplyAction =
|
|
|
|
new NotificationCompat.Action.Builder(R.drawable.ic_reply_24dp,
|
|
|
|
context.getString(R.string.action_quick_reply), quickReplyPendingIntent)
|
|
|
|
.addRemoteInput(replyRemoteInput)
|
|
|
|
.build();
|
2017-04-28 13:29:42 +10:00
|
|
|
|
2018-05-06 19:07:10 +10:00
|
|
|
builder.addAction(quickReplyAction);
|
|
|
|
|
|
|
|
PendingIntent composePendingIntent = getStatusReplyIntent(COMPOSE_ACTION, context, body, account);
|
|
|
|
|
|
|
|
NotificationCompat.Action composeAction =
|
|
|
|
new NotificationCompat.Action.Builder(R.drawable.ic_reply_24dp,
|
|
|
|
context.getString(R.string.action_compose_shortcut), composePendingIntent)
|
|
|
|
.build();
|
|
|
|
|
|
|
|
builder.addAction(composeAction);
|
|
|
|
}
|
|
|
|
|
|
|
|
builder.setSubText(account.getFullName());
|
|
|
|
builder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
|
|
|
|
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL);
|
|
|
|
builder.setOnlyAlertOnce(true);
|
|
|
|
|
2018-06-25 22:08:36 +10:00
|
|
|
// only alert for the first notification of a batch to avoid multiple alerts at once
|
|
|
|
if(!isFirstOfBatch) {
|
|
|
|
builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY);
|
|
|
|
}
|
|
|
|
|
2018-05-06 19:07:10 +10:00
|
|
|
// Summary
|
|
|
|
// =======
|
|
|
|
final NotificationCompat.Builder summaryBuilder = newNotification(context, body, account, true);
|
|
|
|
|
|
|
|
if (currentNotifications.length() != 1) {
|
2017-04-28 13:29:42 +10:00
|
|
|
try {
|
2018-03-02 05:05:47 +11:00
|
|
|
String title = context.getString(R.string.notification_title_summary, currentNotifications.length());
|
2018-05-25 03:00:17 +10:00
|
|
|
String text = joinNames(context, currentNotifications, bidiFormatter);
|
2018-05-06 19:07:10 +10:00
|
|
|
summaryBuilder.setContentTitle(title)
|
2017-07-02 10:32:35 +10:00
|
|
|
.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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-06 19:07:10 +10:00
|
|
|
summaryBuilder.setSubText(account.getFullName());
|
|
|
|
summaryBuilder.setVisibility(NotificationCompat.VISIBILITY_PRIVATE);
|
|
|
|
summaryBuilder.setCategory(NotificationCompat.CATEGORY_SOCIAL);
|
|
|
|
summaryBuilder.setOnlyAlertOnce(true);
|
|
|
|
summaryBuilder.setGroupSummary(true);
|
2018-02-04 08:45:14 +11:00
|
|
|
|
2018-05-06 19:07:10 +10:00
|
|
|
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
2017-04-28 13:29:42 +10:00
|
|
|
|
2018-05-06 19:07:10 +10:00
|
|
|
notificationManager.notify(notificationId, builder.build());
|
|
|
|
if (currentNotifications.length() == 1) {
|
|
|
|
notificationManager.notify((int) account.getId(), builder.setGroupSummary(true).build());
|
|
|
|
} else {
|
|
|
|
notificationManager.notify((int) account.getId(), summaryBuilder.build());
|
|
|
|
}
|
|
|
|
}
|
2018-03-02 05:05:47 +11:00
|
|
|
|
2018-05-06 19:07:10 +10:00
|
|
|
private static NotificationCompat.Builder newNotification(Context context, Notification body, AccountEntity account, boolean summary) {
|
|
|
|
Intent summaryResultIntent = new Intent(context, MainActivity.class);
|
|
|
|
summaryResultIntent.putExtra(ACCOUNT_ID, account.getId());
|
|
|
|
TaskStackBuilder summaryStackBuilder = TaskStackBuilder.create(context);
|
|
|
|
summaryStackBuilder.addParentStack(MainActivity.class);
|
|
|
|
summaryStackBuilder.addNextIntent(summaryResultIntent);
|
2018-02-04 08:45:14 +11:00
|
|
|
|
2018-05-06 19:07:10 +10:00
|
|
|
PendingIntent summaryResultPendingIntent = summaryStackBuilder.getPendingIntent(notificationId,
|
|
|
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
|
|
|
|
|
|
|
// we have to switch account here
|
2018-05-09 04:58:06 +10:00
|
|
|
Intent eventResultIntent = new Intent(context, MainActivity.class);
|
|
|
|
eventResultIntent.putExtra(ACCOUNT_ID, account.getId());
|
2018-05-06 19:07:10 +10:00
|
|
|
TaskStackBuilder eventStackBuilder = TaskStackBuilder.create(context);
|
2018-05-09 04:58:06 +10:00
|
|
|
eventStackBuilder.addParentStack(MainActivity.class);
|
2018-05-06 19:07:10 +10:00
|
|
|
eventStackBuilder.addNextIntent(eventResultIntent);
|
|
|
|
|
|
|
|
PendingIntent eventResultPendingIntent = eventStackBuilder.getPendingIntent((int) account.getId(),
|
|
|
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
|
|
|
|
|
|
|
Intent deleteIntent = new Intent(context, NotificationClearBroadcastReceiver.class);
|
|
|
|
deleteIntent.putExtra(ACCOUNT_ID, account.getId());
|
|
|
|
PendingIntent deletePendingIntent = PendingIntent.getBroadcast(context, summary ? (int) account.getId() : notificationId, deleteIntent,
|
|
|
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
|
|
|
|
|
|
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, getChannelId(account, body))
|
|
|
|
.setSmallIcon(R.drawable.ic_notify)
|
|
|
|
.setContentIntent(summary ? summaryResultPendingIntent : eventResultPendingIntent)
|
|
|
|
.setDeleteIntent(deletePendingIntent)
|
2018-12-18 01:25:35 +11:00
|
|
|
.setColor(BuildConfig.DEBUG ? Color.parseColor("#19A341") : ContextCompat.getColor(context, R.color.tusky_blue))
|
2018-05-06 19:07:10 +10:00
|
|
|
.setGroup(account.getAccountId())
|
2018-05-09 03:15:10 +10:00
|
|
|
.setAutoCancel(true)
|
2018-05-06 19:07:10 +10:00
|
|
|
.setDefaults(0); // So it doesn't ring twice, notify only in Target callback
|
|
|
|
|
|
|
|
setupPreferences(account, builder);
|
|
|
|
|
|
|
|
return builder;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static PendingIntent getStatusReplyIntent(String action, Context context, Notification body, AccountEntity account) {
|
|
|
|
Status status = body.getStatus();
|
|
|
|
|
|
|
|
String citedLocalAuthor = status.getAccount().getLocalUsername();
|
|
|
|
String citedText = status.getContent().toString();
|
|
|
|
String inReplyToId = status.getId();
|
|
|
|
Status actionableStatus = status.getActionableStatus();
|
|
|
|
Status.Visibility replyVisibility = actionableStatus.getVisibility();
|
|
|
|
String contentWarning = actionableStatus.getSpoilerText();
|
|
|
|
Status.Mention[] mentions = actionableStatus.getMentions();
|
|
|
|
List<String> mentionedUsernames = new ArrayList<>();
|
|
|
|
mentionedUsernames.add(actionableStatus.getAccount().getUsername());
|
|
|
|
for (Status.Mention mention : mentions) {
|
|
|
|
mentionedUsernames.add(mention.getUsername());
|
|
|
|
}
|
|
|
|
mentionedUsernames.removeAll(Collections.singleton(account.getUsername()));
|
|
|
|
mentionedUsernames = new ArrayList<>(new LinkedHashSet<>(mentionedUsernames));
|
|
|
|
|
|
|
|
Intent replyIntent = new Intent(context, SendStatusBroadcastReceiver.class)
|
|
|
|
.setAction(action)
|
|
|
|
.putExtra(KEY_CITED_AUTHOR_LOCAL, citedLocalAuthor)
|
|
|
|
.putExtra(KEY_CITED_TEXT, citedText)
|
|
|
|
.putExtra(KEY_SENDER_ACCOUNT_ID, account.getId())
|
|
|
|
.putExtra(KEY_SENDER_ACCOUNT_IDENTIFIER, account.getIdentifier())
|
|
|
|
.putExtra(KEY_SENDER_ACCOUNT_FULL_NAME, account.getFullName())
|
|
|
|
.putExtra(KEY_NOTIFICATION_ID, notificationId)
|
|
|
|
.putExtra(KEY_CITED_STATUS_ID, inReplyToId)
|
|
|
|
.putExtra(KEY_VISIBILITY, replyVisibility)
|
|
|
|
.putExtra(KEY_SPOILER, contentWarning)
|
|
|
|
.putExtra(KEY_MENTIONS, mentionedUsernames.toArray(new String[0]));
|
|
|
|
|
|
|
|
return PendingIntent.getBroadcast(context.getApplicationContext(),
|
|
|
|
notificationId,
|
|
|
|
replyIntent,
|
|
|
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
2017-04-28 13:29:42 +10:00
|
|
|
}
|
|
|
|
|
2018-07-24 05:59:10 +10:00
|
|
|
public static void createNotificationChannelsForAccount(@NonNull AccountEntity account, @NonNull 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
|
|
|
|
2018-02-13 08:03:08 +11:00
|
|
|
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
2017-10-19 07:18:07 +11:00
|
|
|
|
2018-02-04 08:45:14 +11:00
|
|
|
String[] channelIds = new String[]{
|
2018-03-10 08:02:32 +11:00
|
|
|
CHANNEL_MENTION + account.getIdentifier(),
|
|
|
|
CHANNEL_FOLLOW + account.getIdentifier(),
|
|
|
|
CHANNEL_BOOST + account.getIdentifier(),
|
2019-05-03 03:44:35 +10:00
|
|
|
CHANNEL_FAVOURITE + account.getIdentifier(),
|
|
|
|
CHANNEL_POLL + account.getIdentifier(),
|
|
|
|
};
|
2017-10-19 07:18:07 +11:00
|
|
|
int[] channelNames = {
|
2019-05-03 03:44:35 +10:00
|
|
|
R.string.notification_mention_name,
|
|
|
|
R.string.notification_follow_name,
|
|
|
|
R.string.notification_boost_name,
|
|
|
|
R.string.notification_favourite_name,
|
|
|
|
R.string.notification_poll_name
|
2017-10-19 07:18:07 +11:00
|
|
|
};
|
|
|
|
int[] channelDescriptions = {
|
2019-05-03 03:44:35 +10:00
|
|
|
R.string.notification_mention_descriptions,
|
|
|
|
R.string.notification_follow_description,
|
|
|
|
R.string.notification_boost_description,
|
|
|
|
R.string.notification_favourite_description,
|
|
|
|
R.string.notification_poll_description
|
2017-10-19 07:18:07 +11:00
|
|
|
};
|
|
|
|
|
2019-05-03 03:44:35 +10:00
|
|
|
List<NotificationChannel> channels = new ArrayList<>(5);
|
2017-10-19 07:18:07 +11:00
|
|
|
|
2018-02-04 08:45:14 +11:00
|
|
|
NotificationChannelGroup channelGroup = new NotificationChannelGroup(account.getIdentifier(), account.getFullName());
|
|
|
|
|
|
|
|
//noinspection ConstantConditions
|
2018-02-13 08:03:08 +11:00
|
|
|
notificationManager.createNotificationChannelGroup(channelGroup);
|
2018-02-04 08:45:14 +11:00
|
|
|
|
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]);
|
2018-02-13 08:03:08 +11:00
|
|
|
int importance = NotificationManager.IMPORTANCE_DEFAULT;
|
2017-10-19 07:18:07 +11:00
|
|
|
NotificationChannel channel = new NotificationChannel(id, name, importance);
|
|
|
|
|
|
|
|
channel.setDescription(description);
|
|
|
|
channel.enableLights(true);
|
2018-02-13 08:03:08 +11:00
|
|
|
channel.setLightColor(0xFF2B90D9);
|
2017-10-19 07:18:07 +11:00
|
|
|
channel.enableVibration(true);
|
|
|
|
channel.setShowBadge(true);
|
2018-02-04 08:45:14 +11:00
|
|
|
channel.setGroup(account.getIdentifier());
|
2017-10-19 07:18:07 +11:00
|
|
|
channels.add(channel);
|
|
|
|
}
|
|
|
|
|
2018-02-13 08:03:08 +11:00
|
|
|
notificationManager.createNotificationChannels(channels);
|
2017-10-19 07:18:07 +11:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-24 05:59:10 +10:00
|
|
|
public static void deleteNotificationChannelsForAccount(@NonNull AccountEntity account, @NonNull Context context) {
|
2018-02-04 08:45:14 +11:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
|
|
|
2018-02-13 08:03:08 +11:00
|
|
|
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
2018-02-04 08:45:14 +11:00
|
|
|
|
|
|
|
//noinspection ConstantConditions
|
2018-02-13 08:03:08 +11:00
|
|
|
notificationManager.deleteNotificationChannelGroup(account.getIdentifier());
|
2018-02-04 08:45:14 +11:00
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-24 05:59:10 +10:00
|
|
|
public static void deleteLegacyNotificationChannels(@NonNull Context context, @NonNull AccountManager accountManager) {
|
2018-02-04 08:45:14 +11:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
|
|
|
2018-02-13 08:03:08 +11:00
|
|
|
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
2018-02-04 08:45:14 +11:00
|
|
|
|
2018-05-09 06:35:25 +10:00
|
|
|
// used until Tusky 1.4
|
2018-02-04 08:45:14 +11:00
|
|
|
//noinspection ConstantConditions
|
2018-02-13 08:03:08 +11:00
|
|
|
notificationManager.deleteNotificationChannel(CHANNEL_MENTION);
|
|
|
|
notificationManager.deleteNotificationChannel(CHANNEL_FAVOURITE);
|
|
|
|
notificationManager.deleteNotificationChannel(CHANNEL_BOOST);
|
|
|
|
notificationManager.deleteNotificationChannel(CHANNEL_FOLLOW);
|
2018-05-09 06:35:25 +10:00
|
|
|
|
|
|
|
// used until Tusky 1.7
|
|
|
|
for(AccountEntity account: accountManager.getAllAccountsOrderedByActive()) {
|
|
|
|
notificationManager.deleteNotificationChannel(CHANNEL_FAVOURITE+" "+account.getIdentifier());
|
|
|
|
}
|
2018-02-04 08:45:14 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-24 05:59:10 +10:00
|
|
|
public static boolean areNotificationsEnabled(@NonNull Context context, @NonNull AccountManager accountManager) {
|
2018-03-10 08:02:32 +11:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
2018-02-13 08:03:08 +11:00
|
|
|
|
|
|
|
// on Android >= O, notifications are enabled, if at least one channel is enabled
|
|
|
|
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
|
|
|
|
|
|
//noinspection ConstantConditions
|
2018-03-10 08:02:32 +11:00
|
|
|
if (notificationManager.areNotificationsEnabled()) {
|
|
|
|
for (NotificationChannel channel : notificationManager.getNotificationChannels()) {
|
|
|
|
if (channel.getImportance() > NotificationManager.IMPORTANCE_NONE) {
|
2018-02-13 08:03:08 +11:00
|
|
|
Log.d(TAG, "NotificationsEnabled");
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Log.d(TAG, "NotificationsDisabled");
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// on Android < O, notifications are enabled, if at least one account has notification enabled
|
2018-07-24 05:59:10 +10:00
|
|
|
return accountManager.areNotificationsEnabled();
|
2018-02-13 08:03:08 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-11-13 07:09:39 +11:00
|
|
|
public static void enablePullNotifications() {
|
|
|
|
long checkInterval = 1000 * 60 * NOTIFICATION_CHECK_INTERVAL_MINUTES;
|
|
|
|
|
|
|
|
new JobRequest.Builder(NotificationPullJobCreator.NOTIFICATIONS_JOB_TAG)
|
|
|
|
.setPeriodic(checkInterval)
|
|
|
|
.setUpdateCurrent(true)
|
|
|
|
.setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
|
|
|
|
.build()
|
|
|
|
.scheduleAsync();
|
|
|
|
|
|
|
|
Log.d(TAG, "enabled notification checks with "+ NOTIFICATION_CHECK_INTERVAL_MINUTES + "min interval");
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void disablePullNotifications() {
|
|
|
|
JobManager.instance().cancelAllForTag(NotificationPullJobCreator.NOTIFICATIONS_JOB_TAG);
|
|
|
|
Log.d(TAG, "disabled notification checks");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-07-24 05:59:10 +10:00
|
|
|
public static void clearNotificationsForActiveAccount(@NonNull Context context, @NonNull AccountManager accountManager) {
|
2018-02-04 08:45:14 +11:00
|
|
|
AccountEntity account = accountManager.getActiveAccount();
|
|
|
|
if (account != null) {
|
|
|
|
account.setActiveNotifications("[]");
|
|
|
|
accountManager.saveAccount(account);
|
2017-11-16 07:18:35 +11:00
|
|
|
|
2018-02-13 08:03:08 +11:00
|
|
|
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
2017-11-16 07:18:35 +11:00
|
|
|
//noinspection ConstantConditions
|
2018-03-10 08:02:32 +11:00
|
|
|
notificationManager.cancel((int) account.getId());
|
2017-11-16 07:18:35 +11:00
|
|
|
}
|
2017-11-14 05:05:23 +11:00
|
|
|
}
|
|
|
|
|
2018-02-13 08:03:08 +11:00
|
|
|
private static boolean filterNotification(AccountEntity account, Notification notification,
|
|
|
|
Context context) {
|
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) {
|
2018-02-13 08:03:08 +11:00
|
|
|
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
|
|
|
2019-05-03 03:44:35 +10:00
|
|
|
String channelId = getChannelId(account, notification);
|
|
|
|
if(channelId == null) {
|
|
|
|
// unknown notificationtype
|
|
|
|
return false;
|
|
|
|
}
|
2018-02-13 08:03:08 +11:00
|
|
|
//noinspection ConstantConditions
|
2019-05-03 03:44:35 +10:00
|
|
|
NotificationChannel channel = notificationManager.getNotificationChannel(channelId);
|
2018-02-13 08:03:08 +11:00
|
|
|
return channel.getImportance() > NotificationManager.IMPORTANCE_NONE;
|
2017-10-19 07:18:07 +11:00
|
|
|
}
|
|
|
|
|
2018-03-03 23:24:03 +11:00
|
|
|
switch (notification.getType()) {
|
2017-07-02 10:32:35 +10:00
|
|
|
case MENTION:
|
2018-02-04 08:45:14 +11:00
|
|
|
return account.getNotificationsMentioned();
|
2017-07-02 10:32:35 +10:00
|
|
|
case FOLLOW:
|
2018-02-04 08:45:14 +11:00
|
|
|
return account.getNotificationsFollowed();
|
2017-07-02 10:32:35 +10:00
|
|
|
case REBLOG:
|
2018-02-04 08:45:14 +11:00
|
|
|
return account.getNotificationsReblogged();
|
2017-07-02 10:32:35 +10:00
|
|
|
case FAVOURITE:
|
2018-02-04 08:45:14 +11:00
|
|
|
return account.getNotificationsFavorited();
|
2019-05-03 03:44:35 +10:00
|
|
|
case POLL:
|
|
|
|
return account.getNotificationsPolls();
|
2019-03-26 06:35:21 +11:00
|
|
|
default:
|
|
|
|
return false;
|
2017-04-28 13:29:42 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-03 03:44:35 +10:00
|
|
|
private static @Nullable String getChannelId(AccountEntity account, Notification notification) {
|
2018-03-03 23:24:03 +11:00
|
|
|
switch (notification.getType()) {
|
2017-11-14 05:05:23 +11:00
|
|
|
case MENTION:
|
2018-03-10 08:02:32 +11:00
|
|
|
return CHANNEL_MENTION + account.getIdentifier();
|
2017-11-14 05:05:23 +11:00
|
|
|
case FOLLOW:
|
2018-03-10 08:02:32 +11:00
|
|
|
return CHANNEL_FOLLOW + account.getIdentifier();
|
2017-11-14 05:05:23 +11:00
|
|
|
case REBLOG:
|
2018-03-10 08:02:32 +11:00
|
|
|
return CHANNEL_BOOST + account.getIdentifier();
|
2017-11-14 05:05:23 +11:00
|
|
|
case FAVOURITE:
|
2018-03-10 08:02:32 +11:00
|
|
|
return CHANNEL_FAVOURITE + account.getIdentifier();
|
2019-05-03 03:44:35 +10:00
|
|
|
case POLL:
|
|
|
|
return CHANNEL_POLL + account.getIdentifier();
|
|
|
|
default:
|
|
|
|
return null;
|
2017-11-14 05:05:23 +11:00
|
|
|
}
|
2017-10-19 07:18:07 +11:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-02-04 08:45:14 +11:00
|
|
|
private static void setupPreferences(AccountEntity account,
|
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
|
|
|
|
}
|
|
|
|
|
2018-02-04 08:45:14 +11:00
|
|
|
if (account.getNotificationSound()) {
|
2017-04-28 13:29:42 +10:00
|
|
|
builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI);
|
|
|
|
}
|
|
|
|
|
2018-02-04 08:45:14 +11:00
|
|
|
if (account.getNotificationVibration()) {
|
2017-11-14 05:05:23 +11:00
|
|
|
builder.setVibrate(new long[]{500, 500});
|
2017-04-28 13:29:42 +10:00
|
|
|
}
|
|
|
|
|
2018-02-04 08:45:14 +11:00
|
|
|
if (account.getNotificationLight()) {
|
2018-02-13 08:03:08 +11:00
|
|
|
builder.setLights(0xFF2B90D9, 300, 1000);
|
2017-04-28 13:29:42 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-25 03:00:17 +10:00
|
|
|
private static String wrapItemAt(JSONArray array, int index, BidiFormatter bidiFormatter) throws JSONException {
|
|
|
|
return bidiFormatter.unicodeWrap(array.get(index).toString());
|
|
|
|
}
|
|
|
|
|
2017-04-28 13:29:42 +10:00
|
|
|
@Nullable
|
2018-05-25 03:00:17 +10:00
|
|
|
private static String joinNames(Context context, JSONArray array, BidiFormatter bidiFormatter) throws JSONException {
|
2017-04-28 13:29:42 +10:00
|
|
|
if (array.length() > 3) {
|
2018-03-02 05:05:47 +11:00
|
|
|
int length = array.length();
|
2017-07-02 10:32:35 +10:00
|
|
|
return String.format(context.getString(R.string.notification_summary_large),
|
2018-05-25 03:00:17 +10:00
|
|
|
wrapItemAt(array, length - 1, bidiFormatter),
|
|
|
|
wrapItemAt(array, length - 2, bidiFormatter),
|
|
|
|
wrapItemAt(array, length - 3, bidiFormatter),
|
|
|
|
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),
|
2018-05-25 03:00:17 +10:00
|
|
|
wrapItemAt(array, 2, bidiFormatter),
|
|
|
|
wrapItemAt(array, 1, bidiFormatter),
|
|
|
|
wrapItemAt(array, 0, bidiFormatter));
|
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),
|
2018-05-25 03:00:17 +10:00
|
|
|
wrapItemAt(array, 1, bidiFormatter),
|
|
|
|
wrapItemAt(array, 0, bidiFormatter));
|
2017-04-28 13:29:42 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Nullable
|
2019-05-03 03:44:35 +10:00
|
|
|
private static String titleForType(Context context, Notification notification, BidiFormatter bidiFormatter, AccountEntity account) {
|
2018-05-25 03:00:17 +10:00
|
|
|
String accountName = bidiFormatter.unicodeWrap(notification.getAccount().getName());
|
2018-03-03 23:24:03 +11:00
|
|
|
switch (notification.getType()) {
|
2017-04-28 13:29:42 +10:00
|
|
|
case MENTION:
|
2017-07-02 10:32:35 +10:00
|
|
|
return String.format(context.getString(R.string.notification_mention_format),
|
2018-05-25 03:00:17 +10:00
|
|
|
accountName);
|
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),
|
2018-05-25 03:00:17 +10:00
|
|
|
accountName);
|
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),
|
2018-05-25 03:00:17 +10:00
|
|
|
accountName);
|
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),
|
2018-05-25 03:00:17 +10:00
|
|
|
accountName);
|
2019-05-03 03:44:35 +10:00
|
|
|
case POLL:
|
|
|
|
if(notification.getStatus().getAccount().getId().equals(account.getAccountId())) {
|
|
|
|
return context.getString(R.string.poll_ended_created);
|
|
|
|
} else {
|
|
|
|
return context.getString(R.string.poll_ended_voted);
|
|
|
|
}
|
2017-04-28 13:29:42 +10:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-05-03 03:44:35 +10:00
|
|
|
private static String bodyForType(Notification notification, Context context) {
|
2018-03-03 23:24:03 +11:00
|
|
|
switch (notification.getType()) {
|
2017-04-28 13:29:42 +10:00
|
|
|
case FOLLOW:
|
2018-03-10 08:02:32 +11:00
|
|
|
return "@" + notification.getAccount().getUsername();
|
2017-04-28 13:29:42 +10:00
|
|
|
case MENTION:
|
|
|
|
case FAVOURITE:
|
|
|
|
case REBLOG:
|
2019-05-03 03:44:35 +10:00
|
|
|
if (!TextUtils.isEmpty(notification.getStatus().getSpoilerText())) {
|
2018-08-14 06:59:30 +10:00
|
|
|
return notification.getStatus().getSpoilerText();
|
|
|
|
} else {
|
|
|
|
return notification.getStatus().getContent().toString();
|
|
|
|
}
|
2019-05-03 03:44:35 +10:00
|
|
|
case POLL:
|
|
|
|
if (!TextUtils.isEmpty(notification.getStatus().getSpoilerText())) {
|
|
|
|
return notification.getStatus().getSpoilerText();
|
|
|
|
} else {
|
|
|
|
StringBuilder builder = new StringBuilder(notification.getStatus().getContent());
|
|
|
|
builder.append('\n');
|
|
|
|
Poll poll = notification.getStatus().getPoll();
|
|
|
|
for(PollOption option: poll.getOptions()) {
|
|
|
|
int percent = option.getPercent(poll.getVotesCount());
|
|
|
|
CharSequence optionText = HtmlUtils.fromHtml(context.getString(R.string.poll_option_format, percent, option.getTitle()));
|
|
|
|
builder.append(optionText);
|
|
|
|
builder.append('\n');
|
|
|
|
}
|
|
|
|
return builder.toString();
|
|
|
|
}
|
2017-04-28 13:29:42 +10:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2017-10-19 07:18:07 +11:00
|
|
|
|
2017-04-28 13:29:42 +10:00
|
|
|
}
|