diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt index e92da1e424..3e5fcf4e87 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxSession.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams import im.vector.matrix.android.api.session.group.model.GroupSummary +import im.vector.matrix.android.api.session.identity.ThreePid import im.vector.matrix.android.api.session.pushers.Pusher import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams import im.vector.matrix.android.api.session.room.model.RoomSummary @@ -94,6 +95,11 @@ class RxSession(private val session: Session) { return session.getPagedUsersLive(filter, excludedUserIds).asObservable() } + fun liveThreePIds(): Observable> { + return session.getThreePidsLive().asObservable() + .startWithCallable { session.getThreePids() } + } + fun createRoom(roomParams: CreateRoomParams): Single = singleBuilder { session.createRoom(roomParams, it) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/profile/ProfileService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/profile/ProfileService.kt index c1dc9a8afa..3cc1cf19ce 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/profile/ProfileService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/profile/ProfileService.kt @@ -17,7 +17,9 @@ package im.vector.matrix.android.api.session.profile +import androidx.lifecycle.LiveData import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.identity.ThreePid import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.Optional @@ -53,4 +55,14 @@ interface ProfileService { * */ fun getProfile(userId: String, matrixCallback: MatrixCallback): Cancelable + + /** + * Get the current user 3Pids + */ + fun getThreePids(): List + + /** + * Get the current user 3Pids Live + */ + fun getThreePidsLive(): LiveData> } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt index 081a6a5152..accac9ca97 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/SessionRealmModule.kt @@ -37,6 +37,7 @@ import io.realm.annotations.RealmModule UserEntity::class, IgnoredUserEntity::class, BreadcrumbsEntity::class, + UserThreePidEntity::class, EventAnnotationsSummaryEntity::class, ReactionAggregatedSummaryEntity::class, EditAggregatedSummaryEntity::class, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/UserThreePidEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/UserThreePidEntity.kt new file mode 100644 index 0000000000..f41ac1baa7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/UserThreePidEntity.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.matrix.android.internal.database.model + +import io.realm.RealmObject + +internal open class UserThreePidEntity( + var medium: String = "", + var address: String = "", + var validatedAt: Long = 0, + var addedAt: Long = 0 +) : RealmObject() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/AccountThreePidsResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/AccountThreePidsResponse.kt new file mode 100644 index 0000000000..6161ccaf46 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/AccountThreePidsResponse.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.matrix.android.internal.session.profile + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Class representing the ThreePids response + */ +@JsonClass(generateAdapter = true) +internal data class AccountThreePidsResponse( + @Json(name = "threepids") + val threePids: List? = null +) \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/DefaultProfileService.kt index e2c18e41d6..c7c385f895 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/DefaultProfileService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/DefaultProfileService.kt @@ -17,16 +17,23 @@ package im.vector.matrix.android.internal.session.profile +import androidx.lifecycle.LiveData +import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.identity.ThreePid import im.vector.matrix.android.api.session.profile.ProfileService import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.Optional +import im.vector.matrix.android.internal.database.model.UserThreePidEntity import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith +import io.realm.kotlin.where import javax.inject.Inject internal class DefaultProfileService @Inject constructor(private val taskExecutor: TaskExecutor, + private val monarchy: Monarchy, + private val refreshUserThreePidsTask: RefreshUserThreePidsTask, private val getProfileInfoTask: GetProfileInfoTask) : ProfileService { override fun getDisplayName(userId: String, matrixCallback: MatrixCallback>): Cancelable { @@ -73,4 +80,31 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto } .executeBy(taskExecutor) } + + override fun getThreePids(): List { + return monarchy.fetchAllMappedSync( + { it.where() }, + { it.asDomain() } + ) + } + + override fun getThreePidsLive(): LiveData> { + // Force a refresh of the values + refreshUserThreePidsTask + .configureWith() + .executeBy(taskExecutor) + + return monarchy.findAllMappedWithChanges( + { it.where() }, + { it.asDomain() } + ) + } +} + +private fun UserThreePidEntity.asDomain(): ThreePid { + return when (medium) { + ThirdPartyIdentifier.MEDIUM_EMAIL -> ThreePid.Email(address) + ThirdPartyIdentifier.MEDIUM_MSISDN -> ThreePid.Msisdn(address) + else -> error("Invalid medium type") + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ProfileAPI.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ProfileAPI.kt index 197d85f879..a6b44efeb0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ProfileAPI.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ProfileAPI.kt @@ -33,4 +33,10 @@ interface ProfileAPI { */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}") fun getProfile(@Path("userId") userId: String): Call + + /** + * List all 3PIDs linked to the Matrix user account. + */ + @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid") + fun getThreePIDs(): Call } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ProfileModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ProfileModule.kt index 7005a5341f..39ecec3567 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ProfileModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ProfileModule.kt @@ -42,4 +42,7 @@ internal abstract class ProfileModule { @Binds abstract fun bindGetProfileTask(task: DefaultGetProfileInfoTask): GetProfileInfoTask + + @Binds + abstract fun bindRefreshUserThreePidsTask(task: DefaultRefreshUserThreePidsTask): RefreshUserThreePidsTask } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/RefreshUserThreePidsTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/RefreshUserThreePidsTask.kt new file mode 100644 index 0000000000..270443e50e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/RefreshUserThreePidsTask.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.session.profile + +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.internal.database.model.UserThreePidEntity +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.task.Task +import im.vector.matrix.android.internal.util.awaitTransaction +import org.greenrobot.eventbus.EventBus +import timber.log.Timber +import javax.inject.Inject + +internal abstract class RefreshUserThreePidsTask : Task + +internal class DefaultRefreshUserThreePidsTask @Inject constructor(private val profileAPI: ProfileAPI, + private val monarchy: Monarchy, + private val eventBus: EventBus) : RefreshUserThreePidsTask() { + + override suspend fun execute(params: Unit) { + val accountThreePidsResponse = executeRequest(eventBus) { + apiCall = profileAPI.getThreePIDs() + } + + Timber.d("Get ${accountThreePidsResponse.threePids?.size} threePids") + // Store the list in DB + monarchy.awaitTransaction { realm -> + realm.where(UserThreePidEntity::class.java).findAll().deleteAllFromRealm() + accountThreePidsResponse.threePids?.forEach { + val entity = UserThreePidEntity( + it.medium?.takeIf { med -> med in ThirdPartyIdentifier.SUPPORTED_MEDIUM } ?: return@forEach, + it.address ?: return@forEach, + it.validatedAt.toLong(), + it.addedAt.toLong()) + realm.insertOrUpdate(entity) + } + } + } +} + +private fun Any?.toLong(): Long { + return when (this) { + null -> 0L + is Long -> this + is Double -> this.toLong() + else -> 0L + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ThirdPartyIdentifier.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ThirdPartyIdentifier.kt new file mode 100755 index 0000000000..76fa3bd80f --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/profile/ThirdPartyIdentifier.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.matrix.android.internal.session.profile + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class ThirdPartyIdentifier( + /** + * Required. The medium of the third party identifier. One of: ["email", "msisdn"] + */ + @Json(name = "medium") + val medium: String? = null, + + /** + * Required. The third party identifier address. + */ + @Json(name = "address") + val address: String? = null, + + /** + * Required. The timestamp in milliseconds when this 3PID has been validated. + * Define as Object because it should be Long and it is a Double. + * So, it might change. + */ + @Json(name = "validated_at") + val validatedAt: Any? = null, + + /** + * Required. The timestamp in milliseconds when this 3PID has been added to the user account. + * Define as Object because it should be Long and it is a Double. + * So, it might change. + */ + @Json(name = "added_at") + val addedAt: Any? = null +) { + companion object { + const val MEDIUM_EMAIL = "email" + const val MEDIUM_MSISDN = "msisdn" + + val SUPPORTED_MEDIUM = listOf(MEDIUM_EMAIL, MEDIUM_MSISDN) + } +}