From 82f6f358a7987e46a38189e78f96455b94f2b6ad Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 4 Sep 2023 12:50:37 +0200 Subject: [PATCH 1/3] Ensure the sync is started when receiving a Push, to ensure that the encryption loop is running. Fixes notification with endecrypted content (#1178) --- .../android/appnav/LoggedInFlowNode.kt | 5 ++- .../matrix/api/sync/StartSyncReason.kt | 25 ++++++++++++ .../libraries/matrix/api/sync/SyncService.kt | 4 +- .../matrix/impl/sync/RustSyncService.kt | 40 ++++++++++++++----- .../matrix/test/sync/FakeSyncService.kt | 5 ++- .../notifications/NotifiableEventResolver.kt | 12 ++++++ 6 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/StartSyncReason.kt diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 5006218bda..c3a79acb81 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -60,6 +60,7 @@ import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.MAIN_SPACE import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.sync.StartSyncReason import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.push.api.notifications.NotificationDrawerManager import io.element.android.services.appnavstate.api.AppNavigationStateService @@ -124,7 +125,7 @@ class LoggedInFlowNode @AssistedInject constructor( onStop = { //Counterpart startSync is done in observeSyncStateAndNetworkStatus method. coroutineScope.launch { - syncService.stopSync() + syncService.stopSync(StartSyncReason.AppInForeground) } }, onDestroy = { @@ -150,7 +151,7 @@ class LoggedInFlowNode @AssistedInject constructor( .collect { (syncState, networkStatus) -> Timber.d("Sync state: $syncState, network status: $networkStatus") if (syncState != SyncState.Running && networkStatus == NetworkStatus.Online) { - syncService.startSync() + syncService.startSync(StartSyncReason.AppInForeground) } } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/StartSyncReason.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/StartSyncReason.kt new file mode 100644 index 0000000000..4a6b44949e --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/StartSyncReason.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 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 io.element.android.libraries.matrix.api.sync + +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.RoomId + +sealed interface StartSyncReason { + data object AppInForeground : StartSyncReason + data class Notification(val roomId: RoomId, val eventId: EventId) : StartSyncReason +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt index 994b35edc4..03a8402b32 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt @@ -22,12 +22,12 @@ interface SyncService { /** * Tries to start the sync. If already syncing it has no effect. */ - suspend fun startSync(): Result + suspend fun startSync(reason: StartSyncReason): Result /** * Tries to stop the sync. If service is not syncing it has no effect. */ - suspend fun stopSync(): Result + suspend fun stopSync(reason: StartSyncReason): Result /** * Flow of [SyncState]. Will be updated as soon as the current [SyncState] changes. diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt index 932da42afb..b0a9fb31ec 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.impl.sync +import io.element.android.libraries.matrix.api.sync.StartSyncReason import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncState import kotlinx.coroutines.CoroutineScope @@ -25,6 +26,8 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import org.matrix.rustcomponents.sdk.SyncServiceInterface import org.matrix.rustcomponents.sdk.SyncServiceState import timber.log.Timber @@ -33,19 +36,36 @@ class RustSyncService( private val innerSyncService: SyncServiceInterface, sessionCoroutineScope: CoroutineScope ) : SyncService { + private val mutex = Mutex() + private val startSyncReasonSet = mutableSetOf() - override suspend fun startSync() = runCatching { - Timber.i("Start sync") - innerSyncService.start() - }.onFailure { - Timber.d("Start sync failed: $it") + override suspend fun startSync(reason: StartSyncReason): Result { + return mutex.withLock { + startSyncReasonSet.add(reason) + runCatching { + Timber.d("Start sync") + innerSyncService.start() + }.onFailure { + Timber.e("Start sync failed: $it") + } + } } - override suspend fun stopSync() = runCatching { - Timber.i("Stop sync") - innerSyncService.stop() - }.onFailure { - Timber.d("Stop sync failed: $it") + override suspend fun stopSync(reason: StartSyncReason): Result { + return mutex.withLock { + startSyncReasonSet.remove(reason) + if (startSyncReasonSet.isEmpty()) { + runCatching { + Timber.d("Stop sync") + innerSyncService.stop() + }.onFailure { + Timber.e("Stop sync failed: $it") + } + } else { + Timber.d("Stop sync skipped, still $startSyncReasonSet") + Result.success(Unit) + } + } } override val syncState: StateFlow = diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt index 4e618deb9a..87a54a2571 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.test.sync +import io.element.android.libraries.matrix.api.sync.StartSyncReason import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncState import kotlinx.coroutines.flow.MutableStateFlow @@ -29,12 +30,12 @@ class FakeSyncService : SyncService { syncStateFlow.value = SyncState.Error } - override suspend fun startSync(): Result { + override suspend fun startSync(reason: StartSyncReason): Result { syncStateFlow.value = SyncState.Running return Result.success(Unit) } - override suspend fun stopSync(): Result { + override suspend fun stopSync(reason: StartSyncReason): Result { syncStateFlow.value = SyncState.Terminated return Result.success(Unit) } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt index e5af7785db..ce3d09f013 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotifiableEventResolver.kt @@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.notification.NotificationContent import io.element.android.libraries.matrix.api.notification.NotificationData import io.element.android.libraries.matrix.api.room.RoomMembershipState +import io.element.android.libraries.matrix.api.sync.StartSyncReason import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType @@ -44,6 +45,7 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableMess import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.toolbox.api.strings.StringProvider import io.element.android.services.toolbox.api.systemclock.SystemClock +import kotlinx.coroutines.delay import timber.log.Timber import javax.inject.Inject @@ -65,6 +67,14 @@ class NotifiableEventResolver @Inject constructor( // Restore session val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return null val notificationService = client.notificationService() + + // Restart the sync service to ensure that the crypto sync handle the toDevice Events. + client.syncService().startSync(StartSyncReason.Notification(roomId, eventId)) + // Wait for toDevice Event to be processed + // FIXME This delay can be removed when the Rust SDK will handle internal retry to get + // clear notification content. + delay(300) + val notificationData = notificationService.getNotification( userId = sessionId, roomId = roomId, @@ -73,6 +83,8 @@ class NotifiableEventResolver @Inject constructor( Timber.tag(loggerTag.value).e(it, "Unable to resolve event: $eventId.") }.getOrNull() + client.syncService().stopSync(StartSyncReason.Notification(roomId, eventId)) + // TODO this notificationData is not always valid at the moment, sometimes the Rust SDK can't fetch the matching event return notificationData?.asNotifiableEvent(sessionId) } From 948844a901b5fef198cbbf857cfab957ad7fa350 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 4 Sep 2023 12:53:24 +0200 Subject: [PATCH 2/3] changelog. --- changelog.d/1178.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/1178.bugfix diff --git a/changelog.d/1178.bugfix b/changelog.d/1178.bugfix new file mode 100644 index 0000000000..2268ca8561 --- /dev/null +++ b/changelog.d/1178.bugfix @@ -0,0 +1 @@ +Ensure notification for Event from encrypted room get decrypted content. From 4766e0cc8f7b897a39284087da8dad5c4f267a85 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 4 Sep 2023 14:23:03 +0200 Subject: [PATCH 3/3] Fix compilation of sample app. --- .../io/element/android/samples/minimal/RoomListScreen.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index faaccc9b8e..bb90425a48 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -39,6 +39,7 @@ import io.element.android.libraries.eventformatter.impl.StateContentFormatter import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.RoomMembershipObserver +import io.element.android.libraries.matrix.api.sync.StartSyncReason import io.element.android.services.toolbox.impl.strings.AndroidStringProvider import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -109,12 +110,12 @@ class RoomListScreen( DisposableEffect(Unit) { Timber.w("Start sync!") runBlocking { - matrixClient.syncService().startSync() + matrixClient.syncService().startSync(StartSyncReason.AppInForeground) } onDispose { Timber.w("Stop sync!") runBlocking { - matrixClient.syncService().stopSync() + matrixClient.syncService().stopSync(StartSyncReason.AppInForeground) } } }