NotificationThread migration.

This commit is contained in:
Alex Hart
2022-05-10 12:25:13 -03:00
parent af9465fefe
commit eaf36be9f6
39 changed files with 432 additions and 254 deletions

View File

@@ -8,14 +8,17 @@ import android.content.Intent;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
import java.util.ArrayList;
public class DeleteNotificationReceiver extends BroadcastReceiver {
public static String DELETE_NOTIFICATION_ACTION = "org.thoughtcrime.securesms.DELETE_NOTIFICATION";
public static final String EXTRA_IDS = "message_ids";
public static final String EXTRA_MMS = "is_mms";
public static final String EXTRA_THREAD_IDS = "thread_ids";
public static final String EXTRA_IDS = "message_ids";
public static final String EXTRA_MMS = "is_mms";
public static final String EXTRA_THREADS = "threads";
@Override
public void onReceive(final Context context, Intent intent) {
@@ -23,13 +26,13 @@ public class DeleteNotificationReceiver extends BroadcastReceiver {
MessageNotifier notifier = ApplicationDependencies.getMessageNotifier();
notifier.clearReminder(context);
final long[] ids = intent.getLongArrayExtra(EXTRA_IDS);
final boolean[] mms = intent.getBooleanArrayExtra(EXTRA_MMS);
final long[] threadIds = intent.getLongArrayExtra(EXTRA_THREAD_IDS);
final long[] ids = intent.getLongArrayExtra(EXTRA_IDS);
final boolean[] mms = intent.getBooleanArrayExtra(EXTRA_MMS);
final ArrayList<NotificationThread> threads = intent.getParcelableArrayListExtra(EXTRA_THREADS);
if (threadIds != null) {
for (long threadId : threadIds) {
notifier.removeStickyThread(threadId);
if (threads != null) {
for (NotificationThread thread : threads) {
notifier.removeStickyThread(thread);
}
}

View File

@@ -19,9 +19,11 @@ import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.MultiDeviceReadUpdateJob;
import org.thoughtcrime.securesms.jobs.SendReadReceiptJob;
import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -30,7 +32,7 @@ public class MarkReadReceiver extends BroadcastReceiver {
private static final String TAG = Log.tag(MarkReadReceiver.class);
public static final String CLEAR_ACTION = "org.thoughtcrime.securesms.notifications.CLEAR";
public static final String THREAD_IDS_EXTRA = "thread_ids";
public static final String THREADS_EXTRA = "threads";
public static final String NOTIFICATION_ID_EXTRA = "notification_id";
@SuppressLint("StaticFieldLeak")
@@ -39,12 +41,12 @@ public class MarkReadReceiver extends BroadcastReceiver {
if (!CLEAR_ACTION.equals(intent.getAction()))
return;
final long[] threadIds = intent.getLongArrayExtra(THREAD_IDS_EXTRA);
final ArrayList<NotificationThread> threads = intent.getParcelableArrayListExtra(THREADS_EXTRA);
if (threadIds != null) {
if (threads != null) {
MessageNotifier notifier = ApplicationDependencies.getMessageNotifier();
for (long threadId : threadIds) {
notifier.removeStickyThread(threadId);
for (NotificationThread thread : threads) {
notifier.removeStickyThread(thread);
}
NotificationCancellationHelper.cancelLegacy(context, intent.getIntExtra(NOTIFICATION_ID_EXTRA, -1));
@@ -53,9 +55,9 @@ public class MarkReadReceiver extends BroadcastReceiver {
SignalExecutors.BOUNDED.execute(() -> {
List<MarkedMessageInfo> messageIdsCollection = new LinkedList<>();
for (long threadId : threadIds) {
Log.i(TAG, "Marking as read: " + threadId);
List<MarkedMessageInfo> messageIds = SignalDatabase.threads().setRead(threadId, true);
for (NotificationThread thread : threads) {
Log.i(TAG, "Marking as read: " + thread);
List<MarkedMessageInfo> messageIds = SignalDatabase.threads().setRead(thread.getThreadId(), true);
messageIdsCollection.addAll(messageIds);
}

View File

@@ -5,28 +5,32 @@ import android.content.Context;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.BubbleUtil;
import java.util.Optional;
public interface MessageNotifier {
void setVisibleThread(long threadId);
long getVisibleThread();
void setVisibleThread(@Nullable NotificationThread notificationThread);
@NonNull Optional<NotificationThread> getVisibleThread();
void clearVisibleThread();
void setLastDesktopActivityTimestamp(long timestamp);
void notifyMessageDeliveryFailed(@NonNull Context context, @NonNull Recipient recipient, long threadId);
void notifyProofRequired(@NonNull Context context, @NonNull Recipient recipient, long threadId);
void notifyMessageDeliveryFailed(@NonNull Context context, @NonNull Recipient recipient, @NonNull NotificationThread notificationThread);
void notifyProofRequired(@NonNull Context context, @NonNull Recipient recipient, @NonNull NotificationThread notificationThread);
void cancelDelayedNotifications();
void updateNotification(@NonNull Context context);
void updateNotification(@NonNull Context context, long threadId);
void updateNotification(@NonNull Context context, long threadId, @NonNull BubbleUtil.BubbleState defaultBubbleState);
void updateNotification(@NonNull Context context, long threadId, boolean signal);
void updateNotification(@NonNull Context context, long threadId, boolean signal, int reminderCount, @NonNull BubbleUtil.BubbleState defaultBubbleState);
void updateNotification(@NonNull Context context, @NonNull NotificationThread notificationThread);
void updateNotification(@NonNull Context context, @NonNull NotificationThread notificationThread, @NonNull BubbleUtil.BubbleState defaultBubbleState);
void updateNotification(@NonNull Context context, @NonNull NotificationThread notificationThread, boolean signal);
void updateNotification(@NonNull Context context, @Nullable NotificationThread notificationThread, boolean signal, int reminderCount, @NonNull BubbleUtil.BubbleState defaultBubbleState);
void clearReminder(@NonNull Context context);
void addStickyThread(long threadId, long earliestTimestamp);
void removeStickyThread(long threadId);
void addStickyThread(@NonNull NotificationThread notificationThread, long earliestTimestamp);
void removeStickyThread(@NonNull NotificationThread notificationThread);
class ReminderReceiver extends BroadcastReceiver {
@@ -35,7 +39,7 @@ public interface MessageNotifier {
public void onReceive(final Context context, final Intent intent) {
SignalExecutors.BOUNDED.execute(() -> {
int reminderCount = intent.getIntExtra("reminder_count", 0);
ApplicationDependencies.getMessageNotifier().updateNotification(context, -1, true, reminderCount + 1, BubbleUtil.BubbleState.HIDDEN);
ApplicationDependencies.getMessageNotifier().updateNotification(context, null, true, reminderCount + 1, BubbleUtil.BubbleState.HIDDEN);
});
}
}

View File

@@ -15,6 +15,7 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.BubbleUtil;
import org.thoughtcrime.securesms.util.ConversationUtil;
@@ -22,6 +23,7 @@ import org.thoughtcrime.securesms.util.ServiceUtil;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
/**
@@ -183,10 +185,12 @@ public final class NotificationCancellationHelper {
return true;
}
Long threadId = SignalDatabase.threads().getThreadIdFor(recipientId);
long focusedThreadId = ApplicationDependencies.getMessageNotifier().getVisibleThread();
Long threadId = SignalDatabase.threads().getThreadIdFor(recipientId);
Optional<NotificationThread> focusedThread = ApplicationDependencies.getMessageNotifier().getVisibleThread();
Long focusedThreadId = focusedThread.map(NotificationThread::getThreadId).orElse(null);
Long focusedGroupStoryId = focusedThread.map(NotificationThread::getGroupStoryId).orElse(null);
if (Objects.equals(threadId, focusedThreadId)) {
if (Objects.equals(threadId, focusedThreadId) && focusedGroupStoryId == null) {
Log.d(TAG, "isCancellable: user entered full screen thread.");
return true;
}

View File

@@ -1,24 +1,43 @@
package org.thoughtcrime.securesms.notifications;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
public final class NotificationIds {
public static final int FCM_FAILURE = 12;
public static final int PENDING_MESSAGES = 1111;
public static final int MESSAGE_SUMMARY = 1338;
public static final int APPLICATION_MIGRATION = 4242;
public static final int SMS_IMPORT_COMPLETE = 31337;
public static final int PRE_REGISTRATION_SMS = 5050;
public static final int THREAD = 50000;
public static final int INTERNAL_ERROR = 258069;
public static final int LEGACY_SQLCIPHER_MIGRATION = 494949;
public static final int USER_NOTIFICATION_MIGRATION = 525600;
public static final int DEVICE_TRANSFER = 625420;
public static final int DONOR_BADGE_FAILURE = 630001;
public static final int FCM_FETCH = 630002;
public static final int FCM_FAILURE = 12;
public static final int PENDING_MESSAGES = 1111;
public static final int MESSAGE_SUMMARY = 1338;
public static final int APPLICATION_MIGRATION = 4242;
public static final int SMS_IMPORT_COMPLETE = 31337;
public static final int PRE_REGISTRATION_SMS = 5050;
public static final int THREAD = 50000;
public static final int INTERNAL_ERROR = 258069;
public static final int LEGACY_SQLCIPHER_MIGRATION = 494949;
public static final int USER_NOTIFICATION_MIGRATION = 525600;
public static final int DEVICE_TRANSFER = 625420;
public static final int DONOR_BADGE_FAILURE = 630001;
public static final int FCM_FETCH = 630002;
public static final int STORY_THREAD = 700000;
public static final int MESSAGE_DELIVERY_FAILURE = 800000;
public static final int STORY_MESSAGE_DELIVERY_FAILURE = 900000;
private NotificationIds() { }
public static int getNotificationIdForThread(long threadId) {
return THREAD + (int) threadId;
public static int getNotificationIdForThread(@NonNull NotificationThread notificationThread) {
if (notificationThread.getGroupStoryId() != null) {
return STORY_THREAD + notificationThread.getGroupStoryId().intValue();
} else {
return THREAD + (int) notificationThread.getThreadId();
}
}
public static int getNotificationIdForMessageDeliveryFailed(@NonNull NotificationThread notificationThread) {
if (notificationThread.getGroupStoryId() != null) {
return STORY_MESSAGE_DELIVERY_FAILURE + notificationThread.getGroupStoryId().intValue();
} else {
return MESSAGE_DELIVERY_FAILURE + (int) notificationThread.getThreadId();
}
}
}

View File

@@ -6,14 +6,18 @@ import android.os.Handler;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.ExceptionUtil;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.BubbleUtil;
import org.thoughtcrime.securesms.util.LeakyBucketLimiter;
import java.util.Optional;
/**
* Uses a leaky-bucket strategy to limiting notification updates.
*/
@@ -29,12 +33,12 @@ public class OptimizedMessageNotifier implements MessageNotifier {
}
@Override
public void setVisibleThread(long threadId) {
getNotifier().setVisibleThread(threadId);
public void setVisibleThread(@Nullable NotificationThread notificationThread) {
getNotifier().setVisibleThread(notificationThread);
}
@Override
public long getVisibleThread() {
public @NonNull Optional<NotificationThread> getVisibleThread() {
return getNotifier().getVisibleThread();
}
@@ -49,13 +53,13 @@ public class OptimizedMessageNotifier implements MessageNotifier {
}
@Override
public void notifyMessageDeliveryFailed(@NonNull Context context, @NonNull Recipient recipient, long threadId) {
getNotifier().notifyMessageDeliveryFailed(context, recipient, threadId);
public void notifyMessageDeliveryFailed(@NonNull Context context, @NonNull Recipient recipient, @NonNull NotificationThread notificationThread) {
getNotifier().notifyMessageDeliveryFailed(context, recipient, notificationThread);
}
@Override
public void notifyProofRequired(@NonNull Context context, @NonNull Recipient recipient, long threadId) {
getNotifier().notifyProofRequired(context, recipient, threadId);
public void notifyProofRequired(@NonNull Context context, @NonNull Recipient recipient, @NonNull NotificationThread notificationThread) {
getNotifier().notifyProofRequired(context, recipient, notificationThread);
}
@Override
@@ -69,23 +73,23 @@ public class OptimizedMessageNotifier implements MessageNotifier {
}
@Override
public void updateNotification(@NonNull Context context, long threadId) {
runOnLimiter(() -> getNotifier().updateNotification(context, threadId));
public void updateNotification(@NonNull Context context, @NonNull NotificationThread notificationThread) {
runOnLimiter(() -> getNotifier().updateNotification(context, notificationThread));
}
@Override
public void updateNotification(@NonNull Context context, long threadId, @NonNull BubbleUtil.BubbleState defaultBubbleState) {
runOnLimiter(() -> getNotifier().updateNotification(context, threadId, defaultBubbleState));
public void updateNotification(@NonNull Context context, @NonNull NotificationThread notificationThread, @NonNull BubbleUtil.BubbleState defaultBubbleState) {
runOnLimiter(() -> getNotifier().updateNotification(context, notificationThread, defaultBubbleState));
}
@Override
public void updateNotification(@NonNull Context context, long threadId, boolean signal) {
runOnLimiter(() -> getNotifier().updateNotification(context, threadId, signal));
public void updateNotification(@NonNull Context context, @NonNull NotificationThread notificationThread, boolean signal) {
runOnLimiter(() -> getNotifier().updateNotification(context, notificationThread, signal));
}
@Override
public void updateNotification(@NonNull Context context, long threadId, boolean signal, int reminderCount, @NonNull BubbleUtil.BubbleState defaultBubbleState) {
runOnLimiter(() -> getNotifier().updateNotification(context, threadId, signal, reminderCount, defaultBubbleState));
public void updateNotification(@NonNull Context context, @Nullable NotificationThread notificationThread, boolean signal, int reminderCount, @NonNull BubbleUtil.BubbleState defaultBubbleState) {
runOnLimiter(() -> getNotifier().updateNotification(context, notificationThread, signal, reminderCount, defaultBubbleState));
}
@Override
@@ -94,13 +98,13 @@ public class OptimizedMessageNotifier implements MessageNotifier {
}
@Override
public void addStickyThread(long threadId, long earliestTimestamp) {
getNotifier().addStickyThread(threadId, earliestTimestamp);
public void addStickyThread(@NonNull NotificationThread notificationThread, long earliestTimestamp) {
getNotifier().addStickyThread(notificationThread, earliestTimestamp);
}
@Override
public void removeStickyThread(long threadId) {
getNotifier().removeStickyThread(threadId);
public void removeStickyThread(@NonNull NotificationThread notificationThread) {
getNotifier().removeStickyThread(notificationThread);
}
private void runOnLimiter(@NonNull Runnable runnable) {

View File

@@ -28,10 +28,12 @@ import androidx.core.app.RemoteInput;
import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.database.MessageDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.ParentStoryId;
import org.thoughtcrime.securesms.database.model.StoryType;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sms.MessageSender;
@@ -48,10 +50,11 @@ import java.util.concurrent.TimeUnit;
*/
public class RemoteReplyReceiver extends BroadcastReceiver {
public static final String REPLY_ACTION = "org.thoughtcrime.securesms.notifications.WEAR_REPLY";
public static final String RECIPIENT_EXTRA = "recipient_extra";
public static final String REPLY_METHOD = "reply_method";
public static final String EARLIEST_TIMESTAMP = "earliest_timestamp";
public static final String REPLY_ACTION = "org.thoughtcrime.securesms.notifications.WEAR_REPLY";
public static final String RECIPIENT_EXTRA = "recipient_extra";
public static final String REPLY_METHOD = "reply_method";
public static final String EARLIEST_TIMESTAMP = "earliest_timestamp";
public static final String GROUP_STORY_ID_EXTRA = "group_story_id_extra";
@SuppressLint("StaticFieldLeak")
@Override
@@ -65,6 +68,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(MessageNotifierV2.EXTRA_REMOTE_REPLY);
final long groupStoryId = intent.getLongExtra(GROUP_STORY_ID_EXTRA, Long.MIN_VALUE);
if (recipientId == null) throw new AssertionError("No recipientId specified");
if (replyMethod == null) throw new AssertionError("No reply method specified");
@@ -73,9 +77,10 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
SignalExecutors.BOUNDED.execute(() -> {
long threadId;
Recipient recipient = Recipient.resolved(recipientId);
int subscriptionId = recipient.getDefaultSubscriptionId().orElse(-1);
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.getExpiresInSeconds());
Recipient recipient = Recipient.resolved(recipientId);
int subscriptionId = recipient.getDefaultSubscriptionId().orElse(-1);
long expiresIn = TimeUnit.SECONDS.toMillis(recipient.getExpiresInSeconds());
ParentStoryId parentStoryId = groupStoryId != Long.MIN_VALUE ? ParentStoryId.deserialize(groupStoryId) : null;
switch (replyMethod) {
case GroupMessage: {
@@ -88,7 +93,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
false,
0,
StoryType.NONE,
null,
parentStoryId,
false,
null,
Collections.emptyList(),
@@ -114,7 +119,9 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
throw new AssertionError("Unknown Reply method");
}
ApplicationDependencies.getMessageNotifier().addStickyThread(threadId, intent.getLongExtra(EARLIEST_TIMESTAMP, System.currentTimeMillis()));
ApplicationDependencies.getMessageNotifier()
.addStickyThread(new NotificationThread(threadId, groupStoryId != Long.MIN_VALUE ? groupStoryId : null),
intent.getLongExtra(EARLIEST_TIMESTAMP, System.currentTimeMillis()));
List<MarkedMessageInfo> messageIds = SignalDatabase.threads().setRead(threadId, true);

View File

@@ -11,6 +11,7 @@ import android.service.notification.StatusBarNotification
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.content.ContextCompat
import me.leolin.shortcutbadger.ShortcutBadger
import org.signal.core.util.PendingIntentFlags
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.database.MessageDatabase
@@ -31,6 +32,7 @@ import org.thoughtcrime.securesms.util.BubbleUtil.BubbleState
import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder
import org.whispersystems.signalservice.internal.util.Util
import java.util.Optional
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executor
import java.util.concurrent.Executors
@@ -43,7 +45,7 @@ import kotlin.math.max
* MessageNotifier implementation using the new system for creating and showing notifications.
*/
class MessageNotifierV2(context: Application) : MessageNotifier {
@Volatile private var visibleThread: Long = -1
@Volatile private var visibleThread: NotificationThread? = null
@Volatile private var lastDesktopActivityTimestamp: Long = -1
@Volatile private var lastAudibleNotification: Long = -1
@Volatile private var lastScheduledReminder: Long = 0
@@ -51,34 +53,34 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
@Volatile private var previousPrivacyPreference: NotificationPrivacyPreference = SignalStore.settings().messageNotificationsPrivacy
@Volatile private var previousState: NotificationStateV2 = NotificationStateV2.EMPTY
private val threadReminders: MutableMap<Long, Reminder> = ConcurrentHashMap()
private val stickyThreads: MutableMap<Long, StickyThread> = mutableMapOf()
private val threadReminders: MutableMap<NotificationThread, Reminder> = ConcurrentHashMap()
private val stickyThreads: MutableMap<NotificationThread, StickyThread> = mutableMapOf()
private val executor = CancelableExecutor()
override fun setVisibleThread(threadId: Long) {
visibleThread = threadId
stickyThreads.remove(threadId)
override fun setVisibleThread(notificationThread: NotificationThread?) {
visibleThread = notificationThread
stickyThreads.remove(notificationThread)
}
override fun getVisibleThread(): Long {
return visibleThread
override fun getVisibleThread(): Optional<NotificationThread> {
return Optional.ofNullable(visibleThread)
}
override fun clearVisibleThread() {
setVisibleThread(-1)
setVisibleThread(null)
}
override fun setLastDesktopActivityTimestamp(timestamp: Long) {
lastDesktopActivityTimestamp = timestamp
}
override fun notifyMessageDeliveryFailed(context: Context, recipient: Recipient, threadId: Long) {
NotificationFactory.notifyMessageDeliveryFailed(context, recipient, threadId, visibleThread)
override fun notifyMessageDeliveryFailed(context: Context, recipient: Recipient, notificationThread: NotificationThread) {
NotificationFactory.notifyMessageDeliveryFailed(context, recipient, notificationThread, visibleThread)
}
override fun notifyProofRequired(context: Context, recipient: Recipient, threadId: Long) {
NotificationFactory.notifyProofRequired(context, recipient, threadId, visibleThread)
override fun notifyProofRequired(context: Context, recipient: Recipient, notificationThread: NotificationThread) {
NotificationFactory.notifyProofRequired(context, recipient, notificationThread, visibleThread)
}
override fun cancelDelayedNotifications() {
@@ -86,24 +88,24 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
}
override fun updateNotification(context: Context) {
updateNotification(context, -1, false, 0, BubbleState.HIDDEN)
updateNotification(context, null, false, 0, BubbleState.HIDDEN)
}
override fun updateNotification(context: Context, threadId: Long) {
override fun updateNotification(context: Context, notificationThread: NotificationThread) {
if (System.currentTimeMillis() - lastDesktopActivityTimestamp < DESKTOP_ACTIVITY_PERIOD) {
Log.i(TAG, "Scheduling delayed notification...")
executor.enqueue(context, threadId)
executor.enqueue(context, notificationThread)
} else {
updateNotification(context, threadId, true)
updateNotification(context, notificationThread, true)
}
}
override fun updateNotification(context: Context, threadId: Long, defaultBubbleState: BubbleState) {
updateNotification(context, threadId, false, 0, defaultBubbleState)
override fun updateNotification(context: Context, notificationThread: NotificationThread, defaultBubbleState: BubbleState) {
updateNotification(context, notificationThread, false, 0, defaultBubbleState)
}
override fun updateNotification(context: Context, threadId: Long, signal: Boolean) {
updateNotification(context, threadId, signal, 0, BubbleState.HIDDEN)
override fun updateNotification(context: Context, notificationThread: NotificationThread, signal: Boolean) {
updateNotification(context, notificationThread, signal, 0, BubbleState.HIDDEN)
}
/**
@@ -112,7 +114,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
*/
override fun updateNotification(
context: Context,
threadId: Long,
notificationThread: NotificationThread?,
signal: Boolean,
reminderCount: Int,
defaultBubbleState: BubbleState
@@ -162,22 +164,22 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
val displayedNotifications: Set<Int>? = ServiceUtil.getNotificationManager(context).getDisplayedNotificationIds().getOrNull()
if (displayedNotifications != null) {
val cleanedUpThreadIds: MutableSet<Long> = mutableSetOf()
val cleanedUpThreads: MutableSet<NotificationThread> = mutableSetOf()
state.conversations.filterNot { it.hasNewNotifications() || displayedNotifications.contains(it.notificationId) }
.forEach { conversation ->
cleanedUpThreadIds += conversation.threadId
cleanedUpThreads += conversation.thread
conversation.notificationItems.forEach { item ->
val messageDatabase: MessageDatabase = if (item.isMms) SignalDatabase.mms else SignalDatabase.sms
messageDatabase.markAsNotified(item.id)
}
}
if (cleanedUpThreadIds.isNotEmpty()) {
Log.i(TAG, "Cleaned up ${cleanedUpThreadIds.size} thread(s) with dangling notifications")
state = state.copy(conversations = state.conversations.filterNot { cleanedUpThreadIds.contains(it.threadId) })
if (cleanedUpThreads.isNotEmpty()) {
Log.i(TAG, "Cleaned up ${cleanedUpThreads.size} thread(s) with dangling notifications")
state = state.copy(conversations = state.conversations.filterNot { cleanedUpThreads.contains(it.thread) })
}
}
val retainStickyThreadIds: Set<Long> = state.getThreadsWithMostRecentNotificationFromSelf()
val retainStickyThreadIds: Set<NotificationThread> = state.getThreadsWithMostRecentNotificationFromSelf()
stickyThreads.keys.retainAll { retainStickyThreadIds.contains(it) }
if (state.isEmpty) {
@@ -188,13 +190,13 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
return
}
val alertOverrides: Set<Long> = threadReminders.filter { (_, reminder) -> reminder.lastNotified < System.currentTimeMillis() - REMINDER_TIMEOUT }.keys
val alertOverrides: Set<NotificationThread> = threadReminders.filter { (_, reminder) -> reminder.lastNotified < System.currentTimeMillis() - REMINDER_TIMEOUT }.keys
val threadsThatAlerted: Set<Long> = NotificationFactory.notify(
val threadsThatAlerted: Set<NotificationThread> = NotificationFactory.notify(
context = ContextThemeWrapper(context, R.style.TextSecure_LightTheme),
state = state,
visibleThreadId = visibleThread,
targetThreadId = threadId,
visibleThread = visibleThread,
targetThread = notificationThread,
defaultBubbleState = defaultBubbleState,
lastAudibleNotification = lastAudibleNotification,
notificationConfigurationChanged = notificationConfigurationChanged,
@@ -224,7 +226,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
Log.i(TAG, "threads: ${state.threadCount} messages: ${state.messageCount}")
if (Build.VERSION.SDK_INT >= 24) {
val ids = state.conversations.filter { it.threadId != visibleThread }.map { it.notificationId } + stickyThreads.map { (_, stickyThread) -> stickyThread.notificationId }
val ids = state.conversations.filter { it.thread != visibleThread }.map { it.notificationId } + stickyThreads.map { (_, stickyThread) -> stickyThread.notificationId }
val notShown = ids - ServiceUtil.getNotificationManager(context).getDisplayedNotificationIds().getOrDefault(emptySet())
if (notShown.isNotEmpty()) {
Log.e(TAG, "Notifications should be showing but are not for ${notShown.size} threads")
@@ -236,23 +238,23 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
// Intentionally left blank
}
override fun addStickyThread(threadId: Long, earliestTimestamp: Long) {
stickyThreads[threadId] = StickyThread(threadId, NotificationIds.getNotificationIdForThread(threadId), earliestTimestamp)
override fun addStickyThread(notificationThread: NotificationThread, earliestTimestamp: Long) {
stickyThreads[notificationThread] = StickyThread(notificationThread, NotificationIds.getNotificationIdForThread(notificationThread), earliestTimestamp)
}
override fun removeStickyThread(threadId: Long) {
stickyThreads.remove(threadId)
override fun removeStickyThread(notificationThread: NotificationThread) {
stickyThreads.remove(notificationThread)
}
private fun updateReminderTimestamps(context: Context, alertOverrides: Set<Long>, threadsThatAlerted: Set<Long>) {
private fun updateReminderTimestamps(context: Context, alertOverrides: Set<NotificationThread>, threadsThatAlerted: Set<NotificationThread>) {
if (SignalStore.settings().messageNotificationsRepeatAlerts == 0) {
return
}
val iterator: MutableIterator<MutableEntry<Long, Reminder>> = threadReminders.iterator()
val iterator: MutableIterator<MutableEntry<NotificationThread, Reminder>> = threadReminders.iterator()
while (iterator.hasNext()) {
val entry: MutableEntry<Long, Reminder> = iterator.next()
val (id: Long, reminder: Reminder) = entry
val entry: MutableEntry<NotificationThread, Reminder> = iterator.next()
val (id: NotificationThread, reminder: Reminder) = entry
if (alertOverrides.contains(id)) {
val notifyCount: Int = reminder.count + 1
if (notifyCount >= SignalStore.settings().messageNotificationsRepeatAlerts) {
@@ -263,7 +265,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
}
}
for (alertedThreadId: Long in threadsThatAlerted) {
for (alertedThreadId: NotificationThread in threadsThatAlerted) {
threadReminders[alertedThreadId] = Reminder(lastAudibleNotification)
}
@@ -282,7 +284,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
}
val alarmManager: AlarmManager? = ContextCompat.getSystemService(context, AlarmManager::class.java)
val pendingIntent: PendingIntent = PendingIntent.getBroadcast(context, 0, Intent(context, ReminderReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
val pendingIntent: PendingIntent = PendingIntent.getBroadcast(context, 0, Intent(context, ReminderReceiver::class.java), PendingIntentFlags.updateCurrent())
alarmManager?.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeout, pendingIntent)
lastScheduledReminder = System.currentTimeMillis()
}
@@ -291,7 +293,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
lastScheduledReminder = 0
threadReminders.clear()
val pendingIntent: PendingIntent? = PendingIntent.getBroadcast(context, 0, Intent(context, ReminderReceiver::class.java), PendingIntent.FLAG_CANCEL_CURRENT)
val pendingIntent: PendingIntent? = PendingIntent.getBroadcast(context, 0, Intent(context, ReminderReceiver::class.java), PendingIntentFlags.cancelCurrent())
if (pendingIntent != null) {
val alarmManager: AlarmManager? = ContextCompat.getSystemService(context, AlarmManager::class.java)
alarmManager?.cancel(pendingIntent)
@@ -317,7 +319,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
}
}
data class StickyThread(val threadId: Long, val notificationId: Int, val earliestTimestamp: Long)
data class StickyThread(val notificationThread: NotificationThread, val notificationId: Int, val earliestTimestamp: Long)
private data class Reminder(val lastNotified: Long, val count: Int = 0)
}
@@ -366,8 +368,8 @@ private class CancelableExecutor {
private val executor: Executor = Executors.newSingleThreadExecutor()
private val tasks: MutableSet<DelayedNotification> = mutableSetOf()
fun enqueue(context: Context, threadId: Long) {
execute(DelayedNotification(context, threadId))
fun enqueue(context: Context, notificationThread: NotificationThread) {
execute(DelayedNotification(context, notificationThread))
}
private fun execute(runnable: DelayedNotification) {
@@ -387,7 +389,7 @@ private class CancelableExecutor {
}
}
private class DelayedNotification constructor(private val context: Context, private val threadId: Long) : Runnable {
private class DelayedNotification constructor(private val context: Context, private val thread: NotificationThread) : Runnable {
private val canceled = AtomicBoolean(false)
private val delayUntil: Long = System.currentTimeMillis() + DELAY
@@ -399,7 +401,7 @@ private class CancelableExecutor {
}
if (!canceled.get()) {
Log.i(TAG, "Not canceled, notifying...")
ApplicationDependencies.getMessageNotifier().updateNotification(context, threadId, true)
ApplicationDependencies.getMessageNotifier().updateNotification(context, thread, true)
ApplicationDependencies.getMessageNotifier().cancelDelayedNotifications()
} else {
Log.w(TAG, "Canceled, not notifying...")

View File

@@ -334,7 +334,7 @@ sealed class NotificationBuilder(protected val context: Context) {
val intent = PendingIntent.getActivity(
context,
0,
ConversationIntents.createBubbleIntent(context, conversation.recipient.id, conversation.threadId),
ConversationIntents.createBubbleIntent(context, conversation.recipient.id, conversation.thread.threadId),
0
)

View File

@@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.notifications.ReplyMethod
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity
import org.thoughtcrime.securesms.util.Util
/**
@@ -30,11 +31,11 @@ import org.thoughtcrime.securesms.util.Util
*/
data class NotificationConversation(
val recipient: Recipient,
val threadId: Long,
val thread: NotificationThread,
val notificationItems: List<NotificationItemV2>
) {
val mostRecentNotification: NotificationItemV2 = notificationItems.last()
val notificationId: Int = NotificationIds.getNotificationIdForThread(threadId)
val notificationId: Int = NotificationIds.getNotificationIdForThread(thread)
val sortKey: Long = Long.MAX_VALUE - mostRecentNotification.timestamp
val messageCount: Int = notificationItems.size
val isGroup: Boolean = recipient.isGroup
@@ -111,10 +112,14 @@ data class NotificationConversation(
}
fun getPendingIntent(context: Context): PendingIntent {
val intent: Intent = ConversationIntents.createBuilder(context, recipient.id, threadId)
.withStartingPosition(mostRecentNotification.getStartingPosition(context))
.build()
.makeUniqueToPreventMerging()
val intent: Intent = if (thread.groupStoryId != null) {
StoryViewerActivity.createIntent(context, recipient.id, thread.groupStoryId, recipient.shouldHideStory())
} else {
ConversationIntents.createBuilder(context, recipient.id, thread.threadId)
.withStartingPosition(mostRecentNotification.getStartingPosition(context))
.build()
.makeUniqueToPreventMerging()
}
return TaskStackBuilder.create(context)
.addNextIntentWithParentStack(intent)
@@ -133,7 +138,7 @@ data class NotificationConversation(
.setAction(DeleteNotificationReceiver.DELETE_NOTIFICATION_ACTION)
.putExtra(DeleteNotificationReceiver.EXTRA_IDS, ids)
.putExtra(DeleteNotificationReceiver.EXTRA_MMS, mms)
.putExtra(DeleteNotificationReceiver.EXTRA_THREAD_IDS, longArrayOf(threadId))
.putParcelableArrayListExtra(DeleteNotificationReceiver.EXTRA_THREADS, arrayListOf(thread))
.makeUniqueToPreventMerging()
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
@@ -142,19 +147,19 @@ data class NotificationConversation(
fun getMarkAsReadIntent(context: Context): PendingIntent {
val intent = Intent(context, MarkReadReceiver::class.java)
.setAction(MarkReadReceiver.CLEAR_ACTION)
.putExtra(MarkReadReceiver.THREAD_IDS_EXTRA, longArrayOf(mostRecentNotification.threadId))
.putParcelableArrayListExtra(MarkReadReceiver.THREADS_EXTRA, arrayListOf(mostRecentNotification.thread))
.putExtra(MarkReadReceiver.NOTIFICATION_ID_EXTRA, notificationId)
.makeUniqueToPreventMerging()
return PendingIntent.getBroadcast(context, (threadId * 2).toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT)
return PendingIntent.getBroadcast(context, (thread.threadId * 2).toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
fun getQuickReplyIntent(context: Context): PendingIntent {
val intent: Intent = ConversationIntents.createPopUpBuilder(context, recipient.id, mostRecentNotification.threadId)
val intent: Intent = ConversationIntents.createPopUpBuilder(context, recipient.id, mostRecentNotification.thread.threadId)
.build()
.makeUniqueToPreventMerging()
return PendingIntent.getActivity(context, (threadId * 2).toInt() + 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)
return PendingIntent.getActivity(context, (thread.threadId * 2).toInt() + 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
fun getRemoteReplyIntent(context: Context, replyMethod: ReplyMethod): PendingIntent {
@@ -163,21 +168,22 @@ data class NotificationConversation(
.putExtra(RemoteReplyReceiver.RECIPIENT_EXTRA, recipient.id)
.putExtra(RemoteReplyReceiver.REPLY_METHOD, replyMethod)
.putExtra(RemoteReplyReceiver.EARLIEST_TIMESTAMP, notificationItems.first().timestamp)
.putExtra(RemoteReplyReceiver.GROUP_STORY_ID_EXTRA, notificationItems.first().thread.groupStoryId ?: Long.MIN_VALUE)
.makeUniqueToPreventMerging()
return PendingIntent.getBroadcast(context, (threadId * 2).toInt() + 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)
return PendingIntent.getBroadcast(context, (thread.threadId * 2).toInt() + 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
fun getTurnOffJoinedNotificationsIntent(context: Context): PendingIntent {
return PendingIntent.getActivity(
context,
0,
TurnOffContactJoinedNotificationsActivity.newIntent(context, threadId),
TurnOffContactJoinedNotificationsActivity.newIntent(context, thread.threadId),
PendingIntent.FLAG_UPDATE_CURRENT
)
}
override fun toString(): String {
return "NotificationConversation(threadId=$threadId, notificationItems=$notificationItems, messageCount=$messageCount, hasNewNotifications=${hasNewNotifications()})"
return "NotificationConversation(thread=$thread, notificationItems=$notificationItems, messageCount=$messageCount, hasNewNotifications=${hasNewNotifications()})"
}
}

View File

@@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.NotificationIds
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.stories.my.MyStoriesActivity
import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity
import org.thoughtcrime.securesms.util.BubbleUtil
import org.thoughtcrime.securesms.util.ConversationUtil
import org.thoughtcrime.securesms.util.ServiceUtil
@@ -42,26 +43,26 @@ object NotificationFactory {
fun notify(
context: Context,
state: NotificationStateV2,
visibleThreadId: Long,
targetThreadId: Long,
visibleThread: NotificationThread?,
targetThread: NotificationThread?,
defaultBubbleState: BubbleUtil.BubbleState,
lastAudibleNotification: Long,
notificationConfigurationChanged: Boolean,
alertOverrides: Set<Long>,
alertOverrides: Set<NotificationThread>,
previousState: NotificationStateV2
): Set<Long> {
): Set<NotificationThread> {
if (state.isEmpty) {
Log.d(TAG, "State is empty, bailing")
return emptySet()
}
val nonVisibleThreadCount: Int = state.conversations.count { it.threadId != visibleThreadId }
val nonVisibleThreadCount: Int = state.conversations.count { it.thread != visibleThread }
return if (Build.VERSION.SDK_INT < 24) {
notify19(
context = context,
state = state,
visibleThreadId = visibleThreadId,
targetThreadId = targetThreadId,
visibleThread = visibleThread,
targetThread = targetThread,
defaultBubbleState = defaultBubbleState,
lastAudibleNotification = lastAudibleNotification,
alertOverrides = alertOverrides,
@@ -71,8 +72,8 @@ object NotificationFactory {
notify24(
context = context,
state = state,
visibleThreadId = visibleThreadId,
targetThreadId = targetThreadId,
visibleThread = visibleThread,
targetThread = targetThread,
defaultBubbleState = defaultBubbleState,
lastAudibleNotification = lastAudibleNotification,
notificationConfigurationChanged = notificationConfigurationChanged,
@@ -86,16 +87,16 @@ object NotificationFactory {
private fun notify19(
context: Context,
state: NotificationStateV2,
visibleThreadId: Long,
targetThreadId: Long,
visibleThread: NotificationThread?,
targetThread: NotificationThread?,
defaultBubbleState: BubbleUtil.BubbleState,
lastAudibleNotification: Long,
alertOverrides: Set<Long>,
alertOverrides: Set<NotificationThread>,
nonVisibleThreadCount: Int
): Set<Long> {
val threadsThatNewlyAlerted: MutableSet<Long> = mutableSetOf()
): Set<NotificationThread> {
val threadsThatNewlyAlerted: MutableSet<NotificationThread> = mutableSetOf()
state.conversations.find { it.threadId == visibleThreadId }?.let { conversation ->
state.conversations.find { it.thread == visibleThread }?.let { conversation ->
if (conversation.hasNewNotifications()) {
Log.internal().i(TAG, "Thread is visible, notifying in thread. notificationId: ${conversation.notificationId}")
notifyInThread(context, conversation.recipient, lastAudibleNotification)
@@ -103,21 +104,21 @@ object NotificationFactory {
}
if (nonVisibleThreadCount == 1) {
state.conversations.first { it.threadId != visibleThreadId }.let { conversation ->
state.conversations.first { it.thread != visibleThread }.let { conversation ->
notifyForConversation(
context = context,
conversation = conversation,
targetThreadId = targetThreadId,
targetThread = targetThread,
defaultBubbleState = defaultBubbleState,
shouldAlert = (conversation.hasNewNotifications() || alertOverrides.contains(conversation.threadId)) && !conversation.mostRecentNotification.individualRecipient.isSelf
shouldAlert = (conversation.hasNewNotifications() || alertOverrides.contains(conversation.thread)) && !conversation.mostRecentNotification.individualRecipient.isSelf
)
if (conversation.hasNewNotifications()) {
threadsThatNewlyAlerted += conversation.threadId
threadsThatNewlyAlerted += conversation.thread
}
}
} else if (nonVisibleThreadCount > 1) {
val nonVisibleConversations: List<NotificationConversation> = state.getNonVisibleConversation(visibleThreadId)
threadsThatNewlyAlerted += nonVisibleConversations.filter { it.hasNewNotifications() }.map { it.threadId }
val nonVisibleConversations: List<NotificationConversation> = state.getNonVisibleConversation(visibleThread)
threadsThatNewlyAlerted += nonVisibleConversations.filter { it.hasNewNotifications() }.map { it.thread }
notifySummary(context = context, state = state.copy(conversations = nonVisibleConversations))
}
@@ -128,38 +129,38 @@ object NotificationFactory {
private fun notify24(
context: Context,
state: NotificationStateV2,
visibleThreadId: Long,
targetThreadId: Long,
visibleThread: NotificationThread?,
targetThread: NotificationThread?,
defaultBubbleState: BubbleUtil.BubbleState,
lastAudibleNotification: Long,
notificationConfigurationChanged: Boolean,
alertOverrides: Set<Long>,
alertOverrides: Set<NotificationThread>,
nonVisibleThreadCount: Int,
previousState: NotificationStateV2
): Set<Long> {
val threadsThatNewlyAlerted: MutableSet<Long> = mutableSetOf()
): Set<NotificationThread> {
val threadsThatNewlyAlerted: MutableSet<NotificationThread> = mutableSetOf()
state.conversations.forEach { conversation ->
if (conversation.threadId == visibleThreadId && conversation.hasNewNotifications()) {
if (conversation.thread == visibleThread && conversation.hasNewNotifications()) {
Log.internal().i(TAG, "Thread is visible, notifying in thread. notificationId: ${conversation.notificationId}")
notifyInThread(context, conversation.recipient, lastAudibleNotification)
} else if (notificationConfigurationChanged || conversation.hasNewNotifications() || alertOverrides.contains(conversation.threadId) || !conversation.hasSameContent(previousState.getConversation(conversation.threadId))) {
} else if (notificationConfigurationChanged || conversation.hasNewNotifications() || alertOverrides.contains(conversation.thread) || !conversation.hasSameContent(previousState.getConversation(conversation.thread))) {
if (conversation.hasNewNotifications()) {
threadsThatNewlyAlerted += conversation.threadId
threadsThatNewlyAlerted += conversation.thread
}
notifyForConversation(
context = context,
conversation = conversation,
targetThreadId = targetThreadId,
targetThread = targetThread,
defaultBubbleState = defaultBubbleState,
shouldAlert = (conversation.hasNewNotifications() || alertOverrides.contains(conversation.threadId)) && !conversation.mostRecentNotification.individualRecipient.isSelf
shouldAlert = (conversation.hasNewNotifications() || alertOverrides.contains(conversation.thread)) && !conversation.mostRecentNotification.individualRecipient.isSelf
)
}
}
if (nonVisibleThreadCount > 1 || ServiceUtil.getNotificationManager(context).isDisplayingSummaryNotification()) {
notifySummary(context = context, state = state.copy(conversations = state.getNonVisibleConversation(visibleThreadId)))
notifySummary(context = context, state = state.copy(conversations = state.getNonVisibleConversation(visibleThread)))
}
return threadsThatNewlyAlerted
@@ -168,7 +169,7 @@ object NotificationFactory {
private fun notifyForConversation(
context: Context,
conversation: NotificationConversation,
targetThreadId: Long,
targetThread: NotificationThread?,
defaultBubbleState: BubbleUtil.BubbleState,
shouldAlert: Boolean
) {
@@ -204,7 +205,7 @@ object NotificationFactory {
setLights()
setAlarms(conversation.recipient)
setTicker(conversation.mostRecentNotification.getStyledPrimaryText(context, true))
setBubbleMetadata(conversation, if (targetThreadId == conversation.threadId) defaultBubbleState else BubbleUtil.BubbleState.HIDDEN)
setBubbleMetadata(conversation, if (targetThread == conversation.thread) defaultBubbleState else BubbleUtil.BubbleState.HIDDEN)
}
if (conversation.isOnlyContactJoinedEvent) {
@@ -291,8 +292,8 @@ object NotificationFactory {
ringtone.play()
}
fun notifyMessageDeliveryFailed(context: Context, recipient: Recipient, threadId: Long, visibleThread: Long) {
if (threadId == visibleThread) {
fun notifyMessageDeliveryFailed(context: Context, recipient: Recipient, thread: NotificationThread, visibleThread: NotificationThread?) {
if (thread == visibleThread) {
notifyInThread(context, recipient, 0)
return
}
@@ -300,8 +301,10 @@ object NotificationFactory {
val intent: Intent = if (recipient.isDistributionList) {
Intent(context, MyStoriesActivity::class.java)
.makeUniqueToPreventMerging()
} else if (thread.groupStoryId != null) {
StoryViewerActivity.createIntent(context, recipient.id, thread.groupStoryId, recipient.shouldHideStory())
} else {
ConversationIntents.createBuilder(context, recipient.id, threadId)
ConversationIntents.createBuilder(context, recipient.id, thread.threadId)
.build()
.makeUniqueToPreventMerging()
}
@@ -320,11 +323,11 @@ object NotificationFactory {
setChannelId(NotificationChannels.FAILURES)
}
NotificationManagerCompat.from(context).safelyNotify(context, recipient, threadId.toInt(), builder.build())
NotificationManagerCompat.from(context).safelyNotify(context, recipient, NotificationIds.getNotificationIdForMessageDeliveryFailed(thread), builder.build())
}
fun notifyProofRequired(context: Context, recipient: Recipient, threadId: Long, visibleThread: Long) {
if (threadId == visibleThread) {
fun notifyProofRequired(context: Context, recipient: Recipient, thread: NotificationThread, visibleThread: NotificationThread?) {
if (thread == visibleThread) {
notifyInThread(context, recipient, 0)
return
}
@@ -332,8 +335,10 @@ object NotificationFactory {
val intent: Intent = if (recipient.isDistributionList) {
Intent(context, MyStoriesActivity::class.java)
.makeUniqueToPreventMerging()
} else if (thread.groupStoryId != null) {
StoryViewerActivity.createIntent(context, recipient.id, thread.groupStoryId, recipient.shouldHideStory())
} else {
ConversationIntents.createBuilder(context, recipient.id, threadId)
ConversationIntents.createBuilder(context, recipient.id, thread.threadId)
.build()
.makeUniqueToPreventMerging()
}
@@ -352,7 +357,7 @@ object NotificationFactory {
setChannelId(NotificationChannels.FAILURES)
}
NotificationManagerCompat.from(context).safelyNotify(context, recipient, threadId.toInt(), builder.build())
NotificationManagerCompat.from(context).safelyNotify(context, recipient, NotificationIds.getNotificationIdForMessageDeliveryFailed(thread), builder.build())
}
@JvmStatic
@@ -361,7 +366,7 @@ object NotificationFactory {
val conversation = NotificationConversation(
recipient = recipient,
threadId = threadId,
thread = NotificationThread.forConversation(threadId),
notificationItems = listOf(
MessageNotification(
threadRecipient = recipient,

View File

@@ -40,7 +40,7 @@ private const val MAX_DISPLAY_LENGTH = 500
sealed class NotificationItemV2(val threadRecipient: Recipient, protected val record: MessageRecord) : Comparable<NotificationItemV2> {
val id: Long = record.id
val threadId: Long = record.threadId
val thread = NotificationThread.fromMessageRecord(record)
val isMms: Boolean = record.isMms
val slideDeck: SlideDeck? = if (record.isViewOnce) null else (record as? MmsMessageRecord)?.slideDeck
val isJoined: Boolean = record.isJoined
@@ -126,7 +126,7 @@ sealed class NotificationItemV2(val threadRecipient: Recipient, protected val re
fun getPrimaryText(context: Context): CharSequence {
return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayMessage) {
if (RecipientUtil.isMessageRequestAccepted(context, threadId)) {
if (RecipientUtil.isMessageRequestAccepted(context, thread.threadId)) {
getPrimaryTextActual(context)
} else {
SpanUtil.italic(context.getString(R.string.SingleRecipientNotificationBuilder_message_request))
@@ -304,7 +304,7 @@ class ReactionNotification(threadRecipient: Recipient, record: MessageRecord, va
}
override fun getStartingPosition(context: Context): Int {
return SignalDatabase.mmsSms.getMessagePositionInConversation(threadId, record.dateReceived)
return SignalDatabase.mmsSms.getMessagePositionInConversation(thread.threadId, record.dateReceived)
}
override fun getLargeIconUri(): Uri? = null

View File

@@ -21,7 +21,7 @@ object NotificationStateProvider {
private val TAG = Log.tag(NotificationStateProvider::class.java)
@WorkerThread
fun constructNotificationState(stickyThreads: Map<Long, MessageNotifierV2.StickyThread>, notificationProfile: NotificationProfile?): NotificationStateV2 {
fun constructNotificationState(stickyThreads: Map<NotificationThread, MessageNotifierV2.StickyThread>, notificationProfile: NotificationProfile?): NotificationStateV2 {
val messages: MutableList<NotificationMessage> = mutableListOf()
SignalDatabase.mmsSms.getMessagesForNotificationState(stickyThreads.values).use { unreadMessages ->
@@ -40,8 +40,8 @@ object NotificationStateProvider {
messageRecord = record,
reactions = if (hasUnreadReactions) SignalDatabase.reactions.getReactions(MessageId(record.id, record.isMms)) else emptyList(),
threadRecipient = threadRecipient,
threadId = record.threadId,
stickyThread = stickyThreads.containsKey(record.threadId),
thread = NotificationThread.fromMessageRecord(record),
stickyThread = stickyThreads.containsKey(NotificationThread.fromMessageRecord(record)),
isUnreadMessage = CursorUtil.requireInt(unreadMessages, MmsSmsColumns.READ) == 0,
hasUnreadReactions = hasUnreadReactions,
lastReactionRead = CursorUtil.requireLong(unreadMessages, MmsSmsColumns.REACTIONS_LAST_SEEN)
@@ -62,8 +62,8 @@ object NotificationStateProvider {
val muteFilteredMessages: MutableList<NotificationStateV2.FilteredMessage> = mutableListOf()
val profileFilteredMessages: MutableList<NotificationStateV2.FilteredMessage> = mutableListOf()
messages.groupBy { it.threadId }
.forEach { (threadId, threadMessages) ->
messages.groupBy { it.thread }
.forEach { (thread, threadMessages) ->
var notificationItems: MutableList<NotificationItemV2> = mutableListOf()
for (notification: NotificationMessage in threadMessages) {
@@ -87,13 +87,13 @@ object NotificationStateProvider {
}
notificationItems.sort()
if (notificationItems.isNotEmpty() && stickyThreads.containsKey(threadId) && !notificationItems.last().individualRecipient.isSelf) {
if (notificationItems.isNotEmpty() && stickyThreads.containsKey(thread) && !notificationItems.last().individualRecipient.isSelf) {
val indexOfOldestNonSelfMessage: Int = notificationItems.indexOfLast { it.individualRecipient.isSelf } + 1
notificationItems = notificationItems.slice(indexOfOldestNonSelfMessage..notificationItems.lastIndex).toMutableList()
}
if (notificationItems.isNotEmpty()) {
conversations += NotificationConversation(notificationItems[0].threadRecipient, threadId, notificationItems)
conversations += NotificationConversation(notificationItems[0].threadRecipient, thread, notificationItems)
}
}
@@ -104,7 +104,7 @@ object NotificationStateProvider {
val messageRecord: MessageRecord,
val reactions: List<ReactionRecord>,
val threadRecipient: Recipient,
val threadId: Long,
val thread: NotificationThread,
val stickyThread: Boolean,
val isUnreadMessage: Boolean,
val hasUnreadReactions: Boolean,

View File

@@ -39,21 +39,21 @@ data class NotificationStateV2(val conversations: List<NotificationConversation>
val mostRecentSender: Recipient?
get() = mostRecentNotification?.individualRecipient
fun getNonVisibleConversation(visibleThreadId: Long): List<NotificationConversation> {
return conversations.filterNot { it.threadId == visibleThreadId }
fun getNonVisibleConversation(visibleThread: NotificationThread?): List<NotificationConversation> {
return conversations.filterNot { it.thread == visibleThread }
}
fun getConversation(threadId: Long): NotificationConversation? {
return conversations.firstOrNull { it.threadId == threadId }
fun getConversation(notificationThread: NotificationThread): NotificationConversation? {
return conversations.firstOrNull { it.thread == notificationThread }
}
fun getDeleteIntent(context: Context): PendingIntent? {
val ids = LongArray(messageCount)
val mms = BooleanArray(ids.size)
val threadIds: MutableList<Long> = mutableListOf()
val threads: MutableList<NotificationThread> = mutableListOf()
conversations.forEach { conversation ->
threadIds += conversation.threadId
threads += conversation.thread
conversation.notificationItems.forEachIndexed { index, notificationItem ->
ids[index] = notificationItem.id
mms[index] = notificationItem.isMms
@@ -64,30 +64,24 @@ data class NotificationStateV2(val conversations: List<NotificationConversation>
.setAction(DeleteNotificationReceiver.DELETE_NOTIFICATION_ACTION)
.putExtra(DeleteNotificationReceiver.EXTRA_IDS, ids)
.putExtra(DeleteNotificationReceiver.EXTRA_MMS, mms)
.putExtra(DeleteNotificationReceiver.EXTRA_THREAD_IDS, threadIds.toLongArray())
.putParcelableArrayListExtra(DeleteNotificationReceiver.EXTRA_THREADS, ArrayList(threads))
.makeUniqueToPreventMerging()
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
fun getMarkAsReadIntent(context: Context): PendingIntent? {
val threadArray = LongArray(conversations.size)
conversations.forEachIndexed { index, conversation ->
threadArray[index] = conversation.threadId
}
val intent = Intent(context, MarkReadReceiver::class.java).setAction(MarkReadReceiver.CLEAR_ACTION)
.putExtra(MarkReadReceiver.THREAD_IDS_EXTRA, threadArray)
.putParcelableArrayListExtra(MarkReadReceiver.THREADS_EXTRA, ArrayList(conversations.map { it.thread }))
.putExtra(MarkReadReceiver.NOTIFICATION_ID_EXTRA, NotificationIds.MESSAGE_SUMMARY)
.makeUniqueToPreventMerging()
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
fun getThreadsWithMostRecentNotificationFromSelf(): Set<Long> {
fun getThreadsWithMostRecentNotificationFromSelf(): Set<NotificationThread> {
return conversations.filter { it.mostRecentNotification.individualRecipient.isSelf }
.map { it.threadId }
.map { it.thread }
.toSet()
}

View File

@@ -0,0 +1,36 @@
package org.thoughtcrime.securesms.notifications.v2
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.database.model.ParentStoryId
/**
* Represents a "thread" that a notification can belong to.
*/
@Parcelize
data class NotificationThread(
val threadId: Long,
val groupStoryId: Long?
) : Parcelable {
companion object {
@JvmStatic
fun forConversation(threadId: Long): NotificationThread {
return NotificationThread(
threadId = threadId,
groupStoryId = null
)
}
@JvmStatic
fun fromMessageRecord(record: MessageRecord): NotificationThread {
return NotificationThread(record.threadId, ((record as? MmsMessageRecord)?.parentStoryId as? ParentStoryId.GroupReply)?.serialize())
}
@JvmStatic
fun fromThreadAndReply(threadId: Long, groupReply: ParentStoryId.GroupReply?): NotificationThread {
return NotificationThread(threadId, groupReply?.serialize())
}
}
}