From c3ad7faa2c951d232471a52d68c870a2bb347400 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Dec 2022 23:02:43 +0000 Subject: [PATCH 01/40] Bump dependency-check-gradle from 7.4.1 to 7.4.3 Bumps dependency-check-gradle from 7.4.1 to 7.4.3. --- updated-dependencies: - dependency-name: org.owasp:dependency-check-gradle dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0f94fc418c..7e5d659c8b 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ buildscript { classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.5.0.2730' classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5' classpath "com.likethesalad.android:stem-plugin:2.2.3" - classpath 'org.owasp:dependency-check-gradle:7.4.1' + classpath 'org.owasp:dependency-check-gradle:7.4.3' classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.20" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' From 56986c3a77b77b19ea9be782543719e0bdffccf5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 2 Jan 2023 21:15:08 +0100 Subject: [PATCH 02/40] Add a way to get the access token from the advances settings. --- library/ui-strings/src/main/res/values/strings.xml | 3 +++ .../settings/VectorSettingsAdvancedSettingsFragment.kt | 9 +++++++++ .../main/res/xml/vector_settings_advanced_settings.xml | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 73cb60bb68..1aad226dcf 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3501,4 +3501,7 @@ sent a video. sent a sticker. created a poll. + + Access Token + Your access token gives full access to your account. Do not share it with anyone. diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt index b6fa997f41..514f2529e9 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsAdvancedSettingsFragment.kt @@ -25,6 +25,7 @@ import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.preference.VectorPreference import im.vector.app.core.preference.VectorPreferenceCategory import im.vector.app.core.preference.VectorSwitchPreference +import im.vector.app.core.utils.copyToClipboard import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.home.NightlyProxy import im.vector.app.features.rageshake.RageShake @@ -64,6 +65,14 @@ class VectorSettingsAdvancedSettingsFragment : override fun bindPref() { setupRageShakeSection() setupNightlySection() + setupDevToolsSection() + } + + private fun setupDevToolsSection() { + findPreference("SETTINGS_ACCESS_TOKEN")?.setOnPreferenceClickListener { + copyToClipboard(requireActivity(), session.sessionParams.credentials.accessToken) + true + } } private fun setupRageShakeSection() { diff --git a/vector/src/main/res/xml/vector_settings_advanced_settings.xml b/vector/src/main/res/xml/vector_settings_advanced_settings.xml index 9260b33162..6399d54cbb 100644 --- a/vector/src/main/res/xml/vector_settings_advanced_settings.xml +++ b/vector/src/main/res/xml/vector_settings_advanced_settings.xml @@ -93,6 +93,12 @@ android:title="@string/settings_key_requests" app:fragment="im.vector.app.features.settings.devtools.KeyRequestsFragment" /> + + Date: Tue, 3 Jan 2023 11:55:32 +0100 Subject: [PATCH 03/40] Avoid launching coroutine for nothing. --- .../sdk/internal/session/StreamEventsManager.kt | 10 ++++------ .../session/sync/handler/room/RoomSyncHandler.kt | 4 +++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt index cfc26045a0..ce34b0430e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/StreamEventsManager.kt @@ -42,14 +42,12 @@ internal class StreamEventsManager @Inject constructor() { listeners.remove(listener) } - fun dispatchLiveEventReceived(event: Event, roomId: String, initialSync: Boolean) { + fun dispatchLiveEventReceived(event: Event, roomId: String) { Timber.v("## dispatchLiveEventReceived ${event.eventId}") coroutineScope.launch { - if (!initialSync) { - listeners.forEach { - tryOrNull { - it.onLiveEvent(roomId, event) - } + listeners.forEach { + tryOrNull { + it.onLiveEvent(roomId, event) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 4001ae2ccf..d67b903bb9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -423,7 +423,9 @@ internal class RoomSyncHandler @Inject constructor( val isInitialSync = insertType == EventInsertType.INITIAL_SYNC eventIds.add(event.eventId) - liveEventService.get().dispatchLiveEventReceived(event, roomId, isInitialSync) + if (!isInitialSync) { + liveEventService.get().dispatchLiveEventReceived(event, roomId) + } if (event.isEncrypted() && !isInitialSync) { try { From 0e504e90145f021a7a1867184cc1a373dc5a5c2d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Jan 2023 11:55:41 +0100 Subject: [PATCH 04/40] Format --- .../session/sync/handler/room/RoomSyncHandler.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index d67b903bb9..ccc3820bb6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -376,8 +376,15 @@ internal class RoomSyncHandler @Inject constructor( roomEntity.chunks.clearWith { it.deleteOnCascade(deleteStateEvents = true, canDeleteRoot = true) } roomTypingUsersHandler.handle(realm, roomId, null) roomChangeMembershipStateDataSource.setMembershipFromSync(roomId, Membership.LEAVE) - roomSummaryUpdater.update(realm, roomId, membership, roomSync.summary, - roomSync.unreadNotifications, roomSync.unreadThreadNotifications, aggregator = aggregator) + roomSummaryUpdater.update( + realm, + roomId, + membership, + roomSync.summary, + roomSync.unreadNotifications, + roomSync.unreadThreadNotifications, + aggregator = aggregator, + ) return roomEntity } From 6f384c799f46fe63c55b71a3590a8b0349861bbd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Jan 2023 15:02:45 +0100 Subject: [PATCH 05/40] Batch insertion of `shouldShareHistory` --- .../internal/crypto/DefaultCryptoService.kt | 2 ++ .../internal/crypto/store/IMXCryptoStore.kt | 3 +++ .../crypto/store/db/CryptoStoreAggregator.kt | 21 +++++++++++++++ .../crypto/store/db/RealmCryptoStore.kt | 26 ++++++++++++++++--- 4 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 7862da1c17..128f06eacb 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -384,6 +384,7 @@ internal class DefaultCryptoService @Inject constructor( } } } + cryptoStore.onSyncWillProcess() } private fun internalStart() { @@ -432,6 +433,7 @@ internal class DefaultCryptoService @Inject constructor( * @param syncResponse the syncResponse */ fun onSyncCompleted(syncResponse: SyncResponse) { + cryptoStore.onSyncCompleted() cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { runCatching { if (syncResponse.deviceLists != null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 21e3342365..a285dbec78 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -48,6 +48,9 @@ import org.matrix.olm.OlmOutboundGroupSession */ internal interface IMXCryptoStore { + fun onSyncWillProcess() + fun onSyncCompleted() + /** * @return the device id */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt new file mode 100644 index 0000000000..529cad6869 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 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.crypto.store.db + +data class CryptoStoreAggregator( + val setShouldShareHistoryData: MutableMap = mutableMapOf() +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 1b52b79746..a98a7ee0ec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -716,9 +716,7 @@ internal class RealmCryptoStore @Inject constructor( override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) { Timber.tag(loggerTag.value) .v("setShouldShareHistory for room $roomId is $shouldShareHistory") - doRealmTransaction(realmConfiguration) { - CryptoRoomEntity.getOrCreate(it, roomId).shouldShareHistory = shouldShareHistory - } + cryptoStoreAggregator?.setShouldShareHistoryData?.put(roomId, shouldShareHistory) } override fun storeSession(olmSessionWrapper: OlmSessionWrapper, deviceKey: String) { @@ -1815,4 +1813,26 @@ internal class RealmCryptoStore @Inject constructor( // Can we do something for WithHeldSessionEntity? } } + + private var cryptoStoreAggregator: CryptoStoreAggregator? = null + override fun onSyncWillProcess() { + if (cryptoStoreAggregator != null) { + Timber.e("cryptoStoreAggregator is not null...") + } + cryptoStoreAggregator = CryptoStoreAggregator() + } + + override fun onSyncCompleted() { + val aggregator = cryptoStoreAggregator ?: return Unit.also { + Timber.e("cryptoStoreAggregator is null...") + } + + doRealmTransaction("onSyncCompleted", realmConfiguration) { realm -> + // setShouldShareHistory + aggregator.setShouldShareHistoryData.map { + CryptoRoomEntity.getOrCreate(realm, it.key).shouldShareHistory = it.value + } + } + cryptoStoreAggregator = null + } } From c1a8bf828b9b5569a2e6498c7eec99a1d2c8597b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Jan 2023 15:15:15 +0100 Subject: [PATCH 06/40] Batch insertion of `shouldEncryptForInvitedMembers` --- .../sdk/internal/crypto/store/db/CryptoStoreAggregator.kt | 3 ++- .../sdk/internal/crypto/store/db/RealmCryptoStore.kt | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt index 529cad6869..165062f17c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt @@ -17,5 +17,6 @@ package org.matrix.android.sdk.internal.crypto.store.db data class CryptoStoreAggregator( - val setShouldShareHistoryData: MutableMap = mutableMapOf() + val setShouldShareHistoryData: MutableMap = mutableMapOf(), + val setShouldEncryptForInvitedMembersData: MutableMap = mutableMapOf(), ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index a98a7ee0ec..3f9e22140e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -708,9 +708,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) { - doRealmTransaction(realmConfiguration) { - CryptoRoomEntity.getOrCreate(it, roomId).shouldEncryptForInvitedMembers = shouldEncryptForInvitedMembers - } + cryptoStoreAggregator?.setShouldEncryptForInvitedMembersData?.put(roomId, shouldEncryptForInvitedMembers) } override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) { @@ -1832,6 +1830,10 @@ internal class RealmCryptoStore @Inject constructor( aggregator.setShouldShareHistoryData.map { CryptoRoomEntity.getOrCreate(realm, it.key).shouldShareHistory = it.value } + // setShouldEncryptForInvitedMembers + aggregator.setShouldEncryptForInvitedMembersData.map { + CryptoRoomEntity.getOrCreate(realm, it.key).shouldEncryptForInvitedMembers = it.value + } } cryptoStoreAggregator = null } From a386a4762c13ac13922382d7ee119de10b67dd67 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Jan 2023 15:18:32 +0100 Subject: [PATCH 07/40] Crypto store: Log realm transactions and the duration --- .../sdk/internal/crypto/store/db/Helper.kt | 12 ++- .../crypto/store/db/RealmCryptoStore.kt | 84 +++++++++---------- 2 files changed, 50 insertions(+), 46 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt index 2d66ce1488..6412df205f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/Helper.kt @@ -20,10 +20,12 @@ import android.util.Base64 import io.realm.Realm import io.realm.RealmConfiguration import io.realm.RealmObject +import timber.log.Timber import java.io.ByteArrayOutputStream import java.io.ObjectOutputStream import java.util.zip.GZIPInputStream import java.util.zip.GZIPOutputStream +import kotlin.system.measureTimeMillis /** * Get realm, invoke the action, close realm, and return the result of the action. @@ -55,10 +57,12 @@ internal fun doRealmQueryAndCopyList(realmConfiguration: Realm /** * Get realm instance, invoke the action in a transaction and close realm. */ -internal fun doRealmTransaction(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { - Realm.getInstance(realmConfiguration).use { realm -> - realm.executeTransaction { action.invoke(it) } - } +internal fun doRealmTransaction(tag: String, realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { + measureTimeMillis { + Realm.getInstance(realmConfiguration).use { realm -> + realm.executeTransaction { action.invoke(it) } + } + }.also { Timber.w("doRealmTransaction for $tag took $it millis") } } internal fun doRealmTransactionAsync(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 3f9e22140e..c62dd7f5c2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -147,7 +147,7 @@ internal class RealmCryptoStore @Inject constructor( init { // Ensure CryptoMetadataEntity is inserted in DB - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("init", realmConfiguration) { realm -> var currentMetadata = realm.where().findFirst() var deleteAll = false @@ -189,7 +189,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun deleteStore() { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("deleteStore", realmConfiguration) { it.deleteAll() } } @@ -218,7 +218,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeDeviceId(deviceId: String) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("storeDeviceId", realmConfiguration) { it.where().findFirst()?.deviceId = deviceId } } @@ -230,7 +230,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveOlmAccount() { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("saveOlmAccount", realmConfiguration) { it.where().findFirst()?.putOlmAccount(olmAccount) } } @@ -248,7 +248,7 @@ internal class RealmCryptoStore @Inject constructor( @Synchronized override fun getOrCreateOlmAccount(): OlmAccount { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("getOrCreateOlmAccount", realmConfiguration) { val metaData = it.where().findFirst() val existing = metaData!!.getOlmAccount() if (existing == null) { @@ -288,7 +288,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeUserDevices(userId: String, devices: Map?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storeUserDevices", realmConfiguration) { realm -> if (devices == null) { Timber.d("Remove user $userId") // Remove the user @@ -331,7 +331,7 @@ internal class RealmCryptoStore @Inject constructor( selfSigningKey: CryptoCrossSigningKey?, userSigningKey: CryptoCrossSigningKey? ) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storeUserCrossSigningKeys", realmConfiguration) { realm -> UserEntity.getOrCreate(realm, userId) .let { userEntity -> if (masterKey == null || selfSigningKey == null) { @@ -480,7 +480,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) { Timber.v("## CRYPTO | *** storePrivateKeysInfo ${msk != null}, ${usk != null}, ${ssk != null}") - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storePrivateKeysInfo", realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignMasterPrivateKey = msk xSignUserPrivateKey = usk @@ -490,7 +490,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("saveBackupRecoveryKey", realmConfiguration) { realm -> realm.where().findFirst()?.apply { keyBackupRecoveryKey = recoveryKey keyBackupRecoveryKeyVersion = version @@ -516,7 +516,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storeMSKPrivateKey(msk: String?) { Timber.v("## CRYPTO | *** storeMSKPrivateKey ${msk != null} ") - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storeMSKPrivateKey", realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignMasterPrivateKey = msk } @@ -525,7 +525,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storeSSKPrivateKey(ssk: String?) { Timber.v("## CRYPTO | *** storeSSKPrivateKey ${ssk != null} ") - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storeSSKPrivateKey", realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignSelfSignedPrivateKey = ssk } @@ -534,7 +534,7 @@ internal class RealmCryptoStore @Inject constructor( override fun storeUSKPrivateKey(usk: String?) { Timber.v("## CRYPTO | *** storeUSKPrivateKey ${usk != null} ") - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storeUSKPrivateKey", realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignUserPrivateKey = usk } @@ -667,7 +667,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun storeRoomAlgorithm(roomId: String, algorithm: String?) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("storeRoomAlgorithm", realmConfiguration) { CryptoRoomEntity.getOrCreate(it, roomId).let { entity -> entity.algorithm = algorithm // store anyway the new algorithm, but mark the room @@ -729,7 +729,7 @@ internal class RealmCryptoStore @Inject constructor( if (sessionIdentifier != null) { val key = OlmSessionEntity.createPrimaryKey(sessionIdentifier, deviceKey) - doRealmTransaction(realmConfiguration) { + doRealmTransaction("storeSession", realmConfiguration) { val realmOlmSession = OlmSessionEntity().apply { primaryKey = key sessionId = sessionIdentifier @@ -786,7 +786,7 @@ internal class RealmCryptoStore @Inject constructor( return } - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("storeInboundGroupSessions", realmConfiguration) { realm -> sessions.forEach { wrapper -> val sessionIdentifier = try { @@ -910,7 +910,7 @@ internal class RealmCryptoStore @Inject constructor( override fun removeInboundGroupSession(sessionId: String, senderKey: String) { val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey) - doRealmTransaction(realmConfiguration) { + doRealmTransaction("removeInboundGroupSession", realmConfiguration) { it.where() .equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key) .findAll() @@ -929,7 +929,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setKeyBackupVersion(keyBackupVersion: String?) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("setKeyBackupVersion", realmConfiguration) { it.where().findFirst()?.backupVersion = keyBackupVersion } } @@ -941,7 +941,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setKeysBackupData(keysBackupData: KeysBackupDataEntity?) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("setKeysBackupData", realmConfiguration) { if (keysBackupData == null) { // Clear the table it.where() @@ -955,7 +955,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun resetBackupMarkers() { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("resetBackupMarkers", realmConfiguration) { it.where() .findAll() .map { inboundGroupSession -> @@ -969,7 +969,7 @@ internal class RealmCryptoStore @Inject constructor( return } - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("markBackupDoneForInboundGroupSessions", realmConfiguration) { realm -> olmInboundGroupSessionWrappers.forEach { olmInboundGroupSessionWrapper -> try { val sessionIdentifier = @@ -1028,13 +1028,13 @@ internal class RealmCryptoStore @Inject constructor( } override fun setGlobalBlacklistUnverifiedDevices(block: Boolean) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("setGlobalBlacklistUnverifiedDevices", realmConfiguration) { it.where().findFirst()?.globalBlacklistUnverifiedDevices = block } } override fun enableKeyGossiping(enable: Boolean) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("enableKeyGossiping", realmConfiguration) { it.where().findFirst()?.globalEnableKeyGossiping = enable } } @@ -1058,13 +1058,13 @@ internal class RealmCryptoStore @Inject constructor( } override fun enableShareKeyOnInvite(enable: Boolean) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("enableShareKeyOnInvite", realmConfiguration) { it.where().findFirst()?.enableKeyForwardingOnInvite = enable } } override fun setDeviceKeysUploaded(uploaded: Boolean) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("setDeviceKeysUploaded", realmConfiguration) { it.where().findFirst()?.deviceKeysSentToServer = uploaded } } @@ -1111,7 +1111,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun blockUnverifiedDevicesInRoom(roomId: String, block: Boolean) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("blockUnverifiedDevicesInRoom", realmConfiguration) { realm -> CryptoRoomEntity.getById(realm, roomId) ?.blacklistUnverifiedDevices = block } @@ -1131,7 +1131,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveDeviceTrackingStatuses(deviceTrackingStatuses: Map) { - doRealmTransaction(realmConfiguration) { + doRealmTransaction("saveDeviceTrackingStatuses", realmConfiguration) { deviceTrackingStatuses .map { entry -> UserEntity.getOrCreate(it, entry.key) @@ -1264,7 +1264,7 @@ internal class RealmCryptoStore @Inject constructor( ): OutgoingKeyRequest { // Insert the request and return the one passed in parameter lateinit var request: OutgoingKeyRequest - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("getOrAddOutgoingRoomKeyRequest", realmConfiguration) { realm -> val existing = realm.where() .equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, requestBody.sessionId) @@ -1302,7 +1302,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun updateOutgoingRoomKeyRequestState(requestId: String, newState: OutgoingRoomKeyRequestState) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("updateOutgoingRoomKeyRequestState", realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId) .findFirst()?.apply { @@ -1316,7 +1316,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun updateOutgoingRoomKeyRequiredIndex(requestId: String, newIndex: Int) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("updateOutgoingRoomKeyRequiredIndex", realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId) .findFirst()?.apply { @@ -1333,7 +1333,7 @@ internal class RealmCryptoStore @Inject constructor( fromDevice: String?, event: Event ) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("updateOutgoingRoomKeyReply", realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.ROOM_ID, roomId) .equalTo(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, sessionId) @@ -1349,7 +1349,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun deleteOutgoingRoomKeyRequest(requestId: String) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("deleteOutgoingRoomKeyRequest", realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.REQUEST_ID, requestId) .findFirst()?.deleteOnCascade() @@ -1357,7 +1357,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun deleteOutgoingRoomKeyRequestInState(state: OutgoingRoomKeyRequestState) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("deleteOutgoingRoomKeyRequestInState", realmConfiguration) { realm -> realm.where() .equalTo(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR, state.name) .findAll() @@ -1493,7 +1493,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setMyCrossSigningInfo(info: MXCrossSigningInfo?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("setMyCrossSigningInfo", realmConfiguration) { realm -> realm.where().findFirst()?.userId?.let { userId -> addOrUpdateCrossSigningInfo(realm, userId, info) } @@ -1501,7 +1501,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setUserKeysAsTrusted(userId: String, trusted: Boolean) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("setUserKeysAsTrusted", realmConfiguration) { realm -> val xInfoEntity = realm.where(CrossSigningInfoEntity::class.java) .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) .findFirst() @@ -1521,7 +1521,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("setDeviceTrust", realmConfiguration) { realm -> realm.where(DeviceInfoEntity::class.java) .equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId)) .findFirst()?.let { deviceInfoEntity -> @@ -1541,7 +1541,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun clearOtherUserTrust() { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("clearOtherUserTrust", realmConfiguration) { realm -> val xInfoEntities = realm.where(CrossSigningInfoEntity::class.java) .findAll() xInfoEntities?.forEach { info -> @@ -1556,7 +1556,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun updateUsersTrust(check: (String) -> Boolean) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("updateUsersTrust", realmConfiguration) { realm -> val xInfoEntities = realm.where(CrossSigningInfoEntity::class.java) .findAll() xInfoEntities?.forEach { xInfoEntity -> @@ -1664,13 +1664,13 @@ internal class RealmCryptoStore @Inject constructor( } override fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("setCrossSigningInfo", realmConfiguration) { realm -> addOrUpdateCrossSigningInfo(realm, userId, info) } } override fun markMyMasterKeyAsLocallyTrusted(trusted: Boolean) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("markMyMasterKeyAsLocallyTrusted", realmConfiguration) { realm -> realm.where().findFirst()?.userId?.let { myUserId -> CrossSigningInfoEntity.get(realm, myUserId)?.getMasterKey()?.let { xInfoEntity -> val level = xInfoEntity.trustLevelEntity @@ -1709,7 +1709,7 @@ internal class RealmCryptoStore @Inject constructor( val roomId = withHeldContent.roomId ?: return val sessionId = withHeldContent.sessionId ?: return if (withHeldContent.algorithm != MXCRYPTO_ALGORITHM_MEGOLM) return - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("addWithHeldMegolmSession", realmConfiguration) { realm -> WithHeldSessionEntity.getOrCreate(realm, roomId, sessionId)?.let { it.code = withHeldContent.code it.senderKey = withHeldContent.senderKey @@ -1741,7 +1741,7 @@ internal class RealmCryptoStore @Inject constructor( deviceIdentityKey: String, chainIndex: Int ) { - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("markedSessionAsShared", realmConfiguration) { realm -> SharedSessionEntity.create( realm = realm, roomId = roomId, @@ -1790,7 +1790,7 @@ internal class RealmCryptoStore @Inject constructor( */ override fun tidyUpDataBase() { val prevWeekTs = clock.epochMillis() - 7 * 24 * 60 * 60 * 1_000 - doRealmTransaction(realmConfiguration) { realm -> + doRealmTransaction("tidyUpDataBase", realmConfiguration) { realm -> // Clean the old ones? realm.where() From f26178fc211efb9f0fa7fe56267401fd2a2a494a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Jan 2023 15:21:03 +0100 Subject: [PATCH 08/40] Avoid useless transaction --- .../sdk/internal/crypto/store/db/CryptoStoreAggregator.kt | 7 ++++++- .../sdk/internal/crypto/store/db/RealmCryptoStore.kt | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt index 165062f17c..687ec95ec3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/CryptoStoreAggregator.kt @@ -19,4 +19,9 @@ package org.matrix.android.sdk.internal.crypto.store.db data class CryptoStoreAggregator( val setShouldShareHistoryData: MutableMap = mutableMapOf(), val setShouldEncryptForInvitedMembersData: MutableMap = mutableMapOf(), -) +) { + fun isEmpty(): Boolean { + return setShouldShareHistoryData.isEmpty() && + setShouldEncryptForInvitedMembersData.isEmpty() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index c62dd7f5c2..949043f2db 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -1824,7 +1824,11 @@ internal class RealmCryptoStore @Inject constructor( val aggregator = cryptoStoreAggregator ?: return Unit.also { Timber.e("cryptoStoreAggregator is null...") } + cryptoStoreAggregator = null + if (aggregator.isEmpty()) { + return + } doRealmTransaction("onSyncCompleted", realmConfiguration) { realm -> // setShouldShareHistory aggregator.setShouldShareHistoryData.map { @@ -1835,6 +1839,5 @@ internal class RealmCryptoStore @Inject constructor( CryptoRoomEntity.getOrCreate(realm, it.key).shouldEncryptForInvitedMembers = it.value } } - cryptoStoreAggregator = null } } From 4c4ef0d73eca19e846c553456d9f9e500276328c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Jan 2023 15:57:39 +0100 Subject: [PATCH 09/40] Batch insertion of user data after downloading keys. --- .../sdk/internal/crypto/DeviceListManager.kt | 14 +- .../internal/crypto/store/IMXCryptoStore.kt | 2 + .../internal/crypto/store/UserDataToStore.kt | 25 ++ .../crypto/store/db/RealmCryptoStore.kt | 236 ++++++++++-------- 4 files changed, 165 insertions(+), 112 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index 7e9e156003..f6e08ce9f7 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.CryptoInfoMapper import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.store.UserDataToStore import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.sync.SyncTokenStore @@ -371,6 +372,8 @@ internal class DeviceListManager @Inject constructor( Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users") } + val userDataToStore = UserDataToStore() + for (userId in filteredUsers) { // al devices = val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) } @@ -404,7 +407,7 @@ internal class DeviceListManager @Inject constructor( } // Update the store // Note that devices which aren't in the response will be removed from the stores - cryptoStore.storeUserDevices(userId, workingCopy) + userDataToStore.userDevices[userId] = workingCopy } val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also { @@ -416,14 +419,11 @@ internal class DeviceListManager @Inject constructor( val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also { Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}") } - cryptoStore.storeUserCrossSigningKeys( - userId, - masterKey, - selfSigningKey, - userSigningKey - ) + userDataToStore.userCrossSigningKeys[userId] = Triple(masterKey, selfSigningKey, userSigningKey) } + cryptoStore.storeUserDataToStore(userDataToStore) + // Update devices trust for these users // dispatchDeviceChange(downloadUsers) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index a285dbec78..92a92ab36a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -583,4 +583,6 @@ internal interface IMXCryptoStore { fun areDeviceKeysUploaded(): Boolean fun tidyUpDataBase() fun getOutgoingRoomKeyRequests(inStates: Set): List + + fun storeUserDataToStore(userDataToStore: UserDataToStore) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt new file mode 100644 index 0000000000..05afc75e5a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 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.crypto.store + +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo + +internal data class UserDataToStore( + val userDevices: MutableMap> = mutableMapOf(), + val userCrossSigningKeys: MutableMap> = mutableMapOf(), +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 949043f2db..83ff960d53 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrappe import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.store.UserDataToStore import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity @@ -289,37 +290,41 @@ internal class RealmCryptoStore @Inject constructor( override fun storeUserDevices(userId: String, devices: Map?) { doRealmTransaction("storeUserDevices", realmConfiguration) { realm -> - if (devices == null) { - Timber.d("Remove user $userId") - // Remove the user - UserEntity.delete(realm, userId) - } else { - val userEntity = UserEntity.getOrCreate(realm, userId) - // First delete the removed devices - val deviceIds = devices.keys - userEntity.devices.toTypedArray().iterator().let { - while (it.hasNext()) { - val deviceInfoEntity = it.next() - if (deviceInfoEntity.deviceId !in deviceIds) { - Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId") - deviceInfoEntity.deleteOnCascade() - } + storeUserDevices(realm, userId, devices) + } + } + + private fun storeUserDevices(realm: Realm, userId: String, devices: Map?) { + if (devices == null) { + Timber.d("Remove user $userId") + // Remove the user + UserEntity.delete(realm, userId) + } else { + val userEntity = UserEntity.getOrCreate(realm, userId) + // First delete the removed devices + val deviceIds = devices.keys + userEntity.devices.toTypedArray().iterator().let { + while (it.hasNext()) { + val deviceInfoEntity = it.next() + if (deviceInfoEntity.deviceId !in deviceIds) { + Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId") + deviceInfoEntity.deleteOnCascade() } } - // Then update existing devices or add new one - devices.values.forEach { cryptoDeviceInfo -> - val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId } - if (existingDeviceInfoEntity == null) { - // Add the device - Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId") - val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo) - newEntity.firstTimeSeenLocalTs = clock.epochMillis() - userEntity.devices.add(newEntity) - } else { - // Update the device - Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId") - CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo) - } + } + // Then update existing devices or add new one + devices.values.forEach { cryptoDeviceInfo -> + val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId } + if (existingDeviceInfoEntity == null) { + // Add the device + Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId") + val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo) + newEntity.firstTimeSeenLocalTs = clock.epochMillis() + userEntity.devices.add(newEntity) + } else { + // Update the device + Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId") + CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo) } } } @@ -332,85 +337,95 @@ internal class RealmCryptoStore @Inject constructor( userSigningKey: CryptoCrossSigningKey? ) { doRealmTransaction("storeUserCrossSigningKeys", realmConfiguration) { realm -> - UserEntity.getOrCreate(realm, userId) - .let { userEntity -> - if (masterKey == null || selfSigningKey == null) { - // The user has disabled cross signing? - userEntity.crossSigningInfoEntity?.deleteOnCascade() - userEntity.crossSigningInfoEntity = null - } else { - var shouldResetMyDevicesLocalTrust = false - CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> - // What should we do if we detect a change of the keys? - val existingMaster = signingInfo.getMasterKey() - if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingMaster, masterKey) - } else { - Timber.d("## CrossSigning MSK change for $userId") - val keyEntity = crossSigningKeysMapper.map(masterKey) - signingInfo.setMasterKey(keyEntity) - if (userId == this.userId) { - shouldResetMyDevicesLocalTrust = true - // my msk has changed! clear my private key - // Could we have some race here? e.g I am the one that did change the keys - // could i get this update to early and clear the private keys? - // -> initializeCrossSigning is guarding for that by storing all at once - realm.where().findFirst()?.apply { - xSignMasterPrivateKey = null - } + storeUserCrossSigningKeys(realm, userId, masterKey, selfSigningKey, userSigningKey) + } + } + + private fun storeUserCrossSigningKeys( + realm: Realm, + userId: String, + masterKey: CryptoCrossSigningKey?, + selfSigningKey: CryptoCrossSigningKey?, + userSigningKey: CryptoCrossSigningKey? + ) { + UserEntity.getOrCreate(realm, userId) + .let { userEntity -> + if (masterKey == null || selfSigningKey == null) { + // The user has disabled cross signing? + userEntity.crossSigningInfoEntity?.deleteOnCascade() + userEntity.crossSigningInfoEntity = null + } else { + var shouldResetMyDevicesLocalTrust = false + CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> + // What should we do if we detect a change of the keys? + val existingMaster = signingInfo.getMasterKey() + if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingMaster, masterKey) + } else { + Timber.d("## CrossSigning MSK change for $userId") + val keyEntity = crossSigningKeysMapper.map(masterKey) + signingInfo.setMasterKey(keyEntity) + if (userId == this.userId) { + shouldResetMyDevicesLocalTrust = true + // my msk has changed! clear my private key + // Could we have some race here? e.g I am the one that did change the keys + // could i get this update to early and clear the private keys? + // -> initializeCrossSigning is guarding for that by storing all at once + realm.where().findFirst()?.apply { + xSignMasterPrivateKey = null } } - - val existingSelfSigned = signingInfo.getSelfSignedKey() - if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingSelfSigned, selfSigningKey) - } else { - Timber.d("## CrossSigning SSK change for $userId") - val keyEntity = crossSigningKeysMapper.map(selfSigningKey) - signingInfo.setSelfSignedKey(keyEntity) - if (userId == this.userId) { - shouldResetMyDevicesLocalTrust = true - // my ssk has changed! clear my private key - realm.where().findFirst()?.apply { - xSignSelfSignedPrivateKey = null - } - } - } - - // Only for me - if (userSigningKey != null) { - val existingUSK = signingInfo.getUserSigningKey() - if (existingUSK != null && existingUSK.publicKeyBase64 == userSigningKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingUSK, userSigningKey) - } else { - Timber.d("## CrossSigning USK change for $userId") - val keyEntity = crossSigningKeysMapper.map(userSigningKey) - signingInfo.setUserSignedKey(keyEntity) - if (userId == this.userId) { - shouldResetMyDevicesLocalTrust = true - // my usk has changed! clear my private key - realm.where().findFirst()?.apply { - xSignUserPrivateKey = null - } - } - } - } - - // When my cross signing keys are reset, we consider clearing all existing device trust - if (shouldResetMyDevicesLocalTrust) { - realm.where() - .equalTo(UserEntityFields.USER_ID, this.userId) - .findFirst() - ?.devices?.forEach { - it?.trustLevelEntity?.crossSignedVerified = false - it?.trustLevelEntity?.locallyVerified = it.deviceId == deviceId - } - } - userEntity.crossSigningInfoEntity = signingInfo } + + val existingSelfSigned = signingInfo.getSelfSignedKey() + if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingSelfSigned, selfSigningKey) + } else { + Timber.d("## CrossSigning SSK change for $userId") + val keyEntity = crossSigningKeysMapper.map(selfSigningKey) + signingInfo.setSelfSignedKey(keyEntity) + if (userId == this.userId) { + shouldResetMyDevicesLocalTrust = true + // my ssk has changed! clear my private key + realm.where().findFirst()?.apply { + xSignSelfSignedPrivateKey = null + } + } + } + + // Only for me + if (userSigningKey != null) { + val existingUSK = signingInfo.getUserSigningKey() + if (existingUSK != null && existingUSK.publicKeyBase64 == userSigningKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingUSK, userSigningKey) + } else { + Timber.d("## CrossSigning USK change for $userId") + val keyEntity = crossSigningKeysMapper.map(userSigningKey) + signingInfo.setUserSignedKey(keyEntity) + if (userId == this.userId) { + shouldResetMyDevicesLocalTrust = true + // my usk has changed! clear my private key + realm.where().findFirst()?.apply { + xSignUserPrivateKey = null + } + } + } + } + + // When my cross signing keys are reset, we consider clearing all existing device trust + if (shouldResetMyDevicesLocalTrust) { + realm.where() + .equalTo(UserEntityFields.USER_ID, this.userId) + .findFirst() + ?.devices?.forEach { + it?.trustLevelEntity?.crossSignedVerified = false + it?.trustLevelEntity?.locallyVerified = it.deviceId == deviceId + } + } + userEntity.crossSigningInfoEntity = signingInfo } } - } + } } override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? { @@ -1831,13 +1846,24 @@ internal class RealmCryptoStore @Inject constructor( } doRealmTransaction("onSyncCompleted", realmConfiguration) { realm -> // setShouldShareHistory - aggregator.setShouldShareHistoryData.map { + aggregator.setShouldShareHistoryData.forEach { CryptoRoomEntity.getOrCreate(realm, it.key).shouldShareHistory = it.value } // setShouldEncryptForInvitedMembers - aggregator.setShouldEncryptForInvitedMembersData.map { + aggregator.setShouldEncryptForInvitedMembersData.forEach { CryptoRoomEntity.getOrCreate(realm, it.key).shouldEncryptForInvitedMembers = it.value } } } + + override fun storeUserDataToStore(userDataToStore: UserDataToStore) { + doRealmTransaction("storeUserDataToStore", realmConfiguration) { realm -> + userDataToStore.userDevices.forEach { + storeUserDevices(realm, it.key, it.value) + } + userDataToStore.userCrossSigningKeys.forEach { + storeUserCrossSigningKeys(realm, it.key, it.value.first, it.value.second, it.value.third) + } + } + } } From 02e7157206404ea07c2544ac6e590c30ae201f98 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Jan 2023 16:16:17 +0100 Subject: [PATCH 10/40] Introduce CryptoCrossSigningKeys container --- .../crosssigning/CryptoCrossSigningKeys.kt | 26 ++++++++++++++ .../sdk/internal/crypto/DeviceListManager.kt | 7 +++- .../internal/crypto/store/IMXCryptoStore.kt | 5 ++- .../internal/crypto/store/UserDataToStore.kt | 4 +-- .../crypto/store/db/RealmCryptoStore.kt | 36 +++++++++---------- 5 files changed, 52 insertions(+), 26 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKeys.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKeys.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKeys.kt new file mode 100644 index 0000000000..e0a422b54b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKeys.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2023 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.crypto.crosssigning + +/** + * Container for the three cross signing keys: master, self signing and user signing. + */ +data class CryptoCrossSigningKeys( + val masterKey: CryptoCrossSigningKey?, + val selfSigningKey: CryptoCrossSigningKey?, + val userSigningKey: CryptoCrossSigningKey?, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index f6e08ce9f7..8ced0864d0 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.extensions.measureMetric import org.matrix.android.sdk.api.metrics.DownloadDeviceKeysMetricsPlugin +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap @@ -419,7 +420,11 @@ internal class DeviceListManager @Inject constructor( val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also { Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}") } - userDataToStore.userCrossSigningKeys[userId] = Triple(masterKey, selfSigningKey, userSigningKey) + userDataToStore.userCrossSigningKeys[userId] = CryptoCrossSigningKeys( + masterKey = masterKey, + selfSigningKey = selfSigningKey, + userSigningKey = userSigningKey + ) } cryptoStore.storeUserDataToStore(userDataToStore) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 92a92ab36a..1a7f4a5320 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo @@ -235,9 +236,7 @@ internal interface IMXCryptoStore { fun storeUserCrossSigningKeys( userId: String, - masterKey: CryptoCrossSigningKey?, - selfSigningKey: CryptoCrossSigningKey?, - userSigningKey: CryptoCrossSigningKey? + cryptoCrossSigningKeys: CryptoCrossSigningKeys ) /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt index 05afc75e5a..d4b8308650 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt @@ -16,10 +16,10 @@ package org.matrix.android.sdk.internal.crypto.store -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo internal data class UserDataToStore( val userDevices: MutableMap> = mutableMapOf(), - val userCrossSigningKeys: MutableMap> = mutableMapOf(), + val userCrossSigningKeys: MutableMap = mutableMapOf(), ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 83ff960d53..06ae2c00b1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -33,7 +33,7 @@ import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey +import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo @@ -332,25 +332,21 @@ internal class RealmCryptoStore @Inject constructor( override fun storeUserCrossSigningKeys( userId: String, - masterKey: CryptoCrossSigningKey?, - selfSigningKey: CryptoCrossSigningKey?, - userSigningKey: CryptoCrossSigningKey? + cryptoCrossSigningKeys: CryptoCrossSigningKeys, ) { doRealmTransaction("storeUserCrossSigningKeys", realmConfiguration) { realm -> - storeUserCrossSigningKeys(realm, userId, masterKey, selfSigningKey, userSigningKey) + storeUserCrossSigningKeys(realm, userId, cryptoCrossSigningKeys) } } private fun storeUserCrossSigningKeys( realm: Realm, userId: String, - masterKey: CryptoCrossSigningKey?, - selfSigningKey: CryptoCrossSigningKey?, - userSigningKey: CryptoCrossSigningKey? + keys: CryptoCrossSigningKeys, ) { UserEntity.getOrCreate(realm, userId) .let { userEntity -> - if (masterKey == null || selfSigningKey == null) { + if (keys.masterKey == null || keys.selfSigningKey == null) { // The user has disabled cross signing? userEntity.crossSigningInfoEntity?.deleteOnCascade() userEntity.crossSigningInfoEntity = null @@ -359,11 +355,11 @@ internal class RealmCryptoStore @Inject constructor( CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> // What should we do if we detect a change of the keys? val existingMaster = signingInfo.getMasterKey() - if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingMaster, masterKey) + if (existingMaster != null && existingMaster.publicKeyBase64 == keys.masterKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingMaster, keys.masterKey) } else { Timber.d("## CrossSigning MSK change for $userId") - val keyEntity = crossSigningKeysMapper.map(masterKey) + val keyEntity = crossSigningKeysMapper.map(keys.masterKey) signingInfo.setMasterKey(keyEntity) if (userId == this.userId) { shouldResetMyDevicesLocalTrust = true @@ -378,11 +374,11 @@ internal class RealmCryptoStore @Inject constructor( } val existingSelfSigned = signingInfo.getSelfSignedKey() - if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingSelfSigned, selfSigningKey) + if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == keys.selfSigningKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingSelfSigned, keys.selfSigningKey) } else { Timber.d("## CrossSigning SSK change for $userId") - val keyEntity = crossSigningKeysMapper.map(selfSigningKey) + val keyEntity = crossSigningKeysMapper.map(keys.selfSigningKey) signingInfo.setSelfSignedKey(keyEntity) if (userId == this.userId) { shouldResetMyDevicesLocalTrust = true @@ -394,13 +390,13 @@ internal class RealmCryptoStore @Inject constructor( } // Only for me - if (userSigningKey != null) { + if (keys.userSigningKey != null) { val existingUSK = signingInfo.getUserSigningKey() - if (existingUSK != null && existingUSK.publicKeyBase64 == userSigningKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingUSK, userSigningKey) + if (existingUSK != null && existingUSK.publicKeyBase64 == keys.userSigningKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingUSK, keys.userSigningKey) } else { Timber.d("## CrossSigning USK change for $userId") - val keyEntity = crossSigningKeysMapper.map(userSigningKey) + val keyEntity = crossSigningKeysMapper.map(keys.userSigningKey) signingInfo.setUserSignedKey(keyEntity) if (userId == this.userId) { shouldResetMyDevicesLocalTrust = true @@ -1862,7 +1858,7 @@ internal class RealmCryptoStore @Inject constructor( storeUserDevices(realm, it.key, it.value) } userDataToStore.userCrossSigningKeys.forEach { - storeUserCrossSigningKeys(realm, it.key, it.value.first, it.value.second, it.value.third) + storeUserCrossSigningKeys(realm, it.key, it.value) } } } From 06f3c1101042500973c1999f8b9ae0f97a42332e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 3 Jan 2023 16:43:09 +0100 Subject: [PATCH 11/40] Changelog --- changelog.d/7879.bugfix | 1 + .../matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 changelog.d/7879.bugfix diff --git a/changelog.d/7879.bugfix b/changelog.d/7879.bugfix new file mode 100644 index 0000000000..be828ec2cc --- /dev/null +++ b/changelog.d/7879.bugfix @@ -0,0 +1 @@ +Reduce number of crypto database transactions when handling the sync response diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 1a7f4a5320..4ffd93875b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo From 437b93cc18751777e92f7a249dd67c93460382e0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 4 Jan 2023 11:35:04 +0100 Subject: [PATCH 12/40] Add some doc --- .../internal/crypto/store/IMXCryptoStore.kt | 19 +++++++++++++++++++ .../internal/crypto/store/UserDataToStore.kt | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 4ffd93875b..a74a7f2906 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -48,7 +48,19 @@ import org.matrix.olm.OlmOutboundGroupSession */ internal interface IMXCryptoStore { + /** + * Notify the store that a sync response treatment is starting. + * Impacted methods: + * - [setShouldShareHistory] + * - [setShouldEncryptForInvitedMembers] + * @See [onSyncCompleted] to notify that the treatment is over. + */ fun onSyncWillProcess() + + /** + * Notify the store that the sync treatment response is finished. + * The store will save all aggregated data. + */ fun onSyncCompleted() /** @@ -291,6 +303,9 @@ internal interface IMXCryptoStore { fun shouldEncryptForInvitedMembers(roomId: String): Boolean + /** + * The data is not stored immediately, this MUST be call during a sync response treatment. + */ fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) fun shouldShareHistory(roomId: String): Boolean @@ -298,6 +313,7 @@ internal interface IMXCryptoStore { /** * Sets a boolean flag that will determine whether or not room history (existing inbound sessions) * will be shared to new user invites. + * The data is not stored immediately, this MUST be call during a sync response treatment. * * @param roomId the room id * @param shouldShareHistory The boolean flag @@ -582,5 +598,8 @@ internal interface IMXCryptoStore { fun tidyUpDataBase() fun getOutgoingRoomKeyRequests(inStates: Set): List + /** + * Store a bunch of data related to the users. @See [UserDataToStore]. + */ fun storeUserDataToStore(userDataToStore: UserDataToStore) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt index d4b8308650..89cbe4e826 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt @@ -20,6 +20,12 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigning import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo internal data class UserDataToStore( + /** + * Map of userId -> (Map of deviceId -> [CryptoDeviceInfo]). + */ val userDevices: MutableMap> = mutableMapOf(), + /** + * Map of userId -> [CryptoCrossSigningKeys]. + */ val userCrossSigningKeys: MutableMap = mutableMapOf(), ) From e903dac22480525c97ba7ccc90cde41eeb811491 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 29 Dec 2022 15:40:59 +0100 Subject: [PATCH 13/40] Adding changelog entry --- changelog.d/7864.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7864.wip diff --git a/changelog.d/7864.wip b/changelog.d/7864.wip new file mode 100644 index 0000000000..4dc55708be --- /dev/null +++ b/changelog.d/7864.wip @@ -0,0 +1 @@ +[Poll] Render active polls list of a room From cba960fbd782c91ab7de7e84c3635d3374ce3de2 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 29 Dec 2022 16:05:52 +0100 Subject: [PATCH 14/40] Adding new entry "Poll history" into room profile screen --- library/ui-strings/src/main/res/values/strings.xml | 1 + .../app/features/roomprofile/RoomProfileController.kt | 7 +++++++ .../vector/app/features/roomprofile/RoomProfileFragment.kt | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 73cb60bb68..d7e56fcbb0 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -2335,6 +2335,7 @@ "One person" "%1$d people" + Poll history Uploads Leave Room Leave diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt index eb43a345f2..87f5657fc8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt @@ -56,6 +56,7 @@ class RoomProfileController @Inject constructor( fun onMemberListClicked() fun onBannedMemberListClicked() fun onNotificationsClicked() + fun onPollHistoryClicked() fun onUploadsClicked() fun createShortcut() fun onSettingsClicked() @@ -263,6 +264,12 @@ class RoomProfileController @Inject constructor( action = { callback?.onBannedMemberListClicked() } ) } + buildProfileAction( + id = "poll_history", + title = stringProvider.getString(R.string.room_profile_section_more_polls), + icon = R.drawable.ic_attachment_poll, + action = { callback?.onPollHistoryClicked() } + ) buildProfileAction( id = "uploads", title = stringProvider.getString(R.string.room_profile_section_more_uploads), diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index f4394111ab..a06fdf9152 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -269,6 +269,10 @@ class RoomProfileFragment : roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomNotificationSettings) } + override fun onPollHistoryClicked() { + // TODO navigate to new screen + } + override fun onUploadsClicked() { roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomUploads) } From 7436c2e1f5a60ed8507c9df53b49bec014182e07 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 29 Dec 2022 16:41:42 +0100 Subject: [PATCH 15/40] Navigate to new empty screen --- .../app/core/di/MavericksViewModelModule.kt | 6 ++ .../roomprofile/RoomProfileActivity.kt | 6 ++ .../roomprofile/RoomProfileFragment.kt | 2 +- .../roomprofile/RoomProfileSharedAction.kt | 1 + .../roomprofile/polls/RoomPollsAction.kt | 21 +++++++ .../roomprofile/polls/RoomPollsFragment.kt | 61 +++++++++++++++++++ .../roomprofile/polls/RoomPollsViewEvent.kt | 21 +++++++ .../roomprofile/polls/RoomPollsViewModel.kt | 41 +++++++++++++ .../roomprofile/polls/RoomPollsViewState.kt | 27 ++++++++ .../main/res/layout/fragment_room_polls.xml | 30 +++++++++ 10 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt create mode 100644 vector/src/main/res/layout/fragment_room_polls.xml diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index d22ab51e7a..911bbfa4a3 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -84,6 +84,7 @@ import im.vector.app.features.roomprofile.banned.RoomBannedMemberListViewModel import im.vector.app.features.roomprofile.members.RoomMemberListViewModel import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsViewModel import im.vector.app.features.roomprofile.permissions.RoomPermissionsViewModel +import im.vector.app.features.roomprofile.polls.RoomPollsViewModel import im.vector.app.features.roomprofile.settings.RoomSettingsViewModel import im.vector.app.features.roomprofile.settings.joinrule.advanced.RoomJoinRuleChooseRestrictedViewModel import im.vector.app.features.roomprofile.uploads.RoomUploadsViewModel @@ -697,4 +698,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(SetLinkViewModel::class) fun setLinkViewModelFactory(factory: SetLinkViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(RoomPollsViewModel::class) + fun roomPollsViewModelFactory(factory: RoomPollsViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt index 526d676dee..3c37c92650 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt @@ -36,6 +36,7 @@ import im.vector.app.features.roomprofile.banned.RoomBannedMemberListFragment import im.vector.app.features.roomprofile.members.RoomMemberListFragment import im.vector.app.features.roomprofile.notifications.RoomNotificationSettingsFragment import im.vector.app.features.roomprofile.permissions.RoomPermissionsFragment +import im.vector.app.features.roomprofile.polls.RoomPollsFragment import im.vector.app.features.roomprofile.settings.RoomSettingsFragment import im.vector.app.features.roomprofile.uploads.RoomUploadsFragment import im.vector.lib.core.utils.compat.getParcelableCompat @@ -98,6 +99,7 @@ class RoomProfileActivity : RoomProfileSharedAction.OpenRoomSettings -> openRoomSettings() RoomProfileSharedAction.OpenRoomAliasesSettings -> openRoomAlias() RoomProfileSharedAction.OpenRoomPermissionsSettings -> openRoomPermissions() + RoomProfileSharedAction.OpenRoomPolls -> openRoomPolls() RoomProfileSharedAction.OpenRoomUploads -> openRoomUploads() RoomProfileSharedAction.OpenBannedRoomMembers -> openBannedRoomMembers() RoomProfileSharedAction.OpenRoomNotificationSettings -> openRoomNotificationSettings() @@ -126,6 +128,10 @@ class RoomProfileActivity : finish() } + private fun openRoomPolls() { + addFragmentToBackstack(views.simpleFragmentContainer, RoomPollsFragment::class.java, roomProfileArgs) + } + private fun openRoomUploads() { addFragmentToBackstack(views.simpleFragmentContainer, RoomUploadsFragment::class.java, roomProfileArgs) } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt index a06fdf9152..51885dbf39 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileFragment.kt @@ -270,7 +270,7 @@ class RoomProfileFragment : } override fun onPollHistoryClicked() { - // TODO navigate to new screen + roomProfileSharedActionViewModel.post(RoomProfileSharedAction.OpenRoomPolls) } override fun onUploadsClicked() { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt index 7d62bb86a1..b243ceb206 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileSharedAction.kt @@ -25,6 +25,7 @@ sealed class RoomProfileSharedAction : VectorSharedAction { object OpenRoomSettings : RoomProfileSharedAction() object OpenRoomAliasesSettings : RoomProfileSharedAction() object OpenRoomPermissionsSettings : RoomProfileSharedAction() + object OpenRoomPolls : RoomProfileSharedAction() object OpenRoomUploads : RoomProfileSharedAction() object OpenRoomMembers : RoomProfileSharedAction() object OpenBannedRoomMembers : RoomProfileSharedAction() diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt new file mode 100644 index 0000000000..895079ec90 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020 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 im.vector.app.features.roomprofile.polls + +import im.vector.app.core.platform.VectorViewModelAction + +sealed class RoomPollsAction : VectorViewModelAction diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt new file mode 100644 index 0000000000..a33dfdf93d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020 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 im.vector.app.features.roomprofile.polls + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.args +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentRoomPollsBinding +import im.vector.app.features.roomprofile.RoomProfileArgs + +@AndroidEntryPoint +class RoomPollsFragment : VectorBaseFragment() { + + private val roomProfileArgs: RoomProfileArgs by args() + + private val viewModel: RoomPollsViewModel by fragmentViewModel() + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsBinding { + return FragmentRoomPollsBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupToolbar() + setupTabs() + } + + private fun setupToolbar() { + setupToolbar(views.roomPollsToolbar) + .allowBack() + } + + private fun setupTabs() { + // TODO + } + + override fun invalidate() = withState(viewModel) { + // TODO + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt new file mode 100644 index 0000000000..3896abf84c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2020 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 im.vector.app.features.roomprofile.polls + +import im.vector.app.core.platform.VectorViewEvents + +sealed class RoomPollsViewEvent : VectorViewEvents diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt new file mode 100644 index 0000000000..cfda5af0cf --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020 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 im.vector.app.features.roomprofile.polls + +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.platform.VectorViewModel + +class RoomPollsViewModel @AssistedInject constructor( + @Assisted initialState: RoomPollsViewState, +) : VectorViewModel(initialState) { + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: RoomPollsViewState): RoomPollsViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + override fun handle(action: RoomPollsAction) { + // do nothing for now + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt new file mode 100644 index 0000000000..0233ddb693 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.roomprofile.polls + +import com.airbnb.mvrx.MavericksState +import im.vector.app.features.roomprofile.RoomProfileArgs + +data class RoomPollsViewState( + val roomId: String, +) : MavericksState { + + constructor(roomProfileArgs: RoomProfileArgs) : this(roomId = roomProfileArgs.roomId) +} diff --git a/vector/src/main/res/layout/fragment_room_polls.xml b/vector/src/main/res/layout/fragment_room_polls.xml new file mode 100644 index 0000000000..b1b91e9a18 --- /dev/null +++ b/vector/src/main/res/layout/fragment_room_polls.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + From 10133bd20ffe12d059049d906079f3e74e57990f Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 29 Dec 2022 17:46:07 +0100 Subject: [PATCH 16/40] Setup tab layout when landing on the room polls screen --- .../src/main/res/values/strings.xml | 1 + .../roomprofile/polls/RoomPollsAction.kt | 2 +- .../roomprofile/polls/RoomPollsFragment.kt | 24 +++++++++---- .../polls/RoomPollsPagerAdapter.kt | 36 +++++++++++++++++++ .../roomprofile/polls/RoomPollsViewEvent.kt | 2 +- .../roomprofile/polls/RoomPollsViewModel.kt | 2 +- .../polls/active/RoomActivePollsFragment.kt | 35 ++++++++++++++++++ .../main/res/layout/fragment_room_polls.xml | 32 ++++++++++++++--- .../res/layout/fragment_room_polls_list.xml | 7 ++++ 9 files changed, 128 insertions(+), 13 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt create mode 100644 vector/src/main/res/layout/fragment_room_polls_list.xml diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index d7e56fcbb0..e0f2f7b288 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3191,6 +3191,7 @@ Voters see results as soon as they have voted Closed poll Results are only revealed when you end the poll + Active polls Share location diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt index 895079ec90..9d87f13f14 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * Copyright (c) 2022 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. diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt index a33dfdf93d..7617da71df 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * Copyright (c) 2022 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. @@ -22,8 +22,9 @@ import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel -import com.airbnb.mvrx.withState +import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollsBinding import im.vector.app.features.roomprofile.RoomProfileArgs @@ -35,6 +36,8 @@ class RoomPollsFragment : VectorBaseFragment() { private val viewModel: RoomPollsViewModel by fragmentViewModel() + private var tabLayoutMediator: TabLayoutMediator? = null + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsBinding { return FragmentRoomPollsBinding.inflate(inflater, container, false) } @@ -46,16 +49,25 @@ class RoomPollsFragment : VectorBaseFragment() { setupTabs() } + override fun onDestroyView() { + views.roomPollsViewPager.adapter = null + tabLayoutMediator?.detach() + tabLayoutMediator = null + super.onDestroyView() + } + private fun setupToolbar() { setupToolbar(views.roomPollsToolbar) .allowBack() } private fun setupTabs() { - // TODO - } + views.roomPollsViewPager.adapter = RoomPollsPagerAdapter(this) - override fun invalidate() = withState(viewModel) { - // TODO + tabLayoutMediator = TabLayoutMediator(views.roomPollsTabs, views.roomPollsViewPager) { tab, position -> + when (position) { + 0 -> tab.text = getString(R.string.active_polls) + } + }.also { it.attach() } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt new file mode 100644 index 0000000000..5472782079 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsPagerAdapter.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.roomprofile.polls + +import androidx.fragment.app.Fragment +import androidx.viewpager2.adapter.FragmentStateAdapter +import im.vector.app.features.roomprofile.polls.active.RoomActivePollsFragment + +class RoomPollsPagerAdapter( + private val fragment: Fragment +) : FragmentStateAdapter(fragment) { + + override fun getItemCount() = 1 + + override fun createFragment(position: Int): Fragment { + return instantiateFragment(RoomActivePollsFragment::class.java.name) + } + + private fun instantiateFragment(fragmentName: String): Fragment { + return fragment.childFragmentManager.fragmentFactory.instantiate(fragment.requireContext().classLoader, fragmentName) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt index 3896abf84c..231123563a 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewEvent.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * Copyright (c) 2022 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. diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index cfda5af0cf..42278ff976 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * Copyright (c) 2022 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. diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt new file mode 100644 index 0000000000..230da49b22 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.roomprofile.polls.active + +import android.view.LayoutInflater +import android.view.ViewGroup +import com.airbnb.mvrx.parentFragmentViewModel +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentRoomPollsListBinding +import im.vector.app.features.roomprofile.polls.RoomPollsViewModel + +@AndroidEntryPoint +class RoomActivePollsFragment : VectorBaseFragment() { + + private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class) + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding { + return FragmentRoomPollsListBinding.inflate(inflater, container, false) + } +} diff --git a/vector/src/main/res/layout/fragment_room_polls.xml b/vector/src/main/res/layout/fragment_room_polls.xml index b1b91e9a18..96a94cd9c5 100644 --- a/vector/src/main/res/layout/fragment_room_polls.xml +++ b/vector/src/main/res/layout/fragment_room_polls.xml @@ -1,7 +1,6 @@ @@ -21,10 +20,35 @@ + + + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/roomPollsTabs" /> diff --git a/vector/src/main/res/layout/fragment_room_polls_list.xml b/vector/src/main/res/layout/fragment_room_polls_list.xml new file mode 100644 index 0000000000..1d672087a9 --- /dev/null +++ b/vector/src/main/res/layout/fragment_room_polls_list.xml @@ -0,0 +1,7 @@ + + + + + From 9f97579f9dc4c928fd8f95449fde3a10b8f77040 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 10:07:50 +0100 Subject: [PATCH 17/40] Epoxy model for active poll --- .../polls/active/ActivePollItem.kt | 51 +++++++++++++++++++ .../src/main/res/layout/item_poll_active.xml | 42 +++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt create mode 100644 vector/src/main/res/layout/item_poll_active.xml diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt new file mode 100644 index 0000000000..2a927653f1 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.roomprofile.polls.active + +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.onClick + +@EpoxyModelClass +abstract class ActivePollItem : VectorEpoxyModel(R.layout.item_poll_active) { + + @EpoxyAttribute + lateinit var formattedDate: String + + @EpoxyAttribute + lateinit var title: String + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var clickListener: ClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.view.onClick(clickListener) + holder.date.text = formattedDate + holder.title.text = title + } + + class Holder : VectorEpoxyHolder() { + val date by bind(R.id.pollActiveDate) + val title by bind(R.id.pollActiveTitle) + } +} diff --git a/vector/src/main/res/layout/item_poll_active.xml b/vector/src/main/res/layout/item_poll_active.xml new file mode 100644 index 0000000000..8cf6c9e576 --- /dev/null +++ b/vector/src/main/res/layout/item_poll_active.xml @@ -0,0 +1,42 @@ + + + + + + + + + + From 7b63f891c33ffbb28d42491ac4ac48b89aef0a4e Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 10:42:42 +0100 Subject: [PATCH 18/40] Epoxy controller to render active poll list --- .../features/roomprofile/polls/PollSummary.kt | 25 ++++++++++ .../roomprofile/polls/RoomPollsViewState.kt | 1 + .../polls/active/RoomActivePollsController.kt | 49 +++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt new file mode 100644 index 0000000000..3eb45c6144 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/PollSummary.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.roomprofile.polls + +sealed interface PollSummary { + data class ActivePoll( + val id: String, + val creationTimestamp: Long, + val title: String, + ) : PollSummary +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt index 0233ddb693..74794c99b1 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewState.kt @@ -21,6 +21,7 @@ import im.vector.app.features.roomprofile.RoomProfileArgs data class RoomPollsViewState( val roomId: String, + val polls: List = emptyList(), ) : MavericksState { constructor(roomProfileArgs: RoomProfileArgs) : this(roomId = roomProfileArgs.roomId) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt new file mode 100644 index 0000000000..9f26569e58 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.roomprofile.polls.active + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.core.date.DateFormatKind +import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.features.roomprofile.polls.PollSummary +import javax.inject.Inject + +class RoomActivePollsController @Inject constructor( + val dateFormatter: VectorDateFormatter, +) : TypedEpoxyController>() { + + interface Listener { + fun onPollClicked(pollId: String) + } + + var listener: Listener? = null + + override fun buildModels(data: List?) { + if (data == null) return + + val host = this + data.forEach { poll -> + activePollItem { + formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.EDIT_HISTORY_HEADER)) + title(poll.title) + clickListener { + host.listener?.onPollClicked(poll.id) + } + } + } + } +} From f20513eb16602a7b05d02231ca784215f0a28551 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 10:56:44 +0100 Subject: [PATCH 19/40] Render the active polls list on fragment --- .../polls/active/RoomActivePollsFragment.kt | 45 ++++++++++++++++++- .../res/layout/fragment_room_polls_list.xml | 13 ++++++ .../src/main/res/layout/item_poll_active.xml | 1 + 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt index 230da49b22..10518d4b18 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt @@ -16,20 +16,63 @@ package im.vector.app.features.roomprofile.polls.active +import android.os.Bundle import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.extensions.cleanup import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollsListBinding +import im.vector.app.features.roomprofile.polls.PollSummary import im.vector.app.features.roomprofile.polls.RoomPollsViewModel +import javax.inject.Inject @AndroidEntryPoint -class RoomActivePollsFragment : VectorBaseFragment() { +class RoomActivePollsFragment : + VectorBaseFragment(), + RoomActivePollsController.Listener { + + @Inject + lateinit var roomActivePollsController: RoomActivePollsController private val viewModel: RoomPollsViewModel by parentFragmentViewModel(RoomPollsViewModel::class) override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentRoomPollsListBinding { return FragmentRoomPollsListBinding.inflate(inflater, container, false) } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupList() + } + + private fun setupList() { + roomActivePollsController.listener = this + views.activePollsList.adapter = roomActivePollsController.adapter + } + + override fun onDestroyView() { + cleanUpList() + super.onDestroyView() + } + + private fun cleanUpList() { + views.activePollsList.cleanup() + roomActivePollsController.listener = null + } + + override fun invalidate() = withState(viewModel) { viewState -> + renderList(viewState.polls.filterIsInstance(PollSummary.ActivePoll::class.java)) + } + + private fun renderList(polls: List) { + roomActivePollsController.setData(polls) + } + + override fun onPollClicked(pollId: String) { + // TODO navigate to details + } } diff --git a/vector/src/main/res/layout/fragment_room_polls_list.xml b/vector/src/main/res/layout/fragment_room_polls_list.xml index 1d672087a9..39add6a298 100644 --- a/vector/src/main/res/layout/fragment_room_polls_list.xml +++ b/vector/src/main/res/layout/fragment_room_polls_list.xml @@ -1,7 +1,20 @@ + + diff --git a/vector/src/main/res/layout/item_poll_active.xml b/vector/src/main/res/layout/item_poll_active.xml index 8cf6c9e576..2db6450f94 100644 --- a/vector/src/main/res/layout/item_poll_active.xml +++ b/vector/src/main/res/layout/item_poll_active.xml @@ -10,6 +10,7 @@ android:id="@+id/pollActiveDate" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_marginTop="32dp" android:textAppearance="@style/TextAppearance.Vector.Caption" android:textColor="?vctr_content_tertiary" app:layout_constraintStart_toStartOf="parent" From 77d3b7da04b0e8e2cd9ff6ab44392565675eef11 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 11:40:41 +0100 Subject: [PATCH 20/40] Fix missing id in Epoxy model --- .../roomprofile/polls/active/RoomActivePollsController.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt index 9f26569e58..fd32ae51d0 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt @@ -38,6 +38,7 @@ class RoomActivePollsController @Inject constructor( val host = this data.forEach { poll -> activePollItem { + id(poll.id) formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.EDIT_HISTORY_HEADER)) title(poll.title) clickListener { From 8de86e74807339f5a02abcea6fd5c1452ffc68c9 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 11:59:48 +0100 Subject: [PATCH 21/40] Render mocked data get from use case --- .../roomprofile/polls/GetPollsUseCase.kt | 65 +++++++++++++++++++ .../roomprofile/polls/RoomPollsAction.kt | 4 +- .../roomprofile/polls/RoomPollsFilter.kt | 22 +++++++ .../roomprofile/polls/RoomPollsViewModel.kt | 23 ++++++- .../polls/active/RoomActivePollsFragment.kt | 10 ++- .../res/layout/fragment_room_polls_list.xml | 3 +- 6 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilter.kt diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt new file mode 100644 index 0000000000..fa8c6d0aa6 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.roomprofile.polls + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +class GetPollsUseCase @Inject constructor() { + + fun execute(filter: RoomPollsFilter): Flow> { + // TODO unmock and add unit tests + return when (filter) { + RoomPollsFilter.ACTIVE -> getActivePolls() + RoomPollsFilter.ENDED -> emptyFlow() + }.map { it.sortedByDescending { poll -> poll.creationTimestamp } } + } + + private fun getActivePolls(): Flow> { + return flowOf( + listOf( + PollSummary.ActivePoll( + id = "id1", + // 2022/06/28 UTC+1 + creationTimestamp = 1656367200000, + title = "Which charity would you like to support?" + ), + PollSummary.ActivePoll( + id = "id2", + // 2022/06/26 UTC+1 + creationTimestamp = 1656194400000, + title = "Which sport should the pupils do this year?" + ), + PollSummary.ActivePoll( + id = "id3", + // 2022/06/24 UTC+1 + creationTimestamp = 1656021600000, + title = "What type of food should we have at the party?" + ), + PollSummary.ActivePoll( + id = "id4", + // 2022/06/22 UTC+1 + creationTimestamp = 1655848800000, + title = "What film should we show at the end of the year party?" + ), + ) + ) + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt index 9d87f13f14..27753b6d16 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt @@ -18,4 +18,6 @@ package im.vector.app.features.roomprofile.polls import im.vector.app.core.platform.VectorViewModelAction -sealed class RoomPollsAction : VectorViewModelAction +sealed interface RoomPollsAction : VectorViewModelAction { + data class SetFilter(val filter: RoomPollsFilter) : RoomPollsAction +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilter.kt new file mode 100644 index 0000000000..68ebb13f7d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilter.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.roomprofile.polls + +enum class RoomPollsFilter { + ACTIVE, + ENDED, +} diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index 42278ff976..27ba6679d8 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -23,9 +23,13 @@ import dagger.assisted.AssistedInject import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach class RoomPollsViewModel @AssistedInject constructor( @Assisted initialState: RoomPollsViewState, + private val getPollsUseCase: GetPollsUseCase, ) : VectorViewModel(initialState) { @AssistedFactory @@ -35,7 +39,24 @@ class RoomPollsViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + private var pollsCollectionJob: Job? = null + + // TODO add unit tests override fun handle(action: RoomPollsAction) { - // do nothing for now + when (action) { + is RoomPollsAction.SetFilter -> handleSetFilter(action.filter) + } + } + + override fun onCleared() { + pollsCollectionJob = null + super.onCleared() + } + + private fun handleSetFilter(filter: RoomPollsFilter) { + pollsCollectionJob?.cancel() + pollsCollectionJob = getPollsUseCase.execute(filter) + .onEach { setState { copy(polls = it) } } + .launchIn(viewModelScope) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt index 10518d4b18..ed851a045d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt @@ -24,9 +24,12 @@ import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint import im.vector.app.core.extensions.cleanup +import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollsListBinding import im.vector.app.features.roomprofile.polls.PollSummary +import im.vector.app.features.roomprofile.polls.RoomPollsAction +import im.vector.app.features.roomprofile.polls.RoomPollsFilter import im.vector.app.features.roomprofile.polls.RoomPollsViewModel import javax.inject.Inject @@ -51,7 +54,7 @@ class RoomActivePollsFragment : private fun setupList() { roomActivePollsController.listener = this - views.activePollsList.adapter = roomActivePollsController.adapter + views.activePollsList.configureWith(roomActivePollsController) } override fun onDestroyView() { @@ -64,6 +67,11 @@ class RoomActivePollsFragment : roomActivePollsController.listener = null } + override fun onResume() { + super.onResume() + viewModel.handle(RoomPollsAction.SetFilter(RoomPollsFilter.ACTIVE)) + } + override fun invalidate() = withState(viewModel) { viewState -> renderList(viewState.polls.filterIsInstance(PollSummary.ActivePoll::class.java)) } diff --git a/vector/src/main/res/layout/fragment_room_polls_list.xml b/vector/src/main/res/layout/fragment_room_polls_list.xml index 39add6a298..1aa6625ae5 100644 --- a/vector/src/main/res/layout/fragment_room_polls_list.xml +++ b/vector/src/main/res/layout/fragment_room_polls_list.xml @@ -8,8 +8,9 @@ Date: Fri, 30 Dec 2022 12:08:55 +0100 Subject: [PATCH 22/40] Allow access of poll history only in debug variant --- .../roomprofile/RoomProfileController.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt index 87f5657fc8..30bd6c7ed3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt @@ -18,6 +18,7 @@ package im.vector.app.features.roomprofile import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.epoxy.expandableTextItem import im.vector.app.core.epoxy.profiles.buildProfileAction @@ -264,12 +265,15 @@ class RoomProfileController @Inject constructor( action = { callback?.onBannedMemberListClicked() } ) } - buildProfileAction( - id = "poll_history", - title = stringProvider.getString(R.string.room_profile_section_more_polls), - icon = R.drawable.ic_attachment_poll, - action = { callback?.onPollHistoryClicked() } - ) + if (BuildConfig.DEBUG) { + // WIP, will be in release when related screens will be finished + buildProfileAction( + id = "poll_history", + title = stringProvider.getString(R.string.room_profile_section_more_polls), + icon = R.drawable.ic_attachment_poll, + action = { callback?.onPollHistoryClicked() } + ) + } buildProfileAction( id = "uploads", title = stringProvider.getString(R.string.room_profile_section_more_uploads), From 71b7edc6f2c095f476fda596cdc6cbaf2b3ca159 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 12:12:57 +0100 Subject: [PATCH 23/40] Adding debug log --- .../roomprofile/polls/active/RoomActivePollsFragment.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt index ed851a045d..b4e812a49e 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt @@ -31,6 +31,7 @@ import im.vector.app.features.roomprofile.polls.PollSummary import im.vector.app.features.roomprofile.polls.RoomPollsAction import im.vector.app.features.roomprofile.polls.RoomPollsFilter import im.vector.app.features.roomprofile.polls.RoomPollsViewModel +import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint @@ -82,5 +83,6 @@ class RoomActivePollsFragment : override fun onPollClicked(pollId: String) { // TODO navigate to details + Timber.d("poll with id $pollId clicked") } } From bc985aa1ef310e5fe45cbaefc4b4cab71fa71dc8 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 14:19:50 +0100 Subject: [PATCH 24/40] Adding unit tests for ViewModel --- .../roomprofile/polls/RoomPollsViewModel.kt | 5 +- .../polls/RoomPollsViewModelTest.kt | 76 +++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index 27ba6679d8..7def7a508d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.roomprofile.polls +import androidx.annotation.VisibleForTesting import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -39,9 +40,9 @@ class RoomPollsViewModel @AssistedInject constructor( companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - private var pollsCollectionJob: Job? = null + @VisibleForTesting + var pollsCollectionJob: Job? = null - // TODO add unit tests override fun handle(action: RoomPollsAction) { when (action) { is RoomPollsAction.SetFilter -> handleSetFilter(action.filter) diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt new file mode 100644 index 0000000000..54b2a60d55 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2022 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 im.vector.app.features.roomprofile.polls + +import com.airbnb.mvrx.test.MavericksTestRule +import im.vector.app.test.test +import im.vector.app.test.testDispatcher +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.flow.flowOf +import org.amshove.kluent.shouldNotBeNull +import org.junit.Rule +import org.junit.Test + +private const val ROOM_ID = "room-id" + +class RoomPollsViewModelTest { + + @get:Rule + val mavericksTestRule = MavericksTestRule(testDispatcher = testDispatcher) + + private val fakeGetPollsUseCase = mockk() + private val initialState = RoomPollsViewState(ROOM_ID) + + private fun createViewModel(): RoomPollsViewModel { + return RoomPollsViewModel( + initialState = initialState, + getPollsUseCase = fakeGetPollsUseCase, + ) + } + + @Test + fun `given SetFilter action when handle then useCase is called with given filter and viewState is updated`() { + // Given + val filter = RoomPollsFilter.ACTIVE + val action = RoomPollsAction.SetFilter(filter = filter) + val polls = listOf(givenAPollSummary()) + every { fakeGetPollsUseCase.execute(any()) } returns flowOf(polls) + val viewModel = createViewModel() + val expectedViewState = initialState.copy(polls = polls) + + // When + val viewModelTest = viewModel.test() + viewModel.pollsCollectionJob = null + viewModel.handle(action) + + // Then + viewModelTest + .assertLatestState(expectedViewState) + .finish() + viewModel.pollsCollectionJob.shouldNotBeNull() + verify { + viewModel.pollsCollectionJob?.cancel() + fakeGetPollsUseCase.execute(filter) + } + } + + private fun givenAPollSummary(): PollSummary { + return mockk() + } +} From e0b77936c1e4325387a1e06e4d4fef34bd7acfdd Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 14:27:11 +0100 Subject: [PATCH 25/40] Changing the date format --- .../roomprofile/polls/active/RoomActivePollsController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt index fd32ae51d0..2fab886282 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt @@ -39,7 +39,7 @@ class RoomActivePollsController @Inject constructor( data.forEach { poll -> activePollItem { id(poll.id) - formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.EDIT_HISTORY_HEADER)) + formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)) title(poll.title) clickListener { host.listener?.onPollClicked(poll.id) From bd9c53a96c96aa870d3edcb8db44a52f6afb2d42 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 14:57:37 +0100 Subject: [PATCH 26/40] Show message when list is empty --- .../src/main/res/values/strings.xml | 3 ++- .../roomprofile/polls/RoomPollsFragment.kt | 2 +- .../polls/active/RoomActivePollsFragment.kt | 8 +++++-- .../res/layout/fragment_room_polls_list.xml | 23 ++++++++++++++++++- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index e0f2f7b288..43507e60ce 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3191,7 +3191,8 @@ Voters see results as soon as they have voted Closed poll Results are only revealed when you end the poll - Active polls + Active polls + There are no active polls in this room Share location diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt index 7617da71df..5c150f4391 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFragment.kt @@ -66,7 +66,7 @@ class RoomPollsFragment : VectorBaseFragment() { tabLayoutMediator = TabLayoutMediator(views.roomPollsTabs, views.roomPollsViewPager) { tab, position -> when (position) { - 0 -> tab.text = getString(R.string.active_polls) + 0 -> tab.text = getString(R.string.room_polls_active) } }.also { it.attach() } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt index b4e812a49e..4cc318edf9 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt @@ -20,9 +20,11 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible import com.airbnb.mvrx.parentFragmentViewModel import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.R import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.configureWith import im.vector.app.core.platform.VectorBaseFragment @@ -55,7 +57,8 @@ class RoomActivePollsFragment : private fun setupList() { roomActivePollsController.listener = this - views.activePollsList.configureWith(roomActivePollsController) + views.roomPollsList.configureWith(roomActivePollsController) + views.roomPollsEmptyTitle.text = getString(R.string.room_polls_active_no_item) } override fun onDestroyView() { @@ -64,7 +67,7 @@ class RoomActivePollsFragment : } private fun cleanUpList() { - views.activePollsList.cleanup() + views.roomPollsList.cleanup() roomActivePollsController.listener = null } @@ -79,6 +82,7 @@ class RoomActivePollsFragment : private fun renderList(polls: List) { roomActivePollsController.setData(polls) + views.roomPollsEmptyTitle.isVisible = polls.isEmpty() } override fun onPollClicked(pollId: String) { diff --git a/vector/src/main/res/layout/fragment_room_polls_list.xml b/vector/src/main/res/layout/fragment_room_polls_list.xml index 1aa6625ae5..6949bb0c67 100644 --- a/vector/src/main/res/layout/fragment_room_polls_list.xml +++ b/vector/src/main/res/layout/fragment_room_polls_list.xml @@ -6,7 +6,7 @@ android:layout_height="match_parent"> + + + From 6c0c5e506408d242ce1792ddd833c41836e573c3 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 30 Dec 2022 15:12:12 +0100 Subject: [PATCH 27/40] Rename poll item layout to be more generic --- .../app/features/roomprofile/polls/active/ActivePollItem.kt | 2 +- vector/src/main/res/layout/fragment_room_polls_list.xml | 2 +- .../src/main/res/layout/{item_poll_active.xml => item_poll.xml} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename vector/src/main/res/layout/{item_poll_active.xml => item_poll.xml} (100%) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt index 2a927653f1..35b1ecd6e1 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/ActivePollItem.kt @@ -26,7 +26,7 @@ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.onClick @EpoxyModelClass -abstract class ActivePollItem : VectorEpoxyModel(R.layout.item_poll_active) { +abstract class ActivePollItem : VectorEpoxyModel(R.layout.item_poll) { @EpoxyAttribute lateinit var formattedDate: String diff --git a/vector/src/main/res/layout/fragment_room_polls_list.xml b/vector/src/main/res/layout/fragment_room_polls_list.xml index 6949bb0c67..8eb27e5e00 100644 --- a/vector/src/main/res/layout/fragment_room_polls_list.xml +++ b/vector/src/main/res/layout/fragment_room_polls_list.xml @@ -15,7 +15,7 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:itemCount="5" - tools:listitem="@layout/item_poll_active" /> + tools:listitem="@layout/item_poll" /> Date: Fri, 30 Dec 2022 15:48:14 +0100 Subject: [PATCH 28/40] Replace usage of colorAccent --- vector/src/main/res/layout/fragment_room_polls.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/src/main/res/layout/fragment_room_polls.xml b/vector/src/main/res/layout/fragment_room_polls.xml index 96a94cd9c5..dcaf483251 100644 --- a/vector/src/main/res/layout/fragment_room_polls.xml +++ b/vector/src/main/res/layout/fragment_room_polls.xml @@ -24,22 +24,22 @@ android:id="@+id/roomPollsTabs" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="20dp" android:layout_marginHorizontal="10dp" + android:layout_marginTop="20dp" android:background="?android:colorBackground" app:layout_constraintBottom_toTopOf="@id/roomPollsViewPager" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/appBarLayout" + app:tabGravity="start" app:tabIndicatorFullWidth="false" app:tabIndicatorHeight="1dp" - app:tabPaddingBottom="-15dp" - app:tabTextColor="?vctr_content_primary" - app:tabSelectedTextColor="?colorAccent" - app:tabTextAppearance="@style/TextAppearance.Vector.Body" - app:tabGravity="start" app:tabMaxWidth="0dp" - app:tabMode="scrollable" /> + app:tabMode="scrollable" + app:tabPaddingBottom="-15dp" + app:tabSelectedTextColor="?colorSecondary" + app:tabTextAppearance="@style/TextAppearance.Vector.Body" + app:tabTextColor="?vctr_content_primary" /> Date: Fri, 30 Dec 2022 16:45:28 +0100 Subject: [PATCH 29/40] Ignore missing ContentDescription --- .../roomprofile/polls/active/RoomActivePollsController.kt | 4 +++- vector/src/main/res/layout/item_poll.xml | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt index 2fab886282..dc14ec366d 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt @@ -33,7 +33,9 @@ class RoomActivePollsController @Inject constructor( var listener: Listener? = null override fun buildModels(data: List?) { - if (data == null) return + if (data.isNullOrEmpty()) { + return + } val host = this data.forEach { poll -> diff --git a/vector/src/main/res/layout/item_poll.xml b/vector/src/main/res/layout/item_poll.xml index 2db6450f94..05e9b3a62a 100644 --- a/vector/src/main/res/layout/item_poll.xml +++ b/vector/src/main/res/layout/item_poll.xml @@ -25,7 +25,8 @@ android:src="@drawable/ic_attachment_poll" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/pollActiveDate" - app:tint="?vctr_content_secondary" /> + app:tint="?vctr_content_secondary" + tools:ignore="ContentDescription" /> Date: Thu, 5 Jan 2023 09:48:25 +0100 Subject: [PATCH 30/40] Rename fun --- .../org/matrix/android/sdk/internal/crypto/DeviceListManager.kt | 2 +- .../matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt | 2 +- .../android/sdk/internal/crypto/store/db/RealmCryptoStore.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index 8ced0864d0..bb8acbf4b0 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -427,7 +427,7 @@ internal class DeviceListManager @Inject constructor( ) } - cryptoStore.storeUserDataToStore(userDataToStore) + cryptoStore.storeData(userDataToStore) // Update devices trust for these users // dispatchDeviceChange(downloadUsers) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index a74a7f2906..0c20ae79aa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -601,5 +601,5 @@ internal interface IMXCryptoStore { /** * Store a bunch of data related to the users. @See [UserDataToStore]. */ - fun storeUserDataToStore(userDataToStore: UserDataToStore) + fun storeData(userDataToStore: UserDataToStore) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 06ae2c00b1..615df6a5b9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -1852,7 +1852,7 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun storeUserDataToStore(userDataToStore: UserDataToStore) { + override fun storeData(userDataToStore: UserDataToStore) { doRealmTransaction("storeUserDataToStore", realmConfiguration) { realm -> userDataToStore.userDevices.forEach { storeUserDevices(realm, it.key, it.value) From 30940cb9370587b50fe2b5f05652032eaf0c1062 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Jan 2023 09:53:12 +0100 Subject: [PATCH 31/40] Rename `UserCrossSigningKeys` to `UserIdentity` --- ...ptoCrossSigningKeys.kt => UserIdentity.kt} | 2 +- .../sdk/internal/crypto/DeviceListManager.kt | 4 +- .../internal/crypto/store/IMXCryptoStore.kt | 9 +++-- .../internal/crypto/store/UserDataToStore.kt | 6 +-- .../crypto/store/db/RealmCryptoStore.kt | 40 +++++++++---------- 5 files changed, 32 insertions(+), 29 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/{CryptoCrossSigningKeys.kt => UserIdentity.kt} (96%) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKeys.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserIdentity.kt similarity index 96% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKeys.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserIdentity.kt index e0a422b54b..071db7f902 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/CryptoCrossSigningKeys.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/crosssigning/UserIdentity.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.session.crypto.crosssigning /** * Container for the three cross signing keys: master, self signing and user signing. */ -data class CryptoCrossSigningKeys( +data class UserIdentity( val masterKey: CryptoCrossSigningKey?, val selfSigningKey: CryptoCrossSigningKey?, val userSigningKey: CryptoCrossSigningKey?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index bb8acbf4b0..364d77f7ac 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -24,8 +24,8 @@ import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.extensions.measureMetric import org.matrix.android.sdk.api.metrics.DownloadDeviceKeysMetricsPlugin -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel +import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.CryptoInfoMapper @@ -420,7 +420,7 @@ internal class DeviceListManager @Inject constructor( val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also { Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}") } - userDataToStore.userCrossSigningKeys[userId] = CryptoCrossSigningKeys( + userDataToStore.userIdentities[userId] = UserIdentity( masterKey = masterKey, selfSigningKey = selfSigningKey, userSigningKey = userSigningKey diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 0c20ae79aa..10158c7a4d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -22,9 +22,9 @@ import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo @@ -245,9 +245,12 @@ internal interface IMXCryptoStore { */ fun storeUserDevices(userId: String, devices: Map?) - fun storeUserCrossSigningKeys( + /** + * Store the cross signing keys for the user userId. + */ + fun storeUserIdentity( userId: String, - cryptoCrossSigningKeys: CryptoCrossSigningKeys + userIdentity: UserIdentity ) /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt index 89cbe4e826..914ce4704e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/UserDataToStore.kt @@ -16,7 +16,7 @@ package org.matrix.android.sdk.internal.crypto.store -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys +import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo internal data class UserDataToStore( @@ -25,7 +25,7 @@ internal data class UserDataToStore( */ val userDevices: MutableMap> = mutableMapOf(), /** - * Map of userId -> [CryptoCrossSigningKeys]. + * Map of userId -> [UserIdentity]. */ - val userCrossSigningKeys: MutableMap = mutableMapOf(), + val userIdentities: MutableMap = mutableMapOf(), ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 615df6a5b9..1be88249eb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -33,9 +33,9 @@ import org.matrix.android.sdk.api.session.crypto.GlobalCryptoConfig import org.matrix.android.sdk.api.session.crypto.NewSessionListener import org.matrix.android.sdk.api.session.crypto.OutgoingKeyRequest import org.matrix.android.sdk.api.session.crypto.OutgoingRoomKeyRequestState -import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKeys import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.PrivateKeysInfo +import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity import org.matrix.android.sdk.api.session.crypto.keysbackup.SavedKeyBackupKeyInfo import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo @@ -330,23 +330,23 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun storeUserCrossSigningKeys( + override fun storeUserIdentity( userId: String, - cryptoCrossSigningKeys: CryptoCrossSigningKeys, + userIdentity: UserIdentity, ) { - doRealmTransaction("storeUserCrossSigningKeys", realmConfiguration) { realm -> - storeUserCrossSigningKeys(realm, userId, cryptoCrossSigningKeys) + doRealmTransaction("storeUserIdentity", realmConfiguration) { realm -> + storeUserIdentity(realm, userId, userIdentity) } } - private fun storeUserCrossSigningKeys( + private fun storeUserIdentity( realm: Realm, userId: String, - keys: CryptoCrossSigningKeys, + userIdentity: UserIdentity, ) { UserEntity.getOrCreate(realm, userId) .let { userEntity -> - if (keys.masterKey == null || keys.selfSigningKey == null) { + if (userIdentity.masterKey == null || userIdentity.selfSigningKey == null) { // The user has disabled cross signing? userEntity.crossSigningInfoEntity?.deleteOnCascade() userEntity.crossSigningInfoEntity = null @@ -355,11 +355,11 @@ internal class RealmCryptoStore @Inject constructor( CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> // What should we do if we detect a change of the keys? val existingMaster = signingInfo.getMasterKey() - if (existingMaster != null && existingMaster.publicKeyBase64 == keys.masterKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingMaster, keys.masterKey) + if (existingMaster != null && existingMaster.publicKeyBase64 == userIdentity.masterKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingMaster, userIdentity.masterKey) } else { Timber.d("## CrossSigning MSK change for $userId") - val keyEntity = crossSigningKeysMapper.map(keys.masterKey) + val keyEntity = crossSigningKeysMapper.map(userIdentity.masterKey) signingInfo.setMasterKey(keyEntity) if (userId == this.userId) { shouldResetMyDevicesLocalTrust = true @@ -374,11 +374,11 @@ internal class RealmCryptoStore @Inject constructor( } val existingSelfSigned = signingInfo.getSelfSignedKey() - if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == keys.selfSigningKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingSelfSigned, keys.selfSigningKey) + if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == userIdentity.selfSigningKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingSelfSigned, userIdentity.selfSigningKey) } else { Timber.d("## CrossSigning SSK change for $userId") - val keyEntity = crossSigningKeysMapper.map(keys.selfSigningKey) + val keyEntity = crossSigningKeysMapper.map(userIdentity.selfSigningKey) signingInfo.setSelfSignedKey(keyEntity) if (userId == this.userId) { shouldResetMyDevicesLocalTrust = true @@ -390,13 +390,13 @@ internal class RealmCryptoStore @Inject constructor( } // Only for me - if (keys.userSigningKey != null) { + if (userIdentity.userSigningKey != null) { val existingUSK = signingInfo.getUserSigningKey() - if (existingUSK != null && existingUSK.publicKeyBase64 == keys.userSigningKey.unpaddedBase64PublicKey) { - crossSigningKeysMapper.update(existingUSK, keys.userSigningKey) + if (existingUSK != null && existingUSK.publicKeyBase64 == userIdentity.userSigningKey.unpaddedBase64PublicKey) { + crossSigningKeysMapper.update(existingUSK, userIdentity.userSigningKey) } else { Timber.d("## CrossSigning USK change for $userId") - val keyEntity = crossSigningKeysMapper.map(keys.userSigningKey) + val keyEntity = crossSigningKeysMapper.map(userIdentity.userSigningKey) signingInfo.setUserSignedKey(keyEntity) if (userId == this.userId) { shouldResetMyDevicesLocalTrust = true @@ -1857,8 +1857,8 @@ internal class RealmCryptoStore @Inject constructor( userDataToStore.userDevices.forEach { storeUserDevices(realm, it.key, it.value) } - userDataToStore.userCrossSigningKeys.forEach { - storeUserCrossSigningKeys(realm, it.key, it.value) + userDataToStore.userIdentities.forEach { + storeUserIdentity(realm, it.key, it.value) } } } From 682bb8bde09fb8f083a7442091b91a0214fe2cf9 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 4 Jan 2023 14:06:58 +0100 Subject: [PATCH 32/40] VB - Stop listening if we reach the last received chunk and there is no last sequence number --- .../voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt index d56f4ad715..9cb894bb58 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/listening/VoiceBroadcastPlayerImpl.kt @@ -419,7 +419,9 @@ class VoiceBroadcastPlayerImpl @Inject constructor( // Next media player is already attached to this player and will start playing automatically if (nextMediaPlayer != null) return - val hasEnded = !isLiveListening && mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence == playlist.currentSequence + val currentSequence = playlist.currentSequence ?: 0 + val lastChunkSequence = mostRecentVoiceBroadcastEvent?.content?.lastChunkSequence ?: 0 + val hasEnded = !isLiveListening && currentSequence >= lastChunkSequence if (hasEnded) { // We'll not receive new chunks anymore so we can stop the live listening stop() From 27d32188bfd4af638af0a43b8044ce1a2a8701d7 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Jan 2023 11:04:20 +0100 Subject: [PATCH 33/40] Aggregate data outside of the RealmCryptoStore. --- .../internal/crypto/DefaultCryptoService.kt | 33 ++++++++++++------- .../internal/crypto/store/IMXCryptoStore.kt | 28 +++++++--------- .../crypto/store/db/RealmCryptoStore.kt | 33 +++++++------------ .../room/create/CreateLocalRoomTask.kt | 2 +- .../session/sync/SyncResponseHandler.kt | 7 ++-- .../SyncResponsePostTreatmentAggregator.kt | 5 +++ .../session/sync/handler/CryptoSyncHandler.kt | 5 +-- .../sync/handler/room/RoomSyncHandler.kt | 4 +-- 8 files changed, 60 insertions(+), 57 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 128f06eacb..9f0a780703 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -89,6 +89,7 @@ import org.matrix.android.sdk.internal.crypto.model.SessionInfo import org.matrix.android.sdk.internal.crypto.model.toRest import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask @@ -192,21 +193,21 @@ internal class DefaultCryptoService @Inject constructor( private val isStarting = AtomicBoolean(false) private val isStarted = AtomicBoolean(false) - fun onStateEvent(roomId: String, event: Event) { + fun onStateEvent(roomId: String, event: Event, cryptoStoreAggregator: CryptoStoreAggregator?) { when (event.type) { EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) - EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) + EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event, cryptoStoreAggregator) } } - fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean) { + fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean, cryptoStoreAggregator: CryptoStoreAggregator?) { // handle state events if (event.isStateEvent()) { when (event.type) { EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) - EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event) + EventType.STATE_ROOM_HISTORY_VISIBILITY -> onRoomHistoryVisibilityEvent(roomId, event, cryptoStoreAggregator) } } @@ -384,7 +385,6 @@ internal class DefaultCryptoService @Inject constructor( } } } - cryptoStore.onSyncWillProcess() } private fun internalStart() { @@ -432,8 +432,8 @@ internal class DefaultCryptoService @Inject constructor( * * @param syncResponse the syncResponse */ - fun onSyncCompleted(syncResponse: SyncResponse) { - cryptoStore.onSyncCompleted() + fun onSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) { + cryptoStore.storeData(cryptoStoreAggregator) cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { runCatching { if (syncResponse.deviceLists != null) { @@ -1000,15 +1000,26 @@ internal class DefaultCryptoService @Inject constructor( } } - private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) { + private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event, cryptoStoreAggregator: CryptoStoreAggregator?) { if (!event.isStateEvent()) return val eventContent = event.content.toModel() val historyVisibility = eventContent?.historyVisibility if (historyVisibility == null) { - cryptoStore.setShouldShareHistory(roomId, false) + if (cryptoStoreAggregator != null) { + cryptoStoreAggregator.setShouldShareHistoryData[roomId] = false + } else { + // Store immediately + cryptoStore.setShouldShareHistory(roomId, false) + } } else { - cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED) - cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory()) + if (cryptoStoreAggregator != null) { + cryptoStoreAggregator.setShouldEncryptForInvitedMembersData[roomId] = historyVisibility != RoomHistoryVisibility.JOINED + cryptoStoreAggregator.setShouldShareHistoryData[roomId] = historyVisibility.shouldShareHistory() + } else { + // Store immediately + cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED) + cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory()) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 10158c7a4d..0305f73a7b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity import org.matrix.olm.OlmAccount import org.matrix.olm.OlmOutboundGroupSession @@ -48,21 +49,6 @@ import org.matrix.olm.OlmOutboundGroupSession */ internal interface IMXCryptoStore { - /** - * Notify the store that a sync response treatment is starting. - * Impacted methods: - * - [setShouldShareHistory] - * - [setShouldEncryptForInvitedMembers] - * @See [onSyncCompleted] to notify that the treatment is over. - */ - fun onSyncWillProcess() - - /** - * Notify the store that the sync treatment response is finished. - * The store will save all aggregated data. - */ - fun onSyncCompleted() - /** * @return the device id */ @@ -307,7 +293,11 @@ internal interface IMXCryptoStore { fun shouldEncryptForInvitedMembers(roomId: String): Boolean /** - * The data is not stored immediately, this MUST be call during a sync response treatment. + * Sets a boolean flag that will determine whether or not this device should encrypt Events for + * invited members. + * + * @param roomId the room id + * @param shouldEncryptForInvitedMembers The boolean flag */ fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) @@ -316,7 +306,6 @@ internal interface IMXCryptoStore { /** * Sets a boolean flag that will determine whether or not room history (existing inbound sessions) * will be shared to new user invites. - * The data is not stored immediately, this MUST be call during a sync response treatment. * * @param roomId the room id * @param shouldShareHistory The boolean flag @@ -601,6 +590,11 @@ internal interface IMXCryptoStore { fun tidyUpDataBase() fun getOutgoingRoomKeyRequests(inStates: Set): List + /** + * Store a bunch of data collected during a sync response treatment. @See [CryptoStoreAggregator]. + */ + fun storeData(cryptoStoreAggregator: CryptoStoreAggregator) + /** * Store a bunch of data related to the users. @See [UserDataToStore]. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 1be88249eb..b4368467a2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -719,13 +719,17 @@ internal class RealmCryptoStore @Inject constructor( } override fun setShouldEncryptForInvitedMembers(roomId: String, shouldEncryptForInvitedMembers: Boolean) { - cryptoStoreAggregator?.setShouldEncryptForInvitedMembersData?.put(roomId, shouldEncryptForInvitedMembers) + doRealmTransaction("setShouldEncryptForInvitedMembers", realmConfiguration) { + CryptoRoomEntity.getOrCreate(it, roomId).shouldEncryptForInvitedMembers = shouldEncryptForInvitedMembers + } } override fun setShouldShareHistory(roomId: String, shouldShareHistory: Boolean) { Timber.tag(loggerTag.value) .v("setShouldShareHistory for room $roomId is $shouldShareHistory") - cryptoStoreAggregator?.setShouldShareHistoryData?.put(roomId, shouldShareHistory) + doRealmTransaction("setShouldShareHistory", realmConfiguration) { + CryptoRoomEntity.getOrCreate(it, roomId).shouldShareHistory = shouldShareHistory + } } override fun storeSession(olmSessionWrapper: OlmSessionWrapper, deviceKey: String) { @@ -1823,37 +1827,24 @@ internal class RealmCryptoStore @Inject constructor( } } - private var cryptoStoreAggregator: CryptoStoreAggregator? = null - override fun onSyncWillProcess() { - if (cryptoStoreAggregator != null) { - Timber.e("cryptoStoreAggregator is not null...") - } - cryptoStoreAggregator = CryptoStoreAggregator() - } - - override fun onSyncCompleted() { - val aggregator = cryptoStoreAggregator ?: return Unit.also { - Timber.e("cryptoStoreAggregator is null...") - } - cryptoStoreAggregator = null - - if (aggregator.isEmpty()) { + override fun storeData(cryptoStoreAggregator: CryptoStoreAggregator) { + if (cryptoStoreAggregator.isEmpty()) { return } - doRealmTransaction("onSyncCompleted", realmConfiguration) { realm -> + doRealmTransaction("storeData - CryptoStoreAggregator", realmConfiguration) { realm -> // setShouldShareHistory - aggregator.setShouldShareHistoryData.forEach { + cryptoStoreAggregator.setShouldShareHistoryData.forEach { CryptoRoomEntity.getOrCreate(realm, it.key).shouldShareHistory = it.value } // setShouldEncryptForInvitedMembers - aggregator.setShouldEncryptForInvitedMembersData.forEach { + cryptoStoreAggregator.setShouldEncryptForInvitedMembersData.forEach { CryptoRoomEntity.getOrCreate(realm, it.key).shouldEncryptForInvitedMembers = it.value } } } override fun storeData(userDataToStore: UserDataToStore) { - doRealmTransaction("storeUserDataToStore", realmConfiguration) { realm -> + doRealmTransaction("storeData - UserDataToStore", realmConfiguration) { realm -> userDataToStore.userDevices.forEach { storeUserDevices(realm, it.key, it.value) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index 793c2573be..653069b3c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -176,7 +176,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( } // Give info to crypto module - cryptoService.onStateEvent(roomId, event) + cryptoService.onStateEvent(roomId, event, null) } roomMemberContentsByUser.getOrPut(event.senderId) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt index 05d50d9595..cb407bb1cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.DefaultCryptoService +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.session.SessionListeners @@ -92,7 +93,7 @@ internal class SyncResponseHandler @Inject constructor( postTreatmentSyncResponse(syncResponse, isInitialSync) - markCryptoSyncCompleted(syncResponse) + markCryptoSyncCompleted(syncResponse, aggregator.cryptoStoreAggregator) handlePostSync() @@ -218,10 +219,10 @@ internal class SyncResponseHandler @Inject constructor( } } - private fun markCryptoSyncCompleted(syncResponse: SyncResponse) { + private fun markCryptoSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) { relevantPlugins.measureSpan("task", "crypto_sync_handler_onSyncCompleted") { measureTimeMillis { - cryptoSyncHandler.onSyncCompleted(syncResponse) + cryptoSyncHandler.onSyncCompleted(syncResponse, cryptoStoreAggregator) }.also { Timber.v("cryptoSyncHandler.onSyncCompleted took $it ms") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt index 2b7f936fa8..af05e08da3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.internal.session.sync +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator + internal class SyncResponsePostTreatmentAggregator { // List of RoomId val ephemeralFilesToDelete = mutableListOf() @@ -28,4 +30,7 @@ internal class SyncResponsePostTreatmentAggregator { // Set of users to call `crossSigningService.checkTrustAndAffectedRoomShields` once per sync val userIdsForCheckingTrustAndAffectedRoomShields = mutableSetOf() + + // For the crypto store + val cryptoStoreAggregator = CryptoStoreAggregator() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt index 551db52dbd..7224b0c29c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse import org.matrix.android.sdk.internal.crypto.DefaultCryptoService +import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator import org.matrix.android.sdk.internal.crypto.tasks.toDeviceTracingId import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService import org.matrix.android.sdk.internal.session.sync.ProgressReporter @@ -85,8 +86,8 @@ internal class CryptoSyncHandler @Inject constructor( } } - fun onSyncCompleted(syncResponse: SyncResponse) { - cryptoService.onSyncCompleted(syncResponse) + fun onSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) { + cryptoService.onSyncCompleted(syncResponse, cryptoStoreAggregator) } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index ccc3820bb6..5e4886ce1e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -258,7 +258,7 @@ internal class RoomSyncHandler @Inject constructor( root = eventEntity } // Give info to crypto module - cryptoService.onStateEvent(roomId, event) + cryptoService.onStateEvent(roomId, event, aggregator.cryptoStoreAggregator) roomMemberEventHandler.handle(realm, roomId, event, isInitialSync, aggregator) } } @@ -495,7 +495,7 @@ internal class RoomSyncHandler @Inject constructor( } } // Give info to crypto module - cryptoService.onLiveEvent(roomEntity.roomId, event, isInitialSync) + cryptoService.onLiveEvent(roomEntity.roomId, event, isInitialSync, aggregator.cryptoStoreAggregator) // Try to remove local echo event.unsignedData?.transactionId?.also { txId -> From dbf3b763311ea0faae4df39a178ec584f5537f95 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 5 Jan 2023 11:54:19 +0100 Subject: [PATCH 34/40] Update doc. --- .../matrix/android/sdk/internal/crypto/DefaultCryptoService.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 9f0a780703..50497e3a27 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -431,6 +431,7 @@ internal class DefaultCryptoService @Inject constructor( * A sync response has been received. * * @param syncResponse the syncResponse + * @param cryptoStoreAggregator data aggregated during the sync response treatment to store */ fun onSyncCompleted(syncResponse: SyncResponse, cryptoStoreAggregator: CryptoStoreAggregator) { cryptoStore.storeData(cryptoStoreAggregator) From 87e661e3b5d895990648f5cc0e71e60d08d10982 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 5 Jan 2023 14:36:22 +0100 Subject: [PATCH 35/40] Add changelog file --- changelog.d/7899.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7899.bugfix diff --git a/changelog.d/7899.bugfix b/changelog.d/7899.bugfix new file mode 100644 index 0000000000..d95af29d8d --- /dev/null +++ b/changelog.d/7899.bugfix @@ -0,0 +1 @@ +[Voice Broadcast] Stop listening if we reach the last received chunk and there is no last sequence number From d60403545c4f323f7067bef06776660cdcf4b4d3 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:09:41 +0100 Subject: [PATCH 36/40] Renaming of filter enum --- .../app/features/roomprofile/polls/GetPollsUseCase.kt | 6 +++--- .../app/features/roomprofile/polls/RoomPollsAction.kt | 2 +- .../polls/{RoomPollsFilter.kt => RoomPollsFilterType.kt} | 2 +- .../app/features/roomprofile/polls/RoomPollsViewModel.kt | 2 +- .../roomprofile/polls/active/RoomActivePollsFragment.kt | 4 ++-- .../features/roomprofile/polls/RoomPollsViewModelTest.kt | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) rename vector/src/main/java/im/vector/app/features/roomprofile/polls/{RoomPollsFilter.kt => RoomPollsFilterType.kt} (95%) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt index fa8c6d0aa6..d35d192e04 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/GetPollsUseCase.kt @@ -24,11 +24,11 @@ import javax.inject.Inject class GetPollsUseCase @Inject constructor() { - fun execute(filter: RoomPollsFilter): Flow> { + fun execute(filter: RoomPollsFilterType): Flow> { // TODO unmock and add unit tests return when (filter) { - RoomPollsFilter.ACTIVE -> getActivePolls() - RoomPollsFilter.ENDED -> emptyFlow() + RoomPollsFilterType.ACTIVE -> getActivePolls() + RoomPollsFilterType.ENDED -> emptyFlow() }.map { it.sortedByDescending { poll -> poll.creationTimestamp } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt index 27753b6d16..5f074bdd6f 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsAction.kt @@ -19,5 +19,5 @@ package im.vector.app.features.roomprofile.polls import im.vector.app.core.platform.VectorViewModelAction sealed interface RoomPollsAction : VectorViewModelAction { - data class SetFilter(val filter: RoomPollsFilter) : RoomPollsAction + data class SetFilter(val filter: RoomPollsFilterType) : RoomPollsAction } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilter.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilterType.kt similarity index 95% rename from vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilter.kt rename to vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilterType.kt index 68ebb13f7d..39f1163536 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilter.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsFilterType.kt @@ -16,7 +16,7 @@ package im.vector.app.features.roomprofile.polls -enum class RoomPollsFilter { +enum class RoomPollsFilterType { ACTIVE, ENDED, } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt index 7def7a508d..7bc06894fa 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModel.kt @@ -54,7 +54,7 @@ class RoomPollsViewModel @AssistedInject constructor( super.onCleared() } - private fun handleSetFilter(filter: RoomPollsFilter) { + private fun handleSetFilter(filter: RoomPollsFilterType) { pollsCollectionJob?.cancel() pollsCollectionJob = getPollsUseCase.execute(filter) .onEach { setState { copy(polls = it) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt index 4cc318edf9..61c7e961bd 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsFragment.kt @@ -31,7 +31,7 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentRoomPollsListBinding import im.vector.app.features.roomprofile.polls.PollSummary import im.vector.app.features.roomprofile.polls.RoomPollsAction -import im.vector.app.features.roomprofile.polls.RoomPollsFilter +import im.vector.app.features.roomprofile.polls.RoomPollsFilterType import im.vector.app.features.roomprofile.polls.RoomPollsViewModel import timber.log.Timber import javax.inject.Inject @@ -73,7 +73,7 @@ class RoomActivePollsFragment : override fun onResume() { super.onResume() - viewModel.handle(RoomPollsAction.SetFilter(RoomPollsFilter.ACTIVE)) + viewModel.handle(RoomPollsAction.SetFilter(RoomPollsFilterType.ACTIVE)) } override fun invalidate() = withState(viewModel) { viewState -> diff --git a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt index 54b2a60d55..0dce2dd6e0 100644 --- a/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/roomprofile/polls/RoomPollsViewModelTest.kt @@ -47,7 +47,7 @@ class RoomPollsViewModelTest { @Test fun `given SetFilter action when handle then useCase is called with given filter and viewState is updated`() { // Given - val filter = RoomPollsFilter.ACTIVE + val filter = RoomPollsFilterType.ACTIVE val action = RoomPollsAction.SetFilter(filter = filter) val polls = listOf(givenAPollSummary()) every { fakeGetPollsUseCase.execute(any()) } returns flowOf(polls) From ff9e78be42c500fd3a0985cadb9db67be8c54df3 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:20:20 +0100 Subject: [PATCH 37/40] Use classical for loop instead of forEach --- .../roomprofile/polls/active/RoomActivePollsController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt index dc14ec366d..7a7c818693 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/active/RoomActivePollsController.kt @@ -38,7 +38,7 @@ class RoomActivePollsController @Inject constructor( } val host = this - data.forEach { poll -> + for (poll in data) { activePollItem { id(poll.id) formattedDate(host.dateFormatter.format(poll.creationTimestamp, DateFormatKind.TIMELINE_DAY_DIVIDER)) From 2dab6ed052912e6296b643d15b30c69ce0df7516 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 5 Jan 2023 15:27:11 +0100 Subject: [PATCH 38/40] Fix horizontal margin of tabs --- vector/src/main/res/layout/fragment_room_polls.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/fragment_room_polls.xml b/vector/src/main/res/layout/fragment_room_polls.xml index dcaf483251..396d6fd8c5 100644 --- a/vector/src/main/res/layout/fragment_room_polls.xml +++ b/vector/src/main/res/layout/fragment_room_polls.xml @@ -24,7 +24,7 @@ android:id="@+id/roomPollsTabs" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginHorizontal="10dp" + android:layout_marginHorizontal="4dp" android:layout_marginTop="20dp" android:background="?android:colorBackground" app:layout_constraintBottom_toTopOf="@id/roomPollsViewPager" From 7fc9705f3a92d392b0ce8855ecd79e6abb32cc40 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 5 Jan 2023 16:37:06 +0100 Subject: [PATCH 39/40] Adding importantForAccessibility attribute to icon --- vector/src/main/res/layout/item_poll.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/layout/item_poll.xml b/vector/src/main/res/layout/item_poll.xml index 05e9b3a62a..956ecf9b3c 100644 --- a/vector/src/main/res/layout/item_poll.xml +++ b/vector/src/main/res/layout/item_poll.xml @@ -22,6 +22,7 @@ android:layout_width="16dp" android:layout_height="16dp" android:layout_marginTop="12dp" + android:importantForAccessibility="no" android:src="@drawable/ic_attachment_poll" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/pollActiveDate" From b8da53b3bb4a7e62e0fa20aeaab425c4782742ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Jan 2023 11:56:11 +0000 Subject: [PATCH 40/40] Bump checker from 3.27.0 to 3.29.0 (#7903) Bumps [checker](https://github.com/typetools/checker-framework) from 3.27.0 to 3.29.0. - [Release notes](https://github.com/typetools/checker-framework/releases) - [Changelog](https://github.com/typetools/checker-framework/blob/master/docs/CHANGELOG.md) - [Commits](https://github.com/typetools/checker-framework/compare/checker-framework-3.27.0...checker-framework-3.29.0) --- updated-dependencies: - dependency-name: org.checkerframework:checker dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index 83af7ecc04..91d2a8c46a 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -308,7 +308,7 @@ dependencies { // Fix issue with Jitsi. Inspired from https://github.com/android/android-test/issues/861#issuecomment-872067868 // Error was lots of `Duplicate class org.checkerframework.common.reflection.qual.MethodVal found in modules jetified-checker-3.1 (org.checkerframework:checker:3.1.1) and jetified-checker-qual-3.12.0 (org.checkerframework:checker-qual:3.12.0) //noinspection GradleDependency Cannot use latest 3.15.0 since it required min API 26. - implementation "org.checkerframework:checker:3.27.0" + implementation "org.checkerframework:checker:3.29.0" androidTestImplementation libs.androidx.testCore androidTestImplementation libs.androidx.testRunner