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 2cd2bf2dd3..21b4ffa05c 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 @@ -31,6 +31,7 @@ import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.toOptional import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData import io.reactivex.Observable import io.reactivex.Single @@ -121,6 +122,13 @@ class RxSession(private val session: Session) { session.getCrossSigningService().getUserCrossSigningKeys(userId).toOptional() } } + + fun liveAccountData(filter: List): Observable> { + return session.getLiveAccountData(filter).asObservable() + .startWithCallable { + session.getAccountData(filter) + } + } } fun Session.rx(): RxSession { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt index 5bd219247c..5255d7c224 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/Session.kt @@ -21,6 +21,7 @@ import androidx.lifecycle.LiveData import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.failure.GlobalError import im.vector.matrix.android.api.pushrules.PushRuleService +import im.vector.matrix.android.api.session.accountdata.AccountDataService import im.vector.matrix.android.api.session.cache.CacheService import im.vector.matrix.android.api.session.content.ContentUploadStateTracker import im.vector.matrix.android.api.session.content.ContentUrlResolver @@ -57,7 +58,8 @@ interface Session : PushersService, InitialSyncProgressService, HomeServerCapabilitiesService, - SecureStorageService { + SecureStorageService, + AccountDataService { /** * The params associated to the session diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/accountdata/AccountDataService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/accountdata/AccountDataService.kt new file mode 100644 index 0000000000..7a3b9f0171 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/accountdata/AccountDataService.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.api.session.accountdata + +import androidx.lifecycle.LiveData +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.util.Optional +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData + +interface AccountDataService { + + fun getAccountData(type: String): UserAccountData? + + fun getLiveAccountData(type: String): LiveData> + + fun getAccountData(filterType: List): List + + fun getLiveAccountData(filterType: List): LiveData> + + fun updateAccountData(type: String, data: Any, callback: MatrixCallback? = null) +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt index 8878930de0..9a3107a8ca 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/EventType.kt @@ -66,6 +66,9 @@ object EventType { const val ROOM_KEY_REQUEST = "m.room_key_request" const val FORWARDED_ROOM_KEY = "m.forwarded_room_key" + const val REQUEST_SECRET = "m.secret.request" + const val SEND_SECRET = "m.secret.send" + // Interactive key verification const val KEY_VERIFICATION_START = "m.key.verification.start" const val KEY_VERIFICATION_ACCEPT = "m.key.verification.accept" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt new file mode 100644 index 0000000000..fa2042e506 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2019 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.api.session.securestorage + +import im.vector.matrix.android.api.MatrixCallback + +/** + * Some features may require clients to store encrypted data on the server so that it can be shared securely between clients. + * Clients may also wish to securely send such data directly to each other. + * For example, key backups (MSC1219) can store the decryption key for the backups on the server, or cross-signing (MSC1756) can store the signing keys. + * + * https://github.com/matrix-org/matrix-doc/pull/1946 + * + */ + +interface SharedSecretStorageService { + + /** + * Add a key for encrypting secrets. + * + * @param algorithm the algorithm used by the key. + * @param opts the options for the algorithm. The properties used + * depend on the algorithm given. + * @param keyId the ID of the key + * + * @return {string} the ID of the key + */ + fun addKey(algorithm: String, opts: Map, keyId: String, callback: MatrixCallback) + + /** + * Check whether we have a key with a given ID. + * + * @param keyId The ID of the key to check + * @return Whether we have the key. + */ + fun hasKey(keyId: String): Boolean + + /** + * Store an encrypted secret on the server + * + * @param name The name of the secret + * @param secret The secret contents. + * @param keys The IDs of the keys to use to encrypt the secret or null to use the default key. + */ + fun storeSecret(name: String, secretBase64: String, keys: List?, callback: MatrixCallback) + + + /** + * Get an encrypted secret from the shared storage + * + * @param name The name of the secret + * @param keyId The id of the key that should be used to decrypt + * @param privateKey the passphrase/secret + * + * @return The decrypted value + */ + fun getSecret(name: String, keyId: String, privateKey: String) : String + +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/SecretStorageService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/SecretStorageService.kt new file mode 100644 index 0000000000..33306104eb --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/SecretStorageService.kt @@ -0,0 +1,39 @@ +/* + * 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.crypto.secrets + +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService + +internal class DefaultSharedSecureStorage : SharedSecretStorageService { + + override fun addKey(algorithm: String, opts: Map, keyId: String, callback: MatrixCallback) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun hasKey(keyId: String): Boolean { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun storeSecret(name: String, secretBase64: String, keys: List?, callback: MatrixCallback) { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun getSecret(name: String, keyId: String, privateKey: String): String { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } +} 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 74768f8797..081a6a5152 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 @@ -53,6 +53,7 @@ import io.realm.annotations.RealmModule DraftEntity::class, HomeServerCapabilitiesEntity::class, RoomMemberSummaryEntity::class, - CurrentStateEventEntity::class + CurrentStateEventEntity::class, + UserAccountDataEntity::class ]) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/UserAccountDataEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/UserAccountDataEntity.kt new file mode 100644 index 0000000000..9ffbcca527 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/UserAccountDataEntity.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.database.model + +import io.realm.RealmObject +import io.realm.annotations.Index + +/** + * Clients can store custom config data for their account on their homeserver. + * This account data will be synced between different devices and can persist across installations on a particular device. + * Users may only view the account data for their own accountThe account_data may be either global or scoped to a particular rooms. + */ +internal open class UserAccountDataEntity( + @Index var type: String? = null, + var contentStr: String? = null +) : RealmObject() { + + companion object +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/AccountDataJsonAdapterFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/AccountDataJsonAdapterFactory.kt new file mode 100644 index 0000000000..e7290077dd --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/AccountDataJsonAdapterFactory.kt @@ -0,0 +1,29 @@ +/* + * 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.network.parsing + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import java.lang.reflect.Type + +class AccountDataJsonAdapterFactory : JsonAdapter.Factory { + + + override fun create(type: Type, annotations: MutableSet, moshi: Moshi): JsonAdapter<*>? { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt index 77cd3685d7..afe37c1c41 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultSession.kt @@ -25,6 +25,7 @@ import im.vector.matrix.android.api.failure.GlobalError import im.vector.matrix.android.api.pushrules.PushRuleService import im.vector.matrix.android.api.session.InitialSyncProgressService import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.accountdata.AccountDataService import im.vector.matrix.android.api.session.cache.CacheService import im.vector.matrix.android.api.session.content.ContentUploadStateTracker import im.vector.matrix.android.api.session.content.ContentUrlResolver @@ -91,6 +92,7 @@ internal class DefaultSession @Inject constructor( private val contentUploadProgressTracker: ContentUploadStateTracker, private val initialSyncProgressService: Lazy, private val homeServerCapabilitiesService: Lazy, + private val accountDataService: Lazy, private val shieldTrustUpdater: ShieldTrustUpdater) : Session, RoomService by roomService.get(), @@ -106,7 +108,8 @@ internal class DefaultSession @Inject constructor( InitialSyncProgressService by initialSyncProgressService.get(), SecureStorageService by secureStorageService.get(), HomeServerCapabilitiesService by homeServerCapabilitiesService.get(), - ProfileService by profileService.get() { + ProfileService by profileService.get(), + AccountDataService by accountDataService.get() { private var isOpen = false diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt index 969a968a91..636e61c93f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt @@ -32,6 +32,7 @@ import im.vector.matrix.android.api.auth.data.sessionId import im.vector.matrix.android.api.crypto.MXCryptoConfig import im.vector.matrix.android.api.session.InitialSyncProgressService import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.accountdata.AccountDataService import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilitiesService import im.vector.matrix.android.api.session.securestorage.SecureStorageService import im.vector.matrix.android.internal.crypto.verification.VerificationMessageLiveObserver @@ -61,6 +62,7 @@ import im.vector.matrix.android.internal.session.room.create.RoomCreateEventLive import im.vector.matrix.android.internal.session.room.prune.EventsPruner import im.vector.matrix.android.internal.session.room.tombstone.RoomTombstoneEventLiveObserver import im.vector.matrix.android.internal.session.securestorage.DefaultSecureStorageService +import im.vector.matrix.android.internal.session.user.accountdata.DefaultAccountDataService import im.vector.matrix.android.internal.util.md5 import io.realm.RealmConfiguration import okhttp3.OkHttpClient @@ -263,4 +265,7 @@ internal abstract class SessionModule { @Binds abstract fun bindHomeServerCapabilitiesService(homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService + + @Binds + abstract fun bindAccountDataServiceService(accountDataService: DefaultAccountDataService): AccountDataService } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt index f76c2ff448..fc4f73630b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/UserAccountDataSyncHandler.kt @@ -19,9 +19,12 @@ package im.vector.matrix.android.internal.session.sync import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.pushrules.RuleScope import im.vector.matrix.android.api.pushrules.RuleSetKey +import im.vector.matrix.android.api.session.events.model.Content +import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.RoomMemberContent import im.vector.matrix.android.api.session.room.model.RoomSummary +import im.vector.matrix.android.internal.database.mapper.ContentMapper import im.vector.matrix.android.internal.database.mapper.PushRulesMapper import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.model.BreadcrumbsEntity @@ -29,15 +32,18 @@ import im.vector.matrix.android.internal.database.model.IgnoredUserEntity import im.vector.matrix.android.internal.database.model.PushRulesEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields +import im.vector.matrix.android.internal.database.model.UserAccountDataEntity +import im.vector.matrix.android.internal.database.model.UserAccountDataEntityFields import im.vector.matrix.android.internal.database.query.getDirectRooms import im.vector.matrix.android.internal.database.query.getOrCreate import im.vector.matrix.android.internal.database.query.where +import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper import im.vector.matrix.android.internal.session.sync.model.InvitedRoomSync +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataBreadcrumbs import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataDirectMessages -import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataIgnoredUsers import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataPushRules import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataSync @@ -45,6 +51,7 @@ import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHel import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask import io.realm.Realm import io.realm.RealmList +import io.realm.kotlin.where import timber.log.Timber import javax.inject.Inject @@ -56,21 +63,23 @@ internal class UserAccountDataSyncHandler @Inject constructor( fun handle(realm: Realm, accountData: UserAccountDataSync?) { accountData?.list?.forEach { - when (it) { - is UserAccountDataDirectMessages -> handleDirectChatRooms(realm, it) - is UserAccountDataPushRules -> handlePushRules(realm, it) - is UserAccountDataIgnoredUsers -> handleIgnoredUsers(realm, it) - is UserAccountDataBreadcrumbs -> handleBreadcrumbs(realm, it) - is UserAccountDataFallback -> Timber.d("Receive account data of unhandled type ${it.type}") - else -> error("Missing code here!") + // Generic handling, just save in base + handleGenericAccountData(realm, it.type, it.content) + + // Didn't want to break too much thing, so i re-serialize to jsonString before reparsing + // TODO would be better to have a mapper? + val toJson = MoshiProvider.providesMoshi().adapter(Event::class.java).toJson(it) + val model = toJson?.let { json -> + MoshiProvider.providesMoshi().adapter(UserAccountData::class.java).fromJson(json) + } + // Specific parsing + when (model) { + is UserAccountDataDirectMessages -> handleDirectChatRooms(realm, model) + is UserAccountDataPushRules -> handlePushRules(realm, model) + is UserAccountDataIgnoredUsers -> handleIgnoredUsers(realm, model) + is UserAccountDataBreadcrumbs -> handleBreadcrumbs(realm, model) } } - - // TODO Store all account data, app can be interested of it - // accountData?.list?.forEach { - // it.toString() - // MoshiProvider.providesMoshi() - // } } // If we get some direct chat invites, we synchronize the user account data including those. @@ -200,4 +209,18 @@ internal class UserAccountDataSyncHandler @Inject constructor( ?.breadcrumbsIndex = index } } + + private fun handleGenericAccountData(realm: Realm, type: String, content: Content?) { + val existing = realm.where().equalTo(UserAccountDataEntityFields.TYPE, type) + .findFirst() + if (existing != null) { + // Update current value + existing.contentStr = ContentMapper.map(content) + } else { + realm.createObject(UserAccountDataEntity::class.java).let { accountDataEntity -> + accountDataEntity.type = type + accountDataEntity.contentStr = ContentMapper.map(content) + } + } + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountData.kt index accc9c900f..3ec6c3c7eb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountData.kt @@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.session.sync.model.accountdata import com.squareup.moshi.Json -internal abstract class UserAccountData { +abstract class UserAccountData { @Json(name = "type") abstract val type: String diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataFallback.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataFallback.kt index a8b8235d37..d965d2ffee 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataFallback.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataFallback.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -internal data class UserAccountDataFallback( +data class UserAccountDataFallback( @Json(name = "type") override val type: String, @Json(name = "content") val content: Map ) : UserAccountData() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataSync.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataSync.kt index c7f8bfa4c2..8acac86e1a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataSync.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataSync.kt @@ -18,8 +18,9 @@ package im.vector.matrix.android.internal.session.sync.model.accountdata import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.session.events.model.Event @JsonClass(generateAdapter = true) internal data class UserAccountDataSync( - @Json(name = "events") val list: List = emptyList() + @Json(name = "events") val list: List = emptyList() ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/DefaultAccountDataService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/DefaultAccountDataService.kt new file mode 100644 index 0000000000..2aeef3cc0d --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/DefaultAccountDataService.kt @@ -0,0 +1,101 @@ +/* + * 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.user.accountdata + +import androidx.lifecycle.LiveData +import androidx.lifecycle.Transformations +import com.zhuinden.monarchy.Monarchy +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.accountdata.AccountDataService +import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE +import im.vector.matrix.android.api.util.Optional +import im.vector.matrix.android.api.util.toOptional +import im.vector.matrix.android.internal.database.model.UserAccountDataEntity +import im.vector.matrix.android.internal.database.model.UserAccountDataEntityFields +import im.vector.matrix.android.internal.di.MoshiProvider +import im.vector.matrix.android.internal.di.SessionId +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith +import javax.inject.Inject + +internal class DefaultAccountDataService @Inject constructor( + private val monarchy: Monarchy, + @SessionId private val sessionId: String, + private val updateUserAccountDataTask: UpdateUserAccountDataTask, + private val taskExecutor: TaskExecutor +) : AccountDataService { + + private val moshi = MoshiProvider.providesMoshi() + private val adapter = moshi.adapter>(JSON_DICT_PARAMETERIZED_TYPE) + + override fun getAccountData(type: String): UserAccountData? { + return getAccountData(listOf(type)).firstOrNull() + } + + override fun getLiveAccountData(type: String): LiveData> { + return Transformations.map(getLiveAccountData(listOf(type))) { + it.firstOrNull()?.toOptional() + } + } + + override fun getAccountData(filterType: List): List { + return monarchy.fetchAllCopiedSync { realm -> + realm.where(UserAccountDataEntity::class.java) + .apply { + if (filterType.isNotEmpty()) { + `in`(UserAccountDataEntityFields.TYPE, filterType.toTypedArray()) + } + } + }?.mapNotNull { entity -> + entity.type?.let { type -> + UserAccountDataFallback( + type = type, + content = entity.contentStr?.let { adapter.fromJson(it) } ?: emptyMap() + ) + } + } ?: emptyList() + } + + override fun getLiveAccountData(filterType: List): LiveData> { + return monarchy.findAllMappedWithChanges({ realm -> + realm.where(UserAccountDataEntity::class.java) + .apply { + if (filterType.isNotEmpty()) { + `in`(UserAccountDataEntityFields.TYPE, filterType.toTypedArray()) + } + } + }, { entity -> + UserAccountDataFallback( + type = entity.type ?: "", + content = entity.contentStr?.let { adapter.fromJson(it) } ?: emptyMap() + ) + }) + } + + override fun updateAccountData(type: String, data: Any, callback: MatrixCallback?) { + updateUserAccountDataTask.configureWith(UpdateUserAccountDataTask.AnyParams( + type = type, + any = data + )) { + this.retryCount = 5 + callback?.let { this.callback = it } + } + .executeBy(taskExecutor) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt index 068ce4777a..beb3a0fcc0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/UpdateUserAccountDataTask.kt @@ -49,6 +49,14 @@ internal interface UpdateUserAccountDataTask : Task Copyright (c) 2014 Dushyanth Maguluru +
  • + JsonViewer +
    + Copyright 2017 smuyyh, All right reserved. +
  •  Apache License
    diff --git a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt
    index 6031a9f6cf..cca3c20110 100644
    --- a/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt
    +++ b/vector/src/main/java/im/vector/riotx/core/di/FragmentModule.kt
    @@ -72,6 +72,7 @@ import im.vector.riotx.features.settings.VectorSettingsPreferencesFragment
     import im.vector.riotx.features.settings.VectorSettingsSecurityPrivacyFragment
     import im.vector.riotx.features.settings.crosssigning.CrossSigningSettingsFragment
     import im.vector.riotx.features.settings.devices.VectorSettingsDevicesFragment
    +import im.vector.riotx.features.settings.devtools.AccountDataFragment
     import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment
     import im.vector.riotx.features.settings.push.PushGatewaysFragment
     import im.vector.riotx.features.signout.soft.SoftLogoutFragment
    @@ -348,4 +349,9 @@ interface FragmentModule {
         @IntoMap
         @FragmentKey(CrossSigningSettingsFragment::class)
         fun bindCrossSigningSettingsFragment(fragment: CrossSigningSettingsFragment): Fragment
    +
    +    @Binds
    +    @IntoMap
    +    @FragmentKey(AccountDataFragment::class)
    +    fun bindAccountDataFragment(fragment: AccountDataFragment): Fragment
     }
    diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/AccountDataEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/AccountDataEpoxyController.kt
    new file mode 100644
    index 0000000000..c8a09bfb64
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/AccountDataEpoxyController.kt
    @@ -0,0 +1,79 @@
    +/*
    + * 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.riotx.features.settings.devtools
    +
    +import android.view.View
    +import com.airbnb.epoxy.TypedEpoxyController
    +import com.airbnb.mvrx.Fail
    +import com.airbnb.mvrx.Loading
    +import com.airbnb.mvrx.Success
    +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
    +import im.vector.riotx.R
    +import im.vector.riotx.core.epoxy.loadingItem
    +import im.vector.riotx.core.resources.StringProvider
    +import im.vector.riotx.core.ui.list.genericFooterItem
    +import im.vector.riotx.core.ui.list.genericItemWithValue
    +import im.vector.riotx.core.utils.DebouncedClickListener
    +import javax.inject.Inject
    +
    +class AccountDataEpoxyController @Inject constructor(
    +        private val stringProvider: StringProvider
    +) : TypedEpoxyController() {
    +
    +    interface InteractionListener {
    +        fun didTap(data: UserAccountData)
    +    }
    +
    +    var interactionListener: InteractionListener? = null
    +
    +    override fun buildModels(data: AccountDataViewState?) {
    +        if (data == null) return
    +        when (data.accountData) {
    +            is Loading -> {
    +                loadingItem {
    +                    id("loading")
    +                    loadingText(stringProvider.getString(R.string.loading))
    +                }
    +            }
    +            is Fail    -> {
    +                genericFooterItem {
    +                    id("fail")
    +                    text(data.accountData.error.localizedMessage)
    +                }
    +            }
    +            is Success -> {
    +                val dataList = data.accountData.invoke()
    +                if (dataList.isEmpty()) {
    +                    genericFooterItem {
    +                        id("noResults")
    +                        text(stringProvider.getString(R.string.no_result_placeholder))
    +                    }
    +                } else {
    +                    dataList.forEach { accountData ->
    +                        genericItemWithValue {
    +                            id(accountData.type)
    +                            title(accountData.type)
    +                            itemClickAction(DebouncedClickListener(View.OnClickListener {
    +                                interactionListener?.didTap(accountData)
    +                            }))
    +                        }
    +                    }
    +                }
    +            }
    +        }
    +    }
    +}
    diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/AccountDataFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/AccountDataFragment.kt
    new file mode 100644
    index 0000000000..5b7b090dcd
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/AccountDataFragment.kt
    @@ -0,0 +1,65 @@
    +/*
    + * 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.riotx.features.settings.devtools
    +
    +import android.os.Bundle
    +import android.view.View
    +import com.airbnb.mvrx.fragmentViewModel
    +import com.airbnb.mvrx.withState
    +import im.vector.matrix.android.internal.di.MoshiProvider
    +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
    +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataFallback
    +import im.vector.riotx.R
    +import im.vector.riotx.core.extensions.configureWith
    +import im.vector.riotx.core.platform.VectorBaseActivity
    +import im.vector.riotx.core.platform.VectorBaseFragment
    +import kotlinx.android.synthetic.main.fragment_generic_recycler.*
    +import javax.inject.Inject
    +
    +class AccountDataFragment @Inject constructor(
    +        val viewModelFactory: AccountDataViewModel.Factory,
    +        private val epoxyController: AccountDataEpoxyController
    +) : VectorBaseFragment(), AccountDataEpoxyController.InteractionListener {
    +
    +    override fun getLayoutResId() = R.layout.fragment_generic_recycler
    +
    +    private val viewModel: AccountDataViewModel by fragmentViewModel(AccountDataViewModel::class)
    +
    +    override fun onResume() {
    +        super.onResume()
    +        (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.settings_account_data)
    +    }
    +
    +    override fun invalidate() = withState(viewModel) { state ->
    +        epoxyController.setData(state)
    +    }
    +
    +    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    +        super.onViewCreated(view, savedInstanceState)
    +        recyclerView.configureWith(epoxyController, showDivider = true)
    +        epoxyController.interactionListener = this
    +    }
    +
    +    override fun didTap(data: UserAccountData) {
    +        val fb = data as? UserAccountDataFallback ?: return
    +        val jsonString = MoshiProvider.providesMoshi()
    +                .adapter(UserAccountDataFallback::class.java)
    +                .toJson(fb)
    +        JsonViewerBottomSheetDialog.newInstance(jsonString)
    +                .show(childFragmentManager, "JSON_VIEWER")
    +    }
    +}
    diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/AccountDataViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/AccountDataViewModel.kt
    new file mode 100644
    index 0000000000..4a6b0f896a
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/AccountDataViewModel.kt
    @@ -0,0 +1,65 @@
    +/*
    + * 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.riotx.features.settings.devtools
    +
    +import com.airbnb.mvrx.Async
    +import com.airbnb.mvrx.FragmentViewModelContext
    +import com.airbnb.mvrx.MvRxState
    +import com.airbnb.mvrx.MvRxViewModelFactory
    +import com.airbnb.mvrx.Uninitialized
    +import com.airbnb.mvrx.ViewModelContext
    +import com.squareup.inject.assisted.Assisted
    +import com.squareup.inject.assisted.AssistedInject
    +import im.vector.matrix.android.api.session.Session
    +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
    +import im.vector.matrix.rx.rx
    +import im.vector.riotx.core.platform.EmptyAction
    +import im.vector.riotx.core.platform.EmptyViewEvents
    +import im.vector.riotx.core.platform.VectorViewModel
    +
    +data class AccountDataViewState(
    +        val accountData: Async> = Uninitialized
    +) : MvRxState
    +
    +class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: AccountDataViewState,
    +                                                       private val session: Session)
    +    : VectorViewModel(initialState) {
    +
    +
    +    init {
    +        session.rx().liveAccountData(emptyList())
    +                .execute {
    +                    copy(accountData = it)
    +                }
    +    }
    +
    +    override fun handle(action: EmptyAction) {}
    +
    +    @AssistedInject.Factory
    +    interface Factory {
    +        fun create(initialState: AccountDataViewState): AccountDataViewModel
    +    }
    +
    +    companion object : MvRxViewModelFactory {
    +
    +        @JvmStatic
    +        override fun create(viewModelContext: ViewModelContext, state: AccountDataViewState): AccountDataViewModel? {
    +            val fragment: AccountDataFragment = (viewModelContext as FragmentViewModelContext).fragment()
    +            return fragment.viewModelFactory.create(state)
    +        }
    +    }
    +}
    diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/JsonViewerBottomSheetDialog.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/JsonViewerBottomSheetDialog.kt
    new file mode 100644
    index 0000000000..c638846f51
    --- /dev/null
    +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/JsonViewerBottomSheetDialog.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.riotx.features.settings.devtools
    +
    +import android.os.Bundle
    +import android.view.View
    +import androidx.core.content.ContextCompat
    +import butterknife.BindView
    +import com.airbnb.mvrx.MvRx
    +import com.yuyh.jsonviewer.library.JsonRecyclerView
    +import im.vector.riotx.R
    +import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment
    +import im.vector.riotx.features.themes.ThemeUtils
    +
    +class JsonViewerBottomSheetDialog : VectorBaseBottomSheetDialogFragment() {
    +
    +    override fun getLayoutResId() = R.layout.fragment_jsonviewer
    +
    +    @BindView(R.id.rv_json)
    +    lateinit var jsonRecyclerView: JsonRecyclerView
    +
    +    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    +        super.onViewCreated(view, savedInstanceState)
    +
    +        jsonRecyclerView.setKeyColor(ThemeUtils.getColor(requireContext(), R.attr.colorAccent))
    +        jsonRecyclerView.setValueTextColor(ContextCompat.getColor(requireContext(), R.color.riotx_notice_secondary))
    +        jsonRecyclerView.setValueNumberColor(ContextCompat.getColor(requireContext(), R.color.riotx_notice_secondary))
    +        jsonRecyclerView.setValueUrlColor(ThemeUtils.getColor(requireContext(), android.R.attr.textColorLink))
    +        jsonRecyclerView.setValueNullColor(ContextCompat.getColor(requireContext(), R.color.riotx_notice_secondary))
    +        jsonRecyclerView.setBracesColor(ThemeUtils.getColor(requireContext(), R.attr.riotx_text_primary))
    +
    +        val jsonString = arguments?.getString(MvRx.KEY_ARG)
    +        jsonRecyclerView.bindJson(jsonString)
    +    }
    +
    +    companion object {
    +        fun newInstance(jsonString: String): JsonViewerBottomSheetDialog {
    +            return JsonViewerBottomSheetDialog().apply {
    +                setArguments(Bundle().apply { putString(MvRx.KEY_ARG, jsonString) })
    +            }
    +        }
    +    }
    +}
    diff --git a/vector/src/main/res/layout/fragment_jsonviewer.xml b/vector/src/main/res/layout/fragment_jsonviewer.xml
    new file mode 100644
    index 0000000000..5a4aecc56c
    --- /dev/null
    +++ b/vector/src/main/res/layout/fragment_jsonviewer.xml
    @@ -0,0 +1,16 @@
    +
    +
    +
    +    
    +
    \ No newline at end of file
    diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml
    index 75790348de..c6ce955c04 100644
    --- a/vector/src/main/res/values/strings_riotX.xml
    +++ b/vector/src/main/res/values/strings_riotX.xml
    @@ -6,6 +6,8 @@
         
     
         
    +    Dev Tools
    +    Account Data
         
             %d vote
             %d votes
    diff --git a/vector/src/main/res/xml/vector_settings_advanced_settings.xml b/vector/src/main/res/xml/vector_settings_advanced_settings.xml
    index ad698f0036..34cd8743be 100644
    --- a/vector/src/main/res/xml/vector_settings_advanced_settings.xml
    +++ b/vector/src/main/res/xml/vector_settings_advanced_settings.xml
    @@ -63,6 +63,14 @@
                 android:persistent="false"
                 android:title="@string/settings_push_rules"
                 app:fragment="im.vector.riotx.features.settings.push.PushRulesFragment" />
    +    
    +
    +    
    +
    +