From 6318bf41f6abea95f4c92b2753a3d4421bfa6fb1 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Wed, 20 Jul 2022 10:12:24 +0200 Subject: [PATCH] Import v1.4.27-RC2 from Element Android --- dependencies.gradle | 25 +- dependencies_groups.gradle | 5 + matrix-sdk-android/build.gradle | 10 +- .../TestBuildVersionSdkIntProvider.kt | 6 +- .../securestorage/SecretStoringUtilsTest.kt | 96 ++++- .../android/sdk/common/TestMatrixComponent.kt | 8 +- .../sdk/session/space/SpaceCreationTest.kt | 29 +- .../sdk/session/space/SpaceHierarchyTest.kt | 7 +- .../java/org/matrix/android/sdk/api/Matrix.kt | 22 +- .../android/sdk/api/MatrixConfiguration.kt | 5 + .../sdk/api/auth/data/LoginFlowResult.kt | 3 +- .../android/sdk/api/auth/login/LoginWizard.kt | 6 +- .../android/sdk/api/debug/DebugService.kt | 34 ++ .../android/sdk/api/query/QueryStringValue.kt | 15 +- .../securestorage/SecretStoringUtils.kt | 97 +++-- .../api/securestorage/SecureStorageModule.kt | 45 ++ .../securestorage/SecureStorageService.kt | 2 +- .../matrix/android/sdk/api/session/Session.kt | 14 +- .../sdk/api/session/account/AccountService.kt | 8 +- .../sdk/api/session/crypto/CryptoService.kt | 2 - .../homeserver/HomeServerCapabilities.kt | 7 +- .../sdk/api/session/room/RoomExtensions.kt | 7 +- .../room/location/LocationSharingService.kt | 46 ++ .../location/UpdateLiveLocationShareResult.kt | 25 ++ .../room/model/message/LocationInfo.kt | 2 +- .../model/message/MessageLocationContent.kt | 2 +- .../room/model/message/PollCreationInfo.kt | 5 +- .../sdk/api/session/room/send/SendService.kt | 18 - .../api/session/room/state/StateService.kt | 23 +- .../room/state/StateServiceExtension.kt | 2 +- .../sdk/api/session/widgets/WidgetService.kt | 6 +- .../util}/BuildVersionSdkIntProvider.kt | 4 +- .../DefaultBuildVersionSdkIntProvider.kt | 4 +- .../android/sdk/internal/SessionManager.kt | 7 + .../auth/DefaultAuthenticationService.kt | 9 +- .../internal/auth/login/DefaultLoginWizard.kt | 5 +- .../auth/login/ResetPasswordMailConfirmed.kt | 10 +- .../auth/version/HomeServerVersion.kt | 1 + .../sdk/internal/auth/version/Versions.kt | 9 + .../internal/crypto/DefaultCryptoService.kt | 4 - .../internal/crypto/store/IMXCryptoStore.kt | 1 - .../crypto/store/db/RealmCryptoStore.kt | 8 - .../VerificationMessageProcessor.kt | 2 +- .../sdk/internal/database/RealmKeysUtils.kt | 8 +- .../database/RealmSessionStoreMigration.kt | 4 +- .../database/helper/ThreadSummaryHelper.kt | 2 +- .../internal/database/mapper/EventMapper.kt | 2 +- .../mapper/HomeServerCapabilitiesMapper.kt | 3 +- ...iveLocationShareAggregatedSummaryMapper.kt | 6 +- .../database/migration/MigrateSessionTo029.kt | 2 +- .../database/migration/MigrateSessionTo030.kt | 6 +- .../database/migration/MigrateSessionTo031.kt | 31 ++ .../model/HomeServerCapabilitiesEntity.kt | 3 +- ...cationShareAggregatedSummaryEntityQuery.kt | 3 +- .../android/sdk/internal/debug/DebugModule.kt | 29 ++ .../sdk/internal/debug/DefaultDebugService.kt | 44 ++ .../sdk/internal/di/MatrixComponent.kt | 9 +- .../android/sdk/internal/di/NetworkModule.kt | 5 +- .../network/httpclient/OkHttpClientUtil.kt | 15 + .../DefaultSecureStorageService.kt | 5 +- .../sdk/internal/session/DefaultSession.kt | 21 +- .../sdk/internal/session/SessionComponent.kt | 4 +- .../sdk/internal/session/SessionListeners.kt | 3 +- .../sdk/internal/session/SessionModule.kt | 24 +- .../session/account/ChangePasswordParams.kt | 10 +- .../session/account/ChangePasswordTask.kt | 5 +- .../session/account/DefaultAccountService.kt | 4 +- .../GetHomeServerCapabilitiesTask.kt | 2 + .../session/identity/IdentityModule.kt | 6 +- .../session/permalinks/ViaParameterFinder.kt | 2 +- .../pushers/DefaultConditionResolver.kt | 3 +- .../EventRelationsAggregationProcessor.kt | 2 +- .../sdk/internal/session/room/RoomModule.kt | 30 ++ .../LiveLocationAggregationProcessor.kt | 31 +- .../location/CheckIfExistingActiveLiveTask.kt | 45 ++ .../location/DefaultLocationSharingService.kt | 76 +++- .../GetActiveBeaconInfoForUserTask.kt | 54 +++ .../room/location/SendLiveLocationTask.kt | 51 +++ .../room/location/SendStaticLocationTask.kt | 51 +++ .../location/StartLiveLocationShareTask.kt | 66 +++ .../location/StopLiveLocationShareTask.kt | 72 ++++ .../membership/DefaultMembershipService.kt | 2 +- .../room/membership/LoadRoomMembersTask.kt | 46 +- .../room/membership/RoomMemberEventHandler.kt | 32 +- .../room/membership/leaving/LeaveRoomTask.kt | 2 +- .../threads/FetchThreadTimelineTask.kt | 3 +- .../session/room/send/DefaultSendService.kt | 12 - .../room/send/LocalEchoEventFactory.kt | 6 +- .../session/room/state/DefaultStateService.kt | 46 +- .../room/state/StateEventDataSource.kt | 14 +- .../session/room/timeline/DefaultTimeline.kt | 24 +- .../session/room/timeline/GetEventTask.kt | 2 +- .../room/timeline/LiveRoomStateListener.kt | 2 +- .../room/timeline/LoadTimelineStrategy.kt | 11 +- .../session/room/timeline/TimelineChunk.kt | 53 +-- .../room/timeline/TokenChunkEventPersistor.kt | 4 +- .../room/version/DefaultRoomVersionService.kt | 2 +- .../session/space/DefaultSpaceService.kt | 2 +- .../sync/handler/room/RoomSyncHandler.kt | 14 +- .../handler/room/ThreadsAwarenessHandler.kt | 6 +- .../session/widgets/DefaultWidgetService.kt | 6 +- .../internal/session/widgets/WidgetManager.kt | 7 +- .../util/BackgroundDetectionObserver.kt | 3 +- .../sdk/internal/util/system/SystemModule.kt | 4 +- .../android/sdk/api/MatrixPatternsTest.kt | 40 ++ .../pushers/DefaultAddPusherTaskTest.kt | 6 + .../LiveLocationAggregationProcessorTest.kt | 405 ++++++++++++++++++ .../poll/PollAggregationProcessorTest.kt | 15 +- ...efaultCheckIfExistingActiveLiveTaskTest.kt | 105 +++++ ...faultGetActiveBeaconInfoForUserTaskTest.kt | 75 ++++ .../DefaultLocationSharingServiceTest.kt | 268 ++++++++++++ .../DefaultSendLiveLocationTaskTest.kt | 79 ++++ .../DefaultSendStaticLocationTaskTest.kt | 78 ++++ .../DefaultStartLiveLocationShareTaskTest.kt | 114 +++++ .../DefaultStopLiveLocationShareTaskTest.kt | 167 ++++++++ .../android/sdk/test/fakes/FakeClock.kt | 27 ++ .../test/fakes/FakeEventSenderProcessor.kt | 30 ++ .../FakeGetActiveBeaconInfoForUserTask.kt | 34 ++ .../test/fakes/FakeLocalEchoEventFactory.kt | 106 +++++ .../android/sdk/test/fakes/FakeMonarchy.kt | 43 +- .../android/sdk/test/fakes/FakeRealm.kt | 73 +++- .../sdk/test/fakes/FakeSendStateTask.kt | 37 ++ .../test/fakes/FakeStateEventDataSource.kt | 49 +++ .../android/sdk/test/fakes/FakeWorkManager.kt | 45 ++ .../sdk/test/fakes/FakeWorkManagerProvider.kt | 30 ++ .../DefaultBuildVersionSdkIntProviderTests.kt | 31 ++ 126 files changed, 3124 insertions(+), 421 deletions(-) rename matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/{internal/session/securestorage => }/TestBuildVersionSdkIntProvider.kt (78%) rename matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/{internal/session => api}/securestorage/SecretStoringUtilsTest.kt (62%) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/debug/DebugService.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal/session => api}/securestorage/SecretStoringUtils.kt (82%) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecureStorageModule.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/{session => }/securestorage/SecureStorageService.kt (93%) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/UpdateLiveLocationShareResult.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal/util/system => api/util}/BuildVersionSdkIntProvider.kt (87%) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{internal/util/system => api/util}/DefaultBuildVersionSdkIntProvider.kt (85%) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo031.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DebugModule.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DefaultDebugService.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/{session => }/securestorage/DefaultSecureStorageService.kt (86%) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/CheckIfExistingActiveLiveTask.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/GetActiveBeaconInfoForUserTask.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/SendLiveLocationTask.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/SendStaticLocationTask.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StopLiveLocationShareTask.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/MatrixPatternsTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultCheckIfExistingActiveLiveTaskTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultSendLiveLocationTaskTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultSendStaticLocationTaskTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStopLiveLocationShareTaskTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeClock.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventSenderProcessor.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetActiveBeaconInfoForUserTask.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeLocalEchoEventFactory.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSendStateTask.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeWorkManager.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeWorkManagerProvider.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/util/DefaultBuildVersionSdkIntProviderTests.kt diff --git a/dependencies.gradle b/dependencies.gradle index 451ad444..db9278b9 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -13,7 +13,7 @@ ext.versions = [ def gradle = "7.1.3" // Ref: https://kotlinlang.org/releases.html def kotlin = "1.6.21" -def kotlinCoroutines = "1.6.2" +def kotlinCoroutines = "1.6.3" def dagger = "2.42" def retrofit = "2.9.0" def arrow = "0.8.2" @@ -21,20 +21,21 @@ def markwon = "4.6.2" def moshi = "1.13.0" def lifecycle = "2.4.1" def flowBinding = "1.2.0" +def flipper = "0.151.1" def epoxy = "4.6.2" -def mavericks = "2.6.1" +def mavericks = "2.7.0" def glide = "4.13.2" def bigImageViewer = "1.8.1" def jjwt = "0.11.5" def vanniktechEmoji = "0.15.0" +def fragment = "1.4.1" + // Testing -def mockk = "1.12.4" +def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819 def espresso = "3.4.0" def androidxTest = "1.4.0" def androidxOrchestrator = "1.4.1" - - ext.libs = [ gradle : [ 'gradlePlugin' : "com.android.tools.build:gradle:$gradle", @@ -48,12 +49,16 @@ ext.libs = [ 'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines" ], androidx : [ + 'annotation' : "androidx.annotation:annotation:1.4.0", 'activity' : "androidx.activity:activity:1.4.0", + 'annotations' : "androidx.annotation:annotation:1.3.0", 'appCompat' : "androidx.appcompat:appcompat:1.4.2", + 'biometric' : "androidx.biometric:biometric:1.1.0", 'core' : "androidx.core:core-ktx:1.8.0", 'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1", 'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3", - 'fragmentKtx' : "androidx.fragment:fragment-ktx:1.4.1", + 'fragmentKtx' : "androidx.fragment:fragment-ktx:$fragment", + 'fragmentTesting' : "androidx.fragment:fragment-testing:$fragment", 'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.4", 'work' : "androidx.work:work-runtime-ktx:2.7.1", 'autoFill' : "androidx.autofill:autofill:1.1.0", @@ -84,10 +89,16 @@ ext.libs = [ 'dagger' : "com.google.dagger:dagger:$dagger", 'daggerCompiler' : "com.google.dagger:dagger-compiler:$dagger", 'hilt' : "com.google.dagger:hilt-android:$dagger", + 'hiltAndroidTesting' : "com.google.dagger:hilt-android-testing:$dagger", 'hiltCompiler' : "com.google.dagger:hilt-compiler:$dagger" ], + flipper : [ + 'flipper' : "com.facebook.flipper:flipper:$flipper", + 'flipperNetworkPlugin' : "com.facebook.flipper:flipper-network-plugin:$flipper", + ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", + 'moshiKt' : "com.squareup.moshi:moshi-kotlin:$moshi", 'moshiKotlin' : "com.squareup.moshi:moshi-kotlin-codegen:$moshi", 'retrofit' : "com.squareup.retrofit2:retrofit:$retrofit", 'retrofitMoshi' : "com.squareup.retrofit2:converter-moshi:$retrofit" @@ -153,3 +164,5 @@ ext.libs = [ 'junit' : "junit:junit:4.13.2" ] ] + + diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 59cefe7e..b785c7f5 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -9,6 +9,7 @@ ext.groups = [ 'com.github.jetradarmobile', 'com.github.MatrixFrog', 'com.github.tapadoo', + 'com.github.UnifiedPush', 'com.github.vector-im', 'com.github.yalantis', 'com.github.Zhuinden', @@ -31,6 +32,7 @@ ext.groups = [ ], group: [ 'com.android', + 'com.android.ndk.thirdparty', 'com.android.tools', 'com.google.firebase', 'com.google.testing.platform', @@ -52,6 +54,7 @@ ext.groups = [ 'com.dropbox.core', 'com.soywiz.korlibs.korte', 'com.facebook.fbjni', + 'com.facebook.flipper', 'com.facebook.fresco', 'com.facebook.infer.annotation', 'com.facebook.soloader', @@ -93,6 +96,7 @@ ext.groups = [ 'com.ibm.icu', 'com.jakewharton.android.repackaged', 'com.jakewharton.timber', + 'com.kgurgul.flipper', 'com.linkedin.dexmaker', 'com.mapbox.mapboxsdk', 'com.nulab-inc', @@ -168,6 +172,7 @@ ext.groups = [ 'org.glassfish.jaxb', 'org.hamcrest', 'org.jacoco', + 'org.java-websocket', 'org.jetbrains', 'org.jetbrains.dokka', 'org.jetbrains.intellij.deps', diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index f6a7a294..a7cb1cad 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -7,6 +7,10 @@ apply plugin: "org.jetbrains.dokka" // WARNING: always restore this line after importing code from Element Android (1/2) apply plugin: "com.vanniktech.maven.publish" +if (project.hasProperty("coverage")) { + apply plugin: 'jacoco' +} + buildscript { repositories { // Do not use `mavenCentral()`, it prevents Dependabot from working properly @@ -77,7 +81,9 @@ android { buildTypes { debug { - testCoverageEnabled true + if (project.hasProperty("coverage")) { + testCoverageEnabled = coverage.enableTestCoverage + } // Set to true to log privacy or sensible data, such as token buildConfigField "boolean", "LOG_PRIVATE_DATA", project.property("vector.debugPrivateData") // Set to BODY instead of NONE to enable logging @@ -196,7 +202,7 @@ dependencies { implementation libs.apache.commonsImaging // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.50' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.51' testImplementation libs.tests.junit // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/securestorage/TestBuildVersionSdkIntProvider.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/TestBuildVersionSdkIntProvider.kt similarity index 78% rename from matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/securestorage/TestBuildVersionSdkIntProvider.kt rename to matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/TestBuildVersionSdkIntProvider.kt index b08c88fb..d0d64491 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/securestorage/TestBuildVersionSdkIntProvider.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/TestBuildVersionSdkIntProvider.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * Copyright 2022 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. @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.securestorage +package org.matrix.android.sdk -import org.matrix.android.sdk.internal.util.system.BuildVersionSdkIntProvider +import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider class TestBuildVersionSdkIntProvider : BuildVersionSdkIntProvider { var value: Int = 0 diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtilsTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtilsTest.kt similarity index 62% rename from matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtilsTest.kt rename to matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtilsTest.kt index 6bcd1274..14f98524 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtilsTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtilsTest.kt @@ -14,40 +14,57 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.securestorage +package org.matrix.android.sdk.api.securestorage import android.os.Build +import android.util.Base64 import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.spyk +import org.amshove.kluent.invoking import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeInstanceOf +import org.amshove.kluent.shouldNotThrow +import org.amshove.kluent.shouldThrow +import org.junit.Before import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters -import org.matrix.android.sdk.InstrumentedTest -import org.matrix.android.sdk.api.util.fromBase64 -import org.matrix.android.sdk.api.util.toBase64NoPadding +import org.matrix.android.sdk.TestBuildVersionSdkIntProvider import java.io.ByteArrayOutputStream +import java.security.KeyStore +import java.security.KeyStoreException import java.util.UUID @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) -class SecretStoringUtilsTest : InstrumentedTest { +class SecretStoringUtilsTest { + private val context = InstrumentationRegistry.getInstrumentation().targetContext private val buildVersionSdkIntProvider = TestBuildVersionSdkIntProvider() - private val secretStoringUtils = SecretStoringUtils(context(), buildVersionSdkIntProvider) + private val keyStore = spyk(KeyStore.getInstance("AndroidKeyStore")).also { it.load(null) } + private val secretStoringUtils = SecretStoringUtils(context, keyStore, buildVersionSdkIntProvider) companion object { const val TEST_STR = "This is something I want to store safely!" } + @Before + fun setup() { + clearAllMocks() + } + @Test fun testStringNominalCaseApi21() { val alias = generateAlias() buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP // Encrypt - val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias) + val encrypted = secretStoringUtils.securelyStoreBytes(TEST_STR.toByteArray(), alias) // Decrypt - val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias) + val decrypted = String(secretStoringUtils.loadSecureSecretBytes(encrypted, alias)) decrypted shouldBeEqualTo TEST_STR secretStoringUtils.safeDeleteKey(alias) } @@ -57,9 +74,9 @@ class SecretStoringUtilsTest : InstrumentedTest { val alias = generateAlias() buildVersionSdkIntProvider.value = Build.VERSION_CODES.M // Encrypt - val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias) + val encrypted = secretStoringUtils.securelyStoreBytes(TEST_STR.toByteArray(), alias) // Decrypt - val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias) + val decrypted = String(secretStoringUtils.loadSecureSecretBytes(encrypted, alias)) decrypted shouldBeEqualTo TEST_STR secretStoringUtils.safeDeleteKey(alias) } @@ -69,9 +86,9 @@ class SecretStoringUtilsTest : InstrumentedTest { val alias = generateAlias() buildVersionSdkIntProvider.value = Build.VERSION_CODES.R // Encrypt - val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias) + val encrypted = secretStoringUtils.securelyStoreBytes(TEST_STR.toByteArray(), alias) // Decrypt - val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias) + val decrypted = String(secretStoringUtils.loadSecureSecretBytes(encrypted, alias)) decrypted shouldBeEqualTo TEST_STR secretStoringUtils.safeDeleteKey(alias) } @@ -81,13 +98,13 @@ class SecretStoringUtilsTest : InstrumentedTest { val alias = generateAlias() buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP // Encrypt - val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias) + val encrypted = secretStoringUtils.securelyStoreBytes(TEST_STR.toByteArray(), alias) // Simulate a system upgrade buildVersionSdkIntProvider.value = Build.VERSION_CODES.M // Decrypt - val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias) + val decrypted = String(secretStoringUtils.loadSecureSecretBytes(encrypted, alias)) decrypted shouldBeEqualTo TEST_STR secretStoringUtils.safeDeleteKey(alias) } @@ -180,5 +197,56 @@ class SecretStoringUtilsTest : InstrumentedTest { secretStoringUtils.safeDeleteKey(alias) } + @Test + fun testEnsureKeyReturnsSymmetricKeyOnAndroidM() { + buildVersionSdkIntProvider.value = Build.VERSION_CODES.M + val alias = generateAlias() + + val key = secretStoringUtils.ensureKey(alias) + key shouldBeInstanceOf KeyStore.SecretKeyEntry::class + + secretStoringUtils.safeDeleteKey(alias) + } + + @Test + fun testEnsureKeyReturnsPrivateKeyOnAndroidL() { + buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP + val alias = generateAlias() + + val key = secretStoringUtils.ensureKey(alias) + key shouldBeInstanceOf KeyStore.PrivateKeyEntry::class + + secretStoringUtils.safeDeleteKey(alias) + } + + @Test + fun testSafeDeleteCanHandleKeyStoreExceptions() { + every { keyStore.deleteEntry(any()) } throws KeyStoreException() + + invoking { secretStoringUtils.safeDeleteKey(generateAlias()) } shouldNotThrow KeyStoreException::class + } + + @Test + fun testLoadSecureSecretBytesWillThrowOnInvalidStreamFormat() { + invoking { + secretStoringUtils.loadSecureSecretBytes(byteArrayOf(255.toByte()), generateAlias()) + } shouldThrow IllegalArgumentException::class + } + + @Test + fun testLoadSecureSecretWillThrowOnInvalidStreamFormat() { + invoking { + secretStoringUtils.loadSecureSecret(byteArrayOf(255.toByte()).inputStream(), generateAlias()) + } shouldThrow IllegalArgumentException::class + } + private fun generateAlias() = UUID.randomUUID().toString() } + +private fun ByteArray.toBase64NoPadding(): String { + return Base64.encodeToString(this, Base64.NO_PADDING or Base64.NO_WRAP) +} + +private fun String.fromBase64(): ByteArray { + return Base64.decode(this, Base64.DEFAULT) +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt index daf6b733..6cf01d4a 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestMatrixComponent.kt @@ -20,7 +20,9 @@ import android.content.Context import dagger.BindsInstance import dagger.Component import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.securestorage.SecureStorageModule import org.matrix.android.sdk.internal.auth.AuthModule +import org.matrix.android.sdk.internal.debug.DebugModule import org.matrix.android.sdk.internal.di.MatrixComponent import org.matrix.android.sdk.internal.di.MatrixModule import org.matrix.android.sdk.internal.di.MatrixScope @@ -36,8 +38,10 @@ import org.matrix.android.sdk.internal.util.system.SystemModule NetworkModule::class, AuthModule::class, RawModule::class, + DebugModule::class, SettingsModule::class, - SystemModule::class + SystemModule::class, + SecureStorageModule::class, ] ) @MatrixScope @@ -49,7 +53,7 @@ internal interface TestMatrixComponent : MatrixComponent { interface Factory { fun create( @BindsInstance context: Context, - @BindsInstance matrixConfiguration: MatrixConfiguration + @BindsInstance matrixConfiguration: MatrixConfiguration, ): TestMatrixComponent } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt index 0cc0ef57..38136ff5 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt @@ -28,6 +28,7 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.getStateEvent @@ -73,27 +74,37 @@ class SpaceCreationTest : InstrumentedTest { // assertEquals(topic, syncedSpace.asRoom().roomSummary()?., "Room topic should be set") assertNotNull("Space should be found by Id", syncedSpace) - val creationEvent = syncedSpace!!.asRoom().getStateEvent(EventType.STATE_ROOM_CREATE) - val createContent = creationEvent?.content.toModel<RoomCreateContent>() + val createContent = syncedSpace!!.asRoom() + .getStateEvent(EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty) + ?.content + ?.toModel<RoomCreateContent>() assertEquals("Room type should be space", RoomType.SPACE, createContent?.type) var powerLevelsContent: PowerLevelsContent? = null commonTestHelper.waitWithLatch { latch -> commonTestHelper.retryPeriodicallyWithLatch(latch) { - val toModel = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)?.content.toModel<PowerLevelsContent>() - powerLevelsContent = toModel - toModel != null + powerLevelsContent = syncedSpace.asRoom() + .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) + ?.content + ?.toModel<PowerLevelsContent>() + powerLevelsContent != null } } assertEquals("Space-rooms should be created with a power level for events_default of 100", 100, powerLevelsContent?.eventsDefault) - val guestAccess = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_GUEST_ACCESS)?.content - ?.toModel<RoomGuestAccessContent>()?.guestAccess + val guestAccess = syncedSpace.asRoom() + .getStateEvent(EventType.STATE_ROOM_GUEST_ACCESS, QueryStringValue.IsEmpty) + ?.content + ?.toModel<RoomGuestAccessContent>() + ?.guestAccess assertEquals("Public space room should be peekable by guest", GuestAccess.CanJoin, guestAccess) - val historyVisibility = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY)?.content - ?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility + val historyVisibility = syncedSpace.asRoom() + .getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty) + ?.content + ?.toModel<RoomHistoryVisibilityContent>() + ?.historyVisibility assertEquals("Public space room should be world readable", RoomHistoryVisibility.WORLD_READABLE, historyVisibility) } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt index 53962514..63ca9634 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt @@ -569,8 +569,9 @@ class SpaceHierarchyTest : InstrumentedTest { commonTestHelper.waitWithLatch { val room = bobSession.getRoom(bobRoomId)!! val currentPLContent = room - .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS) - ?.let { it.content.toModel<PowerLevelsContent>() } + .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) + ?.content + .toModel<PowerLevelsContent>() val newPowerLevelsContent = currentPLContent ?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value) @@ -583,7 +584,7 @@ class SpaceHierarchyTest : InstrumentedTest { commonTestHelper.waitWithLatch { latch -> commonTestHelper.retryPeriodicallyWithLatch(latch) { val powerLevelsHelper = aliceSession.getRoom(bobRoomId)!! - .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS) + .getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) ?.content ?.toModel<PowerLevelsContent>() ?.let { PowerLevelsHelper(it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt index 97920170..953ebddc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt @@ -17,6 +17,8 @@ package org.matrix.android.sdk.api import android.content.Context +import android.os.Handler +import android.os.Looper import androidx.lifecycle.ProcessLifecycleOwner import androidx.work.Configuration import androidx.work.WorkManager @@ -25,10 +27,12 @@ import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService +import org.matrix.android.sdk.api.debug.DebugService import org.matrix.android.sdk.api.legacy.LegacySessionImporter import org.matrix.android.sdk.api.network.ApiInterceptorListener import org.matrix.android.sdk.api.network.ApiPath import org.matrix.android.sdk.api.raw.RawService +import org.matrix.android.sdk.api.securestorage.SecureStorageService import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.di.DaggerMatrixComponent @@ -54,6 +58,7 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) { @Inject internal lateinit var legacySessionImporter: LegacySessionImporter @Inject internal lateinit var authenticationService: AuthenticationService @Inject internal lateinit var rawService: RawService + @Inject internal lateinit var debugService: DebugService @Inject internal lateinit var userAgentHolder: UserAgentHolder @Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver @Inject internal lateinit var olmManager: OlmManager @@ -62,6 +67,9 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) { @Inject internal lateinit var apiInterceptor: ApiInterceptor @Inject internal lateinit var matrixWorkerFactory: MatrixWorkerFactory @Inject internal lateinit var lightweightSettingsStorage: LightweightSettingsStorage + @Inject internal lateinit var secureStorageService: SecureStorageService + + private val uiHandler = Handler(Looper.getMainLooper()) init { val appContext = context.applicationContext @@ -74,7 +82,9 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) { .build() WorkManager.initialize(appContext, configuration) } - ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver) + uiHandler.post { + ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver) + } } /** @@ -93,6 +103,11 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) { */ fun rawService() = rawService + /** + * Return the DebugService. + */ + fun debugService() = debugService + /** * Return the LightweightSettingsStorage. */ @@ -108,6 +123,11 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) { */ fun legacySessionImporter() = legacySessionImporter + /** + * Returns the SecureStorageService used to encrypt and decrypt sensitive data. + */ + fun secureStorageService(): SecureStorageService = secureStorageService + /** * Get the worker factory. The returned value has to be provided to `WorkConfiguration.Builder()`. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt index 21106fba..893e90fb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api import okhttp3.ConnectionSpec +import okhttp3.Interceptor import org.matrix.android.sdk.api.crypto.MXCryptoConfig import java.net.Proxy @@ -65,4 +66,8 @@ data class MatrixConfiguration( * Thread messages default enable/disabled value. */ val threadMessagesEnabledDefault: Boolean = false, + /** + * List of network interceptors, they will be added when building an OkHttp client. + */ + val networkInterceptors: List<Interceptor> = emptyList(), ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt index 7d1407c0..5b6c1897 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt @@ -21,5 +21,6 @@ data class LoginFlowResult( val ssoIdentityProviders: List<SsoIdentityProvider>?, val isLoginAndRegistrationSupported: Boolean, val homeServerUrl: String, - val isOutdatedHomeserver: Boolean + val isOutdatedHomeserver: Boolean, + val isLogoutDevicesSupported: Boolean ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt index 5b8d2328..145cdbdc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt @@ -72,7 +72,9 @@ interface LoginWizard { * Confirm the new password, once the user has checked their email * When this method succeed, tha account password will be effectively modified. * - * @param newPassword the desired new password + * @param newPassword the desired new password. + * @param logoutAllDevices defaults to true, all devices will be logged out. False values will only be taken into account + * if [org.matrix.android.sdk.api.auth.data.LoginFlowResult.isLogoutDevicesSupported] is true. */ - suspend fun resetPasswordMailConfirmed(newPassword: String) + suspend fun resetPasswordMailConfirmed(newPassword: String, logoutAllDevices: Boolean = true) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/debug/DebugService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/debug/DebugService.kt new file mode 100644 index 00000000..d0cee088 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/debug/DebugService.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 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.debug + +import io.realm.RealmConfiguration + +/** + * Useful methods to access to some private data managed by the SDK. + */ +interface DebugService { + /** + * Get all the available Realm Configuration. + */ + fun getAllRealmConfigurations(): List<RealmConfiguration> + + /** + * Prints out info on DB size to logcat. + */ + fun logDbUsageInfo() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt index f08c8688..d3f6ec22 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt @@ -16,6 +16,11 @@ package org.matrix.android.sdk.api.query +/** + * Only a subset of [QueryStringValue] are applicable to query the `stateKey` of a state event. + */ +sealed interface QueryStateEventValue + /** * Basic query language. All these cases are mutually exclusive. */ @@ -33,22 +38,22 @@ sealed interface QueryStringValue { /** * The tested field has to be not null. */ - object IsNotNull : QueryStringValue + object IsNotNull : QueryStringValue, QueryStateEventValue /** * The tested field has to be empty. */ - object IsEmpty : QueryStringValue + object IsEmpty : QueryStringValue, QueryStateEventValue /** - * The tested field has to not empty. + * The tested field has to be not empty. */ - object IsNotEmpty : QueryStringValue + object IsNotEmpty : QueryStringValue, QueryStateEventValue /** * Interface to check String content. */ - sealed interface ContentQueryStringValue : QueryStringValue { + sealed interface ContentQueryStringValue : QueryStringValue, QueryStateEventValue { val string: String val case: Case } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtils.kt similarity index 82% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtils.kt index 8b35bd17..bd2a1078 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/SecretStoringUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecretStoringUtils.kt @@ -16,7 +16,7 @@ @file:Suppress("DEPRECATION") -package org.matrix.android.sdk.internal.session.securestorage +package org.matrix.android.sdk.api.securestorage import android.annotation.SuppressLint import android.content.Context @@ -25,7 +25,7 @@ import android.security.KeyPairGeneratorSpec import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import androidx.annotation.RequiresApi -import org.matrix.android.sdk.internal.util.system.BuildVersionSdkIntProvider +import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider import timber.log.Timber import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -80,9 +80,11 @@ import javax.security.auth.x500.X500Principal * Important: Keys stored in the keystore can be wiped out (depends of the OS version, like for example if you * add a pin or change the schema); So you might and with a useless pile of bytes. */ -internal class SecretStoringUtils @Inject constructor( +class SecretStoringUtils @Inject constructor( private val context: Context, - private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider + private val keyStore: KeyStore, + private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider, + private val keyNeedsUserAuthentication: Boolean = false, ) { companion object { @@ -94,14 +96,24 @@ internal class SecretStoringUtils @Inject constructor( private const val FORMAT_1: Byte = 1 } - private val keyStore: KeyStore by lazy { - KeyStore.getInstance(ANDROID_KEY_STORE).apply { - load(null) + private val secureRandom = SecureRandom() + + /** + * Allows creation of the crypto keys associated witht he [alias] before encrypting some value with it. + * @return A [KeyStore.Entry] with the keys. + */ + @SuppressLint("NewApi") + fun ensureKey(alias: String): KeyStore.Entry { + when { + buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> getOrGenerateSymmetricKeyForAliasM(alias) + else -> getOrGenerateKeyPairForAlias(alias).privateKey } + return keyStore.getEntry(alias, null) } - private val secureRandom = SecureRandom() - + /** + * Deletes the key associated with the [keyAlias] and logs any [KeyStoreException] that could happen. + */ fun safeDeleteKey(keyAlias: String) { try { keyStore.deleteEntry(keyAlias) @@ -121,24 +133,24 @@ internal class SecretStoringUtils @Inject constructor( */ @SuppressLint("NewApi") @Throws(Exception::class) - fun securelyStoreString(secret: String, keyAlias: String): ByteArray { + fun securelyStoreBytes(secret: ByteArray, keyAlias: String): ByteArray { return when { - buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> encryptStringM(secret, keyAlias) - else -> encryptString(secret, keyAlias) + buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> encryptBytesM(secret, keyAlias) + else -> encryptBytes(secret, keyAlias) } } /** - * Decrypt a secret that was encrypted by #securelyStoreString(). + * Decrypt a secret that was encrypted by [securelyStoreBytes]. */ @SuppressLint("NewApi") @Throws(Exception::class) - fun loadSecureSecret(encrypted: ByteArray, keyAlias: String): String { + fun loadSecureSecretBytes(encrypted: ByteArray, keyAlias: String): ByteArray { encrypted.inputStream().use { inputStream -> // First get the format return when (val format = inputStream.read().toByte()) { - FORMAT_API_M -> decryptStringM(inputStream, keyAlias) - FORMAT_1 -> decryptString(inputStream, keyAlias) + FORMAT_API_M -> decryptBytesM(inputStream, keyAlias) + FORMAT_1 -> decryptBytes(inputStream, keyAlias) else -> throw IllegalArgumentException("Unknown format $format") } } @@ -162,6 +174,22 @@ internal class SecretStoringUtils @Inject constructor( } } + fun getEncryptCipher(alias: String): Cipher { + val key = when (val keyEntry = ensureKey(alias)) { + is KeyStore.SecretKeyEntry -> keyEntry.secretKey + is KeyStore.PrivateKeyEntry -> keyEntry.certificate.publicKey + else -> throw IllegalStateException("Unknown KeyEntry type.") + } + val cipherMode = when { + buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> AES_MODE + else -> RSA_MODE + } + val cipher = Cipher.getInstance(cipherMode) + cipher.init(Cipher.ENCRYPT_MODE, key) + return cipher + } + + @SuppressLint("NewApi") @RequiresApi(Build.VERSION_CODES.M) private fun getOrGenerateSymmetricKeyForAliasM(alias: String): SecretKey { val secretKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry) @@ -176,6 +204,13 @@ internal class SecretStoringUtils @Inject constructor( .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setKeySize(128) + .apply { + setUserAuthenticationRequired(keyNeedsUserAuthentication) + if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.N) { + setInvalidatedByBiometricEnrollment(true) + } + } + .setUserAuthenticationRequired(keyNeedsUserAuthentication) .build() generator.init(keyGenSpec) return generator.generateKey() @@ -216,19 +251,16 @@ internal class SecretStoringUtils @Inject constructor( } @RequiresApi(Build.VERSION_CODES.M) - private fun encryptStringM(text: String, keyAlias: String): ByteArray { - val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias) - - val cipher = Cipher.getInstance(AES_MODE) - cipher.init(Cipher.ENCRYPT_MODE, secretKey) + private fun encryptBytesM(byteArray: ByteArray, keyAlias: String): ByteArray { + val cipher = getEncryptCipher(keyAlias) val iv = cipher.iv // we happen the iv to the final result - val encryptedBytes: ByteArray = cipher.doFinal(text.toByteArray(Charsets.UTF_8)) + val encryptedBytes: ByteArray = cipher.doFinal(byteArray) return formatMMake(iv, encryptedBytes) } @RequiresApi(Build.VERSION_CODES.M) - private fun decryptStringM(inputStream: InputStream, keyAlias: String): String { + private fun decryptBytesM(inputStream: InputStream, keyAlias: String): ByteArray { val (iv, encryptedText) = formatMExtract(inputStream) val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias) @@ -237,10 +269,10 @@ internal class SecretStoringUtils @Inject constructor( val spec = GCMParameterSpec(128, iv) cipher.init(Cipher.DECRYPT_MODE, secretKey, spec) - return String(cipher.doFinal(encryptedText), Charsets.UTF_8) + return cipher.doFinal(encryptedText) } - private fun encryptString(text: String, keyAlias: String): ByteArray { + private fun encryptBytes(byteArray: ByteArray, keyAlias: String): ByteArray { // we generate a random symmetric key val key = ByteArray(16) secureRandom.nextBytes(key) @@ -252,12 +284,12 @@ internal class SecretStoringUtils @Inject constructor( val cipher = Cipher.getInstance(AES_MODE) cipher.init(Cipher.ENCRYPT_MODE, sKey) val iv = cipher.iv - val encryptedBytes: ByteArray = cipher.doFinal(text.toByteArray(Charsets.UTF_8)) + val encryptedBytes: ByteArray = cipher.doFinal(byteArray) return format1Make(encryptedKey, iv, encryptedBytes) } - private fun decryptString(inputStream: InputStream, keyAlias: String): String { + private fun decryptBytes(inputStream: InputStream, keyAlias: String): ByteArray { val (encryptedKey, iv, encrypted) = format1Extract(inputStream) // we need to decrypt the key @@ -266,16 +298,13 @@ internal class SecretStoringUtils @Inject constructor( val spec = GCMParameterSpec(128, iv) cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sKeyBytes, "AES"), spec) - return String(cipher.doFinal(encrypted), Charsets.UTF_8) + return cipher.doFinal(encrypted) } @RequiresApi(Build.VERSION_CODES.M) @Throws(IOException::class) private fun saveSecureObjectM(keyAlias: String, output: OutputStream, writeObject: Any) { - val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias) - - val cipher = Cipher.getInstance(AES_MODE) - cipher.init(Cipher.ENCRYPT_MODE, secretKey/*, spec*/) + val cipher = getEncryptCipher(keyAlias) val iv = cipher.iv val bos1 = ByteArrayOutputStream() @@ -362,10 +391,8 @@ internal class SecretStoringUtils @Inject constructor( @Throws(Exception::class) private fun rsaEncrypt(alias: String, secret: ByteArray): ByteArray { - val privateKeyEntry = getOrGenerateKeyPairForAlias(alias) // Encrypt the text - val inputCipher = Cipher.getInstance(RSA_MODE) - inputCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.certificate.publicKey) + val inputCipher = getEncryptCipher(alias) val outputStream = ByteArrayOutputStream() CipherOutputStream(outputStream, inputCipher).use { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecureStorageModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecureStorageModule.kt new file mode 100644 index 00000000..37a40fd6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecureStorageModule.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2022 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.securestorage + +import android.content.Context +import dagger.Binds +import dagger.Module +import dagger.Provides +import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider +import org.matrix.android.sdk.api.util.DefaultBuildVersionSdkIntProvider +import java.security.KeyStore + +@Module +internal abstract class SecureStorageModule { + + @Module + companion object { + @Provides + fun provideKeyStore(): KeyStore = KeyStore.getInstance("AndroidKeyStore").also { it.load(null) } + + @Provides + fun provideSecretStoringUtils( + context: Context, + keyStore: KeyStore, + buildVersionSdkIntProvider: BuildVersionSdkIntProvider, + ): SecretStoringUtils = SecretStoringUtils(context, keyStore, buildVersionSdkIntProvider) + } + + @Binds + abstract fun bindBuildVersionSdkIntProvider(provider: DefaultBuildVersionSdkIntProvider): BuildVersionSdkIntProvider +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SecureStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecureStorageService.kt similarity index 93% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SecureStorageService.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecureStorageService.kt index 6b75c94c..e217611d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SecureStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/securestorage/SecureStorageService.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.session.securestorage +package org.matrix.android.sdk.api.securestorage import java.io.InputStream import java.io.OutputStream diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index b3a62909..1b01239d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api.session import androidx.annotation.MainThread +import io.realm.RealmConfiguration import okhttp3.OkHttpClient import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.SessionParams @@ -46,7 +47,6 @@ import org.matrix.android.sdk.api.session.pushrules.PushRuleService import org.matrix.android.sdk.api.session.room.RoomDirectoryService import org.matrix.android.sdk.api.session.room.RoomService import org.matrix.android.sdk.api.session.search.SearchService -import org.matrix.android.sdk.api.session.securestorage.SecureStorageService import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.session.signout.SignOutService import org.matrix.android.sdk.api.session.space.SpaceService @@ -199,11 +199,6 @@ interface Session { */ fun syncService(): SyncService - /** - * Returns the SecureStorageService associated with the session. - */ - fun secureStorageService(): SecureStorageService - /** * Returns the ProfileService associated with the session. */ @@ -334,7 +329,12 @@ interface Session { fun getUiaSsoFallbackUrl(authenticationSessionId: String): String /** - * Maintenance API, allows to print outs info on DB size to logcat. + * Debug API, will print out info on DB size to logcat. */ fun logDbUsageInfo() + + /** + * Debug API, return the list of all RealmConfiguration used by this session. + */ + fun getRealmConfigurations(): List<RealmConfiguration> } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt index e3d52adf..094c66f6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/account/AccountService.kt @@ -24,13 +24,13 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor interface AccountService { /** * Ask the homeserver to change the password. + * * @param password Current password. * @param newPassword New password + * @param logoutAllDevices defaults to true, all devices will be logged out. False values will only be taken into account + * if [org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities.canControlLogoutDevices] is true. */ - suspend fun changePassword( - password: String, - newPassword: String - ) + suspend fun changePassword(password: String, newPassword: String, logoutAllDevices: Boolean = true) /** * Deactivate the account. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 9cc87b6f..638da118 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -171,8 +171,6 @@ interface CryptoService { fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap<Int> fun getWithHeldMegolmSession(roomId: String, sessionId: String): RoomKeyWithHeldContent? - fun logDbUsageInfo() - /** * Perform any background tasks that can be done before a message is ready to * send, in order to speed up sending of the message. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt index c78fb9cf..b5d6d891 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt @@ -54,7 +54,12 @@ data class HomeServerCapabilities( /** * True if the home server support threading. */ - val canUseThreading: Boolean = false + val canUseThreading: Boolean = false, + + /** + * True if the home server supports controlling the logout of all devices when changing password. + */ + val canControlLogoutDevices: Boolean = false ) { enum class RoomCapabilitySupport { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt index 0e631427..b30c6055 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomExtensions.kt @@ -16,18 +16,21 @@ package org.matrix.android.sdk.api.session.room -import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent /** * Get a TimelineEvent using the TimelineService of a Room. + * @param eventId The id of the event to retrieve */ fun Room.getTimelineEvent(eventId: String): TimelineEvent? = timelineService().getTimelineEvent(eventId) /** * Get a StateEvent using the StateService of a Room. + * @param eventType The type of the event, see [org.matrix.android.sdk.api.session.events.model.EventType]. + * @param stateKey the query which will be done on the stateKey. */ -fun Room.getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event? = +fun Room.getStateEvent(eventType: String, stateKey: QueryStateEventValue): Event? = stateService().getStateEvent(eventType, stateKey) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt index dd48d51f..ada3dc85 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt @@ -16,12 +16,58 @@ package org.matrix.android.sdk.api.session.room.location +import androidx.annotation.MainThread import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary +import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.api.util.Optional /** * Manage all location sharing related features. */ interface LocationSharingService { + /** + * Send a static location event to the room. + * @param latitude required latitude of the location + * @param longitude required longitude of the location + * @param uncertainty Accuracy of the location in meters + * @param isUserLocation indicates whether the location data corresponds to the user location or not (pinned location) + */ + suspend fun sendStaticLocation(latitude: Double, longitude: Double, uncertainty: Double?, isUserLocation: Boolean): Cancelable + + /** + * Send a live location event to the room. + * To get the beacon info event id, [startLiveLocationShare] must be called before sending live location updates. + * @param beaconInfoEventId event id of the initial beacon info state event + * @param latitude required latitude of the location + * @param longitude required longitude of the location + * @param uncertainty Accuracy of the location in meters + */ + suspend fun sendLiveLocation(beaconInfoEventId: String, latitude: Double, longitude: Double, uncertainty: Double?): Cancelable + + /** + * Starts sharing live location in the room. + * @param timeoutMillis timeout of the live in milliseconds + * @return the result of the update of the live + */ + suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult + + /** + * Stops sharing live location in the room. + * @return the result of the update of the live + */ + suspend fun stopLiveLocationShare(): UpdateLiveLocationShareResult + + /** + * Returns a LiveData on the list of current running live location shares. + */ + @MainThread fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>> + + /** + * Returns a LiveData on the live location share summary with the given eventId. + * @param beaconInfoEventId event id of the initial beacon info state event + */ + @MainThread + fun getLiveLocationShareSummary(beaconInfoEventId: String): LiveData<Optional<LiveLocationShareAggregatedSummary>> } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/UpdateLiveLocationShareResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/UpdateLiveLocationShareResult.kt new file mode 100644 index 00000000..6f8c03be --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/UpdateLiveLocationShareResult.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 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.room.location + +/** + * Represents the result of an update of live location share like a start or a stop. + */ +sealed interface UpdateLiveLocationShareResult { + data class Success(val beaconEventId: String) : UpdateLiveLocationShareResult + data class Failure(val error: Throwable) : UpdateLiveLocationShareResult +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt index a1fd3bd2..e0a78461 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt @@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class LocationInfo( /** - * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location. + * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;u=uncertainty' like 'geo:40.05,29.24;u=30' representing this location. */ @Json(name = "uri") val geoUri: String? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt index 0a66a6e4..30420fd3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt @@ -35,7 +35,7 @@ data class MessageLocationContent( @Json(name = "body") override val body: String, /** - * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location. + * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;u=uncertainty' like 'geo:40.05,29.24;u=30' representing this location. */ @Json(name = "geo_uri") val geoUri: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt index 81b034a8..ee31d595 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt @@ -25,4 +25,7 @@ data class PollCreationInfo( @Json(name = "kind") val kind: PollType? = PollType.DISCLOSED_UNSTABLE, @Json(name = "max_selections") val maxSelections: Int = 1, @Json(name = "answers") val answers: List<PollAnswer>? = null -) +) { + + fun isUndisclosed() = kind in listOf(PollType.UNDISCLOSED_UNSTABLE, PollType.UNDISCLOSED) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 661c3be5..9cf06235 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -142,24 +142,6 @@ interface SendService { */ fun resendMediaMessage(localEcho: TimelineEvent): Cancelable - /** - * Send a location event to the room. - * @param latitude required latitude of the location - * @param longitude required longitude of the location - * @param uncertainty Accuracy of the location in meters - * @param isUserLocation indicates whether the location data corresponds to the user location or not - */ - fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?, isUserLocation: Boolean): Cancelable - - /** - * Send a live location event to the room. beacon_info state event has to be sent before sending live location updates. - * @param beaconInfoEventId event id of the initial beacon info state event - * @param latitude required latitude of the location - * @param longitude required longitude of the location - * @param uncertainty Accuracy of the location in meters - */ - fun sendLiveLocation(beaconInfoEventId: String, latitude: Double, longitude: Double, uncertainty: Double?): Cancelable - /** * Remove this failed message from the timeline. * @param localEcho the unsent local echo diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index c79171f1..6ca63c2c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.room.state import android.net.Uri import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility @@ -66,19 +66,6 @@ interface StateService { */ suspend fun deleteAvatar() - /** - * Stops sharing live location in the room. - * @param userId user id - */ - suspend fun stopLiveLocation(userId: String) - - /** - * Returns beacon info state event of a user. - * @param userId user id who is sharing location - * @param filterOnlyLive filters only ongoing live location sharing beacons if true else ended event is included - */ - suspend fun getLiveLocationBeaconInfo(userId: String, filterOnlyLive: Boolean): Event? - /** * Send a state event to the room. * @param eventType The type of event to send. @@ -93,28 +80,28 @@ interface StateService { * @param eventType An eventType. * @param stateKey the query which will be done on the stateKey */ - fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event? + fun getStateEvent(eventType: String, stateKey: QueryStateEventValue): Event? /** * Get a live state event of the room. * @param eventType An eventType. * @param stateKey the query which will be done on the stateKey */ - fun getStateEventLive(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<Optional<Event>> + fun getStateEventLive(eventType: String, stateKey: QueryStateEventValue): LiveData<Optional<Event>> /** * Get state events of the room. * @param eventTypes Set of eventType. If empty, all state events will be returned * @param stateKey the query which will be done on the stateKey */ - fun getStateEvents(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): List<Event> + fun getStateEvents(eventTypes: Set<String>, stateKey: QueryStateEventValue): List<Event> /** * Get live state events of the room. * @param eventTypes Set of eventType to observe. If empty, all state events will be observed * @param stateKey the query which will be done on the stateKey */ - fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<List<Event>> + fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStateEventValue): LiveData<List<Event>> suspend fun setJoinRulePublic() suspend fun setJoinRuleInviteOnly() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt index 9e45fc12..6a9506fd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateServiceExtension.kt @@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent * Return true if a room can be joined by anyone (RoomJoinRules.PUBLIC). */ fun StateService.isPublic(): Boolean { - return getStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition) + return getStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.IsEmpty) ?.content ?.toModel<RoomJoinRulesContent>() ?.joinRules == RoomJoinRules.PUBLIC diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt index 8ad6500d..c2094f46 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.widgets import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.widgets.model.Widget @@ -49,7 +49,7 @@ interface WidgetService { */ fun getRoomWidgets( roomId: String, - widgetId: QueryStringValue = QueryStringValue.NoCondition, + widgetId: QueryStateEventValue, widgetTypes: Set<String>? = null, excludedTypes: Set<String>? = null ): List<Widget> @@ -70,7 +70,7 @@ interface WidgetService { */ fun getRoomWidgetsLive( roomId: String, - widgetId: QueryStringValue = QueryStringValue.NoCondition, + widgetId: QueryStateEventValue, widgetTypes: Set<String>? = null, excludedTypes: Set<String>? = null ): LiveData<List<Widget>> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/BuildVersionSdkIntProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt similarity index 87% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/BuildVersionSdkIntProvider.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt index 51565604..b7ea187e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/BuildVersionSdkIntProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/BuildVersionSdkIntProvider.kt @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.util.system +package org.matrix.android.sdk.api.util -internal interface BuildVersionSdkIntProvider { +interface BuildVersionSdkIntProvider { /** * Return the current version of the Android SDK. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/DefaultBuildVersionSdkIntProvider.kt similarity index 85% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/DefaultBuildVersionSdkIntProvider.kt index 806c6e97..7f0024ca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/DefaultBuildVersionSdkIntProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/DefaultBuildVersionSdkIntProvider.kt @@ -14,12 +14,12 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.util.system +package org.matrix.android.sdk.api.util import android.os.Build import javax.inject.Inject -internal class DefaultBuildVersionSdkIntProvider @Inject constructor() : +class DefaultBuildVersionSdkIntProvider @Inject constructor() : BuildVersionSdkIntProvider { override fun get() = Build.VERSION.SDK_INT } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt index bd2dac9e..5f5bb1f9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt @@ -40,6 +40,13 @@ internal class SessionManager @Inject constructor( return getOrCreateSessionComponent(sessionParams) } + fun getLastSession(): Session? { + val sessionParams = sessionParamsStore.getLast() + return sessionParams?.let { + getOrCreateSession(it) + } + } + fun getOrCreateSession(sessionParams: SessionParams): Session { return getOrCreateSessionComponent(sessionParams).session() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 61a42366..9d6b018a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -40,6 +40,7 @@ import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard import org.matrix.android.sdk.internal.auth.login.DirectLoginTask import org.matrix.android.sdk.internal.auth.registration.DefaultRegistrationWizard import org.matrix.android.sdk.internal.auth.version.Versions +import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk import org.matrix.android.sdk.internal.auth.version.isSupportedBySdk import org.matrix.android.sdk.internal.di.Unauthenticated @@ -73,10 +74,7 @@ internal class DefaultAuthenticationService @Inject constructor( } override fun getLastAuthenticatedSession(): Session? { - val sessionParams = sessionParamsStore.getLast() - return sessionParams?.let { - sessionManager.getOrCreateSession(it) - } + return sessionManager.getLastSession() } override suspend fun getLoginFlowOfSession(sessionId: String): LoginFlowResult { @@ -295,7 +293,8 @@ internal class DefaultAuthenticationService @Inject constructor( ssoIdentityProviders = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider, isLoginAndRegistrationSupported = versions.isLoginAndRegistrationSupportedBySdk(), homeServerUrl = homeServerUrl, - isOutdatedHomeserver = !versions.isSupportedBySdk() + isOutdatedHomeserver = !versions.isSupportedBySdk(), + isLogoutDevicesSupported = versions.doesServerSupportLogoutDevices() ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt index 20b056f1..656a4f67 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt @@ -121,12 +121,13 @@ internal class DefaultLoginWizard( .also { pendingSessionStore.savePendingSessionData(it) } } - override suspend fun resetPasswordMailConfirmed(newPassword: String) { + override suspend fun resetPasswordMailConfirmed(newPassword: String, logoutAllDevices: Boolean) { val resetPasswordData = pendingSessionData.resetPasswordData ?: throw IllegalStateException("Developer error - Must call resetPassword first") val param = ResetPasswordMailConfirmed.create( pendingSessionData.clientSecret, resetPasswordData.addThreePidRegistrationResponse.sid, - newPassword + newPassword, + logoutAllDevices ) executeRequest(null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordMailConfirmed.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordMailConfirmed.kt index 4e0c000f..01481f70 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordMailConfirmed.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/ResetPasswordMailConfirmed.kt @@ -30,13 +30,17 @@ internal data class ResetPasswordMailConfirmed( // the new password @Json(name = "new_password") - val newPassword: String? = null + val newPassword: String? = null, + + @Json(name = "logout_devices") + val logoutDevices: Boolean? = null ) { companion object { - fun create(clientSecret: String, sid: String, newPassword: String): ResetPasswordMailConfirmed { + fun create(clientSecret: String, sid: String, newPassword: String, logoutDevices: Boolean?): ResetPasswordMailConfirmed { return ResetPasswordMailConfirmed( auth = AuthParams.createForResetPassword(clientSecret, sid), - newPassword = newPassword + newPassword = newPassword, + logoutDevices = logoutDevices ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt index cd38b68a..75639c6a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/HomeServerVersion.kt @@ -58,6 +58,7 @@ internal data class HomeServerVersion( val r0_4_0 = HomeServerVersion(major = 0, minor = 4, patch = 0) val r0_5_0 = HomeServerVersion(major = 0, minor = 5, patch = 0) val r0_6_0 = HomeServerVersion(major = 0, minor = 6, patch = 0) + val r0_6_1 = HomeServerVersion(major = 0, minor = 6, patch = 1) val v1_3_0 = HomeServerVersion(major = 1, minor = 3, patch = 0) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt index cee4b121..915b2513 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/version/Versions.kt @@ -111,6 +111,15 @@ private fun Versions.doesServerSeparatesAddAndBind(): Boolean { unstableFeatures?.get(FEATURE_SEPARATE_ADD_AND_BIND) ?: false } +/** + * Indicate if the server supports MSC2457 `logout_devices` parameter when setting a new password. + * + * @return true if logout_devices is supported + */ +internal fun Versions.doesServerSupportLogoutDevices(): Boolean { + return getMaxVersion() >= HomeServerVersion.r0_6_1 +} + private fun Versions.getMaxVersion(): HomeServerVersion { return supportedVersions ?.mapNotNull { HomeServerVersion.parse(it) } 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 719f3665..e0bcde22 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 @@ -1298,10 +1298,6 @@ internal class DefaultCryptoService @Inject constructor( return cryptoStore.getWithHeldMegolmSession(roomId, sessionId) } - override fun logDbUsageInfo() { - cryptoStore.logDbUsageInfo() - } - override fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date") 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 b18de343..b5b8d8e9 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 @@ -513,6 +513,5 @@ internal interface IMXCryptoStore { fun setDeviceKeysUploaded(uploaded: Boolean) fun areDeviceKeysUploaded(): Boolean fun tidyUpDataBase() - fun logDbUsageInfo() fun getOutgoingRoomKeyRequests(inStates: Set<OutgoingRoomKeyRequestState>): List<OutgoingKeyRequest> } 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 c56e4d32..028d8f73 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 @@ -88,7 +88,6 @@ import org.matrix.android.sdk.internal.crypto.store.db.query.get import org.matrix.android.sdk.internal.crypto.store.db.query.getById import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper -import org.matrix.android.sdk.internal.database.tools.RealmDebugTools import org.matrix.android.sdk.internal.di.CryptoDatabase import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.MoshiProvider @@ -1709,11 +1708,4 @@ internal class RealmCryptoStore @Inject constructor( // Can we do something for WithHeldSessionEntity? } } - - /** - * Prints out database info. - */ - override fun logDbUsageInfo() { - RealmDebugTools(realmConfiguration).logInfo("Crypto") - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt index 9f123f0c..821663bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt @@ -62,7 +62,7 @@ internal class VerificationMessageProcessor @Inject constructor( // If the request is in the future by more than 5 minutes or more than 10 minutes in the past, // the message should be ignored by the receiver. - if (event.ageLocalTs != null && !VerificationService.isValidRequest(event.ageLocalTs, clock.epochMillis())) return Unit.also { + if (!VerificationService.isValidRequest(event.ageLocalTs, clock.epochMillis())) return Unit.also { Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated age:$event.ageLocalTs ms") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmKeysUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmKeysUtils.kt index b3a039d1..86355cea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmKeysUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmKeysUtils.kt @@ -21,7 +21,7 @@ import androidx.core.content.edit import io.realm.Realm import io.realm.RealmConfiguration import org.matrix.android.sdk.BuildConfig -import org.matrix.android.sdk.internal.session.securestorage.SecretStoringUtils +import org.matrix.android.sdk.api.securestorage.SecretStoringUtils import timber.log.Timber import java.security.SecureRandom import javax.inject.Inject @@ -40,7 +40,7 @@ import javax.inject.Inject */ internal class RealmKeysUtils @Inject constructor( context: Context, - private val secretStoringUtils: SecretStoringUtils + private val secretStoringUtils: SecretStoringUtils, ) { private val rng = SecureRandom() @@ -71,7 +71,7 @@ internal class RealmKeysUtils @Inject constructor( private fun createAndSaveKeyForDatabase(alias: String): ByteArray { val key = generateKeyForRealm() val encodedKey = Base64.encodeToString(key, Base64.NO_PADDING) - val toStore = secretStoringUtils.securelyStoreString(encodedKey, alias) + val toStore = secretStoringUtils.securelyStoreBytes(encodedKey.toByteArray(), alias) sharedPreferences.edit { putString("${ENCRYPTED_KEY_PREFIX}_$alias", Base64.encodeToString(toStore, Base64.NO_PADDING)) } @@ -85,7 +85,7 @@ internal class RealmKeysUtils @Inject constructor( private fun extractKeyForDatabase(alias: String): ByteArray { val encryptedB64 = sharedPreferences.getString("${ENCRYPTED_KEY_PREFIX}_$alias", null) val encryptedKey = Base64.decode(encryptedB64, Base64.NO_PADDING) - val b64 = secretStoringUtils.loadSecureSecret(encryptedKey, alias) + val b64 = secretStoringUtils.loadSecureSecretBytes(encryptedKey, alias) return Base64.decode(b64, Base64.NO_PADDING) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 6a8589bc..665567bf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -48,6 +48,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo027 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo028 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo029 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo030 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo031 import org.matrix.android.sdk.internal.util.Normalizer import timber.log.Timber import javax.inject.Inject @@ -62,7 +63,7 @@ internal class RealmSessionStoreMigration @Inject constructor( override fun equals(other: Any?) = other is RealmSessionStoreMigration override fun hashCode() = 1000 - val schemaVersion = 30L + val schemaVersion = 31L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.d("Migrating Realm Session from $oldVersion to $newVersion") @@ -97,5 +98,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 28) MigrateSessionTo028(realm).perform() if (oldVersion < 29) MigrateSessionTo029(realm).perform() if (oldVersion < 30) MigrateSessionTo030(realm).perform() + if (oldVersion < 31) MigrateSessionTo031(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt index 79a99cdf..0a6d4bf8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt @@ -271,7 +271,7 @@ private fun HashMap<String, RoomMemberContent?>.addSenderState(realm: Realm, roo * Create an EventEntity for the root thread event or get an existing one. */ private fun createEventEntity(realm: Realm, roomId: String, event: Event, currentTimeMillis: Long): EventEntity { - val ageLocalTs = event.unsignedData?.age?.let { currentTimeMillis - it } + val ageLocalTs = currentTimeMillis - (event.unsignedData?.age ?: 0) return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt index 5b60c536..0f0a847c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt @@ -130,7 +130,7 @@ internal fun EventEntity.asDomain(castJsonNumbers: Boolean = false): Event { internal fun Event.toEntity( roomId: String, sendState: SendState, - ageLocalTs: Long?, + ageLocalTs: Long, contentToInject: String? = null ): EventEntity { return EventMapper.map(this, roomId).apply { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt index 20af4353..184a0108 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt @@ -42,7 +42,8 @@ internal object HomeServerCapabilitiesMapper { lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported, defaultIdentityServerUrl = entity.defaultIdentityServerUrl, roomVersions = mapRoomVersion(entity.roomVersionsJson), - canUseThreading = entity.canUseThreading + canUseThreading = entity.canUseThreading, + canControlLogoutDevices = entity.canControlLogoutDevices ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt index 9460e4c6..4a4c730a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt @@ -16,15 +16,17 @@ package org.matrix.android.sdk.internal.database.mapper +import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity import javax.inject.Inject -internal class LiveLocationShareAggregatedSummaryMapper @Inject constructor() { +internal class LiveLocationShareAggregatedSummaryMapper @Inject constructor() : + Monarchy.Mapper<LiveLocationShareAggregatedSummary, LiveLocationShareAggregatedSummaryEntity> { - fun map(entity: LiveLocationShareAggregatedSummaryEntity): LiveLocationShareAggregatedSummary { + override fun map(entity: LiveLocationShareAggregatedSummaryEntity): LiveLocationShareAggregatedSummary { return LiveLocationShareAggregatedSummary( userId = entity.userId, isActive = entity.isActive, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo029.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo029.kt index aebca11c..17dc0f7c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo029.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo029.kt @@ -25,7 +25,7 @@ import org.matrix.android.sdk.internal.util.database.RealmMigrator * Migrating to: * Live location sharing aggregated summary: adding new field userId. */ -internal class MigrateSessionTo029(realm: DynamicRealm) : RealmMigrator(realm, 28) { +internal class MigrateSessionTo029(realm: DynamicRealm) : RealmMigrator(realm, 29) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("LiveLocationShareAggregatedSummaryEntity") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt index b9c611f5..5d24b143 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt @@ -52,6 +52,10 @@ internal class MigrateSessionTo030(realm: DynamicRealm) : RealmMigrator(realm, 3 timelineEvents.deleteAllFromRealm() } chunks.deleteAllFromRealm() - Timber.d("MigrateSessionTo030: $nbOfDeletedChunks deleted chunk(s), $nbOfDeletedTimelineEvents deleted TimelineEvent(s) and $nbOfDeletedEvents deleted Event(s).") + Timber.d( + "MigrateSessionTo030: $nbOfDeletedChunks deleted chunk(s)," + + " $nbOfDeletedTimelineEvents deleted TimelineEvent(s)" + + " and $nbOfDeletedEvents deleted Event(s)." + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo031.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo031.kt new file mode 100644 index 00000000..e278b747 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo031.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 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.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields +import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo031(realm: DynamicRealm) : RealmMigrator(realm, 31) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("HomeServerCapabilitiesEntity") + ?.addField(HomeServerCapabilitiesEntityFields.CAN_CONTROL_LOGOUT_DEVICES, Boolean::class.java) + ?.forceRefreshOfHomeServerCapabilities() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt index 47a83f0e..9d90973f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt @@ -29,7 +29,8 @@ internal open class HomeServerCapabilitiesEntity( var lastVersionIdentityServerSupported: Boolean = false, var defaultIdentityServerUrl: String? = null, var lastUpdatedTimestamp: Long = 0L, - var canUseThreading: Boolean = false + var canUseThreading: Boolean = false, + var canControlLogoutDevices: Boolean = false ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt index 7dfeb688..d69f251f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt @@ -76,7 +76,7 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.findActiveLiveIn realm: Realm, roomId: String, userId: String, - ignoredEventId: String + ignoredEventId: String, ): List<LiveLocationShareAggregatedSummaryEntity> { return LiveLocationShareAggregatedSummaryEntity .whereRoomId(realm, roomId = roomId) @@ -84,6 +84,7 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.findActiveLiveIn .equalTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true) .notEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, ignoredEventId) .findAll() + .toList() } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DebugModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DebugModule.kt new file mode 100644 index 00000000..f392d39d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DebugModule.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2022 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.debug + +import dagger.Binds +import dagger.Module +import org.matrix.android.sdk.api.debug.DebugService + +@Module +internal abstract class DebugModule { + + @Binds + abstract fun bindDebugService(service: DefaultDebugService): DebugService +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DefaultDebugService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DefaultDebugService.kt new file mode 100644 index 00000000..3f2e6faf --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/debug/DefaultDebugService.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 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.debug + +import io.realm.RealmConfiguration +import org.matrix.android.sdk.api.debug.DebugService +import org.matrix.android.sdk.internal.SessionManager +import org.matrix.android.sdk.internal.database.tools.RealmDebugTools +import org.matrix.android.sdk.internal.di.AuthDatabase +import org.matrix.android.sdk.internal.di.GlobalDatabase +import javax.inject.Inject + +internal class DefaultDebugService @Inject constructor( + @AuthDatabase private val realmConfigurationAuth: RealmConfiguration, + @GlobalDatabase private val realmConfigurationGlobal: RealmConfiguration, + private val sessionManager: SessionManager, +) : DebugService { + + override fun getAllRealmConfigurations(): List<RealmConfiguration> { + return sessionManager.getLastSession()?.getRealmConfigurations().orEmpty() + + realmConfigurationAuth + + realmConfigurationGlobal + } + + override fun logDbUsageInfo() { + RealmDebugTools(realmConfigurationAuth).logInfo("Auth") + RealmDebugTools(realmConfigurationGlobal).logInfo("Global") + sessionManager.getLastSession()?.logDbUsageInfo() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt index 09591664..44ec90ed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixComponent.kt @@ -28,10 +28,13 @@ import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.raw.RawService +import org.matrix.android.sdk.api.securestorage.SecureStorageModule +import org.matrix.android.sdk.api.securestorage.SecureStorageService import org.matrix.android.sdk.api.settings.LightweightSettingsStorage import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.auth.AuthModule import org.matrix.android.sdk.internal.auth.SessionParamsStore +import org.matrix.android.sdk.internal.debug.DebugModule import org.matrix.android.sdk.internal.raw.RawModule import org.matrix.android.sdk.internal.session.MockHttpInterceptor import org.matrix.android.sdk.internal.session.TestInterceptor @@ -49,9 +52,11 @@ import java.io.File NetworkModule::class, AuthModule::class, RawModule::class, + DebugModule::class, SettingsModule::class, SystemModule::class, - NoOpTestModule::class + NoOpTestModule::class, + SecureStorageModule::class, ] ) @MatrixScope @@ -94,6 +99,8 @@ internal interface MatrixComponent { fun sessionManager(): SessionManager + fun secureStorageService(): SecureStorageService + fun matrixWorkerFactory(): MatrixWorkerFactory fun inject(matrix: Matrix) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt index 862cf463..b5b46a3f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.internal.network.ApiInterceptor import org.matrix.android.sdk.internal.network.TimeOutInterceptor import org.matrix.android.sdk.internal.network.UserAgentInterceptor +import org.matrix.android.sdk.internal.network.httpclient.applyMatrixConfiguration import org.matrix.android.sdk.internal.network.interceptors.CurlLoggingInterceptor import org.matrix.android.sdk.internal.network.interceptors.FormattedJsonHttpLogger import java.util.Collections @@ -92,11 +93,9 @@ internal object NetworkModule { if (BuildConfig.LOG_PRIVATE_DATA) { addInterceptor(curlLoggingInterceptor) } - matrixConfiguration.proxy?.let { - proxy(it) - } } .connectionSpecs(Collections.singletonList(spec)) + .applyMatrixConfiguration(matrixConfiguration) .build() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/httpclient/OkHttpClientUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/httpclient/OkHttpClientUtil.kt index 3920c3b5..1c395c2d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/httpclient/OkHttpClientUtil.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/httpclient/OkHttpClientUtil.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.network.httpclient import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.internal.network.AccessTokenInterceptor import org.matrix.android.sdk.internal.network.interceptors.CurlLoggingInterceptor @@ -51,3 +52,17 @@ internal fun OkHttpClient.Builder.addSocketFactory(homeServerConnectionConfig: H return this } + +internal fun OkHttpClient.Builder.applyMatrixConfiguration(matrixConfiguration: MatrixConfiguration): OkHttpClient.Builder { + matrixConfiguration.proxy?.let { + proxy(it) + } + + // Move networkInterceptors provided in the configuration after all the others + interceptors().removeAll(matrixConfiguration.networkInterceptors) + matrixConfiguration.networkInterceptors.forEach { + addInterceptor(it) + } + + return this +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/DefaultSecureStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/securestorage/DefaultSecureStorageService.kt similarity index 86% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/DefaultSecureStorageService.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/securestorage/DefaultSecureStorageService.kt index ef8133dd..8f6605d6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/securestorage/DefaultSecureStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/securestorage/DefaultSecureStorageService.kt @@ -14,9 +14,10 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.securestorage +package org.matrix.android.sdk.internal.securestorage -import org.matrix.android.sdk.api.session.securestorage.SecureStorageService +import org.matrix.android.sdk.api.securestorage.SecretStoringUtils +import org.matrix.android.sdk.api.securestorage.SecureStorageService import java.io.InputStream import java.io.OutputStream import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index 32269c9a..7c50a0ff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -55,7 +55,6 @@ import org.matrix.android.sdk.api.session.pushrules.PushRuleService import org.matrix.android.sdk.api.session.room.RoomDirectoryService import org.matrix.android.sdk.api.session.room.RoomService import org.matrix.android.sdk.api.session.search.SearchService -import org.matrix.android.sdk.api.session.securestorage.SecureStorageService import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.session.signout.SignOutService import org.matrix.android.sdk.api.session.space.SpaceService @@ -71,6 +70,9 @@ import org.matrix.android.sdk.internal.auth.SSO_UIA_FALLBACK_PATH import org.matrix.android.sdk.internal.auth.SessionParamsStore import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.database.tools.RealmDebugTools +import org.matrix.android.sdk.internal.di.ContentScannerDatabase +import org.matrix.android.sdk.internal.di.CryptoDatabase +import org.matrix.android.sdk.internal.di.IdentityDatabase import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate @@ -88,6 +90,9 @@ internal class DefaultSession @Inject constructor( override val sessionId: String, override val coroutineDispatchers: MatrixCoroutineDispatchers, @SessionDatabase private val realmConfiguration: RealmConfiguration, + @CryptoDatabase private val realmConfigurationCrypto: RealmConfiguration, + @IdentityDatabase private val realmConfigurationIdentity: RealmConfiguration, + @ContentScannerDatabase private val realmConfigurationContentScanner: RealmConfiguration, private val lifecycleObservers: Set<@JvmSuppressWildcards SessionLifecycleObserver>, private val sessionListeners: SessionListeners, private val roomService: Lazy<RoomService>, @@ -105,7 +110,6 @@ internal class DefaultSession @Inject constructor( private val cryptoService: Lazy<DefaultCryptoService>, private val defaultFileService: Lazy<FileService>, private val permalinkService: Lazy<PermalinkService>, - private val secureStorageService: Lazy<SecureStorageService>, private val profileService: Lazy<ProfileService>, private val syncService: Lazy<SyncService>, private val mediaService: Lazy<MediaService>, @@ -214,7 +218,6 @@ internal class DefaultSession @Inject constructor( override fun eventService(): EventService = eventService.get() override fun termsService(): TermsService = termsService.get() override fun syncService(): SyncService = syncService.get() - override fun secureStorageService(): SecureStorageService = secureStorageService.get() override fun profileService(): ProfileService = profileService.get() override fun presenceService(): PresenceService = presenceService.get() override fun accountService(): AccountService = accountService.get() @@ -265,5 +268,17 @@ internal class DefaultSession @Inject constructor( override fun logDbUsageInfo() { RealmDebugTools(realmConfiguration).logInfo("Session") + RealmDebugTools(realmConfigurationCrypto).logInfo("Crypto") + RealmDebugTools(realmConfigurationIdentity).logInfo("Identity") + RealmDebugTools(realmConfigurationContentScanner).logInfo("ContentScanner") + } + + override fun getRealmConfigurations(): List<RealmConfiguration> { + return listOf( + realmConfiguration, + realmConfigurationCrypto, + realmConfigurationIdentity, + realmConfigurationContentScanner, + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt index f01451b6..d3cae3ac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt @@ -20,6 +20,7 @@ import dagger.BindsInstance import dagger.Component import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.SessionParams +import org.matrix.android.sdk.api.securestorage.SecureStorageModule import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.internal.crypto.CryptoModule import org.matrix.android.sdk.internal.crypto.crosssigning.UpdateTrustWorker @@ -98,7 +99,8 @@ import org.matrix.android.sdk.internal.util.system.SystemModule ThirdPartyModule::class, SpaceModule::class, PresenceModule::class, - RequestModule::class + RequestModule::class, + SecureStorageModule::class, ] ) @SessionScope diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt index 756b9cef..0dae24e6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt @@ -19,12 +19,13 @@ package org.matrix.android.sdk.internal.session import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.Session import timber.log.Timber +import java.util.concurrent.CopyOnWriteArraySet import javax.inject.Inject @SessionScope internal class SessionListeners @Inject constructor() { - private val listeners = mutableSetOf<Session.Listener>() + private val listeners = CopyOnWriteArraySet<Session.Listener>() fun addListener(listener: Session.Listener) { synchronized(listeners) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index 2c2317de..f8a52f0b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -41,7 +41,6 @@ import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.openid.OpenIdService import org.matrix.android.sdk.api.session.permalinks.PermalinkService -import org.matrix.android.sdk.api.session.securestorage.SecureStorageService import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.session.typing.TypingUsersTracker import org.matrix.android.sdk.api.util.md5 @@ -73,6 +72,7 @@ import org.matrix.android.sdk.internal.network.PreferredNetworkCallbackStrategy import org.matrix.android.sdk.internal.network.RetrofitFactory import org.matrix.android.sdk.internal.network.httpclient.addAccessTokenInterceptor import org.matrix.android.sdk.internal.network.httpclient.addSocketFactory +import org.matrix.android.sdk.internal.network.httpclient.applyMatrixConfiguration import org.matrix.android.sdk.internal.network.interceptors.CurlLoggingInterceptor import org.matrix.android.sdk.internal.network.token.AccessTokenProvider import org.matrix.android.sdk.internal.network.token.HomeserverAccessTokenProvider @@ -92,7 +92,6 @@ import org.matrix.android.sdk.internal.session.room.prune.RedactionEventProcesso import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessorCoroutine import org.matrix.android.sdk.internal.session.room.tombstone.RoomTombstoneEventProcessor -import org.matrix.android.sdk.internal.session.securestorage.DefaultSecureStorageService import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker import org.matrix.android.sdk.internal.session.user.accountdata.DefaultSessionAccountDataService import org.matrix.android.sdk.internal.session.widgets.DefaultWidgetURLFormatter @@ -212,7 +211,7 @@ internal abstract class SessionModule { @UnauthenticatedWithCertificate fun providesOkHttpClientWithCertificate( @Unauthenticated okHttpClient: OkHttpClient, - homeServerConnectionConfig: HomeServerConnectionConfig + homeServerConnectionConfig: HomeServerConnectionConfig, ): OkHttpClient { return okHttpClient .newBuilder() @@ -228,7 +227,8 @@ internal abstract class SessionModule { @UnauthenticatedWithCertificate okHttpClient: OkHttpClient, @Authenticated accessTokenProvider: AccessTokenProvider, @SessionId sessionId: String, - @MockHttpInterceptor testInterceptor: TestInterceptor? + @MockHttpInterceptor testInterceptor: TestInterceptor?, + matrixConfiguration: MatrixConfiguration, ): OkHttpClient { return okHttpClient .newBuilder() @@ -239,6 +239,7 @@ internal abstract class SessionModule { addInterceptor(testInterceptor) } } + .applyMatrixConfiguration(matrixConfiguration) .build() } @@ -248,11 +249,13 @@ internal abstract class SessionModule { @UnauthenticatedWithCertificateWithProgress fun providesProgressOkHttpClient( @UnauthenticatedWithCertificate okHttpClient: OkHttpClient, - downloadProgressInterceptor: DownloadProgressInterceptor + downloadProgressInterceptor: DownloadProgressInterceptor, + matrixConfiguration: MatrixConfiguration, ): OkHttpClient { - return okHttpClient.newBuilder() + return okHttpClient + .newBuilder() .apply { - // Remove the previous CurlLoggingInterceptor, to add it after the accessTokenInterceptor + // Remove the previous CurlLoggingInterceptor, to add it after the downloadProgressInterceptor val existingCurlInterceptors = interceptors().filterIsInstance<CurlLoggingInterceptor>() interceptors().removeAll(existingCurlInterceptors) @@ -262,7 +265,9 @@ internal abstract class SessionModule { existingCurlInterceptors.forEach { addInterceptor(it) } - }.build() + } + .applyMatrixConfiguration(matrixConfiguration) + .build() } @JvmStatic @@ -360,9 +365,6 @@ internal abstract class SessionModule { @IntoSet abstract fun bindEventSenderProcessorAsSessionLifecycleObserver(processor: EventSenderProcessorCoroutine): SessionLifecycleObserver - @Binds - abstract fun bindSecureStorageService(service: DefaultSecureStorageService): SecureStorageService - @Binds abstract fun bindHomeServerCapabilitiesService(service: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordParams.kt index 1b958209..f6778327 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordParams.kt @@ -29,13 +29,17 @@ internal data class ChangePasswordParams( val auth: UserPasswordAuth? = null, @Json(name = "new_password") - val newPassword: String? = null + val newPassword: String? = null, + + @Json(name = "logout_devices") + val logoutDevices: Boolean = true ) { companion object { - fun create(userId: String, oldPassword: String, newPassword: String): ChangePasswordParams { + fun create(userId: String, oldPassword: String, newPassword: String, logoutAllDevices: Boolean): ChangePasswordParams { return ChangePasswordParams( auth = UserPasswordAuth(user = userId, password = oldPassword), - newPassword = newPassword + newPassword = newPassword, + logoutDevices = logoutAllDevices ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt index 7b21ba2e..e767950f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt @@ -26,7 +26,8 @@ import javax.inject.Inject internal interface ChangePasswordTask : Task<ChangePasswordTask.Params, Unit> { data class Params( val password: String, - val newPassword: String + val newPassword: String, + val logoutAllDevices: Boolean ) } @@ -37,7 +38,7 @@ internal class DefaultChangePasswordTask @Inject constructor( ) : ChangePasswordTask { override suspend fun execute(params: ChangePasswordTask.Params) { - val changePasswordParams = ChangePasswordParams.create(userId, params.password, params.newPassword) + val changePasswordParams = ChangePasswordParams.create(userId, params.password, params.newPassword, params.logoutAllDevices) try { executeRequest(globalErrorReceiver) { accountAPI.changePassword(changePasswordParams) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt index bb830a5e..9d03ec47 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DefaultAccountService.kt @@ -25,8 +25,8 @@ internal class DefaultAccountService @Inject constructor( private val deactivateAccountTask: DeactivateAccountTask ) : AccountService { - override suspend fun changePassword(password: String, newPassword: String) { - changePasswordTask.execute(ChangePasswordTask.Params(password, newPassword)) + override suspend fun changePassword(password: String, newPassword: String, logoutAllDevices: Boolean) { + changePasswordTask.execute(ChangePasswordTask.Params(password, newPassword, logoutAllDevices)) } override suspend fun deactivateAccount(eraseAllData: Boolean, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index d22da8f6..add69dd8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.internal.auth.version.Versions +import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreads import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity @@ -142,6 +143,7 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( if (getVersionResult != null) { homeServerCapabilitiesEntity.lastVersionIdentityServerSupported = getVersionResult.isLoginAndRegistrationSupportedBySdk() + homeServerCapabilitiesEntity.canControlLogoutDevices = getVersionResult.doesServerSupportLogoutDevices() } if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt index 464ae96e..33d81648 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt @@ -21,6 +21,7 @@ import dagger.Module import dagger.Provides import io.realm.RealmConfiguration import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.session.identity.IdentityService import org.matrix.android.sdk.internal.database.RealmKeysUtils import org.matrix.android.sdk.internal.di.AuthenticatedIdentity @@ -29,6 +30,7 @@ import org.matrix.android.sdk.internal.di.SessionFilesDirectory import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.di.UserMd5 import org.matrix.android.sdk.internal.network.httpclient.addAccessTokenInterceptor +import org.matrix.android.sdk.internal.network.httpclient.applyMatrixConfiguration import org.matrix.android.sdk.internal.network.token.AccessTokenProvider import org.matrix.android.sdk.internal.session.SessionModule import org.matrix.android.sdk.internal.session.SessionScope @@ -49,11 +51,13 @@ internal abstract class IdentityModule { @AuthenticatedIdentity fun providesOkHttpClient( @UnauthenticatedWithCertificate okHttpClient: OkHttpClient, - @AuthenticatedIdentity accessTokenProvider: AccessTokenProvider + @AuthenticatedIdentity accessTokenProvider: AccessTokenProvider, + matrixConfiguration: MatrixConfiguration, ): OkHttpClient { return okHttpClient .newBuilder() .addAccessTokenInterceptor(accessTokenProvider) + .applyMatrixConfiguration(matrixConfiguration) .build() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt index edc45fe9..5fb20bb2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt @@ -101,7 +101,7 @@ internal class ViaParameterFinder @Inject constructor( } fun userCanInvite(userId: String, roomId: String): Boolean { - val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) ?.content?.toModel<PowerLevelsContent>() ?.let { PowerLevelsHelper(it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt index ace5ee0b..c2310f4f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultConditionResolver.kt @@ -15,6 +15,7 @@ */ package org.matrix.android.sdk.internal.session.pushers +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel @@ -55,7 +56,7 @@ internal class DefaultConditionResolver @Inject constructor( val roomId = event.roomId ?: return false val room = roomGetter.getRoom(roomId) ?: return false - val powerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS) + val powerLevelsContent = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) ?.content ?.toModel<PowerLevelsContent>() ?: PowerLevelsContent() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index bb43d903..24d4975e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -386,7 +386,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } private fun getPowerLevelsHelper(roomId: String): PowerLevelsHelper? { - return stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + return stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) ?.content?.toModel<PowerLevelsContent>() ?.let { PowerLevelsHelper(it) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index f3845f1f..c4d37d12 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -51,6 +51,18 @@ import org.matrix.android.sdk.internal.session.room.directory.DefaultSetRoomDire import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask +import org.matrix.android.sdk.internal.session.room.location.CheckIfExistingActiveLiveTask +import org.matrix.android.sdk.internal.session.room.location.DefaultCheckIfExistingActiveLiveTask +import org.matrix.android.sdk.internal.session.room.location.DefaultGetActiveBeaconInfoForUserTask +import org.matrix.android.sdk.internal.session.room.location.DefaultSendLiveLocationTask +import org.matrix.android.sdk.internal.session.room.location.DefaultSendStaticLocationTask +import org.matrix.android.sdk.internal.session.room.location.DefaultStartLiveLocationShareTask +import org.matrix.android.sdk.internal.session.room.location.DefaultStopLiveLocationShareTask +import org.matrix.android.sdk.internal.session.room.location.GetActiveBeaconInfoForUserTask +import org.matrix.android.sdk.internal.session.room.location.SendLiveLocationTask +import org.matrix.android.sdk.internal.session.room.location.SendStaticLocationTask +import org.matrix.android.sdk.internal.session.room.location.StartLiveLocationShareTask +import org.matrix.android.sdk.internal.session.room.location.StopLiveLocationShareTask import org.matrix.android.sdk.internal.session.room.membership.DefaultLoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.membership.admin.DefaultMembershipAdminTask @@ -299,4 +311,22 @@ internal abstract class RoomModule { @Binds abstract fun bindFetchThreadSummariesTask(task: DefaultFetchThreadSummariesTask): FetchThreadSummariesTask + + @Binds + abstract fun bindStartLiveLocationShareTask(task: DefaultStartLiveLocationShareTask): StartLiveLocationShareTask + + @Binds + abstract fun bindStopLiveLocationShareTask(task: DefaultStopLiveLocationShareTask): StopLiveLocationShareTask + + @Binds + abstract fun bindSendStaticLocationTask(task: DefaultSendStaticLocationTask): SendStaticLocationTask + + @Binds + abstract fun bindSendLiveLocationTask(task: DefaultSendLiveLocationTask): SendLiveLocationTask + + @Binds + abstract fun bindGetActiveBeaconInfoForUserTask(task: DefaultGetActiveBeaconInfoForUserTask): GetActiveBeaconInfoForUserTask + + @Binds + abstract fun bindCheckIfExistingActiveLiveTask(task: DefaultCheckIfExistingActiveLiveTask): CheckIfExistingActiveLiveTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt index 05bde8f8..92174912 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt @@ -36,16 +36,22 @@ import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Inject -// TODO add unit tests +/** + * Aggregates all live location sharing related events in local database. + */ internal class LiveLocationAggregationProcessor @Inject constructor( @SessionId private val sessionId: String, private val workManagerProvider: WorkManagerProvider, private val clock: Clock, ) { - fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) { + /** + * Handle the content of a beacon info. + * @return true if it has been processed, false if ignored. + */ + fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean): Boolean { if (event.senderId.isNullOrEmpty() || isLocalEcho) { - return + return false } val isLive = content.isLive.orTrue() @@ -58,7 +64,7 @@ internal class LiveLocationAggregationProcessor @Inject constructor( if (targetEventId.isNullOrEmpty()) { Timber.w("no target event id found for the beacon content") - return + return false } val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate( @@ -83,6 +89,8 @@ internal class LiveLocationAggregationProcessor @Inject constructor( } else { cancelDeactivationAfterTimeout(targetEventId, roomId) } + + return true } private fun scheduleDeactivationAfterTimeout(eventId: String, roomId: String, endOfLiveTimestampMillis: Long?) { @@ -110,6 +118,10 @@ internal class LiveLocationAggregationProcessor @Inject constructor( workManagerProvider.workManager.cancelUniqueWork(workName) } + /** + * Handle the content of a beacon location data. + * @return true if it has been processed, false if ignored. + */ fun handleBeaconLocationData( realm: Realm, event: Event, @@ -117,14 +129,14 @@ internal class LiveLocationAggregationProcessor @Inject constructor( roomId: String, relatedEventId: String?, isLocalEcho: Boolean - ) { + ): Boolean { if (event.senderId.isNullOrEmpty() || isLocalEcho) { - return + return false } if (relatedEventId.isNullOrEmpty()) { Timber.w("no related event id found for the live location content") - return + return false } val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate( @@ -139,9 +151,12 @@ internal class LiveLocationAggregationProcessor @Inject constructor( ?.getBestTimestampMillis() ?: 0 - if (updatedLocationTimestamp.isMoreRecentThan(currentLocationTimestamp)) { + return if (updatedLocationTimestamp.isMoreRecentThan(currentLocationTimestamp)) { Timber.d("updating last location of the summary of id=$relatedEventId") aggregatedSummary.lastLocationContent = ContentMapper.map(content.toContent()) + true + } else { + false } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/CheckIfExistingActiveLiveTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/CheckIfExistingActiveLiveTask.kt new file mode 100644 index 00000000..228a046f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/CheckIfExistingActiveLiveTask.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.location + +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface CheckIfExistingActiveLiveTask : Task<CheckIfExistingActiveLiveTask.Params, Boolean> { + data class Params( + val roomId: String, + ) +} + +internal class DefaultCheckIfExistingActiveLiveTask @Inject constructor( + private val getActiveBeaconInfoForUserTask: GetActiveBeaconInfoForUserTask, +) : CheckIfExistingActiveLiveTask { + + override suspend fun execute(params: CheckIfExistingActiveLiveTask.Params): Boolean { + val getActiveBeaconTaskParams = GetActiveBeaconInfoForUserTask.Params( + roomId = params.roomId + ) + return getActiveBeaconInfoForUserTask.execute(getActiveBeaconTaskParams) + ?.getClearContent() + ?.toModel<MessageBeaconInfoContent>() + ?.isLive + .orFalse() + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt index 8cf6fcdf..20320cad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt @@ -17,21 +17,31 @@ package org.matrix.android.sdk.internal.session.room.location import androidx.lifecycle.LiveData +import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import org.matrix.android.sdk.api.session.room.location.LocationSharingService +import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary +import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.mapper.LiveLocationShareAggregatedSummaryMapper import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.query.findRunningLiveInRoom +import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase -// TODO add unit tests internal class DefaultLocationSharingService @AssistedInject constructor( @Assisted private val roomId: String, @SessionDatabase private val monarchy: Monarchy, + private val sendStaticLocationTask: SendStaticLocationTask, + private val sendLiveLocationTask: SendLiveLocationTask, + private val startLiveLocationShareTask: StartLiveLocationShareTask, + private val stopLiveLocationShareTask: StopLiveLocationShareTask, + private val checkIfExistingActiveLiveTask: CheckIfExistingActiveLiveTask, private val liveLocationShareAggregatedSummaryMapper: LiveLocationShareAggregatedSummaryMapper, ) : LocationSharingService { @@ -40,10 +50,72 @@ internal class DefaultLocationSharingService @AssistedInject constructor( fun create(roomId: String): DefaultLocationSharingService } + override suspend fun sendStaticLocation(latitude: Double, longitude: Double, uncertainty: Double?, isUserLocation: Boolean): Cancelable { + val params = SendStaticLocationTask.Params( + roomId = roomId, + latitude = latitude, + longitude = longitude, + uncertainty = uncertainty, + isUserLocation = isUserLocation, + ) + return sendStaticLocationTask.execute(params) + } + + override suspend fun sendLiveLocation(beaconInfoEventId: String, latitude: Double, longitude: Double, uncertainty: Double?): Cancelable { + val params = SendLiveLocationTask.Params( + beaconInfoEventId = beaconInfoEventId, + roomId = roomId, + latitude = latitude, + longitude = longitude, + uncertainty = uncertainty, + ) + return sendLiveLocationTask.execute(params) + } + + override suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult { + // Ensure to stop any active live before starting a new one + if (checkIfExistingActiveLive()) { + val result = stopLiveLocationShare() + if (result is UpdateLiveLocationShareResult.Failure) { + return result + } + } + val params = StartLiveLocationShareTask.Params( + roomId = roomId, + timeoutMillis = timeoutMillis + ) + return startLiveLocationShareTask.execute(params) + } + + private suspend fun checkIfExistingActiveLive(): Boolean { + val params = CheckIfExistingActiveLiveTask.Params( + roomId = roomId + ) + return checkIfExistingActiveLiveTask.execute(params) + } + + override suspend fun stopLiveLocationShare(): UpdateLiveLocationShareResult { + val params = StopLiveLocationShareTask.Params( + roomId = roomId, + ) + return stopLiveLocationShareTask.execute(params) + } + override fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>> { return monarchy.findAllMappedWithChanges( { LiveLocationShareAggregatedSummaryEntity.findRunningLiveInRoom(it, roomId = roomId) }, - { liveLocationShareAggregatedSummaryMapper.map(it) } + liveLocationShareAggregatedSummaryMapper ) } + + override fun getLiveLocationShareSummary(beaconInfoEventId: String): LiveData<Optional<LiveLocationShareAggregatedSummary>> { + return Transformations.map( + monarchy.findAllMappedWithChanges( + { LiveLocationShareAggregatedSummaryEntity.where(it, roomId = roomId, eventId = beaconInfoEventId) }, + liveLocationShareAggregatedSummaryMapper + ) + ) { + it.firstOrNull().toOptional() + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/GetActiveBeaconInfoForUserTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/GetActiveBeaconInfoForUserTask.kt new file mode 100644 index 00000000..a8d955af --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/GetActiveBeaconInfoForUserTask.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.location + +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface GetActiveBeaconInfoForUserTask : Task<GetActiveBeaconInfoForUserTask.Params, Event?> { + data class Params( + val roomId: String, + ) +} + +internal class DefaultGetActiveBeaconInfoForUserTask @Inject constructor( + @UserId private val userId: String, + private val stateEventDataSource: StateEventDataSource, +) : GetActiveBeaconInfoForUserTask { + + override suspend fun execute(params: GetActiveBeaconInfoForUserTask.Params): Event? { + return EventType.STATE_ROOM_BEACON_INFO + .mapNotNull { + stateEventDataSource.getStateEvent( + roomId = params.roomId, + eventType = it, + stateKey = QueryStringValue.Equals(userId) + ) + } + .firstOrNull { beaconInfoEvent -> + beaconInfoEvent.getClearContent()?.toModel<MessageBeaconInfoContent>()?.isLive.orFalse() + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/SendLiveLocationTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/SendLiveLocationTask.kt new file mode 100644 index 00000000..bebd9c77 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/SendLiveLocationTask.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.location + +import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory +import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface SendLiveLocationTask : Task<SendLiveLocationTask.Params, Cancelable> { + data class Params( + val roomId: String, + val beaconInfoEventId: String, + val latitude: Double, + val longitude: Double, + val uncertainty: Double?, + ) +} + +internal class DefaultSendLiveLocationTask @Inject constructor( + private val localEchoEventFactory: LocalEchoEventFactory, + private val eventSenderProcessor: EventSenderProcessor, +) : SendLiveLocationTask { + + override suspend fun execute(params: SendLiveLocationTask.Params): Cancelable { + val event = localEchoEventFactory.createLiveLocationEvent( + beaconInfoEventId = params.beaconInfoEventId, + roomId = params.roomId, + latitude = params.latitude, + longitude = params.longitude, + uncertainty = params.uncertainty, + ) + localEchoEventFactory.createLocalEcho(event) + return eventSenderProcessor.postEvent(event) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/SendStaticLocationTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/SendStaticLocationTask.kt new file mode 100644 index 00000000..e08b82f3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/SendStaticLocationTask.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.location + +import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory +import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface SendStaticLocationTask : Task<SendStaticLocationTask.Params, Cancelable> { + data class Params( + val roomId: String, + val latitude: Double, + val longitude: Double, + val uncertainty: Double?, + val isUserLocation: Boolean + ) +} + +internal class DefaultSendStaticLocationTask @Inject constructor( + private val localEchoEventFactory: LocalEchoEventFactory, + private val eventSenderProcessor: EventSenderProcessor, +) : SendStaticLocationTask { + + override suspend fun execute(params: SendStaticLocationTask.Params): Cancelable { + val event = localEchoEventFactory.createStaticLocationEvent( + roomId = params.roomId, + latitude = params.latitude, + longitude = params.longitude, + uncertainty = params.uncertainty, + isUserLocation = params.isUserLocation + ) + localEchoEventFactory.createLocalEcho(event) + return eventSenderProcessor.postEvent(event) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt new file mode 100644 index 00000000..b943c279 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.location + +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.room.state.SendStateTask +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.time.Clock +import javax.inject.Inject + +internal interface StartLiveLocationShareTask : Task<StartLiveLocationShareTask.Params, UpdateLiveLocationShareResult> { + data class Params( + val roomId: String, + val timeoutMillis: Long, + ) +} + +internal class DefaultStartLiveLocationShareTask @Inject constructor( + @UserId private val userId: String, + private val clock: Clock, + private val sendStateTask: SendStateTask, +) : StartLiveLocationShareTask { + + override suspend fun execute(params: StartLiveLocationShareTask.Params): UpdateLiveLocationShareResult { + val beaconContent = MessageBeaconInfoContent( + timeout = params.timeoutMillis, + isLive = true, + unstableTimestampMillis = clock.epochMillis() + ).toContent() + val eventType = EventType.STATE_ROOM_BEACON_INFO.first() + val sendStateTaskParams = SendStateTask.Params( + roomId = params.roomId, + stateKey = userId, + eventType = eventType, + body = beaconContent + ) + return try { + val eventId = sendStateTask.executeRetry(sendStateTaskParams, 3) + if (eventId.isNotEmpty()) { + UpdateLiveLocationShareResult.Success(eventId) + } else { + UpdateLiveLocationShareResult.Failure(Exception("empty event id for new state event")) + } + } catch (error: Throwable) { + UpdateLiveLocationShareResult.Failure(error) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StopLiveLocationShareTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StopLiveLocationShareTask.kt new file mode 100644 index 00000000..da5fd769 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StopLiveLocationShareTask.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.location + +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.internal.session.room.state.SendStateTask +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface StopLiveLocationShareTask : Task<StopLiveLocationShareTask.Params, UpdateLiveLocationShareResult> { + data class Params( + val roomId: String, + ) +} + +internal class DefaultStopLiveLocationShareTask @Inject constructor( + private val sendStateTask: SendStateTask, + private val getActiveBeaconInfoForUserTask: GetActiveBeaconInfoForUserTask, +) : StopLiveLocationShareTask { + + override suspend fun execute(params: StopLiveLocationShareTask.Params): UpdateLiveLocationShareResult { + val beaconInfoStateEvent = getActiveLiveLocationBeaconInfoForUser(params.roomId) ?: return getResultForIncorrectBeaconInfoEvent() + val stateKey = beaconInfoStateEvent.stateKey ?: return getResultForIncorrectBeaconInfoEvent() + val content = beaconInfoStateEvent.getClearContent()?.toModel<MessageBeaconInfoContent>() ?: return getResultForIncorrectBeaconInfoEvent() + val updatedContent = content.copy(isLive = false).toContent() + val sendStateTaskParams = SendStateTask.Params( + roomId = params.roomId, + stateKey = stateKey, + eventType = EventType.STATE_ROOM_BEACON_INFO.first(), + body = updatedContent + ) + return try { + val eventId = sendStateTask.executeRetry(sendStateTaskParams, 3) + if (eventId.isNotEmpty()) { + UpdateLiveLocationShareResult.Success(eventId) + } else { + UpdateLiveLocationShareResult.Failure(Exception("empty event id for new state event")) + } + } catch (error: Throwable) { + UpdateLiveLocationShareResult.Failure(error) + } + } + + private fun getResultForIncorrectBeaconInfoEvent() = + UpdateLiveLocationShareResult.Failure(Exception("incorrect last beacon info event")) + + private suspend fun getActiveLiveLocationBeaconInfoForUser(roomId: String): Event? { + val params = GetActiveBeaconInfoForUserTask.Params( + roomId = roomId + ) + return getActiveBeaconInfoForUserTask.execute(params) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt index 005d7f26..ef89ca33 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt @@ -58,7 +58,7 @@ internal class DefaultMembershipService @AssistedInject constructor( } override suspend fun loadRoomMembersIfNeeded() { - val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE) + val params = LoadRoomMembersTask.Params(roomId, excludeMembership = Membership.LEAVE) loadRoomMembersTask.execute(params) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt index 15d08892..7052eb23 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt @@ -43,6 +43,7 @@ import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.util.time.Clock +import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -105,32 +106,37 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( } private suspend fun insertInDb(response: RoomMembersResponse, roomId: String) { + val chunks = response.roomMemberEvents.chunked(500) + chunks.forEach { roomMemberEvents -> + monarchy.awaitTransaction { realm -> + Timber.v("Insert ${roomMemberEvents.size} member events in room $roomId") + // We ignore all the already known members + val now = clock.epochMillis() + for (roomMemberEvent in roomMemberEvents) { + if (roomMemberEvent.eventId == null || roomMemberEvent.stateKey == null || roomMemberEvent.type == null) { + continue + } + val ageLocalTs = now - (roomMemberEvent.unsignedData?.age ?: 0) + val eventEntity = roomMemberEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) + CurrentStateEventEntity.getOrCreate( + realm, + roomId, + roomMemberEvent.stateKey, + roomMemberEvent.type + ).apply { + eventId = roomMemberEvent.eventId + root = eventEntity + } + roomMemberEventHandler.handle(realm, roomId, roomMemberEvent, false) + } + } + } monarchy.awaitTransaction { realm -> - // We ignore all the already known members val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId) - val now = clock.epochMillis() - for (roomMemberEvent in response.roomMemberEvents) { - if (roomMemberEvent.eventId == null || roomMemberEvent.stateKey == null || roomMemberEvent.type == null) { - continue - } - val ageLocalTs = roomMemberEvent.unsignedData?.age?.let { now - it } - val eventEntity = roomMemberEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) - CurrentStateEventEntity.getOrCreate( - realm, - roomId, - roomMemberEvent.stateKey, - roomMemberEvent.type - ).apply { - eventId = roomMemberEvent.eventId - root = eventEntity - } - roomMemberEventHandler.handle(realm, roomId, roomMemberEvent, false) - } roomEntity.membersLoadStatus = RoomMembersLoadStatusType.LOADED roomSummaryUpdater.update(realm, roomId, updateMembers = true) } - if (cryptoSessionInfoProvider.isRoomEncrypted(roomId)) { deviceListManager.onRoomMembersLoadedFor(roomId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt index 1e36e9c6..fd655252 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt @@ -83,23 +83,21 @@ internal class RoomMemberEventHandler @Inject constructor( userId: String, roomMember: RoomMemberContent ) { - val roomMemberEntity = RoomMemberEntityFactory.create( - roomId, - userId, - roomMember, - // When an update is happening, insertOrUpdate replace existing values with null if they are not provided, - // but we want to preserve presence record value and not replace it with null - getExistingPresenceState(realm, roomId, userId) - ) - realm.insertOrUpdate(roomMemberEntity) - } - - /** - * Get the already existing presence state for a specific user & room in order NOT to be replaced in RoomMemberSummaryEntity - * by NULL value. - */ - private fun getExistingPresenceState(realm: Realm, roomId: String, userId: String): UserPresenceEntity? { - return RoomMemberSummaryEntity.where(realm, roomId, userId).findFirst()?.userPresenceEntity + val existingRoomMemberSummary = RoomMemberSummaryEntity.where(realm, roomId, userId).findFirst() + if (existingRoomMemberSummary != null) { + existingRoomMemberSummary.displayName = roomMember.displayName + existingRoomMemberSummary.avatarUrl = roomMember.avatarUrl + existingRoomMemberSummary.membership = roomMember.membership + } else { + val presenceEntity = UserPresenceEntity.where(realm, userId).findFirst() + val roomMemberEntity = RoomMemberEntityFactory.create( + roomId, + userId, + roomMember, + presenceEntity + ) + realm.insert(roomMemberEntity) + } } private fun saveUserEntityLocallyIfNecessary( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt index 1b836e36..dd28bbcc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt @@ -60,7 +60,7 @@ internal class DefaultLeaveRoomTask @Inject constructor( val roomCreateStateEvent = stateEventDataSource.getStateEvent( roomId = roomId, eventType = EventType.STATE_ROOM_CREATE, - stateKey = QueryStringValue.NoCondition + stateKey = QueryStringValue.IsEmpty, ) // Server is not cleaning predecessor rooms, so we also try to left them val predecessorRoomId = roomCreateStateEvent?.getClearContent()?.toModel<RoomCreateContent>()?.predecessor?.roomId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt index bad73417..bac810f4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt @@ -209,7 +209,8 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor( * Create an EventEntity to be added in the TimelineEventEntity. */ private fun createEventEntity(roomId: String, event: Event, realm: Realm): EventEntity { - val ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it } + val now = clock.epochMillis() + val ageLocalTs = now - (event.unsignedData?.age ?: 0) return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index fc78abcf..418000ab 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -129,18 +129,6 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } - override fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?, isUserLocation: Boolean): Cancelable { - return localEchoEventFactory.createLocationEvent(roomId, latitude, longitude, uncertainty, isUserLocation) - .also { createLocalEcho(it) } - .let { sendEvent(it) } - } - - override fun sendLiveLocation(beaconInfoEventId: String, latitude: Double, longitude: Double, uncertainty: Double?): Cancelable { - return localEchoEventFactory.createLiveLocationEvent(beaconInfoEventId, roomId, latitude, longitude, uncertainty) - .also { createLocalEcho(it) } - .let { sendEvent(it) } - } - override fun redactEvent(event: Event, reason: String?): Cancelable { // TODO manage media/attachements? val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index 3b9ca44d..f52500de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -244,7 +244,7 @@ internal class LocalEchoEventFactory @Inject constructor( ) } - fun createLocationEvent( + fun createStaticLocationEvent( roomId: String, latitude: Double, longitude: Double, @@ -708,7 +708,7 @@ internal class LocalEchoEventFactory @Inject constructor( } /** - * Returns RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' + * Returns RFC5870 formatted geo uri 'geo:latitude,longitude;u=uncertainty' like 'geo:40.05,29.24;u=30' * Uncertainty of the location is in meters and not required. */ private fun buildGeoUri(latitude: Double, longitude: Double, uncertainty: Double?): String { @@ -718,7 +718,7 @@ internal class LocalEchoEventFactory @Inject constructor( append(",") append(longitude) uncertainty?.let { - append(";") + append(";u=") append(it) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index 2a980f32..ad47b824 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -21,32 +21,27 @@ import androidx.lifecycle.LiveData import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent -import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.api.session.room.state.StateService import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.content.FileUploader -import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder internal class DefaultStateService @AssistedInject constructor( @Assisted private val roomId: String, private val stateEventDataSource: StateEventDataSource, private val sendStateTask: SendStateTask, private val fileUploader: FileUploader, - private val viaParameterFinder: ViaParameterFinder ) : StateService { @AssistedFactory @@ -54,19 +49,19 @@ internal class DefaultStateService @AssistedInject constructor( fun create(roomId: String): DefaultStateService } - override fun getStateEvent(eventType: String, stateKey: QueryStringValue): Event? { + override fun getStateEvent(eventType: String, stateKey: QueryStateEventValue): Event? { return stateEventDataSource.getStateEvent(roomId, eventType, stateKey) } - override fun getStateEventLive(eventType: String, stateKey: QueryStringValue): LiveData<Optional<Event>> { + override fun getStateEventLive(eventType: String, stateKey: QueryStateEventValue): LiveData<Optional<Event>> { return stateEventDataSource.getStateEventLive(roomId, eventType, stateKey) } - override fun getStateEvents(eventTypes: Set<String>, stateKey: QueryStringValue): List<Event> { + override fun getStateEvents(eventTypes: Set<String>, stateKey: QueryStateEventValue): List<Event> { return stateEventDataSource.getStateEvents(roomId, eventTypes, stateKey) } - override fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue): LiveData<List<Event>> { + override fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStateEventValue): LiveData<List<Event>> { return stateEventDataSource.getStateEventsLive(roomId, eventTypes, stateKey) } @@ -190,35 +185,4 @@ internal class DefaultStateService @AssistedInject constructor( } updateJoinRule(RoomJoinRules.RESTRICTED, null, allowEntries) } - - override suspend fun stopLiveLocation(userId: String) { - getLiveLocationBeaconInfo(userId, true)?.let { beaconInfoStateEvent -> - beaconInfoStateEvent.getClearContent()?.toModel<MessageBeaconInfoContent>()?.let { content -> - val updatedContent = content.copy(isLive = false).toContent() - - beaconInfoStateEvent.stateKey?.let { - sendStateEvent( - eventType = EventType.STATE_ROOM_BEACON_INFO.first(), - body = updatedContent, - stateKey = it - ) - } - } - } - } - - override suspend fun getLiveLocationBeaconInfo(userId: String, filterOnlyLive: Boolean): Event? { - return EventType.STATE_ROOM_BEACON_INFO - .mapNotNull { - stateEventDataSource.getStateEvent( - roomId = roomId, - eventType = it, - stateKey = QueryStringValue.Equals(userId) - ) - } - .firstOrNull { beaconInfoEvent -> - !filterOnlyLive || - beaconInfoEvent.getClearContent()?.toModel<MessageBeaconInfoContent>()?.isLive.orFalse() - } - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt index 18c709ad..9971ce3c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt @@ -22,6 +22,7 @@ import com.zhuinden.monarchy.Monarchy import io.realm.Realm import io.realm.RealmQuery import io.realm.kotlin.where +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.util.Optional @@ -40,13 +41,13 @@ internal class StateEventDataSource @Inject constructor( private val queryStringValueProcessor: QueryStringValueProcessor ) { - fun getStateEvent(roomId: String, eventType: String, stateKey: QueryStringValue): Event? { + fun getStateEvent(roomId: String, eventType: String, stateKey: QueryStateEventValue): Event? { return realmSessionProvider.withRealm { realm -> buildStateEventQuery(realm, roomId, setOf(eventType), stateKey).findFirst()?.root?.asDomain() } } - fun getStateEventLive(roomId: String, eventType: String, stateKey: QueryStringValue): LiveData<Optional<Event>> { + fun getStateEventLive(roomId: String, eventType: String, stateKey: QueryStateEventValue): LiveData<Optional<Event>> { val liveData = monarchy.findAllMappedWithChanges( { realm -> buildStateEventQuery(realm, roomId, setOf(eventType), stateKey) }, { it.root?.asDomain() } @@ -56,7 +57,7 @@ internal class StateEventDataSource @Inject constructor( } } - fun getStateEvents(roomId: String, eventTypes: Set<String>, stateKey: QueryStringValue): List<Event> { + fun getStateEvents(roomId: String, eventTypes: Set<String>, stateKey: QueryStateEventValue): List<Event> { return realmSessionProvider.withRealm { realm -> buildStateEventQuery(realm, roomId, eventTypes, stateKey) .findAll() @@ -66,7 +67,7 @@ internal class StateEventDataSource @Inject constructor( } } - fun getStateEventsLive(roomId: String, eventTypes: Set<String>, stateKey: QueryStringValue): LiveData<List<Event>> { + fun getStateEventsLive(roomId: String, eventTypes: Set<String>, stateKey: QueryStateEventValue): LiveData<List<Event>> { val liveData = monarchy.findAllMappedWithChanges( { realm -> buildStateEventQuery(realm, roomId, eventTypes, stateKey) }, { it.root?.asDomain() } @@ -80,7 +81,7 @@ internal class StateEventDataSource @Inject constructor( realm: Realm, roomId: String, eventTypes: Set<String>, - stateKey: QueryStringValue + stateKey: QueryStateEventValue ): RealmQuery<CurrentStateEventEntity> { return with(queryStringValueProcessor) { realm.where<CurrentStateEventEntity>() @@ -90,7 +91,8 @@ internal class StateEventDataSource @Inject constructor( `in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray()) } } - .process(CurrentStateEventEntityFields.STATE_KEY, stateKey) + // It's OK to cast stateKey as QueryStringValue + .process(CurrentStateEventEntityFields.STATE_KEY, stateKey as QueryStringValue) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 7795a56c..4eaac67e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -20,6 +20,7 @@ import io.realm.Realm import io.realm.RealmConfiguration import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.cancelChildren @@ -33,6 +34,7 @@ import kotlinx.coroutines.withContext import okhttp3.internal.closeQuietly import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings @@ -115,6 +117,7 @@ internal class DefaultTimeline( ) private var strategy: LoadTimelineStrategy = buildStrategy(LoadTimelineStrategy.Mode.Live) + private var startTimelineJob: Job? = null override val isLive: Boolean get() = !getPaginationState(Timeline.Direction.FORWARDS).hasMoreToLoad @@ -142,7 +145,7 @@ internal class DefaultTimeline( timelineScope.launch { loadRoomMembersIfNeeded() } - timelineScope.launch { + startTimelineJob = timelineScope.launch { sequencer.post { if (isStarted.compareAndSet(false, true)) { isFromThreadTimeline = rootThreadEventId != null @@ -173,8 +176,10 @@ internal class DefaultTimeline( override fun restartWithEventId(eventId: String?) { timelineScope.launch { - openAround(eventId, rootThreadEventId) - postSnapshot() + sequencer.post { + openAround(eventId, rootThreadEventId) + postSnapshot() + } } } @@ -184,6 +189,7 @@ internal class DefaultTimeline( override fun paginate(direction: Timeline.Direction, count: Int) { timelineScope.launch { + startTimelineJob?.join() val postSnapshot = loadMore(count, direction, fetchOnServerIfNeeded = true) if (postSnapshot) { postSnapshot() @@ -192,6 +198,7 @@ internal class DefaultTimeline( } override suspend fun awaitPaginate(direction: Timeline.Direction, count: Int): List<TimelineEvent> { + startTimelineJob?.join() withContext(timelineDispatcher) { loadMore(count, direction, fetchOnServerIfNeeded = true) } @@ -278,6 +285,7 @@ internal class DefaultTimeline( direction = Timeline.Direction.BACKWARDS, fetchOnServerIfNeeded = false ) + Timber.v("$baseLogMessage finished") } @@ -311,9 +319,11 @@ internal class DefaultTimeline( private fun onLimitedTimeline() { timelineScope.launch { - initPaginationStates(null) - loadMore(settings.initialSize, Timeline.Direction.BACKWARDS, false) - postSnapshot() + sequencer.post { + initPaginationStates(null) + loadMore(settings.initialSize, Timeline.Direction.BACKWARDS, false) + postSnapshot() + } } } @@ -388,7 +398,7 @@ internal class DefaultTimeline( } private suspend fun loadRoomMembersIfNeeded() { - val loadRoomMembersParam = LoadRoomMembersTask.Params(roomId) + val loadRoomMembersParam = LoadRoomMembersTask.Params(roomId, excludeMembership = Membership.LEAVE) try { loadRoomMembersTask.execute(loadRoomMembersParam) } catch (failure: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt index aef9e24c..7c662444 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt @@ -61,7 +61,7 @@ internal class DefaultGetEventTask @Inject constructor( } } - event.ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it } + event.ageLocalTs = clock.epochMillis() - (event.unsignedData?.age ?: 0) return event } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveRoomStateListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveRoomStateListener.kt index b2692bf8..b78ad459 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveRoomStateListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveRoomStateListener.kt @@ -46,7 +46,7 @@ internal class LiveRoomStateListener( stateEventDataSource.getStateEventsLive( roomId = roomId, eventTypes = setOf(EventType.STATE_ROOM_MEMBER), - stateKey = QueryStringValue.NoCondition, + stateKey = QueryStringValue.IsNotNull, ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt index c5d4d346..d81a1156 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LoadTimelineStrategy.kt @@ -22,6 +22,7 @@ import io.realm.Realm import io.realm.RealmConfiguration import io.realm.RealmResults import io.realm.kotlin.createObject +import io.realm.kotlin.executeTransactionAwait import kotlinx.coroutines.CompletableDeferred import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.extensions.orFalse @@ -265,7 +266,7 @@ internal class LoadTimelineStrategy constructor( } } - private fun getChunkEntity(realm: Realm): RealmResults<ChunkEntity> { + private suspend fun getChunkEntity(realm: Realm): RealmResults<ChunkEntity> { return when (mode) { is Mode.Live -> { ChunkEntity.where(realm, roomId) @@ -289,8 +290,8 @@ internal class LoadTimelineStrategy constructor( * Clear any existing thread chunk entity and create a new one, with the * rootThreadEventId included. */ - private fun recreateThreadChunkEntity(realm: Realm, rootThreadEventId: String) { - realm.executeTransaction { + private suspend fun recreateThreadChunkEntity(realm: Realm, rootThreadEventId: String) { + realm.executeTransactionAwait { // Lets delete the chunk and start a new one ChunkEntity.findLastForwardChunkOfThread(it, roomId, rootThreadEventId)?.deleteAndClearThreadEvents()?.let { Timber.i("###THREADS LoadTimelineStrategy [onStart] thread chunk cleared..") @@ -309,8 +310,8 @@ internal class LoadTimelineStrategy constructor( /** * Clear any existing thread chunk. */ - private fun clearThreadChunkEntity(realm: Realm, rootThreadEventId: String) { - realm.executeTransaction { + private suspend fun clearThreadChunkEntity(realm: Realm, rootThreadEventId: String) { + realm.executeTransactionAwait { ChunkEntity.findLastForwardChunkOfThread(it, roomId, rootThreadEventId)?.deleteAndClearThreadEvents()?.let { Timber.i("###THREADS LoadTimelineStrategy [onStop] thread chunk cleared..") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt index e13f3f45..7fa36969 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineChunk.kt @@ -490,38 +490,11 @@ internal class TimelineChunk( private fun handleDatabaseChangeSet(results: RealmResults<TimelineEventEntity>, changeSet: OrderedCollectionChangeSet) { val insertions = changeSet.insertionRanges for (range in insertions) { - // Check if the insertion's displayIndices match our expectations - or skip this insertion. - // Inconsistencies (missing messages) can happen otherwise if we get insertions before having loaded all timeline events of the chunk. - if (builtEvents.isNotEmpty()) { - // Check consistency to item before insertions - if (range.startIndex > 0) { - val firstInsertion = results[range.startIndex]!! - val lastBeforeInsertion = builtEvents[range.startIndex - 1] - if (firstInsertion.displayIndex + 1 != lastBeforeInsertion.displayIndex) { - Timber.i( - "handleDatabaseChangeSet: skip insertion at ${range.startIndex}/${builtEvents.size}, " + - "displayIndex mismatch at ${range.startIndex}: ${firstInsertion.displayIndex} -> ${lastBeforeInsertion.displayIndex}" - ) - continue - } - } - // Check consistency to item after insertions - if (range.startIndex < builtEvents.size) { - val lastInsertion = results[range.startIndex + range.length - 1]!! - val firstAfterInsertion = builtEvents[range.startIndex] - if (firstAfterInsertion.displayIndex + 1 != lastInsertion.displayIndex) { - Timber.i( - "handleDatabaseChangeSet: skip insertion at ${range.startIndex}/${builtEvents.size}, " + - "displayIndex mismatch at ${range.startIndex + range.length}: " + - "${firstAfterInsertion.displayIndex} -> ${lastInsertion.displayIndex}" - ) - continue - } - } - } + if (!validateInsertion(range, results)) continue val newItems = results .subList(range.startIndex, range.startIndex + range.length) .map { it.buildAndDecryptIfNeeded() } + builtEventsIndexes.entries.filter { it.value >= range.startIndex }.forEach { it.setValue(it.value + range.length) } newItems.mapIndexed { index, timelineEvent -> if (timelineEvent.root.type == EventType.STATE_ROOM_CREATE) { @@ -536,12 +509,9 @@ internal class TimelineChunk( for (range in modifications) { for (modificationIndex in (range.startIndex until range.startIndex + range.length)) { val updatedEntity = results[modificationIndex] ?: continue - val displayIndex = builtEventsIndexes[updatedEntity.eventId] - if (displayIndex == null) { - continue - } + val builtEventIndex = builtEventsIndexes[updatedEntity.eventId] ?: continue try { - builtEvents[displayIndex] = updatedEntity.buildAndDecryptIfNeeded() + builtEvents[builtEventIndex] = updatedEntity.buildAndDecryptIfNeeded() } catch (failure: Throwable) { Timber.v("Fail to update items at index: $modificationIndex") } @@ -558,6 +528,21 @@ internal class TimelineChunk( } } + private fun validateInsertion(range: OrderedCollectionChangeSet.Range, results: RealmResults<TimelineEventEntity>): Boolean { + // Insertion can only happen from LastForward chunk after a sync. + if (isLastForward.get()) { + val firstBuiltEvent = builtEvents.firstOrNull() + if (firstBuiltEvent != null) { + val lastInsertion = results[range.startIndex + range.length - 1] ?: return false + if (firstBuiltEvent.displayIndex + 1 != lastInsertion.displayIndex) { + Timber.v("There is no continuation in the chunk, chunk is not fully loaded yet, skip insert.") + return false + } + } + } + return true + } + private fun getNextDisplayIndex(direction: Timeline.Direction): Int? { if (timelineEventEntities.isEmpty()) { return null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt index fd1703db..ea22f8cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt @@ -142,7 +142,7 @@ internal class TokenChunkEventPersistor @Inject constructor( val now = clock.epochMillis() stateEvents?.forEach { stateEvent -> - val ageLocalTs = stateEvent.unsignedData?.age?.let { now - it } + val ageLocalTs = now - (stateEvent.unsignedData?.age ?: 0) val stateEventEntity = stateEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) currentChunk.addStateEvent(roomId, stateEventEntity, direction) if (stateEvent.type == EventType.STATE_ROOM_MEMBER && stateEvent.stateKey != null) { @@ -155,7 +155,7 @@ internal class TokenChunkEventPersistor @Inject constructor( if (event.eventId == null || event.senderId == null) { return@forEach } - val ageLocalTs = event.unsignedData?.age?.let { now - it } + val ageLocalTs = now - (event.unsignedData?.age ?: 0) val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) if (event.type == EventType.STATE_ROOM_MEMBER && event.stateKey != null) { val contentToUse = if (direction == PaginationDirection.BACKWARDS) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt index dc12c320..0bde3a11 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt @@ -71,7 +71,7 @@ internal class DefaultRoomVersionService @AssistedInject constructor( } override fun userMayUpgradeRoom(userId: String): Boolean { - val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) ?.content?.toModel<PowerLevelsContent>() ?.let { PowerLevelsHelper(it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index c08d9389..d2f1b320 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -252,7 +252,7 @@ internal class DefaultSpaceService @Inject constructor( val powerLevelsEvent = stateEventDataSource.getStateEvent( roomId = parentSpaceId, eventType = EventType.STATE_ROOM_POWER_LEVELS, - stateKey = QueryStringValue.NoCondition + stateKey = QueryStringValue.IsEmpty ) val powerLevelsContent = powerLevelsEvent?.content?.toModel<PowerLevelsContent>() ?: throw UnsupportedOperationException("Cannot add canonical child, missing powerlevel") 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 f99fe964..30e1ec66 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 @@ -244,7 +244,7 @@ internal class RoomSyncHandler @Inject constructor( if (event.eventId == null || event.stateKey == null || event.type == null) { continue } - val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } + val ageLocalTs = syncLocalTimestampMillis - (event.unsignedData?.age ?: 0) val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) Timber.v("## received state event ${event.type} and key ${event.stateKey}") CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { @@ -306,7 +306,7 @@ internal class RoomSyncHandler @Inject constructor( if (event.stateKey == null || event.type == null) { return@forEach } - val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } + val ageLocalTs = syncLocalTimestampMillis - (event.unsignedData?.age ?: 0) val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { eventId = eventEntity.eventId @@ -336,7 +336,7 @@ internal class RoomSyncHandler @Inject constructor( if (event.eventId == null || event.stateKey == null || event.type == null) { continue } - val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } + val ageLocalTs = syncLocalTimestampMillis - (event.unsignedData?.age ?: 0) val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { eventId = event.eventId @@ -348,7 +348,7 @@ internal class RoomSyncHandler @Inject constructor( if (event.eventId == null || event.senderId == null || event.type == null) { continue } - val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } + val ageLocalTs = syncLocalTimestampMillis - (event.unsignedData?.age ?: 0) val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType) if (event.stateKey != null) { CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { @@ -401,7 +401,10 @@ internal class RoomSyncHandler @Inject constructor( for (rawEvent in eventList) { // It's annoying roomId is not there, but lot of code rely on it. // And had to do it now as copy would delete all decryption results.. - val event = rawEvent.copy(roomId = roomId) + val ageLocalTs = syncLocalTimestampMillis - (rawEvent.unsignedData?.age ?: 0) + val event = rawEvent.copy(roomId = roomId).also { + it.ageLocalTs = ageLocalTs + } if (event.eventId == null || event.senderId == null || event.type == null) { continue } @@ -423,7 +426,6 @@ internal class RoomSyncHandler @Inject constructor( contentToInject = threadsAwarenessHandler.makeEventThreadAware(realm, roomId, event) } - val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it } val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs, contentToInject).copyToRealmOrIgnore(realm, insertType) if (event.stateKey != null) { CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt index 8c7557a5..70553359 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt @@ -53,6 +53,7 @@ import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.time.Clock import javax.inject.Inject /** @@ -64,7 +65,8 @@ internal class ThreadsAwarenessHandler @Inject constructor( private val permalinkFactory: PermalinkFactory, @SessionDatabase private val monarchy: Monarchy, private val lightweightSettingsStorage: LightweightSettingsStorage, - private val getEventTask: GetEventTask + private val getEventTask: GetEventTask, + private val clock: Clock, ) { // This caching is responsible to improve the performance when we receive a root event @@ -120,7 +122,7 @@ internal class ThreadsAwarenessHandler @Inject constructor( private suspend fun fetchThreadsEvents(threadsToFetch: Map<String, String>) { val eventEntityList = threadsToFetch.mapNotNull { (eventId, roomId) -> fetchEvent(eventId, roomId)?.let { - it.toEntity(roomId, SendState.SYNCED, it.ageLocalTs) + it.toEntity(roomId, SendState.SYNCED, it.ageLocalTs ?: clock.epochMillis()) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt index 7b2edf2d..9c59c113 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.internal.session.widgets import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.widgets.WidgetPostAPIMediator import org.matrix.android.sdk.api.session.widgets.WidgetService @@ -43,7 +43,7 @@ internal class DefaultWidgetService @Inject constructor( override fun getRoomWidgets( roomId: String, - widgetId: QueryStringValue, + widgetId: QueryStateEventValue, widgetTypes: Set<String>?, excludedTypes: Set<String>? ): List<Widget> { @@ -56,7 +56,7 @@ internal class DefaultWidgetService @Inject constructor( override fun getRoomWidgetsLive( roomId: String, - widgetId: QueryStringValue, + widgetId: QueryStateEventValue, widgetTypes: Set<String>?, excludedTypes: Set<String>? ): LiveData<List<Widget>> { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt index 3f7db93b..37a329af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt @@ -21,6 +21,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.SessionLifecycleObserver @@ -71,7 +72,7 @@ internal class WidgetManager @Inject constructor( fun getRoomWidgetsLive( roomId: String, - widgetId: QueryStringValue = QueryStringValue.NoCondition, + widgetId: QueryStateEventValue, widgetTypes: Set<String>? = null, excludedTypes: Set<String>? = null ): LiveData<List<Widget>> { @@ -88,7 +89,7 @@ internal class WidgetManager @Inject constructor( fun getRoomWidgets( roomId: String, - widgetId: QueryStringValue = QueryStringValue.NoCondition, + widgetId: QueryStateEventValue, widgetTypes: Set<String>? = null, excludedTypes: Set<String>? = null ): List<Widget> { @@ -199,7 +200,7 @@ internal class WidgetManager @Inject constructor( val powerLevelsEvent = stateEventDataSource.getStateEvent( roomId = roomId, eventType = EventType.STATE_ROOM_POWER_LEVELS, - stateKey = QueryStringValue.NoCondition + stateKey = QueryStringValue.IsEmpty ) val powerLevelsContent = powerLevelsEvent?.content?.toModel<PowerLevelsContent>() ?: return false return PowerLevelsHelper(powerLevelsContent).isUserAllowedToSend(userId, true, EventType.STATE_ROOM_WIDGET_LEGACY) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt index 2dd16d83..901d0eca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.util import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import timber.log.Timber +import java.util.concurrent.CopyOnWriteArraySet internal interface BackgroundDetectionObserver : DefaultLifecycleObserver { val isInBackground: Boolean @@ -37,7 +38,7 @@ internal class DefaultBackgroundDetectionObserver : BackgroundDetectionObserver override var isInBackground: Boolean = true private set - private val listeners = LinkedHashSet<BackgroundDetectionObserver.Listener>() + private val listeners = CopyOnWriteArraySet<BackgroundDetectionObserver.Listener>() override fun register(listener: BackgroundDetectionObserver.Listener) { listeners.add(listener) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt index 396d12f3..8c7d7704 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/system/SystemModule.kt @@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.util.system import dagger.Binds import dagger.Module +import org.matrix.android.sdk.api.securestorage.SecureStorageService +import org.matrix.android.sdk.internal.securestorage.DefaultSecureStorageService import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.android.sdk.internal.util.time.DefaultClock @@ -25,7 +27,7 @@ import org.matrix.android.sdk.internal.util.time.DefaultClock internal abstract class SystemModule { @Binds - abstract fun bindBuildVersionSdkIntProvider(provider: DefaultBuildVersionSdkIntProvider): BuildVersionSdkIntProvider + abstract fun bindSecureStorageService(service: DefaultSecureStorageService): SecureStorageService @Binds abstract fun bindClock(clock: DefaultClock): Clock diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/MatrixPatternsTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/MatrixPatternsTest.kt new file mode 100644 index 00000000..0d0450ad --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/api/MatrixPatternsTest.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2022 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 + +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test + +class MatrixPatternsTest { + + @Test + fun `given user id cases, when checking isUserId, then returns expected`() { + val cases = listOf( + UserIdCase("foobar", isUserId = false), + UserIdCase("@foobar", isUserId = false), + UserIdCase("foobar@matrix.org", isUserId = false), + UserIdCase("@foobar: matrix.org", isUserId = false), + UserIdCase("@foobar:matrix.org", isUserId = true), + ) + + cases.forEach { (input, expected) -> + MatrixPatterns.isUserId(input) shouldBeEqualTo expected + } + } +} + +private data class UserIdCase(val input: String, val isUserId: Boolean) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt index 32b1d44f..dac33069 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt @@ -23,10 +23,12 @@ import org.amshove.kluent.shouldBeEqualTo import org.junit.Test import org.matrix.android.sdk.api.session.pushers.PusherState import org.matrix.android.sdk.internal.database.model.PusherEntity +import org.matrix.android.sdk.internal.database.model.PusherEntityFields import org.matrix.android.sdk.test.fakes.FakeGlobalErrorReceiver import org.matrix.android.sdk.test.fakes.FakeMonarchy import org.matrix.android.sdk.test.fakes.FakePushersAPI import org.matrix.android.sdk.test.fakes.FakeRequestExecutor +import org.matrix.android.sdk.test.fakes.givenEqualTo import java.net.SocketException private val A_JSON_PUSHER = JsonPusher( @@ -56,6 +58,7 @@ class DefaultAddPusherTaskTest { @Test fun `given no persisted pusher when adding Pusher then updates api and inserts result with Registered state`() { monarchy.givenWhereReturns<PusherEntity>(result = null) + .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) runTest { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } @@ -71,6 +74,7 @@ class DefaultAddPusherTaskTest { fun `given a persisted pusher when adding Pusher then updates api and mutates persisted result with Registered state`() { val realmResult = PusherEntity(appDisplayName = null) monarchy.givenWhereReturns(result = realmResult) + .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) runTest { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } @@ -84,6 +88,7 @@ class DefaultAddPusherTaskTest { fun `given a persisted push entity and SetPush API fails when adding Pusher then mutates persisted result with Failed registration state and rethrows`() { val realmResult = PusherEntity() monarchy.givenWhereReturns(result = realmResult) + .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) pushersAPI.givenSetPusherErrors(SocketException()) assertFailsWith<SocketException> { @@ -96,6 +101,7 @@ class DefaultAddPusherTaskTest { @Test fun `given no persisted push entity and SetPush API fails when adding Pusher then rethrows error`() { monarchy.givenWhereReturns<PusherEntity>(result = null) + .givenEqualTo(PusherEntityFields.PUSH_KEY, A_JSON_PUSHER.pushKey) pushersAPI.givenSetPusherErrors(SocketException()) assertFailsWith<SocketException> { diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt new file mode 100644 index 00000000..933087af --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.aggregation.livelocation + +import androidx.work.ExistingWorkPolicy +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.UnsignedData +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.LocationInfo +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent +import org.matrix.android.sdk.internal.database.mapper.ContentMapper +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields +import org.matrix.android.sdk.test.fakes.FakeClock +import org.matrix.android.sdk.test.fakes.FakeRealm +import org.matrix.android.sdk.test.fakes.FakeWorkManagerProvider +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenFindAll +import org.matrix.android.sdk.test.fakes.givenFindFirst +import org.matrix.android.sdk.test.fakes.givenNotEqualTo + +private const val A_SESSION_ID = "session_id" +private const val A_SENDER_ID = "sender_id" +private const val AN_EVENT_ID = "event_id" +private const val A_ROOM_ID = "room_id" +private const val A_TIMESTAMP = 1654689143L +private const val A_TIMEOUT_MILLIS = 15 * 60 * 1000L +private const val A_LATITUDE = 40.05 +private const val A_LONGITUDE = 29.24 +private const val A_UNCERTAINTY = 30.0 +private const val A_GEO_URI = "geo:$A_LATITUDE,$A_LONGITUDE;u=$A_UNCERTAINTY" + +internal class LiveLocationAggregationProcessorTest { + + private val fakeWorkManagerProvider = FakeWorkManagerProvider() + private val fakeClock = FakeClock() + private val fakeRealm = FakeRealm() + private val fakeQuery = fakeRealm.givenWhere<LiveLocationShareAggregatedSummaryEntity>() + + private val liveLocationAggregationProcessor = LiveLocationAggregationProcessor( + sessionId = A_SESSION_ID, + workManagerProvider = fakeWorkManagerProvider.instance, + clock = fakeClock + ) + + @Test + fun `given beacon info when it is local echo then it is ignored`() { + val event = Event(senderId = A_SENDER_ID) + val beaconInfo = MessageBeaconInfoContent() + + val result = liveLocationAggregationProcessor.handleBeaconInfo( + realm = fakeRealm.instance, + event = event, + content = beaconInfo, + roomId = A_ROOM_ID, + isLocalEcho = true + ) + + result shouldBeEqualTo false + } + + private data class IgnoredBeaconInfoEvent( + val event: Event, + val beaconInfo: MessageBeaconInfoContent + ) + + @Test + fun `given beacon info and event when some values are missing then it is ignored`() { + val ignoredInfoEvents = listOf( + // missing senderId + IgnoredBeaconInfoEvent( + event = Event(eventId = AN_EVENT_ID, senderId = null), + beaconInfo = MessageBeaconInfoContent() + ), + // empty senderId + IgnoredBeaconInfoEvent( + event = Event(eventId = AN_EVENT_ID, senderId = ""), + beaconInfo = MessageBeaconInfoContent() + ), + // beacon is live and no eventId + IgnoredBeaconInfoEvent( + event = Event(eventId = null, senderId = A_SENDER_ID), + beaconInfo = MessageBeaconInfoContent(isLive = true) + ), + // beacon is live and eventId is empty + IgnoredBeaconInfoEvent( + event = Event(eventId = "", senderId = A_SENDER_ID), + beaconInfo = MessageBeaconInfoContent(isLive = true) + ), + // beacon is not live and replaced event id is null + IgnoredBeaconInfoEvent( + event = Event( + eventId = AN_EVENT_ID, + senderId = A_SENDER_ID, + unsignedData = UnsignedData( + age = 123, + replacesState = null + ) + ), + beaconInfo = MessageBeaconInfoContent(isLive = false) + ), + // beacon is not live and replaced event id is empty + IgnoredBeaconInfoEvent( + event = Event( + eventId = AN_EVENT_ID, + senderId = A_SENDER_ID, + unsignedData = UnsignedData( + age = 123, + replacesState = "" + ) + ), + beaconInfo = MessageBeaconInfoContent(isLive = false) + ), + ) + + ignoredInfoEvents.forEach { + val result = liveLocationAggregationProcessor.handleBeaconInfo( + realm = fakeRealm.instance, + event = it.event, + content = it.beaconInfo, + roomId = A_ROOM_ID, + isLocalEcho = false + ) + + result shouldBeEqualTo false + } + } + + @Test + fun `given beacon info and existing entity when beacon content is correct and active then it is aggregated`() { + val event = Event( + senderId = A_SENDER_ID, + eventId = AN_EVENT_ID + ) + val beaconInfo = MessageBeaconInfoContent( + isLive = true, + unstableTimestampMillis = A_TIMESTAMP, + timeout = A_TIMEOUT_MILLIS + ) + fakeClock.givenEpoch(A_TIMESTAMP + 5000) + fakeWorkManagerProvider.fakeWorkManager.expectEnqueueUniqueWork() + val aggregatedEntity = givenLastSummaryQueryReturns(eventId = AN_EVENT_ID, roomId = A_ROOM_ID) + val previousEntities = givenActiveSummaryListQueryReturns( + listOf( + LiveLocationShareAggregatedSummaryEntity( + eventId = "${AN_EVENT_ID}1", + roomId = A_ROOM_ID, + userId = A_SENDER_ID, + isActive = true + ) + ) + ) + + val result = liveLocationAggregationProcessor.handleBeaconInfo( + realm = fakeRealm.instance, + event = event, + content = beaconInfo, + roomId = A_ROOM_ID, + isLocalEcho = false + ) + + result shouldBeEqualTo true + aggregatedEntity.eventId shouldBeEqualTo AN_EVENT_ID + aggregatedEntity.roomId shouldBeEqualTo A_ROOM_ID + aggregatedEntity.userId shouldBeEqualTo A_SENDER_ID + aggregatedEntity.isActive shouldBeEqualTo true + aggregatedEntity.endOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP + A_TIMEOUT_MILLIS + aggregatedEntity.lastLocationContent shouldBeEqualTo null + previousEntities.forEach { entity -> + entity.isActive shouldBeEqualTo false + } + fakeWorkManagerProvider.fakeWorkManager.verifyEnqueueUniqueWork( + workName = DeactivateLiveLocationShareWorker.getWorkName(eventId = AN_EVENT_ID, roomId = A_ROOM_ID), + policy = ExistingWorkPolicy.REPLACE + ) + } + + @Test + fun `given beacon info and existing entity when beacon content is correct and inactive then it is aggregated`() { + val unsignedData = UnsignedData( + age = 123, + replacesState = AN_EVENT_ID + ) + val event = Event( + senderId = A_SENDER_ID, + eventId = "", + unsignedData = unsignedData + ) + val beaconInfo = MessageBeaconInfoContent( + isLive = false, + unstableTimestampMillis = A_TIMESTAMP, + timeout = A_TIMEOUT_MILLIS + ) + fakeClock.givenEpoch(A_TIMESTAMP + 5000) + fakeWorkManagerProvider.fakeWorkManager.expectCancelUniqueWork() + val aggregatedEntity = givenLastSummaryQueryReturns(eventId = AN_EVENT_ID, roomId = A_ROOM_ID) + val previousEntities = givenActiveSummaryListQueryReturns( + listOf( + LiveLocationShareAggregatedSummaryEntity( + eventId = "${AN_EVENT_ID}1", + roomId = A_ROOM_ID, + userId = A_SENDER_ID, + isActive = true + ) + ) + + ) + + val result = liveLocationAggregationProcessor.handleBeaconInfo( + realm = fakeRealm.instance, + event = event, + content = beaconInfo, + roomId = A_ROOM_ID, + isLocalEcho = false + ) + + result shouldBeEqualTo true + aggregatedEntity.eventId shouldBeEqualTo AN_EVENT_ID + aggregatedEntity.roomId shouldBeEqualTo A_ROOM_ID + aggregatedEntity.userId shouldBeEqualTo A_SENDER_ID + aggregatedEntity.isActive shouldBeEqualTo false + aggregatedEntity.endOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP + A_TIMEOUT_MILLIS + aggregatedEntity.lastLocationContent shouldBeEqualTo null + previousEntities.forEach { entity -> + entity.isActive shouldBeEqualTo false + } + fakeWorkManagerProvider.fakeWorkManager.verifyCancelUniqueWork( + workName = DeactivateLiveLocationShareWorker.getWorkName(eventId = AN_EVENT_ID, roomId = A_ROOM_ID) + ) + } + + @Test + fun `given beacon location data when it is local echo then it is ignored`() { + val event = Event(senderId = A_SENDER_ID) + val beaconLocationData = MessageBeaconLocationDataContent() + + val result = liveLocationAggregationProcessor.handleBeaconLocationData( + realm = fakeRealm.instance, + event = event, + content = beaconLocationData, + roomId = A_ROOM_ID, + relatedEventId = AN_EVENT_ID, + isLocalEcho = true + ) + + result shouldBeEqualTo false + } + + private data class IgnoredBeaconLocationDataEvent( + val event: Event, + val beaconLocationData: MessageBeaconLocationDataContent + ) + + @Test + fun `given event and beacon location data when some values are missing then it is ignored`() { + val ignoredLocationDataEvents = listOf( + // missing sender id + IgnoredBeaconLocationDataEvent( + event = Event(eventId = AN_EVENT_ID), + beaconLocationData = MessageBeaconLocationDataContent() + ), + // empty sender id + IgnoredBeaconLocationDataEvent( + event = Event(eventId = AN_EVENT_ID, senderId = ""), + beaconLocationData = MessageBeaconLocationDataContent() + ), + ) + + ignoredLocationDataEvents.forEach { + val result = liveLocationAggregationProcessor.handleBeaconLocationData( + realm = fakeRealm.instance, + event = it.event, + content = it.beaconLocationData, + roomId = A_ROOM_ID, + relatedEventId = "", + isLocalEcho = false + ) + result shouldBeEqualTo false + } + } + + @Test + fun `given beacon location data when relatedEventId is null or empty then it is ignored`() { + val event = Event(senderId = A_SENDER_ID) + val beaconLocationData = MessageBeaconLocationDataContent() + + listOf(null, "").forEach { + val result = liveLocationAggregationProcessor.handleBeaconLocationData( + realm = fakeRealm.instance, + event = event, + content = beaconLocationData, + roomId = A_ROOM_ID, + relatedEventId = it, + isLocalEcho = false + ) + result shouldBeEqualTo false + } + } + + @Test + fun `given beacon location data when location is less recent than the saved one then it is ignored`() { + val event = Event(eventId = AN_EVENT_ID, senderId = A_SENDER_ID) + val beaconLocationData = MessageBeaconLocationDataContent( + unstableTimestampMillis = A_TIMESTAMP - 60_000 + ) + val lastBeaconLocationContent = MessageBeaconLocationDataContent( + unstableTimestampMillis = A_TIMESTAMP + ) + givenLastSummaryQueryReturns( + eventId = AN_EVENT_ID, + roomId = A_ROOM_ID, + beaconLocationContent = lastBeaconLocationContent + ) + + val result = liveLocationAggregationProcessor.handleBeaconLocationData( + realm = fakeRealm.instance, + event = event, + content = beaconLocationData, + roomId = A_ROOM_ID, + relatedEventId = AN_EVENT_ID, + isLocalEcho = false + ) + + result shouldBeEqualTo false + } + + @Test + fun `given beacon location data when location is more recent than the saved one then it is aggregated`() { + val event = Event(eventId = AN_EVENT_ID, senderId = A_SENDER_ID) + val locationInfo = LocationInfo(geoUri = A_GEO_URI) + val beaconLocationData = MessageBeaconLocationDataContent( + unstableTimestampMillis = A_TIMESTAMP, + unstableLocationInfo = locationInfo + ) + val lastBeaconLocationContent = MessageBeaconLocationDataContent( + unstableTimestampMillis = A_TIMESTAMP - 60_000 + ) + val entity = givenLastSummaryQueryReturns( + eventId = AN_EVENT_ID, + roomId = A_ROOM_ID, + beaconLocationContent = lastBeaconLocationContent + ) + + val result = liveLocationAggregationProcessor.handleBeaconLocationData( + realm = fakeRealm.instance, + event = event, + content = beaconLocationData, + roomId = A_ROOM_ID, + relatedEventId = AN_EVENT_ID, + isLocalEcho = false + ) + + result shouldBeEqualTo true + val savedLocationData = ContentMapper.map(entity.lastLocationContent).toModel<MessageBeaconLocationDataContent>() + savedLocationData?.getBestTimestampMillis() shouldBeEqualTo A_TIMESTAMP + savedLocationData?.getBestLocationInfo()?.geoUri shouldBeEqualTo A_GEO_URI + } + + private fun givenLastSummaryQueryReturns( + eventId: String, + roomId: String, + beaconLocationContent: MessageBeaconLocationDataContent? = null + ): LiveLocationShareAggregatedSummaryEntity { + val result = LiveLocationShareAggregatedSummaryEntity( + eventId = eventId, + roomId = roomId, + lastLocationContent = ContentMapper.map(beaconLocationContent?.toContent()) + ) + fakeQuery + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId) + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, roomId) + .givenFindFirst(result) + return result + } + + private fun givenActiveSummaryListQueryReturns( + summaryList: List<LiveLocationShareAggregatedSummaryEntity> + ): List<LiveLocationShareAggregatedSummaryEntity> { + fakeQuery + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, A_ROOM_ID) + .givenNotEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, AN_EVENT_ID) + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.USER_ID, A_SENDER_ID) + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true) + .givenFindAll(summaryList) + return summaryList + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessorTest.kt index 837bbeea..3044ca5d 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessorTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessorTest.kt @@ -19,8 +19,6 @@ package org.matrix.android.sdk.internal.session.room.aggregation.poll import io.mockk.every import io.mockk.mockk import io.realm.RealmList -import io.realm.RealmModel -import io.realm.RealmQuery import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeTrue import org.junit.Before @@ -46,6 +44,8 @@ import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsT import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_TIMELINE_EVENT import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_USER_ID_1 import org.matrix.android.sdk.test.fakes.FakeRealm +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenFindFirst class PollAggregationProcessorTest { @@ -135,14 +135,11 @@ class PollAggregationProcessorTest { pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, event).shouldBeFalse() } - private inline fun <reified T : RealmModel> RealmQuery<T>.givenEqualTo(fieldName: String, value: String, result: RealmQuery<T>) { - every { equalTo(fieldName, value) } returns result - } - private fun mockEventAnnotationsSummaryEntity() { - val queryResult = realm.givenWhereReturns(result = EventAnnotationsSummaryEntity()) - queryResult.givenEqualTo(EventAnnotationsSummaryEntityFields.ROOM_ID, A_POLL_REPLACE_EVENT.roomId!!, queryResult) - queryResult.givenEqualTo(EventAnnotationsSummaryEntityFields.EVENT_ID, A_POLL_REPLACE_EVENT.eventId!!, queryResult) + realm.givenWhere<EventAnnotationsSummaryEntity>() + .givenFindFirst(EventAnnotationsSummaryEntity()) + .givenEqualTo(EventAnnotationsSummaryEntityFields.ROOM_ID, A_POLL_REPLACE_EVENT.roomId!!) + .givenEqualTo(EventAnnotationsSummaryEntityFields.EVENT_ID, A_POLL_REPLACE_EVENT.eventId!!) } private fun mockRoom( diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultCheckIfExistingActiveLiveTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultCheckIfExistingActiveLiveTaskTest.kt new file mode 100644 index 00000000..3198392e --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultCheckIfExistingActiveLiveTaskTest.kt @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.location + +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.test.fakes.FakeGetActiveBeaconInfoForUserTask + +private const val A_USER_ID = "user-id" +private const val A_ROOM_ID = "room-id" +private const val A_TIMEOUT = 15_000L +private const val AN_EPOCH = 1655210176L + +@ExperimentalCoroutinesApi +class DefaultCheckIfExistingActiveLiveTaskTest { + + private val fakeGetActiveBeaconInfoForUserTask = FakeGetActiveBeaconInfoForUserTask() + + private val defaultCheckIfExistingActiveLiveTask = DefaultCheckIfExistingActiveLiveTask( + getActiveBeaconInfoForUserTask = fakeGetActiveBeaconInfoForUserTask + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given parameters and existing active live event when calling the task then result is true`() = runTest { + val params = CheckIfExistingActiveLiveTask.Params( + roomId = A_ROOM_ID + ) + val currentStateEvent = Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) + + val result = defaultCheckIfExistingActiveLiveTask.execute(params) + + result shouldBeEqualTo true + val expectedGetActiveBeaconParams = GetActiveBeaconInfoForUserTask.Params( + roomId = params.roomId + ) + fakeGetActiveBeaconInfoForUserTask.verifyExecute(expectedGetActiveBeaconParams) + } + + @Test + fun `given parameters and no existing active live event when calling the task then result is false`() = runTest { + val params = CheckIfExistingActiveLiveTask.Params( + roomId = A_ROOM_ID + ) + val inactiveEvents = listOf( + // no event + null, + // null content + Event( + stateKey = A_USER_ID, + content = null + ), + // inactive live + Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = false, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + ) + + inactiveEvents.forEach { currentStateEvent -> + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) + + val result = defaultCheckIfExistingActiveLiveTask.execute(params) + + result shouldBeEqualTo false + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt new file mode 100644 index 00000000..588bfaa9 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.location + +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.test.fakes.FakeStateEventDataSource + +private const val A_USER_ID = "user-id" +private const val A_ROOM_ID = "room-id" +private const val A_TIMEOUT = 15_000L +private const val AN_EPOCH = 1655210176L + +@ExperimentalCoroutinesApi +class DefaultGetActiveBeaconInfoForUserTaskTest { + + private val fakeStateEventDataSource = FakeStateEventDataSource() + + private val defaultGetActiveBeaconInfoForUserTask = DefaultGetActiveBeaconInfoForUserTask( + userId = A_USER_ID, + stateEventDataSource = fakeStateEventDataSource.instance + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given parameters and no error when calling the task then result is computed`() = runTest { + val currentStateEvent = Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent) + val params = GetActiveBeaconInfoForUserTask.Params( + roomId = A_ROOM_ID + ) + + val result = defaultGetActiveBeaconInfoForUserTask.execute(params) + + result shouldBeEqualTo currentStateEvent + fakeStateEventDataSource.verifyGetStateEvent( + roomId = params.roomId, + eventType = EventType.STATE_ROOM_BEACON_INFO.first(), + stateKey = A_USER_ID + ) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt new file mode 100644 index 00000000..de912065 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.location + +import androidx.arch.core.util.Function +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Transformations +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.slot +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult +import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary +import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.internal.database.mapper.LiveLocationShareAggregatedSummaryMapper +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenIsNotEmpty +import org.matrix.android.sdk.test.fakes.givenIsNotNull + +private const val A_ROOM_ID = "room_id" +private const val AN_EVENT_ID = "event_id" +private const val A_LATITUDE = 1.4 +private const val A_LONGITUDE = 40.0 +private const val AN_UNCERTAINTY = 5.0 +private const val A_TIMEOUT = 15_000L + +@ExperimentalCoroutinesApi +internal class DefaultLocationSharingServiceTest { + + private val fakeMonarchy = FakeMonarchy() + private val sendStaticLocationTask = mockk<SendStaticLocationTask>() + private val sendLiveLocationTask = mockk<SendLiveLocationTask>() + private val startLiveLocationShareTask = mockk<StartLiveLocationShareTask>() + private val stopLiveLocationShareTask = mockk<StopLiveLocationShareTask>() + private val checkIfExistingActiveLiveTask = mockk<CheckIfExistingActiveLiveTask>() + private val fakeLiveLocationShareAggregatedSummaryMapper = mockk<LiveLocationShareAggregatedSummaryMapper>() + + private val defaultLocationSharingService = DefaultLocationSharingService( + roomId = A_ROOM_ID, + monarchy = fakeMonarchy.instance, + sendStaticLocationTask = sendStaticLocationTask, + sendLiveLocationTask = sendLiveLocationTask, + startLiveLocationShareTask = startLiveLocationShareTask, + stopLiveLocationShareTask = stopLiveLocationShareTask, + checkIfExistingActiveLiveTask = checkIfExistingActiveLiveTask, + liveLocationShareAggregatedSummaryMapper = fakeLiveLocationShareAggregatedSummaryMapper + ) + + @Before + fun setUp() { + mockkStatic("androidx.lifecycle.Transformations") + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `static location can be sent`() = runTest { + val isUserLocation = true + val cancelable = mockk<Cancelable>() + coEvery { sendStaticLocationTask.execute(any()) } returns cancelable + + val result = defaultLocationSharingService.sendStaticLocation( + latitude = A_LATITUDE, + longitude = A_LONGITUDE, + uncertainty = AN_UNCERTAINTY, + isUserLocation = isUserLocation + ) + + result shouldBeEqualTo cancelable + val expectedParams = SendStaticLocationTask.Params( + roomId = A_ROOM_ID, + latitude = A_LATITUDE, + longitude = A_LONGITUDE, + uncertainty = AN_UNCERTAINTY, + isUserLocation = isUserLocation, + ) + coVerify { sendStaticLocationTask.execute(expectedParams) } + } + + @Test + fun `live location can be sent`() = runTest { + val cancelable = mockk<Cancelable>() + coEvery { sendLiveLocationTask.execute(any()) } returns cancelable + + val result = defaultLocationSharingService.sendLiveLocation( + beaconInfoEventId = AN_EVENT_ID, + latitude = A_LATITUDE, + longitude = A_LONGITUDE, + uncertainty = AN_UNCERTAINTY + ) + + result shouldBeEqualTo cancelable + val expectedParams = SendLiveLocationTask.Params( + roomId = A_ROOM_ID, + beaconInfoEventId = AN_EVENT_ID, + latitude = A_LATITUDE, + longitude = A_LONGITUDE, + uncertainty = AN_UNCERTAINTY + ) + coVerify { sendLiveLocationTask.execute(expectedParams) } + } + + @Test + fun `given existing active live can be stopped when starting a live then the current live is stopped and the new live is started`() = runTest { + coEvery { checkIfExistingActiveLiveTask.execute(any()) } returns true + coEvery { stopLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success("stopped-event-id") + coEvery { startLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + + val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params( + roomId = A_ROOM_ID + ) + coVerify { checkIfExistingActiveLiveTask.execute(expectedCheckExistingParams) } + val expectedStopParams = StopLiveLocationShareTask.Params( + roomId = A_ROOM_ID + ) + coVerify { stopLiveLocationShareTask.execute(expectedStopParams) } + val expectedStartParams = StartLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + timeoutMillis = A_TIMEOUT + ) + coVerify { startLiveLocationShareTask.execute(expectedStartParams) } + } + + @Test + fun `given existing active live cannot be stopped when starting a live then the result is failure`() = runTest { + coEvery { checkIfExistingActiveLiveTask.execute(any()) } returns true + val error = Throwable() + coEvery { stopLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Failure(error) + + val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Failure(error) + val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params( + roomId = A_ROOM_ID + ) + coVerify { checkIfExistingActiveLiveTask.execute(expectedCheckExistingParams) } + val expectedStopParams = StopLiveLocationShareTask.Params( + roomId = A_ROOM_ID + ) + coVerify { stopLiveLocationShareTask.execute(expectedStopParams) } + } + + @Test + fun `given no existing active live when starting a live then the new live is started`() = runTest { + coEvery { checkIfExistingActiveLiveTask.execute(any()) } returns false + coEvery { startLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + + val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params( + roomId = A_ROOM_ID + ) + coVerify { checkIfExistingActiveLiveTask.execute(expectedCheckExistingParams) } + val expectedStartParams = StartLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + timeoutMillis = A_TIMEOUT + ) + coVerify { startLiveLocationShareTask.execute(expectedStartParams) } + } + + @Test + fun `live location share can be stopped`() = runTest { + coEvery { stopLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + + val result = defaultLocationSharingService.stopLiveLocationShare() + + result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + val expectedParams = StopLiveLocationShareTask.Params( + roomId = A_ROOM_ID + ) + coVerify { stopLiveLocationShareTask.execute(expectedParams) } + } + + @Test + fun `livedata of live summaries is correctly computed`() { + val entity = LiveLocationShareAggregatedSummaryEntity() + val summary = LiveLocationShareAggregatedSummary( + userId = "", + isActive = true, + endOfLiveTimestampMillis = 123, + lastLocationDataContent = null + ) + + fakeMonarchy.givenWhere<LiveLocationShareAggregatedSummaryEntity>() + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, A_ROOM_ID) + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true) + .givenIsNotEmpty(LiveLocationShareAggregatedSummaryEntityFields.USER_ID) + .givenIsNotNull(LiveLocationShareAggregatedSummaryEntityFields.LAST_LOCATION_CONTENT) + fakeMonarchy.givenFindAllMappedWithChangesReturns( + realmEntities = listOf(entity), + mappedResult = listOf(summary), + fakeLiveLocationShareAggregatedSummaryMapper + ) + + val result = defaultLocationSharingService.getRunningLiveLocationShareSummaries().value + + result shouldBeEqualTo listOf(summary) + } + + @Test + fun `given an event id when getting livedata on corresponding live summary then it is correctly computed`() { + val entity = LiveLocationShareAggregatedSummaryEntity() + val summary = LiveLocationShareAggregatedSummary( + userId = "", + isActive = true, + endOfLiveTimestampMillis = 123, + lastLocationDataContent = null + ) + + fakeMonarchy.givenWhere<LiveLocationShareAggregatedSummaryEntity>() + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, A_ROOM_ID) + .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, AN_EVENT_ID) + val liveData = fakeMonarchy.givenFindAllMappedWithChangesReturns( + realmEntities = listOf(entity), + mappedResult = listOf(summary), + fakeLiveLocationShareAggregatedSummaryMapper + ) + val mapper = slot<Function<List<LiveLocationShareAggregatedSummary>, Optional<LiveLocationShareAggregatedSummary>>>() + every { + Transformations.map( + liveData, + capture(mapper) + ) + } answers { + val value = secondArg<Function<List<LiveLocationShareAggregatedSummary>, Optional<LiveLocationShareAggregatedSummary>>>().apply(listOf(summary)) + MutableLiveData(value) + } + + val result = defaultLocationSharingService.getLiveLocationShareSummary(AN_EVENT_ID).value + + result shouldBeEqualTo summary.toOptional() + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultSendLiveLocationTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultSendLiveLocationTaskTest.kt new file mode 100644 index 00000000..423c6800 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultSendLiveLocationTaskTest.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.location + +import io.mockk.mockk +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Test +import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.test.fakes.FakeEventSenderProcessor +import org.matrix.android.sdk.test.fakes.FakeLocalEchoEventFactory + +private const val A_ROOM_ID = "room_id" +private const val AN_EVENT_ID = "event_id" +private const val A_LATITUDE = 1.4 +private const val A_LONGITUDE = 44.0 +private const val AN_UNCERTAINTY = 5.0 + +@ExperimentalCoroutinesApi +internal class DefaultSendLiveLocationTaskTest { + + private val fakeLocalEchoEventFactory = FakeLocalEchoEventFactory() + private val fakeEventSenderProcessor = FakeEventSenderProcessor() + + private val defaultSendLiveLocationTask = DefaultSendLiveLocationTask( + localEchoEventFactory = fakeLocalEchoEventFactory.instance, + eventSenderProcessor = fakeEventSenderProcessor + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given parameters when calling the task then it is correctly executed`() = runTest { + val params = SendLiveLocationTask.Params( + roomId = A_ROOM_ID, + beaconInfoEventId = AN_EVENT_ID, + latitude = A_LATITUDE, + longitude = A_LONGITUDE, + uncertainty = AN_UNCERTAINTY + ) + val event = fakeLocalEchoEventFactory.givenCreateLiveLocationEvent( + withLocalEcho = true + ) + val cancelable = mockk<Cancelable>() + fakeEventSenderProcessor.givenPostEventReturns(event, cancelable) + + val result = defaultSendLiveLocationTask.execute(params) + + result shouldBeEqualTo cancelable + fakeLocalEchoEventFactory.verifyCreateLiveLocationEvent( + roomId = params.roomId, + beaconInfoEventId = params.beaconInfoEventId, + latitude = params.latitude, + longitude = params.longitude, + uncertainty = params.uncertainty + ) + fakeLocalEchoEventFactory.verifyCreateLocalEcho(event) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultSendStaticLocationTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultSendStaticLocationTaskTest.kt new file mode 100644 index 00000000..cfde568b --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultSendStaticLocationTaskTest.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.location + +import io.mockk.mockk +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Test +import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.test.fakes.FakeEventSenderProcessor +import org.matrix.android.sdk.test.fakes.FakeLocalEchoEventFactory + +private const val A_ROOM_ID = "room_id" +private const val A_LATITUDE = 1.4 +private const val A_LONGITUDE = 44.0 +private const val AN_UNCERTAINTY = 5.0 + +@ExperimentalCoroutinesApi +internal class DefaultSendStaticLocationTaskTest { + + private val fakeLocalEchoEventFactory = FakeLocalEchoEventFactory() + private val fakeEventSenderProcessor = FakeEventSenderProcessor() + + private val defaultSendStaticLocationTask = DefaultSendStaticLocationTask( + localEchoEventFactory = fakeLocalEchoEventFactory.instance, + eventSenderProcessor = fakeEventSenderProcessor + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given parameters when calling the task then it is correctly executed`() = runTest { + val params = SendStaticLocationTask.Params( + roomId = A_ROOM_ID, + latitude = A_LATITUDE, + longitude = A_LONGITUDE, + uncertainty = AN_UNCERTAINTY, + isUserLocation = true + ) + val event = fakeLocalEchoEventFactory.givenCreateStaticLocationEvent( + withLocalEcho = true + ) + val cancelable = mockk<Cancelable>() + fakeEventSenderProcessor.givenPostEventReturns(event, cancelable) + + val result = defaultSendStaticLocationTask.execute(params) + + result shouldBeEqualTo cancelable + fakeLocalEchoEventFactory.verifyCreateStaticLocationEvent( + roomId = params.roomId, + latitude = params.latitude, + longitude = params.longitude, + uncertainty = params.uncertainty, + isUserLocation = params.isUserLocation + ) + fakeLocalEchoEventFactory.verifyCreateLocalEcho(event) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt new file mode 100644 index 00000000..909ba5d0 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.location + +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeInstanceOf +import org.junit.After +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.internal.session.room.state.SendStateTask +import org.matrix.android.sdk.test.fakes.FakeClock +import org.matrix.android.sdk.test.fakes.FakeSendStateTask + +private const val A_USER_ID = "user-id" +private const val A_ROOM_ID = "room-id" +private const val AN_EVENT_ID = "event-id" +private const val A_TIMEOUT = 15_000L +private const val AN_EPOCH = 1655210176L + +@ExperimentalCoroutinesApi +internal class DefaultStartLiveLocationShareTaskTest { + + private val fakeClock = FakeClock() + private val fakeSendStateTask = FakeSendStateTask() + + private val defaultStartLiveLocationShareTask = DefaultStartLiveLocationShareTask( + userId = A_USER_ID, + clock = fakeClock, + sendStateTask = fakeSendStateTask + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given parameters and no error when calling the task then result is success`() = runTest { + val params = StartLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + timeoutMillis = A_TIMEOUT + ) + fakeClock.givenEpoch(AN_EPOCH) + fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID) + + val result = defaultStartLiveLocationShareTask.execute(params) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + val expectedBeaconContent = MessageBeaconInfoContent( + timeout = params.timeoutMillis, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + val expectedParams = SendStateTask.Params( + roomId = params.roomId, + stateKey = A_USER_ID, + eventType = EventType.STATE_ROOM_BEACON_INFO.first(), + body = expectedBeaconContent + ) + fakeSendStateTask.verifyExecuteRetry( + params = expectedParams, + remainingRetry = 3 + ) + } + + @Test + fun `given parameters and an empty returned event id when calling the task then result is failure`() = runTest { + val params = StartLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + timeoutMillis = A_TIMEOUT + ) + fakeClock.givenEpoch(AN_EPOCH) + fakeSendStateTask.givenExecuteRetryReturns("") + + val result = defaultStartLiveLocationShareTask.execute(params) + + result shouldBeInstanceOf UpdateLiveLocationShareResult.Failure::class + } + + @Test + fun `given parameters and error during event sending when calling the task then result is failure`() = runTest { + val params = StartLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + timeoutMillis = A_TIMEOUT + ) + fakeClock.givenEpoch(AN_EPOCH) + val error = Throwable() + fakeSendStateTask.givenExecuteRetryThrows(error) + + val result = defaultStartLiveLocationShareTask.execute(params) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Failure(error) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStopLiveLocationShareTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStopLiveLocationShareTaskTest.kt new file mode 100644 index 00000000..1abf179c --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStopLiveLocationShareTaskTest.kt @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.location + +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeInstanceOf +import org.junit.After +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.internal.session.room.state.SendStateTask +import org.matrix.android.sdk.test.fakes.FakeGetActiveBeaconInfoForUserTask +import org.matrix.android.sdk.test.fakes.FakeSendStateTask + +private const val A_USER_ID = "user-id" +private const val A_ROOM_ID = "room-id" +private const val AN_EVENT_ID = "event-id" +private const val A_TIMEOUT = 15_000L +private const val AN_EPOCH = 1655210176L + +@ExperimentalCoroutinesApi +class DefaultStopLiveLocationShareTaskTest { + + private val fakeSendStateTask = FakeSendStateTask() + private val fakeGetActiveBeaconInfoForUserTask = FakeGetActiveBeaconInfoForUserTask() + + private val defaultStopLiveLocationShareTask = DefaultStopLiveLocationShareTask( + sendStateTask = fakeSendStateTask, + getActiveBeaconInfoForUserTask = fakeGetActiveBeaconInfoForUserTask + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given parameters and no error when calling the task then result is success`() = runTest { + val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID) + val currentStateEvent = Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) + fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID) + + val result = defaultStopLiveLocationShareTask.execute(params) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + val expectedBeaconContent = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = false, + unstableTimestampMillis = AN_EPOCH + ).toContent() + val expectedSendParams = SendStateTask.Params( + roomId = params.roomId, + stateKey = A_USER_ID, + eventType = EventType.STATE_ROOM_BEACON_INFO.first(), + body = expectedBeaconContent + ) + fakeSendStateTask.verifyExecuteRetry( + params = expectedSendParams, + remainingRetry = 3 + ) + val expectedGetBeaconParams = GetActiveBeaconInfoForUserTask.Params( + roomId = params.roomId + ) + fakeGetActiveBeaconInfoForUserTask.verifyExecute( + expectedGetBeaconParams + ) + } + + @Test + fun `given parameters and an incorrect current state event when calling the task then result is failure`() = runTest { + val incorrectCurrentStateEvents = listOf( + // no event + null, + // no stateKey + Event( + stateKey = null, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ), + // null content + Event( + stateKey = A_USER_ID, + content = null + ) + ) + + incorrectCurrentStateEvents.forEach { currentStateEvent -> + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) + fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID) + val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID) + + val result = defaultStopLiveLocationShareTask.execute(params) + + result shouldBeInstanceOf UpdateLiveLocationShareResult.Failure::class + } + } + + @Test + fun `given parameters and an empty returned event id when calling the task then result is failure`() = runTest { + val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID) + val currentStateEvent = Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) + fakeSendStateTask.givenExecuteRetryReturns("") + + val result = defaultStopLiveLocationShareTask.execute(params) + + result shouldBeInstanceOf UpdateLiveLocationShareResult.Failure::class + } + + @Test + fun `given parameters and error during event sending when calling the task then result is failure`() = runTest { + val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID) + val currentStateEvent = Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) + val error = Throwable() + fakeSendStateTask.givenExecuteRetryThrows(error) + + val result = defaultStopLiveLocationShareTask.execute(params) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Failure(error) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeClock.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeClock.kt new file mode 100644 index 00000000..febf94f4 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeClock.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022 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.test.fakes + +import io.mockk.every +import io.mockk.mockk +import org.matrix.android.sdk.internal.util.time.Clock + +internal class FakeClock : Clock by mockk() { + fun givenEpoch(epoch: Long) { + every { epochMillis() } returns epoch + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventSenderProcessor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventSenderProcessor.kt new file mode 100644 index 00000000..fbdcf5bf --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeEventSenderProcessor.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 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.test.fakes + +import io.mockk.every +import io.mockk.mockk +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor + +internal class FakeEventSenderProcessor : EventSenderProcessor by mockk() { + + fun givenPostEventReturns(event: Event, cancelable: Cancelable) { + every { postEvent(event) } returns cancelable + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetActiveBeaconInfoForUserTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetActiveBeaconInfoForUserTask.kt new file mode 100644 index 00000000..dc4a4890 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetActiveBeaconInfoForUserTask.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 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.test.fakes + +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.session.room.location.GetActiveBeaconInfoForUserTask + +internal class FakeGetActiveBeaconInfoForUserTask : GetActiveBeaconInfoForUserTask by mockk() { + + fun givenExecuteReturns(event: Event?) { + coEvery { execute(any()) } returns event + } + + fun verifyExecute(params: GetActiveBeaconInfoForUserTask.Params) { + coVerify { execute(params) } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeLocalEchoEventFactory.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeLocalEchoEventFactory.kt new file mode 100644 index 00000000..50ec85f1 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeLocalEchoEventFactory.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2022 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.test.fakes + +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.runs +import io.mockk.verify +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory + +internal class FakeLocalEchoEventFactory { + + val instance = mockk<LocalEchoEventFactory>() + + fun givenCreateStaticLocationEvent(withLocalEcho: Boolean): Event { + val event = Event() + every { + instance.createStaticLocationEvent( + roomId = any(), + latitude = any(), + longitude = any(), + uncertainty = any(), + isUserLocation = any() + ) + } returns event + + if (withLocalEcho) { + every { instance.createLocalEcho(event) } just runs + } + return event + } + + fun givenCreateLiveLocationEvent(withLocalEcho: Boolean): Event { + val event = Event() + every { + instance.createLiveLocationEvent( + beaconInfoEventId = any(), + roomId = any(), + latitude = any(), + longitude = any(), + uncertainty = any() + ) + } returns event + + if (withLocalEcho) { + every { instance.createLocalEcho(event) } just runs + } + return event + } + + fun verifyCreateStaticLocationEvent( + roomId: String, + latitude: Double, + longitude: Double, + uncertainty: Double?, + isUserLocation: Boolean + ) { + verify { + instance.createStaticLocationEvent( + roomId = roomId, + latitude = latitude, + longitude = longitude, + uncertainty = uncertainty, + isUserLocation = isUserLocation + ) + } + } + + fun verifyCreateLiveLocationEvent( + roomId: String, + beaconInfoEventId: String, + latitude: Double, + longitude: Double, + uncertainty: Double? + ) { + verify { + instance.createLiveLocationEvent( + roomId = roomId, + beaconInfoEventId = beaconInfoEventId, + latitude = latitude, + longitude = longitude, + uncertainty = uncertainty + ) + } + } + + fun verifyCreateLocalEcho(event: Event) { + verify { instance.createLocalEcho(event) } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt index 0a22ef89..d77084fe 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt @@ -16,40 +16,65 @@ package org.matrix.android.sdk.test.fakes +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import com.zhuinden.monarchy.Monarchy import io.mockk.MockKVerificationScope import io.mockk.coEvery import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic -import io.mockk.verify +import io.mockk.slot import io.realm.Realm import io.realm.RealmModel import io.realm.RealmQuery -import io.realm.kotlin.where import org.matrix.android.sdk.internal.util.awaitTransaction internal class FakeMonarchy { val instance = mockk<Monarchy>() - private val realm = mockk<Realm>(relaxed = true) + private val fakeRealm = FakeRealm() init { mockkStatic("org.matrix.android.sdk.internal.util.MonarchyKt") coEvery { instance.awaitTransaction(any<suspend (Realm) -> Any>()) } coAnswers { - secondArg<suspend (Realm) -> Any>().invoke(realm) + secondArg<suspend (Realm) -> Any>().invoke(fakeRealm.instance) } } - inline fun <reified T : RealmModel> givenWhereReturns(result: T?) { - val queryResult = mockk<RealmQuery<T>>(relaxed = true) - every { queryResult.findFirst() } returns result - every { realm.where<T>() } returns queryResult + inline fun <reified T : RealmModel> givenWhere(): RealmQuery<T> { + return fakeRealm.givenWhere() + } + + inline fun <reified T : RealmModel> givenWhereReturns(result: T?): RealmQuery<T> { + return fakeRealm.givenWhere<T>() + .givenFindFirst(result) } inline fun <reified T : RealmModel> verifyInsertOrUpdate(crossinline verification: MockKVerificationScope.() -> T) { - verify { realm.insertOrUpdate(verification()) } + fakeRealm.verifyInsertOrUpdate(verification) + } + + inline fun <reified R, reified T : RealmModel> givenFindAllMappedWithChangesReturns( + realmEntities: List<T>, + mappedResult: List<R>, + mapper: Monarchy.Mapper<R, T> + ): LiveData<List<R>> { + every { mapper.map(any()) } returns mockk() + val monarchyQuery = slot<Monarchy.Query<T>>() + val monarchyMapper = slot<Monarchy.Mapper<R, T>>() + val result = MutableLiveData(mappedResult) + every { + instance.findAllMappedWithChanges(capture(monarchyQuery), capture(monarchyMapper)) + } answers { + monarchyQuery.captured.createQuery(fakeRealm.instance) + realmEntities.forEach { + monarchyMapper.captured.map(it) + } + result + } + return result } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt index c07f8e18..0ebff872 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt @@ -16,21 +16,84 @@ package org.matrix.android.sdk.test.fakes +import io.mockk.MockKVerificationScope import io.mockk.every import io.mockk.mockk +import io.mockk.verify import io.realm.Realm import io.realm.RealmModel import io.realm.RealmQuery +import io.realm.RealmResults import io.realm.kotlin.where internal class FakeRealm { val instance = mockk<Realm>(relaxed = true) - inline fun <reified T : RealmModel> givenWhereReturns(result: T?): RealmQuery<T> { - val queryResult = mockk<RealmQuery<T>>() - every { queryResult.findFirst() } returns result - every { instance.where<T>() } returns queryResult - return queryResult + inline fun <reified T : RealmModel> givenWhere(): RealmQuery<T> { + val query = mockk<RealmQuery<T>>() + every { instance.where<T>() } returns query + return query } + + inline fun <reified T : RealmModel> verifyInsertOrUpdate(crossinline verification: MockKVerificationScope.() -> T) { + verify { instance.insertOrUpdate(verification()) } + } +} + +inline fun <reified T : RealmModel> RealmQuery<T>.givenFindFirst( + result: T? +): RealmQuery<T> { + every { findFirst() } returns result + return this +} + +inline fun <reified T : RealmModel> RealmQuery<T>.givenFindAll( + result: List<T> +): RealmQuery<T> { + val realmResults = mockk<RealmResults<T>>() + result.forEachIndexed { index, t -> + every { realmResults[index] } returns t + } + every { realmResults.size } returns result.size + every { findAll() } returns realmResults + return this +} + +inline fun <reified T : RealmModel> RealmQuery<T>.givenEqualTo( + fieldName: String, + value: String +): RealmQuery<T> { + every { equalTo(fieldName, value) } returns this + return this +} + +inline fun <reified T : RealmModel> RealmQuery<T>.givenEqualTo( + fieldName: String, + value: Boolean +): RealmQuery<T> { + every { equalTo(fieldName, value) } returns this + return this +} + +inline fun <reified T : RealmModel> RealmQuery<T>.givenNotEqualTo( + fieldName: String, + value: String +): RealmQuery<T> { + every { notEqualTo(fieldName, value) } returns this + return this +} + +inline fun <reified T : RealmModel> RealmQuery<T>.givenIsNotEmpty( + fieldName: String +): RealmQuery<T> { + every { isNotEmpty(fieldName) } returns this + return this +} + +inline fun <reified T : RealmModel> RealmQuery<T>.givenIsNotNull( + fieldName: String +): RealmQuery<T> { + every { isNotNull(fieldName) } returns this + return this } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSendStateTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSendStateTask.kt new file mode 100644 index 00000000..08a25be9 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSendStateTask.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 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.test.fakes + +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.room.state.SendStateTask + +internal class FakeSendStateTask : SendStateTask by mockk() { + + fun givenExecuteRetryReturns(eventId: String) { + coEvery { executeRetry(any(), any()) } returns eventId + } + + fun givenExecuteRetryThrows(error: Throwable) { + coEvery { executeRetry(any(), any()) } throws error + } + + fun verifyExecuteRetry(params: SendStateTask.Params, remainingRetry: Int) { + coVerify { executeRetry(params, remainingRetry) } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt new file mode 100644 index 00000000..ca03316f --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022 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.test.fakes + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource + +internal class FakeStateEventDataSource { + + val instance: StateEventDataSource = mockk() + + fun givenGetStateEventReturns(event: Event?) { + every { + instance.getStateEvent( + roomId = any(), + eventType = any(), + stateKey = any() + ) + } returns event + } + + fun verifyGetStateEvent(roomId: String, eventType: String, stateKey: String) { + verify { + instance.getStateEvent( + roomId = roomId, + eventType = eventType, + stateKey = QueryStringValue.Equals(stateKey) + ) + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeWorkManager.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeWorkManager.kt new file mode 100644 index 00000000..b29d015a --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeWorkManager.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 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.test.fakes + +import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequest +import androidx.work.WorkManager +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify + +class FakeWorkManager { + + val instance = mockk<WorkManager>() + + fun expectEnqueueUniqueWork() { + every { instance.enqueueUniqueWork(any(), any(), any<OneTimeWorkRequest>()) } returns mockk() + } + + fun verifyEnqueueUniqueWork(workName: String, policy: ExistingWorkPolicy) { + verify { instance.enqueueUniqueWork(workName, policy, any<OneTimeWorkRequest>()) } + } + + fun expectCancelUniqueWork() { + every { instance.cancelUniqueWork(any()) } returns mockk() + } + + fun verifyCancelUniqueWork(workName: String) { + verify { instance.cancelUniqueWork(workName) } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeWorkManagerProvider.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeWorkManagerProvider.kt new file mode 100644 index 00000000..51ff24c0 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeWorkManagerProvider.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 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.test.fakes + +import io.mockk.every +import io.mockk.mockk +import org.matrix.android.sdk.internal.di.WorkManagerProvider + +internal class FakeWorkManagerProvider( + val fakeWorkManager: FakeWorkManager = FakeWorkManager(), +) { + + val instance = mockk<WorkManagerProvider>().also { + every { it.workManager } returns fakeWorkManager.instance + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/util/DefaultBuildVersionSdkIntProviderTests.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/util/DefaultBuildVersionSdkIntProviderTests.kt new file mode 100644 index 00000000..c118cf07 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/util/DefaultBuildVersionSdkIntProviderTests.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2022 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.util + +import android.os.Build +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.util.DefaultBuildVersionSdkIntProvider + +class DefaultBuildVersionSdkIntProviderTests { + + @Test + fun getReturnsCurrentVersionFromBuild_Version_SDK_INT() { + val provider = DefaultBuildVersionSdkIntProvider() + provider.get() shouldBeEqualTo Build.VERSION.SDK_INT + } +} -- GitLab