diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0291bd8aaa..9dbb93c532 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -718,20 +718,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/InMemoryMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/InMemoryMessageRecord.java
index 14db5ab3b3..4a49a86879 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/database/model/InMemoryMessageRecord.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/InMemoryMessageRecord.java
@@ -20,6 +20,7 @@ public class InMemoryMessageRecord extends MessageRecord {
private static final int NO_GROUPS_IN_COMMON_ID = -1;
private static final int UNIVERSAL_EXPIRE_TIMER_ID = -2;
+ private static final int FORCE_BUBBLE_ID = -3;
private InMemoryMessageRecord(long id,
String body,
@@ -137,4 +138,13 @@ public class InMemoryMessageRecord extends MessageRecord {
return true;
}
}
+
+ /**
+ * Useful for create an empty message record when one is needed.
+ */
+ public static final class ForceConversationBubble extends InMemoryMessageRecord {
+ public ForceConversationBubble(Recipient conversationRecipient, long threadId) {
+ super(FORCE_BUBBLE_ID, "", conversationRecipient, threadId, 0);
+ }
+ }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java
index aabc6b78f3..26daa1bcb4 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencyProvider.java
@@ -34,9 +34,7 @@ import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever;
import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
import org.thoughtcrime.securesms.messages.IncomingMessageProcessor;
import org.thoughtcrime.securesms.net.PipeConnectivityListener;
-import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
-import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier;
import org.thoughtcrime.securesms.payments.MobileCoinConfig;
import org.thoughtcrime.securesms.payments.Payments;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java
deleted file mode 100644
index deeaf2059b..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package org.thoughtcrime.securesms.notifications;
-
-import android.app.Notification;
-import android.content.Context;
-import android.graphics.Color;
-import android.net.Uri;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.app.NotificationCompat;
-
-import org.signal.core.util.logging.Log;
-import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.database.RecipientDatabase;
-import org.thoughtcrime.securesms.keyvalue.SignalStore;
-import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
-import org.thoughtcrime.securesms.recipients.Recipient;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
-import org.thoughtcrime.securesms.util.Util;
-
-public abstract class AbstractNotificationBuilder extends NotificationCompat.Builder {
-
- @SuppressWarnings("unused")
- private static final String TAG = Log.tag(AbstractNotificationBuilder.class);
-
- public static final int MAX_DISPLAY_LENGTH = 500;
-
- protected Context context;
- protected NotificationPrivacyPreference privacy;
-
- public AbstractNotificationBuilder(Context context, NotificationPrivacyPreference privacy) {
- super(context);
-
- this.context = context;
- this.privacy = privacy;
-
- setChannelId(NotificationChannels.getMessagesChannel(context));
- setLed();
- }
-
- protected CharSequence getStyledMessage(@NonNull Recipient recipient, @Nullable CharSequence message) {
- SpannableStringBuilder builder = new SpannableStringBuilder();
- builder.append(Util.getBoldedString(recipient.getDisplayName(context)));
- builder.append(": ");
- builder.append(message == null ? "" : message);
-
- return builder;
- }
-
- public void setAlarms(@Nullable Uri ringtone, RecipientDatabase.VibrateState vibrate) {
- Uri defaultRingtone = NotificationChannels.supported() ? NotificationChannels.getMessageRingtone(context) : SignalStore.settings().getMessageNotificationSound();
- boolean defaultVibrate = NotificationChannels.supported() ? NotificationChannels.getMessageVibrate(context) : SignalStore.settings().isMessageVibrateEnabled();
-
- if (ringtone == null && !TextUtils.isEmpty(defaultRingtone.toString())) setSound(defaultRingtone);
- else if (ringtone != null && !ringtone.toString().isEmpty()) setSound(ringtone);
-
- if (vibrate == RecipientDatabase.VibrateState.ENABLED ||
- (vibrate == RecipientDatabase.VibrateState.DEFAULT && defaultVibrate))
- {
- setDefaults(Notification.DEFAULT_VIBRATE);
- }
- }
-
- private void setLed() {
- String ledColor = SignalStore.settings().getMessageLedColor();
- String ledBlinkPattern = SignalStore.settings().getMessageLedBlinkPattern();
- String ledBlinkPatternCustom = TextSecurePreferences.getNotificationLedPatternCustom(context);
-
- if (!ledColor.equals("none")) {
- String[] blinkPatternArray = parseBlinkPattern(ledBlinkPattern, ledBlinkPatternCustom);
-
- setLights(Color.parseColor(ledColor),
- Integer.parseInt(blinkPatternArray[0]),
- Integer.parseInt(blinkPatternArray[1]));
- }
- }
-
- public void setTicker(@NonNull Recipient recipient, @Nullable CharSequence message) {
- if (privacy.isDisplayMessage()) {
- setTicker(getStyledMessage(recipient, trimToDisplayLength(message)));
- } else if (privacy.isDisplayContact()) {
- setTicker(getStyledMessage(recipient, context.getString(R.string.AbstractNotificationBuilder_new_message)));
- } else {
- setTicker(context.getString(R.string.AbstractNotificationBuilder_new_message));
- }
- }
-
- private String[] parseBlinkPattern(String blinkPattern, String blinkPatternCustom) {
- if (blinkPattern.equals("custom"))
- blinkPattern = blinkPatternCustom;
-
- return blinkPattern.split(",");
- }
-
- protected @NonNull CharSequence trimToDisplayLength(@Nullable CharSequence text) {
- text = text == null ? "" : text;
-
- return text.length() <= MAX_DISPLAY_LENGTH ? text
- : text.subSequence(0, MAX_DISPLAY_LENGTH);
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoHeardReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoHeardReceiver.java
deleted file mode 100644
index d8f2465cf2..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoHeardReceiver.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2011 Whisper Systems
- *
- * 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.
- *
- * This program 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 this program. If not, see .
- */
-
-package org.thoughtcrime.securesms.notifications;
-
-import android.annotation.SuppressLint;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.AsyncTask;
-
-import org.signal.core.util.logging.Log;
-import org.thoughtcrime.securesms.database.DatabaseFactory;
-import org.thoughtcrime.securesms.database.MessageDatabase.MarkedMessageInfo;
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
-
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Marks an Android Auto as read after the driver have listened to it
- */
-public class AndroidAutoHeardReceiver extends BroadcastReceiver {
-
- public static final String TAG = Log.tag(AndroidAutoHeardReceiver.class);
- public static final String HEARD_ACTION = "org.thoughtcrime.securesms.notifications.ANDROID_AUTO_HEARD";
- public static final String THREAD_IDS_EXTRA = "car_heard_thread_ids";
- public static final String NOTIFICATION_ID_EXTRA = "car_notification_id";
-
- @SuppressLint("StaticFieldLeak")
- @Override
- public void onReceive(final Context context, Intent intent)
- {
- if (!HEARD_ACTION.equals(intent.getAction()))
- return;
-
- final long[] threadIds = intent.getLongArrayExtra(THREAD_IDS_EXTRA);
-
- if (threadIds != null) {
- int notificationId = intent.getIntExtra(NOTIFICATION_ID_EXTRA, -1);
- NotificationCancellationHelper.cancelLegacy(context, notificationId);
-
- new AsyncTask() {
- @Override
- protected Void doInBackground(Void... params) {
- List messageIdsCollection = new LinkedList<>();
-
- for (long threadId : threadIds) {
- Log.i(TAG, "Marking meassage as read: " + threadId);
- List messageIds = DatabaseFactory.getThreadDatabase(context).setRead(threadId, true);
-
- messageIdsCollection.addAll(messageIds);
- }
-
- ApplicationDependencies.getMessageNotifier().updateNotification(context);
- MarkReadReceiver.process(context, messageIdsCollection);
-
- return null;
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java
deleted file mode 100644
index ca6632d7e0..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/AndroidAutoReplyReceiver.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2011 Whisper Systems
- *
- * 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.
- *
- * This program 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 this program. If not, see .
- */
-
-package org.thoughtcrime.securesms.notifications;
-
-import android.annotation.SuppressLint;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.Bundle;
-
-import androidx.core.app.RemoteInput;
-
-import org.signal.core.util.logging.Log;
-import org.thoughtcrime.securesms.database.DatabaseFactory;
-import org.thoughtcrime.securesms.database.MessageDatabase.MarkedMessageInfo;
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
-import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
-import org.thoughtcrime.securesms.recipients.Recipient;
-import org.thoughtcrime.securesms.sms.MessageSender;
-import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
-
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Get the response text from the Android Auto and sends an message as a reply
- */
-public class AndroidAutoReplyReceiver extends BroadcastReceiver {
-
- public static final String TAG = Log.tag(AndroidAutoReplyReceiver.class);
- public static final String REPLY_ACTION = "org.thoughtcrime.securesms.notifications.ANDROID_AUTO_REPLY";
- public static final String RECIPIENT_EXTRA = "car_recipient";
- public static final String VOICE_REPLY_KEY = "car_voice_reply_key";
- public static final String THREAD_ID_EXTRA = "car_reply_thread_id";
-
- @SuppressLint("StaticFieldLeak")
- @Override
- public void onReceive(final Context context, Intent intent)
- {
- if (!REPLY_ACTION.equals(intent.getAction())) return;
-
- Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
-
- if (remoteInput == null) return;
-
- final long threadId = intent.getLongExtra(THREAD_ID_EXTRA, -1);
- final CharSequence responseText = getMessageText(intent);
- final Recipient recipient = Recipient.resolved(intent.getParcelableExtra(RECIPIENT_EXTRA));
-
- if (responseText != null) {
- new AsyncTask() {
- @Override
- protected Void doInBackground(Void... params) {
-
- long replyThreadId;
-
- int subscriptionId = recipient.getDefaultSubscriptionId().or(-1);
- long expiresIn = recipient.getExpireMessages() * 1000L;
-
- if (recipient.resolve().isGroup()) {
- Log.w(TAG, "GroupRecipient, Sending media message");
- OutgoingMediaMessage reply = new OutgoingMediaMessage(recipient,
- responseText.toString(),
- new LinkedList<>(),
- System.currentTimeMillis(),
- subscriptionId,
- expiresIn,
- false,
- 0,
- null,
- Collections.emptyList(),
- Collections.emptyList(),
- Collections.emptyList(),
- Collections.emptyList(),
- Collections.emptyList());
- replyThreadId = MessageSender.send(context, reply, threadId, false, null);
- } else {
- Log.w(TAG, "Sending regular message ");
- OutgoingTextMessage reply = new OutgoingTextMessage(recipient, responseText.toString(), expiresIn, subscriptionId);
- replyThreadId = MessageSender.send(context, reply, threadId, false, null);
- }
-
- List messageIds = DatabaseFactory.getThreadDatabase(context).setRead(replyThreadId, true);
-
- ApplicationDependencies.getMessageNotifier().updateNotification(context);
- MarkReadReceiver.process(context, messageIds);
-
- return null;
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- }
- }
-
- private CharSequence getMessageText(Intent intent) {
- Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
- if (remoteInput != null) {
- return remoteInput.getCharSequence(VOICE_REPLY_KEY);
- }
- return null;
- }
-
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java
deleted file mode 100644
index b7d847febe..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java
+++ /dev/null
@@ -1,837 +0,0 @@
-/*
- * Copyright (C) 2011 Whisper Systems
- *
- * 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.
- *
- * This program 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 this program. If not, see .
- */
-package org.thoughtcrime.securesms.notifications;
-
-import android.app.AlarmManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.graphics.BitmapFactory;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.TransactionTooLargeException;
-import android.service.notification.StatusBarNotification;
-import android.text.SpannableString;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-import androidx.core.app.NotificationCompat;
-import androidx.core.app.NotificationManagerCompat;
-
-import com.annimon.stream.Stream;
-
-import org.signal.core.util.concurrent.SignalExecutors;
-import org.signal.core.util.logging.Log;
-import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.contactshare.Contact;
-import org.thoughtcrime.securesms.contactshare.ContactUtil;
-import org.thoughtcrime.securesms.conversation.ConversationIntents;
-import org.thoughtcrime.securesms.database.DatabaseFactory;
-import org.thoughtcrime.securesms.database.MentionUtil;
-import org.thoughtcrime.securesms.database.MmsSmsColumns;
-import org.thoughtcrime.securesms.database.MmsSmsDatabase;
-import org.thoughtcrime.securesms.database.RecipientDatabase;
-import org.thoughtcrime.securesms.database.ThreadBodyUtil;
-import org.thoughtcrime.securesms.database.model.MessageRecord;
-import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
-import org.thoughtcrime.securesms.database.model.ReactionRecord;
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
-import org.thoughtcrime.securesms.keyvalue.SignalStore;
-import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
-import org.thoughtcrime.securesms.mms.Slide;
-import org.thoughtcrime.securesms.mms.SlideDeck;
-import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
-import org.thoughtcrime.securesms.recipients.Recipient;
-import org.thoughtcrime.securesms.recipients.RecipientUtil;
-import org.thoughtcrime.securesms.service.KeyCachingService;
-import org.thoughtcrime.securesms.util.BubbleUtil;
-import org.thoughtcrime.securesms.util.MediaUtil;
-import org.thoughtcrime.securesms.util.MessageRecordUtil;
-import org.thoughtcrime.securesms.util.ServiceUtil;
-import org.thoughtcrime.securesms.util.SpanUtil;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
-import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder;
-import org.whispersystems.signalservice.internal.util.Util;
-
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import me.leolin.shortcutbadger.ShortcutBadger;
-
-
-/**
- * Handles posting system notifications for new messages.
- *
- *
- * @author Moxie Marlinspike
- */
-public class DefaultMessageNotifier implements MessageNotifier {
-
- private static final String TAG = Log.tag(DefaultMessageNotifier.class);
-
- public static final String EXTRA_REMOTE_REPLY = "extra_remote_reply";
- public static final String NOTIFICATION_GROUP = "messages";
-
- private static final String EMOJI_REPLACEMENT_STRING = "__EMOJI__";
- public static final long MIN_AUDIBLE_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(2);
- public static final long DESKTOP_ACTIVITY_PERIOD = TimeUnit.MINUTES.toMillis(1);
-
- private volatile long visibleThread = -1;
- private volatile long lastDesktopActivityTimestamp = -1;
- private volatile long lastAudibleNotification = -1;
- private final CancelableExecutor executor = new CancelableExecutor();
-
- @Override
- public void setVisibleThread(long threadId) {
- visibleThread = threadId;
- }
-
- @Override
- public long getVisibleThread() {
- return visibleThread;
- }
-
- @Override
- public void clearVisibleThread() {
- setVisibleThread(-1);
- }
-
- @Override
- public void setLastDesktopActivityTimestamp(long timestamp) {
- lastDesktopActivityTimestamp = timestamp;
- }
-
- @Override
- public void notifyMessageDeliveryFailed(@NonNull Context context, @NonNull Recipient recipient, long threadId) {
- if (visibleThread == threadId) {
- sendInThreadNotification(context, recipient);
- } else {
- Intent intent = ConversationIntents.createBuilder(context, recipient.getId(), threadId)
- .withDataUri(Uri.parse("custom://" + System.currentTimeMillis()))
- .build();
- FailedNotificationBuilder builder = new FailedNotificationBuilder(context, SignalStore.settings().getMessageNotificationsPrivacy(), intent);
-
- ((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE))
- .notify((int)threadId, builder.build());
- }
- }
-
- @Override
- public void notifyProofRequired(@NonNull Context context, @NonNull Recipient recipient, long threadId) {
- if (visibleThread == threadId) {
- sendInThreadNotification(context, recipient);
- } else {
- Log.w(TAG, "[Proof Required] Not notifying on old notifier.");
- }
- }
-
- @Override
- public void cancelDelayedNotifications() {
- executor.cancel();
- }
-
- private static boolean isDisplayingSummaryNotification(@NonNull Context context) {
- if (Build.VERSION.SDK_INT >= 23) {
- try {
- NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
- StatusBarNotification[] activeNotifications = notificationManager.getActiveNotifications();
-
- for (StatusBarNotification activeNotification : activeNotifications) {
- if (activeNotification.getId() == NotificationIds.MESSAGE_SUMMARY) {
- return true;
- }
- }
-
- return false;
-
- } catch (Throwable e) {
- // XXX Android ROM Bug, see #6043
- Log.w(TAG, e);
- return false;
- }
- } else {
- return false;
- }
- }
-
- private static void cancelOrphanedNotifications(@NonNull Context context, NotificationState notificationState) {
- if (Build.VERSION.SDK_INT >= 23) {
- try {
- NotificationManager notifications = ServiceUtil.getNotificationManager(context);
- StatusBarNotification[] activeNotifications = notifications.getActiveNotifications();
-
- for (StatusBarNotification notification : activeNotifications) {
- boolean validNotification = false;
-
- if (notification.getId() != NotificationIds.MESSAGE_SUMMARY &&
- notification.getId() != KeyCachingService.SERVICE_RUNNING_ID &&
- notification.getId() != IncomingMessageObserver.FOREGROUND_ID &&
- notification.getId() != NotificationIds.PENDING_MESSAGES &&
- !CallNotificationBuilder.isWebRtcNotification(notification.getId()))
- {
- for (NotificationItem item : notificationState.getNotifications()) {
- if (notification.getId() == NotificationIds.getNotificationIdForThread(item.getThreadId())) {
- validNotification = true;
- break;
- }
- }
-
- if (!validNotification) {
- NotificationCancellationHelper.cancel(context, notification.getId());
- }
- }
- }
- } catch (Throwable e) {
- // XXX Android ROM Bug, see #6043
- Log.w(TAG, e);
- }
- }
- }
-
- @Override
- public void updateNotification(@NonNull Context context) {
- if (!SignalStore.settings().isMessageNotificationsEnabled()) {
- return;
- }
-
- updateNotification(context, -1, false, 0, BubbleUtil.BubbleState.HIDDEN);
- }
-
- @Override
- public void updateNotification(@NonNull Context context, long threadId)
- {
- if (System.currentTimeMillis() - lastDesktopActivityTimestamp < DESKTOP_ACTIVITY_PERIOD) {
- Log.i(TAG, "Scheduling delayed notification...");
- executor.execute(new DelayedNotification(context, threadId));
- } else {
- updateNotification(context, threadId, true);
- }
- }
-
- @Override
- public void updateNotification(@NonNull Context context, long threadId, @NonNull BubbleUtil.BubbleState defaultBubbleState) {
- updateNotification(context, threadId, false, 0, defaultBubbleState);
- }
-
- @Override
- public void updateNotification(@NonNull Context context,
- long threadId,
- boolean signal)
- {
- boolean isVisible = visibleThread == threadId;
- Recipient recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId);
-
- if (shouldNotify(context, recipient, threadId)) {
- if (isVisible) {
- sendInThreadNotification(context, recipient);
- } else {
- updateNotification(context, threadId, signal, 0, BubbleUtil.BubbleState.HIDDEN);
- }
- }
- }
-
- private boolean shouldNotify(@NonNull Context context, @Nullable Recipient recipient, long threadId) {
- if (!SignalStore.settings().isMessageNotificationsEnabled()) {
- return false;
- }
-
- if (recipient == null || !recipient.isMuted()) {
- return true;
- }
-
- return recipient.isPushV2Group() &&
- recipient.getMentionSetting() == RecipientDatabase.MentionSetting.ALWAYS_NOTIFY &&
- DatabaseFactory.getMmsDatabase(context).getUnreadMentionCount(threadId) > 0;
- }
-
- @Override
- public void updateNotification(@NonNull Context context,
- long targetThread,
- boolean signal,
- int reminderCount,
- @NonNull BubbleUtil.BubbleState defaultBubbleState)
- {
- if (!SignalStore.settings().isMessageNotificationsEnabled()) {
- return;
- }
-
- boolean isReminder = reminderCount > 0;
- Cursor telcoCursor = null;
-
- try {
- telcoCursor = DatabaseFactory.getMmsSmsDatabase(context).getUnread();
-
- if (telcoCursor == null || telcoCursor.isAfterLast()) {
- NotificationCancellationHelper.cancelAllMessageNotifications(context);
- updateBadge(context, 0);
- clearReminder(context);
- return;
- }
-
- NotificationState notificationState = constructNotificationState(context, telcoCursor);
-
- if (signal && (System.currentTimeMillis() - lastAudibleNotification) < MIN_AUDIBLE_PERIOD_MILLIS) {
- signal = false;
- } else if (signal) {
- lastAudibleNotification = System.currentTimeMillis();
- }
-
- boolean shouldScheduleReminder = signal;
-
- if (notificationState.hasMultipleThreads()) {
- if (Build.VERSION.SDK_INT >= 23) {
- for (long threadId : notificationState.getThreads()) {
- if (targetThread < 1 || targetThread == threadId) {
- sendSingleThreadNotification(context,
- new NotificationState(notificationState.getNotificationsForThread(threadId)),
- signal && (threadId == targetThread),
- true,
- isReminder,
- (threadId == targetThread) ? defaultBubbleState : BubbleUtil.BubbleState.HIDDEN);
- }
- }
- }
-
- sendMultipleThreadNotification(context, notificationState, signal && (Build.VERSION.SDK_INT < 23));
- } else {
- long thread = notificationState.getNotifications().isEmpty() ? -1 : notificationState.getNotifications().get(0).getThreadId();
- BubbleUtil.BubbleState bubbleState = thread == targetThread ? defaultBubbleState : BubbleUtil.BubbleState.HIDDEN;
-
- shouldScheduleReminder = sendSingleThreadNotification(context, notificationState, signal, false, isReminder, bubbleState);
-
- if (isDisplayingSummaryNotification(context)) {
- sendMultipleThreadNotification(context, notificationState, false);
- }
- }
-
- cancelOrphanedNotifications(context, notificationState);
- updateBadge(context, notificationState.getMessageCount());
-
- List smsIds = new LinkedList<>();
- List mmsIds = new LinkedList<>();
- for (NotificationItem item : notificationState.getNotifications()) {
- if (item.isMms()) {
- mmsIds.add(item.getId());
- } else {
- smsIds.add(item.getId());
- }
- }
- DatabaseFactory.getMmsSmsDatabase(context).setNotifiedTimestamp(System.currentTimeMillis(), smsIds, mmsIds);
-
- if (shouldScheduleReminder) {
- scheduleReminder(context, reminderCount);
- }
- } finally {
- if (telcoCursor != null) telcoCursor.close();
- }
- }
-
- private static boolean sendSingleThreadNotification(@NonNull Context context,
- @NonNull NotificationState notificationState,
- boolean signal,
- boolean bundled,
- boolean isReminder,
- @NonNull BubbleUtil.BubbleState defaultBubbleState)
- {
- Log.i(TAG, "sendSingleThreadNotification() signal: " + signal + " bundled: " + bundled);
-
- if (notificationState.getNotifications().isEmpty()) {
- if (!bundled) NotificationCancellationHelper.cancelAllMessageNotifications(context);
- Log.i(TAG, "[sendSingleThreadNotification] Empty notification state. Skipping.");
- return false;
- }
-
- NotificationPrivacyPreference notificationPrivacy = SignalStore.settings().getMessageNotificationsPrivacy();
- SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, notificationPrivacy);
- List notifications = notificationState.getNotifications();
- Recipient recipient = notifications.get(0).getRecipient();
- boolean shouldAlert = signal && (isReminder || Stream.of(notifications).anyMatch(item -> item.getNotifiedTimestamp() == 0));
- int notificationId;
-
- if (Build.VERSION.SDK_INT >= 23) {
- notificationId = NotificationIds.getNotificationIdForThread(notifications.get(0).getThreadId());
- } else {
- notificationId = NotificationIds.MESSAGE_SUMMARY;
- }
-
- builder.setThread(notifications.get(0).getRecipient());
- builder.setMessageCount(notificationState.getMessageCount());
- builder.setPrimaryMessageBody(recipient, notifications.get(0).getIndividualRecipient(),
- notifications.get(0).getText(), notifications.get(0).getSlideDeck());
- builder.setContentIntent(notifications.get(0).getPendingIntent(context));
- builder.setDeleteIntent(notificationState.getDeleteIntent(context));
- builder.setOnlyAlertOnce(!shouldAlert);
- builder.setSortKey(String.valueOf(Long.MAX_VALUE - notifications.get(0).getTimestamp()));
- builder.setDefaultBubbleState(defaultBubbleState);
-
- long timestamp = notifications.get(0).getTimestamp();
- if (timestamp != 0) builder.setWhen(timestamp);
-
- boolean isSingleNotificationContactJoined = notifications.size() == 1 && notifications.get(0).isJoin();
-
- if (notificationPrivacy.isDisplayMessage() &&
- !KeyCachingService.isLocked(context) &&
- RecipientUtil.isMessageRequestAccepted(context, recipient.resolve()))
- {
- ReplyMethod replyMethod = ReplyMethod.forRecipient(context, recipient);
-
- builder.addActions(notificationState.getMarkAsReadIntent(context, notificationId),
- notificationState.getQuickReplyIntent(context, notifications.get(0).getRecipient()),
- notificationState.getRemoteReplyIntent(context, notifications.get(0).getRecipient(), replyMethod),
- replyMethod,
- !isSingleNotificationContactJoined && notificationState.canReply());
-
- builder.addAndroidAutoAction(notificationState.getAndroidAutoReplyIntent(context, notifications.get(0).getRecipient()),
- notificationState.getAndroidAutoHeardIntent(context, notificationId), notifications.get(0).getTimestamp());
- }
-
- if (!KeyCachingService.isLocked(context) && isSingleNotificationContactJoined) {
- builder.addTurnOffTheseNotificationsAction(notificationState.getTurnOffTheseNotificationsIntent(context));
- }
-
- ListIterator iterator = notifications.listIterator(notifications.size());
-
- while(iterator.hasPrevious()) {
- NotificationItem item = iterator.previous();
- builder.addMessageBody(item.getRecipient(), item.getIndividualRecipient(), item.getText(), item.getTimestamp(), item.getSlideDeck());
- }
-
- if (signal) {
- builder.setAlarms(notificationState.getRingtone(context), notificationState.getVibrate());
- builder.setTicker(notifications.get(0).getIndividualRecipient(),
- notifications.get(0).getText());
- }
-
- if (Build.VERSION.SDK_INT >= 23) {
- builder.setGroup(NOTIFICATION_GROUP);
- builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN);
- }
-
- Notification notification = builder.build();
- try {
- NotificationManagerCompat.from(context).notify(notificationId, notification);
- Log.i(TAG, "Posted notification.");
- } catch (SecurityException e) {
- Uri defaultValue = SignalStore.settings().getMessageNotificationSound();
- if (!defaultValue.equals(notificationState.getRingtone(context))) {
- Log.e(TAG, "Security exception when posting notification with custom ringtone", e);
- clearNotificationRingtone(context, notifications.get(0).getRecipient());
- } else {
- throw e;
- }
- }
-
- return shouldAlert;
- }
-
- private static void sendMultipleThreadNotification(@NonNull Context context,
- @NonNull NotificationState notificationState,
- boolean signal)
- {
- Log.i(TAG, "sendMultiThreadNotification() signal: " + signal);
-
- if (notificationState.getNotifications().isEmpty()) {
- Log.i(TAG, "[sendMultiThreadNotification] Empty notification state. Skipping.");
- return;
- }
-
- NotificationPrivacyPreference notificationPrivacy = SignalStore.settings().getMessageNotificationsPrivacy();
- MultipleRecipientNotificationBuilder builder = new MultipleRecipientNotificationBuilder(context, notificationPrivacy);
- List notifications = notificationState.getNotifications();
- boolean shouldAlert = signal && Stream.of(notifications).anyMatch(item -> item.getNotifiedTimestamp() == 0);
-
- builder.setMessageCount(notificationState.getMessageCount(), notificationState.getThreadCount());
- builder.setMostRecentSender(notifications.get(0).getIndividualRecipient());
- builder.setDeleteIntent(notificationState.getDeleteIntent(context));
- builder.setOnlyAlertOnce(!shouldAlert);
-
- if (Build.VERSION.SDK_INT >= 23) {
- builder.setGroup(NOTIFICATION_GROUP);
- builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN);
- }
-
- long timestamp = notifications.get(0).getTimestamp();
- if (timestamp != 0) builder.setWhen(timestamp);
-
- if (notificationPrivacy.isDisplayMessage()) {
- builder.addActions(notificationState.getMarkAsReadIntent(context, NotificationIds.MESSAGE_SUMMARY));
- }
-
- ListIterator iterator = notifications.listIterator(notifications.size());
-
- while(iterator.hasPrevious()) {
- NotificationItem item = iterator.previous();
- builder.addMessageBody(item.getIndividualRecipient(), item.getText());
- }
-
- if (signal) {
- builder.setAlarms(notificationState.getRingtone(context), notificationState.getVibrate());
- builder.setTicker(notifications.get(0).getIndividualRecipient(),
- notifications.get(0).getText());
- }
-
- Notification notification = builder.build();
-
- try {
- NotificationManagerCompat.from(context).notify(NotificationIds.MESSAGE_SUMMARY, builder.build());
- Log.i(TAG, "Posted notification. " + notification.toString());
- } catch (SecurityException securityException) {
- Uri defaultValue = SignalStore.settings().getMessageNotificationSound();
- if (!defaultValue.equals(notificationState.getRingtone(context))) {
- Log.e(TAG, "Security exception when posting notification with custom ringtone", securityException);
- clearNotificationRingtone(context, notifications.get(0).getRecipient());
- } else {
- throw securityException;
- }
- } catch (RuntimeException runtimeException) {
- Throwable cause = runtimeException.getCause();
- if (cause instanceof TransactionTooLargeException) {
- Log.e(TAG, "Transaction too large", runtimeException);
- } else {
- throw runtimeException;
- }
- }
- }
-
- private static void sendInThreadNotification(Context context, Recipient recipient) {
- if (!SignalStore.settings().isMessageNotificationsInChatSoundsEnabled() ||
- ServiceUtil.getAudioManager(context).getRingerMode() != AudioManager.RINGER_MODE_NORMAL)
- {
- return;
- }
-
- Uri uri = null;
- if (recipient != null) {
- uri = NotificationChannels.supported() ? NotificationChannels.getMessageRingtone(context, recipient) : recipient.getMessageRingtone();
- }
-
- if (uri == null) {
- uri = NotificationChannels.supported() ? NotificationChannels.getMessageRingtone(context) : SignalStore.settings().getMessageNotificationSound();
- }
-
- if (uri.toString().isEmpty()) {
- Log.d(TAG, "ringtone uri is empty");
- return;
- }
-
- Ringtone ringtone = RingtoneManager.getRingtone(context, uri);
-
- if (ringtone == null) {
- Log.w(TAG, "ringtone is null");
- return;
- }
-
- if (Build.VERSION.SDK_INT >= 21) {
- ringtone.setAudioAttributes(new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
- .setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
- .build());
- } else {
- ringtone.setStreamType(AudioManager.STREAM_NOTIFICATION);
- }
-
- ringtone.play();
- }
-
- private static NotificationState constructNotificationState(@NonNull Context context,
- @NonNull Cursor cursor)
- {
- NotificationState notificationState = new NotificationState();
- MmsSmsDatabase.Reader reader = DatabaseFactory.getMmsSmsDatabase(context).readerFor(cursor);
-
- MessageRecord record;
-
- while ((record = reader.getNext()) != null) {
- long id = record.getId();
- boolean mms = record.isMms() || record.isMmsNotification();
- Recipient recipient = record.getIndividualRecipient().resolve();
- Recipient conversationRecipient = record.getRecipient().resolve();
- long threadId = record.getThreadId();
- CharSequence body = MentionUtil.updateBodyWithDisplayNames(context, record);
- Recipient threadRecipients = null;
- SlideDeck slideDeck = null;
- long timestamp = record.getTimestamp();
- long receivedTimestamp = record.getDateReceived();
- boolean isUnreadMessage = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.READ)) == 0;
- boolean hasUnreadReactions = cursor.getInt(cursor.getColumnIndexOrThrow(MmsSmsColumns.REACTIONS_UNREAD)) == 1;
- long lastReactionRead = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.REACTIONS_LAST_SEEN));
- long notifiedTimestamp = record.getNotifiedTimestamp();
-
- if (threadId != -1) {
- threadRecipients = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId);
- }
-
- if (isUnreadMessage) {
- boolean canReply = false;
-
- if (!RecipientUtil.isMessageRequestAccepted(context, threadId)) {
- body = SpanUtil.italic(context.getString(R.string.SingleRecipientNotificationBuilder_message_request));
- } else if (KeyCachingService.isLocked(context)) {
- body = SpanUtil.italic(context.getString(R.string.MessageNotifier_locked_message));
- } else if (record.isMms() && !((MmsMessageRecord) record).getSharedContacts().isEmpty()) {
- Contact contact = ((MmsMessageRecord) record).getSharedContacts().get(0);
- body = ContactUtil.getStringSummary(context, contact);
- } else if (record.isMms() && ((MmsMessageRecord) record).isViewOnce()) {
- body = SpanUtil.italic(context.getString(getViewOnceDescription((MmsMessageRecord) record)));
- } else if (record.isRemoteDelete()) {
- body = SpanUtil.italic(context.getString(R.string.MessageNotifier_this_message_was_deleted));;
- } else if (record.isMms() && !record.isMmsNotification() && !((MmsMessageRecord) record).getSlideDeck().getSlides().isEmpty()) {
- body = ThreadBodyUtil.getFormattedBodyFor(context, record);
- slideDeck = ((MmsMessageRecord) record).getSlideDeck();
- canReply = true;
- } else if (record.isGroupCall()) {
- body = new SpannableString(MessageRecord.getGroupCallUpdateDescription(context, record.getBody(), false).getString());
- canReply = false;
- } else {
- canReply = true;
- }
-
- boolean includeMessage = true;
- if (threadRecipients != null && threadRecipients.isMuted()) {
- boolean mentionsOverrideMute = threadRecipients.getMentionSetting() == RecipientDatabase.MentionSetting.ALWAYS_NOTIFY;
-
- includeMessage = mentionsOverrideMute && record.hasSelfMention();
- }
-
- if (threadRecipients == null || includeMessage) {
- notificationState.addNotification(new NotificationItem(id, mms, recipient, conversationRecipient, threadRecipients, threadId, body, timestamp, receivedTimestamp, slideDeck, false, record.isJoined(), canReply, notifiedTimestamp));
- }
- }
-
- if (hasUnreadReactions) {
- CharSequence originalBody = body;
- for (ReactionRecord reaction : record.getReactions()) {
- Recipient reactionSender = Recipient.resolved(reaction.getAuthor());
- if (reactionSender.equals(Recipient.self()) || !record.isOutgoing() || reaction.getDateReceived() <= lastReactionRead) {
- continue;
- }
-
- if (KeyCachingService.isLocked(context)) {
- body = SpanUtil.italic(context.getString(R.string.MessageNotifier_locked_message));
- } else {
- String text = SpanUtil.italic(getReactionMessageBody(context, record, originalBody)).toString();
- String[] parts = text.split(EMOJI_REPLACEMENT_STRING);
-
- SpannableStringBuilder builder = new SpannableStringBuilder();
- for (int i = 0; i < parts.length; i++) {
- builder.append(SpanUtil.italic(parts[i]));
-
- if (i != parts.length -1) {
- builder.append(reaction.getEmoji());
- }
- }
-
- if (text.endsWith(EMOJI_REPLACEMENT_STRING)) {
- builder.append(reaction.getEmoji());
- }
-
- body = builder;
- }
-
- if (threadRecipients == null || !threadRecipients.isMuted()) {
- notificationState.addNotification(new NotificationItem(id, mms, reactionSender, conversationRecipient, threadRecipients, threadId, body, reaction.getDateReceived(), receivedTimestamp, null, true, record.isJoined(), false, 0));
- }
- }
- }
- }
-
- reader.close();
- return notificationState;
- }
-
- private static CharSequence getReactionMessageBody(@NonNull Context context, @NonNull MessageRecord record, @NonNull CharSequence body) {
- boolean bodyIsEmpty = TextUtils.isEmpty(body);
-
- if (MessageRecordUtil.hasSharedContact(record)) {
- Contact contact = ((MmsMessageRecord) record).getSharedContacts().get(0);
- CharSequence summary = ContactUtil.getStringSummary(context, contact);
-
- return context.getString(R.string.MessageNotifier_reacted_s_to_s, EMOJI_REPLACEMENT_STRING, summary);
- } else if (MessageRecordUtil.hasSticker(record)) {
- return context.getString(R.string.MessageNotifier_reacted_s_to_your_sticker, EMOJI_REPLACEMENT_STRING);
- } else if (record.isMms() && record.isViewOnce()){
- return context.getString(R.string.MessageNotifier_reacted_s_to_your_view_once_media, EMOJI_REPLACEMENT_STRING);
- } else if (!bodyIsEmpty) {
- return context.getString(R.string.MessageNotifier_reacted_s_to_s, EMOJI_REPLACEMENT_STRING, body);
- } else if (MessageRecordUtil.isMediaMessage(record) && MediaUtil.isVideoType(getMessageContentType((MmsMessageRecord) record))) {
- return context.getString(R.string.MessageNotifier_reacted_s_to_your_video, EMOJI_REPLACEMENT_STRING);
- } else if (MessageRecordUtil.isMediaMessage(record) && MediaUtil.isImageType(getMessageContentType((MmsMessageRecord) record))) {
- return context.getString(R.string.MessageNotifier_reacted_s_to_your_image, EMOJI_REPLACEMENT_STRING);
- } else if (MessageRecordUtil.isMediaMessage(record) && MediaUtil.isAudioType(getMessageContentType((MmsMessageRecord) record))) {
- return context.getString(R.string.MessageNotifier_reacted_s_to_your_audio, EMOJI_REPLACEMENT_STRING);
- } else if (MessageRecordUtil.isMediaMessage(record)) {
- return context.getString(R.string.MessageNotifier_reacted_s_to_your_file, EMOJI_REPLACEMENT_STRING);
- } else {
- return context.getString(R.string.MessageNotifier_reacted_s_to_s, EMOJI_REPLACEMENT_STRING, body);
- }
- }
-
- private static @StringRes int getViewOnceDescription(@NonNull MmsMessageRecord messageRecord) {
- final String contentType = getMessageContentType(messageRecord);
-
- if (MediaUtil.isImageType(contentType)) {
- return R.string.MessageNotifier_view_once_photo;
- }
- return R.string.MessageNotifier_view_once_video;
- }
-
- private static String getMessageContentType(@NonNull MmsMessageRecord messageRecord) {
- Slide thumbnailSlide = messageRecord.getSlideDeck().getThumbnailSlide();
- if (thumbnailSlide == null) {
- String slideContentType = messageRecord.getSlideDeck().getFirstSlideContentType();
- if (slideContentType != null) {
- return slideContentType;
- }
- Log.w(TAG, "Could not distinguish view-once content type from message record, defaulting to JPEG");
- return MediaUtil.IMAGE_JPEG;
- }
- return thumbnailSlide.getContentType();
- }
-
- private static void updateBadge(Context context, int count) {
- try {
- if (count == 0) ShortcutBadger.removeCount(context);
- else ShortcutBadger.applyCount(context, count);
- } catch (Throwable t) {
- // NOTE :: I don't totally trust this thing, so I'm catching
- // everything.
- Log.w(TAG, t);
- }
- }
-
- private static void scheduleReminder(Context context, int count) {
- if (count >= SignalStore.settings().getMessageNotificationsRepeatAlerts()) {
- return;
- }
-
- AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- Intent alarmIntent = new Intent(context, ReminderReceiver.class);
- alarmIntent.putExtra("reminder_count", count);
-
- PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);
- long timeout = TimeUnit.MINUTES.toMillis(2);
-
- alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeout, pendingIntent);
- }
-
- private static void clearNotificationRingtone(@NonNull Context context, @NonNull Recipient recipient) {
- SignalExecutors.BOUNDED.execute(() -> {
- DatabaseFactory.getRecipientDatabase(context).setMessageRingtone(recipient.getId(), null);
- NotificationChannels.updateMessageRingtone(context, recipient, null);
- });
- }
-
- @Override
- public void clearReminder(@NonNull Context context) {
- Intent alarmIntent = new Intent(context, ReminderReceiver.class);
- PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT);
- AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-
- alarmManager.cancel(pendingIntent);
- }
-
- @Override
- public void addStickyThread(long threadId, long earliestTimestamp) {}
-
- @Override
- public void removeStickyThread(long threadId) {}
-
- private static class DelayedNotification implements Runnable {
-
- private static final long DELAY = TimeUnit.SECONDS.toMillis(5);
-
- private final AtomicBoolean canceled = new AtomicBoolean(false);
-
- private final Context context;
- private final long threadId;
- private final long delayUntil;
-
- private DelayedNotification(Context context, long threadId) {
- this.context = context;
- this.threadId = threadId;
- this.delayUntil = System.currentTimeMillis() + DELAY;
- }
-
- @Override
- public void run() {
- long delayMillis = delayUntil - System.currentTimeMillis();
- Log.i(TAG, "Waiting to notify: " + delayMillis);
-
- if (delayMillis > 0) {
- Util.sleep(delayMillis);
- }
-
- if (!canceled.get()) {
- Log.i(TAG, "Not canceled, notifying...");
- ApplicationDependencies.getMessageNotifier().updateNotification(context, threadId, true);
- ApplicationDependencies.getMessageNotifier().cancelDelayedNotifications();
- } else {
- Log.w(TAG, "Canceled, not notifying...");
- }
- }
-
- public void cancel() {
- canceled.set(true);
- }
- }
-
- private static class CancelableExecutor {
-
- private final Executor executor = Executors.newSingleThreadExecutor();
- private final Set tasks = new HashSet<>();
-
- public void execute(final DelayedNotification runnable) {
- synchronized (tasks) {
- tasks.add(runnable);
- }
-
- Runnable wrapper = () -> {
- runnable.run();
-
- synchronized (tasks) {
- tasks.remove(runnable);
- }
- };
-
- executor.execute(wrapper);
- }
-
- public void cancel() {
- synchronized (tasks) {
- for (DelayedNotification task : tasks) {
- task.cancel();
- }
- }
- }
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/FailedNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/FailedNotificationBuilder.java
deleted file mode 100644
index 084785932b..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/FailedNotificationBuilder.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.thoughtcrime.securesms.notifications;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.BitmapFactory;
-
-import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.database.RecipientDatabase;
-import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
-
-public class FailedNotificationBuilder extends AbstractNotificationBuilder {
-
- public FailedNotificationBuilder(Context context, NotificationPrivacyPreference privacy, Intent intent) {
- super(context, privacy);
-
- setSmallIcon(R.drawable.ic_notification);
- setLargeIcon(BitmapFactory.decodeResource(context.getResources(),
- R.drawable.ic_action_warning_red));
- setContentTitle(context.getString(R.string.MessageNotifier_message_delivery_failed));
- setContentText(context.getString(R.string.MessageNotifier_failed_to_deliver_message));
- setTicker(context.getString(R.string.MessageNotifier_error_delivering_message));
- setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
- setAutoCancel(true);
- setAlarms(null, RecipientDatabase.VibrateState.DEFAULT);
- setChannelId(NotificationChannels.FAILURES);
- }
-
-
-
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java
deleted file mode 100644
index f9ebfa8304..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package org.thoughtcrime.securesms.notifications;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.app.NotificationCompat;
-
-import org.thoughtcrime.securesms.MainActivity;
-import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
-import org.thoughtcrime.securesms.recipients.Recipient;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
-import org.thoughtcrime.securesms.util.Util;
-
-import java.util.LinkedList;
-import java.util.List;
-
-public class MultipleRecipientNotificationBuilder extends AbstractNotificationBuilder {
-
- private final List messageBodies = new LinkedList<>();
-
- public MultipleRecipientNotificationBuilder(Context context, NotificationPrivacyPreference privacy) {
- super(context, privacy);
-
- setColor(context.getResources().getColor(R.color.core_ultramarine));
- setSmallIcon(R.drawable.ic_notification);
- setContentTitle(context.getString(R.string.app_name));
- // TODO [greyson] Navigation
- setContentIntent(PendingIntent.getActivity(context, 0, MainActivity.clearTop(context), 0));
- setCategory(NotificationCompat.CATEGORY_MESSAGE);
- setGroupSummary(true);
-
- if (!NotificationChannels.supported()) {
- setPriority(TextSecurePreferences.getNotificationPriority(context));
- }
- }
-
- public void setMessageCount(int messageCount, int threadCount) {
- setSubText(context.getString(R.string.MessageNotifier_d_new_messages_in_d_conversations,
- messageCount, threadCount));
- setContentInfo(String.valueOf(messageCount));
- setNumber(messageCount);
- }
-
- public void setMostRecentSender(Recipient recipient) {
- if (privacy.isDisplayContact()) {
- setContentText(context.getString(R.string.MessageNotifier_most_recent_from_s,
- recipient.getDisplayName(context)));
- }
-
- if (recipient.getNotificationChannel() != null) {
- setChannelId(recipient.getNotificationChannel());
- }
- }
-
- public void addActions(PendingIntent markAsReadIntent) {
- NotificationCompat.Action markAllAsReadAction = new NotificationCompat.Action(R.drawable.check,
- context.getString(R.string.MessageNotifier_mark_all_as_read),
- markAsReadIntent);
- addAction(markAllAsReadAction);
- extend(new NotificationCompat.WearableExtender().addAction(markAllAsReadAction));
- }
-
- public void addMessageBody(@NonNull Recipient sender, @Nullable CharSequence body) {
- if (privacy.isDisplayMessage()) {
- messageBodies.add(getStyledMessage(sender, body));
- } else if (privacy.isDisplayContact()) {
- messageBodies.add(Util.getBoldedString(sender.getDisplayName(context)));
- }
-
- if (privacy.isDisplayContact() && sender.getContactUri() != null) {
- addPerson(sender.getContactUri().toString());
- }
- }
-
- @Override
- public Notification build() {
- if (privacy.isDisplayMessage() || privacy.isDisplayContact()) {
- NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
-
- for (CharSequence body : messageBodies) {
- style.addLine(trimToDisplayLength(body));
- }
-
- setStyle(style);
- }
-
- return super.build();
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCancellationHelper.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCancellationHelper.java
index 842fe59ac6..904bffb9a2 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCancellationHelper.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCancellationHelper.java
@@ -14,6 +14,7 @@ import com.annimon.stream.Stream;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
+import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.BubbleUtil;
import org.thoughtcrime.securesms.util.ConversationUtil;
@@ -44,7 +45,7 @@ public final class NotificationCancellationHelper {
/**
* Cancels all Message-Based notifications. Specifically, this is any notification that is not the
- * summary notification assigned to the {@link DefaultMessageNotifier#NOTIFICATION_GROUP} group.
+ * summary notification assigned to the {@link MessageNotifierV2#NOTIFICATION_GROUP} group.
*
* We utilize our wrapped cancellation methods and a counter to make sure that we do not lose
* bubble notifications that do not have unread messages in them.
@@ -110,7 +111,7 @@ public final class NotificationCancellationHelper {
@RequiresApi(23)
private static boolean isSingleThreadNotification(@NonNull StatusBarNotification statusBarNotification) {
return statusBarNotification.getId() != NotificationIds.MESSAGE_SUMMARY &&
- Objects.equals(statusBarNotification.getNotification().getGroup(), DefaultMessageNotifier.NOTIFICATION_GROUP);
+ Objects.equals(statusBarNotification.getNotification().getGroup(), MessageNotifierV2.NOTIFICATION_GROUP);
}
/**
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java
deleted file mode 100644
index 66b679d54f..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java
+++ /dev/null
@@ -1,131 +0,0 @@
-package org.thoughtcrime.securesms.notifications;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.app.TaskStackBuilder;
-
-import org.thoughtcrime.securesms.conversation.ConversationIntents;
-import org.thoughtcrime.securesms.database.DatabaseFactory;
-import org.thoughtcrime.securesms.mms.SlideDeck;
-import org.thoughtcrime.securesms.recipients.Recipient;
-
-public class NotificationItem {
-
- private final long id;
- private final boolean mms;
- @NonNull private final Recipient conversationRecipient;
- @NonNull private final Recipient individualRecipient;
- @Nullable private final Recipient threadRecipient;
- private final long threadId;
- @Nullable private final CharSequence text;
- private final long timestamp;
- private final long messageReceivedTimestamp;
- @Nullable private final SlideDeck slideDeck;
- private final boolean jumpToMessage;
- private final boolean isJoin;
- private final boolean canReply;
- private final long notifiedTimestamp;
-
- public NotificationItem(long id,
- boolean mms,
- @NonNull Recipient individualRecipient,
- @NonNull Recipient conversationRecipient,
- @Nullable Recipient threadRecipient,
- long threadId,
- @Nullable CharSequence text,
- long timestamp,
- long messageReceivedTimestamp,
- @Nullable SlideDeck slideDeck,
- boolean jumpToMessage,
- boolean isJoin,
- boolean canReply,
- long notifiedTimestamp)
- {
- this.id = id;
- this.mms = mms;
- this.individualRecipient = individualRecipient;
- this.conversationRecipient = conversationRecipient;
- this.threadRecipient = threadRecipient;
- this.text = text;
- this.threadId = threadId;
- this.timestamp = timestamp;
- this.messageReceivedTimestamp = messageReceivedTimestamp;
- this.slideDeck = slideDeck;
- this.jumpToMessage = jumpToMessage;
- this.isJoin = isJoin;
- this.canReply = canReply;
- this.notifiedTimestamp = notifiedTimestamp;
- }
-
- public @NonNull Recipient getRecipient() {
- return threadRecipient == null ? conversationRecipient : threadRecipient;
- }
-
- public @NonNull Recipient getIndividualRecipient() {
- return individualRecipient;
- }
-
- public @Nullable CharSequence getText() {
- return text;
- }
-
- public long getTimestamp() {
- return timestamp;
- }
-
- public long getThreadId() {
- return threadId;
- }
-
- public @Nullable SlideDeck getSlideDeck() {
- return slideDeck;
- }
-
- public PendingIntent getPendingIntent(Context context) {
- Recipient recipient = threadRecipient != null ? threadRecipient : conversationRecipient;
- int startingPosition = jumpToMessage ? getStartingPosition(context, threadId, messageReceivedTimestamp) : -1;
-
- Intent intent = ConversationIntents.createBuilder(context, recipient.getId(), threadId)
- .withStartingPosition(startingPosition)
- .build();
-
- makeIntentUniqueToPreventMerging(intent);
-
- return TaskStackBuilder.create(context)
- .addNextIntentWithParentStack(intent)
- .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- public long getId() {
- return id;
- }
-
- public boolean isMms() {
- return mms;
- }
-
- public boolean isJoin() {
- return isJoin;
- }
-
- private static int getStartingPosition(@NonNull Context context, long threadId, long receivedTimestampMs) {
- return DatabaseFactory.getMmsSmsDatabase(context).getMessagePositionInConversation(threadId, receivedTimestampMs);
- }
-
- private static void makeIntentUniqueToPreventMerging(@NonNull Intent intent) {
- intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
- }
-
- public boolean canReply() {
- return canReply;
- }
-
- public long getNotifiedTimestamp() {
- return notifiedTimestamp;
- }
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java
deleted file mode 100644
index 2f905d45d9..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java
+++ /dev/null
@@ -1,213 +0,0 @@
-package org.thoughtcrime.securesms.notifications;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.signal.core.util.logging.Log;
-import org.thoughtcrime.securesms.contacts.TurnOffContactJoinedNotificationsActivity;
-import org.thoughtcrime.securesms.conversation.ConversationIntents;
-import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState;
-import org.thoughtcrime.securesms.recipients.Recipient;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.List;
-
-public class NotificationState {
-
- private static final String TAG = Log.tag(NotificationState.class);
-
- private final Comparator notificationItemComparator = (a, b) -> -Long.compare(a.getTimestamp(), b.getTimestamp());
- private final List notifications = new LinkedList<>();
- private final LinkedHashSet threads = new LinkedHashSet<>();
-
- public NotificationState() {}
-
- public NotificationState(@NonNull List items) {
- for (NotificationItem item : items) {
- addNotification(item);
- }
- }
-
- public void addNotification(NotificationItem item) {
- notifications.add(item);
- Collections.sort(notifications, notificationItemComparator);
-
- threads.remove(item.getThreadId());
- threads.add(item.getThreadId());
- }
-
- public @Nullable Uri getRingtone(@NonNull Context context) {
- if (!notifications.isEmpty()) {
- Recipient recipient = notifications.get(0).getRecipient();
-
- if (recipient != null) {
- return NotificationChannels.supported() ? NotificationChannels.getMessageRingtone(context, recipient)
- : recipient.resolve().getMessageRingtone();
- }
- }
-
- return null;
- }
-
- public VibrateState getVibrate() {
- if (!notifications.isEmpty()) {
- Recipient recipient = notifications.get(0).getRecipient();
-
- if (recipient != null) {
- return recipient.resolve().getMessageVibrate();
- }
- }
-
- return VibrateState.DEFAULT;
- }
-
- public boolean hasMultipleThreads() {
- return threads.size() > 1;
- }
-
- public Collection getThreads() {
- return threads;
- }
-
- public int getThreadCount() {
- return threads.size();
- }
-
- public int getMessageCount() {
- return notifications.size();
- }
-
- public List getNotifications() {
- return notifications;
- }
-
- public List getNotificationsForThread(long threadId) {
- List list = new LinkedList<>();
-
- for (NotificationItem item : notifications) {
- if (item.getThreadId() == threadId) list.add(item);
- }
-
- Collections.sort(list, notificationItemComparator);
- return list;
- }
-
- public PendingIntent getTurnOffTheseNotificationsIntent(Context context) {
- long threadId = threads.iterator().next();
-
- return PendingIntent.getActivity(context,
- 0,
- TurnOffContactJoinedNotificationsActivity.newIntent(context, threadId),
- PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- public PendingIntent getMarkAsReadIntent(Context context, int notificationId) {
- long[] threadArray = new long[threads.size()];
- int index = 0;
- StringBuilder threadString = new StringBuilder();
-
- for (long thread : threads) {
- threadString.append(thread).append(" ");
- threadArray[index++] = thread;
- }
-
- Log.i(TAG, "Added threads: " + threadString.toString());
-
- Intent intent = new Intent(MarkReadReceiver.CLEAR_ACTION);
- intent.setClass(context, MarkReadReceiver.class);
- intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
- intent.putExtra(MarkReadReceiver.THREAD_IDS_EXTRA, threadArray);
- intent.putExtra(MarkReadReceiver.NOTIFICATION_ID_EXTRA, notificationId);
-
- return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- public PendingIntent getRemoteReplyIntent(Context context, Recipient recipient, ReplyMethod replyMethod) {
- if (threads.size() != 1) throw new AssertionError("We only support replies to single thread notifications!");
-
- Intent intent = new Intent(RemoteReplyReceiver.REPLY_ACTION);
- intent.setClass(context, RemoteReplyReceiver.class);
- intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
- intent.putExtra(RemoteReplyReceiver.RECIPIENT_EXTRA, recipient.getId());
- intent.putExtra(RemoteReplyReceiver.REPLY_METHOD, replyMethod);
- intent.setPackage(context.getPackageName());
-
- return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- public PendingIntent getAndroidAutoReplyIntent(Context context, Recipient recipient) {
- if (threads.size() != 1) throw new AssertionError("We only support replies to single thread notifications!");
-
- Intent intent = new Intent(AndroidAutoReplyReceiver.REPLY_ACTION);
- intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
- intent.setClass(context, AndroidAutoReplyReceiver.class);
- intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
- intent.putExtra(AndroidAutoReplyReceiver.RECIPIENT_EXTRA, recipient.getId());
- intent.putExtra(AndroidAutoReplyReceiver.THREAD_ID_EXTRA, (long)threads.toArray()[0]);
- intent.setPackage(context.getPackageName());
-
- return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- public PendingIntent getAndroidAutoHeardIntent(Context context, int notificationId) {
- long[] threadArray = new long[threads.size()];
- int index = 0;
- for (long thread : threads) {
- Log.i(TAG, "getAndroidAutoHeardIntent Added thread: " + thread);
- threadArray[index++] = thread;
- }
-
- Intent intent = new Intent(AndroidAutoHeardReceiver.HEARD_ACTION);
- intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
- intent.setClass(context, AndroidAutoHeardReceiver.class);
- intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
- intent.putExtra(AndroidAutoHeardReceiver.THREAD_IDS_EXTRA, threadArray);
- intent.putExtra(AndroidAutoHeardReceiver.NOTIFICATION_ID_EXTRA, notificationId);
- intent.setPackage(context.getPackageName());
-
- return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- public PendingIntent getQuickReplyIntent(Context context, Recipient recipient) {
- if (threads.size() != 1) throw new AssertionError("We only support replies to single thread notifications! " + threads.size());
-
- Intent intent = ConversationIntents.createPopUpBuilder(context, recipient.getId(), (long) threads.toArray()[0])
- .withDataUri(Uri.parse("custom://"+System.currentTimeMillis()))
- .build();
-
- return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- public PendingIntent getDeleteIntent(Context context) {
- int index = 0;
- long[] ids = new long[notifications.size()];
- boolean[] mms = new boolean[ids.length];
-
- for (NotificationItem notificationItem : notifications) {
- ids[index] = notificationItem.getId();
- mms[index++] = notificationItem.isMms();
- }
-
- Intent intent = new Intent(context, DeleteNotificationReceiver.class);
- intent.setAction(DeleteNotificationReceiver.DELETE_NOTIFICATION_ACTION);
- intent.putExtra(DeleteNotificationReceiver.EXTRA_IDS, ids);
- intent.putExtra(DeleteNotificationReceiver.EXTRA_MMS, mms);
- intent.setData((Uri.parse("custom://"+System.currentTimeMillis())));
-
- return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- public boolean canReply() {
- return notifications.size() >= 1 && notifications.get(0).canReply();
- }
-
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java
index 406fd644c5..3151ea3d65 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java
@@ -11,7 +11,6 @@ import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.BubbleUtil;
-import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.LeakyBucketLimiter;
import org.thoughtcrime.securesms.util.Util;
@@ -21,14 +20,11 @@ import org.thoughtcrime.securesms.util.Util;
public class OptimizedMessageNotifier implements MessageNotifier {
private final LeakyBucketLimiter limiter;
-
- private final DefaultMessageNotifier messageNotifierV1;
- private final MessageNotifierV2 messageNotifierV2;
+ private final MessageNotifierV2 messageNotifierV2;
@MainThread
public OptimizedMessageNotifier(@NonNull Application context) {
this.limiter = new LeakyBucketLimiter(5, 1000, new Handler(SignalExecutors.getAndStartHandlerThread("signal-notifier").getLooper()));
- this.messageNotifierV1 = new DefaultMessageNotifier();
this.messageNotifierV2 = new MessageNotifierV2(context);
}
@@ -119,10 +115,6 @@ public class OptimizedMessageNotifier implements MessageNotifier {
}
private MessageNotifier getNotifier() {
- if (FeatureFlags.useNewNotificationSystem()) {
- return messageNotifierV2;
- } else {
- return messageNotifierV1;
- }
+ return messageNotifierV2;
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java
index 599f057a8d..92237250d4 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java
@@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessageDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
+import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sms.MessageSender;
@@ -61,7 +62,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
final RecipientId recipientId = intent.getParcelableExtra(RECIPIENT_EXTRA);
final ReplyMethod replyMethod = (ReplyMethod) intent.getSerializableExtra(REPLY_METHOD);
- final CharSequence responseText = remoteInput.getCharSequence(DefaultMessageNotifier.EXTRA_REMOTE_REPLY);
+ final CharSequence responseText = remoteInput.getCharSequence(MessageNotifierV2.EXTRA_REMOTE_REPLY);
if (recipientId == null) throw new AssertionError("No recipientId specified");
if (replyMethod == null) throw new AssertionError("No reply method specified");
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java
deleted file mode 100644
index a659cd97a2..0000000000
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java
+++ /dev/null
@@ -1,455 +0,0 @@
-package org.thoughtcrime.securesms.notifications;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Build;
-import android.text.SpannableStringBuilder;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.StringRes;
-import androidx.appcompat.view.ContextThemeWrapper;
-import androidx.core.app.NotificationCompat;
-import androidx.core.app.NotificationCompat.Action;
-import androidx.core.app.Person;
-import androidx.core.app.RemoteInput;
-import androidx.core.graphics.drawable.IconCompat;
-
-import com.annimon.stream.Stream;
-import com.bumptech.glide.load.MultiTransformation;
-import com.bumptech.glide.load.Transformation;
-import com.bumptech.glide.load.engine.DiskCacheStrategy;
-import com.bumptech.glide.load.resource.bitmap.CircleCrop;
-
-import org.signal.core.util.logging.Log;
-import org.thoughtcrime.securesms.R;
-import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto;
-import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto;
-import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto;
-import org.thoughtcrime.securesms.conversation.ConversationIntents;
-import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
-import org.thoughtcrime.securesms.database.DatabaseFactory;
-import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
-import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
-import org.thoughtcrime.securesms.mms.GlideApp;
-import org.thoughtcrime.securesms.mms.Slide;
-import org.thoughtcrime.securesms.mms.SlideDeck;
-import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
-import org.thoughtcrime.securesms.recipients.Recipient;
-import org.thoughtcrime.securesms.util.AvatarUtil;
-import org.thoughtcrime.securesms.util.BitmapUtil;
-import org.thoughtcrime.securesms.util.BlurTransformation;
-import org.thoughtcrime.securesms.util.BubbleUtil;
-import org.thoughtcrime.securesms.util.ConversationUtil;
-import org.thoughtcrime.securesms.util.TextSecurePreferences;
-import org.thoughtcrime.securesms.util.Util;
-import org.whispersystems.libsignal.util.guava.Optional;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-
-public class SingleRecipientNotificationBuilder extends AbstractNotificationBuilder {
-
- private static final String TAG = Log.tag(SingleRecipientNotificationBuilder.class);
-
- private static final int BIG_PICTURE_DIMEN = 500;
- private static final int LARGE_ICON_DIMEN = 250;
-
- private final List messages = new LinkedList<>();
-
- private SlideDeck slideDeck;
- private CharSequence contentTitle;
- private CharSequence contentText;
- private Recipient threadRecipient;
- private BubbleUtil.BubbleState defaultBubbleState;
-
- public SingleRecipientNotificationBuilder(@NonNull Context context, @NonNull NotificationPrivacyPreference privacy)
- {
- super(new ContextThemeWrapper(context, R.style.TextSecure_LightTheme), privacy);
-
- setSmallIcon(R.drawable.ic_notification);
- setColor(context.getResources().getColor(R.color.core_ultramarine));
- setCategory(NotificationCompat.CATEGORY_MESSAGE);
-
- if (!NotificationChannels.supported()) {
- setPriority(TextSecurePreferences.getNotificationPriority(context));
- }
- }
-
- public void setThread(@NonNull Recipient recipient) {
- String channelId = recipient.getNotificationChannel();
- setChannelId(channelId != null ? channelId : NotificationChannels.getMessagesChannel(context));
-
- if (privacy.isDisplayContact()) {
- setContentTitle(recipient.getDisplayName(context));
-
- if (recipient.getContactUri() != null) {
- addPerson(recipient.getContactUri().toString());
- }
-
- setLargeIcon(getContactDrawable(recipient));
-
- } else {
- setContentTitle(context.getString(R.string.SingleRecipientNotificationBuilder_signal));
- setLargeIcon(new GeneratedContactPhoto("Unknown", R.drawable.ic_profile_outline_40).asDrawable(context, AvatarColor.UNKNOWN.colorInt()));
- }
-
- setShortcutId(ConversationUtil.getShortcutId(recipient));
- }
-
- private Drawable getContactDrawable(@NonNull Recipient recipient) {
- ContactPhoto contactPhoto = recipient.getContactPhoto();
- FallbackContactPhoto fallbackContactPhoto = recipient.getFallbackContactPhoto();
-
- if (contactPhoto != null) {
- try {
- List> transforms = new ArrayList<>();
- if (recipient.shouldBlurAvatar()) {
- transforms.add(new BlurTransformation(ApplicationDependencies.getApplication(), 0.25f, BlurTransformation.MAX_RADIUS));
- }
- transforms.add(new CircleCrop());
-
- return GlideApp.with(context.getApplicationContext())
- .load(contactPhoto)
- .diskCacheStrategy(DiskCacheStrategy.ALL)
- .transform(new MultiTransformation<>(transforms))
- .submit(context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
- context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height))
- .get();
- } catch (InterruptedException | ExecutionException e) {
- return fallbackContactPhoto.asDrawable(context, recipient.getAvatarColor().colorInt());
- }
- } else {
- return fallbackContactPhoto.asDrawable(context, recipient.getAvatarColor().colorInt());
- }
- }
-
- public void setMessageCount(int messageCount) {
- setContentInfo(String.valueOf(messageCount));
- setNumber(messageCount);
- }
-
- public void setPrimaryMessageBody(@NonNull Recipient threadRecipients,
- @NonNull Recipient individualRecipient,
- @NonNull CharSequence message,
- @Nullable SlideDeck slideDeck)
- {
- SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
-
- if (privacy.isDisplayContact() && threadRecipients.isGroup()) {
- stringBuilder.append(Util.getBoldedString(individualRecipient.getDisplayName(context) + ": "));
- }
-
- if (privacy.isDisplayMessage()) {
- setContentText(stringBuilder.append(message));
- this.slideDeck = slideDeck;
- } else {
- setContentText(stringBuilder.append(context.getString(R.string.SingleRecipientNotificationBuilder_new_message)));
- }
- }
-
- public void addAndroidAutoAction(@NonNull PendingIntent androidAutoReplyIntent,
- @NonNull PendingIntent androidAutoHeardIntent, long timestamp)
- {
-
- if (contentTitle == null || contentText == null)
- return;
-
- RemoteInput remoteInput = new RemoteInput.Builder(AndroidAutoReplyReceiver.VOICE_REPLY_KEY)
- .setLabel(context.getString(R.string.MessageNotifier_reply))
- .build();
-
- NotificationCompat.CarExtender.UnreadConversation.Builder unreadConversationBuilder =
- new NotificationCompat.CarExtender.UnreadConversation.Builder(contentTitle.toString())
- .addMessage(contentText.toString())
- .setLatestTimestamp(timestamp)
- .setReadPendingIntent(androidAutoHeardIntent)
- .setReplyAction(androidAutoReplyIntent, remoteInput);
-
- extend(new NotificationCompat.CarExtender().setUnreadConversation(unreadConversationBuilder.build()));
- }
-
- public void addTurnOffTheseNotificationsAction(@NonNull PendingIntent turnOffTheseNotificationsIntent) {
- Action turnOffTheseNotifications = new Action(R.drawable.check,
- context.getString(R.string.MessageNotifier_turn_off_these_notifications),
- turnOffTheseNotificationsIntent);
-
- addAction(turnOffTheseNotifications);
- }
-
- public void addActions(@NonNull PendingIntent markReadIntent,
- @NonNull PendingIntent quickReplyIntent,
- @NonNull PendingIntent wearableReplyIntent,
- @NonNull ReplyMethod replyMethod,
- boolean replyEnabled)
- {
- NotificationCompat.WearableExtender extender = new NotificationCompat.WearableExtender();
- Action markAsReadAction = new Action(R.drawable.check,
- context.getString(R.string.MessageNotifier_mark_read),
- markReadIntent);
-
- addAction(markAsReadAction);
- extender.addAction(markAsReadAction);
-
- if (replyEnabled) {
- String actionName = context.getString(R.string.MessageNotifier_reply);
- String label = context.getString(replyMethodLongDescription(replyMethod));
-
- Action replyAction = new Action(R.drawable.ic_reply_white_36dp,
- actionName,
- quickReplyIntent);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- replyAction = new Action.Builder(R.drawable.ic_reply_white_36dp,
- actionName,
- wearableReplyIntent)
- .addRemoteInput(new RemoteInput.Builder(DefaultMessageNotifier.EXTRA_REMOTE_REPLY)
- .setLabel(label)
- .build())
- .build();
- }
-
- Action wearableReplyAction = new Action.Builder(R.drawable.ic_reply,
- actionName,
- wearableReplyIntent)
- .addRemoteInput(new RemoteInput.Builder(DefaultMessageNotifier.EXTRA_REMOTE_REPLY)
- .setLabel(label)
- .build())
- .build();
-
- addAction(replyAction);
- extend(extender.addAction(wearableReplyAction));
- }
- }
-
- @StringRes
- private static int replyMethodLongDescription(@NonNull ReplyMethod replyMethod) {
- switch (replyMethod) {
- case GroupMessage:
- return R.string.MessageNotifier_reply;
- case SecureMessage:
- return R.string.MessageNotifier_signal_message;
- case UnsecuredSmsMessage:
- return R.string.MessageNotifier_unsecured_sms;
- default:
- return R.string.MessageNotifier_reply;
- }
- }
-
- public void addMessageBody(@NonNull Recipient threadRecipient,
- @NonNull Recipient individualRecipient,
- @Nullable CharSequence messageBody,
- long timestamp,
- @Nullable SlideDeck slideDeck)
- {
- SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
- Person.Builder personBuilder = new Person.Builder()
- .setKey(ConversationUtil.getShortcutId(individualRecipient))
- .setBot(false);
-
- this.threadRecipient = threadRecipient;
-
- if (privacy.isDisplayContact()) {
- personBuilder.setName(individualRecipient.getDisplayName(context));
- personBuilder.setUri(individualRecipient.isSystemContact() ? individualRecipient.getContactUri().toString() : null);
-
- Bitmap bitmap = getLargeBitmap(getContactDrawable(individualRecipient));
- if (bitmap != null) {
- personBuilder.setIcon(IconCompat.createWithBitmap(bitmap));
- }
- } else {
- personBuilder.setName("");
- }
-
- final CharSequence text;
- if (privacy.isDisplayMessage()) {
- text = messageBody == null ? "" : messageBody;
- } else {
- text = stringBuilder.append(context.getString(R.string.SingleRecipientNotificationBuilder_new_message));
- }
-
- Uri dataUri = null;
- String mimeType = null;
-
- if (slideDeck != null && slideDeck.getThumbnailSlide() != null) {
- Slide thumbnail = slideDeck.getThumbnailSlide();
-
- if (Build.VERSION.SDK_INT >= 28) {
- dataUri = thumbnail.getPublicUri();
- } else {
- dataUri = thumbnail.getUri();
- }
-
- mimeType = thumbnail.getContentType();
- }
-
- messages.add(new NotificationCompat.MessagingStyle.Message(text, timestamp, personBuilder.build()).setData(mimeType, dataUri));
- }
-
- public void setDefaultBubbleState(@NonNull BubbleUtil.BubbleState bubbleState) {
- this.defaultBubbleState = bubbleState;
- }
-
- @Override
- public Notification build() {
- if (privacy.isDisplayMessage()) {
- Optional largeIconUri = getLargeIconUri(slideDeck);
- Optional bigPictureUri = getBigPictureUri(slideDeck);
-
- if (messages.size() == 1 && largeIconUri.isPresent()) {
- setLargeIcon(getNotificationPicture(largeIconUri.get(), LARGE_ICON_DIMEN));
- }
-
- if (messages.size() == 1 && bigPictureUri.isPresent() && Build.VERSION.SDK_INT < ConversationUtil.CONVERSATION_SUPPORT_VERSION) {
- setStyle(new NotificationCompat.BigPictureStyle()
- .bigPicture(getNotificationPicture(bigPictureUri.get(), BIG_PICTURE_DIMEN))
- .setSummaryText(getBigText()));
- } else {
- if (Build.VERSION.SDK_INT >= 24) {
- applyMessageStyle();
-
- if (Build.VERSION.SDK_INT >= ConversationUtil.CONVERSATION_SUPPORT_VERSION) {
- applyBubbleMetadata();
- }
- } else {
- applyLegacy();
- }
- }
- }
-
- return super.build();
- }
-
- private void applyMessageStyle() {
- ConversationUtil.pushShortcutForRecipientIfNeededSync(context, threadRecipient);
- NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(ConversationUtil.buildPersonCompat(context, Recipient.self()));
-
- if (threadRecipient.isGroup()) {
- if (privacy.isDisplayContact()) {
- messagingStyle.setConversationTitle(threadRecipient.getDisplayName(context));
- } else {
- messagingStyle.setConversationTitle(context.getString(R.string.SingleRecipientNotificationBuilder_signal));
- }
-
- messagingStyle.setGroupConversation(true);
- }
-
- Stream.of(messages).forEach(messagingStyle::addMessage);
- setStyle(messagingStyle);
- }
-
- private void applyBubbleMetadata() {
- long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(threadRecipient);
- PendingIntent intent = PendingIntent.getActivity(context, 0, ConversationIntents.createBubbleIntent(context, threadRecipient.getId(), threadId), 0);
- NotificationCompat.BubbleMetadata bubbleMetadata = new NotificationCompat.BubbleMetadata.Builder()
- .setAutoExpandBubble(defaultBubbleState == BubbleUtil.BubbleState.SHOWN)
- .setDesiredHeight(600)
- .setIcon(AvatarUtil.getIconCompatForShortcut(context, threadRecipient))
- .setSuppressNotification(defaultBubbleState == BubbleUtil.BubbleState.SHOWN)
- .setIntent(intent)
- .build();
- setBubbleMetadata(bubbleMetadata);
- }
-
- private void applyLegacy() {
- setStyle(new NotificationCompat.BigTextStyle().bigText(getBigText()));
- }
-
- private void setLargeIcon(@Nullable Drawable drawable) {
- if (drawable != null) {
- setLargeIcon(getLargeBitmap(drawable));
- }
- }
-
- private @Nullable Bitmap getLargeBitmap(@Nullable Drawable drawable) {
- if (drawable != null) {
- int largeIconTargetSize = context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size);
-
- return BitmapUtil.createFromDrawable(drawable, largeIconTargetSize, largeIconTargetSize);
- }
-
- return null;
- }
-
- private static Optional getLargeIconUri(@Nullable SlideDeck slideDeck) {
- if (slideDeck == null) {
- return Optional.absent();
- }
-
- Slide thumbnailSlide = Optional.fromNullable(slideDeck.getThumbnailSlide()).or(Optional.fromNullable(slideDeck.getStickerSlide())).orNull();
- return getThumbnailUri(thumbnailSlide);
- }
-
- private static Optional getBigPictureUri(@Nullable SlideDeck slideDeck) {
- if (slideDeck == null) {
- return Optional.absent();
- }
-
- Slide thumbnailSlide = slideDeck.getThumbnailSlide();
- return getThumbnailUri(thumbnailSlide);
- }
-
- private static Optional getThumbnailUri(@Nullable Slide slide) {
- if (slide != null && !slide.isInProgress() && slide.getUri() != null) {
- return Optional.of(slide.getUri());
- } else {
- return Optional.absent();
- }
- }
-
- private Bitmap getNotificationPicture(@NonNull Uri uri, int dimension)
- {
- try {
- return GlideApp.with(context.getApplicationContext())
- .asBitmap()
- .load(new DecryptableStreamUriLoader.DecryptableUri(uri))
- .diskCacheStrategy(DiskCacheStrategy.NONE)
- .submit(dimension, dimension)
- .get();
- } catch (InterruptedException | ExecutionException e) {
- Log.w(TAG, e);
- return Bitmap.createBitmap(dimension, dimension, Bitmap.Config.RGB_565);
- }
- }
-
- @Override
- public NotificationCompat.Builder setContentTitle(CharSequence contentTitle) {
- this.contentTitle = contentTitle;
- return super.setContentTitle(contentTitle);
- }
-
- public NotificationCompat.Builder setContentText(CharSequence contentText) {
- this.contentText = trimToDisplayLength(contentText);
- return super.setContentText(this.contentText);
- }
-
- private CharSequence getBigText() {
- SpannableStringBuilder content = new SpannableStringBuilder();
-
- for (int i = 0; i < messages.size(); i++) {
- content.append(getBigTextFor(messages.get(i)));
- if (i < messages.size() - 1) {
- content.append('\n');
- }
- }
-
- return content;
- }
-
- private CharSequence getBigTextFor(NotificationCompat.MessagingStyle.Message message) {
- SpannableStringBuilder content = new SpannableStringBuilder();
-
- if (message.getPerson() != null && message.getPerson().getName() != null && threadRecipient.isGroup()) {
- content.append(Util.getBoldedString(message.getPerson().getName().toString())).append(": ");
- }
-
- return trimToDisplayLength(content.append(message.getText()));
- }
-
-}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt
index ccb43afac1..88306b7835 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt
@@ -18,7 +18,6 @@ import org.thoughtcrime.securesms.database.MessageDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.messages.IncomingMessageObserver
-import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier
import org.thoughtcrime.securesms.notifications.MessageNotifier
import org.thoughtcrime.securesms.notifications.MessageNotifier.ReminderReceiver
import org.thoughtcrime.securesms.notifications.NotificationCancellationHelper
@@ -89,7 +88,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
}
override fun updateNotification(context: Context, threadId: Long) {
- if (System.currentTimeMillis() - lastDesktopActivityTimestamp < DefaultMessageNotifier.DESKTOP_ACTIVITY_PERIOD) {
+ if (System.currentTimeMillis() - lastDesktopActivityTimestamp < DESKTOP_ACTIVITY_PERIOD) {
Log.i(TAG, "Scheduling delayed notification...")
executor.enqueue(context, threadId)
} else {
@@ -264,7 +263,13 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
companion object {
val TAG: String = Log.tag(MessageNotifierV2::class.java)
+
private val REMINDER_TIMEOUT: Long = TimeUnit.MINUTES.toMillis(2)
+ val MIN_AUDIBLE_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(2)
+ val DESKTOP_ACTIVITY_PERIOD = TimeUnit.MINUTES.toMillis(1)
+
+ const val EXTRA_REMOTE_REPLY = "extra_remote_reply"
+ const val NOTIFICATION_GROUP = "messages"
private fun updateBadge(context: Context, count: Int) {
try {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt
index 42b71935f0..997a8e0af0 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt
@@ -1,19 +1,15 @@
package org.thoughtcrime.securesms.notifications.v2
-import android.annotation.TargetApi
import android.app.Notification
import android.app.PendingIntent
-import android.app.Person
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
-import android.graphics.drawable.Icon
import android.net.Uri
import android.os.Build
import android.text.TextUtils
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
-import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.core.app.NotificationCompat
import androidx.core.app.RemoteInput
@@ -22,7 +18,6 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.database.RecipientDatabase
import org.thoughtcrime.securesms.keyvalue.SignalStore
-import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.ReplyMethod
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference
@@ -171,7 +166,7 @@ sealed class NotificationBuilder(protected val context: Context) {
/**
* Notification builder using solely androidx/compat libraries.
*/
- class NotificationBuilderCompat(context: Context) : NotificationBuilder(context) {
+ private class NotificationBuilderCompat(context: Context) : NotificationBuilder(context) {
val builder: NotificationCompat.Builder = NotificationCompat.Builder(context, NotificationChannels.getMessagesChannel(context))
override fun addActions(replyMethod: ReplyMethod, conversation: NotificationConversation) {
@@ -194,7 +189,7 @@ sealed class NotificationBuilder(protected val context: Context) {
val label: String = context.getString(replyMethod.toLongDescription())
val replyAction: NotificationCompat.Action = if (Build.VERSION.SDK_INT >= 24) {
NotificationCompat.Action.Builder(R.drawable.ic_reply_white_36dp, actionName, remoteReply)
- .addRemoteInput(RemoteInput.Builder(DefaultMessageNotifier.EXTRA_REMOTE_REPLY).setLabel(label).build())
+ .addRemoteInput(RemoteInput.Builder(MessageNotifierV2.EXTRA_REMOTE_REPLY).setLabel(label).build())
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
.setShowsUserInterface(false)
.build()
@@ -203,7 +198,7 @@ sealed class NotificationBuilder(protected val context: Context) {
}
val wearableReplyAction = NotificationCompat.Action.Builder(R.drawable.ic_reply, actionName, remoteReply)
- .addRemoteInput(RemoteInput.Builder(DefaultMessageNotifier.EXTRA_REMOTE_REPLY).setLabel(label).build())
+ .addRemoteInput(RemoteInput.Builder(MessageNotifierV2.EXTRA_REMOTE_REPLY).setLabel(label).build())
.build()
builder.addAction(replyAction)
@@ -451,228 +446,6 @@ sealed class NotificationBuilder(protected val context: Context) {
builder.setSubText(subText)
}
}
-
- /**
- * Notification builder using solely on device OS libraries.
- */
- @TargetApi(28)
- class NotificationBuilderOS(context: Context) : NotificationBuilder(context) {
- val builder: Notification.Builder = Notification.Builder(context, NotificationChannels.getMessagesChannel(context))
-
- override fun addActions(replyMethod: ReplyMethod, conversation: NotificationConversation) {
- val markAsRead: PendingIntent = conversation.getMarkAsReadIntent(context)
- val markAsReadAction: Notification.Action = Notification.Action.Builder(context.getIcon(R.drawable.check), context.getString(R.string.MessageNotifier_mark_read), markAsRead)
- .setSemanticAction(Notification.Action.SEMANTIC_ACTION_MARK_AS_READ)
- .build()
- val extender: Notification.WearableExtender = Notification.WearableExtender()
-
- builder.addAction(markAsReadAction)
- extender.addAction(markAsReadAction)
-
- if (conversation.mostRecentNotification.canReply(context)) {
- val remoteReply: PendingIntent = conversation.getRemoteReplyIntent(context, replyMethod)
-
- val actionName: String = context.getString(R.string.MessageNotifier_reply)
- val label: String = context.getString(replyMethod.toLongDescription())
- val replyAction: Notification.Action = Notification.Action.Builder(context.getIcon(R.drawable.ic_reply_white_36dp), actionName, remoteReply)
- .addRemoteInput(android.app.RemoteInput.Builder(DefaultMessageNotifier.EXTRA_REMOTE_REPLY).setLabel(label).build())
- .setSemanticAction(Notification.Action.SEMANTIC_ACTION_REPLY)
- .build()
-
- val wearableReplyAction = Notification.Action.Builder(context.getIcon(R.drawable.ic_reply), actionName, remoteReply)
- .addRemoteInput(android.app.RemoteInput.Builder(DefaultMessageNotifier.EXTRA_REMOTE_REPLY).setLabel(label).build())
- .build()
-
- builder.addAction(replyAction)
- extender.addAction(wearableReplyAction)
- }
-
- builder.extend(extender)
- }
-
- override fun addMarkAsReadActionActual(state: NotificationStateV2) {
- val markAllAsReadAction: Notification.Action = Notification.Action.Builder(
- context.getIcon(R.drawable.check),
- context.getString(R.string.MessageNotifier_mark_all_as_read),
- state.getMarkAsReadIntent(context)
- ).build()
-
- builder.addAction(markAllAsReadAction)
- builder.extend(Notification.WearableExtender().addAction(markAllAsReadAction))
- }
-
- override fun addTurnOffJoinedNotificationsAction(pendingIntent: PendingIntent) {
- val turnOffTheseNotifications: Notification.Action = Notification.Action.Builder(
- context.getIcon(R.drawable.check),
- context.getString(R.string.MessageNotifier_turn_off_these_notifications),
- pendingIntent
- ).build()
-
- builder.addAction(turnOffTheseNotifications)
- }
-
- override fun addMessagesActual(conversation: NotificationConversation, includeShortcut: Boolean) {
- val self: Person = Person.Builder()
- .setBot(false)
- .setName(if (includeShortcut) Recipient.self().getDisplayName(context) else context.getString(R.string.SingleRecipientNotificationBuilder_you))
- .setIcon(if (includeShortcut) Recipient.self().getContactDrawable(context).toLargeBitmap(context).toIcon() else null)
- .setKey(ConversationUtil.getShortcutId(Recipient.self().id))
- .build()
-
- val messagingStyle: Notification.MessagingStyle = Notification.MessagingStyle(self)
- messagingStyle.conversationTitle = conversation.getConversationTitle(context)
- messagingStyle.isGroupConversation = conversation.isGroup
-
- conversation.notificationItems.forEach { notificationItem ->
- val personBuilder: Person.Builder = Person.Builder()
- .setBot(false)
- .setName(notificationItem.getPersonName(context))
- .setUri(notificationItem.getPersonUri(context))
- .setIcon(notificationItem.getPersonIcon(context).toIcon())
-
- if (includeShortcut) {
- personBuilder.setKey(ConversationUtil.getShortcutId(notificationItem.individualRecipient))
- }
-
- val (dataUri: Uri?, mimeType: String?) = notificationItem.getThumbnailInfo(context)
-
- messagingStyle.addMessage(Notification.MessagingStyle.Message(notificationItem.getPrimaryText(context), notificationItem.timestamp, personBuilder.build()).setData(mimeType, dataUri))
- }
-
- builder.style = messagingStyle
- }
-
- override fun setBubbleMetadataActual(conversation: NotificationConversation, bubbleState: BubbleUtil.BubbleState) {
- if (Build.VERSION.SDK_INT < ConversationUtil.CONVERSATION_SUPPORT_VERSION) {
- return
- }
-
- val intent = PendingIntent.getActivity(
- context,
- 0,
- ConversationIntents.createBubbleIntent(context, conversation.recipient.id, conversation.threadId),
- 0
- )
-
- val bubbleMetadata = Notification.BubbleMetadata.Builder(intent, AvatarUtil.getIconForShortcut(context, conversation.recipient))
- .setAutoExpandBubble(bubbleState === BubbleUtil.BubbleState.SHOWN)
- .setDesiredHeight(600)
- .setSuppressNotification(bubbleState === BubbleUtil.BubbleState.SHOWN)
- .build()
-
- builder.setBubbleMetadata(bubbleMetadata)
- }
-
- override fun addMessagesActual(state: NotificationStateV2) {
- // Intentionally left blank
- }
-
- override fun setLights(@ColorInt color: Int, onTime: Int, offTime: Int) {
- // Intentionally left blank
- }
-
- override fun setAlarms(recipient: Recipient?) {
- // Intentionally left blank
- }
-
- override fun setSmallIcon(drawable: Int) {
- builder.setSmallIcon(drawable)
- }
-
- override fun setColor(@ColorInt color: Int) {
- builder.setColor(color)
- }
-
- override fun setCategory(category: String) {
- builder.setCategory(category)
- }
-
- override fun setGroup(group: String) {
- builder.setGroup(group)
- }
-
- override fun setGroupAlertBehavior(behavior: Int) {
- builder.setGroupAlertBehavior(behavior)
- }
-
- override fun setChannelId(channelId: String) {
- builder.setChannelId(channelId)
- }
-
- override fun setContentTitle(contentTitle: CharSequence) {
- builder.setContentTitle(contentTitle)
- }
-
- override fun setLargeIcon(largeIcon: Bitmap?) {
- builder.setLargeIcon(largeIcon)
- }
-
- override fun setShortcutIdActual(shortcutId: String) {
- builder.setShortcutId(shortcutId)
- }
-
- @Suppress("DEPRECATION")
- override fun setContentInfo(contentInfo: String) {
- builder.setContentInfo(contentInfo)
- }
-
- override fun setNumber(number: Int) {
- builder.setNumber(number)
- }
-
- override fun setContentText(contentText: CharSequence?) {
- builder.setContentText(contentText)
- }
-
- override fun setTicker(ticker: CharSequence?) {
- builder.setTicker(ticker)
- }
-
- override fun setContentIntent(pendingIntent: PendingIntent?) {
- builder.setContentIntent(pendingIntent)
- }
-
- override fun setDeleteIntent(deleteIntent: PendingIntent?) {
- builder.setDeleteIntent(deleteIntent)
- }
-
- override fun setSortKey(sortKey: String) {
- builder.setSortKey(sortKey)
- }
-
- override fun setOnlyAlertOnce(onlyAlertOnce: Boolean) {
- builder.setOnlyAlertOnce(onlyAlertOnce)
- }
-
- override fun setPriority(priority: Int) {
- // Intentionally left blank
- }
-
- override fun setAutoCancel(autoCancel: Boolean) {
- builder.setAutoCancel(autoCancel)
- }
-
- override fun build(): Notification {
- return builder.build()
- }
-
- override fun addPersonActual(recipient: Recipient) {
- builder.addPerson(ConversationUtil.buildPerson(context, recipient))
- }
-
- override fun setWhen(timestamp: Long) {
- builder.setWhen(timestamp)
- builder.setShowWhen(true)
- }
-
- override fun setGroupSummary(isGroupSummary: Boolean) {
- builder.setGroupSummary(isGroupSummary)
- }
-
- override fun setSubText(subText: String) {
- builder.setSubText(subText)
- }
- }
}
private fun Bitmap?.toIconCompat(): IconCompat? {
@@ -683,20 +456,6 @@ private fun Bitmap?.toIconCompat(): IconCompat? {
}
}
-@RequiresApi(23)
-private fun Bitmap?.toIcon(): Icon? {
- return if (this != null) {
- Icon.createWithBitmap(this)
- } else {
- null
- }
-}
-
-@RequiresApi(23)
-private fun Context.getIcon(@DrawableRes drawableRes: Int): Icon {
- return Icon.createWithResource(this, drawableRes)
-}
-
@StringRes
private fun ReplyMethod.toLongDescription(): Int {
return when (this) {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt
index 98c22fccb3..0ead43a1ce 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt
@@ -21,8 +21,8 @@ import org.thoughtcrime.securesms.MainActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.database.DatabaseFactory
+import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord
import org.thoughtcrime.securesms.keyvalue.SignalStore
-import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.NotificationIds
import org.thoughtcrime.securesms.recipients.Recipient
@@ -181,7 +181,7 @@ object NotificationFactory {
setSmallIcon(R.drawable.ic_notification)
setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
setCategory(NotificationCompat.CATEGORY_MESSAGE)
- setGroup(DefaultMessageNotifier.NOTIFICATION_GROUP)
+ setGroup(MessageNotifierV2.NOTIFICATION_GROUP)
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
setChannelId(conversation.getChannelId(context))
setContentTitle(conversation.getContentTitle(context))
@@ -225,7 +225,7 @@ object NotificationFactory {
setSmallIcon(R.drawable.ic_notification)
setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
setCategory(NotificationCompat.CATEGORY_MESSAGE)
- setGroup(DefaultMessageNotifier.NOTIFICATION_GROUP)
+ setGroup(MessageNotifierV2.NOTIFICATION_GROUP)
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
setChannelId(NotificationChannels.getMessagesChannel(context))
setContentTitle(context.getString(R.string.app_name))
@@ -253,7 +253,7 @@ object NotificationFactory {
private fun notifyInThread(context: Context, recipient: Recipient, lastAudibleNotification: Long) {
if (!SignalStore.settings().isMessageNotificationsInChatSoundsEnabled ||
ServiceUtil.getAudioManager(context).ringerMode != AudioManager.RINGER_MODE_NORMAL ||
- (System.currentTimeMillis() - lastAudibleNotification) < DefaultMessageNotifier.MIN_AUDIBLE_PERIOD_MILLIS
+ (System.currentTimeMillis() - lastAudibleNotification) < MessageNotifierV2.MIN_AUDIBLE_PERIOD_MILLIS
) {
return
}
@@ -343,6 +343,38 @@ object NotificationFactory {
NotificationManagerCompat.from(context).safelyNotify(context, recipient, threadId.toInt(), builder.build())
}
+ @JvmStatic
+ fun notifyToBubbleConversation(context: Context, recipient: Recipient, threadId: Long) {
+ val builder: NotificationBuilder = NotificationBuilder.create(context)
+
+ val conversation = NotificationConversation(
+ recipient = recipient,
+ threadId = threadId,
+ notificationItems = listOf(
+ MessageNotification(
+ threadRecipient = recipient,
+ record = InMemoryMessageRecord.ForceConversationBubble(recipient, threadId)
+ )
+ )
+ )
+
+ builder.apply {
+ setSmallIcon(R.drawable.ic_notification)
+ setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
+ setCategory(NotificationCompat.CATEGORY_MESSAGE)
+ setChannelId(conversation.getChannelId(context))
+ setContentTitle(conversation.getContentTitle(context))
+ setLargeIcon(conversation.getContactLargeIcon(context).toLargeBitmap(context))
+ addPerson(conversation.recipient)
+ setShortcutId(ConversationUtil.getShortcutId(conversation.recipient))
+ addMessages(conversation)
+ setBubbleMetadata(conversation, BubbleUtil.BubbleState.SHOWN)
+ }
+
+ Log.d(TAG, "Posting Notification for requested bubble")
+ NotificationManagerCompat.from(context).safelyNotify(context, recipient, conversation.notificationId, builder.build())
+ }
+
private fun NotificationManagerCompat.safelyNotify(context: Context, threadRecipient: Recipient?, notificationId: Int, notification: Notification) {
try {
notify(notificationId, notification)
diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt
index aba04d7f58..35c8442b44 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt
@@ -19,7 +19,6 @@ import org.thoughtcrime.securesms.database.model.ReactionRecord
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.mms.Slide
import org.thoughtcrime.securesms.mms.SlideDeck
-import org.thoughtcrime.securesms.notifications.AbstractNotificationBuilder
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientUtil
import org.thoughtcrime.securesms.service.KeyCachingService
@@ -30,6 +29,7 @@ import org.thoughtcrime.securesms.util.Util
private val TAG: String = Log.tag(NotificationItemV2::class.java)
private const val EMOJI_REPLACEMENT_STRING = "__EMOJI__"
+private const val MAX_DISPLAY_LENGTH = 500
/**
* Base for messaged-based notifications. Represents a single notification.
@@ -145,10 +145,10 @@ sealed class NotificationItemV2(val threadRecipient: Recipient, protected val re
private fun CharSequence?.trimToDisplayLength(): CharSequence {
val text: CharSequence = this ?: ""
- return if (text.length <= AbstractNotificationBuilder.MAX_DISPLAY_LENGTH) {
+ return if (text.length <= MAX_DISPLAY_LENGTH) {
text
} else {
- text.subSequence(0, AbstractNotificationBuilder.MAX_DISPLAY_LENGTH)
+ text.subSequence(0, MAX_DISPLAY_LENGTH)
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/BubbleUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/BubbleUtil.java
index 7f95cb6ab6..30f6d2d76d 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/util/BubbleUtil.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/BubbleUtil.java
@@ -18,9 +18,8 @@ import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
-import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
import org.thoughtcrime.securesms.notifications.NotificationIds;
-import org.thoughtcrime.securesms.notifications.SingleRecipientNotificationBuilder;
+import org.thoughtcrime.securesms.notifications.v2.NotificationFactory;
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
@@ -90,16 +89,8 @@ public final class BubbleUtil {
if (activeThreadNotification != null && activeThreadNotification.deleteIntent != null) {
ApplicationDependencies.getMessageNotifier().updateNotification(context, threadId, BubbleState.SHOWN);
} else {
- Recipient recipient = Recipient.resolved(recipientId);
- SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, SignalStore.settings().getMessageNotificationsPrivacy());
-
- builder.addMessageBody(recipient, recipient, "", System.currentTimeMillis(), null);
- builder.setThread(recipient);
- builder.setDefaultBubbleState(BubbleState.SHOWN);
- builder.setGroup(DefaultMessageNotifier.NOTIFICATION_GROUP);
-
- Log.d(TAG, "Posting Notification for requested bubble");
- notificationManager.notify(NotificationIds.getNotificationIdForThread(threadId), builder.build());
+ Recipient recipient = Recipient.resolved(recipientId);
+ NotificationFactory.notifyToBubbleConversation(context, recipient, threadId);
}
}
});
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java
index 4a15f52ac4..81e6f2999c 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java
@@ -1,7 +1,5 @@
package org.thoughtcrime.securesms.util;
-import android.app.Application;
-import android.os.Build;
import android.text.TextUtils;
import androidx.annotation.NonNull;
@@ -77,7 +75,6 @@ public final class FeatureFlags {
private static final String ANIMATED_STICKER_MIN_TOTAL_MEMORY = "android.animatedStickerMinTotalMemory";
private static final String MESSAGE_PROCESSOR_ALARM_INTERVAL = "android.messageProcessor.alarmIntervalMins";
private static final String MESSAGE_PROCESSOR_DELAY = "android.messageProcessor.foregroundDelayMs";
- private static final String NOTIFICATION_REWRITE = "android.notificationRewrite";
private static final String MP4_GIF_SEND_SUPPORT = "android.mp4GifSendSupport";
private static final String MEDIA_QUALITY_LEVELS = "android.mediaQuality.levels";
private static final String RETRY_RECEIPT_LIFESPAN = "android.retryReceiptLifespan";
@@ -114,7 +111,6 @@ public final class FeatureFlags {
ANIMATED_STICKER_MIN_TOTAL_MEMORY,
MESSAGE_PROCESSOR_ALARM_INTERVAL,
MESSAGE_PROCESSOR_DELAY,
- NOTIFICATION_REWRITE,
MP4_GIF_SEND_SUPPORT,
MEDIA_QUALITY_LEVELS,
RETRY_RECEIPT_LIFESPAN,
@@ -163,7 +159,6 @@ public final class FeatureFlags {
MESSAGE_PROCESSOR_ALARM_INTERVAL,
MESSAGE_PROCESSOR_DELAY,
GV1_FORCED_MIGRATE,
- NOTIFICATION_REWRITE,
MP4_GIF_SEND_SUPPORT,
MEDIA_QUALITY_LEVELS,
RETRY_RECEIPT_LIFESPAN,
@@ -354,11 +349,6 @@ public final class FeatureFlags {
return getInteger(ANIMATED_STICKER_MIN_TOTAL_MEMORY, (int) ByteUnit.GIGABYTES.toMegabytes(3));
}
- /** Whether or not to use the new notification system. */
- public static boolean useNewNotificationSystem() {
- return Build.VERSION.SDK_INT >= 26 || getBoolean(NOTIFICATION_REWRITE, false);
- }
-
public static boolean mp4GifSendSupport() {
return getBoolean(MP4_GIF_SEND_SUPPORT, false);
}