diff --git a/CHANGES.md b/CHANGES.md index ca705ea53b62adc3f7110b3a125a332a63fccac6..01cac0b47acd8aeddb3cab2469b12ccf379959d5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,16 @@ Please also refer to the Changelog of Element Android: https://github.com/vector-im/element-android/blob/main/CHANGES.md +Changes in Matrix-SDK 1.3.4 (2021-10-20) +=================================================== + +Imported from Element 1.3.4. (https://github.com/vector-im/element-android/releases/tag/v1.3.4) + +SDK API changes âš ï¸ +------------------ +Add Presence support: +- PresenceService has been added, with the ability to set the presence of the current user +- For DM, the presence of the other user is added to the RoomSummary + Changes in Matrix-SDK 1.3.2 (2021-10-08) =================================================== diff --git a/dependencies.gradle b/dependencies.gradle index 92358952db5c828d612dd79ddcb377e8a84f0f80..7fa42a666f0bb8fccbd69efe145c73cf14d5083d 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -7,7 +7,7 @@ ext.versions = [ 'targetCompat' : JavaVersion.VERSION_11, ] -def gradle = "7.0.2" +def gradle = "7.0.3" // Ref: https://kotlinlang.org/releases.html def kotlin = "1.5.31" def kotlinCoroutines = "1.5.2" @@ -19,6 +19,7 @@ def moshi = "1.12.0" def lifecycle = "2.2.0" def rxBinding = "3.1.0" def epoxy = "4.6.2" +def mavericks = "2.4.0" def glide = "4.12.0" def bigImageViewer = "1.8.1" def jjwt = "0.11.2" @@ -36,8 +37,6 @@ ext.libs = [ 'kotlinPlugin' : "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin" ], jetbrains : [ - 'kotlinStdlibJdk7' : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin", - 'kotlinStdlib' : "org.jetbrains.kotlin:kotlin-stdlib:$kotlin", 'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines", 'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines", 'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines" @@ -98,7 +97,9 @@ ext.libs = [ 'epoxyGlide' : "com.airbnb.android:epoxy-glide-preloading:$epoxy", 'epoxyProcessor' : "com.airbnb.android:epoxy-processor:$epoxy", 'epoxyPaging' : "com.airbnb.android:epoxy-paging:$epoxy", - 'mvrx' : "com.airbnb.android:mvrx:1.5.1" + 'mavericks' : "com.airbnb.android:mavericks:$mavericks", + 'mavericksRx' : "com.airbnb.android:mavericks-rxjava2:$mavericks", + 'mavericksTesting' : "com.airbnb.android:mavericks-testing:$mavericks" ], mockk : [ 'mockk' : "io.mockk:mockk:$mockk", @@ -123,10 +124,13 @@ ext.libs = [ 'jjwtImpl' : "io.jsonwebtoken:jjwt-impl:$jjwt", 'jjwtOrgjson' : "io.jsonwebtoken:jjwt-orgjson:$jjwt" ], - vanniktech: [ + vanniktech : [ 'emojiMaterial' : "com.vanniktech:emoji-material:$vanniktechEmoji", 'emojiGoogle' : "com.vanniktech:emoji-google:$vanniktechEmoji" ], + apache : [ + 'commonsImaging' : "org.apache.sanselan:sanselan:0.97-incubator" + ], tests : [ 'kluent' : "org.amshove.kluent:kluent-android:1.68", 'timberJunitRule' : "net.lachlanmckee:timber-junit-rule:1.0.1", diff --git a/gradle.properties b/gradle.properties index a00acf5c92e35e0d88a092e9d1fbc90958bdfcbd..970f551b196656343ba73975e84b813a4c24632d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,7 +26,7 @@ vector.httpLogLevel=NONE # Ref: https://github.com/vanniktech/gradle-maven-publish-plugin GROUP=org.matrix.android POM_ARTIFACT_ID=matrix-android-sdk2 -VERSION_NAME=1.3.2 +VERSION_NAME=1.3.4 POM_PACKAGING=aar diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 7196a97bd9cd07f9d4627d139c66d3b2d935bbaf..12b37b00b23d826819ee1de20f337e502f94be1d 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -103,8 +103,6 @@ static def gitRevisionDate() { } dependencies { - - implementation libs.jetbrains.kotlinStdlibJdk7 implementation libs.jetbrains.coroutinesCore implementation libs.jetbrains.coroutinesAndroid @@ -133,6 +131,7 @@ dependencies { // Database implementation 'com.github.Zhuinden:realm-monarchy:0.7.1' + kapt 'dk.ilios:realmfieldnameshelper:2.0.0' // Work @@ -156,8 +155,11 @@ dependencies { // Video compression implementation 'com.otaliastudios:transcoder:0.10.4' + // Exif data handling + implementation libs.apache.commonsImaging + // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.34' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.35' testImplementation libs.tests.junit testImplementation 'org.robolectric:robolectric:4.6.1' @@ -168,6 +170,8 @@ dependencies { implementation libs.jetbrains.coroutinesAndroid // Plant Timber tree for test testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' + // Transitively required for mocking realm as monarchy doesn't expose Rx + testImplementation libs.rx.rxKotlin kaptAndroidTest libs.dagger.daggerCompiler androidTestImplementation libs.androidx.testCore diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SingleThreadCoroutineDispatcher.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SingleThreadCoroutineDispatcher.kt index 192f6442b22c97b5436a530f1da914ab938fbde9..3e3af10799a4d07daa6395ad1c96af33fcf9032b 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SingleThreadCoroutineDispatcher.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SingleThreadCoroutineDispatcher.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.asCoroutineDispatcher -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import java.util.concurrent.Executors internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MatrixCoroutineDispatchers.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixCoroutineDispatchers.kt similarity index 85% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MatrixCoroutineDispatchers.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixCoroutineDispatchers.kt index b44a543c1cadb8d4fd116d8fda91c0b650110b5e..592974be743e02e5e671f6e21964f4a073bd89dd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MatrixCoroutineDispatchers.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixCoroutineDispatchers.kt @@ -5,7 +5,7 @@ * 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 + * 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, @@ -14,11 +14,11 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.util +package org.matrix.android.sdk.api import kotlinx.coroutines.CoroutineDispatcher -internal data class MatrixCoroutineDispatchers( +data class MatrixCoroutineDispatchers( val io: CoroutineDispatcher, val computation: CoroutineDispatcher, val main: CoroutineDispatcher, 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 bde68da9d7e4d1563eedf22cf4fc9bb31d117a66..d4bfd4ee8c13ebe02c81ff1157b1b27525acde5e 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 @@ -20,6 +20,7 @@ import androidx.annotation.MainThread import androidx.lifecycle.LiveData import kotlinx.coroutines.flow.SharedFlow import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.federation.FederationService @@ -42,6 +43,7 @@ import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerS import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.api.session.openid.OpenIdService import org.matrix.android.sdk.api.session.permalinks.PermalinkService +import org.matrix.android.sdk.api.session.presence.PresenceService import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.pushers.PushersService import org.matrix.android.sdk.api.session.room.RoomDirectoryService @@ -75,6 +77,7 @@ interface Session : TermsService, EventService, ProfileService, + PresenceService, PushRuleService, PushersService, SyncStatusService, @@ -82,6 +85,8 @@ interface Session : SecureStorageService, AccountService { + val coroutineDispatchers: MatrixCoroutineDispatchers + /** * The params associated to the session */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 96b44ce8c9034616ecdcc0e2b1eac3759ca5a12c..169f90dbca0a5b023a8f27b32e21af705cdd2051 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.internal.session.presence.model.PresenceContent import timber.log.Timber typealias Content = JsonDict @@ -305,3 +306,7 @@ fun Event.isReply(): Boolean { fun Event.isEdition(): Boolean { return getRelationContent()?.takeIf { it.type == RelationType.REPLACE }?.eventId != null } + +fun Event.getPresenceContent(): PresenceContent? { + return content.toModel<PresenceContent>() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt new file mode 100644 index 0000000000000000000000000000000000000000..82a81f4b64e064fb61cd0d3536ebc63259349172 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/PresenceService.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.matrix.android.sdk.api.session.presence + +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum +import org.matrix.android.sdk.api.session.presence.model.UserPresence + +/** + * This interface defines methods for handling user presence information. + */ +interface PresenceService { + /** + * Update the presence status for the current user + * @param presence the new presence state + * @param statusMsg the status message to attach to this state + */ + suspend fun setMyPresence(presence: PresenceEnum, statusMsg: String? = null) + + /** + * Fetch the given user's presence state. + * @param userId the userId whose presence state to get. + */ + suspend fun fetchPresence(userId: String): UserPresence + + // TODO Add live data (of Flow) of the presence of a userId +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/PresenceEnum.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/PresenceEnum.kt new file mode 100644 index 0000000000000000000000000000000000000000..6d9994ef1ca9e75b0bde56257e4ba7603f260fbd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/PresenceEnum.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.presence.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = false) +enum class PresenceEnum(val value: String) { + @Json(name = "online") + ONLINE("online"), + + @Json(name = "offline") + OFFLINE("offline"), + + @Json(name = "unavailable") + UNAVAILABLE("unavailable"); + + companion object { + fun from(s: String): PresenceEnum? = values().find { it.value == s } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/UserPresence.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/UserPresence.kt new file mode 100644 index 0000000000000000000000000000000000000000..6b33ff07d5a5d38f1bd9a0aef109a3f28cfc095f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/presence/model/UserPresence.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.presence.model + +data class UserPresence( + val lastActiveAgo: Long? = null, + val statusMessage: String? = null, + val isCurrentlyActive: Boolean? = null, + val presence: PresenceEnum = PresenceEnum.OFFLINE +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt index 2cd17952c688ac2a985960ce00345660bd090120..f884d3e8906b8c298d55191254729d41ef363069 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt @@ -29,38 +29,19 @@ interface PushersService { * Add a new HTTP pusher. * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set * - * @param pushkey This is a unique identifier for this pusher. The value you should use for - * this is the routing or destination address information for the notification, - * for example, the APNS token for APNS or the Registration ID for GCM. If your - * notification client has no such concept, use any unique identifier. Max length, 512 chars. - * @param appId the application id - * This is a reverse-DNS style identifier for the application. It is recommended - * that this end with the platform, such that different platform versions get - * different app identifiers. Max length, 64 chars. - * @param profileTag This string determines which set of device specific rules this pusher executes. - * @param lang The preferred language for receiving notifications (e.g. "en" or "en-US"). - * @param appDisplayName A human readable string that will allow the user to identify what application owns this pusher. - * @param deviceDisplayName A human readable string that will allow the user to identify what device owns this pusher. - * @param url The URL to use to send notifications to. MUST be an HTTPS URL with a path of /_matrix/push/v1/notify. - * @param append If true, the homeserver should add another pusher with the given pushkey and App ID in addition - * to any others with different user IDs. Otherwise, the homeserver must remove any other pushers - * with the same App ID and pushkey for different users. - * @param withEventIdOnly true to limit the push content to only id and not message content - * Ref: https://matrix.org/docs/spec/push_gateway/r0.1.1#homeserver-behaviour + * @throws [InvalidParameterException] if a parameter is not correct + */ + suspend fun addHttpPusher(httpPusher: HttpPusher) + + /** + * Enqueues a new HTTP pusher via the WorkManager API. + * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set * - * @return A work request uuid. Can be used to listen to the status - * (LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(<UUID>)) + * @return A work request uuid. Can be used to listen to the status + * (LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(<UUID>)) * @throws [InvalidParameterException] if a parameter is not correct */ - fun addHttpPusher(pushkey: String, - appId: String, - profileTag: String, - lang: String, - appDisplayName: String, - deviceDisplayName: String, - url: String, - append: Boolean, - withEventIdOnly: Boolean): UUID + fun enqueueAddHttpPusher(httpPusher: HttpPusher): UUID /** * Add a new Email pusher. @@ -75,16 +56,14 @@ interface PushersService { * to any others with different user IDs. Otherwise, the homeserver must remove any other pushers * with the same App ID and pushkey for different users. Typically We always want to append for * email pushers since we don't want to stop other accounts notifying to the same email address. - * @return A work request uuid. Can be used to listen to the status - * (LiveData<WorkInfo> status = workManager.getWorkInfoByIdLiveData(<UUID>)) * @throws [InvalidParameterException] if a parameter is not correct */ - fun addEmailPusher(email: String, - lang: String, - emailBranding: String, - appDisplayName: String, - deviceDisplayName: String, - append: Boolean = true): UUID + suspend fun addEmailPusher(email: String, + lang: String, + emailBranding: String, + appDisplayName: String, + deviceDisplayName: String, + append: Boolean = true) /** * Directly ask the push gateway to send a push to this device @@ -128,4 +107,61 @@ interface PushersService { * Get the current pushers */ fun getPushers(): List<Pusher> + + data class HttpPusher( + + /** + * This is a unique identifier for this pusher. The value you should use for + * this is the routing or destination address information for the notification, + * for example, the APNS token for APNS or the Registration ID for GCM. If your + * notification client has no such concept, use any unique identifier. Max length, 512 chars. + */ + val pushkey: String, + + /** + * The application id + * This is a reverse-DNS style identifier for the application. It is recommended + * that this end with the platform, such that different platform versions get + * different app identifiers. Max length, 64 chars. + */ + val appId: String, + + /** + * This string determines which set of device specific rules this pusher executes. + */ + val profileTag: String, + + /** + * The preferred language for receiving notifications (e.g. "en" or "en-US"). + */ + val lang: String, + + /** + * A human readable string that will allow the user to identify what application owns this pusher. + */ + val appDisplayName: String, + + /** + * A human readable string that will allow the user to identify what device owns this pusher. + */ + val deviceDisplayName: String, + + /** + * The URL to use to send notifications to. MUST be an HTTPS URL with a path of /_matrix/push/v1/notify. + */ + val url: String, + + /** + * If true, the homeserver should add another pusher with the given pushkey and App ID in addition + * to any others with different user IDs. Otherwise, the homeserver must remove any other pushers + * with the same App ID and pushkey for different users. + */ + val append: Boolean, + + /** + * true to limit the push content to only id and not message content + * Ref: https://matrix.org/docs/spec/push_gateway/r0.1.1#homeserver-behaviour + */ + val withEventIdOnly: Boolean + ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt index ebe96b638298096ae360f11f0c46e6a512d1ced2..6c0e730499468b4139fbccd9e69d610efb690887 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api.session.room import androidx.lifecycle.LiveData +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataService import org.matrix.android.sdk.api.session.room.alias.AliasService import org.matrix.android.sdk.api.session.room.call.RoomCallService @@ -61,6 +62,8 @@ interface Room : RoomAccountDataService, RoomVersionService { + val coroutineDispatchers: MatrixCoroutineDispatchers + /** * The roomId of this room */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt index fba3a1dd719f6e213abfa8752c733fa946c99856..39177a4296b8983dcb5b8e66e5e1c7531e05b9ac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomMemberSummary.kt @@ -16,12 +16,15 @@ package org.matrix.android.sdk.api.session.room.model +import org.matrix.android.sdk.api.session.presence.model.UserPresence + /** * Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content */ data class RoomMemberSummary constructor( val membership: Membership, val userId: String, + val userPresence: UserPresence? = null, val displayName: String? = null, val avatarUrl: String? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt index cae4775e7173580d369b3564ad5bc60e7cad7427..10cad026bcbe1100dfbb4ecfe31d6b90d0cd6e60 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api.session.room.model import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel +import org.matrix.android.sdk.api.session.presence.model.UserPresence import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.session.room.sender.SenderInfo @@ -38,6 +39,7 @@ data class RoomSummary( val joinRules: RoomJoinRules? = null, val isDirect: Boolean = false, val directUserId: String? = null, + val directUserPresence: UserPresence? = null, val joinedMembersCount: Int? = 0, val invitedMembersCount: Int? = 0, val latestPreviewableEvent: TimelineEvent? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 7115ff5db2f910c544cd1fa5b8c0fb05c2d82568..7b96148e2e55c4a32ca0da22086157231b15cbd3 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -29,6 +29,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.crypto.MXCryptoConfig @@ -93,7 +94,6 @@ import org.matrix.android.sdk.internal.task.TaskThread import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.olm.OlmManager import timber.log.Timber import java.util.concurrent.atomic.AtomicBoolean diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index 8a91376b60782cdcfabec5ef765c7a450c91964c..494e6d7cc7a3138d43fe4709161ccce2b22224dd 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto import kotlinx.coroutines.CancellationException import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel @@ -29,7 +30,6 @@ import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.logLimit import timber.log.Timber import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt index fe17dd08e47b283a483c81a46ce1aa74886869c5..57381eacfb47954160d19e3e4b8e05618fb85f72 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType @@ -27,16 +28,17 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo +import org.matrix.android.sdk.internal.crypto.model.MXOlmSessionResult import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.event.OlmEventContent import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.extensions.foldToCallback import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import timber.log.Timber import javax.inject.Inject -import kotlin.jvm.Throws + +private const val SEND_TO_DEVICE_RETRY_COUNT = 3 @SessionScope internal class EventDecryptor @Inject constructor( @@ -146,29 +148,36 @@ internal class EventDecryptor @Inject constructor( // offload this from crypto thread (?) cryptoCoroutineScope.launch(coroutineDispatchers.computation) { - val ensured = ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true) + runCatching { ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true) }.fold( + onSuccess = { sendDummyToDevice(ensured = it, deviceInfo, senderId) }, + onFailure = { + Timber.e("## CRYPTO | markOlmSessionForUnwedging() : failed to ensure device info ${senderId}${deviceInfo.deviceId}") + } + ) + } + } - Timber.i("## CRYPTO | markOlmSessionForUnwedging() : ensureOlmSessionsForDevicesAction isEmpty:${ensured.isEmpty}") + private suspend fun sendDummyToDevice(ensured: MXUsersDevicesMap<MXOlmSessionResult>, deviceInfo: CryptoDeviceInfo, senderId: String) { + Timber.i("## CRYPTO | markOlmSessionForUnwedging() : ensureOlmSessionsForDevicesAction isEmpty:${ensured.isEmpty}") - // Now send a blank message on that session so the other side knows about it. - // (The keyshare request is sent in the clear so that won't do) - // We send this first such that, as long as the toDevice messages arrive in the - // same order we sent them, the other end will get this first, set up the new session, - // then get the keyshare request and send the key over this new session (because it - // is the session it has most recently received a message on). - val payloadJson = mapOf<String, Any>("type" to EventType.DUMMY) + // Now send a blank message on that session so the other side knows about it. + // (The keyshare request is sent in the clear so that won't do) + // We send this first such that, as long as the toDevice messages arrive in the + // same order we sent them, the other end will get this first, set up the new session, + // then get the keyshare request and send the key over this new session (because it + // is the session it has most recently received a message on). + val payloadJson = mapOf<String, Any>("type" to EventType.DUMMY) - val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) - val sendToDeviceMap = MXUsersDevicesMap<Any>() - sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload) - Timber.i("## CRYPTO | markOlmSessionForUnwedging() : sending dummy to $senderId:${deviceInfo.deviceId}") - withContext(coroutineDispatchers.io) { - val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) - try { - sendToDeviceTask.execute(sendToDeviceParams) - } catch (failure: Throwable) { - Timber.e(failure, "## CRYPTO | markOlmSessionForUnwedging() : failed to send dummy to $senderId:${deviceInfo.deviceId}") - } + val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) + val sendToDeviceMap = MXUsersDevicesMap<Any>() + sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload) + Timber.i("## CRYPTO | markOlmSessionForUnwedging() : sending dummy to $senderId:${deviceInfo.deviceId}") + withContext(coroutineDispatchers.io) { + val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) + try { + sendToDeviceTask.executeRetry(sendToDeviceParams, remainingRetry = SEND_TO_DEVICE_RETRY_COUNT) + } catch (failure: Throwable) { + Timber.e(failure, "## CRYPTO | markOlmSessionForUnwedging() : failed to send dummy to $senderId:${deviceInfo.deviceId}") } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt index 3825a5dab2e4d72284d58a50c8dd969a682584a6..e7a46750b0a84afad9c7b07a3a26a37d5a349a22 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/InboundGroupSessionStore.kt @@ -19,10 +19,10 @@ package org.matrix.android.sdk.internal.crypto import android.util.LruCache import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2 import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import timber.log.Timber import java.util.Timer import java.util.TimerTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt index e8640d5011d769e4edc76cfb62ffc25b9b2d83bc..220f25ec80096fb1b2b0c0214666639a05402a4e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.crypto.MXCryptoConfig import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME @@ -38,7 +39,6 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import timber.log.Timber import java.util.concurrent.Executors diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt index 6fc71036688495f5365a379e6ab6b20d315e7660..fd60e4326041d6248d560ce0b38c49c1e151ba6a 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt @@ -19,13 +19,13 @@ package org.matrix.android.sdk.internal.crypto import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import timber.log.Timber import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt index 52876b0fff87a577976a08f5f9d46ce4c49c688c..3979ff1fb4879a36c52644093ca4a9b3c0a124f2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForDevicesAction.kt @@ -25,6 +25,8 @@ import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDevi import timber.log.Timber import javax.inject.Inject +private const val ONE_TIME_KEYS_RETRY_COUNT = 3 + internal class EnsureOlmSessionsForDevicesAction @Inject constructor( private val olmDevice: MXOlmDevice, private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) { @@ -72,7 +74,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor( Timber.i("## CRYPTO | claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim") val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim) - val oneTimeKeys = oneTimeKeysForUsersDeviceTask.execute(claimParams) + val oneTimeKeys = oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, remainingRetry = ONE_TIME_KEYS_RETRY_COUNT) Timber.v("## CRYPTO | claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys") for ((userId, deviceInfos) in devicesByUser) { for (deviceInfo in deviceInfos) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index d2f6bd0382b490a37881fa1322821e6632f6e62b..d7411ad0be7ebfcefe0b1504a724e376d3cb66d6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType @@ -41,7 +42,6 @@ import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import timber.log.Timber internal class MXMegolmDecryption(private val userId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt index 91640523fc0feb08294db92693a13d2878722cbd..29f9d193f84700a34983c208a6a28f3bd7859bb9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm import kotlinx.coroutines.CoroutineScope +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.OutgoingGossipingRequestManager @@ -25,7 +26,6 @@ import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import javax.inject.Inject internal class MXMegolmDecryptionFactory @Inject constructor( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 63fe678229fae6ab1b7a432ee85ce4f8168b4122..031bb4e194f489584425c962f3abb8793109e526 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event @@ -39,7 +40,6 @@ import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepo import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.convertToUTF8 import timber.log.Timber diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt index 9f6312ea97804478bdd3d8cdbaf1e80ab4dead1e..238d7eed880a119686d2d405c958ef8bb5d0b459 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryptionFactory.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm import kotlinx.coroutines.CoroutineScope +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction @@ -27,7 +28,6 @@ import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import javax.inject.Inject internal class MXMegolmEncryptionFactory @Inject constructor( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt index 68a95e395bb0ee3306ecb7f7cbdfcbc5fa643712..50ce2d2bf3d02a65f509b3e05b7b0998950000a8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryptionFactory.kt @@ -16,12 +16,12 @@ package org.matrix.android.sdk.internal.crypto.algorithms.olm +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.MXOlmDevice import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForUsersAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import javax.inject.Inject internal class MXOlmEncryptionFactory @Inject constructor(private val olmDevice: MXOlmDevice, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ComputeTrustTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ComputeTrustTask.kt index 113255bb7ea8cf26be480d2910afc0fddce321d4..b470ab34bb703161487ef19ec9cdb36463dd2fa9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ComputeTrustTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/ComputeTrustTask.kt @@ -16,13 +16,13 @@ package org.matrix.android.sdk.internal.crypto.crosssigning import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.task.Task -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import javax.inject.Inject internal interface ComputeTrustTask : Task<ComputeTrustTask.Params, RoomEncryptionTrustLevel> { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 8a851b12674bfc206e175ca759746df2d7ddce52..83de06a66872abb103e9654744f4e5cde678fe55 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -22,6 +22,7 @@ import androidx.work.ExistingWorkPolicy import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService @@ -42,7 +43,6 @@ import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskThread import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.logLimit import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import org.matrix.olm.OlmPkSigning diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt index c6e2c1217f8712a17f48bb691c35fcf5d262bbc3..b20168eaa3cd25ffe869d8ce252ae3595ee8f142 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError @@ -83,7 +84,6 @@ import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskThread import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.JsonCanonicalizer -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.olm.OlmException import org.matrix.olm.OlmPkDecryption @@ -410,7 +410,7 @@ internal class DefaultKeysBackupService @Inject constructor( val keysBackupVersionTrust = KeysBackupVersionTrust() val authData = keysBackupVersion.getAuthDataAsMegolmBackupAuthData() - if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isEmpty()) { + if (authData == null || authData.publicKey.isEmpty() || authData.signatures.isNullOrEmpty()) { Timber.v("getKeysBackupTrust: Key backup is absent or missing required data") return keysBackupVersionTrust } @@ -478,7 +478,7 @@ internal class DefaultKeysBackupService @Inject constructor( cryptoCoroutineScope.launch(coroutineDispatchers.main) { val updateKeysBackupVersionBody = withContext(coroutineDispatchers.crypto) { // Get current signatures, or create an empty set - val myUserSignatures = authData.signatures[userId].orEmpty().toMutableMap() + val myUserSignatures = authData.signatures?.get(userId).orEmpty().toMutableMap() if (trust) { // Add current device signature @@ -497,7 +497,7 @@ internal class DefaultKeysBackupService @Inject constructor( // Create an updated version of KeysVersionResult val newMegolmBackupAuthData = authData.copy() - val newSignatures = newMegolmBackupAuthData.signatures.toMutableMap() + val newSignatures = newMegolmBackupAuthData.signatures.orEmpty().toMutableMap() newSignatures[userId] = myUserSignatures val newMegolmBackupAuthDataWithNewSignature = newMegolmBackupAuthData.copy( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt index 54b92546e9bdf6db74d93851c5d70738fd3e1e4e..17c895762c400bf6549dbdb1a31abebc42520391 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/model/MegolmBackupAuthData.kt @@ -51,7 +51,7 @@ data class MegolmBackupAuthData( * userId -> (deviceSignKeyId -> signature) */ @Json(name = "signatures") - val signatures: Map<String, Map<String, String>> + val signatures: Map<String, Map<String, String>>? = null ) { fun toJsonDict(): JsonDict { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt index ad3bc012dfb16381c0b738cb725939de96a0fa41..e6d8b5e84f246d5ee171184875fa3cf54b403214 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.secrets import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService @@ -44,7 +45,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey import org.matrix.android.sdk.internal.crypto.tools.HkdfSha256 import org.matrix.android.sdk.internal.crypto.tools.withOlmDecryption import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.olm.OlmPkMessage import java.security.SecureRandom import javax.crypto.Cipher diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 238d06738ccabaab272c75f54cac3411d78ca2e5..9b75f88f917316491252f313bf5729455a3d97d7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -379,7 +379,8 @@ internal interface IMXCryptoStore { fun getOrAddOutgoingSecretShareRequest(secretName: String, recipients: Map<String, List<String>>): OutgoingSecretRequest? - fun saveGossipingEvent(event: Event) + fun saveGossipingEvent(event: Event) = saveGossipingEvents(listOf(event)) + fun saveGossipingEvents(events: List<Event>) fun updateGossipingRequestState(request: IncomingShareRequestCommon, state: GossipingRequestState) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 3c8f74d419e78b6f9213d4a8e9feec9b35e5c876..40678a6ce6415782003706c9a5b9abb6a116dbe0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -25,6 +25,7 @@ import io.realm.Realm import io.realm.RealmConfiguration import io.realm.Sort import io.realm.kotlin.where +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.send.SendState @@ -100,6 +101,8 @@ import org.matrix.olm.OlmAccount import org.matrix.olm.OlmException import org.matrix.olm.OlmOutboundGroupSession import timber.log.Timber +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit import javax.inject.Inject import kotlin.collections.set @@ -137,8 +140,11 @@ internal class RealmCryptoStore @Inject constructor( newSessionListeners.remove(listener) } + private val monarchyWriteAsyncExecutor = Executors.newSingleThreadExecutor() + private val monarchy = Monarchy.Builder() .setRealmConfiguration(realmConfiguration) + .setWriteAsyncExecutor(monarchyWriteAsyncExecutor) .build() init { @@ -199,6 +205,14 @@ internal class RealmCryptoStore @Inject constructor( } override fun close() { + // Ensure no async request will be run later + val tasks = monarchyWriteAsyncExecutor.shutdownNow() + Timber.w("Closing RealmCryptoStore, ${tasks.size} async task(s) cancelled") + tryOrNull("Interrupted") { + // Wait 1 minute max + monarchyWriteAsyncExecutor.awaitTermination(1, TimeUnit.MINUTES) + } + olmSessionsToRelease.forEach { it.value.olmSession.releaseSession() } @@ -1163,8 +1177,8 @@ internal class RealmCryptoStore @Inject constructor( } override fun saveGossipingEvents(events: List<Event>) { - val now = System.currentTimeMillis() monarchy.writeAsync { realm -> + val now = System.currentTimeMillis() events.forEach { event -> val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now val entity = GossipingEventEntity( @@ -1182,23 +1196,6 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun saveGossipingEvent(event: Event) { - monarchy.writeAsync { realm -> - val now = System.currentTimeMillis() - val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now - val entity = GossipingEventEntity( - type = event.type, - sender = event.senderId, - ageLocalTs = ageLocalTs, - content = ContentMapper.map(event.content) - ).apply { - sendState = SendState.SYNCED - decryptionResultJson = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult) - decryptionErrorCode = event.mCryptoError?.name - } - realm.insertOrUpdate(entity) - } - } // override fun getOutgoingRoomKeyRequestByState(states: Set<ShareRequestState>): OutgoingRoomKeyRequest? { // val statesIndex = states.map { it.ordinal }.toTypedArray() // return doRealmQueryAndCopy(realmConfiguration) { realm -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt index 768109979d3c37b9c1c5f2a429695ab1b819822f..388ecb965952802695fff2d543fa7b8347599a9d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt @@ -21,6 +21,7 @@ import android.os.Looper import dagger.Lazy import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME @@ -83,7 +84,6 @@ import org.matrix.android.sdk.internal.di.DeviceId import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import timber.log.Timber import java.util.UUID import javax.inject.Inject 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 aa96ca5e1a99078e2ba74c90430a77673d575b0a..05137f810529f83b80799266739c3b66370ff376 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 @@ -35,19 +35,21 @@ import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityField import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields import org.matrix.android.sdk.internal.database.model.RoomEntityFields +import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.query.process import timber.log.Timber internal object RealmSessionStoreMigration : RealmMigration { - const val SESSION_STORE_SCHEMA_VERSION = 17L + const val SESSION_STORE_SCHEMA_VERSION = 18L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Session from $oldVersion to $newVersion") @@ -69,6 +71,7 @@ internal object RealmSessionStoreMigration : RealmMigration { if (oldVersion <= 14) migrateTo15(realm) if (oldVersion <= 15) migrateTo16(realm) if (oldVersion <= 16) migrateTo17(realm) + if (oldVersion <= 17) migrateTo18(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -338,4 +341,27 @@ internal object RealmSessionStoreMigration : RealmMigration { realm.schema.get("EventInsertEntity") ?.addField(EventInsertEntityFields.CAN_BE_PROCESSED, Boolean::class.java) } + + private fun migrateTo18(realm: DynamicRealm) { + Timber.d("Step 17 -> 18") + realm.schema.create("UserPresenceEntity") + ?.addField(UserPresenceEntityFields.USER_ID, String::class.java) + ?.addPrimaryKey(UserPresenceEntityFields.USER_ID) + ?.setRequired(UserPresenceEntityFields.USER_ID, true) + ?.addField(UserPresenceEntityFields.PRESENCE_STR, String::class.java) + ?.addField(UserPresenceEntityFields.LAST_ACTIVE_AGO, Long::class.java) + ?.setNullable(UserPresenceEntityFields.LAST_ACTIVE_AGO, true) + ?.addField(UserPresenceEntityFields.STATUS_MESSAGE, String::class.java) + ?.addField(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, Boolean::class.java) + ?.setNullable(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, true) + ?.addField(UserPresenceEntityFields.AVATAR_URL, String::class.java) + ?.addField(UserPresenceEntityFields.DISPLAY_NAME, String::class.java) + + val userPresenceEntity = realm.schema.get("UserPresenceEntity") ?: return + realm.schema.get("RoomSummaryEntity") + ?.addRealmObjectField(RoomSummaryEntityFields.DIRECT_USER_PRESENCE.`$`, userPresenceEntity) + + realm.schema.get("RoomMemberSummaryEntity") + ?.addRealmObjectField(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`, userPresenceEntity) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomMemberSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomMemberSummaryMapper.kt index 2365a39567ab6a544b2e57a0407ccd3cae461d59..efd9b68011fe95a47f91a2a203ac6d5ce5dada64 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomMemberSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomMemberSummaryMapper.kt @@ -18,12 +18,14 @@ package org.matrix.android.sdk.internal.database.mapper import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.presence.toUserPresence internal object RoomMemberSummaryMapper { fun map(roomMemberSummaryEntity: RoomMemberSummaryEntity): RoomMemberSummary { return RoomMemberSummary( userId = roomMemberSummaryEntity.userId, + userPresence = roomMemberSummaryEntity.userPresenceEntity?.toUserPresence(), avatarUrl = roomMemberSummaryEntity.avatarUrl, displayName = roomMemberSummaryEntity.displayName, membership = roomMemberSummaryEntity.membership diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 0cf431c340c0809a64ac23576084597b2fdffa27..5900ef6b76dcf840d9f3f7974ac7525df07aa6a9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.SpaceParentInfo import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.presence.toUserPresence import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker import javax.inject.Inject @@ -48,6 +49,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa joinRules = roomSummaryEntity.joinRules, isDirect = roomSummaryEntity.isDirect, directUserId = roomSummaryEntity.directUserId, + directUserPresence = roomSummaryEntity.directUserPresence?.toUserPresence(), latestPreviewableEvent = latestEvent, joinedMembersCount = roomSummaryEntity.joinedMembersCount, invitedMembersCount = roomSummaryEntity.invitedMembersCount, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt index 75771ff12c7fded14eb2699b81f8714b823b9025..a8a76d16811f661fcbf4f2ce2bbc9b8a9a43aa33 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt @@ -21,6 +21,7 @@ import io.realm.annotations.Index import io.realm.annotations.PrimaryKey import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.util.MatrixItem +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String = "", @Index var userId: String = "", @@ -40,6 +41,11 @@ internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String = membershipStr = value.name } + var userPresenceEntity: UserPresenceEntity? = null + set(value) { + if (value != field) field = value + } + fun toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl) companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index 64dc08e8274d1e98bd201a363a8a3eda6d14f38d..88b8886936a2802ff9ff8527f47443beb3bad19d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.VersioningState import org.matrix.android.sdk.api.session.room.model.tag.RoomTag +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity internal open class RoomSummaryEntity( @PrimaryKey var roomId: String = "", @@ -204,6 +205,11 @@ internal open class RoomSummaryEntity( if (value != field) field = value } + var directUserPresence: UserPresenceEntity? = null + set(value) { + if (value != field) field = value + } + var hasFailedSending: Boolean = false set(value) { if (value != field) field = value diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index 19472e21d96838372b3fa283d3dee5ca23547b9d..c090777972194dc6cef0f40f286bb796f6f7d1a4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.database.model import io.realm.annotations.RealmModule +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity /** * Realm module for Session @@ -64,6 +65,7 @@ import io.realm.annotations.RealmModule WellknownIntegrationManagerConfigEntity::class, RoomAccountDataEntity::class, SpaceChildSummaryEntity::class, - SpaceParentSummaryEntity::class + SpaceParentSummaryEntity::class, + UserPresenceEntity::class ]) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt new file mode 100644 index 0000000000000000000000000000000000000000..5713337ec574fc9893ec2e91ee44ecca4259e9e8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/presence/UserPresenceEntity.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2021 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.presence + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum +import org.matrix.android.sdk.api.session.presence.model.UserPresence + +internal open class UserPresenceEntity(@PrimaryKey var userId: String = "", + var lastActiveAgo: Long? = null, + var statusMessage: String? = null, + var isCurrentlyActive: Boolean? = null, + var avatarUrl: String? = null, + var displayName: String? = null +) : RealmObject() { + + var presence: PresenceEnum + get() { + return PresenceEnum.valueOf(presenceStr) + } + set(value) { + presenceStr = value.name + } + + private var presenceStr: String = PresenceEnum.UNAVAILABLE.name + + companion object +} + +internal fun UserPresenceEntity.toUserPresence() = + UserPresence( + lastActiveAgo, + statusMessage, + isCurrentlyActive, + presence + ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomMemberEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomMemberEntityQueries.kt index a19a9cf725c33947eee634033a2d5232b5360bce..1ea06b9dfbc6a47c502d8edc913858cb28608dd0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomMemberEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomMemberEntityQueries.kt @@ -21,6 +21,7 @@ import io.realm.RealmQuery import io.realm.kotlin.where import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity internal fun RoomMemberSummaryEntity.Companion.where(realm: Realm, roomId: String, userId: String? = null): RealmQuery<RoomMemberSummaryEntity> { val query = realm @@ -32,3 +33,13 @@ internal fun RoomMemberSummaryEntity.Companion.where(realm: Realm, roomId: Strin } return query } + +internal fun RoomMemberSummaryEntity.Companion.updateUserPresence(realm: Realm, userId: String, userPresenceEntity: UserPresenceEntity) { + realm.where<RoomMemberSummaryEntity>() + .equalTo(RoomMemberSummaryEntityFields.USER_ID, userId) + .isNull(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`) + .findAll() + .map { + it.userPresenceEntity = userPresenceEntity + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt index 5294f849afb077d91ad45dbbabb47c5835267665..d1b05a4932c2dc70e7028dec264a82fe292b230f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt @@ -23,6 +23,7 @@ import io.realm.kotlin.createObject import io.realm.kotlin.where 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.model.presence.UserPresenceEntity internal fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery<RoomSummaryEntity> { val query = realm.where<RoomSummaryEntity>() @@ -67,3 +68,11 @@ internal fun RoomSummaryEntity.Companion.isDirect(realm: Realm, roomId: String): .findAll() .isNotEmpty() } + +internal fun RoomSummaryEntity.Companion.updateDirectUserPresence(realm: Realm, directUserId: String, userPresenceEntity: UserPresenceEntity) { + RoomSummaryEntity.where(realm) + .equalTo(RoomSummaryEntityFields.IS_DIRECT, true) + .equalTo(RoomSummaryEntityFields.DIRECT_USER_ID, directUserId) + .findFirst() + ?.directUserPresence = userPresenceEntity +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/UserPresenceEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/UserPresenceEntityQueries.kt new file mode 100644 index 0000000000000000000000000000000000000000..22790b6f4fe9aadc6b97c46bda938d4c95474d27 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/UserPresenceEntityQueries.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2021 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.RealmQuery +import io.realm.kotlin.where +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields + +internal fun UserPresenceEntity.Companion.where(realm: Realm, userId: String): RealmQuery<UserPresenceEntity> { + return realm + .where<UserPresenceEntity>() + .equalTo(UserPresenceEntityFields.USER_ID, userId) +} 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 5bc519e96049aede5d142aff61e37cb462069f28..81a067f2c010a2898551a4ad9272dafb04080f0d 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 @@ -24,6 +24,7 @@ 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.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.raw.RawService @@ -35,7 +36,6 @@ 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 org.matrix.android.sdk.internal.util.system.SystemModule import org.matrix.olm.OlmManager import java.io.File diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt index 4cd960f4264b724fb8fef7f4c6420b8241c64bef..9cab307c6153638cffcb76e84279b5346cc71a78 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MatrixModule.kt @@ -23,7 +23,7 @@ import dagger.Provides import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.createBackgroundHandler import org.matrix.olm.OlmManager import java.io.File diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/LiveData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/LiveData.kt index 718e7869ddf5d3e3fd5d0897fedebd5038d54d60..fecbb874d0b798d20896c9203c735ae94eca1803 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/LiveData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/LiveData.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.extensions import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.Observer inline fun <T> LiveData<T>.observeK(owner: LifecycleOwner, crossinline observer: (T?) -> Unit) { @@ -27,3 +28,25 @@ inline fun <T> LiveData<T>.observeK(owner: LifecycleOwner, crossinline observer: inline fun <T> LiveData<T>.observeNotNull(owner: LifecycleOwner, crossinline observer: (T) -> Unit) { this.observe(owner, Observer { it?.run(observer) }) } + +fun <T1, T2, R> combineLatest(source1: LiveData<T1>, source2: LiveData<T2>, mapper: (T1, T2) -> R): LiveData<R> { + val combined = MediatorLiveData<R>() + var source1Result: T1? = null + var source2Result: T2? = null + + fun notify() { + if (source1Result != null && source2Result != null) { + combined.value = mapper(source1Result!!, source2Result!!) + } + } + + combined.addSource(source1) { + source1Result = it + notify() + } + combined.addSource(source2) { + source2Result = it + notify() + } + return combined +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt new file mode 100644 index 0000000000000000000000000000000000000000..5cd2d880003ba2caf9d6f956b11d60c587e58e7e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestExecutor.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2021 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.network +import org.matrix.android.sdk.internal.network.executeRequest as internalExecuteRequest + +internal interface RequestExecutor { + suspend fun <DATA> executeRequest(globalErrorReceiver: GlobalErrorReceiver?, + canRetry: Boolean = false, + maxDelayBeforeRetry: Long = 32_000L, + maxRetriesCount: Int = 4, + requestBlock: suspend () -> DATA): DATA +} + +internal object DefaultRequestExecutor : RequestExecutor { + override suspend fun <DATA> executeRequest(globalErrorReceiver: GlobalErrorReceiver?, + canRetry: Boolean, + maxDelayBeforeRetry: Long, + maxRetriesCount: Int, + requestBlock: suspend () -> DATA): DATA { + return internalExecuteRequest(globalErrorReceiver, canRetry, maxDelayBeforeRetry, maxRetriesCount, requestBlock) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..7b2bb9fcba338d0a4922fa31b5eb9834bdcdd8c0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RequestModule.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2021 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.network + +import dagger.Module +import dagger.Provides + +@Module +internal object RequestModule { + + @Provides + fun providesRequestExecutor(): RequestExecutor { + return DefaultRequestExecutor + } +} 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 414c018074dad12c116ad4c4594a47d1fd14dc5e..46c596787652d6a245744c44602dd7d78e46d056 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 @@ -25,6 +25,7 @@ import kotlinx.coroutines.completeWith import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.file.FileService @@ -33,7 +34,6 @@ import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.file.AtomicFileCreator import org.matrix.android.sdk.internal.util.md5 import org.matrix.android.sdk.internal.util.writeToFile 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 d41bf8a70214bf496caef00874fe20f021af9224..6a6bce5ce28e2f5b14940fa1afef164ae0d49c10 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 @@ -22,6 +22,7 @@ import io.realm.RealmConfiguration import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.withContext import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.federation.FederationService @@ -45,6 +46,7 @@ import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerS import org.matrix.android.sdk.api.session.media.MediaService import org.matrix.android.sdk.api.session.openid.OpenIdService import org.matrix.android.sdk.api.session.permalinks.PermalinkService +import org.matrix.android.sdk.api.session.presence.PresenceService import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.pushers.PushersService import org.matrix.android.sdk.api.session.room.RoomDirectoryService @@ -86,6 +88,7 @@ internal class DefaultSession @Inject constructor( private val globalErrorHandler: GlobalErrorHandler, @SessionId override val sessionId: String, + override val coroutineDispatchers: MatrixCoroutineDispatchers, @SessionDatabase private val realmConfiguration: RealmConfiguration, private val lifecycleObservers: Set<@JvmSuppressWildcards SessionLifecycleObserver>, private val sessionListeners: SessionListeners, @@ -127,6 +130,7 @@ internal class DefaultSession @Inject constructor( private val callSignalingService: Lazy<CallSignalingService>, private val spaceService: Lazy<SpaceService>, private val openIdService: Lazy<OpenIdService>, + private val presenceService: Lazy<PresenceService>, @UnauthenticatedWithCertificate private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient> ) : Session, @@ -145,6 +149,7 @@ internal class DefaultSession @Inject constructor( SecureStorageService by secureStorageService.get(), HomeServerCapabilitiesService by homeServerCapabilitiesService.get(), ProfileService by profileService.get(), + PresenceService by presenceService.get(), AccountService by accountService.get() { override val sharedSecretStorageService: SharedSecretStorageService diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt index 71031a4614ec8c1315c06df0d777780ad284822b..bc8a70753086901243df739a70aef66e6f97432d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session import dagger.BindsInstance import dagger.Component +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.internal.crypto.CancelGossipRequestWorker @@ -29,6 +30,7 @@ import org.matrix.android.sdk.internal.crypto.verification.SendVerificationMessa import org.matrix.android.sdk.internal.di.MatrixComponent import org.matrix.android.sdk.internal.federation.FederationModule import org.matrix.android.sdk.internal.network.NetworkConnectivityChecker +import org.matrix.android.sdk.internal.network.RequestModule import org.matrix.android.sdk.internal.session.account.AccountModule import org.matrix.android.sdk.internal.session.cache.CacheModule import org.matrix.android.sdk.internal.session.call.CallModule @@ -42,6 +44,7 @@ import org.matrix.android.sdk.internal.session.identity.IdentityModule import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerModule import org.matrix.android.sdk.internal.session.media.MediaModule import org.matrix.android.sdk.internal.session.openid.OpenIdModule +import org.matrix.android.sdk.internal.session.presence.di.PresenceModule import org.matrix.android.sdk.internal.session.profile.ProfileModule import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker import org.matrix.android.sdk.internal.session.pushers.PushersModule @@ -62,7 +65,6 @@ import org.matrix.android.sdk.internal.session.user.UserModule import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataModule import org.matrix.android.sdk.internal.session.widgets.WidgetModule import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.system.SystemModule @Component(dependencies = [MatrixComponent::class], @@ -94,7 +96,9 @@ import org.matrix.android.sdk.internal.util.system.SystemModule CallModule::class, SearchModule::class, ThirdPartyModule::class, - SpaceModule::class + SpaceModule::class, + PresenceModule::class, + RequestModule::class ] ) @SessionScope diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt index d4374e0702f896310adf80bd4394df1d899ae570..e8d3eb1a78d40411f122808be5d6a5077322ad24 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session.cleanup import io.realm.Realm import io.realm.RealmConfiguration -import org.matrix.android.sdk.BuildConfig +import kotlinx.coroutines.delay import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.auth.SessionParamsStore import org.matrix.android.sdk.internal.crypto.CryptoModule @@ -51,37 +51,56 @@ internal class CleanupSession @Inject constructor( @UserMd5 private val userMd5: String ) { suspend fun handle() { + val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration) + val cryptoRealmCount = Realm.getGlobalInstanceCount(realmCryptoConfiguration) + Timber.d("Realm instance ($sessionRealmCount - $cryptoRealmCount)") + Timber.d("Cleanup: delete session params...") sessionParamsStore.delete(sessionId) Timber.d("Cleanup: cancel pending works...") workManagerProvider.cancelAllWorks() + Timber.d("Cleanup: release session...") + sessionManager.releaseSession(sessionId) + Timber.d("Cleanup: clear session data...") clearSessionDataTask.execute(Unit) Timber.d("Cleanup: clear crypto data...") clearCryptoDataTask.execute(Unit) - Timber.d("Cleanup: clear file system") - sessionFiles.deleteRecursively() - sessionCache.deleteRecursively() - Timber.d("Cleanup: clear the database keys") realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5)) realmKeysUtils.clear(CryptoModule.getKeyAlias(userMd5)) - Timber.d("Cleanup: release session...") - sessionManager.releaseSession(sessionId) + // Wait for all the Realm instance to be released properly. Closing Realm instance is async. + // After that we can safely delete the Realm files + waitRealmRelease() + + Timber.d("Cleanup: clear file system") + sessionFiles.deleteRecursively() + sessionCache.deleteRecursively() + } + + private suspend fun waitRealmRelease() { + var timeToWaitMillis = MAX_TIME_TO_WAIT_MILLIS + do { + val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration) + val cryptoRealmCount = Realm.getGlobalInstanceCount(realmCryptoConfiguration) + Timber.d("Wait for all Realm instance to be closed ($sessionRealmCount - $cryptoRealmCount)") + if (sessionRealmCount > 0 || cryptoRealmCount > 0) { + Timber.d("Waiting ${TIME_TO_WAIT_MILLIS}ms") + delay(TIME_TO_WAIT_MILLIS) + timeToWaitMillis -= TIME_TO_WAIT_MILLIS + } else { + timeToWaitMillis = 0 + } + } while (timeToWaitMillis > 0) + } - // Sanity check - if (BuildConfig.DEBUG) { - Realm.getGlobalInstanceCount(realmSessionConfiguration) - .takeIf { it > 0 } - ?.let { Timber.e("All realm instance for session has not been closed ($it)") } - Realm.getGlobalInstanceCount(realmCryptoConfiguration) - .takeIf { it > 0 } - ?.let { Timber.e("All realm instance for crypto has not been closed ($it)") } - } + companion object { + private const val MAX_TIME_TO_WAIT_MILLIS = 10_000L + private const val TIME_TO_WAIT_MILLIS = 10L } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt index 9b01d0a00e033fa6976c79dba71acdcdd0a17cf9..01eb52ff2270e875444f6ae7c4e18c583c6a4fbc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageCompressor.kt @@ -20,22 +20,23 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Matrix import androidx.exifinterface.media.ExifInterface -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.TemporaryFileCreator import timber.log.Timber import java.io.File import javax.inject.Inject internal class ImageCompressor @Inject constructor( - private val temporaryFileCreator: TemporaryFileCreator + private val temporaryFileCreator: TemporaryFileCreator, + private val coroutineDispatchers: MatrixCoroutineDispatchers ) { suspend fun compress( imageFile: File, desiredWidth: Int, desiredHeight: Int, desiredQuality: Int = 80): File { - return withContext(Dispatchers.IO) { + return withContext(coroutineDispatchers.io) { val compressedBitmap = BitmapFactory.Options().run { inJustDecodeBounds = true decodeBitmap(imageFile, this) @@ -52,6 +53,8 @@ internal class ImageCompressor @Inject constructor( destinationFile.outputStream().use { compressedBitmap.compress(Bitmap.CompressFormat.JPEG, desiredQuality, it) } + }.onFailure { + return@withContext imageFile } destinationFile diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt new file mode 100644 index 0000000000000000000000000000000000000000..239a768498500f298815e7d1e602163f58d3f919 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/ImageExifTagRemover.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.content + +import kotlinx.coroutines.withContext +import org.apache.sanselan.Sanselan +import org.apache.sanselan.formats.jpeg.JpegImageMetadata +import org.apache.sanselan.formats.jpeg.exifRewrite.ExifRewriter +import org.apache.sanselan.formats.tiff.constants.ExifTagConstants +import org.apache.sanselan.formats.tiff.constants.GPSTagConstants +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.internal.util.TemporaryFileCreator +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileOutputStream +import javax.inject.Inject + +/** + * This class is responsible for removing Exif tags from image files + */ + +internal class ImageExifTagRemover @Inject constructor( + private val temporaryFileCreator: TemporaryFileCreator, + private val coroutineDispatchers: MatrixCoroutineDispatchers +) { + + /** + * Remove sensitive exif tags from a jpeg image file. + * Scrubbing exif tags like GPS location and user comments + * @param jpegImageFile The image file to be scrubbed + * @return the new scrubbed image file, or the original file if the operation failed + */ + suspend fun removeSensitiveJpegExifTags(jpegImageFile: File): File = withContext(coroutineDispatchers.io) { + val outputSet = tryOrNull("Unable to read JpegImageMetadata") { + (Sanselan.getMetadata(jpegImageFile) as? JpegImageMetadata)?.exif?.outputSet + } ?: return@withContext jpegImageFile + + tryOrNull("Unable to remove ExifData") { + outputSet.removeField(ExifTagConstants.EXIF_TAG_GPSINFO) + outputSet.removeField(ExifTagConstants.EXIF_TAG_SUBJECT_LOCATION_1) + outputSet.removeField(ExifTagConstants.EXIF_TAG_SUBJECT_LOCATION_2) + outputSet.removeField(ExifTagConstants.EXIF_TAG_USER_COMMENT) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_ALTITUDE) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_ALTITUDE_REF) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_LONGITUDE) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_LONGITUDE_REF) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_DEST_LONGITUDE) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_DEST_LONGITUDE_REF) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_LATITUDE) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_LATITUDE_REF) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_DEST_LATITUDE) + outputSet.removeField(GPSTagConstants.GPS_TAG_GPS_DEST_LATITUDE_REF) + } ?: return@withContext jpegImageFile + + val scrubbedFile = temporaryFileCreator.create() + return@withContext runCatching { + FileOutputStream(scrubbedFile).use { fos -> + val outputStream = BufferedOutputStream(fos) + ExifRewriter().updateExifMetadataLossless(jpegImageFile, outputStream, outputSet) + } + }.fold( + onSuccess = { + scrubbedFile + }, + onFailure = { + scrubbedFile.delete() + jpegImageFile + } + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt index 11c200c54b0e975cad26e08be28efda549ccae96..0c5a90ca60cb2df91fc878c11e9219c1d18039a7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt @@ -64,7 +64,7 @@ private data class NewAttachmentAttributes( * Possible next worker : Always [MultipleEventSendingDispatcherWorker] */ internal class UploadContentWorker(val context: Context, params: WorkerParameters) : - SessionSafeCoroutineWorker<UploadContentWorker.Params>(context, params, Params::class.java) { + SessionSafeCoroutineWorker<UploadContentWorker.Params>(context, params, Params::class.java) { @JsonClass(generateAdapter = true) internal data class Params( @@ -81,6 +81,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter @Inject lateinit var fileService: DefaultFileService @Inject lateinit var cancelSendTracker: CancelSendTracker @Inject lateinit var imageCompressor: ImageCompressor + @Inject lateinit var imageExitTagRemover: ImageExifTagRemover @Inject lateinit var videoCompressor: VideoCompressor @Inject lateinit var thumbnailExtractor: ThumbnailExtractor @Inject lateinit var localEchoRepository: LocalEchoRepository @@ -114,7 +115,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter } val attachment = params.attachment - val filesToDelete = mutableListOf<File>() + val filesToDelete = hashSetOf<File>() return try { val inputStream = context.contentResolver.openInputStream(attachment.queryUri) @@ -219,6 +220,10 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter } } } + } else if (attachment.type == ContentAttachmentData.Type.IMAGE && !params.compressBeforeSending) { + fileToUpload = imageExitTagRemover.removeSensitiveJpegExifTags(workingFile) + .also { filesToDelete.add(it) } + newAttachmentAttributes = newAttachmentAttributes.copy(newFileSize = fileToUpload.length()) } else { fileToUpload = workingFile // Fix: OpenableColumns.SIZE may return -1 or 0 @@ -291,6 +296,11 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter filesToDelete.forEach { tryOrNull { it.delete() } } + + // Delete the temporary voice message file + if (params.attachment.type == ContentAttachmentData.Type.AUDIO && params.attachment.mimeType == MimeTypes.Ogg) { + context.contentResolver.delete(params.attachment.queryUri, null, null) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt index da37948cd4fb95f3682176937943db0d91ab44c5..37d9a4e74f8d649576ca9ee7583721d672ac6e72 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.LifecycleRegistry import dagger.Lazy import kotlinx.coroutines.withContext import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull @@ -51,7 +52,6 @@ import org.matrix.android.sdk.internal.session.profile.UnbindThreePidsTask import org.matrix.android.sdk.internal.session.sync.model.accountdata.IdentityServerContent import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.ensureProtocol import timber.log.Timber import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt new file mode 100644 index 0000000000000000000000000000000000000000..53d0d5e9631e8f7599545cc272e22e8866b48574 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/PresenceAPI.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.presence + +import org.matrix.android.sdk.internal.network.NetworkConstants +import org.matrix.android.sdk.internal.session.presence.model.GetPresenceResponse +import org.matrix.android.sdk.internal.session.presence.model.SetPresenceBody +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.PUT +import retrofit2.http.Path + +internal interface PresenceAPI { + + /** + * Set the presence status of the current user + * Ref: https://matrix.org/docs/spec/client_server/latest#put-matrix-client-r0-presence-userid-status + */ + @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "presence/{userId}/status") + suspend fun setPresence(@Path("userId") userId: String, + @Body body: SetPresenceBody) + + /** + * Get the given user's presence state. + * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-presence-userid-status + */ + @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "presence/{userId}/status") + suspend fun getPresence(@Path("userId") userId: String): GetPresenceResponse +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/di/PresenceModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/di/PresenceModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..6b2ee76046d053bcffd7549675e10687aa226dd3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/di/PresenceModule.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.presence.di + +import dagger.Binds +import dagger.Module +import dagger.Provides +import org.matrix.android.sdk.api.session.presence.PresenceService +import org.matrix.android.sdk.internal.session.SessionScope +import org.matrix.android.sdk.internal.session.presence.PresenceAPI +import org.matrix.android.sdk.internal.session.presence.service.DefaultPresenceService +import org.matrix.android.sdk.internal.session.presence.service.task.DefaultGetPresenceTask +import org.matrix.android.sdk.internal.session.presence.service.task.DefaultSetPresenceTask +import org.matrix.android.sdk.internal.session.presence.service.task.GetPresenceTask +import org.matrix.android.sdk.internal.session.presence.service.task.SetPresenceTask +import retrofit2.Retrofit + +@Module +internal abstract class PresenceModule { + + @Module + companion object { + @Provides + @JvmStatic + @SessionScope + fun providesPresenceAPI(retrofit: Retrofit): PresenceAPI { + return retrofit.create(PresenceAPI::class.java) + } + } + + @Binds + abstract fun bindPresenceService(service: DefaultPresenceService): PresenceService + + @Binds + abstract fun bindSetPresenceTask(task: DefaultSetPresenceTask): SetPresenceTask + + @Binds + abstract fun bindGetPresenceTask(task: DefaultGetPresenceTask): GetPresenceTask +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt new file mode 100644 index 0000000000000000000000000000000000000000..a7552f7b027900f4e3cdc524d795a1013cee9185 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/GetPresenceResponse.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.presence.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum + +@JsonClass(generateAdapter = true) +data class GetPresenceResponse( + @Json(name = "presence") + val presence: PresenceEnum, + @Json(name = "last_active_ago") + val lastActiveAgo: Long? = null, + @Json(name = "status_msg") + val message: String? = null, + @Json(name = "currently_active") + val isCurrentlyActive: Boolean? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt new file mode 100644 index 0000000000000000000000000000000000000000..45e0fcf06efa07c175568aea621d679e415e2386 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/PresenceContent.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.presence.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum + +/** + * Class representing the EventType.PRESENCE event content + */ +@JsonClass(generateAdapter = true) +data class PresenceContent( + /** + * Required. The presence state for this user. One of: ["online", "offline", "unavailable"] + */ + @Json(name = "presence") val presence: PresenceEnum, + /** + * The last time since this used performed some action, in milliseconds. + */ + @Json(name = "last_active_ago") val lastActiveAgo: Long? = null, + /** + * An optional description to accompany the presence. + */ + @Json(name = "status_msg") val statusMessage: String? = null, + /** + * Whether the user is currently active + */ + @Json(name = "currently_active") val isCurrentlyActive: Boolean = false, + /** + * The current avatar URL for this user, if any. + */ + @Json(name = "avatar_url") val avatarUrl: String? = null, + /** + * The current display name for this user, if any. + */ + @Json(name = "displayname") val displayName: String? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt new file mode 100644 index 0000000000000000000000000000000000000000..0c81791ec0f8482498f5138bf87a605d215ba89e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/model/SetPresenceBody.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.matrix.android.sdk.internal.session.presence.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum + +@JsonClass(generateAdapter = true) +internal data class SetPresenceBody( + @Json(name = "presence") + val presence: PresenceEnum, + @Json(name = "status_msg") + val statusMsg: String? +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt new file mode 100644 index 0000000000000000000000000000000000000000..1083d5b4c2e4878cb56a084222d9fc695c0e4f96 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/DefaultPresenceService.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.matrix.android.sdk.internal.session.presence.service + +import org.matrix.android.sdk.api.session.presence.PresenceService +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum +import org.matrix.android.sdk.api.session.presence.model.UserPresence +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.presence.service.task.GetPresenceTask +import org.matrix.android.sdk.internal.session.presence.service.task.SetPresenceTask +import javax.inject.Inject + +internal class DefaultPresenceService @Inject constructor( + @UserId private val userId: String, + private val setPresenceTask: SetPresenceTask, + private val getPresenceTask: GetPresenceTask +) : PresenceService { + + override suspend fun setMyPresence(presence: PresenceEnum, statusMsg: String?) { + setPresenceTask.execute(SetPresenceTask.Params(userId, presence, statusMsg)) + } + + override suspend fun fetchPresence(userId: String): UserPresence { + val result = getPresenceTask.execute(GetPresenceTask.Params(userId)) + + return UserPresence( + lastActiveAgo = result.lastActiveAgo, + statusMessage = result.message, + isCurrentlyActive = result.isCurrentlyActive, + presence = result.presence + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/GetPresenceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/GetPresenceTask.kt new file mode 100644 index 0000000000000000000000000000000000000000..bb628dbab426bbf1f039ee8c262d1c10c68cb17d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/GetPresenceTask.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.matrix.android.sdk.internal.session.presence.service.task + +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.presence.PresenceAPI +import org.matrix.android.sdk.internal.session.presence.model.GetPresenceResponse +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal abstract class GetPresenceTask : Task<GetPresenceTask.Params, GetPresenceResponse> { + data class Params( + val userId: String + ) +} + +internal class DefaultGetPresenceTask @Inject constructor( + private val presenceAPI: PresenceAPI, + private val globalErrorReceiver: GlobalErrorReceiver +) : GetPresenceTask() { + override suspend fun execute(params: Params): GetPresenceResponse { + return executeRequest(globalErrorReceiver) { + presenceAPI.getPresence(params.userId) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt new file mode 100644 index 0000000000000000000000000000000000000000..1b3bdabd3047ee41180670b29d163d159d3039a0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/presence/service/task/SetPresenceTask.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.matrix.android.sdk.internal.session.presence.service.task + +import org.matrix.android.sdk.api.session.presence.model.PresenceEnum +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.presence.PresenceAPI +import org.matrix.android.sdk.internal.session.presence.model.SetPresenceBody +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal abstract class SetPresenceTask : Task<SetPresenceTask.Params, Any> { + data class Params( + val userId: String, + val presence: PresenceEnum, + val statusMsg: String? + ) +} + +internal class DefaultSetPresenceTask @Inject constructor( + private val presenceAPI: PresenceAPI, + private val globalErrorReceiver: GlobalErrorReceiver +) : SetPresenceTask() { + + override suspend fun execute(params: Params): Any { + return executeRequest(globalErrorReceiver) { + val setPresenceBody = SetPresenceBody(params.presence, params.statusMsg) + presenceAPI.setPresence(params.userId, setPresenceBody) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt index 386fec8256f62d8b13fbe71c693c2f1fccf9d9da..a19832c5230dbdb68ce7003edcc12950978a7665 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy import io.realm.kotlin.where import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.profile.ProfileService @@ -35,7 +36,6 @@ import org.matrix.android.sdk.internal.session.content.FileUploader import org.matrix.android.sdk.internal.session.user.UserStore import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import javax.inject.Inject internal class DefaultProfileService @Inject constructor(private val taskExecutor: TaskExecutor, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt new file mode 100644 index 0000000000000000000000000000000000000000..7d81e19265c3eef69921db05e216bd51c72209d3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherTask.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.pushers + +import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.session.pushers.PusherState +import org.matrix.android.sdk.internal.database.mapper.toEntity +import org.matrix.android.sdk.internal.database.model.PusherEntity +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.RequestExecutor +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import javax.inject.Inject + +internal interface AddPusherTask : Task<AddPusherTask.Params, Unit> { + data class Params(val pusher: JsonPusher) +} + +internal class DefaultAddPusherTask @Inject constructor( + private val pushersAPI: PushersAPI, + @SessionDatabase private val monarchy: Monarchy, + private val requestExecutor: RequestExecutor, + private val globalErrorReceiver: GlobalErrorReceiver +) : AddPusherTask { + override suspend fun execute(params: AddPusherTask.Params) { + val pusher = params.pusher + try { + setPusher(pusher) + } catch (error: Throwable) { + monarchy.awaitTransaction { realm -> + PusherEntity.where(realm, pusher.pushKey).findFirst()?.let { + it.state = PusherState.FAILED_TO_REGISTER + } + } + throw error + } + } + + private suspend fun setPusher(pusher: JsonPusher) { + requestExecutor.executeRequest(globalErrorReceiver) { + pushersAPI.setPusher(pusher) + } + monarchy.awaitTransaction { realm -> + val echo = PusherEntity.where(realm, pusher.pushKey).findFirst() + if (echo == null) { + pusher.toEntity().also { + it.state = PusherState.REGISTERED + realm.insertOrUpdate(it) + } + } else { + echo.appDisplayName = pusher.appDisplayName + echo.appId = pusher.appId + echo.kind = pusher.kind + echo.lang = pusher.lang + echo.profileTag = pusher.profileTag + echo.data?.format = pusher.data?.format + echo.data?.url = pusher.data?.url + echo.state = PusherState.REGISTERED + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt index 63fd855c08aae3523e4ce25f8def4787f56fcab8..4df42b2cfbef50715b2614d953bdb3ab86aab181 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPusherWorker.kt @@ -18,17 +18,8 @@ package org.matrix.android.sdk.internal.session.pushers import android.content.Context import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass -import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.session.pushers.PusherState -import org.matrix.android.sdk.internal.database.mapper.toEntity -import org.matrix.android.sdk.internal.database.model.PusherEntity -import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.network.GlobalErrorReceiver -import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.SessionComponent -import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams import javax.inject.Inject @@ -43,9 +34,7 @@ internal class AddPusherWorker(context: Context, params: WorkerParameters) : override val lastFailureMessage: String? = null ) : SessionWorkerParams - @Inject lateinit var pushersAPI: PushersAPI - @Inject @SessionDatabase lateinit var monarchy: Monarchy - @Inject lateinit var globalErrorReceiver: GlobalErrorReceiver + @Inject lateinit var addPusherTask: AddPusherTask override fun injectWith(injector: SessionComponent) { injector.inject(this) @@ -58,20 +47,12 @@ internal class AddPusherWorker(context: Context, params: WorkerParameters) : return Result.failure() } return try { - setPusher(pusher) + addPusherTask.execute(AddPusherTask.Params(pusher)) Result.success() } catch (exception: Throwable) { when (exception) { is Failure.NetworkConnection -> Result.retry() - else -> { - monarchy.awaitTransaction { realm -> - PusherEntity.where(realm, pusher.pushKey).findFirst()?.let { - // update it - it.state = PusherState.FAILED_TO_REGISTER - } - } - Result.failure() - } + else -> Result.failure() } } } @@ -79,29 +60,4 @@ internal class AddPusherWorker(context: Context, params: WorkerParameters) : override fun buildErrorParams(params: Params, message: String): Params { return params.copy(lastFailureMessage = params.lastFailureMessage ?: message) } - - private suspend fun setPusher(pusher: JsonPusher) { - executeRequest(globalErrorReceiver) { - pushersAPI.setPusher(pusher) - } - monarchy.awaitTransaction { realm -> - val echo = PusherEntity.where(realm, pusher.pushKey).findFirst() - if (echo != null) { - // update it - echo.appDisplayName = pusher.appDisplayName - echo.appId = pusher.appId - echo.kind = pusher.kind - echo.lang = pusher.lang - echo.profileTag = pusher.profileTag - echo.data?.format = pusher.data?.format - echo.data?.url = pusher.data?.url - echo.state = PusherState.REGISTERED - } else { - pusher.toEntity().also { - it.state = PusherState.REGISTERED - realm.insertOrUpdate(it) - } - } - } - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt index 9a50abfe3572e3510da9da2dd9a30c0216f1b081..e87c27e601573e5e6a97cba49b238473a58d87bd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt @@ -30,7 +30,6 @@ import org.matrix.android.sdk.internal.session.pushers.gateway.PushGatewayNotify import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.worker.WorkerParamsFactory -import java.security.InvalidParameterException import java.util.UUID import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -41,6 +40,7 @@ internal class DefaultPushersService @Inject constructor( @SessionId private val sessionId: String, private val getPusherTask: GetPushersTask, private val pushGatewayNotifyTask: PushGatewayNotifyTask, + private val addPusherTask: AddPusherTask, private val removePusherTask: RemovePusherTask, private val taskExecutor: TaskExecutor ) : PushersService { @@ -58,51 +58,48 @@ internal class DefaultPushersService @Inject constructor( .executeBy(taskExecutor) } - override fun addHttpPusher(pushkey: String, - appId: String, - profileTag: String, - lang: String, - appDisplayName: String, - deviceDisplayName: String, - url: String, - append: Boolean, - withEventIdOnly: Boolean - ) = addPusher( - JsonPusher( - pushKey = pushkey, - kind = Pusher.KIND_HTTP, - appId = appId, - profileTag = profileTag, - lang = lang, - appDisplayName = appDisplayName, - deviceDisplayName = deviceDisplayName, - data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }), - append = append - ) - ) + override fun enqueueAddHttpPusher(httpPusher: PushersService.HttpPusher): UUID { + return enqueueAddPusher(httpPusher.toJsonPusher()) + } - override fun addEmailPusher(email: String, - lang: String, - emailBranding: String, - appDisplayName: String, - deviceDisplayName: String, - append: Boolean - ) = addPusher( - JsonPusher( - pushKey = email, - kind = Pusher.KIND_EMAIL, - appId = Pusher.APP_ID_EMAIL, - profileTag = "", - lang = lang, - appDisplayName = appDisplayName, - deviceDisplayName = deviceDisplayName, - data = JsonPusherData(brand = emailBranding), - append = append - ) + override suspend fun addHttpPusher(httpPusher: PushersService.HttpPusher) { + addPusherTask.execute(AddPusherTask.Params(httpPusher.toJsonPusher())) + } + + private fun PushersService.HttpPusher.toJsonPusher() = JsonPusher( + pushKey = pushkey, + kind = "http", + appId = appId, + profileTag = profileTag, + lang = lang, + appDisplayName = appDisplayName, + deviceDisplayName = deviceDisplayName, + data = JsonPusherData(url, EVENT_ID_ONLY.takeIf { withEventIdOnly }), + append = append ) - private fun addPusher(pusher: JsonPusher): UUID { - pusher.validateParameters() + override suspend fun addEmailPusher(email: String, + lang: String, + emailBranding: String, + appDisplayName: String, + deviceDisplayName: String, + append: Boolean) { + addPusherTask.execute( + AddPusherTask.Params(JsonPusher( + pushKey = email, + kind = Pusher.KIND_EMAIL, + appId = Pusher.APP_ID_EMAIL, + profileTag = "", + lang = lang, + appDisplayName = appDisplayName, + deviceDisplayName = deviceDisplayName, + data = JsonPusherData(brand = emailBranding), + append = append + )) + ) + } + + private fun enqueueAddPusher(pusher: JsonPusher): UUID { val params = AddPusherWorker.Params(sessionId, pusher) val request = workManagerProvider.matrixOneTimeWorkRequestBuilder<AddPusherWorker>() .setConstraints(WorkManagerProvider.workConstraints) @@ -113,13 +110,6 @@ internal class DefaultPushersService @Inject constructor( return request.id } - private fun JsonPusher.validateParameters() { - // Do some parameter checks. It's ok to throw Exception, to inform developer of the problem - if (pushKey.length > 512) throw InvalidParameterException("pushkey should not exceed 512 chars") - if (appId.length > 64) throw InvalidParameterException("appId should not exceed 64 chars") - data?.url?.let { url -> if ("/_matrix/push/v1/notify" !in url) throw InvalidParameterException("url should contain '/_matrix/push/v1/notify'") } - } - override suspend fun removePusher(pusher: Pusher) { removePusher(pusher.pushKey, pusher.appId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt index a594675e28aa40b79ae804fc1510a3491ab2a9c2..8dc0954694c8e1a3a9bdd59eaa01695cf6f53d15 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/JsonPusher.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.pushers import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import org.matrix.android.sdk.internal.di.SerializeNulls +import java.security.InvalidParameterException /** * Example: @@ -112,4 +113,11 @@ internal data class JsonPusher( */ @Json(name = "append") val append: Boolean? = false -) +) { + init { + // Do some parameter checks. It's ok to throw Exception, to inform developer of the problem + if (pushKey.length > 512) throw InvalidParameterException("pushkey should not exceed 512 chars") + if (appId.length > 64) throw InvalidParameterException("appId should not exceed 64 chars") + data?.url?.let { url -> if ("/_matrix/push/v1/notify" !in url) throw InvalidParameterException("url should contain '/_matrix/push/v1/notify'") } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt index 4030c63514eb16fde9bb1c1104c67cb592b109a5..d53a4eed65462a96b60c8db9a967ef0133d7ebd9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersModule.kt @@ -65,6 +65,9 @@ internal abstract class PushersModule { @Binds abstract fun bindSavePushRulesTask(task: DefaultSavePushRulesTask): SavePushRulesTask + @Binds + abstract fun bindAddPusherTask(task: DefaultAddPusherTask): AddPusherTask + @Binds abstract fun bindRemovePusherTask(task: DefaultRemovePusherTask): RemovePusherTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt index 8afd690f642d82fcf62dcf175dfdd11f90b57d15..cb4bcdb606a24975bc8c49266fe04c06d585ea69 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.room import androidx.lifecycle.LiveData +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.Room @@ -71,7 +72,8 @@ internal class DefaultRoom(override val roomId: String, private val roomVersionService: RoomVersionService, private val sendStateTask: SendStateTask, private val viaParameterFinder: ViaParameterFinder, - private val searchTask: SearchTask + private val searchTask: SearchTask, + override val coroutineDispatchers: MatrixCoroutineDispatchers ) : Room, TimelineService by timelineService, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt index d44eb32529b3954fb8d66938aaf15299550a30f2..4ab06338a2555b06080a4cd4b2fc6858e34c8ee0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.session.room +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.internal.session.SessionScope @@ -66,7 +67,8 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory, private val sendStateTask: SendStateTask, private val viaParameterFinder: ViaParameterFinder, - private val searchTask: SearchTask) : + private val searchTask: SearchTask, + private val coroutineDispatchers: MatrixCoroutineDispatchers) : RoomFactory { override fun create(roomId: String): Room { @@ -92,7 +94,8 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: roomVersionService = roomVersionServiceFactory.create(roomId), sendStateTask = sendStateTask, searchTask = searchTask, - viaParameterFinder = viaParameterFinder + viaParameterFinder = viaParameterFinder, + coroutineDispatchers = coroutineDispatchers ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt index 046f8ba8ba59bba7926729868f69ad7c47e73137..3867e0dc8d3fe6cd589f115593385e46875d31d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt @@ -21,10 +21,10 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.session.room.send.DraftService import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers internal class DefaultDraftService @AssistedInject constructor(@Assisted private val roomId: String, private val draftRepository: DraftRepository, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEntityFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEntityFactory.kt index f78b5d7992b30e687c8699e03c7a221909dc41f6..d6a04de5e7d0b30099b4da0f5c153b4a03196ca1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEntityFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEntityFactory.kt @@ -18,10 +18,11 @@ package org.matrix.android.sdk.internal.session.room.membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity internal object RoomMemberEntityFactory { - fun create(roomId: String, userId: String, roomMember: RoomMemberContent): RoomMemberSummaryEntity { + fun create(roomId: String, userId: String, roomMember: RoomMemberContent, presence: UserPresenceEntity?): RoomMemberSummaryEntity { val primaryKey = "${roomId}_$userId" return RoomMemberSummaryEntity( primaryKey = primaryKey, @@ -31,6 +32,7 @@ internal object RoomMemberEntityFactory { avatarUrl = roomMember.avatarUrl ).apply { membership = roomMember.membership + userPresenceEntity = presence } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt index 7528f80cc2ece1e4d0a65f829d28d9e0b215ff8a..25c124bd6bea067456c2b97471e948fb89ecc814 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt @@ -20,6 +20,9 @@ import io.realm.Realm import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity +import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator @@ -47,7 +50,13 @@ internal class RoomMemberEventHandler @Inject constructor( if (roomMember == null) { return false } - val roomMemberEntity = RoomMemberEntityFactory.create(roomId, userId, roomMember) + val roomMemberEntity = RoomMemberEntityFactory.create( + roomId, + userId, + roomMember, + // When an update is happening, insertOrUpdate replace existing values with null if they are not provided, + // but we want to preserve presence record value and not replace it with null + getExistingPresenceState(realm, roomId, userId)) realm.insertOrUpdate(roomMemberEntity) if (roomMember.membership.isActive()) { val userEntity = UserEntityFactory.create(userId, roomMember) @@ -60,7 +69,15 @@ internal class RoomMemberEventHandler @Inject constructor( if (mxId != null && mxId != myUserId) { aggregator?.directChatsToCheck?.put(roomId, mxId) } - return true } + + /** + * Get the already existing presence state for a specific user & room in order NOT to be replaced in RoomMemberSummaryEntity + * by NULL value. + */ + + private fun getExistingPresenceState(realm: Realm, roomId: String, userId: String): UserPresenceEntity? { + return RoomMemberSummaryEntity.where(realm, roomId, userId).findFirst()?.userPresenceEntity + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt index eb48958afb17c5ef801e9fc702efe1630c02328a..64f1cc34f12896ac21d39c241ae93c2a142e2527 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt @@ -30,8 +30,8 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI -import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler -import org.matrix.android.sdk.internal.session.sync.RoomFullyReadHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import timber.log.Timber 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 c0e428ec85178e1f76e6c0ce5bd9e98287a829b4..0c917448cc4cb8889666a6d8acf46d5445d367a4 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 @@ -42,7 +42,7 @@ import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendState import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask -import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.Debouncer 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 8de36d0427633af6a7fdfbfa150cf0c1df95028d..47e8f7e3a333df8541eaf0d4ce229f6559cb9cfc 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 @@ -37,7 +37,7 @@ 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.session.room.membership.LoadRoomMembersTask -import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.ReadReceiptHandler import org.matrix.android.sdk.internal.task.TaskExecutor internal class DefaultTimelineService @AssistedInject constructor( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt index 8c7401ab475c201f3e23147e1fcbcde9de80069a..335f61962335c3d6965c1d3b2426bfc02baa189e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt @@ -33,6 +33,12 @@ import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker import org.matrix.android.sdk.internal.session.initsync.ProgressReporter import org.matrix.android.sdk.internal.session.initsync.reportSubtask import org.matrix.android.sdk.internal.session.notification.ProcessEventForPushTask +import org.matrix.android.sdk.internal.session.sync.handler.CryptoSyncHandler +import org.matrix.android.sdk.internal.session.sync.handler.GroupSyncHandler +import org.matrix.android.sdk.internal.session.sync.handler.PresenceSyncHandler +import org.matrix.android.sdk.internal.session.sync.handler.SyncResponsePostTreatmentAggregatorHandler +import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.RoomSyncHandler import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import timber.log.Timber @@ -55,7 +61,9 @@ internal class SyncResponseHandler @Inject constructor( private val cryptoService: DefaultCryptoService, private val tokenStore: SyncTokenStore, private val processEventForPushTask: ProcessEventForPushTask, - private val pushRuleService: PushRuleService) { + private val pushRuleService: PushRuleService, + private val presenceSyncHandler: PresenceSyncHandler +) { suspend fun handleResponse(syncResponse: SyncResponse, fromToken: String?, @@ -118,6 +126,13 @@ internal class SyncResponseHandler @Inject constructor( }.also { Timber.v("Finish handling accountData in $it ms") } + + measureTimeMillis { + Timber.v("Handle Presence") + presenceSyncHandler.handle(realm, syncResponse.presence) + }.also { + Timber.v("Finish handling Presence in $it ms") + } tokenStore.saveToken(realm, syncResponse.nextBatch) } @@ -145,7 +160,8 @@ internal class SyncResponseHandler @Inject constructor( private fun dispatchInvitedRoom(roomsSyncResponse: RoomsSyncResponse) { roomsSyncResponse.invite.keys.forEach { roomId -> sessionListeners.dispatch { session, listener -> - listener.onNewInvitedRoom(session, roomId) } + listener.onNewInvitedRoom(session, roomId) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt index cc4ccc2e464689ed5019ae8b389b7f1ca44f1d8f..fe44531390bef1254433fe4c113c3038031ffda1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt @@ -5,7 +5,7 @@ * 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 + * 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, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt similarity index 98% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/CryptoSyncHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt index cec5689a828f5c424c8eb25bf885e7db7f740b4a..c5ec34176c643fbaf682530d69833b0e71b25e12 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.Event diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/GroupSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt similarity index 98% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/GroupSyncHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt index 2b054e578f0ab9f3a85a53aca29b34c8cac3afef..552462e25e69e451edd1533a8451db14a5897701 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/GroupSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/GroupSyncHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler import io.realm.Realm import org.matrix.android.sdk.api.session.initsync.InitSyncStep diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..fe173a35c3855f2993006f091cbd70174cc6281c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/PresenceSyncHandler.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.sync.handler + +import io.realm.Realm +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.getPresenceContent +import org.matrix.android.sdk.api.session.sync.model.PresenceSyncResponse +import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity +import org.matrix.android.sdk.internal.database.query.updateDirectUserPresence +import org.matrix.android.sdk.internal.database.query.updateUserPresence +import javax.inject.Inject + +internal class PresenceSyncHandler @Inject constructor() { + + fun handle(realm: Realm, presenceSyncResponse: PresenceSyncResponse?) { + presenceSyncResponse?.events + ?.filter { event -> event.type == EventType.PRESENCE } + ?.forEach { event -> + val content = event.getPresenceContent() ?: return@forEach + val userId = event.senderId ?: return@forEach + val userPresenceEntity = UserPresenceEntity( + userId = userId, + lastActiveAgo = content.lastActiveAgo, + statusMessage = content.statusMessage, + isCurrentlyActive = content.isCurrentlyActive, + avatarUrl = content.avatarUrl, + displayName = content.displayName + ).also { + it.presence = content.presence + } + + storePresenceToDB(realm, userPresenceEntity) + } + } + + /** + * Store user presence to DB and update Direct Rooms and Room Member Summaries accordingly + */ + private fun storePresenceToDB(realm: Realm, userPresenceEntity: UserPresenceEntity) = + realm.copyToRealmOrUpdate(userPresenceEntity)?.apply { + RoomSummaryEntity.updateDirectUserPresence(realm, userPresenceEntity.userId, this) + RoomMemberSummaryEntity.updateUserPresence(realm, userPresenceEntity.userId, this) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt similarity index 93% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt index db1100d76c5cdd9eef17876822f5a6416403ccb5..1e0e87a450e4ecf9aeccbf3a2c898182920235d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/SyncResponsePostTreatmentAggregatorHandler.kt @@ -14,9 +14,11 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler import org.matrix.android.sdk.api.MatrixPatterns +import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore +import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator import org.matrix.android.sdk.internal.session.sync.model.accountdata.toMutable import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt similarity index 99% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt index a9926c70aa9bf581a9b8918c330fc19c8b439859..3e38cd78391a9c1849353eb33d8c7e93d790496f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler import com.zhuinden.monarchy.Monarchy import io.realm.Realm diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt similarity index 96% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt index fc1a2c387014d8f51f9068f8667023c64f1b6941..025ee329f8a0dd6731b67458760428e401a4d7ac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ReadReceiptHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler.room import io.realm.Realm import org.matrix.android.sdk.api.session.events.model.EventType @@ -23,6 +23,8 @@ import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity import org.matrix.android.sdk.internal.database.query.createUnmanaged import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore +import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator import timber.log.Timber import javax.inject.Inject diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomFullyReadHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomFullyReadHandler.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomFullyReadHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomFullyReadHandler.kt index 3d0db212c2586bf9fbe5b4990f14b33159ccba84..b5c8a099d358b95c773c8f6f1ac1363b5879a176 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomFullyReadHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomFullyReadHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler.room import io.realm.Realm import org.matrix.android.sdk.internal.database.model.ReadMarkerEntity diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt similarity index 98% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 52e5b6b58d8c86359296e81ae733d4619e4af88b..8c4af81c99a38665f030ef0183510bc4297afcd5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler.room import io.realm.Realm import io.realm.kotlin.createObject @@ -62,6 +62,9 @@ import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent +import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy +import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator +import org.matrix.android.sdk.internal.session.sync.initialSyncStrategy import org.matrix.android.sdk.internal.session.sync.parsing.RoomSyncAccountDataHandler import org.matrix.android.sdk.internal.util.computeBestChunkSize import timber.log.Timber diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTagHandler.kt similarity index 95% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTagHandler.kt index 8997435be4b081a689ecb490066fb9f58b8ff799..55b15624bc4c328ad4fb748715d2df41b5df2ac1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTagHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler.room import io.realm.Realm import org.matrix.android.sdk.api.session.room.model.tag.RoomTagContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt similarity index 96% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt index 1433d891434b8b99fb4b8fa5dc331fc7329e74ad..63db13a5b804871163c24174c532bc3fdbfac4e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomTypingUsersHandler.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.session.sync +package org.matrix.android.sdk.internal.session.sync.handler.room import io.realm.Realm import org.matrix.android.sdk.api.session.room.sender.SenderInfo 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 cce169c2464af123907e3c25f942b08692313746..9480cc73f142ebdc50405cc3ec52e5e66adb7cd6 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 @@ -25,6 +25,7 @@ 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.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.isTokenError import org.matrix.android.sdk.api.session.Session @@ -34,7 +35,6 @@ import org.matrix.android.sdk.internal.session.sync.SyncPresence 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 timber.log.Timber import java.net.SocketTimeoutException import java.util.concurrent.atomic.AtomicBoolean diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt index 6ca008c5b18e01707113892384d20d475aa95b8f..5e7bde87e77d2a668e1e9369331debe9ef283206 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt @@ -28,8 +28,8 @@ import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityField import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.session.room.read.FullyReadContent -import org.matrix.android.sdk.internal.session.sync.RoomFullyReadHandler -import org.matrix.android.sdk.internal.session.sync.RoomTagHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.RoomFullyReadHandler +import org.matrix.android.sdk.internal.session.sync.handler.room.RoomTagHandler import javax.inject.Inject internal class RoomSyncAccountDataHandler @Inject constructor(private val roomTagHandler: RoomTagHandler, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt index e5c338d51105a018cfdbb1cd2843cd4715dda0e6..ddcac475ee6d2cae50c79281d9e61596ef88a17e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt @@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataDataSource -import org.matrix.android.sdk.internal.session.sync.UserAccountDataSyncHandler +import org.matrix.android.sdk.internal.session.sync.handler.UserAccountDataSyncHandler import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.awaitCallback diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt index 86848d10188af2b7d72d8c8e9dcd63192acebfc1..57574a96fa9f120d705b421c6176d5a50c8cf343 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt @@ -21,10 +21,10 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.di.MatrixScope import org.matrix.android.sdk.internal.extensions.foldToCallback -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.toCancelable import timber.log.Timber import javax.inject.Inject diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..c8be0f54871b23e29b0fdd0ce95c89157ea43d9d --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/pushers/DefaultAddPusherTaskTest.kt @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.pushers + +import kotlinx.coroutines.runBlocking +import org.amshove.kluent.internal.assertFailsWith +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test +import org.matrix.android.sdk.api.session.pushers.PusherState +import org.matrix.android.sdk.internal.database.model.PusherEntity +import org.matrix.android.sdk.test.fakes.FakeGlobalErrorReceiver +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.FakePushersAPI +import org.matrix.android.sdk.test.fakes.FakeRequestExecutor +import java.net.SocketException + +private val A_JSON_PUSHER = JsonPusher( + pushKey = "push-key", + kind = "http", + appId = "m.email", + appDisplayName = "Element", + deviceDisplayName = null, + profileTag = "", + lang = "en-GB", + data = JsonPusherData(brand = "Element") +) + +class DefaultAddPusherTaskTest { + + private val pushersAPI = FakePushersAPI() + private val monarchy = FakeMonarchy() + + private val addPusherTask = DefaultAddPusherTask( + pushersAPI = pushersAPI, + monarchy = monarchy.instance, + requestExecutor = FakeRequestExecutor(), + globalErrorReceiver = FakeGlobalErrorReceiver() + ) + + @Test + fun `given no persisted pusher when adding Pusher then updates api and inserts result with Registered state`() { + monarchy.givenWhereReturns<PusherEntity>(result = null) + + runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } + + pushersAPI.verifySetPusher(A_JSON_PUSHER) + monarchy.verifyInsertOrUpdate<PusherEntity> { + withArg { actual -> + actual.state shouldBeEqualTo PusherState.REGISTERED + } + } + } + + @Test + fun `given a persisted pusher when adding Pusher then updates api and mutates persisted result with Registered state`() { + val realmResult = PusherEntity(appDisplayName = null) + monarchy.givenWhereReturns(result = realmResult) + + runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } + + pushersAPI.verifySetPusher(A_JSON_PUSHER) + + realmResult.appDisplayName shouldBeEqualTo A_JSON_PUSHER.appDisplayName + realmResult.state shouldBeEqualTo PusherState.REGISTERED + } + + @Test + fun `given a persisted push entity and SetPush API fails when adding Pusher then mutates persisted result with Failed registration state and rethrows error`() { + val realmResult = PusherEntity() + monarchy.givenWhereReturns(result = realmResult) + pushersAPI.givenSetPusherErrors(SocketException()) + + assertFailsWith<SocketException> { + runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } + } + + realmResult.state shouldBeEqualTo PusherState.FAILED_TO_REGISTER + } + + @Test + fun `given no persisted push entity and SetPush API fails when adding Pusher then rethrows error`() { + monarchy.givenWhereReturns<PusherEntity>(result = null) + pushersAPI.givenSetPusherErrors(SocketException()) + + assertFailsWith<SocketException> { + runBlocking { addPusherTask.execute(AddPusherTask.Params(A_JSON_PUSHER)) } + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGlobalErrorReceiver.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGlobalErrorReceiver.kt new file mode 100644 index 0000000000000000000000000000000000000000..ebddb3fafa5428695a091d935dea56d868d763a6 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGlobalErrorReceiver.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.test.fakes + +import org.matrix.android.sdk.api.failure.GlobalError +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver + +internal class FakeGlobalErrorReceiver : GlobalErrorReceiver { + override fun handleGlobalError(globalError: GlobalError) { + // do nothing + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt new file mode 100644 index 0000000000000000000000000000000000000000..0a22ef899664023c842b35034aee27c5c233acb5 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.test.fakes + +import com.zhuinden.monarchy.Monarchy +import io.mockk.MockKVerificationScope +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.verify +import io.realm.Realm +import io.realm.RealmModel +import io.realm.RealmQuery +import io.realm.kotlin.where +import org.matrix.android.sdk.internal.util.awaitTransaction + +internal class FakeMonarchy { + + val instance = mockk<Monarchy>() + private val realm = mockk<Realm>(relaxed = true) + + init { + mockkStatic("org.matrix.android.sdk.internal.util.MonarchyKt") + coEvery { + instance.awaitTransaction(any<suspend (Realm) -> Any>()) + } coAnswers { + secondArg<suspend (Realm) -> Any>().invoke(realm) + } + } + + inline fun <reified T : RealmModel> givenWhereReturns(result: T?) { + val queryResult = mockk<RealmQuery<T>>(relaxed = true) + every { queryResult.findFirst() } returns result + every { realm.where<T>() } returns queryResult + } + + inline fun <reified T : RealmModel> verifyInsertOrUpdate(crossinline verification: MockKVerificationScope.() -> T) { + verify { realm.insertOrUpdate(verification()) } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakePushersAPI.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakePushersAPI.kt new file mode 100644 index 0000000000000000000000000000000000000000..29f93c2fafa1b16a1cd4e657c2ced0af428b6501 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakePushersAPI.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.test.fakes + +import org.amshove.kluent.shouldBeEqualTo +import org.matrix.android.sdk.internal.session.pushers.GetPushersResponse +import org.matrix.android.sdk.internal.session.pushers.JsonPusher +import org.matrix.android.sdk.internal.session.pushers.PushersAPI + +internal class FakePushersAPI : PushersAPI { + + private var setRequestPayload: JsonPusher? = null + private var error: Throwable? = null + + override suspend fun getPushers(): GetPushersResponse { + TODO("Not yet implemented") + } + + override suspend fun setPusher(jsonPusher: JsonPusher) { + error?.let { throw it } + setRequestPayload = jsonPusher + } + + fun verifySetPusher(payload: JsonPusher) { + this.setRequestPayload shouldBeEqualTo payload + } + + fun givenSetPusherErrors(error: Throwable) { + this.error = error + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRequestExecutor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRequestExecutor.kt new file mode 100644 index 0000000000000000000000000000000000000000..2f332a89a807be3d15d43842ac9e34b8a848fadb --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRequestExecutor.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.test.fakes + +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.RequestExecutor + +internal class FakeRequestExecutor : RequestExecutor { + + override suspend fun <DATA> executeRequest(globalErrorReceiver: GlobalErrorReceiver?, + canRetry: Boolean, + maxDelayBeforeRetry: Long, + maxRetriesCount: Int, + requestBlock: suspend () -> DATA): DATA { + return requestBlock() + } +}