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 2f3c401bc..606e9ec14 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 @@ -48,6 +48,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier { @Volatile private var lastScheduledReminder: Long = 0 @Volatile private var previousLockedStatus: Boolean = KeyCachingService.isLocked(context) @Volatile private var previousPrivacyPreference: NotificationPrivacyPreference = TextSecurePreferences.getNotificationPrivacy(context) + @Volatile private var previousState: NotificationStateV2 = NotificationStateV2.EMPTY private val threadReminders: MutableMap = ConcurrentHashMap() private val stickyThreads: MutableMap = mutableMapOf() @@ -167,9 +168,11 @@ class MessageNotifierV2(context: Application) : MessageNotifier { defaultBubbleState = defaultBubbleState, lastAudibleNotification = lastAudibleNotification, notificationConfigurationChanged = notificationConfigurationChanged, - alertOverrides = alertOverrides + alertOverrides = alertOverrides, + previousState = previousState ) + previousState = state lastAudibleNotification = System.currentTimeMillis() updateReminderTimestamps(context, alertOverrides, threadsThatAlerted) @@ -281,7 +284,7 @@ private fun StatusBarNotification.isMessageNotification(): Boolean { } private fun NotificationManager.getDisplayedNotificationIds(): Result> { - if (Build.VERSION.SDK_INT < 23) { + if (Build.VERSION.SDK_INT < 24) { return Result.failure(UnsupportedOperationException("SDK level too low")) } @@ -294,7 +297,7 @@ private fun NotificationManager.getDisplayedNotificationIds(): Result> } private fun NotificationManager.cancelOrphanedNotifications(context: Context, state: NotificationStateV2, stickyNotifications: Set) { - if (Build.VERSION.SDK_INT < 23) { + if (Build.VERSION.SDK_INT < 24) { return } 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 99ad3f2b3..07e5f7d91 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 @@ -68,7 +68,7 @@ sealed class NotificationBuilder(protected val context: Context) { abstract fun addMarkAsReadActionActual(state: NotificationStateV2) abstract fun setPriority(priority: Int) abstract fun setAlarms(recipient: Recipient?) - abstract fun setTicker(ticker: CharSequence) + abstract fun setTicker(ticker: CharSequence?) abstract fun addTurnOffJoinedNotificationsAction(pendingIntent: PendingIntent) abstract fun setAutoCancel(autoCancel: Boolean) abstract fun build(): Notification @@ -100,8 +100,8 @@ sealed class NotificationBuilder(protected val context: Context) { } } - fun setWhen(notificationItem: NotificationItemV2) { - if (notificationItem.timestamp != 0L) { + fun setWhen(notificationItem: NotificationItemV2?) { + if (notificationItem != null && notificationItem.timestamp != 0L) { setWhen(notificationItem.timestamp) } } @@ -136,12 +136,12 @@ sealed class NotificationBuilder(protected val context: Context) { } } - fun setSummaryContentText(recipient: Recipient) { - if (privacy.isDisplayContact) { + fun setSummaryContentText(recipient: Recipient?) { + if (privacy.isDisplayContact && recipient != null) { setContentText(context.getString(R.string.MessageNotifier_most_recent_from_s, recipient.getDisplayName(context))) } - recipient.notificationChannel?.let { channel -> setChannelId(channel) } + recipient?.notificationChannel?.let { channel -> setChannelId(channel) } } fun setLights() { @@ -244,8 +244,8 @@ sealed class NotificationBuilder(protected val context: Context) { val self: PersonCompat = PersonCompat.Builder() .setBot(false) - .setName(Recipient.self().getDisplayName(context)) - .setIcon(Recipient.self().getContactDrawable(context).toLargeBitmap(context).toIconCompat()) + .setName(if (includeShortcut) Recipient.self().getDisplayName(context) else context.getString(R.string.SingleRecipientNotificationBuilder_you)) + .setIcon(if (includeShortcut) Recipient.self().getContactDrawable(context).toLargeBitmap(context).toIconCompat() else null) .build() val messagingStyle: NotificationCompat.MessagingStyle = NotificationCompat.MessagingStyle(self) @@ -332,7 +332,7 @@ sealed class NotificationBuilder(protected val context: Context) { } override fun setGroup(group: String) { - if (Build.VERSION.SDK_INT < 23) { + if (Build.VERSION.SDK_INT < 24) { return } @@ -340,7 +340,7 @@ sealed class NotificationBuilder(protected val context: Context) { } override fun setGroupAlertBehavior(behavior: Int) { - if (Build.VERSION.SDK_INT < 23) { + if (Build.VERSION.SDK_INT < 24) { return } @@ -375,7 +375,7 @@ sealed class NotificationBuilder(protected val context: Context) { builder.setContentText(contentText) } - override fun setTicker(ticker: CharSequence) { + override fun setTicker(ticker: CharSequence?) { builder.setTicker(ticker) } @@ -489,8 +489,8 @@ sealed class NotificationBuilder(protected val context: Context) { override fun addMessagesActual(conversation: NotificationConversation, includeShortcut: Boolean) { val self: Person = Person.Builder() .setBot(false) - .setName(Recipient.self().getDisplayName(context)) - .setIcon(Recipient.self().getContactDrawable(context).toLargeBitmap(context).toIcon()) + .setName(if (includeShortcut) Recipient.self().getDisplayName(context) else context.getString(R.string.SingleRecipientNotificationBuilder_you)) + .setIcon(if (includeShortcut) Recipient.self().getContactDrawable(context).toLargeBitmap(context).toIcon() else null) .build() val messagingStyle: Notification.MessagingStyle = Notification.MessagingStyle(self) @@ -598,7 +598,7 @@ sealed class NotificationBuilder(protected val context: Context) { builder.setContentText(contentText) } - override fun setTicker(ticker: CharSequence) { + override fun setTicker(ticker: CharSequence?) { builder.setTicker(ticker) } 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 b2768af15..8552fbe62 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 @@ -88,14 +88,10 @@ data class NotificationConversation( } fun getConversationTitle(context: Context): CharSequence? { - if (isGroup) { - return if (TextSecurePreferences.getNotificationPrivacy(context).isDisplayContact) { - recipient.getDisplayName(context) - } else { - context.getString(R.string.SingleRecipientNotificationBuilder_signal) - } + if (TextSecurePreferences.getNotificationPrivacy(context).isDisplayContact) { + return if (isGroup) recipient.getDisplayName(context) else null } - return null + return context.getString(R.string.SingleRecipientNotificationBuilder_signal) } fun getWhen(): Long { @@ -114,6 +110,14 @@ data class NotificationConversation( } } + fun hasSameContent(other: NotificationConversation?): Boolean { + if (other == null) { + return false + } + + return messageCount == other.messageCount && notificationItems.zip(other.notificationItems).all { (item, otherItem) -> item.hasSameContent(otherItem) } + } + fun getPendingIntent(context: Context): PendingIntent { val intent: Intent = ConversationIntents.createBuilder(context, recipient.id, threadId) .withStartingPosition(mostRecentNotification.getStartingPosition(context)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationExtensions.kt index 9d3e9b99e..c9479c752 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationExtensions.kt @@ -1,10 +1,12 @@ package org.thoughtcrime.securesms.notifications.v2 +import android.app.NotificationManager import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.net.Uri +import android.os.Build import com.bumptech.glide.load.MultiTransformation import com.bumptech.glide.load.Transformation import com.bumptech.glide.load.engine.DiskCacheStrategy @@ -17,6 +19,7 @@ import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri import org.thoughtcrime.securesms.mms.GlideApp +import org.thoughtcrime.securesms.notifications.NotificationIds import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.BitmapUtil import org.thoughtcrime.securesms.util.BlurTransformation @@ -84,3 +87,13 @@ fun Intent.makeUniqueToPreventMerging(): Intent { fun Recipient.getFallback(context: Context): FallbackContactPhoto { return GeneratedContactPhoto(getDisplayName(context), R.drawable.ic_profile_outline_40) } + +fun NotificationManager.isDisplayingSummaryNotification(): Boolean { + if (Build.VERSION.SDK_INT > 23) { + try { + return activeNotifications.any { notification -> notification.id == NotificationIds.MESSAGE_SUMMARY } + } catch (e: Throwable) { + } + } + return false +} 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 c89f88249..97b094846 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 @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.notifications.v2 import android.annotation.TargetApi import android.app.Notification -import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent @@ -46,15 +45,16 @@ object NotificationFactory { defaultBubbleState: BubbleUtil.BubbleState, lastAudibleNotification: Long, notificationConfigurationChanged: Boolean, - alertOverrides: Set + alertOverrides: Set, + previousState: NotificationStateV2 ): Set { if (state.isEmpty) { Log.d(TAG, "State is empty, bailing") return emptySet() } - val nonVisibleThreadCount = state.conversations.count { it.threadId != visibleThreadId } - return if (Build.VERSION.SDK_INT < 23) { + val nonVisibleThreadCount: Int = state.conversations.count { it.threadId != visibleThreadId } + return if (Build.VERSION.SDK_INT < 24) { notify19( context = context, state = state, @@ -66,7 +66,7 @@ object NotificationFactory { nonVisibleThreadCount = nonVisibleThreadCount ) } else { - notify23( + notify24( context = context, state = state, visibleThreadId = visibleThreadId, @@ -75,7 +75,8 @@ object NotificationFactory { lastAudibleNotification = lastAudibleNotification, notificationConfigurationChanged = notificationConfigurationChanged, alertOverrides = alertOverrides, - nonVisibleThreadCount = nonVisibleThreadCount + nonVisibleThreadCount = nonVisibleThreadCount, + previousState = previousState ) } } @@ -121,8 +122,8 @@ object NotificationFactory { return threadsThatNewlyAlerted } - @TargetApi(23) - private fun notify23( + @TargetApi(24) + private fun notify24( context: Context, state: NotificationStateV2, visibleThreadId: Long, @@ -131,7 +132,8 @@ object NotificationFactory { lastAudibleNotification: Long, notificationConfigurationChanged: Boolean, alertOverrides: Set, - nonVisibleThreadCount: Int + nonVisibleThreadCount: Int, + previousState: NotificationStateV2 ): Set { val threadsThatNewlyAlerted: MutableSet = mutableSetOf() @@ -139,7 +141,7 @@ object NotificationFactory { if (conversation.threadId == visibleThreadId && 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)) { + } else if (notificationConfigurationChanged || conversation.hasNewNotifications() || alertOverrides.contains(conversation.threadId) || !conversation.hasSameContent(previousState.getConversation(conversation.threadId))) { if (conversation.hasNewNotifications()) { threadsThatNewlyAlerted += conversation.threadId } @@ -206,7 +208,7 @@ object NotificationFactory { builder.addTurnOffJoinedNotificationsAction(conversation.getTurnOffJoinedNotificationsIntent(context)) } - val notificationId: Int = if (Build.VERSION.SDK_INT < 23) NotificationIds.MESSAGE_SUMMARY else conversation.notificationId + val notificationId: Int = if (Build.VERSION.SDK_INT < 24) NotificationIds.MESSAGE_SUMMARY else conversation.notificationId NotificationManagerCompat.from(context).safelyNotify(context, conversation.recipient, notificationId, builder.build()) } @@ -240,7 +242,7 @@ object NotificationFactory { setPriority(TextSecurePreferences.getNotificationPriority(context)) setLights() setAlarms(state.mostRecentSender) - setTicker(state.mostRecentNotification.getStyledPrimaryText(context, true)) + setTicker(state.mostRecentNotification?.getStyledPrimaryText(context, true)) } Log.d(TAG, "showing summary notification") @@ -313,16 +315,6 @@ object NotificationFactory { NotificationManagerCompat.from(context).safelyNotify(context, recipient, threadId.toInt(), builder.build()) } - private fun NotificationManager.isDisplayingSummaryNotification(): Boolean { - if (Build.VERSION.SDK_INT >= 23) { - try { - return activeNotifications.any { notification -> notification.id == NotificationIds.MESSAGE_SUMMARY } - } catch (e: Throwable) { - } - } - return false - } - private fun NotificationManagerCompat.safelyNotify(context: Context, threadRecipient: Recipient?, notificationId: Int, notification: Notification) { try { notify(notificationId, notification) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItemV2.kt index 0da8c7a92..32a21a7c4 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 @@ -90,7 +90,7 @@ sealed class NotificationItemV2(val threadRecipient: Recipient, protected val re return if (TextSecurePreferences.getNotificationPrivacy(context).isDisplayContact) { individualRecipient.getDisplayName(context) } else { - "" + context.getString(R.string.SingleRecipientNotificationBuilder_signal) } } @@ -133,6 +133,16 @@ sealed class NotificationItemV2(val threadRecipient: Recipient, protected val re } } + fun hasSameContent(other: NotificationItemV2): Boolean { + return timestamp == other.timestamp && + id == other.id && + isMms == other.isMms && + individualRecipient == other.individualRecipient && + individualRecipient.hasSameContent(other.individualRecipient) && + slideDeck?.thumbnailSlide?.isInProgress == other.slideDeck?.thumbnailSlide?.isInProgress && + record.isRemoteDelete == other.record.isRemoteDelete + } + private fun CharSequence?.trimToDisplayLength(): CharSequence { val text: CharSequence = this ?: "" return if (text.length <= AbstractNotificationBuilder.MAX_DISPLAY_LENGTH) { 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 e969b0119..9c0deb2dd 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 @@ -33,16 +33,20 @@ data class NotificationStateV2(val conversations: List .toSet() } - val mostRecentNotification: NotificationItemV2 - get() = notificationItems.last() + val mostRecentNotification: NotificationItemV2? + get() = notificationItems.lastOrNull() - val mostRecentSender: Recipient - get() = mostRecentNotification.individualRecipient + val mostRecentSender: Recipient? + get() = mostRecentNotification?.individualRecipient fun getNonVisibleConversation(visibleThreadId: Long): List { return conversations.filterNot { it.threadId == visibleThreadId } } + fun getConversation(threadId: Long): NotificationConversation? { + return conversations.firstOrNull { it.threadId == threadId } + } + fun getDeleteIntent(context: Context): PendingIntent? { val ids = LongArray(messageCount) val mms = BooleanArray(ids.size) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7a7f4381e..7bf162816 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1751,6 +1751,7 @@ Signal New message Message request + You Play video