From af023669baded41cc3d21a5e76be832a04675008 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 24 Mar 2021 12:09:06 +0100 Subject: [PATCH 01/11] Implement FirstThrottler, to gain 200 ms for first refresh --- .../im/vector/app/core/extensions/LiveData.kt | 2 +- .../vector/app/core/utils/FirstThrottler.kt | 21 +++++++++++++++---- .../NotificationDrawerManager.kt | 10 +++++++-- .../VectorSettingsHelpAboutFragment.kt | 2 +- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/extensions/LiveData.kt b/vector/src/main/java/im/vector/app/core/extensions/LiveData.kt index 588063e2a4..5a6599acff 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/LiveData.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/LiveData.kt @@ -39,7 +39,7 @@ inline fun LiveData>.observeEventFirstThrottle(owner: Lifecycle val firstThrottler = FirstThrottler(minimumInterval) this.observe(owner, EventObserver { - if (firstThrottler.canHandle()) { + if (firstThrottler.canHandle() is FirstThrottler.CanHandlerResult.Yes) { it.run(observer) } }) diff --git a/vector/src/main/java/im/vector/app/core/utils/FirstThrottler.kt b/vector/src/main/java/im/vector/app/core/utils/FirstThrottler.kt index 3d52ca99db..004f500c4e 100644 --- a/vector/src/main/java/im/vector/app/core/utils/FirstThrottler.kt +++ b/vector/src/main/java/im/vector/app/core/utils/FirstThrottler.kt @@ -24,14 +24,27 @@ import android.os.SystemClock class FirstThrottler(private val minimumInterval: Long = 800) { private var lastDate = 0L - fun canHandle(): Boolean { + sealed class CanHandlerResult { + object Yes : CanHandlerResult() + data class No(val shouldWaitMillis: Long) : CanHandlerResult() + + fun waitMillis(): Long { + return when (this) { + Yes -> 0 + is No -> shouldWaitMillis + } + } + } + + fun canHandle(): CanHandlerResult { val now = SystemClock.elapsedRealtime() - if (now > lastDate + minimumInterval) { + val delaySinceLast = now - lastDate + if (delaySinceLast > minimumInterval) { lastDate = now - return true + return CanHandlerResult.Yes } // Too soon - return false + return CanHandlerResult.No(minimumInterval - delaySinceLast) } } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index 7f3c0a5beb..36b69a7958 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -26,6 +26,7 @@ import im.vector.app.ActiveSessionDataSource import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.FirstThrottler import im.vector.app.features.settings.VectorPreferences import me.gujun.android.span.span import org.matrix.android.sdk.api.session.Session @@ -194,10 +195,14 @@ class NotificationDrawerManager @Inject constructor(private val context: Context notificationUtils.cancelNotificationMessage(roomId, ROOM_INVITATION_NOTIFICATION_ID) } + private var firstThrottler = FirstThrottler(200) + fun refreshNotificationDrawer() { // Implement last throttler - Timber.v("refreshNotificationDrawer()") + val canHandle = firstThrottler.canHandle() + Timber.v("refreshNotificationDrawer(), delay: ${canHandle.waitMillis()} ms") backgroundHandler.removeCallbacksAndMessages(null) + backgroundHandler.postDelayed( { try { @@ -206,7 +211,8 @@ class NotificationDrawerManager @Inject constructor(private val context: Context // It can happen if for instance session has been destroyed. It's a bit ugly to try catch like this, but it's safer Timber.w(throwable, "refreshNotificationDrawerBg failure") } - }, 200) + }, + canHandle.waitMillis()) } @WorkerThread diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt index 17c5cad1c2..03b7c16274 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt @@ -101,7 +101,7 @@ class VectorSettingsHelpAboutFragment @Inject constructor( // third party notice findPreference(VectorPreferences.SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY)!! .onPreferenceClickListener = Preference.OnPreferenceClickListener { - if (firstThrottler.canHandle()) { + if (firstThrottler.canHandle() is FirstThrottler.CanHandlerResult.Yes) { activity?.displayInWebView(VectorSettingsUrls.THIRD_PARTY_LICENSES) } false From 96153fe92a3e865ecdb8164609ddd0400fcb36ef Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 24 Mar 2021 18:48:25 +0100 Subject: [PATCH 02/11] Get Event after a Push for a faster notification display in some conditions --- CHANGES.md | 1 + .../matrix/android/sdk/api/session/Session.kt | 2 + .../sdk/api/session/events/EventService.kt | 28 ++++++++++ .../internal/database/query/ReadQueries.kt | 23 ++++---- .../sdk/internal/session/DefaultSession.kt | 3 ++ .../sdk/internal/session/SessionModule.kt | 7 ++- .../session/call/CallEventProcessor.kt | 13 ++++- .../session/call/CallSignalingHandler.kt | 20 ++++--- .../session/events/DefaultEventService.kt | 40 ++++++++++++++ .../sdk/internal/session/room/RoomModule.kt | 5 ++ .../session/room/timeline/GetEventTask.kt | 43 +++++++++++---- .../fcm/VectorFirebaseMessagingService.kt | 54 ++++++++++++++++--- .../notifications/NotifiableEventResolver.kt | 30 +++++++++-- .../NotificationDrawerManager.kt | 6 ++- 14 files changed, 234 insertions(+), 41 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt diff --git a/CHANGES.md b/CHANGES.md index 13385123e6..86ba91c9df 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,7 @@ Improvements 🙌: - Add better support for empty room name fallback (#3106) - Room list improvements (paging) - Fix quick click action (#3127) + - Get Event after a Push for a faster notification display in some conditions Bugfix 🐛: - Fix bad theme change for the MainActivity diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index 7a24ccac11..a15799d862 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.call.CallSignalingService import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.crypto.CryptoService +import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.group.GroupService @@ -68,6 +69,7 @@ interface Session : SignOutService, FilterService, TermsService, + EventService, ProfileService, PushRuleService, PushersService, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt new file mode 100644 index 0000000000..41bc0a1a62 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 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.session.events + +import org.matrix.android.sdk.api.session.events.model.Event + +interface EventService { + + /** + * Ask the homeserver for an event content. The SDK will try to decrypt it if it is possible + * The result will not be stored into cache + */ + suspend fun getEvent(roomId: String, eventId: String): Event +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt index a3c741ad55..5423025823 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt @@ -38,16 +38,21 @@ internal fun isEventRead(realmConfiguration: RealmConfiguration, Realm.getInstance(realmConfiguration).use { realm -> val liveChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: return@use val eventToCheck = liveChunk.timelineEvents.find(eventId) - isEventRead = if (eventToCheck == null || eventToCheck.root?.sender == userId) { - true - } else { - val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst() - ?: return@use - val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.displayIndex - ?: Int.MIN_VALUE - val eventToCheckIndex = eventToCheck.displayIndex + isEventRead = when { + eventToCheck == null -> { + // This can happen in case of fast lane Event + false + } + eventToCheck.root?.sender == userId -> true + else -> { + val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst() + ?: return@use + val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.displayIndex + ?: Int.MIN_VALUE + val eventToCheckIndex = eventToCheck.displayIndex - eventToCheckIndex <= readReceiptIndex + eventToCheckIndex <= readReceiptIndex + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index 45fcc5af2d..821a9cba8c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.call.CallSignalingService import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.crypto.CryptoService +import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.group.GroupService @@ -114,6 +115,7 @@ internal class DefaultSession @Inject constructor( private val accountDataService: Lazy, private val _sharedSecretStorageService: Lazy, private val accountService: Lazy, + private val eventService: Lazy, private val defaultIdentityService: DefaultIdentityService, private val integrationManagerService: IntegrationManagerService, private val thirdPartyService: Lazy, @@ -129,6 +131,7 @@ internal class DefaultSession @Inject constructor( FilterService by filterService.get(), PushRuleService by pushRuleService.get(), PushersService by pushersService.get(), + EventService by eventService.get(), TermsService by termsService.get(), InitialSyncProgressService by initialSyncProgressService.get(), SecureStorageService by secureStorageService.get(), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index f10eb67921..e61e4ecd89 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -32,10 +32,11 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.auth.data.sessionId import org.matrix.android.sdk.api.crypto.MXCryptoConfig -import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.accountdata.AccountDataService +import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService +import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.securestorage.SecureStorageService import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService @@ -75,6 +76,7 @@ import org.matrix.android.sdk.internal.network.token.AccessTokenProvider import org.matrix.android.sdk.internal.network.token.HomeserverAccessTokenProvider import org.matrix.android.sdk.internal.session.call.CallEventProcessor import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor +import org.matrix.android.sdk.internal.session.events.DefaultEventService import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService @@ -357,6 +359,9 @@ internal abstract class SessionModule { @Binds abstract fun bindAccountDataService(service: DefaultAccountDataService): AccountDataService + @Binds + abstract fun bindEventService(service: DefaultEventService): EventService + @Binds abstract fun bindSharedSecretStorageService(service: DefaultSharedSecretStorageService): SharedSecretStorageService diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt index 4887351709..a190ff62ac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt @@ -21,9 +21,11 @@ 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.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor +import org.matrix.android.sdk.internal.session.SessionScope import timber.log.Timber import javax.inject.Inject +@SessionScope internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler) : EventInsertLiveProcessor { @@ -51,6 +53,15 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH eventsToPostProcess.add(event) } + fun shouldProcessFastLane(eventType: String): Boolean { + return eventType == EventType.CALL_INVITE + } + + suspend fun processFastLane(event: Event) { + eventsToPostProcess.add(event) + onPostProcess() + } + override suspend fun onPostProcess() { eventsToPostProcess.forEach { dispatchToCallSignalingHandlerIfNeeded(it) @@ -60,7 +71,7 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) { val now = System.currentTimeMillis() - // TODO might check if an invite is not closed (hangup/answsered) in the same event batch? + // TODO might check if an invite is not closed (hangup/answered) in the same event batch? event.roomId ?: return Unit.also { Timber.w("Event with no room id ${event.eventId}") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt index 7e54301f63..8d7e9e819a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt @@ -56,25 +56,25 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa fun onCallEvent(event: Event) { when (event.getClearType()) { - EventType.CALL_ANSWER -> { + EventType.CALL_ANSWER -> { handleCallAnswerEvent(event) } - EventType.CALL_INVITE -> { + EventType.CALL_INVITE -> { handleCallInviteEvent(event) } - EventType.CALL_HANGUP -> { + EventType.CALL_HANGUP -> { handleCallHangupEvent(event) } - EventType.CALL_REJECT -> { + EventType.CALL_REJECT -> { handleCallRejectEvent(event) } - EventType.CALL_CANDIDATES -> { + EventType.CALL_CANDIDATES -> { handleCallCandidatesEvent(event) } EventType.CALL_SELECT_ANSWER -> { handleCallSelectAnswerEvent(event) } - EventType.CALL_NEGOTIATE -> { + EventType.CALL_NEGOTIATE -> { handleCallNegotiateEvent(event) } } @@ -168,6 +168,14 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa return } val content = event.getClearContent().toModel() ?: return + + content.callId ?: return + if (activeCallHandler.getCallWithId(content.callId) != null) { + // Call is already known, maybe due to fast lane. Ignore + Timber.d("Ignoring already known call invite") + return + } + val incomingCall = mxCallFactory.createIncomingCall( roomId = event.roomId, opponentUserId = event.senderId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt new file mode 100644 index 0000000000..d7e9ef2ee0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 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.internal.session.events + +import org.matrix.android.sdk.api.session.events.EventService +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.session.call.CallEventProcessor +import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask +import javax.inject.Inject + +internal class DefaultEventService @Inject constructor( + private val getEventTask: GetEventTask, + private val callEventProcessor: CallEventProcessor +) : EventService { + + override suspend fun getEvent(roomId: String, eventId: String): Event { + val event = getEventTask.execute(GetEventTask.Params(roomId, eventId)) + + // Fast lane to the call event processors: try to make the incoming call ring faster + if (callEventProcessor.shouldProcessFastLane(event.getClearType())) { + callEventProcessor.processFastLane(event) + } + + return event + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index 66b7272360..5133f72932 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -79,9 +79,11 @@ import org.matrix.android.sdk.internal.session.room.tags.DefaultDeleteTagFromRoo import org.matrix.android.sdk.internal.session.room.tags.DeleteTagFromRoomTask import org.matrix.android.sdk.internal.session.room.timeline.DefaultFetchTokenAndPaginateTask import org.matrix.android.sdk.internal.session.room.timeline.DefaultGetContextOfEventTask +import org.matrix.android.sdk.internal.session.room.timeline.DefaultGetEventTask import org.matrix.android.sdk.internal.session.room.timeline.DefaultPaginationTask import org.matrix.android.sdk.internal.session.room.timeline.FetchTokenAndPaginateTask import org.matrix.android.sdk.internal.session.room.timeline.GetContextOfEventTask +import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask import org.matrix.android.sdk.internal.session.room.timeline.PaginationTask import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask @@ -228,4 +230,7 @@ internal abstract class RoomModule { @Binds abstract fun bindPeekRoomTask(task: DefaultPeekRoomTask): PeekRoomTask + + @Binds + abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt index a9b8683a28..f1a20e8057 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt @@ -16,28 +16,49 @@ package org.matrix.android.sdk.internal.session.room.timeline +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.crypto.EventDecryptor +import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject -// TODO Add parent task - -internal class GetEventTask @Inject constructor( - private val roomAPI: RoomAPI, - private val globalErrorReceiver: GlobalErrorReceiver -) : Task { - - internal data class Params( +internal interface GetEventTask : Task { + data class Params( val roomId: String, - val eventId: String + val eventId: String, ) +} - override suspend fun execute(params: Params): Event { - return executeRequest(globalErrorReceiver) { +internal class DefaultGetEventTask @Inject constructor( + private val roomAPI: RoomAPI, + private val globalErrorReceiver: GlobalErrorReceiver, + private val eventDecryptor: EventDecryptor +) : GetEventTask { + + override suspend fun execute(params: GetEventTask.Params): Event { + val event = executeRequest(globalErrorReceiver) { roomAPI.getEvent(params.roomId, params.eventId) } + + // Try to decrypt the Event + if (event.isEncrypted()) { + tryOrNull(message = "Unable to decrypt the event") { + eventDecryptor.decryptEvent(event, "") + } + ?.let { result -> + event.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } + } + + return event } } diff --git a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt index 4d2cbecfe4..7f54be56fb 100755 --- a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt @@ -40,6 +40,9 @@ import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.notifications.SimpleNotifiableEvent import im.vector.app.features.settings.VectorPreferences import im.vector.app.push.fcm.FcmHelper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.pushrules.Action import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event @@ -56,6 +59,8 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { private lateinit var activeSessionHolder: ActiveSessionHolder private lateinit var vectorPreferences: VectorPreferences + private val coroutineScope = CoroutineScope(SupervisorJob()) + // UI handler private val mUIHandler by lazy { Handler(Looper.getMainLooper()) @@ -78,6 +83,11 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { * @param message the message */ override fun onMessageReceived(message: RemoteMessage) { + if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { + Timber.d("## onMessageReceived() %s", message.data.toString()) + } + Timber.d("## onMessageReceived() from FCM with priority %s", message.priority) + // Diagnostic Push if (message.data["event_id"] == PushersManager.TEST_EVENT_ID) { val intent = Intent(NotificationUtils.PUSH_ACTION) @@ -90,14 +100,10 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { return } - if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { - Timber.i("## onMessageReceived() %s", message.data.toString()) - Timber.i("## onMessageReceived() from FCM with priority %s", message.priority) - } mUIHandler.post { if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { // we are in foreground, let the sync do the things? - Timber.v("PUSH received in a foreground state, ignore") + Timber.d("PUSH received in a foreground state, ignore") } else { onMessageReceivedInternal(message.data) } @@ -140,7 +146,9 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { private fun onMessageReceivedInternal(data: Map) { try { if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { - Timber.i("## onMessageReceivedInternal() : $data") + Timber.d("## onMessageReceivedInternal() : $data") + } else { + Timber.d("## onMessageReceivedInternal() : $data") } // update the badge counter @@ -156,9 +164,13 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { val roomId = data["room_id"] if (isEventAlreadyKnown(eventId, roomId)) { - Timber.i("Ignoring push, event already known") + Timber.d("Ignoring push, event already known") } else { - Timber.v("Requesting background sync") + // Try to get the Event content faster + Timber.d("Requesting event in fast lane") + getEventFastLane(session, roomId, eventId) + + Timber.d("Requesting background sync") session.requireBackgroundSync() } } @@ -167,6 +179,32 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { } } + private fun getEventFastLane(session: Session, roomId: String?, eventId: String?) { + roomId?.takeIf { it.isNotEmpty() } ?: return + eventId?.takeIf { it.isNotEmpty() } ?: return + + // If the room is currently displayed, we will not show a notification, so no need to get the Event faster + if (notificationDrawerManager.shouldIgnoreMessageEventInRoom(roomId)) { + return + } + + coroutineScope.launch { + Timber.d("Fast lane: start request") + val event = session.getEvent(roomId, eventId) + + val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event) + + // TODO Test the Event against the push rules + resolvedEvent + ?.also { Timber.d("Fast lane: notify drawer") } + ?.let { + it.isPushGatewayEvent = true + notificationDrawerManager.onNotifiableEventReceived(it) + notificationDrawerManager.refreshNotificationDrawer() + } + } + } + // check if the event was not yet received // a previous catchup might have already retrieved the notified event private fun isEventAlreadyKnown(eventId: String?, roomId: String?): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index a4f617bf5b..d5862f3a85 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType 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.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.internal.crypto.algorithms.olm.OlmDecryptionResult @@ -42,9 +43,10 @@ import javax.inject.Inject * The NotifiableEventResolver is the only aware of session/store, the NotificationDrawerManager has no knowledge of that, * this pattern allow decoupling between the object responsible of displaying notifications and the matrix sdk. */ -class NotifiableEventResolver @Inject constructor(private val stringProvider: StringProvider, - private val noticeEventFormatter: NoticeEventFormatter, - private val displayableEventFormatter: DisplayableEventFormatter) { +class NotifiableEventResolver @Inject constructor( + private val stringProvider: StringProvider, + private val noticeEventFormatter: NoticeEventFormatter, + private val displayableEventFormatter: DisplayableEventFormatter) { // private val eventDisplay = RiotEventDisplay(context) @@ -84,6 +86,28 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St } } + fun resolveInMemoryEvent(session: Session, event: Event): NotifiableEvent? { + if (event.getClearType() != EventType.MESSAGE) return null + + // TODO Ignore message edition + val user = session.getUser(event.senderId!!) ?: return null + + val timelineEvent = TimelineEvent( + root = event, + localId = -1, + eventId = event.eventId!!, + displayIndex = 0, + senderInfo = SenderInfo( + userId = user.userId, + displayName = user.getBestName(), + isUniqueDisplayName = true, + avatarUrl = user.avatarUrl + ) + ) + + return resolveMessageEvent(timelineEvent, session) + } + private fun resolveMessageEvent(event: TimelineEvent, session: Session): 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*/) diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index 36b69a7958..7ac9b28b9a 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -89,7 +89,9 @@ class NotificationDrawerManager @Inject constructor(private val context: Context // If we support multi session, event list should be per userId // Currently only manage single session if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { - Timber.v("%%%%%%%% onNotifiableEventReceived $notifiableEvent") + Timber.d("onNotifiableEventReceived(): $notifiableEvent") + } else { + Timber.d("onNotifiableEventReceived(): is push: ${notifiableEvent.isPushGatewayEvent}") } synchronized(eventList) { val existing = eventList.firstOrNull { it.eventId == notifiableEvent.eventId } @@ -550,7 +552,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context return bitmapLoader.getRoomBitmap(roomAvatarPath) } - private fun shouldIgnoreMessageEventInRoom(roomId: String?): Boolean { + fun shouldIgnoreMessageEventInRoom(roomId: String?): Boolean { return currentRoomId != null && roomId == currentRoomId } From 7f7f90f89e28340c7e02d153d98fbac35a802036 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 Mar 2021 14:11:22 +0100 Subject: [PATCH 03/11] ktlint --- .../android/sdk/internal/session/room/timeline/GetEventTask.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt index f1a20e8057..cbbc54e90d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt @@ -29,7 +29,7 @@ import javax.inject.Inject internal interface GetEventTask : Task { data class Params( val roomId: String, - val eventId: String, + val eventId: String ) } From 4a0a6e9f01d665505851fbb27221239c3223bc38 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 Mar 2021 14:42:15 +0100 Subject: [PATCH 04/11] FastLane: Ignore message edition --- .../org/matrix/android/sdk/api/session/events/model/Event.kt | 4 ++++ .../app/features/notifications/NotifiableEventResolver.kt | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 844e8dbbab..89b873febb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -289,3 +289,7 @@ fun Event.getRelationContent(): RelationDefaultContent? { fun Event.isReply(): Boolean { return getRelationContent()?.inReplyTo?.eventId != null } + +fun Event.isEdition(): Boolean { + return getRelationContent()?.takeIf { it.type == RelationType.REPLACE }?.eventId != null +} diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index d5862f3a85..477534eda5 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt @@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.content.ContentUrlResolver 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.toModel import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent @@ -89,7 +90,9 @@ class NotifiableEventResolver @Inject constructor( fun resolveInMemoryEvent(session: Session, event: Event): NotifiableEvent? { if (event.getClearType() != EventType.MESSAGE) return null - // TODO Ignore message edition + // Ignore message edition + if (event.isEdition()) return null + val user = session.getUser(event.senderId!!) ?: return null val timelineEvent = TimelineEvent( From f0f66cbd0e687f7bdc219a2e1926ae4177fc3db6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 Mar 2021 14:57:32 +0100 Subject: [PATCH 05/11] Add comment --- .../matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt index 3ea32b3bb4..2bb606e921 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt @@ -408,6 +408,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private fun decryptIfNeeded(event: Event, roomId: String) { try { + // Event from sync does not have roomId, so add it to the event first val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "") event.mxDecryptionResult = OlmDecryptionResult( payload = result.clearEvent, From 3a1b8bc33df89b6c89017091d0f27ba7629c6ed3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 Mar 2021 15:30:00 +0100 Subject: [PATCH 06/11] FastLane: handle push rules --- .../sdk/api/pushrules/PushRuleService.kt | 2 + .../notification/DefaultPushRuleService.kt | 20 +++++++++ .../notifications/NotifiableEventResolver.kt | 45 +++++++++++++------ 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt index 4da1662681..d9bf5cfd13 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt @@ -39,6 +39,8 @@ interface PushRuleService { fun removePushRuleListener(listener: PushRuleListener) + fun getActions(event: Event): List + // fun fulfilledBingRule(event: Event, rules: List): PushRule? interface PushRuleListener { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt index e00d2ff26c..8ee230192c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt @@ -16,8 +16,11 @@ package org.matrix.android.sdk.internal.session.notification import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.pushrules.Action +import org.matrix.android.sdk.api.pushrules.ConditionResolver import org.matrix.android.sdk.api.pushrules.PushRuleService import org.matrix.android.sdk.api.pushrules.RuleKind +import org.matrix.android.sdk.api.pushrules.RuleScope import org.matrix.android.sdk.api.pushrules.RuleSetKey import org.matrix.android.sdk.api.pushrules.getActions import org.matrix.android.sdk.api.pushrules.rest.PushRule @@ -45,6 +48,7 @@ internal class DefaultPushRuleService @Inject constructor( private val addPushRuleTask: AddPushRuleTask, private val updatePushRuleActionsTask: UpdatePushRuleActionsTask, private val removePushRuleTask: RemovePushRuleTask, + private val conditionResolver: ConditionResolver, private val taskExecutor: TaskExecutor, @SessionDatabase private val monarchy: Monarchy ) : PushRuleService { @@ -130,6 +134,22 @@ internal class DefaultPushRuleService @Inject constructor( } } + override fun getActions(event: Event): List { + val rules = getPushRules(RuleScope.GLOBAL).getAllRules() + + return fulfilledBingRule(event, rules)?.getActions().orEmpty() + } + + // TODO This is a copy paste, try to have only once this code + private fun fulfilledBingRule(event: Event, rules: List): PushRule? { + return rules.firstOrNull { rule -> + // All conditions must hold true for an event in order to apply the action for the event. + rule.enabled && rule.conditions?.all { + it.asExecutableCondition(rule)?.isSatisfied(event, conditionResolver) ?: false + } ?: false + } + } + // fun processEvents(events: List) { // var hasDoneSomething = false // events.forEach { event -> diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index 477534eda5..494c30aab9 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt @@ -93,22 +93,39 @@ class NotifiableEventResolver @Inject constructor( // Ignore message edition if (event.isEdition()) return null - val user = session.getUser(event.senderId!!) ?: return null + val actions = session.getActions(event) + val notificationAction = actions.toNotificationAction() - val timelineEvent = TimelineEvent( - root = event, - localId = -1, - eventId = event.eventId!!, - displayIndex = 0, - senderInfo = SenderInfo( - userId = user.userId, - displayName = user.getBestName(), - isUniqueDisplayName = true, - avatarUrl = user.avatarUrl - ) - ) + return if (notificationAction.shouldNotify) { + val user = session.getUser(event.senderId!!) ?: return null - return resolveMessageEvent(timelineEvent, session) + val timelineEvent = TimelineEvent( + root = event, + localId = -1, + eventId = event.eventId!!, + displayIndex = 0, + senderInfo = SenderInfo( + userId = user.userId, + displayName = user.getBestName(), + isUniqueDisplayName = true, + avatarUrl = user.avatarUrl + ) + ) + + val notifiableEvent = resolveMessageEvent(timelineEvent, session) + + if (notifiableEvent == null) { + Timber.d("## Failed to resolve event") + // TODO + null + } else { + notifiableEvent.noisy = !notificationAction.soundName.isNullOrBlank() + notifiableEvent + } + } else { + Timber.d("Matched push rule is set to not notify") + null + } } private fun resolveMessageEvent(event: TimelineEvent, session: Session): NotifiableEvent? { From c46f7fed5fc8374af2d2fc1e1c39977db91e7d69 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 Mar 2021 16:58:00 +0100 Subject: [PATCH 07/11] Avoid code duplication --- .../notification/DefaultPushRuleService.kt | 15 ++------ .../notification/ProcessEventForPushTask.kt | 15 ++------ .../session/notification/PushRuleFinder.kt | 35 +++++++++++++++++++ 3 files changed, 39 insertions(+), 26 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt index 8ee230192c..38f6b08b43 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.notification import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.pushrules.Action -import org.matrix.android.sdk.api.pushrules.ConditionResolver import org.matrix.android.sdk.api.pushrules.PushRuleService import org.matrix.android.sdk.api.pushrules.RuleKind import org.matrix.android.sdk.api.pushrules.RuleScope @@ -48,7 +47,7 @@ internal class DefaultPushRuleService @Inject constructor( private val addPushRuleTask: AddPushRuleTask, private val updatePushRuleActionsTask: UpdatePushRuleActionsTask, private val removePushRuleTask: RemovePushRuleTask, - private val conditionResolver: ConditionResolver, + private val pushRuleFinder: PushRuleFinder, private val taskExecutor: TaskExecutor, @SessionDatabase private val monarchy: Monarchy ) : PushRuleService { @@ -137,17 +136,7 @@ internal class DefaultPushRuleService @Inject constructor( override fun getActions(event: Event): List { val rules = getPushRules(RuleScope.GLOBAL).getAllRules() - return fulfilledBingRule(event, rules)?.getActions().orEmpty() - } - - // TODO This is a copy paste, try to have only once this code - private fun fulfilledBingRule(event: Event, rules: List): PushRule? { - return rules.firstOrNull { rule -> - // All conditions must hold true for an event in order to apply the action for the event. - rule.enabled && rule.conditions?.all { - it.asExecutableCondition(rule)?.isSatisfied(event, conditionResolver) ?: false - } ?: false - } + return pushRuleFinder.fulfilledBingRule(event, rules)?.getActions().orEmpty() } // fun processEvents(events: List) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt index 54883b51e6..0ece07fc15 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt @@ -16,9 +16,7 @@ package org.matrix.android.sdk.internal.session.notification -import org.matrix.android.sdk.api.pushrules.ConditionResolver import org.matrix.android.sdk.api.pushrules.rest.PushRule -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.internal.di.UserId import org.matrix.android.sdk.internal.session.sync.model.RoomsSyncResponse @@ -35,7 +33,7 @@ internal interface ProcessEventForPushTask : Task - fulfilledBingRule(event, params.rules)?.let { + pushRuleFinder.fulfilledBingRule(event, params.rules)?.let { Timber.v("[PushRules] Rule $it match for event ${event.eventId}") defaultPushRuleService.dispatchBing(event, it) } @@ -94,13 +92,4 @@ internal class DefaultProcessEventForPushTask @Inject constructor( defaultPushRuleService.dispatchFinish() } - - private fun fulfilledBingRule(event: Event, rules: List): PushRule? { - return rules.firstOrNull { rule -> - // All conditions must hold true for an event in order to apply the action for the event. - rule.enabled && rule.conditions?.all { - it.asExecutableCondition(rule)?.isSatisfied(event, conditionResolver) ?: false - } ?: false - } - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt new file mode 100644 index 0000000000..6e302d373d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 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.internal.session.notification + +import org.matrix.android.sdk.api.pushrules.ConditionResolver +import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.events.model.Event +import javax.inject.Inject + +internal class PushRuleFinder @Inject constructor( + private val conditionResolver: ConditionResolver +) { + fun fulfilledBingRule(event: Event, rules: List): PushRule? { + return rules.firstOrNull { rule -> + // All conditions must hold true for an event in order to apply the action for the event. + rule.enabled && rule.conditions?.all { + it.asExecutableCondition(rule)?.isSatisfied(event, conditionResolver) ?: false + } ?: false + } + } +} From 9a124f7630609e9c08358eb100c5c3c349009b07 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 Mar 2021 17:06:43 +0100 Subject: [PATCH 08/11] Done TODO --- .../vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt index 7f54be56fb..6bb28a9d77 100755 --- a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt @@ -194,7 +194,6 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event) - // TODO Test the Event against the push rules resolvedEvent ?.also { Timber.d("Fast lane: notify drawer") } ?.let { From b89a258fdf84938cd5e7f81139b30fd9e5221193 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 Mar 2021 18:30:54 +0100 Subject: [PATCH 09/11] FastLane: Only is Wifi is detected --- .../sdk/api/session/events/EventService.kt | 6 ++- .../sdk/internal/network/WifiDetector.kt | 45 +++++++++++++++++++ .../session/events/DefaultEventService.kt | 12 ++++- .../fcm/VectorFirebaseMessagingService.kt | 2 +- 4 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt index 41bc0a1a62..3169c8107c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt @@ -23,6 +23,10 @@ interface EventService { /** * Ask the homeserver for an event content. The SDK will try to decrypt it if it is possible * The result will not be stored into cache + * @param onlyOnWifi if true and if WiFi is not available, no request will be done, + * and null will be returned */ - suspend fun getEvent(roomId: String, eventId: String): Event + suspend fun getEvent(roomId: String, + eventId: String, + onlyOnWifi: Boolean): Event? } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt new file mode 100644 index 0000000000..2159627a0b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * 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.internal.network + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.os.Build +import androidx.core.content.getSystemService +import org.matrix.android.sdk.api.extensions.orFalse +import timber.log.Timber +import javax.inject.Inject + +internal class WifiDetector @Inject constructor( + context: Context +) { + private val connectivityManager = context.getSystemService()!! + + fun isConnectedToWifi(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + connectivityManager.activeNetwork + ?.let { connectivityManager.getNetworkCapabilities(it) } + ?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + .orFalse() + } else { + @Suppress("DEPRECATION") + connectivityManager.activeNetworkInfo?.type == ConnectivityManager.TYPE_WIFI + } + .also { Timber.d("isConnected to WiFi: $it") } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt index d7e9ef2ee0..45b772f138 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt @@ -18,16 +18,24 @@ package org.matrix.android.sdk.internal.session.events import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.network.WifiDetector import org.matrix.android.sdk.internal.session.call.CallEventProcessor import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask +import timber.log.Timber import javax.inject.Inject internal class DefaultEventService @Inject constructor( private val getEventTask: GetEventTask, - private val callEventProcessor: CallEventProcessor + private val callEventProcessor: CallEventProcessor, + private val wifiDetector: WifiDetector ) : EventService { - override suspend fun getEvent(roomId: String, eventId: String): Event { + override suspend fun getEvent(roomId: String, eventId: String, onlyOnWifi: Boolean): Event? { + if (onlyOnWifi && !wifiDetector.isConnectedToWifi()) { + Timber.d("No WiFi network, do not get Event") + return null + } + val event = getEventTask.execute(GetEventTask.Params(roomId, eventId)) // Fast lane to the call event processors: try to make the incoming call ring faster diff --git a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt index 6bb28a9d77..d421fc97e8 100755 --- a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt @@ -190,7 +190,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { coroutineScope.launch { Timber.d("Fast lane: start request") - val event = session.getEvent(roomId, eventId) + val event = session.getEvent(roomId, eventId, onlyOnWifi = true)?: return@launch val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event) From dead57b9feb78352b78b6988689f8b7e4490ebba Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Mar 2021 10:47:03 +0100 Subject: [PATCH 10/11] Cleanup --- .../org/matrix/android/sdk/internal/network/WifiDetector.kt | 2 +- .../vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt index 2159627a0b..b1ae4e3b94 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 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. diff --git a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt index d421fc97e8..edde8e69b9 100755 --- a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt @@ -190,7 +190,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { coroutineScope.launch { Timber.d("Fast lane: start request") - val event = session.getEvent(roomId, eventId, onlyOnWifi = true)?: return@launch + val event = session.getEvent(roomId, eventId, onlyOnWifi = true) ?: return@launch val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event) From 7309c1066ce052de65d7d23f0b47d9b62de01051 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 15:07:22 +0200 Subject: [PATCH 11/11] Move WifiDetector to the app side And protect the call to getEvent() --- .../android/sdk/api/session/events/EventService.kt | 5 +---- .../internal/session/events/DefaultEventService.kt | 12 ++---------- .../gplay/push/fcm/VectorFirebaseMessagingService.kt | 11 ++++++++++- .../java/im/vector/app/core/di/VectorComponent.kt | 3 +++ .../java/im/vector/app/core}/network/WifiDetector.kt | 6 +++--- 5 files changed, 19 insertions(+), 18 deletions(-) rename {matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal => vector/src/main/java/im/vector/app/core}/network/WifiDetector.kt (90%) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt index 3169c8107c..297f277497 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt @@ -23,10 +23,7 @@ interface EventService { /** * Ask the homeserver for an event content. The SDK will try to decrypt it if it is possible * The result will not be stored into cache - * @param onlyOnWifi if true and if WiFi is not available, no request will be done, - * and null will be returned */ suspend fun getEvent(roomId: String, - eventId: String, - onlyOnWifi: Boolean): Event? + eventId: String): Event } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt index 45b772f138..d7e9ef2ee0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt @@ -18,24 +18,16 @@ package org.matrix.android.sdk.internal.session.events import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.internal.network.WifiDetector import org.matrix.android.sdk.internal.session.call.CallEventProcessor import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask -import timber.log.Timber import javax.inject.Inject internal class DefaultEventService @Inject constructor( private val getEventTask: GetEventTask, - private val callEventProcessor: CallEventProcessor, - private val wifiDetector: WifiDetector + private val callEventProcessor: CallEventProcessor ) : EventService { - override suspend fun getEvent(roomId: String, eventId: String, onlyOnWifi: Boolean): Event? { - if (onlyOnWifi && !wifiDetector.isConnectedToWifi()) { - Timber.d("No WiFi network, do not get Event") - return null - } - + override suspend fun getEvent(roomId: String, eventId: String): Event { val event = getEventTask.execute(GetEventTask.Params(roomId, eventId)) // Fast lane to the call event processors: try to make the incoming call ring faster diff --git a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt index edde8e69b9..4cefeadb62 100755 --- a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt @@ -31,6 +31,7 @@ import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.vectorComponent +import im.vector.app.core.network.WifiDetector import im.vector.app.core.pushers.PushersManager import im.vector.app.features.badge.BadgeProxy import im.vector.app.features.notifications.NotifiableEventResolver @@ -43,6 +44,7 @@ import im.vector.app.push.fcm.FcmHelper import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.pushrules.Action import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event @@ -58,6 +60,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { private lateinit var pusherManager: PushersManager private lateinit var activeSessionHolder: ActiveSessionHolder private lateinit var vectorPreferences: VectorPreferences + private lateinit var wifiDetector: WifiDetector private val coroutineScope = CoroutineScope(SupervisorJob()) @@ -74,6 +77,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { pusherManager = pusherManager() activeSessionHolder = activeSessionHolder() vectorPreferences = vectorPreferences() + wifiDetector = wifiDetector() } } @@ -188,9 +192,14 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { return } + if (wifiDetector.isConnectedToWifi().not()) { + Timber.d("No WiFi network, do not get Event") + return + } + coroutineScope.launch { Timber.d("Fast lane: start request") - val event = session.getEvent(roomId, eventId, onlyOnWifi = true) ?: return@launch + val event = tryOrNull { session.getEvent(roomId, eventId) } ?: return@launch val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event) diff --git a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt index cae7a2ece6..4b88ff6767 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt @@ -26,6 +26,7 @@ import im.vector.app.EmojiCompatWrapper import im.vector.app.VectorApplication import im.vector.app.core.dialogs.UnrecognizedCertificateDialog import im.vector.app.core.error.ErrorFormatter +import im.vector.app.core.network.WifiDetector import im.vector.app.core.pushers.PushersManager import im.vector.app.core.utils.AssetReader import im.vector.app.core.utils.DimensionConverter @@ -140,6 +141,8 @@ interface VectorComponent { fun vectorPreferences(): VectorPreferences + fun wifiDetector(): WifiDetector + fun vectorFileLogger(): VectorFileLogger fun uiStateRepository(): UiStateRepository diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt b/vector/src/main/java/im/vector/app/core/network/WifiDetector.kt similarity index 90% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt rename to vector/src/main/java/im/vector/app/core/network/WifiDetector.kt index b1ae4e3b94..34b2a7590d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt +++ b/vector/src/main/java/im/vector/app/core/network/WifiDetector.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * Copyright (c) 2021 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.network +package im.vector.app.core.network import android.content.Context import android.net.ConnectivityManager @@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.extensions.orFalse import timber.log.Timber import javax.inject.Inject -internal class WifiDetector @Inject constructor( +class WifiDetector @Inject constructor( context: Context ) { private val connectivityManager = context.getSystemService()!!