Skip to content

Commit

Permalink
Merge pull request #4402 from vector-im/feature/adm/notification-images
Browse files Browse the repository at this point in the history
Supporting images in notifications
  • Loading branch information
ouchadam authored Nov 5, 2021
2 parents 183ca18 + 1eb544e commit d80ca29
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 16 deletions.
1 change: 1 addition & 0 deletions changelog.d/4402.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adds support for images inside message notifications
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,7 @@ fun String?.insertBeforeLast(insert: String, delimiter: String = "."): String {
replaceRange(idx, idx, insert)
}
}

inline fun <reified R> Any?.takeAs(): R? {
return takeIf { it is R } as R?
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/
package im.vector.app.features.notifications

import android.net.Uri
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.extensions.takeAs
import im.vector.app.core.resources.StringProvider
import im.vector.app.features.displayname.getBestName
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
Expand All @@ -28,12 +30,15 @@ import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.isEdition
import org.matrix.android.sdk.api.session.events.model.isImageMessage
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.getEditedEventId
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
import org.matrix.android.sdk.api.util.toMatrixItem
import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
import timber.log.Timber
Expand All @@ -49,11 +54,12 @@ import javax.inject.Inject
class NotifiableEventResolver @Inject constructor(
private val stringProvider: StringProvider,
private val noticeEventFormatter: NoticeEventFormatter,
private val displayableEventFormatter: DisplayableEventFormatter) {
private val displayableEventFormatter: DisplayableEventFormatter
) {

// private val eventDisplay = RiotEventDisplay(context)

fun resolveEvent(event: Event/*, roomState: RoomState?, bingRule: PushRule?*/, session: Session, isNoisy: Boolean): NotifiableEvent? {
suspend fun resolveEvent(event: Event/*, roomState: RoomState?, bingRule: PushRule?*/, session: Session, isNoisy: Boolean): NotifiableEvent? {
val roomID = event.roomId ?: return null
val eventId = event.eventId ?: return null
if (event.getClearType() == EventType.STATE_ROOM_MEMBER) {
Expand Down Expand Up @@ -89,7 +95,7 @@ class NotifiableEventResolver @Inject constructor(
}
}

fun resolveInMemoryEvent(session: Session, event: Event, canBeReplaced: Boolean): NotifiableEvent? {
suspend fun resolveInMemoryEvent(session: Session, event: Event, canBeReplaced: Boolean): NotifiableEvent? {
if (event.getClearType() != EventType.MESSAGE) return null

// Ignore message edition
Expand Down Expand Up @@ -120,7 +126,7 @@ class NotifiableEventResolver @Inject constructor(
}
}

private fun resolveMessageEvent(event: TimelineEvent, session: Session, canBeReplaced: Boolean, isNoisy: Boolean): NotifiableEvent {
private suspend fun resolveMessageEvent(event: TimelineEvent, session: Session, canBeReplaced: Boolean, isNoisy: Boolean): NotifiableEvent {
// The event only contains an eventId, and roomId (type is m.room.*) , we need to get the displayable content (names, avatar, text, etc...)
val room = session.getRoom(event.root.roomId!! /*roomID cannot be null*/)

Expand All @@ -140,6 +146,7 @@ class NotifiableEventResolver @Inject constructor(
senderName = senderDisplayName,
senderId = event.root.senderId,
body = body.toString(),
imageUri = event.fetchImageIfPresent(session),
roomId = event.root.roomId!!,
roomName = roomName,
matrixID = session.myUserId
Expand Down Expand Up @@ -173,6 +180,7 @@ class NotifiableEventResolver @Inject constructor(
senderName = senderDisplayName,
senderId = event.root.senderId,
body = body,
imageUri = event.fetchImageIfPresent(session),
roomId = event.root.roomId!!,
roomName = roomName,
roomIsDirect = room.roomSummary()?.isDirect ?: false,
Expand All @@ -192,6 +200,26 @@ class NotifiableEventResolver @Inject constructor(
}
}

private suspend fun TimelineEvent.fetchImageIfPresent(session: Session): Uri? {
return when {
root.isEncrypted() && root.mxDecryptionResult == null -> null
root.isImageMessage() -> downloadAndExportImage(session)
else -> null
}
}

private suspend fun TimelineEvent.downloadAndExportImage(session: Session): Uri? {
return kotlin.runCatching {
getLastMessageContent()?.takeAs<MessageWithAttachmentContent>()?.let { imageMessage ->
val fileService = session.fileService()
fileService.downloadFile(imageMessage)
fileService.getTemporarySharableURI(imageMessage)
}
}.onFailure {
Timber.e(it, "Failed to download and export image for notification")
}.getOrNull()
}

private fun resolveStateRoomEvent(event: Event, session: Session, canBeReplaced: Boolean, isNoisy: Boolean): NotifiableEvent? {
val content = event.content?.toModel<RoomMemberContent>() ?: return null
val roomId = event.roomId ?: return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package im.vector.app.features.notifications

import android.net.Uri
import org.matrix.android.sdk.api.session.events.model.EventType

data class NotifiableMessageEvent(
Expand All @@ -26,6 +27,7 @@ data class NotifiableMessageEvent(
val senderName: String?,
val senderId: String?,
val body: String?,
val imageUri: Uri?,
val roomId: String,
val roomName: String?,
val roomIsDirect: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
?: context?.getString(R.string.notification_sender_me),
senderId = session.myUserId,
body = message,
imageUri = null,
roomId = room.roomId,
roomName = room.roomSummary()?.displayName ?: room.roomId,
roomIsDirect = room.roomSummary()?.isDirect == true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@

package im.vector.app.features.notifications

import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.pushrules.PushEvents
import org.matrix.android.sdk.api.pushrules.PushRuleService
import org.matrix.android.sdk.api.pushrules.getActions
Expand All @@ -31,21 +36,24 @@ class PushRuleTriggerListener @Inject constructor(
) : PushRuleService.PushRuleListener {

private var session: Session? = null
private val scope: CoroutineScope = CoroutineScope(SupervisorJob())

override fun onEvents(pushEvents: PushEvents) {
session?.let { session ->
val notifiableEvents = createNotifiableEvents(pushEvents, session)
notificationDrawerManager.updateEvents { queuedEvents ->
notifiableEvents.forEach { notifiableEvent ->
queuedEvents.onNotifiableEventReceived(notifiableEvent)
scope.launch {
session?.let { session ->
val notifiableEvents = createNotifiableEvents(pushEvents, session)
notificationDrawerManager.updateEvents { queuedEvents ->
notifiableEvents.forEach { notifiableEvent ->
queuedEvents.onNotifiableEventReceived(notifiableEvent)
}
queuedEvents.syncRoomEvents(roomsLeft = pushEvents.roomsLeft, roomsJoined = pushEvents.roomsJoined)
queuedEvents.markRedacted(pushEvents.redactedEventIds)
}
queuedEvents.syncRoomEvents(roomsLeft = pushEvents.roomsLeft, roomsJoined = pushEvents.roomsJoined)
queuedEvents.markRedacted(pushEvents.redactedEventIds)
}
} ?: Timber.e("Called without active session")
} ?: Timber.e("Called without active session")
}
}

private fun createNotifiableEvents(pushEvents: PushEvents, session: Session): List<NotifiableEvent> {
private suspend fun createNotifiableEvents(pushEvents: PushEvents, session: Session): List<NotifiableEvent> {
return pushEvents.matchedEvents.mapNotNull { (event, pushRule) ->
Timber.v("Push rule match for event ${event.eventId}")
val action = pushRule.getActions().toNotificationAction()
Expand All @@ -67,6 +75,7 @@ class PushRuleTriggerListener @Inject constructor(
}

fun stop() {
scope.coroutineContext.cancelChildren(CancellationException("PushRuleTriggerListener stopping"))
session?.removePushRuleListener(this)
session = null
notificationDrawerManager.clearAllEvents()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,14 @@ class RoomGroupMessageCreator @Inject constructor(
}
when {
event.isSmartReplyError() -> addMessage(stringProvider.getString(R.string.notification_inline_reply_failed), event.timestamp, senderPerson)
else -> addMessage(event.body, event.timestamp, senderPerson)
else -> {
val message = NotificationCompat.MessagingStyle.Message(event.body, event.timestamp, senderPerson).also { message ->
event.imageUri?.let {
message.setData("image/", it)
}
}
addMessage(message)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,6 @@ fun aNotifiableMessageEvent(
roomName = "room-name",
roomIsDirect = false,
canBeReplaced = false,
isRedacted = isRedacted
isRedacted = isRedacted,
imageUri = null
)

0 comments on commit d80ca29

Please sign in to comment.