diff --git a/dependencies.gradle b/dependencies.gradle index 451ad4449bc026db99e0f5852244dc49172b1e28..db9278b9757695e11478cb61a1bbae54046c6e33 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 59cefe7e899545202918938527213805f5982217..b785c7f50b71d941d22ebd9d8beabd29e73c1a07 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 f6a7a294de26955a5ed883525332809aea9a93f4..a7cb1cad2fc016d84f240d069491f57bf544ef66 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 b08c88fb246da80217ee2c84e079053e56382319..d0d64491efaba62d3b6f740c33eb64ffe1f9f880 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 6bcd12742b3d630a577f99355aca1e3064a74a60..14f985243cd528d78e00deee39161f8774603d1d 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 daf6b73313fd900db73e8fde539321714fafba6e..6cf01d4ae238e2da5e5661c1db528cb73d9924eb 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 0cc0ef57c411e7585ac56ed889e18603551f0d0f..38136ff5cee79fd5576e8063884218fca948c4ce 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 5396251438d56fbe6d230220f833f0851a0e78c2..63ca963479f2c0edb135aac507a861d700d90bb4 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 979201706b20cb838744b7764c8db745589239f7..953ebddcbf73fe4226e8e723750a5b2f2ef2061c 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 21106fba6c3d6d7f724310c1cf051961082200f5..893e90fb3efbafe59c44b5125522b082e63aadbf 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 7d1407c0d81a9186c7cb912ef0a21d251611f316..5b6c1897bf73236a023a15d9c7f1e52f31dee8c0 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 5b8d2328c769ef3c49d192eac13c232e4226bd56..145cdbdc228ad1f405a66f1b60c7f154614ff2eb 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 0000000000000000000000000000000000000000..d0cee08831d90160ab84c5713b7a886d68b31356 --- /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 f08c86885d7e2179e40d26fe10d437e82ee2f772..d3f6ec2287f8e375f43788c2e1cab9bf45489de9 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 8b35bd173ee35dc73993e43bb610c39d7b3ac928..bd2a1078b244702923b86f1e54715005cba9cead 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 0000000000000000000000000000000000000000..37a40fd677d79984a9d64215c58a83003be9a64a --- /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 6b75c94cb2ef2f057bdf99f49a7e03b947f6ef5d..e217611d9691661ebe15f52e538fef91f4ea3423 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 b3a629094cb20d79a751600b795c69c8ed6638a1..1b01239de5222f1b0deca3a3660c862ec987cf81 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 e3d52adfc58da34c9825536ebeaab226e5ea32a0..094c66f6f7bf11b868535442e22c2ff99c69a96d 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 9cc87b6f718476c8b0ea5ee530926e9507688427..638da1180498472bb9e936248dcf045611c0a9b9 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 c78fb9cf7928c372435b2930d6d0b8f3f9be3eda..b5d6d891e400a3f18e132c85dfbc9683efd8c6bb 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 0e631427bd320aefbf516ca120f16b278a31071d..b30c60554f2811c19f9c046a2e711fe465be5ea8 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 dd48d51f45a6a63ce67f4ac143e5581b6e85633c..ada3dc85d78a66291a9cd7e89af6fb66335ad30c 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 0000000000000000000000000000000000000000..6f8c03be4642a5a5859d4ca7075303164d5b5d0d --- /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 a1fd3bd2ecc5c3c2251568984db63ace128a96a1..e0a7846167e0e868ec213704ded41284e1b5ff26 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 0a66a6e400d5b3a75a7549ad15863890d379dd71..30420fd3c7028089852754fcb01fdab675ce4c8d 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 81b034a809b3debb3dd2faf8d13b241b0ae36c6b..ee31d5959ea93ff426d300c4ff70e1f6e10606e1 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 661c3be5bd01a703d70bcba346b44f853e50bc54..9cf062356f3e132159307746e4681d69d23cbf68 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 c79171f1564cb0e6c7960f9fd4b6080e806f2240..6ca63c2c49b75fafbd4a717564c0d9e02fb47a3f 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 9e45fc126dbad4b82ddc4c3c8b011229c86d7ecc..6a9506fd9e09b296f7484d0acb1b6c3307362aa3 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 8ad6500d25bb6c7b366309a3151aa1fed2855dc4..c2094f46bd4881fce4058ca82500a1e8eaeb9a75 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 515656049a0fe1af25056af5f64aae720a0fd122..b7ea187ec508b7453f821973187eea8aa0b0e7e5 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 806c6e97351b4289fdef0a758fc938228eaa2839..7f0024cafa8495a5bce7c3b1d21c7047eab1d2d3 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 bd2dac9e3ccaef4f3c31870a2f33942f4f9e01e3..5f5bb1f9514d452645eadb80f5a639dc885ef44a 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 61a423669c46ea9e43fae37217edadc743246712..9d6b018a672cc113b3cf5e92820ba9d41b3f92b0 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 20b056f1c79bcd3300a09d281e89e8c7a122e5d2..656a4f671bd46488bedd98cebaca32363a8f3fca 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 4e0c000f87f4e769451907448b2a39f832da195d..01481f70dcc5b102aa20902fd20a616d35ec1e85 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 cd38b68a85ec10a9b4101fc9c44061c6d2b80674..75639c6a218d97e04fe1f2a3212d2eb75a980168 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 cee4b12138ed45063314d4c0a8399ba44efd01ee..915b25134b2f0f687a7a443796db800f50c6e0d2 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 719f366518fd685e41ebe0f75dda37bdd3321e9d..e0bcde2296369032d1140b01a51e0cfa831f1a55 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 b18de34329452f229e429a1027dd257939440bf3..b5b8d8e97446d08d5cf5494738824f3066f0dd49 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 c56e4d320b82ec41cb6a9fb22f42ba9c9c0aaccd..028d8f73f9556bcdb8002704e8d107b3e36d8e84 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 9f123f0c08638163b26a6c3288793c29656fb693..821663bcff1f8255576454bb39e4a4ccc33dc9d3 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 b3a039d11927f9badf9b04d7df0980acd4c964ba..86355ceaa848eba98adeef3924635a6858e71ad7 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 6a8589bc5e26bafdd377cab9b15a055b6e7223e9..665567bf2a4d3ebdf10ee4d3f8bf3e6b3157e0ce 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 79a99cdfac4c282d5d9877fed8ba7278a930ab59..0a6d4bf833285e5312630fdb373faa39e448b6a0 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 5b60c536425155562f3cfad3528e5a3793eb9f7b..0f0a847c783f3836b7d8fe223f9bd48ff2e19f80 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 20af43530c4821a11892dc1fd8d37c8dbb1a5da3..184a0108b910191cacf7380f48b7bf96f03230db 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 9460e4c6ba228a5264826819c88fc9e760b2d349..4a4c730a0be7fd2b7b2f2a4dfaf299498634917d 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 aebca11c2bb3b409057d09de82dbe8d508d42604..17dc0f7c8270487595fe7031e1ac948d2e5cb91c 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 b9c611f5dde8c0d209229c60a3b8ba70759e3554..5d24b1433c63ba92b13e74fa148a7c3452dc25c2 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 0000000000000000000000000000000000000000..e278b7475670e46916e1d9ef2ae05e93504f8476 --- /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 47a83f0ed998fbbd27713416a302b90bde103809..9d90973f8abded22d4ccb9b017badfc5a8351ed7 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 7dfeb6884abf247224c8561a2e6ab6b8a7b2d743..d69f251f6ffbf769227e29b722d0ce93a52fd72c 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 0000000000000000000000000000000000000000..f392d39d7b7b53150bae04b4e5eaa85028a23614 --- /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 0000000000000000000000000000000000000000..3f2e6fafc880bd9081968afd64bfe7fbbf3000f0 --- /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 095916643cb8d5e7fc35e54a72d1e65cfa3af103..44ec90ed40f3c08aeecd5b3ed2dd8ab91df865f5 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 862cf463b2c2ce7e1316c4bda26dea59a09d9c97..b5b46a3f5ae5b89c82364f2765ce9ddd84fed033 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 3920c3b527bd0c7c65654d5269682dc62ccf747f..1c395c2d615fd276c0870d92870243774283f2b2 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 ef8133dd15cfd6574018be8e27411bbbefdc0469..8f6605d65702f0bc7dfa5820d2f3c2f939bbce9f 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 32269c9afddbdbe0bf751e38c1743b9b5da7eedf..7c50a0ff84ede28af924cbcf0c1856206b8e46fa 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 f01451b6887e4b1b5e85b14ed5be44ada7664676..d3cae3ac2d84cc85bb474608f9daa910408fdd36 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 756b9cef836e5d4cd6dced030e1723f53c46352c..0dae24e64b878d936c85c1e35cf817a43850f16f 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 2c2317de0d0ed7a6bd06526c109e1a9626d216b4..f8a52f0b7ed5ac51f4473a32f9a6b607fde41d7b 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 1b95820918e3d830e4b14e5b96d3b079004e2137..f6778327d9f53073cc1c45c0b92f07b55585d8f2 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 7b21ba2e63b918a37cbbd6a53f0e2668d74b2a10..e767950ff747bca0425d60103b8ba59ebe28963f 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 bb830a5e419a1bc42e906c0b0bbd9c4442d481b0..9d03ec479b3a5c8d16a9e6ae82ed4840af332639 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 d22da8f6f2102a4f6ce0cae9f318f06cbc5f117c..add69dd8c7d90e387909626283f3bb6358454b8a 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 464ae96e3a4eda60f5fd7125767d257117e7bbe0..33d81648954bf7a8864ce07e74808ed9dce3eccd 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 edc45fe94549b839c2e0b8096af61d38d539aaea..5fb20bb259830b842dc0d10851326174de8e70d1 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 ace5ee0b9a17d0448e8243deed3f27dfcc83d02d..c2310f4fdafcad997399072a61b8e76f58b7aeb0 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 bb43d90328b90c4b4b6cb9b9865e0df55bf4aa44..24d4975eb9a905d53cfac917043dbc93c464334a 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 f3845f1f15c93911224c72b339367387088912a6..c4d37d124b2f2d664762205f25742d79ba315dd2 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 05bde8f83f9214beda1bb6d9ac86e83c4000791d..921749122b3e511dcf7246ab05f3fc9161d75d03 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 0000000000000000000000000000000000000000..228a046f531e9a491c7be47e7b6efa9e997978f2 --- /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 8cf6fcdfbf4e43389e62f2165e35157555e96b69..20320cad230919b30ddfe0225e18999b69e4b265 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 0000000000000000000000000000000000000000..a8d955af1dd0652641763d745f3b88996aa5f434 --- /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 0000000000000000000000000000000000000000..bebd9c774a2fa865449ba5bf62e73240db97092f --- /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 0000000000000000000000000000000000000000..e08b82f3d4893589718a41769f5e953f03ea07aa --- /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 0000000000000000000000000000000000000000..b943c279778b1ef043b7bc4222ee306fb791ac90 --- /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 0000000000000000000000000000000000000000..da5fd76940dbffba32cfc02bc99bd519af62f06b --- /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 005d7f26db4f9fe86aeebcc56fe834862e6f0656..ef89ca33a723f71b52f60b82479573ad505fb40d 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 15d0889255155dadf49bbae7c156d9ccc4a551e1..7052eb23e247c99c4d195a0850588e3fdf85f334 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 1e36e9c6da52a350429d74038a7be2d9f6623cdf..fd6552525e92883a9e09e8f4b6f0aa5fb2abffb2 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 1b836e36a681da6db9d3031050c7b761cc25726f..dd28bbcc73dd1e62626c73171488b4ee264670aa 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 bad734173e84c33455141c27db2111549b3a738a..bac810f424704bb3604d25d07dd044410c4198bc 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 fc78abcfd93bd355147e81fd847167c8ce87cb67..418000abed9037e598550f14533a94234b111fa0 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 3b9ca44d18cb34826a2f5ed9518c5a74a3c94674..f52500de1b58638ba3d0c26021007653e7e10d78 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 2a980f32863e77ac2a4bf38d3d2330599a449942..ad47b82428ca86ec961aa575fd1341559a4ac47f 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 18c709adf2d2b3f1ac470ec672bd525632715203..9971ce3ccc536f9abb409cc272a67b05237ab036 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 7795a56cbf44ce60b6a66962f05d0892dcf1b1bb..4eaac67e5ae3662ddfe4632e18cef9446e533deb 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 aef9e24c8b912d7a80dc02a082a55fe81c839a35..7c662444e4bb10cbf28db56acfda64a8f524518b 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 b2692bf8051c7924a3c1c6573447c85b4bad48eb..b78ad45938ca61a7819ab7098f0fb766dd93240b 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 c5d4d346fdf8b82dbf7b4f490fd118468d305a41..d81a115676fd8659529780275d0ed868783895e5 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 e13f3f454fb0af42327164318f449a255053b38f..7fa36969b16e823d7c67a5bf01b553c87187a8fb 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 fd1703dbc8a507a0886f5d5bfff211b91541e895..ea22f8cd789c16f55d6a1bd5302449ad2c94f708 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 dc12c3209b2811398a41c35ec2009ac0074b5f08..0bde3a11d29861a4290a1950d284e9836c695a31 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 c08d9389a87b5f5841b93eea8cd475b85f0d2bf9..d2f1b3202b049da22a5d84c6d8516c2418b26c52 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 f99fe964102fa06a7ad9bacc93f9d1625e68af3f..30e1ec6679347ca37b5c6790d198f60452f98994 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 8c7557a5b8611f156d8f970457fc0c2e79ea584f..70553359ff1a521ce6fadc486019afa02454c7d1 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 7b2edf2dbf3f22b189a5018e07ee1015c36a377d..9c59c11345aff2b47b4abe3c274ab0425ace7484 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 3f7db93b97d23efe45721238f63c3b03c641a8d7..37a329af34f54137d2877ad98384d4eb4f784b12 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 2dd16d83750a26aecf2a9cd4c67b9082cce14736..901d0eca8fb98b1028f5e93210097480507ff75c 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 396d12f369c8fb611444f1549dfbc2504830339e..8c7d7704eda3115058b697264c257bf392971180 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 0000000000000000000000000000000000000000..0d0450adc2b9aab323a55c97a3627d0ddb8fbd81 --- /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 32b1d44fb9a5429c25cf1187f358507085b5175e..dac33069f348732ec667531b993025c5e1ebbad6 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 0000000000000000000000000000000000000000..933087af2b6027fb264ed584973dd882095fc85a --- /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 837bbeea268b60defcf9da04a6437c3b501db454..3044ca5d436e6945027ccd2712a09ea1bffc5442 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 0000000000000000000000000000000000000000..3198392eabecbc1d592b63911f3787279d0aecbd --- /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 0000000000000000000000000000000000000000..588bfaa9796a68ae4d9e523d7f206897a97c5e71 --- /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 0000000000000000000000000000000000000000..de9120653160a866563253f94ae3d8e9794bea71 --- /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 0000000000000000000000000000000000000000..423c6800549ab4c96fb21ed80afcfb44ccc83c82 --- /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 0000000000000000000000000000000000000000..cfde568b71ba15d09685f1aa3f9aeadba310c143 --- /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 0000000000000000000000000000000000000000..909ba5d048eb93e39e130a9f59c33dd9fb5c2c01 --- /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 0000000000000000000000000000000000000000..1abf179ccf3bacec5c676c73d8b765a0eab6819e --- /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 0000000000000000000000000000000000000000..febf94f4cf59c31fddee5b8b23386acd051dbee6 --- /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 0000000000000000000000000000000000000000..fbdcf5bfd7cf6f0bf6ba97d0e726afb35c0af6b1 --- /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 0000000000000000000000000000000000000000..dc4a48908a6e3e1de8732c9a105eab5d6eef4e30 --- /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 0000000000000000000000000000000000000000..50ec85f14a2e186c3033c3f3f508111157ef5c60 --- /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 0a22ef899664023c842b35034aee27c5c233acb5..d77084fe3b0fdb33e81f5ffc9e5482dacc2b3565 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 c07f8e187363e934a79ca52e16ae224f1ecff85f..0ebff8727889dd42832674f3e5ab2bbacfa39843 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 0000000000000000000000000000000000000000..08a25be93ed372a6e4f63d161fe43d79e53bf9e1 --- /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 0000000000000000000000000000000000000000..ca03316fa728a0ae34652496b106c1b3c7c0b9ba --- /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 0000000000000000000000000000000000000000..b29d015a431b2c4661be5fe735eb8189f7f9966a --- /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 0000000000000000000000000000000000000000..51ff24c01d24efab7c53a24ca95b37a749b83d13 --- /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 0000000000000000000000000000000000000000..c118cf07a16e9690bafd7847da6c610939d52c6a --- /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 + } +}