diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsRepository.kt index 08c25daa8b..e582a1d536 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/internal/InternalSettingsRepository.kt @@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.emoji.EmojiFiles import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob import org.thoughtcrime.securesms.jobs.CreateReleaseChannelJob import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.notifications.v2.NotificationThread import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.releasechannel.ReleaseChannel @@ -53,7 +54,7 @@ class InternalSettingsRepository(context: Context) { SignalDatabase.attachments.getAttachmentsForMessage(insertResult.messageId) .forEach { ApplicationDependencies.getJobManager().add(AttachmentDownloadJob(insertResult.messageId, it.attachmentId, false)) } - ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.threadId) + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.threadId)) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt index f2b2295afa..2b27db7f21 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt @@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.notifications.NotificationChannels +import org.thoughtcrime.securesms.notifications.v2.NotificationThread import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter import org.thoughtcrime.securesms.profiles.ProfileName @@ -214,7 +215,7 @@ object ContactDiscovery { .forEach { result -> val hour = Calendar.getInstance()[Calendar.HOUR_OF_DAY] if (hour in 9..22) { - ApplicationDependencies.getMessageNotifier().updateNotification(context, result.threadId, true) + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(result.threadId), true) } else { Log.i(TAG, "Not notifying of a new user due to the time of day. (Hour: $hour)") } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java index 28e7e7707d..3d901f79a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationParentFragment.java @@ -240,6 +240,7 @@ import org.thoughtcrime.securesms.mms.SlideFactory.MediaType; import org.thoughtcrime.securesms.mms.StickerSlide; import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.notifications.NotificationChannels; +import org.thoughtcrime.securesms.notifications.v2.NotificationThread; import org.thoughtcrime.securesms.payments.CanNotSendPaymentDialog; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.profiles.spoofing.ReviewBannerView; @@ -884,7 +885,7 @@ public class ConversationParentFragment extends Fragment private void setVisibleThread(long threadId) { if (!isInBubble()) { // TODO [alex] LargeScreenSupport -- Inform MainActivityViewModel that the conversation was opened. - ApplicationDependencies.getMessageNotifier().setVisibleThread(threadId); + ApplicationDependencies.getMessageNotifier().setVisibleThread(NotificationThread.forConversation(threadId)); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java index 2c3bcf636a..ab1ebe80bd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessageDatabase.java @@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchSet; import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.database.model.MessageId; import org.thoughtcrime.securesms.database.model.MessageRecord; +import org.thoughtcrime.securesms.database.model.ParentStoryId; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.StoryResult; import org.thoughtcrime.securesms.database.model.StoryViewState; @@ -200,6 +201,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns public abstract @Nullable Long getOldestStorySendTimestamp(); public abstract int deleteStoriesOlderThan(long timestamp); public abstract @NonNull MessageDatabase.Reader getUnreadStories(@NonNull RecipientId recipientId, int limit); + public abstract @Nullable ParentStoryId.GroupReply getParentStoryIdForGroupReply(long messageId); public abstract @NonNull StoryViewState getStoryViewState(@NonNull RecipientId recipientId); public abstract void updateViewedStories(@NonNull Set syncMessageIds); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index 3c72a5ef99..d6c6be0021 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -614,6 +614,25 @@ public class MmsDatabase extends MessageDatabase { return new Reader(rawQuery(query, args, false, limit)); } + @Override + public @Nullable ParentStoryId.GroupReply getParentStoryIdForGroupReply(long messageId) { + String[] projection = SqlUtil.buildArgs(PARENT_STORY_ID); + String[] args = SqlUtil.buildArgs(messageId); + + try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, projection, ID_WHERE, args, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + ParentStoryId parentStoryId = ParentStoryId.deserialize(CursorUtil.requireLong(cursor, PARENT_STORY_ID)); + if (parentStoryId != null && parentStoryId.isGroupReply()) { + return (ParentStoryId.GroupReply) parentStoryId; + } else { + return null; + } + } + } + + return null; + } + @Override public @NonNull StoryViewState getStoryViewState(@NonNull RecipientId recipientId) { if (!Stories.isFeatureEnabled()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 3f433a5667..6100af9d0f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -259,20 +259,29 @@ public class MmsSmsDatabase extends Database { } stickyQuery.append("(") .append(MmsSmsColumns.THREAD_ID + " = ") - .append(stickyThread.getThreadId()) + .append(stickyThread.getNotificationThread().getThreadId()) .append(" AND ") .append(MmsSmsColumns.NORMALIZED_DATE_RECEIVED) .append(" >= ") .append(stickyThread.getEarliestTimestamp()) + .append(getStickyWherePartForParentStoryId(stickyThread.getNotificationThread().getGroupStoryId())) .append(")"); } String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC"; - String selection = MmsSmsColumns.NOTIFIED + " = 0 AND " + MmsDatabase.STORY_TYPE + " = 0 AND " + MmsDatabase.PARENT_STORY_ID + " <= 0 AND (" + MmsSmsColumns.READ + " = 0 OR " + MmsSmsColumns.REACTIONS_UNREAD + " = 1" + (stickyQuery.length() > 0 ? " OR (" + stickyQuery.toString() + ")" : "") + ")"; + String selection = MmsSmsColumns.NOTIFIED + " = 0 AND " + MmsDatabase.STORY_TYPE + " = 0 AND (" + MmsSmsColumns.READ + " = 0 OR " + MmsSmsColumns.REACTIONS_UNREAD + " = 1" + (stickyQuery.length() > 0 ? " OR (" + stickyQuery + ")" : "") + ")"; return queryTables(PROJECTION, selection, order, null, true); } + private @NonNull String getStickyWherePartForParentStoryId(@Nullable Long parentStoryId) { + if (parentStoryId == null) { + return " AND " + MmsDatabase.PARENT_STORY_ID + " <= 0"; + } + + return " AND " + MmsDatabase.PARENT_STORY_ID + " = " + parentStoryId; + } + public int getUnreadCount(long threadId) { String selection = MmsSmsColumns.READ + " = 0 AND " + MmsDatabase.STORY_TYPE + " = 0 AND " + MmsSmsColumns.THREAD_ID + " = " + threadId + " AND " + MmsDatabase.PARENT_STORY_ID + " <= 0"; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 7ad51fc23a..7fe720e32b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -42,6 +42,7 @@ import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.database.model.GroupCallUpdateDetailsUtil; import org.thoughtcrime.securesms.database.model.MessageId; import org.thoughtcrime.securesms.database.model.MessageRecord; +import org.thoughtcrime.securesms.database.model.ParentStoryId; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.database.model.StoryResult; import org.thoughtcrime.securesms.database.model.StoryViewState; @@ -1471,6 +1472,11 @@ public class SmsDatabase extends MessageDatabase { throw new UnsupportedOperationException(); } + @Override + public @Nullable ParentStoryId.GroupReply getParentStoryIdForGroupReply(long messageId) { + throw new UnsupportedOperationException(); + } + @Override public MessageRecord getMessageRecord(long messageId) throws NoSuchMessageException { return getSmsMessage(messageId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt index 31933ca8af..2137ad90a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt @@ -197,8 +197,9 @@ object SignalDatabaseMigrations { private const val GROUP_SERVICE_ID = 141 private const val QUOTE_TYPE = 142 private const val STORY_SYNCS = 143 + private const val GROUP_STORY_NOTIFICATIONS = 144 - const val DATABASE_VERSION = 143 + const val DATABASE_VERSION = 144 @JvmStatic fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { @@ -2567,6 +2568,10 @@ object SignalDatabaseMigrations { db.execSQL("ALTER TABLE story_sends_tmp RENAME TO story_sends") db.execSQL("CREATE INDEX story_sends_recipient_id_sent_timestamp_allows_replies_index ON story_sends (recipient_id, sent_timestamp, allows_replies)") } + + if (oldVersion < GROUP_STORY_NOTIFICATIONS) { + db.execSQL("UPDATE mms SET read = 1 WHERE parent_story_id > 0") + } } @JvmStatic diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupV1MessageProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupV1MessageProcessor.java index 212d7773f7..7e6b95b30e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupV1MessageProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupV1MessageProcessor.java @@ -19,6 +19,7 @@ import org.thoughtcrime.securesms.jobs.AvatarGroupsV1DownloadJob; import org.thoughtcrime.securesms.jobs.PushGroupUpdateJob; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.OutgoingGroupUpdateMessage; +import org.thoughtcrime.securesms.notifications.v2.NotificationThread; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.sms.IncomingGroupUpdateMessage; @@ -254,7 +255,7 @@ public final class GroupV1MessageProcessor { Optional insertResult = smsDatabase.insertMessageInbox(groupMessage); if (insertResult.isPresent()) { - ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.get().getThreadId()); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId())); return insertResult.get().getThreadId(); } else { return null; diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java index 829ebfddcb..f9c04a76b7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java @@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.JobLogger; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.mms.MmsException; +import org.thoughtcrime.securesms.notifications.v2.NotificationThread; import org.thoughtcrime.securesms.releasechannel.ReleaseChannel; import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.util.AttachmentUtil; @@ -119,7 +120,7 @@ public final class AttachmentDownloadJob extends BaseJob { doWork(); if (!SignalDatabase.mms().isStory(messageId)) { - ApplicationDependencies.getMessageNotifier().updateNotification(context, 0); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(0)); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java index 64f98e572e..dee161789a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AutomaticSessionResetJob.java @@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.DecryptionsDrainedConstraint; +import org.thoughtcrime.securesms.notifications.v2.NotificationThread; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; @@ -121,7 +122,7 @@ public class AutomaticSessionResetJob extends BaseJob { private void insertLocalMessage() { MessageDatabase.InsertResult result = SignalDatabase.sms().insertChatSessionRefreshedMessage(recipientId, deviceId, sentTimestamp); - ApplicationDependencies.getMessageNotifier().updateNotification(context, result.getThreadId()); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(result.getThreadId())); } private void sendNullMessage() throws IOException { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java index a87240b0ab..8c62ee7f0b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java @@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.mms.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.MmsRadioException; import org.thoughtcrime.securesms.mms.PartParser; +import org.thoughtcrime.securesms.notifications.v2.NotificationThread; import org.thoughtcrime.securesms.providers.BlobProvider; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; @@ -174,7 +175,7 @@ public class MmsDownloadJob extends BaseJob { if (automatic) { database.markIncomingNotificationReceived(threadId); - ApplicationDependencies.getMessageNotifier().updateNotification(context, threadId); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(threadId)); } } @@ -254,7 +255,7 @@ public class MmsDownloadJob extends BaseJob { if (insertResult.isPresent()) { database.deleteMessage(messageId); - ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.get().getThreadId()); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId())); } } @@ -266,7 +267,7 @@ public class MmsDownloadJob extends BaseJob { if (automatic) { db.markIncomingNotificationReceived(threadId); - ApplicationDependencies.getMessageNotifier().updateNotification(context, threadId); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(threadId)); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsSendJob.java index 62fcfa0b94..3736cda932 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsSendJob.java @@ -22,6 +22,7 @@ import com.google.android.mms.pdu_alt.SendReq; import com.google.android.mms.smil.SmilHelper; import com.klinker.android.send_message.Utils; +import org.signal.core.util.Hex; import org.signal.core.util.StreamUtil; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.attachments.Attachment; @@ -44,11 +45,11 @@ import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.MmsSendResult; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.PartAuthority; +import org.thoughtcrime.securesms.notifications.v2.NotificationThread; import org.thoughtcrime.securesms.phonenumbers.NumberUtil; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.transport.InsecureFallbackApprovalException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException; -import org.signal.core.util.Hex; import org.thoughtcrime.securesms.util.Util; import java.io.ByteArrayOutputStream; @@ -347,7 +348,7 @@ public final class MmsSendJob extends SendJob { Recipient recipient = SignalDatabase.threads().getRecipientForThreadId(threadId); if (recipient != null) { - ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, threadId); + ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, NotificationThread.forConversation(threadId)); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java index 976f70537c..3a96efeb7b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushSendJob.java @@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.contactshare.ContactModelMapper; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.model.Mention; +import org.thoughtcrime.securesms.database.model.ParentStoryId; import org.thoughtcrime.securesms.database.model.StickerRecord; import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; @@ -43,6 +44,7 @@ import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.QuoteModel; import org.thoughtcrime.securesms.net.NotPushRegisteredException; +import org.thoughtcrime.securesms.notifications.v2.NotificationThread; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; @@ -289,11 +291,12 @@ public abstract class PushSendJob extends SendJob { } protected static void notifyMediaMessageDeliveryFailed(Context context, long messageId) { - long threadId = SignalDatabase.mms().getThreadIdForMessage(messageId); - Recipient recipient = SignalDatabase.threads().getRecipientForThreadId(threadId); + long threadId = SignalDatabase.mms().getThreadIdForMessage(messageId); + Recipient recipient = SignalDatabase.threads().getRecipientForThreadId(threadId); + ParentStoryId.GroupReply groupReplyStoryId = SignalDatabase.mms().getParentStoryIdForGroupReply(messageId); if (threadId != -1 && recipient != null) { - ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, threadId); + ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, NotificationThread.fromThreadAndReply(threadId, groupReplyStoryId)); } } @@ -511,7 +514,8 @@ public abstract class PushSendJob extends SendJob { SignalStore.rateLimit().markNeedsRecaptcha(proofRequired.getToken()); if (recipient != null) { - ApplicationDependencies.getMessageNotifier().notifyProofRequired(context, recipient, threadId); + ParentStoryId.GroupReply groupReply = SignalDatabase.mms().getParentStoryIdForGroupReply(messageId); + ApplicationDependencies.getMessageNotifier().notifyProofRequired(context, recipient, NotificationThread.fromThreadAndReply(threadId, groupReply)); } else { Log.w(TAG, "[Proof Required] No recipient! Couldn't notify."); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java index 3a159e5ee7..562c2bf0f9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushTextSendJob.java @@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.keyvalue.SignalStore; +import org.thoughtcrime.securesms.notifications.v2.NotificationThread; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; @@ -126,7 +127,7 @@ public class PushTextSendJob extends PushSendJob { } catch (InsecureFallbackApprovalException e) { warn(TAG, String.valueOf(record.getDateSent()), "Failure", e); database.markAsPendingInsecureSmsFallback(record.getId()); - ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId()); + ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getRecipient(), NotificationThread.forConversation(record.getThreadId())); ApplicationDependencies.getJobManager().add(new DirectoryRefreshJob(false)); } catch (UntrustedIdentityException e) { warn(TAG, String.valueOf(record.getDateSent()), "Failure", e); @@ -156,7 +157,7 @@ public class PushTextSendJob extends PushSendJob { Recipient recipient = SignalDatabase.threads().getRecipientForThreadId(threadId); if (threadId != -1 && recipient != null) { - ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, threadId); + ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, NotificationThread.forConversation(threadId)); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveReleaseChannelJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveReleaseChannelJob.kt index 656246b951..179b88e500 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveReleaseChannelJob.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveReleaseChannelJob.kt @@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.jobmanager.Data import org.thoughtcrime.securesms.jobmanager.Job import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint import org.thoughtcrime.securesms.keyvalue.SignalStore +import org.thoughtcrime.securesms.notifications.v2.NotificationThread import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.releasechannel.ReleaseChannel import org.thoughtcrime.securesms.s3.S3 @@ -187,7 +188,7 @@ class RetrieveReleaseChannelJob private constructor(private val force: Boolean, SignalDatabase.attachments.getAttachmentsForMessage(insertResult.messageId) .forEach { ApplicationDependencies.getJobManager().add(AttachmentDownloadJob(insertResult.messageId, it.attachmentId, false)) } - ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.threadId) + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.threadId)) TrimThreadJob.enqueueAsync(insertResult.threadId) highestVersion = max(highestVersion, note.releaseNote.androidMinVersion!!.toInt()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java index 2ae8a21ad4..8e636fe419 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsReceiveJob.java @@ -26,6 +26,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.notifications.NotificationIds; +import org.thoughtcrime.securesms.notifications.v2.NotificationThread; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.service.VerificationCodeParser; import org.thoughtcrime.securesms.sms.IncomingTextMessage; @@ -123,7 +124,7 @@ public class SmsReceiveJob extends BaseJob { Optional insertResult = storeMessage(message.get()); if (insertResult.isPresent()) { - ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.get().getThreadId()); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId())); } } else if (message.isPresent()) { Log.w(TAG, "Received an SMS from a blocked user. Ignoring."); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsSendJob.java index d30c3da27a..20826f3486 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsSendJob.java @@ -20,11 +20,11 @@ import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkOrCellServiceConstraint; import org.thoughtcrime.securesms.keyvalue.SignalStore; +import org.thoughtcrime.securesms.notifications.v2.NotificationThread; import org.thoughtcrime.securesms.phonenumbers.NumberUtil; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.service.SmsDeliveryListener; import org.thoughtcrime.securesms.transport.UndeliverableMessageException; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import java.util.ArrayList; @@ -98,7 +98,7 @@ public class SmsSendJob extends SendJob { } catch (UndeliverableMessageException ude) { warn(TAG, ude); SignalDatabase.sms().markAsSentFailed(record.getId()); - ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId()); + ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getRecipient(), NotificationThread.fromMessageRecord(record)); } } @@ -116,7 +116,7 @@ public class SmsSendJob extends SendJob { SignalDatabase.sms().markAsSentFailed(messageId); if (threadId != -1 && recipient != null) { - ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, threadId); + ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, recipient, NotificationThread.forConversation(threadId)); } else { Log.w(TAG, "Could not find message! threadId: " + threadId + ", recipient: " + (recipient != null ? recipient.getId().toString() : "null")); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsSentJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsSentJob.java index 47f7d20585..23dcc2a2d3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsSentJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/SmsSentJob.java @@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.notifications.v2.NotificationThread; import org.thoughtcrime.securesms.service.SmsDeliveryListener; public class SmsSentJob extends BaseJob { @@ -108,7 +109,7 @@ public class SmsSentJob extends BaseJob { if (isMultipart) { Log.w(TAG, "Service connectivity problem, but not retrying due to multipart"); database.markAsSentFailed(messageId); - ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId()); + ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getRecipient(), NotificationThread.forConversation(record.getThreadId())); } else { Log.w(TAG, "Service connectivity problem, requeuing..."); ApplicationDependencies.getJobManager().add(new SmsSendJob(messageId, record.getIndividualRecipient(), runAttempt + 1)); @@ -116,7 +117,7 @@ public class SmsSentJob extends BaseJob { break; default: database.markAsSentFailed(messageId); - ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getRecipient(), record.getThreadId()); + ApplicationDependencies.getMessageNotifier().notifyMessageDeliveryFailed(context, record.getRecipient(), NotificationThread.forConversation(record.getThreadId())); } } catch (NoSuchMessageException e) { Log.w(TAG, e); diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java index 1031338a87..d85f9c7805 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/MessageContentProcessor.java @@ -113,6 +113,7 @@ import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.StickerSlide; import org.thoughtcrime.securesms.notifications.MarkReadReceiver; import org.thoughtcrime.securesms.notifications.MessageNotifier; +import org.thoughtcrime.securesms.notifications.v2.NotificationThread; import org.thoughtcrime.securesms.payments.MobileCoinPublicAddress; import org.thoughtcrime.securesms.ratelimit.RateLimitUtil; import org.thoughtcrime.securesms.recipients.Recipient; @@ -410,8 +411,8 @@ public final class MessageContentProcessor { Long threadId = SignalDatabase.threads().getThreadIdFor(destination.getId()); if (threadId != null) { - ThreadDatabase.ConversationMetadata metadata = SignalDatabase.threads().getConversationMetadata(threadId); - long visibleThread = ApplicationDependencies.getMessageNotifier().getVisibleThread(); + ThreadDatabase.ConversationMetadata metadata = SignalDatabase.threads().getConversationMetadata(threadId); + long visibleThread = ApplicationDependencies.getMessageNotifier().getVisibleThread().map(NotificationThread::getThreadId).orElse(-1L); if (threadId != visibleThread && metadata.getLastSeen() > 0 && metadata.getLastSeen() < pending.getReceivedTimestamp()) { receivedTime = pending.getReceivedTimestamp(); @@ -754,7 +755,7 @@ public final class MessageContentProcessor { ApplicationDependencies.getProtocolStore().aci().deleteAllSessions(content.getSender().getIdentifier()); SecurityEvent.broadcastSecurityUpdateEvent(context); - ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.get().getThreadId()); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId())); return new MessageId(insertResult.get().getMessageId(), true); } else { @@ -982,7 +983,7 @@ public final class MessageContentProcessor { } else { ReactionRecord reactionRecord = new ReactionRecord(reaction.getEmoji(), senderRecipient.getId(), message.getTimestamp(), System.currentTimeMillis()); SignalDatabase.reactions().addReaction(targetMessageId, reactionRecord); - ApplicationDependencies.getMessageNotifier().updateNotification(context, targetMessage.getThreadId(), false); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.fromMessageRecord(targetMessage), false); } return new MessageId(targetMessage.getId(), targetMessage.isMms()); @@ -998,7 +999,7 @@ public final class MessageContentProcessor { if (targetMessage != null && RemoteDeleteUtil.isValidReceive(targetMessage, senderRecipient, content.getServerReceivedTimestamp())) { MessageDatabase db = targetMessage.isMms() ? SignalDatabase.mms() : SignalDatabase.sms(); db.markAsRemoteDelete(targetMessage.getId()); - ApplicationDependencies.getMessageNotifier().updateNotification(context, targetMessage.getThreadId(), false); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.fromMessageRecord(targetMessage), false); return new MessageId(targetMessage.getId(), targetMessage.isMms()); } else if (targetMessage == null) { warn(String.valueOf(content.getTimestamp()), "[handleRemoteDelete] Could not find matching message! timestamp: " + delete.getTargetSentTimestamp() + " author: " + senderRecipient.getId()); @@ -1776,7 +1777,7 @@ public final class MessageContentProcessor { } if (insertResult.isPresent()) { - ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.get().getThreadId()); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId())); TrimThreadJob.enqueueAsync(insertResult.get().getThreadId()); return new MessageId(insertResult.get().getMessageId(), true); @@ -1860,7 +1861,7 @@ public final class MessageContentProcessor { ApplicationDependencies.getJobManager().add(new AttachmentDownloadJob(insertResult.get().getMessageId(), attachment.getAttachmentId(), false)); } - ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.get().getThreadId()); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId())); TrimThreadJob.enqueueAsync(insertResult.get().getThreadId()); if (message.isViewOnce()) { @@ -2324,7 +2325,7 @@ public final class MessageContentProcessor { } if (insertResult.isPresent()) { - ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.get().getThreadId()); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId())); return new MessageId(insertResult.get().getMessageId(), false); } else { return null; @@ -2411,7 +2412,7 @@ public final class MessageContentProcessor { if (insertResult.isPresent()) { smsDatabase.markAsInvalidVersionKeyExchange(insertResult.get().getMessageId()); - ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.get().getThreadId()); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId())); } } else { smsDatabase.markAsInvalidVersionKeyExchange(smsMessageId.get()); @@ -2430,7 +2431,7 @@ public final class MessageContentProcessor { if (insertResult.isPresent()) { smsDatabase.markAsDecryptFailed(insertResult.get().getMessageId()); - ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.get().getThreadId()); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId())); } } else { smsDatabase.markAsDecryptFailed(smsMessageId.get()); @@ -2452,7 +2453,7 @@ public final class MessageContentProcessor { if (insertResult.isPresent()) { smsDatabase.markAsUnsupportedProtocolVersion(insertResult.get().getMessageId()); - ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.get().getThreadId()); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId())); } } else { smsDatabase.markAsNoSession(smsMessageId.get()); @@ -2474,7 +2475,7 @@ public final class MessageContentProcessor { if (insertResult.isPresent()) { smsDatabase.markAsInvalidMessage(insertResult.get().getMessageId()); - ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.get().getThreadId()); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId())); } } else { smsDatabase.markAsNoSession(smsMessageId.get()); @@ -2493,7 +2494,7 @@ public final class MessageContentProcessor { if (insertResult.isPresent()) { smsDatabase.markAsLegacyVersion(insertResult.get().getMessageId()); - ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.get().getThreadId()); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId())); } } else { smsDatabase.markAsLegacyVersion(smsMessageId.get()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DeleteNotificationReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DeleteNotificationReceiver.java index 005a9f0602..c0f9c6eef6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DeleteNotificationReceiver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DeleteNotificationReceiver.java @@ -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 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); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java index 81189058c6..c8789f18b9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java @@ -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 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 messageIdsCollection = new LinkedList<>(); - for (long threadId : threadIds) { - Log.i(TAG, "Marking as read: " + threadId); - List messageIds = SignalDatabase.threads().setRead(threadId, true); + for (NotificationThread thread : threads) { + Log.i(TAG, "Marking as read: " + thread); + List messageIds = SignalDatabase.threads().setRead(thread.getThreadId(), true); messageIdsCollection.addAll(messageIds); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MessageNotifier.java index 40ad22c287..5e1b4a10a6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MessageNotifier.java @@ -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 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); }); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCancellationHelper.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCancellationHelper.java index 8d57b1b9bb..db7db3452e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCancellationHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationCancellationHelper.java @@ -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 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; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationIds.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationIds.java index 3057efd830..07bd9a454d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationIds.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationIds.java @@ -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(); + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java index 39111bddb8..e77558f583 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java @@ -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 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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java index 37c58572c8..78ec008d19 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java @@ -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 messageIds = SignalDatabase.threads().setRead(threadId, true); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt index 9f75392fd2..7d45a091f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/MessageNotifierV2.kt @@ -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 = ConcurrentHashMap() - private val stickyThreads: MutableMap = mutableMapOf() + private val threadReminders: MutableMap = ConcurrentHashMap() + private val stickyThreads: MutableMap = 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 { + 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? = ServiceUtil.getNotificationManager(context).getDisplayedNotificationIds().getOrNull() if (displayedNotifications != null) { - val cleanedUpThreadIds: MutableSet = mutableSetOf() + val cleanedUpThreads: MutableSet = 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 = state.getThreadsWithMostRecentNotificationFromSelf() + val retainStickyThreadIds: Set = state.getThreadsWithMostRecentNotificationFromSelf() stickyThreads.keys.retainAll { retainStickyThreadIds.contains(it) } if (state.isEmpty) { @@ -188,13 +190,13 @@ class MessageNotifierV2(context: Application) : MessageNotifier { return } - val alertOverrides: Set = threadReminders.filter { (_, reminder) -> reminder.lastNotified < System.currentTimeMillis() - REMINDER_TIMEOUT }.keys + val alertOverrides: Set = threadReminders.filter { (_, reminder) -> reminder.lastNotified < System.currentTimeMillis() - REMINDER_TIMEOUT }.keys - val threadsThatAlerted: Set = NotificationFactory.notify( + val threadsThatAlerted: Set = 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, threadsThatAlerted: Set) { + private fun updateReminderTimestamps(context: Context, alertOverrides: Set, threadsThatAlerted: Set) { if (SignalStore.settings().messageNotificationsRepeatAlerts == 0) { return } - val iterator: MutableIterator> = threadReminders.iterator() + val iterator: MutableIterator> = threadReminders.iterator() while (iterator.hasNext()) { - val entry: MutableEntry = iterator.next() - val (id: Long, reminder: Reminder) = entry + val entry: MutableEntry = 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 = 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...") diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt index e5582ce8a2..1499e5c45c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt @@ -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 ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt index c37571b74b..16dd4398fd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt @@ -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 ) { 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()})" } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt index ab1c4a0d1b..7dc1e824f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt @@ -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, + alertOverrides: Set, previousState: NotificationStateV2 - ): Set { + ): Set { 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, + alertOverrides: Set, nonVisibleThreadCount: Int - ): Set { - val threadsThatNewlyAlerted: MutableSet = mutableSetOf() + ): Set { + val threadsThatNewlyAlerted: MutableSet = 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 = state.getNonVisibleConversation(visibleThreadId) - threadsThatNewlyAlerted += nonVisibleConversations.filter { it.hasNewNotifications() }.map { it.threadId } + val nonVisibleConversations: List = 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, + alertOverrides: Set, nonVisibleThreadCount: Int, previousState: NotificationStateV2 - ): Set { - val threadsThatNewlyAlerted: MutableSet = mutableSetOf() + ): Set { + val threadsThatNewlyAlerted: MutableSet = 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, diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt index a58dd66be2..32e93cae58 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt @@ -40,7 +40,7 @@ private const val MAX_DISPLAY_LENGTH = 500 sealed class NotificationItemV2(val threadRecipient: Recipient, protected val record: MessageRecord) : Comparable { 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 diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateProvider.kt index 354439affb..e60100bcf5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateProvider.kt @@ -21,7 +21,7 @@ object NotificationStateProvider { private val TAG = Log.tag(NotificationStateProvider::class.java) @WorkerThread - fun constructNotificationState(stickyThreads: Map, notificationProfile: NotificationProfile?): NotificationStateV2 { + fun constructNotificationState(stickyThreads: Map, notificationProfile: NotificationProfile?): NotificationStateV2 { val messages: MutableList = 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 = mutableListOf() val profileFilteredMessages: MutableList = mutableListOf() - messages.groupBy { it.threadId } - .forEach { (threadId, threadMessages) -> + messages.groupBy { it.thread } + .forEach { (thread, threadMessages) -> var notificationItems: MutableList = 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, val threadRecipient: Recipient, - val threadId: Long, + val thread: NotificationThread, val stickyThread: Boolean, val isUnreadMessage: Boolean, val hasUnreadReactions: Boolean, diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateV2.kt index 10999dc8aa..1dd9f89a96 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationStateV2.kt @@ -39,21 +39,21 @@ data class NotificationStateV2(val conversations: List val mostRecentSender: Recipient? get() = mostRecentNotification?.individualRecipient - fun getNonVisibleConversation(visibleThreadId: Long): List { - return conversations.filterNot { it.threadId == visibleThreadId } + fun getNonVisibleConversation(visibleThread: NotificationThread?): List { + 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 = mutableListOf() + val threads: MutableList = 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 .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 { + fun getThreadsWithMostRecentNotificationFromSelf(): Set { return conversations.filter { it.mostRecentNotification.individualRecipient.isSelf } - .map { it.threadId } + .map { it.thread } .toSet() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationThread.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationThread.kt new file mode 100644 index 0000000000..edc92dfddc --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationThread.kt @@ -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()) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java index dec180037c..5cbd776f22 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/SignalCallManager.java @@ -40,6 +40,7 @@ import org.thoughtcrime.securesms.jobs.GroupCallUpdateSendJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.messages.GroupSendUtil; +import org.thoughtcrime.securesms.notifications.v2.NotificationThread; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; @@ -352,7 +353,7 @@ private void processStateless(@NonNull Function1 messageAndThreadId = SignalDatabase.sms().insertMissedCall(remotePeer.getId(), timestamp, isVideoOffer); ApplicationDependencies.getMessageNotifier() - .updateNotification(context, messageAndThreadId.second(), signal); + .updateNotification(context, NotificationThread.forConversation(messageAndThreadId.second()), signal); } public void insertReceivedCall(@NonNull RemotePeer remotePeer, boolean signal, boolean isVideoOffer) { Pair messageAndThreadId = SignalDatabase.sms().insertReceivedCall(remotePeer.getId(), isVideoOffer); ApplicationDependencies.getMessageNotifier() - .updateNotification(context, messageAndThreadId.second(), signal); + .updateNotification(context, NotificationThread.forConversation(messageAndThreadId.second()), signal); } public void retrieveTurnServers(@NonNull RemotePeer remotePeer) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/BubbleUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/BubbleUtil.java index 30f6d2d76d..012c6ca39b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/BubbleUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/BubbleUtil.java @@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.notifications.NotificationIds; import org.thoughtcrime.securesms.notifications.v2.NotificationFactory; +import org.thoughtcrime.securesms.notifications.v2.NotificationThread; import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; @@ -75,11 +76,12 @@ public final class BubbleUtil { */ public static void displayAsBubble(@NonNull Context context, @NonNull RecipientId recipientId, long threadId) { if (Build.VERSION.SDK_INT >= CONVERSATION_SUPPORT_VERSION) { + NotificationThread notificationThread = NotificationThread.forConversation(threadId); SignalExecutors.BOUNDED.execute(() -> { if (canBubble(context, recipientId, threadId)) { NotificationManager notificationManager = ServiceUtil.getNotificationManager(context); StatusBarNotification[] notifications = notificationManager.getActiveNotifications(); - int threadNotificationId = NotificationIds.getNotificationIdForThread(threadId); + int threadNotificationId = NotificationIds.getNotificationIdForThread(notificationThread); Notification activeThreadNotification = Stream.of(notifications) .filter(n -> n.getId() == threadNotificationId) .findFirst() @@ -87,7 +89,7 @@ public final class BubbleUtil { .orElse(null); if (activeThreadNotification != null && activeThreadNotification.deleteIntent != null) { - ApplicationDependencies.getMessageNotifier().updateNotification(context, threadId, BubbleState.SHOWN); + ApplicationDependencies.getMessageNotifier().updateNotification(context, notificationThread, BubbleState.SHOWN); } else { Recipient recipient = Recipient.resolved(recipientId); NotificationFactory.notifyToBubbleConversation(context, recipient, threadId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java index 54f36bd9f0..7c396365ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java @@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.database.MessageDatabase.InsertResult; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.database.model.IdentityRecord; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.notifications.v2.NotificationThread; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.sms.IncomingIdentityDefaultMessage; @@ -140,7 +141,7 @@ public final class IdentityUtil { Optional insertResult = smsDatabase.insertMessageInbox(individualUpdate); if (insertResult.isPresent()) { - ApplicationDependencies.getMessageNotifier().updateNotification(context, insertResult.get().getThreadId()); + ApplicationDependencies.getMessageNotifier().updateNotification(context, NotificationThread.forConversation(insertResult.get().getThreadId())); } } diff --git a/core-util/src/main/java/org/signal/core/util/PendingIntentFlags.kt b/core-util/src/main/java/org/signal/core/util/PendingIntentFlags.kt new file mode 100644 index 0000000000..a9d5c9cd94 --- /dev/null +++ b/core-util/src/main/java/org/signal/core/util/PendingIntentFlags.kt @@ -0,0 +1,30 @@ +package org.signal.core.util + +import android.app.PendingIntent +import android.os.Build + +/** + * Wrapper class for lower level API compatibility with the new Pending Intents flags. + * + * This is meant to be a replacement to using PendingIntent flags independently, and should + * end up being the only place in our codebase that accesses these values. + * + * The "default" value is FLAG_MUTABLE + */ +object PendingIntentFlags { + + fun updateCurrent(): Int { + return mutable() or PendingIntent.FLAG_UPDATE_CURRENT + } + + fun cancelCurrent(): Int { + return mutable() or PendingIntent.FLAG_CANCEL_CURRENT + } + + /** + * The backwards compatible "default" value for pending intent flags. + */ + fun mutable(): Int { + return if (Build.VERSION.SDK_INT >= 31) PendingIntent.FLAG_MUTABLE else 0 + } +}