Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notification images #4430

Merged
merged 17 commits into from
Nov 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/4401.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Breaking SDK API change to PushRuleListener, the separated callbacks have been merged into one with a data class which includes all the previously separated push information
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
@@ -0,0 +1,26 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.pushrules

import org.matrix.android.sdk.api.pushrules.rest.PushRule
import org.matrix.android.sdk.api.session.events.model.Event

data class PushEvents(
val matchedEvents: List<Pair<Event, PushRule>>,
val roomsJoined: Collection<String>,
val roomsLeft: Collection<String>,
val redactedEventIds: List<String>
)
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,7 @@ interface PushRuleService {
// fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule?

interface PushRuleListener {
fun onMatchRule(event: Event, actions: List<Action>)
fun onRoomJoined(roomId: String)
fun onRoomLeft(roomId: String)
fun onEventRedacted(redactedEventId: String)
fun batchFinish()
fun onEvents(pushEvents: PushEvents)
}

fun getKeywords(): LiveData<Set<String>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.pushrules.Action
import org.matrix.android.sdk.api.pushrules.PushEvents
import org.matrix.android.sdk.api.pushrules.PushRuleService
import org.matrix.android.sdk.api.pushrules.RuleKind
import org.matrix.android.sdk.api.pushrules.RuleScope
Expand Down Expand Up @@ -142,79 +143,6 @@ internal class DefaultPushRuleService @Inject constructor(
return pushRuleFinder.fulfilledBingRule(event, rules)?.getActions().orEmpty()
}

// fun processEvents(events: List<Event>) {
// var hasDoneSomething = false
// events.forEach { event ->
// fulfilledBingRule(event)?.let {
// hasDoneSomething = true
// dispatchBing(event, it)
// }
// }
// if (hasDoneSomething)
// dispatchFinish()
// }

fun dispatchBing(event: Event, rule: PushRule) {
synchronized(listeners) {
val actionsList = rule.getActions()
listeners.forEach {
try {
it.onMatchRule(event, actionsList)
} catch (e: Throwable) {
Timber.e(e, "Error while dispatching bing")
}
}
}
}

fun dispatchRoomJoined(roomId: String) {
synchronized(listeners) {
listeners.forEach {
try {
it.onRoomJoined(roomId)
} catch (e: Throwable) {
Timber.e(e, "Error while dispatching room joined")
}
}
}
}

fun dispatchRoomLeft(roomId: String) {
synchronized(listeners) {
listeners.forEach {
try {
it.onRoomLeft(roomId)
} catch (e: Throwable) {
Timber.e(e, "Error while dispatching room left")
}
}
}
}

fun dispatchRedactedEventId(redactedEventId: String) {
synchronized(listeners) {
listeners.forEach {
try {
it.onEventRedacted(redactedEventId)
} catch (e: Throwable) {
Timber.e(e, "Error while dispatching redacted event")
}
}
}
}

fun dispatchFinish() {
synchronized(listeners) {
listeners.forEach {
try {
it.batchFinish()
} catch (e: Throwable) {
Timber.e(e, "Error while dispatching finish")
}
}
}
}

override fun getKeywords(): LiveData<Set<String>> {
// Keywords are all content rules that don't start with '.'
val liveData = monarchy.findAllMappedWithChanges(
Expand All @@ -229,4 +157,16 @@ internal class DefaultPushRuleService @Inject constructor(
results.firstOrNull().orEmpty().toSet()
}
}

fun dispatchEvents(pushEvents: PushEvents) {
synchronized(listeners) {
listeners.forEach {
try {
it.onEvents(pushEvents)
} catch (e: Throwable) {
Timber.e(e, "Error while dispatching push events")
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.matrix.android.sdk.internal.session.notification

import org.matrix.android.sdk.api.pushrules.PushEvents
import org.matrix.android.sdk.api.pushrules.rest.PushRule
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.isInvitation
Expand All @@ -39,14 +40,6 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
) : ProcessEventForPushTask {

override suspend fun execute(params: ProcessEventForPushTask.Params) {
// Handle left rooms
params.syncResponse.leave.keys.forEach {
defaultPushRuleService.dispatchRoomLeft(it)
}
// Handle joined rooms
params.syncResponse.join.keys.forEach {
defaultPushRuleService.dispatchRoomJoined(it)
}
val newJoinEvents = params.syncResponse.join
.mapNotNull { (key, value) ->
value.timeline?.events?.mapNotNull {
Expand Down Expand Up @@ -74,10 +67,10 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
}
Timber.v("[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" +
" to check for push rules with ${params.rules.size} rules")
allEvents.forEach { event ->
val matchedEvents = allEvents.mapNotNull { event ->
pushRuleFinder.fulfilledBingRule(event, params.rules)?.let {
Timber.v("[PushRules] Rule $it match for event ${event.eventId}")
defaultPushRuleService.dispatchBing(event, it)
event to it
}
}

Expand All @@ -91,10 +84,13 @@ internal class DefaultProcessEventForPushTask @Inject constructor(

Timber.v("[PushRules] Found ${allRedactedEvents.size} redacted events")

allRedactedEvents.forEach { redactedEventId ->
defaultPushRuleService.dispatchRedactedEventId(redactedEventId)
}

defaultPushRuleService.dispatchFinish()
defaultPushRuleService.dispatchEvents(
PushEvents(
matchedEvents = matchedEvents,
roomsJoined = params.syncResponse.join.keys,
roomsLeft = params.syncResponse.leave.keys,
redactedEventIds = allRedactedEvents
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
resolvedEvent
?.also { Timber.tag(loggerTag.value).d("Fast lane: notify drawer") }
?.let {
notificationDrawerManager.onNotifiableEventReceived(it)
notificationDrawerManager.refreshNotificationDrawer()
notificationDrawerManager.updateEvents { it.onNotifiableEventReceived(resolvedEvent) }
}
}
}
Expand Down
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 @@ -2102,12 +2102,12 @@ class RoomDetailFragment @Inject constructor(
// VectorInviteView.Callback

override fun onAcceptInvite() {
notificationDrawerManager.clearMemberShipNotificationForRoom(roomDetailArgs.roomId)
notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(roomDetailArgs.roomId) }
roomDetailViewModel.handle(RoomDetailAction.AcceptInvite)
}

override fun onRejectInvite() {
notificationDrawerManager.clearMemberShipNotificationForRoom(roomDetailArgs.roomId)
notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(roomDetailArgs.roomId) }
roomDetailViewModel.handle(RoomDetailAction.RejectInvite)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ class RoomListFragment @Inject constructor(
}

override fun onAcceptRoomInvitation(room: RoomSummary) {
notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId)
notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(room.roomId) }
roomListViewModel.handle(RoomListAction.AcceptInvitation(room))
}

Expand All @@ -495,7 +495,7 @@ class RoomListFragment @Inject constructor(
}

override fun onRejectRoomInvitation(room: RoomSummary) {
notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId)
notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(room.roomId) }
roomListViewModel.handle(RoomListAction.RejectInvitation(room))
}
}
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
Loading