Skip to content

Commit 16c9300

Browse files
committed
Improve wearables and support multiple previews
1 parent 06eeaf0 commit 16c9300

6 files changed

Lines changed: 239 additions & 109 deletions

File tree

app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationBuilder.kt

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.notifications.v2
33
import android.app.Notification
44
import android.app.PendingIntent
55
import android.content.Context
6+
import android.content.Intent
67
import android.graphics.Bitmap
78
import android.graphics.Color
89
import android.net.Uri
@@ -13,6 +14,7 @@ import androidx.annotation.DrawableRes
1314
import androidx.annotation.StringRes
1415
import androidx.annotation.WorkerThread
1516
import androidx.core.app.NotificationCompat
17+
import androidx.core.app.NotificationManagerCompat
1618
import androidx.core.app.RemoteInput
1719
import androidx.core.content.LocusIdCompat
1820
import androidx.core.graphics.drawable.IconCompat
@@ -82,7 +84,7 @@ sealed class NotificationBuilder(protected val context: Context) {
8284
protected abstract fun addActions(replyMethod: ReplyMethod, conversation: NotificationConversation)
8385
protected abstract fun addMarkAsReadActionActual(conversation: NotificationConversation)
8486
protected abstract fun addMarkAsReadActionActual(state: NotificationState)
85-
protected abstract fun addMessagesActual(conversation: NotificationConversation, includeShortcut: Boolean)
87+
protected abstract fun addMessagesActual(conversation: NotificationConversation, includeShortcut: Boolean, includeMediaContent: Boolean)
8688
protected abstract fun addMessagesActual(state: NotificationState)
8789

8890
@WorkerThread
@@ -145,8 +147,8 @@ sealed class NotificationBuilder(protected val context: Context) {
145147
}
146148
}
147149

148-
fun addMessages(conversation: NotificationConversation) {
149-
addMessagesActual(conversation, privacy.isDisplayContact)
150+
fun addMessages(conversation: NotificationConversation, includeMediaContent: Boolean = true) {
151+
addMessagesActual(conversation, privacy.isDisplayContact, includeMediaContent)
150152
}
151153

152154
fun addMessages(state: NotificationState) {
@@ -200,10 +202,9 @@ sealed class NotificationBuilder(protected val context: Context) {
200202
*/
201203
private class NotificationBuilderCompat(context: Context) : NotificationBuilder(context) {
202204
val builder: NotificationCompat.Builder = NotificationCompat.Builder(context, NotificationChannels.getInstance().messagesChannel)
205+
private val wearableExtender: NotificationCompat.WearableExtender = NotificationCompat.WearableExtender()
203206

204207
override fun addActions(replyMethod: ReplyMethod, conversation: NotificationConversation) {
205-
val extender: NotificationCompat.WearableExtender = NotificationCompat.WearableExtender()
206-
207208
addMarkAsReadActionActual(conversation)
208209

209210
if (conversation.mostRecentNotification.canReply(context)) {
@@ -231,11 +232,9 @@ sealed class NotificationBuilder(protected val context: Context) {
231232
.addRemoteInput(RemoteInput.Builder(DefaultMessageNotifier.EXTRA_REMOTE_REPLY).setLabel(label).build())
232233
.build()
233234

234-
extender.addAction(wearableReplyAction)
235+
wearableExtender.addAction(wearableReplyAction)
235236
}
236237
}
237-
238-
builder.extend(extender)
239238
}
240239

241240
override fun addMarkAsReadActionActual(conversation: NotificationConversation) {
@@ -248,7 +247,7 @@ sealed class NotificationBuilder(protected val context: Context) {
248247
.build()
249248

250249
builder.addAction(markAsReadAction)
251-
builder.extend(NotificationCompat.WearableExtender().addAction(markAsReadAction))
250+
wearableExtender.addAction(markAsReadAction)
252251
}
253252
}
254253

@@ -258,7 +257,7 @@ sealed class NotificationBuilder(protected val context: Context) {
258257
if (markAsRead != null) {
259258
val markAllAsReadAction = NotificationCompat.Action(CoreUiR.drawable.symbol_check_24, context.getString(R.string.MessageNotifier_mark_all_as_read), markAsRead)
260259
builder.addAction(markAllAsReadAction)
261-
builder.extend(NotificationCompat.WearableExtender().addAction(markAllAsReadAction))
260+
wearableExtender.addAction(markAllAsReadAction)
262261
}
263262
}
264263

@@ -274,8 +273,8 @@ sealed class NotificationBuilder(protected val context: Context) {
274273
}
275274
}
276275

277-
override fun addMessagesActual(conversation: NotificationConversation, includeShortcut: Boolean) {
278-
if (Build.VERSION.SDK_INT < 24) {
276+
override fun addMessagesActual(conversation: NotificationConversation, includeShortcut: Boolean, includeMediaContent: Boolean) {
277+
if (Build.VERSION.SDK_INT < 24 && includeMediaContent) {
279278
val bigPictureUri: Uri? = conversation.getSlideBigPictureUri(context)
280279
if (bigPictureUri != null) {
281280
builder.setStyle(
@@ -317,14 +316,41 @@ sealed class NotificationBuilder(protected val context: Context) {
317316
person = personBuilder.build()
318317
}
319318

320-
val (dataUri: Uri?, mimeType: String?) = notificationItem.getThumbnailInfo(context)
319+
val thumbnailInfos: List<NotificationItem.ThumbnailInfo> = if (includeMediaContent) {
320+
notificationItem.getThumbnailInfos(context)
321+
} else {
322+
emptyList()
323+
}
324+
val mediaContents: List<MessageMediaContent> = thumbnailInfos.toMessageMediaContent()
321325

322-
messagingStyle.addMessage(NotificationCompat.MessagingStyle.Message(notificationItem.getPrimaryText(context), notificationItem.timestamp, person).setData(mimeType, dataUri))
326+
if (!includeMediaContent || thumbnailInfos.isEmpty()) {
327+
messagingStyle.addMessage(NotificationCompat.MessagingStyle.Message(notificationItem.getPrimaryText(context), notificationItem.timestamp, person))
328+
} else {
329+
mediaContents.forEach { mediaContent ->
330+
val message = NotificationCompat.MessagingStyle.Message("", notificationItem.timestamp, person)
331+
332+
grantNotificationListenerReadAccess(mediaContent.uri)
333+
message.setData(mediaContent.contentType, mediaContent.uri)
334+
messagingStyle.addMessage(message)
335+
}
336+
}
323337
}
324338

325339
builder.setStyle(messagingStyle)
326340
}
327341

342+
private fun grantNotificationListenerReadAccess(uri: Uri?) {
343+
if (uri == null) {
344+
return
345+
}
346+
347+
NotificationManagerCompat.getEnabledListenerPackages(context).forEach { packageName ->
348+
runCatching {
349+
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
350+
}
351+
}
352+
}
353+
328354
override fun addMessagesActual(state: NotificationState) {
329355
if (Build.VERSION.SDK_INT >= 24) {
330356
return
@@ -486,6 +512,7 @@ sealed class NotificationBuilder(protected val context: Context) {
486512
}
487513

488514
override fun build(): Notification {
515+
builder.extend(wearableExtender)
489516
return builder.build()
490517
}
491518

@@ -517,6 +544,17 @@ sealed class NotificationBuilder(protected val context: Context) {
517544
}
518545
}
519546

547+
private fun List<NotificationItem.ThumbnailInfo>.toMessageMediaContent(): List<MessageMediaContent> {
548+
return mapNotNull { thumbnailInfo ->
549+
val uri: Uri = thumbnailInfo.uri ?: return@mapNotNull null
550+
val contentType: String = thumbnailInfo.contentType ?: return@mapNotNull null
551+
552+
MessageMediaContent(uri, contentType)
553+
}
554+
}
555+
556+
private data class MessageMediaContent(val uri: Uri, val contentType: String)
557+
520558
private fun Bitmap?.toIconCompat(): IconCompat? {
521559
return if (this != null) {
522560
IconCompat.createWithBitmap(this)

app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationConversation.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ data class NotificationConversation(
7373
}
7474
}
7575

76+
fun hasImagePreview(context: Context): Boolean {
77+
return SignalStore.settings.messageNotificationsPrivacy.isDisplayMessage &&
78+
!KeyCachingService.isLocked(context) &&
79+
notificationItems.any { it.getBigPictureUri() != null }
80+
}
81+
7682
fun getContentText(context: Context): CharSequence? {
7783
val privacy: NotificationPrivacyPreference = SignalStore.settings.messageNotificationsPrivacy
7884
val stringBuilder = SpannableStringBuilder()

app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,35 @@ object NotificationFactory {
218218
return ((conversation.hasNewNotifications() && canAlertBasedOnTime) || alertOverride) && (!conversation.mostRecentNotification.authorRecipient.isSelf || isUnreadNoteToSelf)
219219
}
220220

221+
private data class ConversationNotificationPresentation(
222+
val contentText: CharSequence?,
223+
val includeMediaContent: Boolean,
224+
val ticker: CharSequence?
225+
)
226+
227+
private fun NotificationConversation.getNotificationPresentation(context: Context): ConversationNotificationPresentation {
228+
if (!hasImagePreview(context)) {
229+
return ConversationNotificationPresentation(
230+
contentText = getContentText(context),
231+
includeMediaContent = true,
232+
ticker = mostRecentNotification.getStyledPrimaryText(context, true)
233+
)
234+
}
235+
236+
return when (WearableNotificationBridgePolicy.getImagePreviewPresentation(context)) {
237+
WearableNotificationBridgePolicy.ImagePreviewPresentation.MEDIA -> ConversationNotificationPresentation(
238+
contentText = null,
239+
includeMediaContent = true,
240+
ticker = null
241+
)
242+
WearableNotificationBridgePolicy.ImagePreviewPresentation.TEXT_ONLY_FALLBACK -> ConversationNotificationPresentation(
243+
contentText = getContentText(context),
244+
includeMediaContent = false,
245+
ticker = null
246+
)
247+
}
248+
}
249+
221250
@WorkerThread
222251
private fun notifyForConversation(
223252
context: Context,
@@ -231,6 +260,7 @@ object NotificationFactory {
231260
}
232261

233262
val builder: NotificationBuilder = NotificationBuilder.create(context)
263+
val presentation: ConversationNotificationPresentation = conversation.getNotificationPresentation(context)
234264

235265
builder.apply {
236266
setSmallIcon(R.drawable.ic_notification)
@@ -250,18 +280,18 @@ object NotificationFactory {
250280

251281
setContentInfo(conversation.messageCount.toString())
252282
setNumber(conversation.messageCount)
253-
setContentText(conversation.getContentText(context))
283+
setContentText(presentation.contentText)
254284
setContentIntent(conversation.getPendingIntent(context))
255285
setDeleteIntent(conversation.getDeleteIntent(context))
256286
setSortKey(conversation.sortKey.toString())
257287
setWhen(conversation)
258288
addReplyActions(conversation)
259289
setOnlyAlertOnce(!shouldAlert)
260-
addMessages(conversation)
290+
addMessages(conversation, includeMediaContent = presentation.includeMediaContent)
261291
setPriority(TextSecurePreferences.getNotificationPriority(context))
262292
setLights()
263293
setAlarms(conversation.recipient)
264-
setTicker(conversation.mostRecentNotification.getStyledPrimaryText(context, true))
294+
setTicker(presentation.ticker)
265295
setBubbleMetadata(conversation, if (targetThread == conversation.thread) defaultBubbleState else BubbleUtil.BubbleState.HIDDEN)
266296
}
267297

app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItem.kt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ sealed class NotificationItem(val threadRecipient: Recipient, protected val reco
7171
abstract fun getStartingPosition(context: Context): Int
7272
abstract fun getLargeIconUri(): Uri?
7373
abstract fun getBigPictureUri(): Uri?
74-
abstract fun getThumbnailInfo(context: Context): ThumbnailInfo
74+
abstract fun getThumbnailInfos(context: Context): List<ThumbnailInfo>
7575
abstract fun canReply(context: Context): Boolean
7676

7777
protected fun getMessageContentType(messageRecord: MmsMessageRecord): String {
@@ -238,7 +238,7 @@ class MessageNotification(threadRecipient: Recipient, record: MessageRecord, val
238238
override val isNewNotification: Boolean = notifiedTimestamp == 0L && !record.isEditMessage
239239
val hasSelfMention = record.hasSelfMention() || (record is MmsMessageRecord && record.quote?.author == Recipient.self().id)
240240

241-
private var thumbnailInfo: ThumbnailInfo = NotificationThumbnails.getWithoutModifying(this)
241+
private var thumbnailInfos: List<ThumbnailInfo> = NotificationThumbnails.getWithoutModifying(this)
242242

243243
override fun getPrimaryTextActual(context: Context): CharSequence {
244244
return if (KeyCachingService.isLocked(context)) {
@@ -295,15 +295,15 @@ class MessageNotification(threadRecipient: Recipient, record: MessageRecord, val
295295
return if (slide?.isInProgress == false) slide.uri else null
296296
}
297297

298-
override fun getThumbnailInfo(context: Context): ThumbnailInfo {
298+
override fun getThumbnailInfos(context: Context): List<ThumbnailInfo> {
299299
return if (SignalStore.settings.messageNotificationsPrivacy.isDisplayMessage && !KeyCachingService.isLocked(context)) {
300-
if (thumbnailInfo.needsShrinking) {
301-
thumbnailInfo = NotificationThumbnails.get(context, this)
300+
if (thumbnailInfos.any { it.needsShrinking }) {
301+
thumbnailInfos = NotificationThumbnails.get(context, this)
302302
}
303303

304-
thumbnailInfo
304+
thumbnailInfos
305305
} else {
306-
ThumbnailInfo.NONE
306+
emptyList()
307307
}
308308
}
309309

@@ -325,7 +325,7 @@ class MessageNotification(threadRecipient: Recipient, record: MessageRecord, val
325325
}
326326

327327
override fun hasSameContent(other: NotificationItem): Boolean {
328-
return super.hasSameContent(other) && thumbnailInfo == (other as? MessageNotification)?.thumbnailInfo
328+
return super.hasSameContent(other) && thumbnailInfos == (other as? MessageNotification)?.thumbnailInfos
329329
}
330330

331331
override fun toString(): String {
@@ -397,7 +397,7 @@ class ReactionNotification(threadRecipient: Recipient, record: MessageRecord, va
397397

398398
override fun getLargeIconUri(): Uri? = null
399399
override fun getBigPictureUri(): Uri? = null
400-
override fun getThumbnailInfo(context: Context): ThumbnailInfo = ThumbnailInfo()
400+
override fun getThumbnailInfos(context: Context): List<ThumbnailInfo> = emptyList()
401401
override fun canReply(context: Context): Boolean = false
402402

403403
override fun toString(): String {
@@ -427,7 +427,7 @@ class VoteNotification(threadRecipient: Recipient, record: MessageRecord, val vo
427427

428428
override fun getLargeIconUri(): Uri? = null
429429
override fun getBigPictureUri(): Uri? = null
430-
override fun getThumbnailInfo(context: Context): ThumbnailInfo = ThumbnailInfo()
430+
override fun getThumbnailInfos(context: Context): List<ThumbnailInfo> = emptyList()
431431
override fun canReply(context: Context): Boolean = false
432432

433433
override fun toString(): String {

0 commit comments

Comments
 (0)