diff --git a/CHANGES.md b/CHANGES.md index 125e9b3ac914145cddc73865193cf5e5559d7558..c64c102d129b913d0071a50668f61167854c0826 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ Please also refer to the Changelog of Element Android: https://github.com/vector-im/element-android/blob/master/CHANGES.md +Changes in Matrix-SDK 1.0.7 (2020-09-23) +=================================================== + +Imported from Element 1.0.7. (https://github.com/vector-im/element-android/releases/tag/v1.0.7) + Changes in Matrix-SDK 1.0.6 (2020-09-08) =================================================== diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index e2ad3986e09e89dcd9715f47612682ef3886e210..222e8ea3c42846532296da088e16b68772197c1f 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -26,7 +26,7 @@ android { minSdkVersion 21 targetSdkVersion 29 versionCode 1 - versionName "1.0.6" + versionName "1.0.7" // Multidex is useful for tests multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt index df26bb12273f973dc48dc6419e7fc9ce67021b97..751b2a708c7c4e41f1571cb57849dd270ef85fd5 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt @@ -24,6 +24,7 @@ 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.legacy.LegacySessionImporter +import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.common.DaggerTestMatrixComponent import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.network.UserAgentHolder @@ -41,6 +42,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo @Inject internal lateinit var legacySessionImporter: LegacySessionImporter @Inject internal lateinit var authenticationService: AuthenticationService + @Inject internal lateinit var rawService: RawService @Inject internal lateinit var userAgentHolder: UserAgentHolder @Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver @Inject internal lateinit var olmManager: OlmManager @@ -61,6 +63,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo return authenticationService } + fun rawService() = rawService + fun legacySessionImporter(): LegacySessionImporter { return legacySessionImporter } 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 50290e1d632498ca9878f3b43862ebeb0cd5eaf4..e2ab16cad31046593959c08ab10d9f90099c8252 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 @@ -25,8 +25,16 @@ import org.matrix.android.sdk.internal.di.MatrixComponent import org.matrix.android.sdk.internal.di.MatrixModule import org.matrix.android.sdk.internal.di.MatrixScope import org.matrix.android.sdk.internal.di.NetworkModule +import org.matrix.android.sdk.internal.raw.RawModule -@Component(modules = [TestModule::class, MatrixModule::class, NetworkModule::class, AuthModule::class, TestNetworkModule::class]) +@Component(modules = [ + TestModule::class, + MatrixModule::class, + NetworkModule::class, + AuthModule::class, + RawModule::class, + TestNetworkModule::class +]) @MatrixScope internal interface TestMatrixComponent : MatrixComponent { 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 aafefa204861176dbd2e27ac06a3751f3901bff2..e6f982682bc35664c1abcb2af0a1b9d7f2ea4961 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 @@ -25,6 +25,7 @@ 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.legacy.LegacySessionImporter +import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.di.DaggerMatrixComponent import org.matrix.android.sdk.internal.network.UserAgentHolder @@ -42,6 +43,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo @Inject internal lateinit var legacySessionImporter: LegacySessionImporter @Inject internal lateinit var authenticationService: AuthenticationService + @Inject internal lateinit var rawService: RawService @Inject internal lateinit var userAgentHolder: UserAgentHolder @Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver @Inject internal lateinit var olmManager: OlmManager @@ -62,6 +64,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo return authenticationService } + fun rawService() = rawService + fun legacySessionImporter(): LegacySessionImporter { return legacySessionImporter } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt index b10cae6171a08d7228f2760bd18113af59980d1c..4f7bc75556c1e9988d57c33611745531a45b6f6f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/WellKnown.kt @@ -42,9 +42,6 @@ import org.matrix.android.sdk.api.util.JsonDict * } * ] * } - * "im.vector.riot.jitsi": { - * "preferredDomain": "https://jitsi.riot.im/" - * } * } * </pre> */ @@ -57,24 +54,5 @@ data class WellKnown( val identityServer: WellKnownBaseConfig? = null, @Json(name = "m.integrations") - val integrations: JsonDict? = null, - - @Json(name = "im.vector.riot.e2ee") - val e2eAdminSetting: E2EWellKnownConfig? = null, - - @Json(name = "im.vector.riot.jitsi") - val jitsiServer: WellKnownPreferredConfig? = null - -) - -@JsonClass(generateAdapter = true) -data class E2EWellKnownConfig( - @Json(name = "default") - val e2eDefault: Boolean = true -) - -@JsonClass(generateAdapter = true) -data class WellKnownPreferredConfig( - @Json(name = "preferredDomain") - val preferredDomain: String? = null + val integrations: JsonDict? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt index ff68107ffcb585dac73fe5bd9a8c9269c1a6dbb7..77a5dc4f85bcca8ca914178e0566fcd01edeafe7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt @@ -132,6 +132,8 @@ data class MatrixError( const val M_CANNOT_LEAVE_SERVER_NOTICE_ROOM = "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM" /** (Not documented yet) */ const val M_WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION" + /** (Not documented yet) */ + const val M_WEAK_PASSWORD = "M_WEAK_PASSWORD" const val M_TERMS_NOT_SIGNED = "M_TERMS_NOT_SIGNED" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawCacheStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawCacheStrategy.kt new file mode 100644 index 0000000000000000000000000000000000000000..06657a98697d33eaabc9eabed99e44f4d13054da --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawCacheStrategy.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * Copyright 2020 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.raw + +sealed class RawCacheStrategy { + // Data is always fetched from the server + object NoCache: RawCacheStrategy() + + // Once data is retrieved, it is stored for the provided amount of time. + // In case of error, and if strict is set to false, the cache can be returned if available + data class TtlCache(val validityDurationInMillis: Long, val strict: Boolean): RawCacheStrategy() + + // Once retrieved, the data is stored in cache and will be always get from the cache + object InfiniteCache: RawCacheStrategy() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt new file mode 100644 index 0000000000000000000000000000000000000000..5c96d175cb30dd27c4dfb502786a306d86cd58d4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2020 New Vector Ltd + * Copyright 2020 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.raw + +import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.util.Cancelable + +/** + * Useful methods to fetch raw data from the server. The access token will not be used to fetched the data + */ +interface RawService { + /** + * Get a URL, either from cache or from the remote server, depending on the cache strategy + */ + fun getUrl(url: String, + rawCacheStrategy: RawCacheStrategy, + matrixCallback: MatrixCallback<String>): Cancelable + + /** + * Specific case for the well-known file. Cache validity is 8 hours + */ + fun getWellknown(userId: String, matrixCallback: MatrixCallback<String>): Cancelable + + /** + * Clear all the cache data + */ + fun clearCache(matrixCallback: MatrixCallback<Unit>): Cancelable +} 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 cfddf73363cda80472e784ed55632794553819ff..4dfc24ddaeb6300c50a1e4056d3e675b3ce0dbf2 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 @@ -110,7 +110,7 @@ interface Session : * This does not work in doze mode :/ * If battery optimization is on it can work in app standby but that's all :/ */ - fun startAutomaticBackgroundSync(repeatDelay: Long = 30_000L) + fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long) fun stopAnyBackgroundSync() 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 de7ac45bf364943e338b043567719ceb21761e11..e12d99d6b8f980469bebc44d2e8fdf98e01aa6b4 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 @@ -33,16 +33,7 @@ data class HomeServerCapabilities( /** * Default identity server url, provided in Wellknown */ - val defaultIdentityServerUrl: String? = null, - /** - * Option to allow homeserver admins to set the default E2EE behaviour back to disabled for DMs / private rooms - * (as it was before) for various environments where this is desired. - */ - val adminE2EByDefault: Boolean = true, - /** - * Preferred Jitsi domain, provided in Wellknown - */ - val preferredJitsiDomain: String? = null + val defaultIdentityServerUrl: String? = null ) { companion object { const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt new file mode 100644 index 0000000000000000000000000000000000000000..2b0132817d3033d794b7ed00e85c6ee0d85adc58 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomSummaryConstants.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.summary + +import org.matrix.android.sdk.api.session.events.model.EventType + +object RoomSummaryConstants { + + val PREVIEWABLE_TYPES = listOf( + // TODO filter message type (KEY_VERIFICATION_READY, etc.) + EventType.MESSAGE, + EventType.CALL_INVITE, + EventType.CALL_HANGUP, + EventType.CALL_ANSWER, + EventType.ENCRYPTED, + EventType.STICKER, + EventType.REACTION + ) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt new file mode 100644 index 0000000000000000000000000000000000000000..eccc46b5d84261d1c47b282f8d071f9568fbad78 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEventFilters.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.timeline + +data class TimelineEventFilters( + /** + * A flag to filter edit events + */ + val filterEdits: Boolean = false, + /** + * A flag to filter redacted events + */ + val filterRedacted: Boolean = false, + /** + * A flag to filter useless events, such as membership events without any change + */ + val filterUseless: Boolean = false, + /** + * A flag to filter by types. It should be used with [allowedTypes] field + */ + val filterTypes: Boolean = false, + /** + * If [filterTypes] is true, the list of types allowed by the list. + */ + val allowedTypes: List<String> = emptyList() +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt index 4f915cb907037b11e82ff3da9926dba6715cf414..ab98208eedeed7123b023b03b9e65a2c42d9181b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt @@ -26,25 +26,9 @@ data class TimelineSettings( */ val initialSize: Int, /** - * A flag to filter edit events + * Filters for timeline event */ - val filterEdits: Boolean = false, - /** - * A flag to filter redacted events - */ - val filterRedacted: Boolean = false, - /** - * A flag to filter useless events, such as membership events without any change - */ - val filterUseless: Boolean = false, - /** - * A flag to filter by types. It should be used with [allowedTypes] field - */ - val filterTypes: Boolean = false, - /** - * If [filterTypes] is true, the list of types allowed by the list. - */ - val allowedTypes: List<String> = emptyList(), + val filters: TimelineEventFilters = TimelineEventFilters(), /** * If true, will build read receipts for each event. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmInstanceWrapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmInstanceWrapper.kt new file mode 100644 index 0000000000000000000000000000000000000000..e2ddbcbca8b4f7812fcc65aef3462eda69e28ae0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmInstanceWrapper.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database + +import io.realm.Realm +import java.io.Closeable + +internal class RealmInstanceWrapper(private val realm: Realm, private val closeRealmOnClose: Boolean) : Closeable { + + override fun close() { + if (closeRealmOnClose) { + realm.close() + } + } + + fun <R> withRealm(block: (Realm) -> R): R { + return use { + block(it.realm) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt new file mode 100644 index 0000000000000000000000000000000000000000..a7f934ffc0cb7685c1962ff429e7fa30041693f6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database + +import android.os.Looper +import androidx.annotation.MainThread +import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.SessionLifecycleObserver +import org.matrix.android.sdk.internal.session.SessionScope +import javax.inject.Inject +import kotlin.concurrent.getOrSet + +/** + * This class keeps an instance of realm open in the main thread so you can grab it whenever you want to get a realm + * instance. This does check each time if you are on the main thread or not and returns the appropriate realm instance. + */ +@SessionScope +internal class RealmSessionProvider @Inject constructor(@SessionDatabase private val monarchy: Monarchy) + : SessionLifecycleObserver { + + private val realmThreadLocal = ThreadLocal<Realm>() + + /** + * Allow you to execute a block with an opened realm. It automatically closes it if necessary (ie. when not in main thread) + */ + fun <R> withRealm(block: (Realm) -> R): R { + return getRealmWrapper().withRealm(block) + } + + @MainThread + override fun onStart() { + realmThreadLocal.getOrSet { + Realm.getInstance(monarchy.realmConfiguration) + } + } + + @MainThread + override fun onStop() { + realmThreadLocal.get()?.close() + realmThreadLocal.remove() + } + + private fun getRealmWrapper(): RealmInstanceWrapper { + val isOnMainThread = isOnMainThread() + val realm = if (isOnMainThread) { + realmThreadLocal.getOrSet { + Realm.getInstance(monarchy.realmConfiguration) + } + } else { + Realm.getInstance(monarchy.realmConfiguration) + } + return RealmInstanceWrapper(realm, closeRealmOnClose = !isOnMainThread) + } + + private fun isOnMainThread() = Looper.myLooper() == Looper.getMainLooper() +} 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 ad05406aa0bd9dfa03cbab06e405128206dd12bd..26ce38e322b8d2571be26b4127991d21951a89ef 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 @@ -28,7 +28,7 @@ import javax.inject.Inject class RealmSessionStoreMigration @Inject constructor() : RealmMigration { companion object { - const val SESSION_STORE_SCHEMA_VERSION = 4L + const val SESSION_STORE_SCHEMA_VERSION = 5L } override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { @@ -38,6 +38,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { if (oldVersion <= 1) migrateTo2(realm) if (oldVersion <= 2) migrateTo3(realm) if (oldVersion <= 3) migrateTo4(realm) + if (oldVersion <= 4) migrateTo5(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -54,16 +55,16 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { private fun migrateTo2(realm: DynamicRealm) { Timber.d("Step 1 -> 2") realm.schema.get("HomeServerCapabilitiesEntity") - ?.addField(HomeServerCapabilitiesEntityFields.ADMIN_E2_E_BY_DEFAULT, Boolean::class.java) + ?.addField("adminE2EByDefault", Boolean::class.java) ?.transform { obj -> - obj.setBoolean(HomeServerCapabilitiesEntityFields.ADMIN_E2_E_BY_DEFAULT, true) + obj.setBoolean("adminE2EByDefault", true) } } private fun migrateTo3(realm: DynamicRealm) { Timber.d("Step 2 -> 3") realm.schema.get("HomeServerCapabilitiesEntity") - ?.addField(HomeServerCapabilitiesEntityFields.PREFERRED_JITSI_DOMAIN, String::class.java) + ?.addField("preferredJitsiDomain", String::class.java) ?.transform { obj -> // Schedule a refresh of the capabilities obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0) @@ -82,4 +83,11 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { .setRequired(PendingThreePidEntityFields.SID, true) .addField(PendingThreePidEntityFields.SUBMIT_URL, String::class.java) } + + private fun migrateTo5(realm: DynamicRealm) { + Timber.d("Step 4 -> 5") + realm.schema.get("HomeServerCapabilitiesEntity") + ?.removeField("adminE2EByDefault") + ?.removeField("preferredJitsiDomain") + } } 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 e5de271d9328b7b9c87c39fdf5ad986445a451fa..4eb9b4b47f2ee2d6537dfde5a9190bfd084de967 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 @@ -30,9 +30,7 @@ internal object HomeServerCapabilitiesMapper { canChangePassword = entity.canChangePassword, maxUploadFileSize = entity.maxUploadFileSize, lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported, - defaultIdentityServerUrl = entity.defaultIdentityServerUrl, - adminE2EByDefault = entity.adminE2EByDefault, - preferredJitsiDomain = entity.preferredJitsiDomain + defaultIdentityServerUrl = entity.defaultIdentityServerUrl ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ReadReceiptsSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ReadReceiptsSummaryMapper.kt index 188ca4937c47d758f8e8f709619bfd966d561bce..6b9c0e7a45277fe28d4d60366580d1cc4e6e521e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ReadReceiptsSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/ReadReceiptsSummaryMapper.kt @@ -18,26 +18,24 @@ package org.matrix.android.sdk.internal.database.mapper import org.matrix.android.sdk.api.session.room.model.ReadReceipt +import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity import org.matrix.android.sdk.internal.database.model.UserEntity import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.di.SessionDatabase -import io.realm.Realm -import io.realm.RealmConfiguration import javax.inject.Inject -internal class ReadReceiptsSummaryMapper @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration) { +internal class ReadReceiptsSummaryMapper @Inject constructor(private val realmSessionProvider: RealmSessionProvider) { fun map(readReceiptsSummaryEntity: ReadReceiptsSummaryEntity?): List<ReadReceipt> { if (readReceiptsSummaryEntity == null) { return emptyList() } - return Realm.getInstance(realmConfiguration).use { realm -> + return realmSessionProvider.withRealm { realm -> val readReceipts = readReceiptsSummaryEntity.readReceipts readReceipts .mapNotNull { val user = UserEntity.where(realm, it.userId).findFirst() - ?: return@mapNotNull null + ?: return@mapNotNull null ReadReceipt(user.asDomain(), it.originServerTs.toLong()) } } 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 7e3af69436e2355d79f200dd5bf4d16c76c9954b..a905dc9535f32a44b9ff861f2130698cc7a4e749 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 @@ -17,17 +17,15 @@ package org.matrix.android.sdk.internal.database.model -import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import io.realm.RealmObject +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities internal open class HomeServerCapabilitiesEntity( var canChangePassword: Boolean = true, var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN, var lastVersionIdentityServerSupported: Boolean = false, var defaultIdentityServerUrl: String? = null, - var adminE2EByDefault: Boolean = true, - var lastUpdatedTimestamp: Long = 0L, - var preferredJitsiDomain: String? = null + var lastUpdatedTimestamp: Long = 0L ) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RawCacheEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RawCacheEntity.kt new file mode 100644 index 0000000000000000000000000000000000000000..3c0a280476d78042352a959fd0b98eefe1504c60 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RawCacheEntity.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2019 New Vector Ltd + * Copyright 2020 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.model + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey + +internal open class RawCacheEntity( + @PrimaryKey + var url: String = "", + var data: String = "", + var lastUpdatedTimestamp: Long = 0L +) : RealmObject() { + + companion object +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RawCacheQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RawCacheQueries.kt new file mode 100644 index 0000000000000000000000000000000000000000..93753ff24b525fdcfcb8afc177d93fa47a9a4cb8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RawCacheQueries.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2019 New Vector Ltd + * Copyright 2020 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.query + +import io.realm.Realm +import io.realm.kotlin.createObject +import io.realm.kotlin.where +import org.matrix.android.sdk.internal.database.model.RawCacheEntity +import org.matrix.android.sdk.internal.database.model.RawCacheEntityFields + +/** + * Get the current RawCacheEntity, return null if it does not exist + */ +internal fun RawCacheEntity.Companion.get(realm: Realm, url: String): RawCacheEntity? { + return realm.where<RawCacheEntity>() + .equalTo(RawCacheEntityFields.URL, url) + .findFirst() +} + +/** + * Get the current RawCacheEntity, create one if it does not exist + */ +internal fun RawCacheEntity.Companion.getOrCreate(realm: Realm, url: String): RawCacheEntity { + return get(realm, url) ?: realm.createObject(url) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt index 83075a192ca925cb40fefc6fa6231a87e889f9b2..d49b64c4328daec7e5fdc601618eb1ab0fce274e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/TimelineEventEntityQueries.kt @@ -17,17 +17,18 @@ package org.matrix.android.sdk.internal.database.query -import org.matrix.android.sdk.api.session.room.send.SendState -import org.matrix.android.sdk.internal.database.model.ChunkEntity -import org.matrix.android.sdk.internal.database.model.RoomEntity -import org.matrix.android.sdk.internal.database.model.TimelineEventEntity -import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import io.realm.Realm import io.realm.RealmList import io.realm.RealmQuery import io.realm.RealmResults import io.realm.Sort import io.realm.kotlin.where +import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters +import org.matrix.android.sdk.internal.database.model.ChunkEntity +import org.matrix.android.sdk.internal.database.model.RoomEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery<TimelineEventEntity> { return realm.where<TimelineEventEntity>() @@ -56,16 +57,10 @@ internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm: internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm, roomId: String, includesSending: Boolean, - filterContentRelation: Boolean = false, - filterTypes: List<String> = emptyList()): TimelineEventEntity? { + filters: TimelineEventFilters = TimelineEventFilters()): TimelineEventEntity? { val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null - val sendingTimelineEvents = roomEntity.sendingTimelineEvents.where().filterTypes(filterTypes) - val liveEvents = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)?.timelineEvents?.where()?.filterTypes(filterTypes) - if (filterContentRelation) { - liveEvents - ?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT) - ?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE) - } + val sendingTimelineEvents = roomEntity.sendingTimelineEvents.where().filterEvents(filters) + val liveEvents = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)?.timelineEvents?.where()?.filterEvents(filters) val query = if (includesSending && sendingTimelineEvents.findAll().isNotEmpty()) { sendingTimelineEvents } else { @@ -76,6 +71,24 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm, ?.findFirst() } +internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEventFilters): RealmQuery<TimelineEventEntity> { + if (filters.filterTypes) { + `in`(TimelineEventEntityFields.ROOT.TYPE, filters.allowedTypes.toTypedArray()) + } + if (filters.filterUseless) { + not() + .equalTo(TimelineEventEntityFields.ROOT.IS_USELESS, true) + } + if (filters.filterEdits) { + not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT) + not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE) + } + if (filters.filterRedacted) { + not().like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, TimelineEventFilter.Unsigned.REDACTED) + } + return this +} + internal fun RealmQuery<TimelineEventEntity>.filterTypes(filterTypes: List<String>): RealmQuery<TimelineEventEntity> { return if (filterTypes.isEmpty()) { this diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/DbQualifiers.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/DbQualifiers.kt index 9442dc48653c2cb80a380601f1fd5d6df7c95b31..2380ea68b8d75cb08088d85e279b2e4a8b740528 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/DbQualifiers.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/DbQualifiers.kt @@ -23,6 +23,10 @@ import javax.inject.Qualifier @Retention(AnnotationRetention.RUNTIME) internal annotation class AuthDatabase +@Qualifier +@Retention(AnnotationRetention.RUNTIME) +internal annotation class GlobalDatabase + @Qualifier @Retention(AnnotationRetention.RUNTIME) internal annotation class SessionDatabase 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 816a674d8184f75b11acf06feaae3adb5db6007c..e51d8f3ad32fec699211c2d550a8903fdf1802c9 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 @@ -22,22 +22,30 @@ import android.content.res.Resources import com.squareup.moshi.Moshi import dagger.BindsInstance import dagger.Component +import okhttp3.OkHttpClient import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.auth.AuthenticationService +import org.matrix.android.sdk.api.raw.RawService 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.raw.RawModule import org.matrix.android.sdk.internal.session.MockHttpInterceptor import org.matrix.android.sdk.internal.session.TestInterceptor import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers -import okhttp3.OkHttpClient import org.matrix.olm.OlmManager import java.io.File -@Component(modules = [MatrixModule::class, NetworkModule::class, AuthModule::class, NoOpTestModule::class]) +@Component(modules = [ + MatrixModule::class, + NetworkModule::class, + AuthModule::class, + RawModule::class, + NoOpTestModule::class +]) @MatrixScope internal interface MatrixComponent { @@ -53,6 +61,8 @@ internal interface MatrixComponent { fun authenticationService(): AuthenticationService + fun rawService(): RawService + fun context(): Context fun matrixConfiguration(): MatrixConfiguration diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultCleanRawCacheTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultCleanRawCacheTask.kt new file mode 100644 index 0000000000000000000000000000000000000000..7ab664524420396973c6f051c57e6bb65b6a888c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultCleanRawCacheTask.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2019 New Vector Ltd + * Copyright 2020 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.raw + +import com.zhuinden.monarchy.Monarchy +import io.realm.kotlin.where +import org.matrix.android.sdk.internal.database.model.RawCacheEntity +import org.matrix.android.sdk.internal.di.GlobalDatabase +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import javax.inject.Inject + +internal interface CleanRawCacheTask : Task<Unit, Unit> + +internal class DefaultCleanRawCacheTask @Inject constructor( + @GlobalDatabase private val monarchy: Monarchy +) : CleanRawCacheTask { + + override suspend fun execute(params: Unit) { + monarchy.awaitTransaction { realm -> + realm.where<RawCacheEntity>() + .findAll() + .deleteAllFromRealm() + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultGetUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultGetUrlTask.kt new file mode 100644 index 0000000000000000000000000000000000000000..1733abccd4a5c94377de53b3fbe84ac1e1f6bfd0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultGetUrlTask.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2019 New Vector Ltd + * Copyright 2020 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.raw + +import com.zhuinden.monarchy.Monarchy +import okhttp3.ResponseBody +import org.matrix.android.sdk.api.raw.RawCacheStrategy +import org.matrix.android.sdk.internal.database.model.RawCacheEntity +import org.matrix.android.sdk.internal.database.query.get +import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.di.GlobalDatabase +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import java.util.Date +import javax.inject.Inject + +internal interface GetUrlTask : Task<GetUrlTask.Params, String> { + data class Params( + val url: String, + val rawCacheStrategy: RawCacheStrategy + ) +} + +internal class DefaultGetUrlTask @Inject constructor( + private val rawAPI: RawAPI, + @GlobalDatabase private val monarchy: Monarchy +) : GetUrlTask { + + override suspend fun execute(params: GetUrlTask.Params): String { + return when (params.rawCacheStrategy) { + RawCacheStrategy.NoCache -> doRequest(params.url) + is RawCacheStrategy.TtlCache -> doRequestWithCache( + params.url, + params.rawCacheStrategy.validityDurationInMillis, + params.rawCacheStrategy.strict + ) + RawCacheStrategy.InfiniteCache -> doRequestWithCache( + params.url, + Long.MAX_VALUE, + true + ) + } + } + + private suspend fun doRequest(url: String): String { + return executeRequest<ResponseBody>(null) { + apiCall = rawAPI.getUrl(url) + } + .string() + } + + private suspend fun doRequestWithCache(url: String, validityDurationInMillis: Long, strict: Boolean): String { + // Get data from cache + var dataFromCache: String? = null + var isCacheValid = false + monarchy.doWithRealm { realm -> + val entity = RawCacheEntity.get(realm, url) + dataFromCache = entity?.data + isCacheValid = entity != null && Date().time < entity.lastUpdatedTimestamp + validityDurationInMillis + } + + if (dataFromCache != null && isCacheValid) { + return dataFromCache as String + } + + // No cache or outdated cache + val data = try { + doRequest(url) + } catch (throwable: Throwable) { + // In case of error, we can return value from cache even if outdated + return dataFromCache + ?.takeIf { !strict } + ?: throw throwable + } + + // Store cache + monarchy.awaitTransaction { realm -> + val rawCacheEntity = RawCacheEntity.getOrCreate(realm, url) + rawCacheEntity.data = data + rawCacheEntity.lastUpdatedTimestamp = Date().time + } + + return data + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt new file mode 100644 index 0000000000000000000000000000000000000000..792a0b3aa757eff486f2178a68996537d6ad56b1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.raw + +import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.raw.RawCacheStrategy +import org.matrix.android.sdk.api.raw.RawService +import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.internal.task.TaskExecutor +import org.matrix.android.sdk.internal.task.configureWith +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +internal class DefaultRawService @Inject constructor( + private val taskExecutor: TaskExecutor, + private val getUrlTask: GetUrlTask, + private val cleanRawCacheTask: CleanRawCacheTask +) : RawService { + override fun getUrl(url: String, + rawCacheStrategy: RawCacheStrategy, + matrixCallback: MatrixCallback<String>): Cancelable { + return getUrlTask + .configureWith(GetUrlTask.Params(url, rawCacheStrategy)) { + callback = matrixCallback + } + .executeBy(taskExecutor) + } + + override fun getWellknown(userId: String, + matrixCallback: MatrixCallback<String>): Cancelable { + val homeServerDomain = userId.substringAfter(":") + return getUrl( + "https://$homeServerDomain/.well-known/matrix/client", + RawCacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false), + matrixCallback + ) + } + + override fun clearCache(matrixCallback: MatrixCallback<Unit>): Cancelable { + return cleanRawCacheTask + .configureWith(Unit) { + callback = matrixCallback + } + .executeBy(taskExecutor) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..4df5edae884af5a48415912f8c7a702618ce9084 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmModule.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2019 New Vector Ltd + * Copyright 2020 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.raw + +import io.realm.annotations.RealmModule +import org.matrix.android.sdk.internal.database.model.RawCacheEntity + +/** + * Realm module for global classes + */ +@RealmModule(library = true, + classes = [ + RawCacheEntity::class + ]) +internal class GlobalRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawAPI.kt new file mode 100644 index 0000000000000000000000000000000000000000..f7aa738e90dda8c59b36a909b4398c4eda5257b8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawAPI.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2020 New Vector Ltd + * Copyright 2020 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.raw + +import okhttp3.ResponseBody +import retrofit2.Call +import retrofit2.http.GET +import retrofit2.http.Url + +internal interface RawAPI { + @GET + fun getUrl(@Url url: String): Call<ResponseBody> +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..95de057f049c6fa5640680783d8d871f791d24ce --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2020 New Vector Ltd + * Copyright 2020 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.raw + +import com.zhuinden.monarchy.Monarchy +import dagger.Binds +import dagger.Lazy +import dagger.Module +import dagger.Provides +import io.realm.RealmConfiguration +import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.raw.RawService +import org.matrix.android.sdk.internal.database.RealmKeysUtils +import org.matrix.android.sdk.internal.di.GlobalDatabase +import org.matrix.android.sdk.internal.di.MatrixScope +import org.matrix.android.sdk.internal.di.Unauthenticated +import org.matrix.android.sdk.internal.network.RetrofitFactory + +@Module +internal abstract class RawModule { + + @Module + companion object { + private const val DB_ALIAS = "matrix-sdk-global" + + @JvmStatic + @Provides + @GlobalDatabase + fun providesMonarchy(@GlobalDatabase realmConfiguration: RealmConfiguration): Monarchy { + return Monarchy.Builder() + .setRealmConfiguration(realmConfiguration) + .build() + } + + @JvmStatic + @Provides + @GlobalDatabase + @MatrixScope + fun providesRealmConfiguration(realmKeysUtils: RealmKeysUtils): RealmConfiguration { + return RealmConfiguration.Builder() + .apply { + realmKeysUtils.configureEncryption(this, DB_ALIAS) + } + .name("matrix-sdk-global.realm") + .modules(GlobalRealmModule()) + .build() + } + + @Provides + @JvmStatic + fun providesRawAPI(@Unauthenticated okHttpClient: Lazy<OkHttpClient>, + retrofitFactory: RetrofitFactory): RawAPI { + return retrofitFactory.create(okHttpClient, "https://example.org").create(RawAPI::class.java) + } + } + + @Binds + abstract fun bindRawService(service: DefaultRawService): RawService + + @Binds + abstract fun bindGetUrlTask(task: DefaultGetUrlTask): GetUrlTask + + @Binds + abstract fun bindCleanRawCacheTask(task: DefaultCleanRawCacheTask): CleanRawCacheTask +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index aa4114c8c2899221d81a71d374892249481a6cf6..a163cd48094fa3c894f393be91d832e2fb35c376 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -144,11 +144,13 @@ internal class DefaultFileService @Inject constructor( if (elementToDecrypt != null) { Timber.v("## FileService: decrypt file") - val decryptSuccess = MXEncryptedAttachments.decryptAttachment( - source.inputStream(), - elementToDecrypt, - destFile.outputStream().buffered() - ) + val decryptSuccess = destFile.outputStream().buffered().use { + MXEncryptedAttachments.decryptAttachment( + source.inputStream(), + elementToDecrypt, + it + ) + } response.close() if (!decryptSuccess) { return@flatMap Try.Failure(IllegalStateException("Decryption error")) 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 f8ba6259478c0a708adea3ad23dc107cd8d3a2fa..004c5afe8f87ba83bc4879f1738b2c673e4aef60 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 @@ -166,8 +166,8 @@ internal class DefaultSession @Inject constructor( SyncWorker.requireBackgroundSync(workManagerProvider, sessionId) } - override fun startAutomaticBackgroundSync(repeatDelay: Long) { - SyncWorker.automaticallyBackgroundSync(workManagerProvider, sessionId, 0, repeatDelay) + override fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long) { + SyncWorker.automaticallyBackgroundSync(workManagerProvider, sessionId, timeOutInSeconds, repeatDelayInSeconds) } override fun stopAnyBackgroundSync() { 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 d404cecc51f214089d1434c98739266e5421a513..5397b8d9bd15ac2cfd06cf39369e6b39564b3674 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 @@ -47,6 +47,7 @@ import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorage import org.matrix.android.sdk.internal.crypto.verification.VerificationMessageProcessor import org.matrix.android.sdk.internal.database.DatabaseCleaner import org.matrix.android.sdk.internal.database.EventInsertLiveObserver +import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory import org.matrix.android.sdk.internal.di.Authenticated import org.matrix.android.sdk.internal.di.DeviceId @@ -325,23 +326,27 @@ internal abstract class SessionModule { @Binds @IntoSet - abstract fun bindIntegrationManager(observer: IntegrationManager): SessionLifecycleObserver + abstract fun bindIntegrationManager(manager: IntegrationManager): SessionLifecycleObserver @Binds @IntoSet - abstract fun bindWidgetUrlFormatter(observer: DefaultWidgetURLFormatter): SessionLifecycleObserver + abstract fun bindWidgetUrlFormatter(formatter: DefaultWidgetURLFormatter): SessionLifecycleObserver @Binds @IntoSet - abstract fun bindShieldTrustUpdated(observer: ShieldTrustUpdater): SessionLifecycleObserver + abstract fun bindShieldTrustUpdated(updater: ShieldTrustUpdater): SessionLifecycleObserver @Binds @IntoSet - abstract fun bindIdentityService(observer: DefaultIdentityService): SessionLifecycleObserver + abstract fun bindIdentityService(service: DefaultIdentityService): SessionLifecycleObserver @Binds @IntoSet - abstract fun bindDatabaseCleaner(observer: DatabaseCleaner): SessionLifecycleObserver + abstract fun bindDatabaseCleaner(cleaner: DatabaseCleaner): SessionLifecycleObserver + + @Binds + @IntoSet + abstract fun bindRealmSessionProvider(provider: RealmSessionProvider): SessionLifecycleObserver @Binds abstract fun bindInitialSyncProgressService(service: DefaultInitialSyncProgressService): InitialSyncProgressService diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt index 13ce84cf902577e3fabd08fb40268ab6cf82eddc..7e1ad600e32c2c3f3326af0fe5baa48110fce198 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.homeserver import com.zhuinden.monarchy.Monarchy +import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities @@ -32,7 +33,6 @@ import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationMan import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.wellknown.GetWellknownTask -import org.greenrobot.eventbus.EventBus import timber.log.Timber import java.util.Date import javax.inject.Inject @@ -109,16 +109,12 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) { homeServerCapabilitiesEntity.defaultIdentityServerUrl = getWellknownResult.identityServerUrl - homeServerCapabilitiesEntity.adminE2EByDefault = getWellknownResult.wellKnown.e2eAdminSetting?.e2eDefault ?: true - homeServerCapabilitiesEntity.preferredJitsiDomain = getWellknownResult.wellKnown.jitsiServer?.preferredDomain // We are also checking for integration manager configurations val config = configExtractor.extract(getWellknownResult.wellKnown) if (config != null) { Timber.v("Extracted integration config : $config") realm.insertOrUpdate(config) } - } else { - homeServerCapabilitiesEntity.adminE2EByDefault = true } homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomGetter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomGetter.kt index 38dcad231167b82ab7c92317095059432461a3cf..985cf80e9727870221c257d4c6324e3b4120bb57 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomGetter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomGetter.kt @@ -17,17 +17,16 @@ package org.matrix.android.sdk.internal.session.room -import com.zhuinden.monarchy.Monarchy +import io.realm.Realm import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper -import io.realm.Realm import javax.inject.Inject internal interface RoomGetter { @@ -38,18 +37,18 @@ internal interface RoomGetter { @SessionScope internal class DefaultRoomGetter @Inject constructor( - @SessionDatabase private val monarchy: Monarchy, + private val realmSessionProvider: RealmSessionProvider, private val roomFactory: RoomFactory ) : RoomGetter { override fun getRoom(roomId: String): Room? { - return Realm.getInstance(monarchy.realmConfiguration).use { realm -> + return realmSessionProvider.withRealm { realm -> createRoom(realm, roomId) } } override fun getDirectRoomWith(otherUserId: String): Room? { - return Realm.getInstance(monarchy.realmConfiguration).use { realm -> + return realmSessionProvider.withRealm { realm -> RoomSummaryEntity.where(realm) .equalTo(RoomSummaryEntityFields.IS_DIRECT, true) .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt index b3188883c052385ef6f250a30bf44657a91c0ca4..00c624a20dffb3edce15ba051ef2ea2143965188 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.send import com.zhuinden.monarchy.Monarchy +import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType @@ -27,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult +import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.helper.nextId import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper @@ -43,12 +45,11 @@ import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimeline import org.matrix.android.sdk.internal.util.awaitTransaction -import io.realm.Realm -import org.greenrobot.eventbus.EventBus import timber.log.Timber import javax.inject.Inject internal class LocalEchoRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy, + private val realmSessionProvider: RealmSessionProvider, private val roomSummaryUpdater: RoomSummaryUpdater, private val eventBus: EventBus, private val timelineEventMapper: TimelineEventMapper) { @@ -59,7 +60,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private if (event.eventId == null) { throw IllegalStateException("You should have set an eventId for your event") } - val timelineEventEntity = Realm.getInstance(monarchy.realmConfiguration).use { realm -> + val timelineEventEntity = realmSessionProvider.withRealm { realm -> val eventEntity = event.toEntity(roomId, SendState.UNSENT, System.currentTimeMillis()) val roomMemberHelper = RoomMemberHelper(realm, roomId) val myUser = roomMemberHelper.getLastRoomMember(senderId) @@ -150,7 +151,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private } fun getAllEventsWithStates(roomId: String, states : List<SendState>): List<TimelineEvent> { - return Realm.getInstance(monarchy.realmConfiguration).use { realm -> + return realmSessionProvider.withRealm { realm -> TimelineEventEntity .findAllInRoomWithSendStates(realm, roomId, states) .sortedByDescending { it.displayIndex } 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 e8dc2ddf4027486cde64cd2eb5ee05dc5b9dfa0e..65d30868d847a7580252202185d8f3267315b8e1 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 @@ -20,24 +20,26 @@ package org.matrix.android.sdk.internal.session.room.state import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations 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.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.query.process -import io.realm.Realm -import io.realm.RealmQuery -import io.realm.kotlin.where import javax.inject.Inject -internal class StateEventDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy) { +internal class StateEventDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy, + private val realmSessionProvider: RealmSessionProvider) { fun getStateEvent(roomId: String, eventType: String, stateKey: QueryStringValue): Event? { - return Realm.getInstance(monarchy.realmConfiguration).use { realm -> + return realmSessionProvider.withRealm { realm -> buildStateEventQuery(realm, roomId, setOf(eventType), stateKey).findFirst()?.root?.asDomain() } } @@ -53,7 +55,7 @@ internal class StateEventDataSource @Inject constructor(@SessionDatabase private } fun getStateEvents(roomId: String, eventTypes: Set<String>, stateKey: QueryStringValue): List<Event> { - return Realm.getInstance(monarchy.realmConfiguration).use { realm -> + return realmSessionProvider.withRealm { realm -> buildStateEventQuery(realm, roomId, eventTypes, stateKey) .findAll() .mapNotNull { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryEventsHelper.kt new file mode 100644 index 0000000000000000000000000000000000000000..dd71bff436be02e76e6434f7375442ccb84424c5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryEventsHelper.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.summary + +import io.realm.Realm +import org.matrix.android.sdk.api.session.room.summary.RoomSummaryConstants +import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters +import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.query.latestEvent + +internal object RoomSummaryEventsHelper { + + private val previewFilters = TimelineEventFilters( + filterTypes = true, + allowedTypes = RoomSummaryConstants.PREVIEWABLE_TYPES, + filterUseless = true, + filterRedacted = false, + filterEdits = true + ) + + fun getLatestPreviewableEvent(realm: Realm, roomId: String): TimelineEventEntity? { + return TimelineEventEntity.latestEvent( + realm = realm, + roomId = roomId, + includesSending = true, + filters = previewFilters + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 99671c232a1ca4092ed89594647fbe4b19a38b86..0aac30654a5f8722dea26e6cbe563b528ba03a77 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.session.room.summary import dagger.Lazy +import io.realm.Realm +import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel @@ -40,7 +42,6 @@ import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendState import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.database.query.isEventRead -import org.matrix.android.sdk.internal.database.query.latestEvent import org.matrix.android.sdk.internal.database.query.whereType import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver @@ -49,8 +50,6 @@ import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.room.timeline.TimelineEventDecryptor import org.matrix.android.sdk.internal.session.sync.model.RoomSyncSummary import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications -import io.realm.Realm -import org.greenrobot.eventbus.EventBus import timber.log.Timber import javax.inject.Inject @@ -61,28 +60,6 @@ internal class RoomSummaryUpdater @Inject constructor( private val timelineEventDecryptor: Lazy<TimelineEventDecryptor>, private val eventBus: EventBus) { - companion object { - // TODO: maybe allow user of SDK to give that list - val PREVIEWABLE_TYPES = listOf( - // TODO filter message type (KEY_VERIFICATION_READY, etc.) - EventType.MESSAGE, - EventType.STATE_ROOM_NAME, - EventType.STATE_ROOM_TOPIC, - EventType.STATE_ROOM_AVATAR, - EventType.STATE_ROOM_MEMBER, - EventType.STATE_ROOM_HISTORY_VISIBILITY, - EventType.CALL_INVITE, - EventType.CALL_HANGUP, - EventType.CALL_ANSWER, - EventType.ENCRYPTED, - EventType.STATE_ROOM_ENCRYPTION, - EventType.STATE_ROOM_THIRD_PARTY_INVITE, - EventType.STICKER, - EventType.REACTION, - EventType.STATE_ROOM_CREATE - ) - } - fun update(realm: Realm, roomId: String, membership: Membership? = null, @@ -110,9 +87,6 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.membership = membership } - val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, - filterTypes = PREVIEWABLE_TYPES, filterContentRelation = true) - val lastNameEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_NAME, stateKey = "")?.root val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root @@ -123,6 +97,8 @@ internal class RoomSummaryUpdater @Inject constructor( .contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"") .findFirst() + val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) + roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 // avoid this call if we are sure there are unread events || !isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId) @@ -178,8 +154,7 @@ internal class RoomSummaryUpdater @Inject constructor( fun updateSendingInformation(realm: Realm, roomId: String) { val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) roomSummaryEntity.updateHasFailedSending() - roomSummaryEntity.latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true, - filterTypes = PREVIEWABLE_TYPES, filterContentRelation = true) + roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) } fun updateShieldTrust(realm: Realm, 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 421cd1b0631924dca08fa8741e89a5f55e9828cc..2dead1d9cc7142fb2a2dec23ac85c261be607f4a 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 @@ -39,13 +39,14 @@ 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 import org.matrix.android.sdk.api.util.CancelableBag +import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntityFields import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields -import org.matrix.android.sdk.internal.database.query.TimelineEventFilter +import org.matrix.android.sdk.internal.database.query.filterEvents import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId @@ -76,7 +77,8 @@ internal class DefaultTimeline( private val settings: TimelineSettings, private val hiddenReadReceipts: TimelineHiddenReadReceipts, private val eventBus: EventBus, - private val eventDecryptor: TimelineEventDecryptor + private val eventDecryptor: TimelineEventDecryptor, + private val realmSessionProvider: RealmSessionProvider ) : Timeline, TimelineHiddenReadReceipts.Delegate { data class OnNewTimelineEvents(val roomId: String, val eventIds: List<String>) @@ -136,13 +138,13 @@ internal class DefaultTimeline( } override fun pendingEventCount(): Int { - return Realm.getInstance(realmConfiguration).use { + return realmSessionProvider.withRealm { RoomEntity.where(it, roomId).findFirst()?.sendingTimelineEvents?.count() ?: 0 } } override fun failedToDeliverEventCount(): Int { - return Realm.getInstance(realmConfiguration).use { + return realmSessionProvider.withRealm { TimelineEventEntity.findAllInRoomWithSendStates(it, roomId, SendState.HAS_FAILED_STATES).count() } } @@ -182,7 +184,7 @@ internal class DefaultTimeline( } private fun TimelineSettings.shouldHandleHiddenReadReceipts(): Boolean { - return buildReadReceipts && (filterEdits || filterTypes) + return buildReadReceipts && (filters.filterEdits || filters.filterTypes) } override fun dispose() { @@ -239,7 +241,7 @@ internal class DefaultTimeline( return eventId } // Otherwise, we should check if the event is in the db, but is hidden because of filters - return Realm.getInstance(realmConfiguration).use { localRealm -> + return realmSessionProvider.withRealm { localRealm -> val nonFilteredEvents = buildEventQuery(localRealm) .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) .findAll() @@ -757,29 +759,15 @@ internal class DefaultTimeline( } private fun RealmQuery<TimelineEventEntity>.filterEventsWithSettings(): RealmQuery<TimelineEventEntity> { - if (settings.filterTypes) { - `in`(TimelineEventEntityFields.ROOT.TYPE, settings.allowedTypes.toTypedArray()) - } - if (settings.filterUseless) { - not() - .equalTo(TimelineEventEntityFields.ROOT.IS_USELESS, true) - } - if (settings.filterEdits) { - not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT) - not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE) - } - if (settings.filterRedacted) { - not().like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, TimelineEventFilter.Unsigned.REDACTED) - } - return this + return filterEvents(settings.filters) } private fun List<TimelineEvent>.filterEventsWithSettings(): List<TimelineEvent> { return filter { - val filterType = !settings.filterTypes || settings.allowedTypes.contains(it.root.type) + val filterType = !settings.filters.filterTypes || settings.filters.allowedTypes.contains(it.root.type) if (!filterType) return@filter false - val filterEdits = if (settings.filterEdits && it.root.type == EventType.MESSAGE) { + val filterEdits = if (settings.filters.filterEdits && it.root.type == EventType.MESSAGE) { val messageContent = it.root.content.toModel<MessageContent>() messageContent?.relatesTo?.type != RelationType.REPLACE && messageContent?.relatesTo?.type != RelationType.RESPONSE } else { @@ -787,7 +775,7 @@ internal class DefaultTimeline( } if (!filterEdits) return@filter false - val filterRedacted = !settings.filterRedacted || it.root.isRedacted() + val filterRedacted = !settings.filters.filterRedacted || it.root.isRedacted() filterRedacted } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index db675f69f5bd64253eeccece8ee17f82f749cf46..c60a9444092ef42f9dfefd01acb111b7f0be630d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -22,6 +22,9 @@ import androidx.lifecycle.Transformations import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import com.zhuinden.monarchy.Monarchy +import io.realm.Sort +import io.realm.kotlin.where +import org.greenrobot.eventbus.EventBus import org.matrix.android.sdk.api.session.events.model.isImageMessage import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.room.timeline.Timeline @@ -30,7 +33,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional -import org.matrix.android.sdk.internal.crypto.store.db.doWithRealm +import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.TimelineEventEntity @@ -38,13 +41,10 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.util.fetchCopyMap -import io.realm.Sort -import io.realm.kotlin.where -import org.greenrobot.eventbus.EventBus internal class DefaultTimelineService @AssistedInject constructor(@Assisted private val roomId: String, @SessionDatabase private val monarchy: Monarchy, + private val realmSessionProvider: RealmSessionProvider, private val eventBus: EventBus, private val taskExecutor: TaskExecutor, private val contextOfEventTask: GetContextOfEventTask, @@ -73,17 +73,17 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings), eventBus = eventBus, eventDecryptor = eventDecryptor, - fetchTokenAndPaginateTask = fetchTokenAndPaginateTask + fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, + realmSessionProvider = realmSessionProvider ) } override fun getTimeLineEvent(eventId: String): TimelineEvent? { - return monarchy - .fetchCopyMap({ - TimelineEventEntity.where(it, roomId = roomId, eventId = eventId).findFirst() - }, { entity, _ -> - timelineEventMapper.map(entity) - }) + return realmSessionProvider.withRealm { realm -> + TimelineEventEntity.where(realm, roomId = roomId, eventId = eventId).findFirst()?.let { + timelineEventMapper.map(it) + } + } } override fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>> { @@ -98,7 +98,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv override fun getAttachmentMessages(): List<TimelineEvent> { // TODO pretty bad query.. maybe we should denormalize clear type in base? - return doWithRealm(monarchy.realmConfiguration) { realm -> + return realmSessionProvider.withRealm { realm -> realm.where<TimelineEventEntity>() .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt index 426daa4b57d15efdd0463984d476284714608035..f2c520a50f4227f2fddc55ef0324f8ebcb79e647 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt @@ -18,6 +18,10 @@ package org.matrix.android.sdk.internal.session.room.timeline import android.util.SparseArray +import io.realm.OrderedRealmCollectionChangeListener +import io.realm.Realm +import io.realm.RealmQuery +import io.realm.RealmResults import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper @@ -27,10 +31,6 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.TimelineEventFilter import org.matrix.android.sdk.internal.database.query.whereInRoom -import io.realm.OrderedRealmCollectionChangeListener -import io.realm.Realm -import io.realm.RealmQuery -import io.realm.RealmResults /** * This class is responsible for handling the read receipts for hidden events (check [TimelineSettings] to see filtering). @@ -151,23 +151,24 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu private fun RealmQuery<ReadReceiptsSummaryEntity>.filterReceiptsWithSettings(): RealmQuery<ReadReceiptsSummaryEntity> { beginGroup() var needOr = false - if (settings.filterTypes) { - not().`in`("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.TYPE}", settings.allowedTypes.toTypedArray()) + if (settings.filters.filterTypes) { + val allowedTypes = settings.filters.allowedTypes.toTypedArray() + not().`in`("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.TYPE}", allowedTypes) needOr = true } - if (settings.filterUseless) { + if (settings.filters.filterUseless) { if (needOr) or() equalTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.IS_USELESS}", true) needOr = true } - if (settings.filterEdits) { + if (settings.filters.filterEdits) { if (needOr) or() like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", TimelineEventFilter.Content.EDIT) or() like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", TimelineEventFilter.Content.RESPONSE) needOr = true } - if (settings.filterRedacted) { + if (settings.filters.filterRedacted) { if (needOr) or() like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.UNSIGNED_DATA}", TimelineEventFilter.Unsigned.REDACTED) } 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 da4eebe14222742bebfbb21005c36eaf664ea3c2..1fefdf9b5077b3314e0a92c6df0857d4f9dfdcb3 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 @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.timeline import com.zhuinden.monarchy.Monarchy +import io.realm.Realm 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.RoomMemberContent @@ -32,19 +33,16 @@ import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity -import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.create import org.matrix.android.sdk.internal.database.query.find import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom import org.matrix.android.sdk.internal.database.query.getOrCreate -import org.matrix.android.sdk.internal.database.query.latestEvent import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater +import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryEventsHelper import org.matrix.android.sdk.internal.util.awaitTransaction -import io.realm.Realm import timber.log.Timber import javax.inject.Inject @@ -177,12 +175,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri currentChunk.isLastForward = true currentLastForwardChunk?.deleteOnCascade() RoomSummaryEntity.where(realm, roomId).findFirst()?.apply { - latestPreviewableEvent = TimelineEventEntity.latestEvent( - realm, - roomId, - includesSending = true, - filterTypes = RoomSummaryUpdater.PREVIEWABLE_TYPES - ) + latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) } } } else { @@ -249,13 +242,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri val shouldUpdateSummary = roomSummaryEntity.latestPreviewableEvent == null || (chunksToDelete.isNotEmpty() && currentChunk.isLastForward && direction == PaginationDirection.FORWARDS) if (shouldUpdateSummary) { - val latestPreviewableEvent = TimelineEventEntity.latestEvent( - realm, - roomId, - includesSending = true, - filterTypes = RoomSummaryUpdater.PREVIEWABLE_TYPES - ) - roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent + roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) } if (currentChunk.isValid) { RoomEntity.where(realm, roomId).findFirst()?.addOrUpdate(currentChunk) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt index 02afd53908a7516d783dbe27febb42ddd60e0e55..9924d4476445b0c75d8125b648438403b292db47 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt @@ -32,7 +32,7 @@ import javax.inject.Inject internal interface SyncTask : Task<SyncTask.Params, Unit> { - data class Params(var timeout: Long = 30_000L) + data class Params(var timeout: Long = 6_000L) } internal class DefaultSyncTask @Inject constructor( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt index 20aa4093365870acfe6a57d61e0a5402661f69ba..485eca6f748a30304f75b5a612f981fd2e24e988 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt @@ -19,7 +19,14 @@ package org.matrix.android.sdk.internal.session.sync.job import android.app.Service import android.content.Intent import android.os.IBinder +import android.os.PowerManager +import androidx.core.content.getSystemService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancelChildren +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.Matrix +import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.isTokenError import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.sync.SyncState @@ -28,10 +35,6 @@ import org.matrix.android.sdk.internal.session.sync.SyncTask import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancelChildren -import kotlinx.coroutines.launch import timber.log.Timber import java.util.concurrent.atomic.AtomicBoolean @@ -46,6 +49,11 @@ abstract class SyncService : Service() { private var sessionId: String? = null private var mIsSelfDestroyed: Boolean = false + private var syncTimeoutSeconds: Int = 6 + private var syncDelaySeconds: Int = 60 + private var periodic: Boolean = false + private var preventReschedule: Boolean = false + private var isInitialSync: Boolean = false private lateinit var session: Session private lateinit var syncTask: SyncTask @@ -59,27 +67,60 @@ abstract class SyncService : Service() { private val serviceScope = CoroutineScope(SupervisorJob()) override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Timber.i("onStartCommand $intent") - val isInit = initialize(intent) - if (isInit) { - onStart(isInitialSync) - doSyncIfNotAlreadyRunning() - } else { - // We should start and stop as we have to ensure to call Service.startForeground() - onStart(isInitialSync) - stopMe() + Timber.i("## Sync: onStartCommand [$this] $intent with action: ${intent?.action}") + + // We should start we have to ensure we fulfill contract to show notification + // for foreground service (as per design for this service) + // TODO can we check if it's really in foreground + onStart(isInitialSync) + when (intent?.action) { + ACTION_STOP -> { + Timber.i("## Sync: stop command received") + // If it was periodic we ensure that it will not reschedule itself + preventReschedule = true + // we don't want to cancel initial syncs, let it finish + if (!isInitialSync) { + stopMe() + } + } + else -> { + val isInit = initialize(intent) + if (isInit) { + periodic = intent?.getBooleanExtra(EXTRA_PERIODIC, false) ?: false + val onNetworkBack = intent?.getBooleanExtra(EXTRA_NETWORK_BACK_RESTART, false) ?: false + Timber.d("## Sync: command received, periodic: $periodic networkBack: $onNetworkBack") + if (onNetworkBack && !backgroundDetectionObserver.isInBackground) { + // the restart after network occurs while the app is in foreground + // so just stop. It will be restarted when entering background + preventReschedule = true + stopMe() + } else { + // default is syncing + doSyncIfNotAlreadyRunning() + } + } else { + Timber.d("## Sync: Failed to initialize service") + stopMe() + } + } } - // No intent just start the service, an alarm will should call with intent - return START_STICKY + + // It's ok to be not sticky because we will explicitly start it again on the next alarm? + return START_NOT_STICKY } override fun onDestroy() { - Timber.i("## onDestroy() : $this") + Timber.i("## Sync: onDestroy() [$this] periodic:$periodic preventReschedule:$preventReschedule") if (!mIsSelfDestroyed) { - Timber.w("## Destroy by the system : $this") + Timber.d("## Sync: Destroy by the system : $this") } - serviceScope.coroutineContext.cancelChildren() isRunning.set(false) + // Cancelling the context will trigger the catch close the doSync try + serviceScope.coroutineContext.cancelChildren() + if (!preventReschedule && periodic && sessionId != null && backgroundDetectionObserver.isInBackground) { + Timber.d("## Sync: Reschedule service in $syncDelaySeconds sec") + onRescheduleAsked(sessionId ?: "", false, syncTimeoutSeconds, syncDelaySeconds) + } super.onDestroy() } @@ -90,9 +131,15 @@ abstract class SyncService : Service() { private fun doSyncIfNotAlreadyRunning() { if (isRunning.get()) { - Timber.i("Received a start while was already syncing... ignore") + Timber.i("## Sync: Received a start while was already syncing... ignore") } else { isRunning.set(true) + // Acquire a lock to give enough time for the sync :/ + getSystemService<PowerManager>()?.run { + newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "riotx:fdroidSynclock").apply { + acquire((syncTimeoutSeconds * 1000L + 10_000L)) + } + } serviceScope.launch(coroutineDispatchers.io) { doSync() } @@ -100,9 +147,10 @@ abstract class SyncService : Service() { } private suspend fun doSync() { - Timber.v("Execute sync request with timeout 0") - val params = SyncTask.Params(TIME_OUT) + Timber.v("## Sync: Execute sync request with timeout $syncTimeoutSeconds seconds") + val params = SyncTask.Params(syncTimeoutSeconds * 1000L) try { + // never do that in foreground, let the syncThread work syncTask.execute(params) // Start sync if we were doing an initial sync and the syncThread is not launched yet if (isInitialSync && session.getSyncState() == SyncState.Idle) { @@ -111,28 +159,34 @@ abstract class SyncService : Service() { } stopMe() } catch (throwable: Throwable) { - Timber.e(throwable) + Timber.e(throwable, "## Sync: sync service did fail ${isRunning.get()}") if (throwable.isTokenError()) { - stopMe() - } else { - Timber.v("Should be rescheduled to avoid wasting resources") - sessionId?.also { - onRescheduleAsked(it, isInitialSync, delay = 10_000L) - } - stopMe() + // no need to retry + preventReschedule = true } + if (throwable is Failure.NetworkConnection) { + // Network is off, no need to reschedule endless alarms :/ + preventReschedule = true + // Instead start a work to restart background sync when network is back + onNetworkError(sessionId ?: "", isInitialSync, syncTimeoutSeconds, syncDelaySeconds) + } + // JobCancellation could be caught here when onDestroy cancels the coroutine context + if (isRunning.get()) stopMe() } } private fun initialize(intent: Intent?): Boolean { if (intent == null) { + Timber.d("## Sync: initialize intent is null") return false } val matrix = Matrix.getInstance(applicationContext) val safeSessionId = intent.getStringExtra(EXTRA_SESSION_ID) ?: return false + syncTimeoutSeconds = intent.getIntExtra(EXTRA_TIMEOUT_SECONDS, 6) + syncDelaySeconds = intent.getIntExtra(EXTRA_DELAY_SECONDS, 60) try { val sessionComponent = matrix.sessionManager.getSessionComponent(safeSessionId) - ?: throw IllegalStateException("You should have a session to make it work") + ?: throw IllegalStateException("## Sync: You should have a session to make it work") session = sessionComponent.session() sessionId = safeSessionId syncTask = sessionComponent.syncTask() @@ -143,14 +197,16 @@ abstract class SyncService : Service() { backgroundDetectionObserver = matrix.backgroundDetectionObserver return true } catch (exception: Exception) { - Timber.e(exception, "An exception occurred during initialisation") + Timber.e(exception, "## Sync: An exception occurred during initialisation") return false } } abstract fun onStart(isInitialSync: Boolean) - abstract fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, delay: Long) + abstract fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int) + + abstract fun onNetworkError(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int) override fun onBind(intent: Intent?): IBinder? { return null @@ -158,6 +214,11 @@ abstract class SyncService : Service() { companion object { const val EXTRA_SESSION_ID = "EXTRA_SESSION_ID" - private const val TIME_OUT = 0L + const val EXTRA_TIMEOUT_SECONDS = "EXTRA_TIMEOUT_SECONDS" + const val EXTRA_DELAY_SECONDS = "EXTRA_DELAY_SECONDS" + const val EXTRA_PERIODIC = "EXTRA_PERIODIC" + const val EXTRA_NETWORK_BACK_RESTART = "EXTRA_NETWORK_BACK_RESTART" + + const val ACTION_STOP = "ACTION_STOP" } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt index e702de3573caffc07afa359a2811798793b382b4..3e0a29ba72e802e7c9ced67765a74f940868d9c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt @@ -34,7 +34,8 @@ import timber.log.Timber import java.util.concurrent.TimeUnit import javax.inject.Inject -private const val DEFAULT_LONG_POOL_TIMEOUT = 0L +private const val DEFAULT_LONG_POOL_TIMEOUT = 6L +private const val DEFAULT_DELAY_TIMEOUT = 30_000L /** * Possible previous worker: None @@ -48,13 +49,15 @@ internal class SyncWorker(context: Context, internal data class Params( override val sessionId: String, val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT, - val automaticallyRetry: Boolean = false, + val delay: Long = DEFAULT_DELAY_TIMEOUT, + val periodic: Boolean = false, override val lastFailureMessage: String? = null ) : SessionWorkerParams @Inject lateinit var syncTask: SyncTask @Inject lateinit var taskExecutor: TaskExecutor @Inject lateinit var networkConnectivityChecker: NetworkConnectivityChecker + @Inject lateinit var workManagerProvider: WorkManagerProvider override suspend fun doWork(): Result { Timber.i("Sync work starting") @@ -67,11 +70,21 @@ internal class SyncWorker(context: Context, return runCatching { doSync(params.timeout) }.fold( - { Result.success() }, + { + Result.success().also { + if (params.periodic) { + // we want to schedule another one after delay + automaticallyBackgroundSync(workManagerProvider, params.sessionId, params.timeout, params.delay) + } + } + }, { failure -> - if (failure.isTokenError() || !params.automaticallyRetry) { + if (failure.isTokenError()) { Result.failure() } else { + // If the worker was stopped (when going back in foreground), a JobCancellation exception is sent + // but in this case the result is ignored, as the work is considered stopped, + // so don't worry of the retry here for this case Result.retry() } } @@ -79,7 +92,7 @@ internal class SyncWorker(context: Context, } private suspend fun doSync(timeout: Long) { - val taskParams = SyncTask.Params(timeout) + val taskParams = SyncTask.Params(timeout * 1000) syncTask.execute(taskParams) } @@ -87,25 +100,27 @@ internal class SyncWorker(context: Context, private const val BG_SYNC_WORK_NAME = "BG_SYNCP" fun requireBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0) { - val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, false)) + val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, 0L, false)) val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SyncWorker>() .setConstraints(WorkManagerProvider.workConstraints) .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS) .setInputData(data) .build() workManagerProvider.workManager - .enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest) + .enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest) } - fun automaticallyBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0, delay: Long = 30_000) { - val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, true)) + fun automaticallyBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0, delayInSeconds: Long = 30) { + val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, delayInSeconds, true)) val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SyncWorker>() .setConstraints(WorkManagerProvider.workConstraints) .setInputData(data) - .setBackoffCriteria(BackoffPolicy.LINEAR, delay, TimeUnit.MILLISECONDS) + .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS) + .setInitialDelay(delayInSeconds, TimeUnit.SECONDS) .build() + workManagerProvider.workManager - .enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest) + .enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest) } fun stopAnyBackgroundSync(workManagerProvider: WorkManagerProvider) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt index dd3c6856c0af7d0c041d9476cc182e21b311e00c..f6cb86c0ed966f18aa370462b297c1ecb86667eb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/UserDataSource.kt @@ -23,9 +23,11 @@ import androidx.paging.DataSource import androidx.paging.LivePagedListBuilder import androidx.paging.PagedList import com.zhuinden.monarchy.Monarchy +import io.realm.Case import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.IgnoredUserEntity import org.matrix.android.sdk.internal.database.model.IgnoredUserEntityFields @@ -33,11 +35,10 @@ import org.matrix.android.sdk.internal.database.model.UserEntity import org.matrix.android.sdk.internal.database.model.UserEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.util.fetchCopied -import io.realm.Case import javax.inject.Inject -internal class UserDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy) { +internal class UserDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy, + private val realmSessionProvider: RealmSessionProvider) { private val realmDataSourceFactory: Monarchy.RealmDataSourceFactory<UserEntity> by lazy { monarchy.createDataSourceFactory { realm -> @@ -58,10 +59,10 @@ internal class UserDataSource @Inject constructor(@SessionDatabase private val m } fun getUser(userId: String): User? { - val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() } - ?: return null - - return userEntity.asDomain() + return realmSessionProvider.withRealm { + val userEntity = UserEntity.where(it, userId).findFirst() + userEntity?.asDomain() + } } fun getUserLive(userId: String): LiveData<Optional<User>> { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataDataSource.kt index d54bfdd63d856ebfd7949259b3f70dbf2032e3ad..a9261eddaba1945ce037a411143a5f72315e41f0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataDataSource.kt @@ -20,18 +20,20 @@ package org.matrix.android.sdk.internal.session.user.accountdata import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import io.realm.RealmQuery +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional +import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.mapper.AccountDataMapper import org.matrix.android.sdk.internal.database.model.UserAccountDataEntity import org.matrix.android.sdk.internal.database.model.UserAccountDataEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent -import io.realm.Realm -import io.realm.RealmQuery import javax.inject.Inject internal class AccountDataDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy, + private val realmSessionProvider: RealmSessionProvider, private val accountDataMapper: AccountDataMapper) { fun getAccountDataEvent(type: String): UserAccountDataEvent? { @@ -45,10 +47,9 @@ internal class AccountDataDataSource @Inject constructor(@SessionDatabase privat } fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> { - return monarchy.fetchAllMappedSync( - { accountDataEventsQuery(it, types) }, - accountDataMapper::map - ) + return realmSessionProvider.withRealm { + accountDataEventsQuery(it, types).findAll().map(accountDataMapper::map) + } } fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt index 2cbc9b23dc63307c03e27957b23028dc558df134..992dbf16dfacdcdaa7e2f9e5c9ffb6cd605780f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/WidgetFactory.kt @@ -23,17 +23,15 @@ import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetContent import org.matrix.android.sdk.api.session.widgets.model.WidgetType -import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.user.UserDataSource -import io.realm.Realm -import io.realm.RealmConfiguration import java.net.URLEncoder import javax.inject.Inject -internal class WidgetFactory @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration, - private val userDataSource: UserDataSource, +internal class WidgetFactory @Inject constructor(private val userDataSource: UserDataSource, + private val realmSessionProvider: RealmSessionProvider, @UserId private val userId: String) { fun create(widgetEvent: Event): Widget? { @@ -44,7 +42,7 @@ internal class WidgetFactory @Inject constructor(@SessionDatabase private val re val senderInfo = if (widgetEvent.senderId == null || widgetEvent.roomId == null) { null } else { - Realm.getInstance(realmConfiguration).use { + realmSessionProvider.withRealm { val roomMemberHelper = RoomMemberHelper(it, widgetEvent.roomId) val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(widgetEvent.senderId) SenderInfo( diff --git a/matrix-sdk-android/src/main/res/values-ar/strings.xml b/matrix-sdk-android/src/main/res/values-ar/strings.xml index e9aba1721afa64cf350740046a1b170d7e40980c..0fc7bd1b49597ab4b3710c582b409d20fa4764d2 100644 --- a/matrix-sdk-android/src/main/res/values-ar/strings.xml +++ b/matrix-sdk-android/src/main/res/values-ar/strings.xml @@ -6,21 +6,21 @@ <string name="notice_room_invite_no_invitee">دعوة من â¨%sâ©</string> <string name="notice_room_invite">دعى â¨%1$sâ© â¨%2$sâ©</string> <string name="notice_room_invite_you">دعاك â¨%1$sâ©</string> - <string name="notice_room_join">انضمّ â¨%1$sâ©</string> - <string name="notice_room_leave">غادر â¨%1$sâ©</string> + <string name="notice_room_join">انضمّ â¨%1$s⩠إلى Ø§Ù„ØºØ±ÙØ©</string> + <string name="notice_room_leave">غادر â¨%1$sâ© Ø§Ù„ØºØ±ÙØ©</string> <string name="notice_room_reject">Ø±ÙØ¶ â¨%1$s⩠الدعوة</string> <string name="notice_room_kick">طرد â¨%1$sâ© â¨%2$sâ©</string> - <string name="notice_room_unban">Ø±ÙØ¹ â¨%1$sâ© Ø§Ù„ØØ¸Ø± عن â¨%2$sâ©</string> + <string name="notice_room_unban">Ø±ÙØ¹ â¨%1$s⩠المنع عن â¨%2$sâ©</string> <string name="notice_room_ban">منع â¨%1$sâ© â¨%2$sâ©</string> <string name="notice_avatar_url_changed">غيّر â¨%1$s⩠صورته</string> <string name="notice_display_name_set">ضبط â¨%1$s⩠اسم العرض على â¨%2$sâ©</string> - <string name="notice_display_name_changed_from">غيّر â¨%1$s⩠اسم Ø§Ù„ØØ³Ø§Ø¨ المعروض من %2$s⩠إلى %3$sâ©</string> - <string name="notice_display_name_removed">أزال â¨%1$s⩠اسم Ø§Ù„ØØ³Ø§Ø¨ المعروض (â¨%2$sâ©)</string> + <string name="notice_display_name_changed_from">غيّر â¨%1$s⩠اسم العرض من â¨%2$s⩠إلى â¨%3$sâ©</string> + <string name="notice_display_name_removed">أزال â¨%1$s⩠اسم العرض (â¨ÙƒØ§Ù† â¨%2$sâ©)</string> <string name="notice_room_topic_changed">غيّر â¨%1$s⩠الموضوع إلى: â¨%2$sâ©</string> <string name="notice_room_name_changed">غيّر â¨%1$s⩠اسم Ø§Ù„ØºØ±ÙØ© إلى: â¨%2$sâ©</string> <string name="notice_answered_call">ردّ â¨%s⩠على المكالمة.</string> <string name="notice_ended_call">أنهى â¨%s⩠المكالمة.</string> - <string name="notice_made_future_room_visibility">جعل â¨%1$s⩠تأريخ Ø§Ù„ØºØ±ÙØ© مستقبلًا ظاهرا على %2$s</string> + <string name="notice_made_future_room_visibility">جعل â¨%1$s⩠تأريخ Ø§Ù„ØºØ±ÙØ© مستقبلًا ظاهرًا على â¨%2$sâ©</string> <string name="notice_room_visibility_invited">كل أعضاء Ø§Ù„ØºØ±ÙØ© من Ù„ØØ¸Ø© دعوتهم.</string> <string name="notice_room_visibility_joined">كل أعضاء Ø§Ù„ØºØ±ÙØ© من Ù„ØØ¸Ø© انضمامهم.</string> <string name="notice_room_visibility_shared">كل أعضاء Ø§Ù„ØºØ±ÙØ©.</string> @@ -46,7 +46,7 @@ <string name="network_error">خطأ ÙÙŠ الشبكة</string> <string name="matrix_error">خطأ ÙÙŠ «ماترÙكس»</string> - <string name="room_error_join_failed_empty_room">ليس ممكنا الانضمام ثانيةً إلى ØºØ±ÙØ© ÙØ§Ø±ØºØ©.</string> + <string name="room_error_join_failed_empty_room">لا يمكنك ØØ§Ù„يًا الانضمام ثانيةً إلى ØºØ±ÙØ© ÙØ§Ø±ØºØ©.</string> <string name="encrypted_message">رسالة معمّاة</string> @@ -54,13 +54,13 @@ <string name="medium_phone_number">رقم الهاتÙ</string> <string name="summary_message">â€â€â¨%1$sâ©: â€â¨%2$sâ©</string> - <string name="notice_room_withdraw">Ø§Ù†Ø³ØØ¨ â¨%1$s⩠من الدعوة â¨%2$sâ©</string> + <string name="notice_room_withdraw">Ø§Ù†Ø³ØØ¨ â¨%1$s⩠من دعوة â¨%2$sâ©</string> <string name="notice_placed_video_call">أجرى â¨%s⩠مكالمة مرئية.</string> <string name="notice_placed_voice_call">أجرى â¨%s⩠مكالمة صوتية.</string> - <string name="notice_room_third_party_registered_invite">قبل â¨%1$s⩠دعوة â¨%2$sâ©</string> + <string name="notice_room_third_party_registered_invite">قَبÙÙ„ â¨%1$s⩠دعوة â¨%2$sâ©</string> <string name="could_not_redact">تعذر التهذيب</string> - <string name="summary_user_sent_sticker">أرسل â¨%1$s⩠ملصقا.</string> + <string name="summary_user_sent_sticker">أرسل â¨%1$s⩠ملصقًا.</string> <string name="notice_avatar_changed_too">(تغيّرت الصورة أيضا)</string> @@ -71,12 +71,77 @@ <string name="room_displayname_room_invite">دعوة إلى ØºØ±ÙØ©</string> <plurals name="room_displayname_three_and_more_members"> - <item quantity="zero">ØµÙØ±</item> - <item quantity="one">ÙˆØ§ØØ¯</item> - <item quantity="two">اثنان</item> - <item quantity="few">قليل</item> - <item quantity="many">كثير</item> - <item quantity="other">اخرى</item> + <item quantity="zero"></item> + <item quantity="one"></item> + <item quantity="two"></item> + <item quantity="few"></item> + <item quantity="many"></item> + <item quantity="other"></item> </plurals> + <string name="summary_you_sent_image">أرسلت صورة.</string> + <string name="summary_you_sent_sticker">أرسلت ملصقًا.</string> + + <string name="notice_room_invite_no_invitee_by_you">دعوة منك أنت</string> + <string name="notice_room_created">أنشأ â¨%1$sâ© Ø§Ù„ØºØ±ÙØ©</string> + <string name="notice_room_created_by_you">أنشأت Ø§Ù„ØºØ±ÙØ©</string> + <string name="notice_room_invite_by_you">دعوت â¨%1$sâ©</string> + <string name="notice_room_join_by_you">انضممت إلى Ø§Ù„ØºØ±ÙØ©</string> + <string name="notice_room_leave_by_you">غادرت Ø§Ù„ØºØ±ÙØ©</string> + <string name="notice_room_reject_by_you">Ø±ÙØ¶Øª الدعوة</string> + <string name="notice_room_kick_by_you">طردت â¨%1$sâ©</string> + <string name="notice_room_unban_by_you">Ø±ÙØ¹Øª المنع عن â¨%1$sâ©</string> + <string name="notice_room_ban_by_you">منعت â¨%1$sâ©</string> + <string name="notice_room_withdraw_by_you">Ø§Ù†Ø³ØØ¨Øª من دعوة â¨%1$sâ©</string> + <string name="notice_avatar_url_changed_by_you">غيّرت صورتك</string> + <string name="notice_display_name_set_by_you">ضبطت اسم العرض على â¨%1$sâ©</string> + <string name="notice_display_name_changed_from_by_you">غيّرت اسم العرض من â¨%1$s⩠إلى â¨%2$sâ©</string> + <string name="notice_display_name_removed_by_you">أزلت اسم العرض (كان â¨%1$sâ©)</string> + <string name="notice_room_topic_changed_by_you">غيّرت الموضوع إلى: â¨%1$sâ©</string> + <string name="notice_room_avatar_changed">غيّر â¨%1$s⩠صورة Ø§Ù„ØºØ±ÙØ©</string> + <string name="notice_room_avatar_changed_by_you">غيّرت صورة Ø§Ù„ØºØ±ÙØ©</string> + <string name="notice_room_name_changed_by_you">غيّرت اسم Ø§Ù„ØºØ±ÙØ© إلى: â¨%1$sâ©</string> + <string name="notice_placed_video_call_by_you">أجريت مكالمة مرئية.</string> + <string name="notice_placed_voice_call_by_you">أجريت مكالمة صوتية.</string> + <string name="notice_call_candidates">أرسل â¨%s⩠البيانات لإعداد المكالمة.</string> + <string name="notice_call_candidates_by_you">أرسلت البيانات لإعداد المكالمة.</string> + <string name="notice_answered_call_by_you">رددت على المكالمة.</string> + <string name="notice_ended_call_by_you">أنهيت المكالمة.</string> + <string name="notice_made_future_room_visibility_by_you">جعلت تأريخ Ø§Ù„ØºØ±ÙØ© مستقبلًا ظاهرًا على â¨%1$sâ©</string> + <string name="notice_end_to_end_by_you">ÙØ¹Ù‘لت تعمية الطرÙين (â¨%1$sâ©)</string> + <string name="notice_room_update">رقّى â¨%s⩠هذه Ø§Ù„ØºØ±ÙØ©.</string> + <string name="notice_room_update_by_you">رقّيت هذه Ø§Ù„ØºØ±ÙØ©.</string> + + <string name="notice_requested_voip_conference_by_you">طلبت اجتماع VoIP</string> + <string name="notice_room_name_removed_by_you">أزلت اسم Ø§Ù„ØºØ±ÙØ©</string> + <string name="notice_room_topic_removed_by_you">أزلت موضوع Ø§Ù„ØºØ±ÙØ©</string> + <string name="notice_room_avatar_removed">أزال â¨%1$s⩠صورة Ø§Ù„ØºØ±ÙØ©</string> + <string name="notice_room_avatar_removed_by_you">أزلت صورة Ø§Ù„ØºØ±ÙØ©</string> + <string name="notice_event_redacted">Ø£ÙØ²ÙŠÙ„ت الرسالة</string> + <string name="notice_event_redacted_by">أزال â¨%1$s⩠الرسالة</string> + <string name="notice_event_redacted_with_reason">Ø£ÙØ²ÙŠÙ„ت الرسالة [السبب: â¨%1$sâ©]</string> + <string name="notice_event_redacted_by_with_reason">أزال â¨%1$s⩠الرسالة [السبب: â¨%2$sâ©]</string> + <string name="notice_room_third_party_invite_by_you">أرسلت دعوة إلى â¨%1$s⩠للانضمام إلى Ø§Ù„ØºØ±ÙØ©</string> + <string name="notice_room_third_party_revoked_invite">Ø³ØØ¨ â¨%1$s⩠دعوة â¨%2$s⩠للانضمام إلى Ø§Ù„ØºØ±ÙØ©</string> + <string name="notice_room_third_party_revoked_invite_by_you">Ø³ØØ¨Øª دعوة â¨%1$s⩠للانضمام إلى Ø§Ù„ØºØ±ÙØ©</string> + <string name="notice_room_third_party_registered_invite_by_you">قَبÙلت دعوة â¨%1$sâ©</string> + + <string name="notice_widget_added">أضا٠â¨%1$s⩠الودجة â¨%2$sâ©</string> + <string name="notice_widget_added_by_you">Ø£Ø¶ÙØª الودجة â¨%1$sâ©</string> + <string name="notice_widget_removed">أزال â¨%1$s⩠الودجة â¨%2$sâ©</string> + <string name="notice_widget_removed_by_you">أزلت الودجة â¨%1$sâ©</string> + <string name="notice_widget_modified">عدّل â¨%1$s⩠الودجة â¨%2$sâ©</string> + <string name="notice_widget_modified_by_you">عدّلت الودجة â¨%1$sâ©</string> + + <string name="power_level_admin">مدير</string> + <string name="power_level_default">المبدئي</string> + <string name="power_level_custom">مخصّص (â¨%1$dâ©)</string> + <string name="power_level_custom_no_value">مخصّص</string> + + <string name="notice_power_level_changed_by_you">غيّرت مستوى قوّة %1$sâ©.</string> + <string name="notice_power_level_changed">غيّر â¨%1$s⩠مستوى قوّة %2$sâ©.</string> + <string name="notice_power_level_diff">â€â¨%1$s⩠من â¨%2$s⩠إلى â¨%3$sâ©</string> + + <string name="initial_sync_start_importing_account">المزامنة الأولية: +\nيستورد Ø§Ù„ØØ³Ø§Ø¨â€¦</string> </resources> diff --git a/matrix-sdk-android/src/main/res/values-es/strings.xml b/matrix-sdk-android/src/main/res/values-es/strings.xml index ae1f5633bf1e16b56dfbcdcc26288aaa5a86ba2a..1b1935602cd560cf89804cd86f0cdb67a630ceeb 100644 --- a/matrix-sdk-android/src/main/res/values-es/strings.xml +++ b/matrix-sdk-android/src/main/res/values-es/strings.xml @@ -147,4 +147,98 @@ <string name="key_verification_request_fallback_message">%s solicita verificar su clave, pero su cliente no soporta la verificación de la clave en chat. Necesitará usar la verificación de claves clásica para poder verificar las claves.</string> + <string name="summary_you_sent_image">Enviaste una imagen.</string> + <string name="summary_you_sent_sticker">Enviaste un sticker.</string> + + <string name="notice_room_invite_no_invitee_by_you">Tu invitación</string> + <string name="notice_room_created">%1$s creó la habitación</string> + <string name="notice_room_created_by_you">Tu creaste la habitación</string> + <string name="notice_room_invite_by_you">Invitaste a %1$s</string> + <string name="notice_room_join_by_you">Te uniste a la Sala</string> + <string name="notice_room_leave_by_you">Dejaste la Sala</string> + <string name="notice_room_reject_by_you">Rechazaste la invitación</string> + <string name="notice_room_kick_by_you">Tu pateaste a %1$s</string> + <string name="notice_room_unban_by_you">Tu desbanaste a %1$s</string> + <string name="notice_room_ban_by_you">Usted prohibió a %1$s</string> + <string name="notice_room_withdraw_by_you">Retiró la invitación de %1$s\'s</string> + <string name="notice_avatar_url_changed_by_you">Cambiaste tu avatar</string> + <string name="notice_display_name_set_by_you">Establece su nombre de visualización en %1$s</string> + <string name="notice_display_name_changed_from_by_you">Cambiaste tu nombre para mostrar de %1$s a %2$s</string> + <string name="notice_display_name_removed_by_you">Quitaste tu nombre para mostrar (era %1$s)</string> + <string name="notice_room_topic_changed_by_you">Cambiaste el tema a: %1$s</string> + <string name="notice_room_avatar_changed">%1$s cambió el avatar de la sala</string> + <string name="notice_room_avatar_changed_by_you">Cambiaste el avatar de la habitación</string> + <string name="notice_room_name_changed_by_you">Cambiaste el nombre de la habitación a: %1$s</string> + <string name="notice_placed_video_call_by_you">Hiciste una videollamada.</string> + <string name="notice_placed_voice_call_by_you">Hiciste una llamada de voz.</string> + <string name="notice_call_candidates">%s envió datos para configurar la llamada.</string> + <string name="notice_call_candidates_by_you">Enviaste datos para configurar la llamada.</string> + <string name="notice_answered_call_by_you">Respondiste la llamada.</string> + <string name="notice_ended_call_by_you">Terminaste la llamada.</string> + <string name="notice_made_future_room_visibility_by_you">Hiciste visible el futuro historial de la %1$s</string> + <string name="notice_end_to_end_by_you">Activó el cifrado de un extremo a otro (%1$s)</string> + <string name="notice_room_update_by_you">Has mejorado esta habitación.</string> + + <string name="notice_requested_voip_conference_by_you">Solicitaste una conferencia de VoIP</string> + <string name="notice_room_name_removed_by_you">Quitaste el nombre de la sala</string> + <string name="notice_room_topic_removed_by_you">Quitaste el tema de la sala</string> + <string name="notice_room_avatar_removed">%1$s eliminó el avatar de la habitación</string> + <string name="notice_room_avatar_removed_by_you">Quitaste el avatar de la habitación</string> + <string name="notice_profile_change_redacted_by_you">Actualizaste tu perfil %1$s</string> + <string name="notice_room_third_party_invite_by_you">Enviaste una invitación a %1$s para unirse a la sala</string> + <string name="notice_room_third_party_revoked_invite_by_you">Revocaste la invitación para que %1$s se una a la sala</string> + <string name="notice_room_third_party_registered_invite_by_you">Aceptaste la invitación para %1$s</string> + + <string name="notice_widget_added">%1$s agrego el widget %2$s</string> + <string name="notice_widget_added_by_you">Agregaste el widget %1$s</string> + <string name="notice_widget_removed">%1$s eliminó el widget %2$s</string> + <string name="notice_widget_removed_by_you">Quitaste el widget %1$s</string> + <string name="notice_widget_modified">%1$s modifico el widget %2$s</string> + <string name="notice_widget_modified_by_you">Modificaste el widget %1$s</string> + + <string name="power_level_admin">Administrador</string> + <string name="power_level_moderator">Moderador</string> + <string name="power_level_default">Por defecto</string> + <string name="power_level_custom">Personalizado (%1$d)</string> + <string name="power_level_custom_no_value">Personalizado</string> + + <string name="notice_power_level_changed_by_you">Cambiaste el nivel de potencia de %1$s.</string> + <string name="notice_power_level_changed">%1$s cambió el nivel de potencia de %2$s.</string> + <string name="notice_power_level_diff">%1$s de %2$s a %3$s</string> + + <string name="notice_room_invite_no_invitee_with_reason_by_you">Tu invitación. Razón: %1$s</string> + <string name="notice_room_invite_with_reason_by_you">"nvitaste a %1$s. Razón: %2$s"</string> + <string name="notice_room_join_with_reason_by_you">Te uniste a la habitación. Razón: %1$s</string> + <string name="notice_room_leave_with_reason_by_you">Dejaste la habitación. Razón: %1$s</string> + <string name="notice_room_reject_with_reason_by_you">Rechazaste la invitación. Razón: %1$s</string> + <string name="notice_room_kick_with_reason_by_you">Pateaste a %1$s. Motivo: %2$s</string> + <string name="notice_room_unban_with_reason_by_you">Has desactivado a %1$s. Motivo: %2$s</string> + <string name="notice_room_ban_with_reason_by_you">Prohibiste a %1$s. Motivo: %2$s</string> + <string name="notice_room_third_party_invite_with_reason_by_you">Enviaste una invitación a %1$s para unirse a la sala. Motivo: %2$s</string> + <string name="notice_room_third_party_revoked_invite_with_reason_by_you">Revocaste la invitación para que %1$s se una a la sala. Motivo: %2$s</string> + <string name="notice_room_third_party_registered_invite_with_reason_by_you">Aceptaste la invitación para %1$s. Motivo: %2$s</string> + <string name="notice_room_withdraw_with_reason_by_you">Retiró la invitación de %1$s\'s. Motivo: %2$s</string> + + <plurals name="notice_room_aliases_added_by_you"> + <item quantity="one">Agregaste %1$s como dirección para esta sala.</item> + <item quantity="other">Agregaste %1$s como direcciones para esta sala.</item> + </plurals> + + <plurals name="notice_room_aliases_removed_by_you"> + <item quantity="one">Quitaste %1$s como dirección para esta sala.</item> + <item quantity="other">Quitaste %2$s como direcciones para esta sala.</item> + </plurals> + + <string name="notice_room_aliases_added_and_removed">"%1$s agregó %2$s y eliminó %3$s como direcciones para esta sala."</string> + <string name="notice_room_aliases_added_and_removed_by_you">Agregaste %1$s y quitaste %2$s como direcciones para esta sala.</string> + + <string name="notice_room_canonical_alias_set_by_you">Estableciste la dirección principal de esta sala en %1$s.</string> + <string name="notice_room_canonical_alias_unset_by_you">Quitaste la dirección principal de esta sala.</string> + + <string name="notice_room_guest_access_can_join_by_you">Ha permitido que los invitados se unan a la sala.</string> + <string name="notice_room_guest_access_forbidden_by_you">Ha impedido que los invitados se unan a la sala.</string> + + <string name="notice_end_to_end_ok_by_you">Activó el cifrado de extremo a extremo.</string> + <string name="notice_end_to_end_unknown_algorithm_by_you">Activó el cifrado de un extremo a otro (algoritmo %1$s no reconocido).</string> + </resources>