Implement group story notifications.

This commit is contained in:
Alex Hart
2022-05-12 15:37:28 -03:00
committed by Cody Henthorne
parent 01543dd52b
commit a03c49e12c
66 changed files with 865 additions and 473 deletions

View File

@@ -8,7 +8,7 @@ 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 org.thoughtcrime.securesms.notifications.v2.ConversationId;
import java.util.ArrayList;
@@ -27,11 +27,11 @@ public class DeleteNotificationReceiver extends BroadcastReceiver {
notifier.clearReminder(context);
final long[] ids = intent.getLongArrayExtra(EXTRA_IDS);
final boolean[] mms = intent.getBooleanArrayExtra(EXTRA_MMS);
final ArrayList<NotificationThread> threads = intent.getParcelableArrayListExtra(EXTRA_THREADS);
final boolean[] mms = intent.getBooleanArrayExtra(EXTRA_MMS);
final ArrayList<ConversationId> threads = intent.getParcelableArrayListExtra(EXTRA_THREADS);
if (threads != null) {
for (NotificationThread thread : threads) {
for (ConversationId thread : threads) {
notifier.removeStickyThread(thread);
}
}

View File

@@ -19,7 +19,7 @@ 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.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
@@ -41,11 +41,11 @@ public class MarkReadReceiver extends BroadcastReceiver {
if (!CLEAR_ACTION.equals(intent.getAction()))
return;
final ArrayList<NotificationThread> threads = intent.getParcelableArrayListExtra(THREADS_EXTRA);
final ArrayList<ConversationId> threads = intent.getParcelableArrayListExtra(THREADS_EXTRA);
if (threads != null) {
MessageNotifier notifier = ApplicationDependencies.getMessageNotifier();
for (NotificationThread thread : threads) {
for (ConversationId thread : threads) {
notifier.removeStickyThread(thread);
}
@@ -55,9 +55,9 @@ public class MarkReadReceiver extends BroadcastReceiver {
SignalExecutors.BOUNDED.execute(() -> {
List<MarkedMessageInfo> messageIdsCollection = new LinkedList<>();
for (NotificationThread thread : threads) {
for (ConversationId thread : threads) {
Log.i(TAG, "Marking as read: " + thread);
List<MarkedMessageInfo> messageIds = SignalDatabase.threads().setRead(thread.getThreadId(), true);
List<MarkedMessageInfo> messageIds = SignalDatabase.threads().setRead(thread, true);
messageIdsCollection.addAll(messageIds);
}

View File

@@ -9,28 +9,28 @@ 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.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.BubbleUtil;
import java.util.Optional;
public interface MessageNotifier {
void setVisibleThread(@Nullable NotificationThread notificationThread);
@NonNull Optional<NotificationThread> getVisibleThread();
void setVisibleThread(@Nullable ConversationId conversationId);
@NonNull Optional<ConversationId> getVisibleThread();
void clearVisibleThread();
void setLastDesktopActivityTimestamp(long timestamp);
void notifyMessageDeliveryFailed(@NonNull Context context, @NonNull Recipient recipient, @NonNull NotificationThread notificationThread);
void notifyProofRequired(@NonNull Context context, @NonNull Recipient recipient, @NonNull NotificationThread notificationThread);
void notifyMessageDeliveryFailed(@NonNull Context context, @NonNull Recipient recipient, @NonNull ConversationId conversationId);
void notifyProofRequired(@NonNull Context context, @NonNull Recipient recipient, @NonNull ConversationId conversationId);
void cancelDelayedNotifications();
void updateNotification(@NonNull Context context);
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 updateNotification(@NonNull Context context, @NonNull ConversationId conversationId);
void updateNotification(@NonNull Context context, @NonNull ConversationId conversationId, @NonNull BubbleUtil.BubbleState defaultBubbleState);
void updateNotification(@NonNull Context context, @NonNull ConversationId conversationId, boolean signal);
void updateNotification(@NonNull Context context, @Nullable ConversationId conversationId, boolean signal, int reminderCount, @NonNull BubbleUtil.BubbleState defaultBubbleState);
void clearReminder(@NonNull Context context);
void addStickyThread(@NonNull NotificationThread notificationThread, long earliestTimestamp);
void removeStickyThread(@NonNull NotificationThread notificationThread);
void addStickyThread(@NonNull ConversationId conversationId, long earliestTimestamp);
void removeStickyThread(@NonNull ConversationId conversationId);
class ReminderReceiver extends BroadcastReceiver {

View File

@@ -15,7 +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.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.BubbleUtil;
import org.thoughtcrime.securesms.util.ConversationUtil;
@@ -185,10 +185,10 @@ public final class NotificationCancellationHelper {
return true;
}
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);
Long threadId = SignalDatabase.threads().getThreadIdFor(recipientId);
Optional<ConversationId> focusedThread = ApplicationDependencies.getMessageNotifier().getVisibleThread();
Long focusedThreadId = focusedThread.map(ConversationId::getThreadId).orElse(null);
Long focusedGroupStoryId = focusedThread.map(ConversationId::getGroupStoryId).orElse(null);
if (Objects.equals(threadId, focusedThreadId) && focusedGroupStoryId == null) {
Log.d(TAG, "isCancellable: user entered full screen thread.");

View File

@@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.notifications;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.notifications.v2.NotificationThread;
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
public final class NotificationIds {
@@ -25,19 +25,19 @@ public final class NotificationIds {
private NotificationIds() { }
public static int getNotificationIdForThread(@NonNull NotificationThread notificationThread) {
if (notificationThread.getGroupStoryId() != null) {
return STORY_THREAD + notificationThread.getGroupStoryId().intValue();
public static int getNotificationIdForThread(@NonNull ConversationId conversationId) {
if (conversationId.getGroupStoryId() != null) {
return STORY_THREAD + conversationId.getGroupStoryId().intValue();
} else {
return THREAD + (int) notificationThread.getThreadId();
return THREAD + (int) conversationId.getThreadId();
}
}
public static int getNotificationIdForMessageDeliveryFailed(@NonNull NotificationThread notificationThread) {
if (notificationThread.getGroupStoryId() != null) {
return STORY_MESSAGE_DELIVERY_FAILURE + notificationThread.getGroupStoryId().intValue();
public static int getNotificationIdForMessageDeliveryFailed(@NonNull ConversationId conversationId) {
if (conversationId.getGroupStoryId() != null) {
return STORY_MESSAGE_DELIVERY_FAILURE + conversationId.getGroupStoryId().intValue();
} else {
return MESSAGE_DELIVERY_FAILURE + (int) notificationThread.getThreadId();
return MESSAGE_DELIVERY_FAILURE + (int) conversationId.getThreadId();
}
}
}

View File

@@ -11,7 +11,7 @@ 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.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.BubbleUtil;
import org.thoughtcrime.securesms.util.LeakyBucketLimiter;
@@ -33,12 +33,12 @@ public class OptimizedMessageNotifier implements MessageNotifier {
}
@Override
public void setVisibleThread(@Nullable NotificationThread notificationThread) {
getNotifier().setVisibleThread(notificationThread);
public void setVisibleThread(@Nullable ConversationId conversationId) {
getNotifier().setVisibleThread(conversationId);
}
@Override
public @NonNull Optional<NotificationThread> getVisibleThread() {
public @NonNull Optional<ConversationId> getVisibleThread() {
return getNotifier().getVisibleThread();
}
@@ -53,13 +53,13 @@ public class OptimizedMessageNotifier implements MessageNotifier {
}
@Override
public void notifyMessageDeliveryFailed(@NonNull Context context, @NonNull Recipient recipient, @NonNull NotificationThread notificationThread) {
getNotifier().notifyMessageDeliveryFailed(context, recipient, notificationThread);
public void notifyMessageDeliveryFailed(@NonNull Context context, @NonNull Recipient recipient, @NonNull ConversationId conversationId) {
getNotifier().notifyMessageDeliveryFailed(context, recipient, conversationId);
}
@Override
public void notifyProofRequired(@NonNull Context context, @NonNull Recipient recipient, @NonNull NotificationThread notificationThread) {
getNotifier().notifyProofRequired(context, recipient, notificationThread);
public void notifyProofRequired(@NonNull Context context, @NonNull Recipient recipient, @NonNull ConversationId conversationId) {
getNotifier().notifyProofRequired(context, recipient, conversationId);
}
@Override
@@ -73,23 +73,23 @@ public class OptimizedMessageNotifier implements MessageNotifier {
}
@Override
public void updateNotification(@NonNull Context context, @NonNull NotificationThread notificationThread) {
runOnLimiter(() -> getNotifier().updateNotification(context, notificationThread));
public void updateNotification(@NonNull Context context, @NonNull ConversationId conversationId) {
runOnLimiter(() -> getNotifier().updateNotification(context, conversationId));
}
@Override
public void updateNotification(@NonNull Context context, @NonNull NotificationThread notificationThread, @NonNull BubbleUtil.BubbleState defaultBubbleState) {
runOnLimiter(() -> getNotifier().updateNotification(context, notificationThread, defaultBubbleState));
public void updateNotification(@NonNull Context context, @NonNull ConversationId conversationId, @NonNull BubbleUtil.BubbleState defaultBubbleState) {
runOnLimiter(() -> getNotifier().updateNotification(context, conversationId, defaultBubbleState));
}
@Override
public void updateNotification(@NonNull Context context, @NonNull NotificationThread notificationThread, boolean signal) {
runOnLimiter(() -> getNotifier().updateNotification(context, notificationThread, signal));
public void updateNotification(@NonNull Context context, @NonNull ConversationId conversationId, boolean signal) {
runOnLimiter(() -> getNotifier().updateNotification(context, conversationId, signal));
}
@Override
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));
public void updateNotification(@NonNull Context context, @Nullable ConversationId conversationId, boolean signal, int reminderCount, @NonNull BubbleUtil.BubbleState defaultBubbleState) {
runOnLimiter(() -> getNotifier().updateNotification(context, conversationId, signal, reminderCount, defaultBubbleState));
}
@Override
@@ -98,13 +98,13 @@ public class OptimizedMessageNotifier implements MessageNotifier {
}
@Override
public void addStickyThread(@NonNull NotificationThread notificationThread, long earliestTimestamp) {
getNotifier().addStickyThread(notificationThread, earliestTimestamp);
public void addStickyThread(@NonNull ConversationId conversationId, long earliestTimestamp) {
getNotifier().addStickyThread(conversationId, earliestTimestamp);
}
@Override
public void removeStickyThread(@NonNull NotificationThread notificationThread) {
getNotifier().removeStickyThread(notificationThread);
public void removeStickyThread(@NonNull ConversationId conversationId) {
getNotifier().removeStickyThread(conversationId);
}
private void runOnLimiter(@NonNull Runnable runnable) {

View File

@@ -33,7 +33,7 @@ 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.notifications.v2.ConversationId;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sms.MessageSender;
@@ -120,7 +120,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
}
ApplicationDependencies.getMessageNotifier()
.addStickyThread(new NotificationThread(threadId, groupStoryId != Long.MIN_VALUE ? groupStoryId : null),
.addStickyThread(new ConversationId(threadId, groupStoryId != Long.MIN_VALUE ? groupStoryId : null),
intent.getLongExtra(EARLIEST_TIMESTAMP, System.currentTimeMillis()));
List<MarkedMessageInfo> messageIds = SignalDatabase.threads().setRead(threadId, true);

View File

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

View File

@@ -45,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: NotificationThread? = null
@Volatile private var visibleThread: ConversationId? = null
@Volatile private var lastDesktopActivityTimestamp: Long = -1
@Volatile private var lastAudibleNotification: Long = -1
@Volatile private var lastScheduledReminder: Long = 0
@@ -53,17 +53,17 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
@Volatile private var previousPrivacyPreference: NotificationPrivacyPreference = SignalStore.settings().messageNotificationsPrivacy
@Volatile private var previousState: NotificationStateV2 = NotificationStateV2.EMPTY
private val threadReminders: MutableMap<NotificationThread, Reminder> = ConcurrentHashMap()
private val stickyThreads: MutableMap<NotificationThread, StickyThread> = mutableMapOf()
private val threadReminders: MutableMap<ConversationId, Reminder> = ConcurrentHashMap()
private val stickyThreads: MutableMap<ConversationId, StickyThread> = mutableMapOf()
private val executor = CancelableExecutor()
override fun setVisibleThread(notificationThread: NotificationThread?) {
visibleThread = notificationThread
stickyThreads.remove(notificationThread)
override fun setVisibleThread(conversationId: ConversationId?) {
visibleThread = conversationId
stickyThreads.remove(conversationId)
}
override fun getVisibleThread(): Optional<NotificationThread> {
override fun getVisibleThread(): Optional<ConversationId> {
return Optional.ofNullable(visibleThread)
}
@@ -75,12 +75,12 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
lastDesktopActivityTimestamp = timestamp
}
override fun notifyMessageDeliveryFailed(context: Context, recipient: Recipient, notificationThread: NotificationThread) {
NotificationFactory.notifyMessageDeliveryFailed(context, recipient, notificationThread, visibleThread)
override fun notifyMessageDeliveryFailed(context: Context, recipient: Recipient, conversationId: ConversationId) {
NotificationFactory.notifyMessageDeliveryFailed(context, recipient, conversationId, visibleThread)
}
override fun notifyProofRequired(context: Context, recipient: Recipient, notificationThread: NotificationThread) {
NotificationFactory.notifyProofRequired(context, recipient, notificationThread, visibleThread)
override fun notifyProofRequired(context: Context, recipient: Recipient, conversationId: ConversationId) {
NotificationFactory.notifyProofRequired(context, recipient, conversationId, visibleThread)
}
override fun cancelDelayedNotifications() {
@@ -91,21 +91,21 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
updateNotification(context, null, false, 0, BubbleState.HIDDEN)
}
override fun updateNotification(context: Context, notificationThread: NotificationThread) {
override fun updateNotification(context: Context, conversationId: ConversationId) {
if (System.currentTimeMillis() - lastDesktopActivityTimestamp < DESKTOP_ACTIVITY_PERIOD) {
Log.i(TAG, "Scheduling delayed notification...")
executor.enqueue(context, notificationThread)
executor.enqueue(context, conversationId)
} else {
updateNotification(context, notificationThread, true)
updateNotification(context, conversationId, true)
}
}
override fun updateNotification(context: Context, notificationThread: NotificationThread, defaultBubbleState: BubbleState) {
updateNotification(context, notificationThread, false, 0, defaultBubbleState)
override fun updateNotification(context: Context, conversationId: ConversationId, defaultBubbleState: BubbleState) {
updateNotification(context, conversationId, false, 0, defaultBubbleState)
}
override fun updateNotification(context: Context, notificationThread: NotificationThread, signal: Boolean) {
updateNotification(context, notificationThread, signal, 0, BubbleState.HIDDEN)
override fun updateNotification(context: Context, conversationId: ConversationId, signal: Boolean) {
updateNotification(context, conversationId, signal, 0, BubbleState.HIDDEN)
}
/**
@@ -114,7 +114,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
*/
override fun updateNotification(
context: Context,
notificationThread: NotificationThread?,
conversationId: ConversationId?,
signal: Boolean,
reminderCount: Int,
defaultBubbleState: BubbleState
@@ -164,7 +164,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
val displayedNotifications: Set<Int>? = ServiceUtil.getNotificationManager(context).getDisplayedNotificationIds().getOrNull()
if (displayedNotifications != null) {
val cleanedUpThreads: MutableSet<NotificationThread> = mutableSetOf()
val cleanedUpThreads: MutableSet<ConversationId> = mutableSetOf()
state.conversations.filterNot { it.hasNewNotifications() || displayedNotifications.contains(it.notificationId) }
.forEach { conversation ->
cleanedUpThreads += conversation.thread
@@ -179,7 +179,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
}
}
val retainStickyThreadIds: Set<NotificationThread> = state.getThreadsWithMostRecentNotificationFromSelf()
val retainStickyThreadIds: Set<ConversationId> = state.getThreadsWithMostRecentNotificationFromSelf()
stickyThreads.keys.retainAll { retainStickyThreadIds.contains(it) }
if (state.isEmpty) {
@@ -190,13 +190,13 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
return
}
val alertOverrides: Set<NotificationThread> = threadReminders.filter { (_, reminder) -> reminder.lastNotified < System.currentTimeMillis() - REMINDER_TIMEOUT }.keys
val alertOverrides: Set<ConversationId> = threadReminders.filter { (_, reminder) -> reminder.lastNotified < System.currentTimeMillis() - REMINDER_TIMEOUT }.keys
val threadsThatAlerted: Set<NotificationThread> = NotificationFactory.notify(
val threadsThatAlerted: Set<ConversationId> = NotificationFactory.notify(
context = ContextThemeWrapper(context, R.style.TextSecure_LightTheme),
state = state,
visibleThread = visibleThread,
targetThread = notificationThread,
targetThread = conversationId,
defaultBubbleState = defaultBubbleState,
lastAudibleNotification = lastAudibleNotification,
notificationConfigurationChanged = notificationConfigurationChanged,
@@ -238,23 +238,23 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
// Intentionally left blank
}
override fun addStickyThread(notificationThread: NotificationThread, earliestTimestamp: Long) {
stickyThreads[notificationThread] = StickyThread(notificationThread, NotificationIds.getNotificationIdForThread(notificationThread), earliestTimestamp)
override fun addStickyThread(conversationId: ConversationId, earliestTimestamp: Long) {
stickyThreads[conversationId] = StickyThread(conversationId, NotificationIds.getNotificationIdForThread(conversationId), earliestTimestamp)
}
override fun removeStickyThread(notificationThread: NotificationThread) {
stickyThreads.remove(notificationThread)
override fun removeStickyThread(conversationId: ConversationId) {
stickyThreads.remove(conversationId)
}
private fun updateReminderTimestamps(context: Context, alertOverrides: Set<NotificationThread>, threadsThatAlerted: Set<NotificationThread>) {
private fun updateReminderTimestamps(context: Context, alertOverrides: Set<ConversationId>, threadsThatAlerted: Set<ConversationId>) {
if (SignalStore.settings().messageNotificationsRepeatAlerts == 0) {
return
}
val iterator: MutableIterator<MutableEntry<NotificationThread, Reminder>> = threadReminders.iterator()
val iterator: MutableIterator<MutableEntry<ConversationId, Reminder>> = threadReminders.iterator()
while (iterator.hasNext()) {
val entry: MutableEntry<NotificationThread, Reminder> = iterator.next()
val (id: NotificationThread, reminder: Reminder) = entry
val entry: MutableEntry<ConversationId, Reminder> = iterator.next()
val (id: ConversationId, reminder: Reminder) = entry
if (alertOverrides.contains(id)) {
val notifyCount: Int = reminder.count + 1
if (notifyCount >= SignalStore.settings().messageNotificationsRepeatAlerts) {
@@ -265,7 +265,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
}
}
for (alertedThreadId: NotificationThread in threadsThatAlerted) {
for (alertedThreadId: ConversationId in threadsThatAlerted) {
threadReminders[alertedThreadId] = Reminder(lastAudibleNotification)
}
@@ -319,7 +319,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
}
}
data class StickyThread(val notificationThread: NotificationThread, val notificationId: Int, val earliestTimestamp: Long)
data class StickyThread(val conversationId: ConversationId, val notificationId: Int, val earliestTimestamp: Long)
private data class Reminder(val lastNotified: Long, val count: Int = 0)
}
@@ -368,8 +368,8 @@ private class CancelableExecutor {
private val executor: Executor = Executors.newSingleThreadExecutor()
private val tasks: MutableSet<DelayedNotification> = mutableSetOf()
fun enqueue(context: Context, notificationThread: NotificationThread) {
execute(DelayedNotification(context, notificationThread))
fun enqueue(context: Context, conversationId: ConversationId) {
execute(DelayedNotification(context, conversationId))
}
private fun execute(runnable: DelayedNotification) {
@@ -389,7 +389,7 @@ private class CancelableExecutor {
}
}
private class DelayedNotification constructor(private val context: Context, private val thread: NotificationThread) : Runnable {
private class DelayedNotification constructor(private val context: Context, private val thread: ConversationId) : Runnable {
private val canceled = AtomicBoolean(false)
private val delayUntil: Long = System.currentTimeMillis() + DELAY

View File

@@ -7,6 +7,7 @@ import android.graphics.drawable.Drawable
import android.net.Uri
import android.text.SpannableStringBuilder
import androidx.core.app.TaskStackBuilder
import org.signal.core.util.PendingIntentFlags
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.contacts.TurnOffContactJoinedNotificationsActivity
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto
@@ -22,6 +23,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.StoryViewerArgs
import org.thoughtcrime.securesms.stories.viewer.StoryViewerActivity
import org.thoughtcrime.securesms.util.Util
@@ -31,7 +33,7 @@ import org.thoughtcrime.securesms.util.Util
*/
data class NotificationConversation(
val recipient: Recipient,
val thread: NotificationThread,
val thread: ConversationId,
val notificationItems: List<NotificationItemV2>
) {
val mostRecentNotification: NotificationItemV2 = notificationItems.last()
@@ -43,7 +45,7 @@ data class NotificationConversation(
fun getContentTitle(context: Context): CharSequence {
return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayContact) {
recipient.getDisplayName(context)
getDisplayName(context)
} else {
context.getString(R.string.SingleRecipientNotificationBuilder_signal)
}
@@ -82,7 +84,7 @@ data class NotificationConversation(
fun getConversationTitle(context: Context): CharSequence? {
if (SignalStore.settings().messageNotificationsPrivacy.isDisplayContact) {
return if (isGroup) recipient.getDisplayName(context) else null
return if (isGroup) getDisplayName(context) else null
}
return context.getString(R.string.SingleRecipientNotificationBuilder_signal)
}
@@ -113,13 +115,21 @@ data class NotificationConversation(
fun getPendingIntent(context: Context): PendingIntent {
val intent: Intent = if (thread.groupStoryId != null) {
StoryViewerActivity.createIntent(context, recipient.id, thread.groupStoryId, recipient.shouldHideStory())
StoryViewerActivity.createIntent(
context,
StoryViewerArgs(
recipientId = recipient.id,
storyId = thread.groupStoryId,
isInHiddenStoryMode = recipient.shouldHideStory(),
isFromNotification = true,
groupReplyStartPosition = mostRecentNotification.getStartingPosition(context)
)
)
} else {
ConversationIntents.createBuilder(context, recipient.id, thread.threadId)
.withStartingPosition(mostRecentNotification.getStartingPosition(context))
.build()
.makeUniqueToPreventMerging()
}
}.makeUniqueToPreventMerging()
return TaskStackBuilder.create(context)
.addNextIntentWithParentStack(intent)
@@ -141,7 +151,7 @@ data class NotificationConversation(
.putParcelableArrayListExtra(DeleteNotificationReceiver.EXTRA_THREADS, arrayListOf(thread))
.makeUniqueToPreventMerging()
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
return PendingIntent.getBroadcast(context, 0, intent, PendingIntentFlags.updateCurrent())
}
fun getMarkAsReadIntent(context: Context): PendingIntent {
@@ -151,7 +161,7 @@ data class NotificationConversation(
.putExtra(MarkReadReceiver.NOTIFICATION_ID_EXTRA, notificationId)
.makeUniqueToPreventMerging()
return PendingIntent.getBroadcast(context, (thread.threadId * 2).toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT)
return PendingIntent.getBroadcast(context, (thread.threadId * 2).toInt(), intent, PendingIntentFlags.updateCurrent())
}
fun getQuickReplyIntent(context: Context): PendingIntent {
@@ -159,7 +169,7 @@ data class NotificationConversation(
.build()
.makeUniqueToPreventMerging()
return PendingIntent.getActivity(context, (thread.threadId * 2).toInt() + 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)
return PendingIntent.getActivity(context, (thread.threadId * 2).toInt() + 1, intent, PendingIntentFlags.updateCurrent())
}
fun getRemoteReplyIntent(context: Context, replyMethod: ReplyMethod): PendingIntent {
@@ -171,7 +181,7 @@ data class NotificationConversation(
.putExtra(RemoteReplyReceiver.GROUP_STORY_ID_EXTRA, notificationItems.first().thread.groupStoryId ?: Long.MIN_VALUE)
.makeUniqueToPreventMerging()
return PendingIntent.getBroadcast(context, (thread.threadId * 2).toInt() + 1, intent, PendingIntent.FLAG_UPDATE_CURRENT)
return PendingIntent.getBroadcast(context, (thread.threadId * 2).toInt() + 1, intent, PendingIntentFlags.updateCurrent())
}
fun getTurnOffJoinedNotificationsIntent(context: Context): PendingIntent {
@@ -179,10 +189,18 @@ data class NotificationConversation(
context,
0,
TurnOffContactJoinedNotificationsActivity.newIntent(context, thread.threadId),
PendingIntent.FLAG_UPDATE_CURRENT
PendingIntentFlags.updateCurrent()
)
}
private fun getDisplayName(context: Context): String {
return if (thread.groupStoryId != null) {
context.getString(R.string.SingleRecipientNotificationBuilder__s_dot_story, recipient.getDisplayName(context))
} else {
recipient.getDisplayName(context)
}
}
override fun toString(): String {
return "NotificationConversation(thread=$thread, notificationItems=$notificationItems, messageCount=$messageCount, hasNewNotifications=${hasNewNotifications()})"
}

View File

@@ -27,7 +27,6 @@ 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
@@ -43,14 +42,14 @@ object NotificationFactory {
fun notify(
context: Context,
state: NotificationStateV2,
visibleThread: NotificationThread?,
targetThread: NotificationThread?,
visibleThread: ConversationId?,
targetThread: ConversationId?,
defaultBubbleState: BubbleUtil.BubbleState,
lastAudibleNotification: Long,
notificationConfigurationChanged: Boolean,
alertOverrides: Set<NotificationThread>,
alertOverrides: Set<ConversationId>,
previousState: NotificationStateV2
): Set<NotificationThread> {
): Set<ConversationId> {
if (state.isEmpty) {
Log.d(TAG, "State is empty, bailing")
return emptySet()
@@ -87,14 +86,14 @@ object NotificationFactory {
private fun notify19(
context: Context,
state: NotificationStateV2,
visibleThread: NotificationThread?,
targetThread: NotificationThread?,
visibleThread: ConversationId?,
targetThread: ConversationId?,
defaultBubbleState: BubbleUtil.BubbleState,
lastAudibleNotification: Long,
alertOverrides: Set<NotificationThread>,
alertOverrides: Set<ConversationId>,
nonVisibleThreadCount: Int
): Set<NotificationThread> {
val threadsThatNewlyAlerted: MutableSet<NotificationThread> = mutableSetOf()
): Set<ConversationId> {
val threadsThatNewlyAlerted: MutableSet<ConversationId> = mutableSetOf()
state.conversations.find { it.thread == visibleThread }?.let { conversation ->
if (conversation.hasNewNotifications()) {
@@ -129,16 +128,16 @@ object NotificationFactory {
private fun notify24(
context: Context,
state: NotificationStateV2,
visibleThread: NotificationThread?,
targetThread: NotificationThread?,
visibleThread: ConversationId?,
targetThread: ConversationId?,
defaultBubbleState: BubbleUtil.BubbleState,
lastAudibleNotification: Long,
notificationConfigurationChanged: Boolean,
alertOverrides: Set<NotificationThread>,
alertOverrides: Set<ConversationId>,
nonVisibleThreadCount: Int,
previousState: NotificationStateV2
): Set<NotificationThread> {
val threadsThatNewlyAlerted: MutableSet<NotificationThread> = mutableSetOf()
): Set<ConversationId> {
val threadsThatNewlyAlerted: MutableSet<ConversationId> = mutableSetOf()
state.conversations.forEach { conversation ->
if (conversation.thread == visibleThread && conversation.hasNewNotifications()) {
@@ -169,7 +168,7 @@ object NotificationFactory {
private fun notifyForConversation(
context: Context,
conversation: NotificationConversation,
targetThread: NotificationThread?,
targetThread: ConversationId?,
defaultBubbleState: BubbleUtil.BubbleState,
shouldAlert: Boolean
) {
@@ -189,8 +188,12 @@ object NotificationFactory {
setContentTitle(conversation.getContentTitle(context))
setLargeIcon(conversation.getContactLargeIcon(context).toLargeBitmap(context))
addPerson(conversation.recipient)
setShortcutId(ConversationUtil.getShortcutId(conversation.recipient))
setLocusId(ConversationUtil.getShortcutId(conversation.recipient))
if (conversation.thread.groupStoryId == null) {
setShortcutId(ConversationUtil.getShortcutId(conversation.recipient))
setLocusId(ConversationUtil.getShortcutId(conversation.recipient))
}
setContentInfo(conversation.messageCount.toString())
setNumber(conversation.messageCount)
setContentText(conversation.getContentText(context))
@@ -292,22 +295,18 @@ object NotificationFactory {
ringtone.play()
}
fun notifyMessageDeliveryFailed(context: Context, recipient: Recipient, thread: NotificationThread, visibleThread: NotificationThread?) {
fun notifyMessageDeliveryFailed(context: Context, recipient: Recipient, thread: ConversationId, visibleThread: ConversationId?) {
if (thread == visibleThread) {
notifyInThread(context, recipient, 0)
return
}
val intent: Intent = if (recipient.isDistributionList) {
val intent: Intent = if (recipient.isDistributionList || thread.groupStoryId != null) {
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, thread.threadId)
.build()
.makeUniqueToPreventMerging()
}
}.makeUniqueToPreventMerging()
val builder: NotificationBuilder = NotificationBuilder.create(context)
@@ -326,22 +325,18 @@ object NotificationFactory {
NotificationManagerCompat.from(context).safelyNotify(context, recipient, NotificationIds.getNotificationIdForMessageDeliveryFailed(thread), builder.build())
}
fun notifyProofRequired(context: Context, recipient: Recipient, thread: NotificationThread, visibleThread: NotificationThread?) {
fun notifyProofRequired(context: Context, recipient: Recipient, thread: ConversationId, visibleThread: ConversationId?) {
if (thread == visibleThread) {
notifyInThread(context, recipient, 0)
return
}
val intent: Intent = if (recipient.isDistributionList) {
val intent: Intent = if (recipient.isDistributionList || thread.groupStoryId != null) {
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, thread.threadId)
.build()
.makeUniqueToPreventMerging()
}
}.makeUniqueToPreventMerging()
val builder: NotificationBuilder = NotificationBuilder.create(context)
@@ -366,7 +361,7 @@ object NotificationFactory {
val conversation = NotificationConversation(
recipient = recipient,
thread = NotificationThread.forConversation(threadId),
thread = ConversationId.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 thread = NotificationThread.fromMessageRecord(record)
val thread = ConversationId.fromMessageRecord(record)
val isMms: Boolean = record.isMms
val slideDeck: SlideDeck? = if (record.isViewOnce) null else (record as? MmsMessageRecord)?.slideDeck
val isJoined: Boolean = record.isJoined
@@ -201,7 +201,11 @@ class MessageNotification(threadRecipient: Recipient, record: MessageRecord) : N
}
override fun getStartingPosition(context: Context): Int {
return -1
return if (thread.groupStoryId != null) {
SignalDatabase.mmsSms.getMessagePositionInConversation(thread.threadId, thread.groupStoryId, record.dateReceived)
} else {
-1
}
}
override fun getLargeIconUri(): Uri? {
@@ -304,7 +308,7 @@ class ReactionNotification(threadRecipient: Recipient, record: MessageRecord, va
}
override fun getStartingPosition(context: Context): Int {
return SignalDatabase.mmsSms.getMessagePositionInConversation(thread.threadId, record.dateReceived)
return SignalDatabase.mmsSms.getMessagePositionInConversation(thread.threadId, thread.groupStoryId ?: 0L, 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<NotificationThread, MessageNotifierV2.StickyThread>, notificationProfile: NotificationProfile?): NotificationStateV2 {
fun constructNotificationState(stickyThreads: Map<ConversationId, MessageNotifierV2.StickyThread>, notificationProfile: NotificationProfile?): NotificationStateV2 {
val messages: MutableList<NotificationMessage> = mutableListOf()
SignalDatabase.mmsSms.getMessagesForNotificationState(stickyThreads.values).use { unreadMessages ->
@@ -35,16 +35,27 @@ object NotificationStateProvider {
val threadRecipient: Recipient? = SignalDatabase.threads.getRecipientForThreadId(record.threadId)
if (threadRecipient != null) {
val hasUnreadReactions = CursorUtil.requireInt(unreadMessages, MmsSmsColumns.REACTIONS_UNREAD) == 1
val conversationId = ConversationId.fromMessageRecord(record)
val parentRecord = conversationId.groupStoryId?.let {
SignalDatabase.mms.getMessageRecord(it)
}
val hasSelfRepliedToGroupStory = conversationId.groupStoryId?.let {
SignalDatabase.mms.hasSelfReplyInGroupStory(it)
}
messages += NotificationMessage(
messageRecord = record,
reactions = if (hasUnreadReactions) SignalDatabase.reactions.getReactions(MessageId(record.id, record.isMms)) else emptyList(),
threadRecipient = threadRecipient,
thread = NotificationThread.fromMessageRecord(record),
stickyThread = stickyThreads.containsKey(NotificationThread.fromMessageRecord(record)),
thread = conversationId,
stickyThread = stickyThreads.containsKey(conversationId),
isUnreadMessage = CursorUtil.requireInt(unreadMessages, MmsSmsColumns.READ) == 0,
hasUnreadReactions = hasUnreadReactions,
lastReactionRead = CursorUtil.requireLong(unreadMessages, MmsSmsColumns.REACTIONS_LAST_SEEN)
lastReactionRead = CursorUtil.requireLong(unreadMessages, MmsSmsColumns.REACTIONS_LAST_SEEN),
isParentStorySentBySelf = parentRecord?.isOutgoing ?: false,
hasSelfRepliedToStory = hasSelfRepliedToGroupStory ?: false
)
}
try {
@@ -104,16 +115,20 @@ object NotificationStateProvider {
val messageRecord: MessageRecord,
val reactions: List<ReactionRecord>,
val threadRecipient: Recipient,
val thread: NotificationThread,
val thread: ConversationId,
val stickyThread: Boolean,
val isUnreadMessage: Boolean,
val hasUnreadReactions: Boolean,
val lastReactionRead: Long
val lastReactionRead: Long,
val isParentStorySentBySelf: Boolean,
val hasSelfRepliedToStory: Boolean
) {
private val isUnreadIncoming: Boolean = isUnreadMessage && !messageRecord.isOutgoing
private val isGroupStoryReply: Boolean = thread.groupStoryId != null
private val isUnreadIncoming: Boolean = isUnreadMessage && !messageRecord.isOutgoing && !isGroupStoryReply
private val isNotifiableGroupStoryMessage: Boolean = isUnreadMessage && !messageRecord.isOutgoing && isGroupStoryReply && (isParentStorySentBySelf || hasSelfRepliedToStory)
fun includeMessage(notificationProfile: NotificationProfile?): MessageInclusion {
return if (isUnreadIncoming || stickyThread) {
return if (isUnreadIncoming || stickyThread || isNotifiableGroupStoryMessage) {
if (threadRecipient.isMuted && (threadRecipient.isDoNotNotifyMentions || !messageRecord.hasSelfMention())) {
MessageInclusion.MUTE_FILTERED
} else if (notificationProfile != null && !notificationProfile.isRecipientAllowed(threadRecipient.id) && !(notificationProfile.allowAllMentions && messageRecord.hasSelfMention())) {

View File

@@ -39,18 +39,18 @@ data class NotificationStateV2(val conversations: List<NotificationConversation>
val mostRecentSender: Recipient?
get() = mostRecentNotification?.individualRecipient
fun getNonVisibleConversation(visibleThread: NotificationThread?): List<NotificationConversation> {
fun getNonVisibleConversation(visibleThread: ConversationId?): List<NotificationConversation> {
return conversations.filterNot { it.thread == visibleThread }
}
fun getConversation(notificationThread: NotificationThread): NotificationConversation? {
return conversations.firstOrNull { it.thread == notificationThread }
fun getConversation(conversationId: ConversationId): NotificationConversation? {
return conversations.firstOrNull { it.thread == conversationId }
}
fun getDeleteIntent(context: Context): PendingIntent? {
val ids = LongArray(messageCount)
val mms = BooleanArray(ids.size)
val threads: MutableList<NotificationThread> = mutableListOf()
val threads: MutableList<ConversationId> = mutableListOf()
conversations.forEach { conversation ->
threads += conversation.thread
@@ -79,7 +79,7 @@ data class NotificationStateV2(val conversations: List<NotificationConversation>
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
fun getThreadsWithMostRecentNotificationFromSelf(): Set<NotificationThread> {
fun getThreadsWithMostRecentNotificationFromSelf(): Set<ConversationId> {
return conversations.filter { it.mostRecentNotification.individualRecipient.isSelf }
.map { it.thread }
.toSet()