From 8a3e93ae9641e3e225c12a7eb30a7c8bfbb888d5 Mon Sep 17 00:00:00 2001 From: onurays Date: Thu, 13 Feb 2020 14:59:41 +0300 Subject: [PATCH 01/22] Do not ask for a reason if user wants to delete his own message. Fixes (#1003) --- .../features/home/room/detail/RoomDetailFragment.kt | 10 +++++++--- .../room/detail/timeline/action/EventSharedAction.kt | 2 +- .../detail/timeline/action/MessageActionsViewModel.kt | 2 +- vector/src/main/res/layout/dialog_delete_event.xml | 3 +-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 2fa2243060..cbda6ab13e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -788,12 +788,15 @@ class RoomDetailFragment @Inject constructor( .show() } - private fun promptReasonToRedactEvent(eventId: String) { + private fun promptConfirmationToRedactEvent(eventId: String, askForReason: Boolean) { val layout = requireActivity().layoutInflater.inflate(R.layout.dialog_delete_event, null) val reasonCheckBox = layout.findViewById(R.id.deleteEventReasonCheck) val reasonTextInputLayout = layout.findViewById(R.id.deleteEventReasonTextInputLayout) val reasonInput = layout.findViewById(R.id.deleteEventReasonInput) + reasonCheckBox.isVisible = askForReason + reasonTextInputLayout.isVisible = askForReason + reasonCheckBox.setOnCheckedChangeListener { _, isChecked -> reasonTextInputLayout.isEnabled = isChecked } AlertDialog.Builder(requireActivity()) @@ -801,7 +804,8 @@ class RoomDetailFragment @Inject constructor( .setView(layout) .setPositiveButton(R.string.remove) { _, _ -> val reason = reasonInput.text.toString() - .takeIf { reasonCheckBox.isChecked } + .takeIf { askForReason } + ?.takeIf { reasonCheckBox.isChecked } ?.takeIf { it.isNotBlank() } roomDetailViewModel.handle(RoomDetailAction.RedactAction(eventId, reason)) } @@ -1121,7 +1125,7 @@ class RoomDetailFragment @Inject constructor( showSnackWithMessage(getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT) } is EventSharedAction.Redact -> { - promptReasonToRedactEvent(action.eventId) + promptConfirmationToRedactEvent(action.eventId, action.askForReason) } is EventSharedAction.Share -> { // TODO current data communication is too limited diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/EventSharedAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/EventSharedAction.kt index 8a8766c3ef..cba89d8481 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/EventSharedAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/EventSharedAction.kt @@ -55,7 +55,7 @@ sealed class EventSharedAction(@StringRes val titleRes: Int, data class Remove(val eventId: String) : EventSharedAction(R.string.remove, R.drawable.ic_trash, true) - data class Redact(val eventId: String) : + data class Redact(val eventId: String, val askForReason: Boolean) : EventSharedAction(R.string.message_action_item_redact, R.drawable.ic_delete, true) data class Cancel(val eventId: String) : diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 4b130e2103..5a52907bfa 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -227,7 +227,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } if (canRedact(timelineEvent, session.myUserId)) { - add(EventSharedAction.Redact(eventId)) + add(EventSharedAction.Redact(eventId, askForReason = informationData.senderId != session.myUserId)) } if (canCopy(msgType)) { diff --git a/vector/src/main/res/layout/dialog_delete_event.xml b/vector/src/main/res/layout/dialog_delete_event.xml index 8ca7a25113..08b0131f6a 100644 --- a/vector/src/main/res/layout/dialog_delete_event.xml +++ b/vector/src/main/res/layout/dialog_delete_event.xml @@ -43,8 +43,7 @@ + android:layout_height="wrap_content" /> From 1b413934b51b05c84cabed2a6257df78503cdc40 Mon Sep 17 00:00:00 2001 From: onurays Date: Thu, 13 Feb 2020 16:42:13 +0300 Subject: [PATCH 02/22] Set redaction reason as message body. --- .../action/MessageActionsViewModel.kt | 46 ++++++++++++++----- vector/src/main/res/values/strings_riotX.xml | 3 ++ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 5a52907bfa..e7c8cfec54 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -171,18 +171,23 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted return when (timelineEvent.root.getClearType()) { EventType.MESSAGE, EventType.STICKER -> { - val messageContent: MessageContent? = timelineEvent.getLastMessageContent() - if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { - val html = messageContent.formattedBody - ?.takeIf { it.isNotBlank() } - ?.let { htmlCompressor.compress(it) } - ?: messageContent.body + when (timelineEvent.root.isRedacted()) { + true -> getRedactionReason(timelineEvent) + false -> { + val messageContent: MessageContent? = timelineEvent.getLastMessageContent() + if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { + val html = messageContent.formattedBody + ?.takeIf { it.isNotBlank() } + ?.let { htmlCompressor.compress(it) } + ?: messageContent.body - eventHtmlRenderer.get().render(html) - } else if (messageContent is MessageVerificationRequestContent) { - stringProvider.getString(R.string.verification_request) - } else { - messageContent?.body + eventHtmlRenderer.get().render(html) + } else if (messageContent is MessageVerificationRequestContent) { + stringProvider.getString(R.string.verification_request) + } else { + messageContent?.body + } + } } } EventType.STATE_ROOM_NAME, @@ -200,6 +205,25 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } ?: "" } + private fun getRedactionReason(timelineEvent: TimelineEvent) = + (timelineEvent + .root + .unsignedData + ?.redactedEvent + ?.content + ?.get("reason") as? String) + ?.takeIf { it.isNotBlank() } + ?.let { reason -> + stringProvider.getString( + (R.string.event_redacted_by_user_reason_with_reason + .takeIf { timelineEvent.root.senderId == session.myUserId } + ?: R.string.event_redacted_by_admin_reason_with_reason), reason + ) + } ?: stringProvider.getString( + R.string.event_redacted_by_user_reason + .takeIf { timelineEvent.root.senderId == session.myUserId } + ?: R.string.event_redacted_by_admin_reason_with_reason) + private fun actionsForEvent(timelineEvent: TimelineEvent): List { val messageContent: MessageContent? = timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() ?: timelineEvent.root.getClearContent().toModel() diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 75790348de..ee70ac91ff 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -33,6 +33,9 @@ Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change. Include a reason Reason for redacting + + Event deleted by user, reason: %1$s + Event moderated by room admin, reason: %1$s From 983593d6476431bf0d6927978fda7d36fac7d8ff Mon Sep 17 00:00:00 2001 From: onurays Date: Thu, 13 Feb 2020 17:28:14 +0300 Subject: [PATCH 03/22] getRedactionReason function is refactored. --- .../action/MessageActionsViewModel.kt | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index e7c8cfec54..5e65330d6f 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -214,15 +214,17 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted ?.get("reason") as? String) ?.takeIf { it.isNotBlank() } ?.let { reason -> - stringProvider.getString( - (R.string.event_redacted_by_user_reason_with_reason - .takeIf { timelineEvent.root.senderId == session.myUserId } - ?: R.string.event_redacted_by_admin_reason_with_reason), reason - ) - } ?: stringProvider.getString( - R.string.event_redacted_by_user_reason - .takeIf { timelineEvent.root.senderId == session.myUserId } - ?: R.string.event_redacted_by_admin_reason_with_reason) + when (timelineEvent.root.senderId == session.myUserId) { + true -> stringProvider.getString(R.string.event_redacted_by_user_reason_with_reason, reason) + false -> stringProvider.getString(R.string.event_redacted_by_admin_reason_with_reason, reason) + } + } + ?: run { + when (timelineEvent.root.senderId == session.myUserId) { + true -> stringProvider.getString(R.string.event_redacted_by_user_reason) + false -> stringProvider.getString(R.string.event_redacted_by_admin_reason) + } + } private fun actionsForEvent(timelineEvent: TimelineEvent): List { val messageContent: MessageContent? = timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() From f28e4cf991e1eee58dd2071d03ae9153833882e6 Mon Sep 17 00:00:00 2001 From: onurays Date: Thu, 13 Feb 2020 17:57:38 +0300 Subject: [PATCH 04/22] Fix comparison of user ids. --- .../room/detail/timeline/action/MessageActionsViewModel.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 5e65330d6f..0fbdb61a45 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -214,13 +214,13 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted ?.get("reason") as? String) ?.takeIf { it.isNotBlank() } ?.let { reason -> - when (timelineEvent.root.senderId == session.myUserId) { + when (timelineEvent.root.senderId == timelineEvent.root.unsignedData?.redactedEvent?.senderId) { true -> stringProvider.getString(R.string.event_redacted_by_user_reason_with_reason, reason) false -> stringProvider.getString(R.string.event_redacted_by_admin_reason_with_reason, reason) } } ?: run { - when (timelineEvent.root.senderId == session.myUserId) { + when (timelineEvent.root.senderId == timelineEvent.root.unsignedData?.redactedEvent?.senderId) { true -> stringProvider.getString(R.string.event_redacted_by_user_reason) false -> stringProvider.getString(R.string.event_redacted_by_admin_reason) } From fd135e1eeb8eede9df5fb9700f1cd5a70809421e Mon Sep 17 00:00:00 2001 From: onurays Date: Thu, 13 Feb 2020 18:09:26 +0300 Subject: [PATCH 05/22] Compute message body for encrypted messages too. --- .../home/room/detail/timeline/action/MessageActionsViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 0fbdb61a45..04841266ec 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -170,6 +170,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private fun computeMessageBody(timelineEvent: TimelineEvent): CharSequence { return when (timelineEvent.root.getClearType()) { EventType.MESSAGE, + EventType.ENCRYPTED, EventType.STICKER -> { when (timelineEvent.root.isRedacted()) { true -> getRedactionReason(timelineEvent) From a250a895fe0a4acf08c671e03434edcd29ccd84f Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 14 Feb 2020 10:47:34 +0100 Subject: [PATCH 06/22] Remove redudant calls to eventBus post --- .../crypto/crosssigning/DefaultCrossSigningService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 920e5e9e4d..7fc3c0a549 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -637,9 +637,9 @@ internal class DefaultCrossSigningService @Inject constructor( // In this case it will change my MSK trust, and should then re-trigger a check of all other user trust setUserKeysAsTrusted(otherUserId, checkSelfTrust().isVerified()) } - - eventBus.post(CryptoToSessionUserTrustChange(userIds)) } + + eventBus.post(CryptoToSessionUserTrustChange(userIds)) } } From bf06b57baddaa0dd3d0dbebd37c2539ac6ff0f81 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 10 Feb 2020 10:51:58 +0100 Subject: [PATCH 07/22] Refactor Account Data Auto stash before rebase of "develop" --- .../java/im/vector/matrix/rx/RxSession.kt | 8 ++ .../matrix/android/api/session/Session.kt | 4 +- .../session/accountdata/AccountDataService.kt | 35 ++++++ .../api/session/events/model/EventType.kt | 3 + .../SharedSecretStorageService.kt | 73 +++++++++++++ .../crypto/secrets/SecretStorageService.kt | 39 +++++++ .../database/model/SessionRealmModule.kt | 3 +- .../database/model/UserAccountDataEntity.kt | 33 ++++++ .../parsing/AccountDataJsonAdapterFactory.kt | 29 +++++ .../internal/session/DefaultSession.kt | 5 +- .../android/internal/session/SessionModule.kt | 5 + .../sync/UserAccountDataSyncHandler.kt | 51 ++++++--- .../sync/model/accountdata/UserAccountData.kt | 2 +- .../accountdata/UserAccountDataFallback.kt | 2 +- .../model/accountdata/UserAccountDataSync.kt | 3 +- .../accountdata/DefaultAccountDataService.kt | 101 ++++++++++++++++++ .../accountdata/UpdateUserAccountDataTask.kt | 8 ++ vector/build.gradle | 3 + .../src/main/assets/open_source_licenses.html | 5 + .../im/vector/riotx/core/di/FragmentModule.kt | 6 ++ .../devtools/AccountDataEpoxyController.kt | 79 ++++++++++++++ .../settings/devtools/AccountDataFragment.kt | 65 +++++++++++ .../settings/devtools/AccountDataViewModel.kt | 65 +++++++++++ .../devtools/JsonViewerBottomSheetDialog.kt | 57 ++++++++++ .../main/res/layout/fragment_jsonviewer.xml | 16 +++ vector/src/main/res/values/strings_riotX.xml | 2 + .../xml/vector_settings_advanced_settings.xml | 8 ++ 27 files changed, 690 insertions(+), 20 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/accountdata/AccountDataService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/SecretStorageService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/UserAccountDataEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/AccountDataJsonAdapterFactory.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/DefaultAccountDataService.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/devtools/AccountDataEpoxyController.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/devtools/AccountDataFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/devtools/AccountDataViewModel.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/devtools/JsonViewerBottomSheetDialog.kt create mode 100644 vector/src/main/res/layout/fragment_jsonviewer.xml 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" />
    +    
    +
    +    
    +
    +        
     
         
     
    
    From 108ebea84e51c97ac91f96eb806e815cd90f0f13 Mon Sep 17 00:00:00 2001
    From: Valere 
    Date: Wed, 12 Feb 2020 11:28:13 +0100
    Subject: [PATCH 08/22] SSSS service + test
    
    ---
     .../java/im/vector/matrix/rx/RxSession.kt     |   4 +-
     .../internal/crypto/ssss/QuadSTests.kt        | 272 +++++++++++++
     .../matrix/android/api/session/Session.kt     |   3 +
     .../session/accountdata/AccountDataService.kt |  10 +-
     .../securestorage/EncryptedSecretContent.kt   |  47 +++
     .../session/securestorage/KeyInfoResult.kt    |  24 ++
     .../api/session/securestorage/KeySigner.kt    |  21 +
     .../securestorage/SSSSKeyCreationInfo.kt      |  23 ++
     .../api/session/securestorage/SSSSKeySpec.kt  |  67 ++++
     .../securestorage/SecretStorageKeyContent.kt  | 101 +++++
     .../securestorage/SharedSecretStorageError.kt |  30 ++
     .../SharedSecretStorageService.kt             |  37 +-
     .../internal/crypto/keysbackup/KeysBackup.kt  |  24 +-
     .../crypto/keysbackup/KeysBackupPassword.kt   |   8 +-
     .../secrets/DefaultSharedSecretStorage.kt     | 366 ++++++++++++++++++
     .../crypto/secrets/SecretStorageService.kt    |  39 --
     .../android/internal/di/MoshiProvider.kt      |   4 +-
     .../internal/session/DefaultSession.kt        |   5 +
     .../android/internal/session/SessionModule.kt |   5 +
     ...ataFallback.kt => UserAccountDataEvent.kt} |   2 +-
     .../accountdata/DefaultAccountDataService.kt  |  15 +-
     .../settings/devtools/AccountDataFragment.kt  |   6 +-
     22 files changed, 1043 insertions(+), 70 deletions(-)
     create mode 100644 matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
     create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/EncryptedSecretContent.kt
     create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/KeyInfoResult.kt
     create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/KeySigner.kt
     create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeyCreationInfo.kt
     create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeySpec.kt
     create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SecretStorageKeyContent.kt
     create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageError.kt
     create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt
     delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/SecretStorageService.kt
     rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/{UserAccountDataFallback.kt => UserAccountDataEvent.kt} (96%)
    
    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 21b4ffa05c..50f55577d0 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,7 +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 im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
     import io.reactivex.Observable
     import io.reactivex.Single
     
    @@ -123,7 +123,7 @@ class RxSession(private val session: Session) {
                     }
         }
     
    -    fun liveAccountData(filter: List): Observable> {
    +    fun liveAccountData(filter: List): Observable> {
             return session.getLiveAccountData(filter).asObservable()
                     .startWithCallable {
                         session.getAccountData(filter)
    diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
    new file mode 100644
    index 0000000000..4ea611b875
    --- /dev/null
    +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
    @@ -0,0 +1,272 @@
    +/*
    + * 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.ssss
    +
    +import android.util.Base64
    +import androidx.lifecycle.Observer
    +import androidx.test.ext.junit.runners.AndroidJUnit4
    +import im.vector.matrix.android.InstrumentedTest
    +import im.vector.matrix.android.api.MatrixCallback
    +import im.vector.matrix.android.api.session.Session
    +import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
    +import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent
    +import im.vector.matrix.android.api.session.securestorage.KeySigner
    +import im.vector.matrix.android.api.session.securestorage.SSSSKeyCreationInfo
    +import im.vector.matrix.android.api.session.securestorage.SecretStorageKeyContent
    +import im.vector.matrix.android.api.util.Optional
    +import im.vector.matrix.android.common.CommonTestHelper
    +import im.vector.matrix.android.common.CryptoTestHelper
    +import im.vector.matrix.android.common.SessionTestParams
    +import im.vector.matrix.android.common.TestConstants
    +import im.vector.matrix.android.common.TestMatrixCallback
    +import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecureStorage
    +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
    +import kotlinx.coroutines.Dispatchers
    +import kotlinx.coroutines.GlobalScope
    +import kotlinx.coroutines.launch
    +import kotlinx.coroutines.runBlocking
    +import org.junit.Assert
    +import org.junit.Assert.fail
    +import org.junit.FixMethodOrder
    +import org.junit.Test
    +import org.junit.runner.RunWith
    +import org.junit.runners.MethodSorters
    +import java.util.concurrent.CountDownLatch
    +
    +@RunWith(AndroidJUnit4::class)
    +@FixMethodOrder(MethodSorters.NAME_ASCENDING)
    +class QuadSTests : InstrumentedTest {
    +
    +    private val mTestHelper = CommonTestHelper(context())
    +    private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
    +
    +    @Test
    +    fun test_Generate4SKey() {
    +
    +        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
    +
    +        val aliceLatch = CountDownLatch(1)
    +
    +        val quadS = aliceSession.sharedSecretStorageService
    +
    +        val emptyKeySigner = object : KeySigner {
    +            override fun sign(canonicalJson: String): Map>? {
    +                return null
    +            }
    +        }
    +
    +        var recoveryKey: String? = null
    +
    +        val TEST_KEY_ID = "my.test.Key"
    +
    +        quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner,
    +                object : MatrixCallback {
    +
    +                    override fun onSuccess(data: SSSSKeyCreationInfo) {
    +                        recoveryKey = data.recoveryKey
    +                        aliceLatch.countDown()
    +                    }
    +
    +                    override fun onFailure(failure: Throwable) {
    +                        Assert.fail("onFailure " + failure.localizedMessage)
    +                        aliceLatch.countDown()
    +                    }
    +                })
    +
    +        mTestHelper.await(aliceLatch)
    +
    +        // Assert Account data is updated
    +        val accountDataLock = CountDownLatch(1)
    +        var accountData: UserAccountDataEvent? = null
    +
    +        val liveAccountData = runBlocking(Dispatchers.Main) {
    +            aliceSession.getLiveAccountData("m.secret_storage.key.$TEST_KEY_ID")
    +        }
    +        val accountDataObserver = Observer?> { t ->
    +            if (t?.getOrNull()?.type == "m.secret_storage.key.$TEST_KEY_ID") {
    +                accountData = t.getOrNull()
    +                accountDataLock.countDown()
    +            }
    +        }
    +        GlobalScope.launch(Dispatchers.Main) { liveAccountData.observeForever(accountDataObserver) }
    +
    +        mTestHelper.await(accountDataLock)
    +
    +        Assert.assertNotNull("Key should be stored in account data", accountData)
    +        val parsed = SecretStorageKeyContent.fromJson(accountData!!.content)
    +        Assert.assertNotNull("Key Content cannot be parsed", parsed)
    +        Assert.assertEquals("Unexpected Algorithm", DefaultSharedSecureStorage.ALGORITHM_CURVE25519_AES_SHA2, parsed!!.algorithm)
    +        Assert.assertEquals("Unexpected key name", "Test Key", parsed.name)
    +        Assert.assertNull("Key was not generated from passphrase", parsed.passphrase)
    +        Assert.assertNotNull("Pubkey should be defined", parsed.publicKey)
    +
    +        val privateKeySpec = Curve25519AesSha2KeySpec.fromRecoveryKey(recoveryKey!!)
    +        DefaultSharedSecureStorage.withOlmDecryption { olmPkDecryption ->
    +            val pubKey = olmPkDecryption.setPrivateKey(privateKeySpec!!.privateKey)
    +            Assert.assertEquals("Unexpected Public Key", pubKey, parsed.publicKey)
    +        }
    +
    +        // Set as default key
    +        quadS.setDefaultKey(TEST_KEY_ID, object : MatrixCallback {})
    +
    +        var defaultKeyAccountData: UserAccountDataEvent? = null
    +        val defaultDataLock = CountDownLatch(1)
    +
    +        val liveDefAccountData = runBlocking(Dispatchers.Main) {
    +            aliceSession.getLiveAccountData(DefaultSharedSecureStorage.DEFAULT_KEY_ID)
    +        }
    +        val accountDefDataObserver = Observer?> { t ->
    +            if (t?.getOrNull()?.type == DefaultSharedSecureStorage.DEFAULT_KEY_ID) {
    +                defaultKeyAccountData = t.getOrNull()!!
    +                defaultDataLock.countDown()
    +            }
    +        }
    +        GlobalScope.launch(Dispatchers.Main) { liveDefAccountData.observeForever(accountDefDataObserver) }
    +
    +        mTestHelper.await(defaultDataLock)
    +
    +
    +        Assert.assertNotNull(defaultKeyAccountData?.content)
    +        Assert.assertEquals("Unexpected default key ${defaultKeyAccountData?.content}", TEST_KEY_ID, defaultKeyAccountData?.content?.get("key"))
    +
    +
    +        mTestHelper.signout(aliceSession)
    +    }
    +
    +    @Test
    +    fun test_StoreSecret() {
    +        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
    +        val keyId = "My.Key"
    +        val info = generatedSecret(aliceSession, keyId, true)
    +
    +        // Store a secret
    +
    +        val storeCountDownLatch = CountDownLatch(1)
    +        val clearSecret = Base64.encodeToString("42".toByteArray(), Base64.NO_PADDING or Base64.NO_WRAP)
    +        aliceSession.sharedSecretStorageService.storeSecret(
    +                "secret.of.life",
    +                clearSecret,
    +                null, // default key
    +                TestMatrixCallback(storeCountDownLatch)
    +        )
    +
    +        val secretAccountData = assertAccountData(aliceSession,"secret.of.life" )
    +
    +        val encryptedContent = secretAccountData.content.get("encrypted") as? Map<*,*>
    +        Assert.assertNotNull("Element should be encrypted", encryptedContent)
    +        Assert.assertNotNull("Secret should be encrypted with default key", encryptedContent?.get(keyId))
    +
    +        val secret = EncryptedSecretContent.fromJson(encryptedContent?.get(keyId))
    +        Assert.assertNotNull(secret?.ciphertext)
    +        Assert.assertNotNull(secret?.mac)
    +        Assert.assertNotNull(secret?.ephemeral)
    +
    +        // Try to decrypt??
    +
    +        val keySpec = Curve25519AesSha2KeySpec.fromRecoveryKey(info.recoveryKey)
    +
    +        var decryptedSecret: String? = null
    +
    +        val decryptCountDownLatch = CountDownLatch(1)
    +        aliceSession.sharedSecretStorageService.getSecret("secret.of.life" ,
    +                 null, //default key
    +                keySpec!!,
    +                null,
    +                object : MatrixCallback {
    +                    override fun onFailure(failure: Throwable) {
    +                        fail("Fail to decrypt -> " +failure.localizedMessage)
    +                        decryptCountDownLatch.countDown()
    +                    }
    +
    +                    override fun onSuccess(data: String) {
    +                        decryptedSecret = data
    +                        decryptCountDownLatch.countDown()
    +                    }
    +                }
    +        )
    +        mTestHelper.await(decryptCountDownLatch)
    +
    +
    +        Assert.assertEquals("Secret mismatch", clearSecret, decryptedSecret)
    +        mTestHelper.signout(aliceSession)
    +
    +    }
    +
    +    private fun assertAccountData(session: Session, type: String): UserAccountDataEvent {
    +        val accountDataLock = CountDownLatch(1)
    +        var accountData: UserAccountDataEvent? = null
    +
    +        val liveAccountData = runBlocking(Dispatchers.Main) {
    +            session.getLiveAccountData(type)
    +        }
    +        val accountDataObserver = Observer?> { t ->
    +            if (t?.getOrNull()?.type == type) {
    +                accountData = t.getOrNull()
    +                accountDataLock.countDown()
    +            }
    +        }
    +        GlobalScope.launch(Dispatchers.Main) { liveAccountData.observeForever(accountDataObserver) }
    +        mTestHelper.await(accountDataLock)
    +
    +        Assert.assertNotNull("Account Data type:$type should be found", accountData)
    +
    +        return accountData!!
    +    }
    +
    +    private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SSSSKeyCreationInfo {
    +
    +        val quadS = session.sharedSecretStorageService
    +
    +        val emptyKeySigner = object : KeySigner {
    +            override fun sign(canonicalJson: String): Map>? {
    +                return null
    +            }
    +        }
    +
    +        var creationInfo: SSSSKeyCreationInfo? = null
    +
    +        val generateLatch = CountDownLatch(1)
    +
    +        quadS.generateKey(keyId, keyId, emptyKeySigner,
    +                object : MatrixCallback {
    +
    +                    override fun onSuccess(data: SSSSKeyCreationInfo) {
    +                        creationInfo = data
    +                        generateLatch.countDown()
    +                    }
    +
    +                    override fun onFailure(failure: Throwable) {
    +                        Assert.fail("onFailure " + failure.localizedMessage)
    +                        generateLatch.countDown()
    +                    }
    +                })
    +
    +        mTestHelper.await(generateLatch)
    +
    +        Assert.assertNotNull(creationInfo)
    +
    +        assertAccountData(session, "m.secret_storage.key.$keyId")
    +        if (asDefault) {
    +            val setDefaultLatch = CountDownLatch(1)
    +            quadS.setDefaultKey(keyId, TestMatrixCallback(setDefaultLatch))
    +            mTestHelper.await(setDefaultLatch)
    +            assertAccountData(session, DefaultSharedSecureStorage.DEFAULT_KEY_ID)
    +        }
    +
    +        return creationInfo!!
    +    }
    +}
    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 5255d7c224..4167131c68 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
    @@ -34,6 +34,7 @@ import im.vector.matrix.android.api.session.pushers.PushersService
     import im.vector.matrix.android.api.session.room.RoomDirectoryService
     import im.vector.matrix.android.api.session.room.RoomService
     import im.vector.matrix.android.api.session.securestorage.SecureStorageService
    +import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService
     import im.vector.matrix.android.api.session.signout.SignOutService
     import im.vector.matrix.android.api.session.sync.FilterService
     import im.vector.matrix.android.api.session.sync.SyncState
    @@ -161,4 +162,6 @@ interface Session :
              */
             fun onGlobalError(globalError: GlobalError)
         }
    +
    +    val sharedSecretStorageService: SharedSecretStorageService
     }
    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
    index 7a3b9f0171..a832921dc7 100644
    --- 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
    @@ -19,17 +19,17 @@ 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
    +import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
     
     interface AccountDataService {
     
    -    fun getAccountData(type: String): UserAccountData?
    +    fun getAccountData(type: String): UserAccountDataEvent?
     
    -    fun getLiveAccountData(type: String): LiveData>
    +    fun getLiveAccountData(type: String): LiveData>
     
    -    fun getAccountData(filterType: List): List
    +    fun getAccountData(filterType: List): List
     
    -    fun getLiveAccountData(filterType: List): LiveData>
    +    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/securestorage/EncryptedSecretContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/EncryptedSecretContent.kt
    new file mode 100644
    index 0000000000..4c8b51c668
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/EncryptedSecretContent.kt
    @@ -0,0 +1,47 @@
    +/*
    + * 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.securestorage
    +
    +import com.squareup.moshi.Json
    +import com.squareup.moshi.JsonClass
    +import im.vector.matrix.android.internal.di.MoshiProvider
    +
    +/**
    + * The account_data will have an encrypted property that is a map from key ID to an object.
    + * The algorithm from the m.secret_storage.key.[key ID] data for the given key defines how the other properties are interpreted,
    + * though it's expected that most encryption schemes would have ciphertext and mac properties,
    + * where the ciphertext property is the unpadded base64-encoded ciphertext, and the mac is used to ensure the integrity of the data.
    + */
    +@JsonClass(generateAdapter = true)
    +data class EncryptedSecretContent(
    +        /** unpadded base64-encoded ciphertext */
    +        @Json(name = "ciphertext") val ciphertext: String? = null,
    +        @Json(name = "mac") val mac: String? = null,
    +        @Json(name = "ephemeral") val ephemeral: String? = null
    +) {
    +    companion object {
    +        /**
    +         * Facility method to convert from object which must be comprised of maps, lists,
    +         * strings, numbers, booleans and nulls.
    +         */
    +        fun fromJson(obj: Any?): EncryptedSecretContent? {
    +            return MoshiProvider.providesMoshi()
    +                    .adapter(EncryptedSecretContent::class.java)
    +                    .fromJsonValue(obj)
    +        }
    +    }
    +}
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/KeyInfoResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/KeyInfoResult.kt
    new file mode 100644
    index 0000000000..940f5298ef
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/KeyInfoResult.kt
    @@ -0,0 +1,24 @@
    +/*
    + * 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.securestorage
    +
    +sealed class KeyInfoResult {
    +    data class Success(val keyInfo: KeyInfo) : KeyInfoResult()
    +    data class Error(val error: SharedSecretStorageError) : KeyInfoResult()
    +
    +    fun isSuccess(): Boolean = this is Success
    +}
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/KeySigner.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/KeySigner.kt
    new file mode 100644
    index 0000000000..2cd7a74f31
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/KeySigner.kt
    @@ -0,0 +1,21 @@
    +/*
    + * 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.securestorage
    +
    +interface KeySigner {
    +    fun sign(canonicalJson: String): Map>?
    +}
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeyCreationInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeyCreationInfo.kt
    new file mode 100644
    index 0000000000..3c629290fc
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeyCreationInfo.kt
    @@ -0,0 +1,23 @@
    +/*
    + * 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.securestorage
    +
    +data class SSSSKeyCreationInfo (
    +        val keyId: String = "",
    +        var content: SecretStorageKeyContent?,
    +        val recoveryKey: String = ""
    +)
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeySpec.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeySpec.kt
    new file mode 100644
    index 0000000000..dcdba38d8e
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeySpec.kt
    @@ -0,0 +1,67 @@
    +/*
    + * 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.securestorage
    +
    +import im.vector.matrix.android.api.listeners.ProgressListener
    +import im.vector.matrix.android.internal.crypto.keysbackup.deriveKey
    +import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
    +
    +/** Tag class */
    +interface SSSSKeySpec
    +
    +data class Curve25519AesSha2KeySpec(
    +        val privateKey: ByteArray
    +) : SSSSKeySpec {
    +
    +    companion object {
    +
    +        fun fromPassphrase(passphrase: String, salt: String, iterations: Int, progressListener: ProgressListener?): Curve25519AesSha2KeySpec {
    +            return Curve25519AesSha2KeySpec(
    +                    privateKey = deriveKey(
    +                            passphrase,
    +                            salt,
    +                            iterations,
    +                            progressListener
    +                    )
    +            )
    +        }
    +
    +        fun fromRecoveryKey(recoveryKey: String): Curve25519AesSha2KeySpec? {
    +            return extractCurveKeyFromRecoveryKey(recoveryKey)?.let {
    +                Curve25519AesSha2KeySpec(
    +                        privateKey = it
    +                )
    +            }
    +        }
    +    }
    +
    +    override fun equals(other: Any?): Boolean {
    +        if (this === other) return true
    +        if (javaClass != other?.javaClass) return false
    +
    +        other as Curve25519AesSha2KeySpec
    +
    +        if (!privateKey.contentEquals(other.privateKey)) return false
    +
    +        return true
    +    }
    +
    +    override fun hashCode(): Int {
    +        return privateKey.contentHashCode()
    +    }
    +}
    +
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SecretStorageKeyContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SecretStorageKeyContent.kt
    new file mode 100644
    index 0000000000..0aba3d700d
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SecretStorageKeyContent.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.api.session.securestorage
    +
    +import com.squareup.moshi.Json
    +import com.squareup.moshi.JsonClass
    +import im.vector.matrix.android.internal.di.MoshiProvider
    +import im.vector.matrix.android.internal.util.JsonCanonicalizer
    +
    +/**
    + *
    + * The contents of the account data for the key will include an algorithm property, which indicates the encryption algorithm used, as well as a name property,
    + * which is a human-readable name.
    + * The contents will be signed as signed JSON using the user's master cross-signing key. Other properties depend on the encryption algorithm.
    + *
    + *
    + * "content": {
    + *     "algorithm": "m.secret_storage.v1.curve25519-aes-sha2",
    + *     "passphrase": {
    + *         "algorithm": "m.pbkdf2",
    + *         "iterations": 500000,
    + *         "salt": "IrswcMWnYieBALCAOMBw9k93xSzlc2su"
    + *     },
    + *     "pubkey": "qql1q3IvBbwMU97zLnyh9HYW5x/zqTy5eoK1n+9fm1Y",
    + *     "signatures": {
    + *         "@valere35:matrix.org": {
    + *             "ed25519:nOUQYiH9L8uKp5JajqiQyv+Loa3+lsdil7UBverz/Ko": "QtePmwfUL7+SHYRJT/HaTgF7gUFog1E/wtUCt0qc5aB8N+Sz5iCOvQ0KtaFHQ5SJzsBlYH8k7ejoBc0RcnU7BA"
    + *         }
    + *     }
    + * }
    + */
    +
    +data class KeyInfo(
    +        val id: String,
    +        val content: SecretStorageKeyContent
    +)
    +
    +@JsonClass(generateAdapter = true)
    +data class SecretStorageKeyContent(
    +        /** Currently support m.secret_storage.v1.curve25519-aes-sha2 */
    +        @Json(name = "algorithm") val algorithm: String? = null,
    +        @Json(name = "name") val name: String? = null,
    +        @Json(name = "passphrase") val passphrase: SSSSPassphrase? = null,
    +        @Json(name = "pubkey") val publicKey: String? = null,
    +        @Json(name = "signatures")
    +        var signatures: Map>? = null
    +) {
    +
    +    private fun signalableJSONDictionary(): Map {
    +        val map = HashMap()
    +        algorithm?.let { map["algorithm"] = it }
    +        name?.let { map["name"] = it }
    +        publicKey?.let { map["pubkey"] = it }
    +        passphrase?.let { ssspp ->
    +            map["passphrase"] = mapOf(
    +                    "algorithm" to ssspp.algorithm,
    +                    "iterations" to ssspp.salt,
    +                    "salt" to ssspp.salt
    +            )
    +        }
    +        return map
    +    }
    +
    +    fun canonicalSignable(): String {
    +        return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
    +    }
    +
    +    companion object {
    +        /**
    +         * Facility method to convert from object which must be comprised of maps, lists,
    +         * strings, numbers, booleans and nulls.
    +         */
    +        fun fromJson(obj: Any?): SecretStorageKeyContent? {
    +            return MoshiProvider.providesMoshi()
    +                    .adapter(SecretStorageKeyContent::class.java)
    +                    .fromJsonValue(obj)
    +        }
    +    }
    +}
    +
    +@JsonClass(generateAdapter = true)
    +data class SSSSPassphrase(
    +        @Json(name = "algorithm") val algorithm: String?,
    +        @Json(name = "iterations") val iterations: Int,
    +        @Json(name = "salt") val salt: String?
    +)
    +
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageError.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageError.kt
    new file mode 100644
    index 0000000000..1ff5cf12f3
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageError.kt
    @@ -0,0 +1,30 @@
    +/*
    + * 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.securestorage
    +
    +sealed class SharedSecretStorageError(message: String?) : Throwable(message) {
    +
    +    data class UnknownSecret(val secretName: String) : SharedSecretStorageError("Unknown Secret $secretName")
    +    data class UnknownKey(val keyId: String) : SharedSecretStorageError("Unknown key $keyId")
    +    data class UnknownAlgorithm(val keyId: String) : SharedSecretStorageError("Unknown algorithm $keyId")
    +    data class UnsupportedAlgorithm(val algorithm: String) : SharedSecretStorageError("Unknown algorithm $algorithm")
    +    data class SecretNotEncrypted(val secretName: String) : SharedSecretStorageError("Missing content for secret $secretName")
    +    data class SecretNotEncryptedWithKey(val secretName: String, val keyId: String) : SharedSecretStorageError("Missing content for secret $secretName with key $keyId")
    +    object BadKeyFormat : SharedSecretStorageError("Bad Key Format")
    +    object ParsingError : SharedSecretStorageError("parsing Error")
    +    data class OtherError(val reason: Throwable) : SharedSecretStorageError(reason.localizedMessage)
    +}
    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
    index fa2042e506..9923aab606 100644
    --- 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
    @@ -17,6 +17,7 @@
     package im.vector.matrix.android.api.session.securestorage
     
     import im.vector.matrix.android.api.MatrixCallback
    +import im.vector.matrix.android.api.listeners.ProgressListener
     
     /**
      * Some features may require clients to store encrypted data on the server so that it can be shared securely between clients.
    @@ -39,7 +40,29 @@ interface SharedSecretStorageService {
          *
          * @return {string} the ID of the key
          */
    -    fun addKey(algorithm: String, opts: Map, keyId: String, callback: MatrixCallback)
    +    fun generateKey(keyId: String,
    +                    keyName: String,
    +                    keySigner: KeySigner,
    +                    callback: MatrixCallback)
    +
    +    fun generateKeyWithPassphrase(keyId: String,
    +                                  keyName: String,
    +                                  passphrase: String,
    +                                  keySigner: KeySigner,
    +                                  progressListener: ProgressListener?,
    +                                  callback: MatrixCallback)
    +
    +    fun getKey(keyId: String): KeyInfoResult
    +
    +    /**
    +     * A key can be marked as the "default" key by setting the user's account_data with event type m.secret_storage.default_key
    +     * to an object that has the ID of the key as its key property.
    +     * The default key will be used to encrypt all secrets that the user would expect to be available on all their clients.
    +     * Unless the user specifies otherwise, clients will try to use the default key to decrypt secrets.
    +     */
    +    fun getDefaultKey(): KeyInfoResult
    +
    +    fun setDefaultKey(keyId: String, callback: MatrixCallback)
     
         /**
          * Check whether we have a key with a given ID.
    @@ -51,23 +74,29 @@ interface SharedSecretStorageService {
     
         /**
          * Store an encrypted secret on the server
    +     * Clients MUST ensure that the key is trusted before using it to encrypt secrets.
          *
          * @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.
    +     * @param keys The list of (ID,privateKey) of the keys to use to encrypt the secret.
          */
         fun storeSecret(name: String, secretBase64: String, keys: List?, callback: MatrixCallback)
     
    +    /**
    +     * Use this call to determine which SSSSKeySpec to use for requesting secret
    +     */
    +    fun getAlgorithmsForSecret(name: String): List
     
         /**
          * 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 keyId The id of the key that should be used to decrypt (null for default key)
          * @param privateKey the passphrase/secret
          *
          * @return The decrypted value
          */
    -    fun getSecret(name: String, keyId: String, privateKey: String) : String
    +    @Throws
     
    +    fun getSecret(name: String, keyId: String?, secretKey: SSSSKeySpec, callback: MatrixCallback)
     }
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt
    index 7906005046..595b55a7a6 100644
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt
    @@ -40,8 +40,28 @@ import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersi
     import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrustSignature
     import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupAuthData
     import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
    -import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.*
    -import im.vector.matrix.android.internal.crypto.keysbackup.tasks.*
    +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeysResult
    +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody
    +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeyBackupData
    +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysBackupData
    +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
    +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
    +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.RoomKeysBackupData
    +import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody
    +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
    +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteBackupTask
    +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteRoomSessionDataTask
    +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteRoomSessionsDataTask
    +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteSessionsDataTask
    +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask
    +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask
    +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetRoomSessionDataTask
    +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetRoomSessionsDataTask
    +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetSessionsDataTask
    +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessionDataTask
    +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask
    +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
    +import im.vector.matrix.android.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
     import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey
     import im.vector.matrix.android.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
     import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPassword.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPassword.kt
    index 344ba61277..2429c1e658 100644
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPassword.kt
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupPassword.kt
    @@ -83,10 +83,10 @@ fun retrievePrivateKeyWithPassword(password: String,
      * @return a private key.
      */
     @WorkerThread
    -private fun deriveKey(password: String,
    -                      salt: String,
    -                      iterations: Int,
    -                      progressListener: ProgressListener?): ByteArray {
    +fun deriveKey(password: String,
    +              salt: String,
    +              iterations: Int,
    +              progressListener: ProgressListener?): ByteArray {
         // Note: copied and adapted from MXMegolmExportEncryption
         val t0 = System.currentTimeMillis()
     
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt
    new file mode 100644
    index 0000000000..a4cf02aeae
    --- /dev/null
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt
    @@ -0,0 +1,366 @@
    +/*
    + * 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.listeners.ProgressListener
    +import im.vector.matrix.android.api.session.accountdata.AccountDataService
    +import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
    +import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent
    +import im.vector.matrix.android.api.session.securestorage.KeyInfo
    +import im.vector.matrix.android.api.session.securestorage.KeyInfoResult
    +import im.vector.matrix.android.api.session.securestorage.KeySigner
    +import im.vector.matrix.android.api.session.securestorage.SSSSKeyCreationInfo
    +import im.vector.matrix.android.api.session.securestorage.SSSSKeySpec
    +import im.vector.matrix.android.api.session.securestorage.SSSSPassphrase
    +import im.vector.matrix.android.api.session.securestorage.SecretStorageKeyContent
    +import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageError
    +import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService
    +import im.vector.matrix.android.internal.crypto.keysbackup.generatePrivateKeyWithPassword
    +import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey
    +import im.vector.matrix.android.internal.extensions.foldToCallback
    +import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
    +import kotlinx.coroutines.CoroutineScope
    +import kotlinx.coroutines.launch
    +import org.matrix.olm.OlmPkDecryption
    +import org.matrix.olm.OlmPkEncryption
    +import org.matrix.olm.OlmPkMessage
    +import javax.inject.Inject
    +
    +internal class DefaultSharedSecureStorage @Inject constructor(
    +        private val accountDataService: AccountDataService,
    +        private val coroutineDispatchers: MatrixCoroutineDispatchers,
    +        private val cryptoCoroutineScope: CoroutineScope
    +) : SharedSecretStorageService {
    +
    +    override fun generateKey(keyId: String,
    +                             keyName: String,
    +                             keySigner: KeySigner,
    +                             callback: MatrixCallback) {
    +
    +        cryptoCoroutineScope.launch(coroutineDispatchers.main) {
    +            val pkDecryption = OlmPkDecryption()
    +            val pubKey: String
    +            val privateKey: ByteArray
    +            try {
    +                pubKey = pkDecryption.generateKey()
    +                privateKey = pkDecryption.privateKey()
    +            } catch (failure: Throwable) {
    +                return@launch Unit.also {
    +                    callback.onFailure(failure)
    +                }
    +            } finally {
    +                pkDecryption.releaseDecryption()
    +            }
    +
    +            val storageKeyContent = SecretStorageKeyContent(
    +                    name = keyName,
    +                    algorithm = ALGORITHM_CURVE25519_AES_SHA2,
    +                    passphrase = null,
    +                    publicKey = pubKey
    +            )
    +
    +            val signedContent = keySigner.sign(storageKeyContent.canonicalSignable())?.let {
    +                storageKeyContent.copy(
    +                        signatures = it
    +                )
    +            } ?: storageKeyContent
    +
    +            accountDataService.updateAccountData(
    +                    "$KEY_ID_BASE.$keyId",
    +                    signedContent,
    +                    object : MatrixCallback {
    +                        override fun onFailure(failure: Throwable) {
    +                            callback.onFailure(failure)
    +                        }
    +
    +                        override fun onSuccess(data: Unit) {
    +                            callback.onSuccess(SSSSKeyCreationInfo(
    +                                    keyId = keyId,
    +                                    content = storageKeyContent,
    +                                    recoveryKey = computeRecoveryKey(privateKey)
    +                            ))
    +                        }
    +                    }
    +            )
    +        }
    +    }
    +
    +    override fun generateKeyWithPassphrase(keyId: String,
    +                                           keyName: String,
    +                                           passphrase: String,
    +                                           keySigner: KeySigner,
    +                                           progressListener: ProgressListener?,
    +                                           callback: MatrixCallback) {
    +        cryptoCoroutineScope.launch(coroutineDispatchers.main) {
    +
    +            val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener)
    +
    +            val pkDecryption = OlmPkDecryption()
    +            val pubKey: String
    +            try {
    +                pubKey = pkDecryption.setPrivateKey(privatePart.privateKey)
    +            } catch (failure: Throwable) {
    +                return@launch Unit.also {
    +                    callback.onFailure(failure)
    +                }
    +            } finally {
    +                pkDecryption.releaseDecryption()
    +            }
    +
    +            val storageKeyContent = SecretStorageKeyContent(
    +                    algorithm = ALGORITHM_CURVE25519_AES_SHA2,
    +                    passphrase = SSSSPassphrase(algorithm = "m.pbkdf2", iterations = privatePart.iterations, salt = privatePart.salt),
    +                    publicKey = pubKey
    +            )
    +
    +            val signedContent = keySigner.sign(storageKeyContent.canonicalSignable())?.let {
    +                storageKeyContent.copy(
    +                        signatures = it
    +                )
    +            } ?: storageKeyContent
    +
    +            accountDataService.updateAccountData(
    +                    "$KEY_ID_BASE.$keyId",
    +                    signedContent,
    +                    object : MatrixCallback {
    +                        override fun onFailure(failure: Throwable) {
    +                            callback.onFailure(failure)
    +                        }
    +
    +                        override fun onSuccess(data: Unit) {
    +                            callback.onSuccess(SSSSKeyCreationInfo(
    +                                    keyId = keyId,
    +                                    content = storageKeyContent,
    +                                    recoveryKey = computeRecoveryKey(privatePart.privateKey)
    +                            ))
    +                        }
    +                    }
    +            )
    +
    +        }
    +    }
    +
    +    override fun hasKey(keyId: String): Boolean {
    +        return accountDataService.getAccountData("$KEY_ID_BASE.$keyId") != null
    +    }
    +
    +    override fun getKey(keyId: String): KeyInfoResult {
    +        val accountData = accountDataService.getAccountData("$KEY_ID_BASE.$keyId")
    +                ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(keyId))
    +        return SecretStorageKeyContent.fromJson(accountData.content)?.let {
    +            KeyInfoResult.Success(
    +                    KeyInfo(id = keyId, content = it)
    +            )
    +        } ?: KeyInfoResult.Error(SharedSecretStorageError.UnknownAlgorithm(keyId))
    +    }
    +
    +    override fun setDefaultKey(keyId: String, callback: MatrixCallback) {
    +        val existingKey = getKey(keyId)
    +        if (existingKey is KeyInfoResult.Success) {
    +            accountDataService.updateAccountData(DEFAULT_KEY_ID,
    +                    mapOf("key" to keyId),
    +                    callback
    +            )
    +        } else {
    +            callback.onFailure(SharedSecretStorageError.UnknownKey(keyId))
    +        }
    +    }
    +
    +    override fun getDefaultKey(): KeyInfoResult {
    +        val accountData = accountDataService.getAccountData(DEFAULT_KEY_ID)
    +                ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(DEFAULT_KEY_ID))
    +        val keyId = accountData.content["key"] as? String
    +                ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(DEFAULT_KEY_ID))
    +        return getKey(keyId)
    +    }
    +
    +    override fun storeSecret(name: String, secretBase64: String, keys: List?, callback: MatrixCallback) {
    +
    +        cryptoCoroutineScope.launch(coroutineDispatchers.main) {
    +            val encryptedContents = HashMap()
    +            try {
    +
    +                if (keys == null || keys.isEmpty()) {
    +                    //use default key
    +                    val key = getDefaultKey()
    +                    when (key) {
    +                        is KeyInfoResult.Success -> {
    +                            if (key.keyInfo.content.algorithm == ALGORITHM_CURVE25519_AES_SHA2) {
    +                                withOlmEncryption { olmEncrypt ->
    +                                    olmEncrypt.setRecipientKey(key.keyInfo.content.publicKey)
    +                                    val encryptedResult = olmEncrypt.encrypt(secretBase64)
    +                                    encryptedContents[key.keyInfo.id] = EncryptedSecretContent(
    +                                            ciphertext = encryptedResult.mCipherText,
    +                                            ephemeral = encryptedResult.mEphemeralKey,
    +                                            mac = encryptedResult.mMac
    +                                    )
    +                                }
    +                            } else {
    +                                // Unknown algorithm
    +                                callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: ""))
    +                                return@launch
    +                            }
    +                        }
    +                        is KeyInfoResult.Error   -> {
    +                            callback.onFailure(key.error)
    +                            return@launch
    +                        }
    +                    }
    +                } else {
    +                    keys.forEach {
    +                        val keyId = it
    +                        // encrypt the content
    +                        val key = getKey(keyId)
    +                        when (key) {
    +                            is KeyInfoResult.Success -> {
    +                                if (key.keyInfo.content.algorithm == ALGORITHM_CURVE25519_AES_SHA2) {
    +                                    withOlmEncryption { olmEncrypt ->
    +                                        olmEncrypt.setRecipientKey(key.keyInfo.content.publicKey)
    +                                        val encryptedResult = olmEncrypt.encrypt(secretBase64)
    +                                        encryptedContents[keyId] = EncryptedSecretContent(
    +                                                ciphertext = encryptedResult.mCipherText,
    +                                                ephemeral = encryptedResult.mEphemeralKey,
    +                                                mac = encryptedResult.mMac
    +                                        )
    +                                    }
    +                                } else {
    +                                    // Unknown algorithm
    +                                    callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: ""))
    +                                    return@launch
    +                                }
    +                            }
    +                            is KeyInfoResult.Error   -> {
    +                                callback.onFailure(key.error)
    +                                return@launch
    +                            }
    +                        }
    +                    }
    +                }
    +
    +                accountDataService.updateAccountData(
    +                        type = name,
    +                        data = mapOf(
    +                                "encrypted" to encryptedContents
    +                        ),
    +                        callback = callback
    +                )
    +            } catch (failure: Throwable) {
    +                callback.onFailure(failure)
    +            }
    +
    +        }
    +
    +        // Add default key
    +    }
    +
    +    override fun getAlgorithmsForSecret(name: String): List {
    +        val accountData = accountDataService.getAccountData(name)
    +                ?: return listOf(KeyInfoResult.Error(SharedSecretStorageError.UnknownSecret(name)))
    +        val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *>
    +                ?: return listOf(KeyInfoResult.Error(SharedSecretStorageError.SecretNotEncrypted(name)))
    +
    +        val results = ArrayList()
    +        encryptedContent.keys.forEach {
    +            (it as? String)?.let { keyId ->
    +                results.add(getKey(keyId))
    +            }
    +        }
    +        return results
    +    }
    +
    +    override fun getSecret(name: String, keyId: String?, secretKey: SSSSKeySpec, callback: MatrixCallback) {
    +        val accountData = accountDataService.getAccountData(name) ?: return Unit.also {
    +            callback.onFailure(SharedSecretStorageError.UnknownSecret(name))
    +        }
    +        val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *> ?: return Unit.also {
    +            callback.onFailure(SharedSecretStorageError.SecretNotEncrypted(name))
    +        }
    +        val key = keyId?.let { getKey(it) } as? KeyInfoResult.Success ?: getDefaultKey() as? KeyInfoResult.Success ?: return Unit.also {
    +            callback.onFailure(SharedSecretStorageError.UnknownKey(name))
    +        }
    +
    +        val encryptedForKey = encryptedContent[key.keyInfo.id] ?: return Unit.also {
    +            callback.onFailure(SharedSecretStorageError.SecretNotEncryptedWithKey(name, key.keyInfo.id))
    +        }
    +
    +        val secretContent = EncryptedSecretContent.fromJson(encryptedForKey)
    +                ?: return Unit.also {
    +                    callback.onFailure(SharedSecretStorageError.ParsingError)
    +                }
    +
    +        val algorithm = key.keyInfo.content
    +        if (ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) {
    +            val keySpec = secretKey as? Curve25519AesSha2KeySpec ?: return Unit.also {
    +                callback.onFailure(SharedSecretStorageError.BadKeyFormat)
    +            }
    +            cryptoCoroutineScope.launch(coroutineDispatchers.main) {
    +                kotlin.runCatching {
    +                    // decryt from recovery key
    +                    val keyBytes = keySpec.privateKey
    +                    val decryption = OlmPkDecryption()
    +                    try {
    +                        decryption.setPrivateKey(keyBytes)
    +                        decryption.decrypt(OlmPkMessage().apply {
    +                            mCipherText = secretContent.ciphertext
    +                            mEphemeralKey = secretContent.ephemeral
    +                            mMac = secretContent.mac
    +                        })
    +                    } catch (failure: Throwable) {
    +                        throw failure
    +                    } finally {
    +                        decryption.releaseDecryption()
    +                    }
    +                }.foldToCallback(callback)
    +            }
    +        } else {
    +            callback.onFailure(SharedSecretStorageError.UnsupportedAlgorithm(algorithm.algorithm ?: ""))
    +        }
    +    }
    +
    +    companion object {
    +        const val KEY_ID_BASE = "m.secret_storage.key"
    +        const val ENCRYPTED = "encrypted"
    +        const val DEFAULT_KEY_ID = "m.secret_storage.default_key"
    +
    +        const val ALGORITHM_CURVE25519_AES_SHA2 = "m.secret_storage.v1.curve25519-aes-sha2"
    +
    +        fun withOlmEncryption(block: (OlmPkEncryption) -> Unit) {
    +            val olmPkEncryption = OlmPkEncryption()
    +            try {
    +                block(olmPkEncryption)
    +            } catch (failure: Throwable) {
    +                throw failure
    +            } finally {
    +                olmPkEncryption.releaseEncryption()
    +            }
    +        }
    +
    +        fun withOlmDecryption(block: (OlmPkDecryption) -> Unit) {
    +            val olmPkDecryption = OlmPkDecryption()
    +            try {
    +                block(olmPkDecryption)
    +            } catch (failure: Throwable) {
    +                throw failure
    +            } finally {
    +                olmPkDecryption.releaseDecryption()
    +            }
    +        }
    +    }
    +}
    +
    +
    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
    deleted file mode 100644
    index 33306104eb..0000000000
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/SecretStorageService.kt
    +++ /dev/null
    @@ -1,39 +0,0 @@
    -/*
    - * 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/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt
    index c19c686329..cd4e9abbc1 100644
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt
    @@ -23,7 +23,7 @@ import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter
     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.UserAccountDataEvent
     import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataIgnoredUsers
     import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataPushRules
     
    @@ -31,7 +31,7 @@ object MoshiProvider {
     
         private val moshi: Moshi = Moshi.Builder()
                 .add(UriMoshiAdapter())
    -            .add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataFallback::class.java)
    +            .add(RuntimeJsonAdapterFactory.of(UserAccountData::class.java, "type", UserAccountDataEvent::class.java)
                         .registerSubtype(UserAccountDataDirectMessages::class.java, UserAccountData.TYPE_DIRECT_MESSAGES)
                         .registerSubtype(UserAccountDataIgnoredUsers::class.java, UserAccountData.TYPE_IGNORED_USER_LIST)
                         .registerSubtype(UserAccountDataPushRules::class.java, UserAccountData.TYPE_PUSH_RULES)
    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 afe37c1c41..46264ceb85 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
    @@ -38,6 +38,7 @@ import im.vector.matrix.android.api.session.pushers.PushersService
     import im.vector.matrix.android.api.session.room.RoomDirectoryService
     import im.vector.matrix.android.api.session.room.RoomService
     import im.vector.matrix.android.api.session.securestorage.SecureStorageService
    +import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService
     import im.vector.matrix.android.api.session.signout.SignOutService
     import im.vector.matrix.android.api.session.sync.FilterService
     import im.vector.matrix.android.api.session.sync.SyncState
    @@ -93,6 +94,7 @@ internal class DefaultSession @Inject constructor(
             private val initialSyncProgressService: Lazy,
             private val homeServerCapabilitiesService: Lazy,
             private val accountDataService: Lazy,
    +        private val _sharedSecretStorageService: Lazy,
             private val shieldTrustUpdater: ShieldTrustUpdater)
         : Session,
             RoomService by roomService.get(),
    @@ -111,6 +113,9 @@ internal class DefaultSession @Inject constructor(
             ProfileService by profileService.get(),
             AccountDataService by accountDataService.get() {
     
    +    override val sharedSecretStorageService: SharedSecretStorageService
    +        get() = _sharedSecretStorageService.get()
    +
         private var isOpen = false
     
         private var syncThread: SyncThread? = null
    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 636e61c93f..02d8a35009 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
    @@ -35,6 +35,8 @@ 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.api.session.securestorage.SharedSecretStorageService
    +import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecureStorage
     import im.vector.matrix.android.internal.crypto.verification.VerificationMessageLiveObserver
     import im.vector.matrix.android.internal.database.LiveEntityObserver
     import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory
    @@ -268,4 +270,7 @@ internal abstract class SessionModule {
     
         @Binds
         abstract fun bindAccountDataServiceService(accountDataService: DefaultAccountDataService): AccountDataService
    +
    +    @Binds
    +    abstract fun bindSharedSecuredSecretStorageService(service: DefaultSharedSecureStorage): SharedSecretStorageService
     }
    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/UserAccountDataEvent.kt
    similarity index 96%
    rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataFallback.kt
    rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/model/accountdata/UserAccountDataEvent.kt
    index d965d2ffee..a4ba0fc91a 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/UserAccountDataEvent.kt
    @@ -20,7 +20,7 @@ import com.squareup.moshi.Json
     import com.squareup.moshi.JsonClass
     
     @JsonClass(generateAdapter = true)
    -data class UserAccountDataFallback(
    +data class UserAccountDataEvent(
             @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/user/accountdata/DefaultAccountDataService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/user/accountdata/DefaultAccountDataService.kt
    index 2aeef3cc0d..0bb57f0dae 100644
    --- 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
    @@ -28,8 +28,7 @@ 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.session.sync.model.accountdata.UserAccountDataEvent
     import im.vector.matrix.android.internal.task.TaskExecutor
     import im.vector.matrix.android.internal.task.configureWith
     import javax.inject.Inject
    @@ -44,17 +43,17 @@ internal class DefaultAccountDataService @Inject constructor(
         private val moshi = MoshiProvider.providesMoshi()
         private val adapter = moshi.adapter>(JSON_DICT_PARAMETERIZED_TYPE)
     
    -    override fun getAccountData(type: String): UserAccountData? {
    +    override fun getAccountData(type: String): UserAccountDataEvent? {
             return getAccountData(listOf(type)).firstOrNull()
         }
     
    -    override fun getLiveAccountData(type: String): LiveData> {
    +    override fun getLiveAccountData(type: String): LiveData> {
             return Transformations.map(getLiveAccountData(listOf(type))) {
                 it.firstOrNull()?.toOptional()
             }
         }
     
    -    override fun getAccountData(filterType: List): List {
    +    override fun getAccountData(filterType: List): List {
             return monarchy.fetchAllCopiedSync { realm ->
                 realm.where(UserAccountDataEntity::class.java)
                         .apply {
    @@ -64,7 +63,7 @@ internal class DefaultAccountDataService @Inject constructor(
                         }
             }?.mapNotNull { entity ->
                 entity.type?.let { type ->
    -                UserAccountDataFallback(
    +                UserAccountDataEvent(
                             type = type,
                             content = entity.contentStr?.let { adapter.fromJson(it) } ?: emptyMap()
                     )
    @@ -72,7 +71,7 @@ internal class DefaultAccountDataService @Inject constructor(
             } ?: emptyList()
         }
     
    -    override fun getLiveAccountData(filterType: List): LiveData> {
    +    override fun getLiveAccountData(filterType: List): LiveData> {
             return monarchy.findAllMappedWithChanges({ realm ->
                 realm.where(UserAccountDataEntity::class.java)
                         .apply {
    @@ -81,7 +80,7 @@ internal class DefaultAccountDataService @Inject constructor(
                             }
                         }
             }, { entity ->
    -            UserAccountDataFallback(
    +            UserAccountDataEvent(
                         type = entity.type ?: "",
                         content = entity.contentStr?.let { adapter.fromJson(it) } ?: emptyMap()
                 )
    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
    index 5b7b090dcd..a7d5d82fb1 100644
    --- 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
    @@ -22,7 +22,7 @@ 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.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
     import im.vector.riotx.R
     import im.vector.riotx.core.extensions.configureWith
     import im.vector.riotx.core.platform.VectorBaseActivity
    @@ -55,9 +55,9 @@ class AccountDataFragment @Inject constructor(
         }
     
         override fun didTap(data: UserAccountData) {
    -        val fb = data as? UserAccountDataFallback ?: return
    +        val fb = data as? UserAccountDataEvent ?: return
             val jsonString = MoshiProvider.providesMoshi()
    -                .adapter(UserAccountDataFallback::class.java)
    +                .adapter(UserAccountDataEvent::class.java)
                     .toJson(fb)
             JsonViewerBottomSheetDialog.newInstance(jsonString)
                     .show(childFragmentManager, "JSON_VIEWER")
    
    From 61ea4191dc616ef5ec2baaee37947d84cd3967b3 Mon Sep 17 00:00:00 2001
    From: Valere 
    Date: Wed, 12 Feb 2020 16:48:11 +0100
    Subject: [PATCH 09/22] Update json viewer lib
    
    ---
     build.gradle                                  |  1 +
     vector/build.gradle                           |  5 +-
     .../src/main/assets/open_source_licenses.html |  5 +-
     .../im/vector/riotx/core/utils/UserColor.kt   | 13 +++++
     .../home/room/detail/RoomDetailFragment.kt    | 37 +++++-------
     .../settings/devtools/AccountDataFragment.kt  | 13 ++++-
     .../devtools/JsonViewerBottomSheetDialog.kt   | 57 -------------------
     .../main/res/layout/fragment_jsonviewer.xml   | 16 ------
     8 files changed, 43 insertions(+), 104 deletions(-)
     delete mode 100644 vector/src/main/java/im/vector/riotx/features/settings/devtools/JsonViewerBottomSheetDialog.kt
     delete mode 100644 vector/src/main/res/layout/fragment_jsonviewer.xml
    
    diff --git a/build.gradle b/build.gradle
    index a2fac55175..1ee6fa49bb 100644
    --- a/build.gradle
    +++ b/build.gradle
    @@ -34,6 +34,7 @@ allprojects {
                     includeGroupByRegex "com\\.github\\.jaiselrahman"
                     // And monarchy
                     includeGroupByRegex "com\\.github\\.Zhuinden"
    +                includeGroupByRegex 'com\\.github\\.BillCarsonFr'
                 }
             }
             maven {
    diff --git a/vector/build.gradle b/vector/build.gradle
    index dac6a5a4c9..a59f3340d5 100644
    --- a/vector/build.gradle
    +++ b/vector/build.gradle
    @@ -354,9 +354,6 @@ dependencies {
         compileOnly 'com.squareup.inject:assisted-inject-annotations-dagger2:0.5.0'
         kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.5.0'
     
    -    // Json viewer
    -    implementation "com.yuyh.json:jsonviewer:1.0.6"
    -
         // gplay flavor only
         // Warning: due to the exclude, Android Studio does not propose to upgrade. Uncomment next line to be proposed to upgrade
         // implementation 'com.google.firebase:firebase-messaging:20.0.0'
    @@ -371,6 +368,8 @@ dependencies {
     
         implementation "androidx.emoji:emoji-appcompat:1.0.0"
     
    +    implementation 'com.github.BillCarsonFr:JsonViewer:0.4'
    +
         // QR-code
         // Stick to 3.3.3 because of https://github.com/zxing/zxing/issues/1170
         implementation 'com.google.zxing:core:3.3.3'
    diff --git a/vector/src/main/assets/open_source_licenses.html b/vector/src/main/assets/open_source_licenses.html
    index 55ec775e58..02ef5aba18 100755
    --- a/vector/src/main/assets/open_source_licenses.html
    +++ b/vector/src/main/assets/open_source_licenses.html
    @@ -22,6 +22,7 @@
                 padding: 4px;
             }
     
    +
         
     
     
    @@ -375,9 +376,7 @@ SOFTWARE.
             Copyright (c) 2014 Dushyanth Maguluru
         
         
  • - JsonViewer -
    - Copyright 2017 smuyyh, All right reserved. + BillCarsonFr/JsonViewer
  • diff --git a/vector/src/main/java/im/vector/riotx/core/utils/UserColor.kt b/vector/src/main/java/im/vector/riotx/core/utils/UserColor.kt
    index 1f8308cd5c..15c4ce8a15 100644
    --- a/vector/src/main/java/im/vector/riotx/core/utils/UserColor.kt
    +++ b/vector/src/main/java/im/vector/riotx/core/utils/UserColor.kt
    @@ -18,6 +18,8 @@ package im.vector.riotx.core.utils
     
     import androidx.annotation.ColorRes
     import im.vector.riotx.R
    +import im.vector.riotx.core.resources.ColorProvider
    +import org.billcarsonfr.jsonviewer.JSonViewerStyleProvider
     import kotlin.math.abs
     
     @ColorRes
    @@ -37,3 +39,14 @@ fun getColorFromUserId(userId: String?): Int {
             else -> R.color.riotx_username_1
         }
     }
    +
    +fun jsonViewerStyler(colorProvider: ColorProvider): JSonViewerStyleProvider {
    +    return JSonViewerStyleProvider(
    +            keyColor = colorProvider.getColor(R.color.riotx_accent),
    +            secondaryColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary),
    +            stringColor = colorProvider.getColorFromAttribute(R.attr.vctr_notice_text_color),
    +            baseColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_primary),
    +            booleanColor = colorProvider.getColorFromAttribute(R.attr.vctr_notice_text_color),
    +            numberColor = colorProvider.getColorFromAttribute(R.attr.vctr_notice_text_color)
    +    )
    +}
    diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
    index 5cb3024712..79eb63bfb7 100644
    --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
    @@ -27,12 +27,10 @@ import android.os.Bundle
     import android.os.Parcelable
     import android.text.Spannable
     import android.view.HapticFeedbackConstants
    -import android.view.LayoutInflater
     import android.view.Menu
     import android.view.MenuItem
     import android.view.View
     import android.view.Window
    -import android.widget.TextView
     import android.widget.Toast
     import androidx.annotation.DrawableRes
     import androidx.annotation.StringRes
    @@ -96,6 +94,7 @@ import im.vector.riotx.core.extensions.showKeyboard
     import im.vector.riotx.core.files.addEntryToDownloadManager
     import im.vector.riotx.core.glide.GlideApp
     import im.vector.riotx.core.platform.VectorBaseFragment
    +import im.vector.riotx.core.resources.ColorProvider
     import im.vector.riotx.core.ui.views.JumpToReadMarkerView
     import im.vector.riotx.core.ui.views.NotificationAreaView
     import im.vector.riotx.core.utils.Debouncer
    @@ -110,6 +109,7 @@ import im.vector.riotx.core.utils.checkPermissions
     import im.vector.riotx.core.utils.copyToClipboard
     import im.vector.riotx.core.utils.createUIHandler
     import im.vector.riotx.core.utils.getColorFromUserId
    +import im.vector.riotx.core.utils.jsonViewerStyler
     import im.vector.riotx.core.utils.openUrlInExternalBrowser
     import im.vector.riotx.core.utils.shareMedia
     import im.vector.riotx.core.utils.toast
    @@ -154,6 +154,7 @@ import kotlinx.android.parcel.Parcelize
     import kotlinx.android.synthetic.main.fragment_room_detail.*
     import kotlinx.android.synthetic.main.merge_composer_layout.view.*
     import kotlinx.android.synthetic.main.merge_overlay_waiting_view.*
    +import org.billcarsonfr.jsonviewer.JSonViewerDialog
     import org.commonmark.parser.Parser
     import timber.log.Timber
     import java.io.File
    @@ -178,8 +179,8 @@ class RoomDetailFragment @Inject constructor(
             private val notificationDrawerManager: NotificationDrawerManager,
             val roomDetailViewModelFactory: RoomDetailViewModel.Factory,
             private val eventHtmlRenderer: EventHtmlRenderer,
    -        private val vectorPreferences: VectorPreferences
    -) :
    +        private val vectorPreferences: VectorPreferences,
    +        private val colorProvider: ColorProvider) :
             VectorBaseFragment(),
             TimelineEventController.Callback,
             VectorInviteView.Callback,
    @@ -1155,26 +1156,18 @@ class RoomDetailFragment @Inject constructor(
                     onEditedDecorationClicked(action.messageInformationData)
                 }
                 is EventSharedAction.ViewSource                 -> {
    -                val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null)
    -                view.findViewById(R.id.event_content_text_view)?.let {
    -                    it.text = action.content
    -                }
    -
    -                AlertDialog.Builder(requireActivity())
    -                        .setView(view)
    -                        .setPositiveButton(R.string.ok, null)
    -                        .show()
    +                JSonViewerDialog.newInstance(
    +                        action.content,
    +                        -1,
    +                        jsonViewerStyler(colorProvider)
    +                ).show(childFragmentManager, "JSON_VIEWER")
                 }
                 is EventSharedAction.ViewDecryptedSource        -> {
    -                val view = LayoutInflater.from(requireContext()).inflate(R.layout.dialog_event_content, null)
    -                view.findViewById(R.id.event_content_text_view)?.let {
    -                    it.text = action.content
    -                }
    -
    -                AlertDialog.Builder(requireActivity())
    -                        .setView(view)
    -                        .setPositiveButton(R.string.ok, null)
    -                        .show()
    +                JSonViewerDialog.newInstance(
    +                        action.content,
    +                        -1,
    +                        jsonViewerStyler(colorProvider)
    +                ).show(childFragmentManager, "JSON_VIEWER")
                 }
                 is EventSharedAction.QuickReact                 -> {
                     // eventId,ClickedOn,Add
    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
    index a7d5d82fb1..0799ae270b 100644
    --- 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
    @@ -27,12 +27,16 @@ 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 im.vector.riotx.core.resources.ColorProvider
    +import im.vector.riotx.core.utils.jsonViewerStyler
     import kotlinx.android.synthetic.main.fragment_generic_recycler.*
    +import org.billcarsonfr.jsonviewer.JSonViewerDialog
     import javax.inject.Inject
     
     class AccountDataFragment @Inject constructor(
             val viewModelFactory: AccountDataViewModel.Factory,
    -        private val epoxyController: AccountDataEpoxyController
    +        private val epoxyController: AccountDataEpoxyController,
    +        private val colorProvider: ColorProvider
     ) : VectorBaseFragment(), AccountDataEpoxyController.InteractionListener {
     
         override fun getLayoutResId() = R.layout.fragment_generic_recycler
    @@ -59,7 +63,10 @@ class AccountDataFragment @Inject constructor(
             val jsonString = MoshiProvider.providesMoshi()
                     .adapter(UserAccountDataEvent::class.java)
                     .toJson(fb)
    -        JsonViewerBottomSheetDialog.newInstance(jsonString)
    -                .show(childFragmentManager, "JSON_VIEWER")
    +        JSonViewerDialog.newInstance(
    +                jsonString,
    +                -1, // open All
    +                jsonViewerStyler(colorProvider)
    +        ).show(childFragmentManager, "JSON_VIEWER")
         }
     }
    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
    deleted file mode 100644
    index c638846f51..0000000000
    --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/JsonViewerBottomSheetDialog.kt
    +++ /dev/null
    @@ -1,57 +0,0 @@
    -/*
    - * 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
    deleted file mode 100644
    index 5a4aecc56c..0000000000
    --- a/vector/src/main/res/layout/fragment_jsonviewer.xml
    +++ /dev/null
    @@ -1,16 +0,0 @@
    -
    -
    -
    -    
    -
    \ No newline at end of file
    
    From def01cca8fcedd4a480a55c0495ebea0b1db1a0d Mon Sep 17 00:00:00 2001
    From: Valere 
    Date: Wed, 12 Feb 2020 17:20:01 +0100
    Subject: [PATCH 10/22] Fix test + changes
    
    ---
     CHANGES.md                                                       | 1 +
     .../im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt  | 1 -
     2 files changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/CHANGES.md b/CHANGES.md
    index e09aa3fed3..69efff64b9 100644
    --- a/CHANGES.md
    +++ b/CHANGES.md
    @@ -2,6 +2,7 @@ Changes in RiotX 0.16.0 (2020-XX-XX)
     ===================================================
     
     Features ✨:
    + - Secured Shared Storage Support (#984, #936)
      - Polls and Bot Buttons (MSC 2192 matrix-org/matrix-doc#2192)
     
     Improvements 🙌:
    diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
    index 4ea611b875..c84716c4d9 100644
    --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
    +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
    @@ -185,7 +185,6 @@ class QuadSTests : InstrumentedTest {
             aliceSession.sharedSecretStorageService.getSecret("secret.of.life" ,
                      null, //default key
                     keySpec!!,
    -                null,
                     object : MatrixCallback {
                         override fun onFailure(failure: Throwable) {
                             fail("Fail to decrypt -> " +failure.localizedMessage)
    
    From 64647cb465c83d78f42ec73841a2541c9fd3b463 Mon Sep 17 00:00:00 2001
    From: Valere 
    Date: Wed, 12 Feb 2020 18:59:00 +0100
    Subject: [PATCH 11/22] Fix / Save account data after update (local echo)
    
    ---
     .../internal/crypto/ssss/QuadSTests.kt        | 186 +++++++++++++++++-
     .../session/accountdata/AccountDataService.kt |   3 +-
     .../secrets/DefaultSharedSecretStorage.kt     |   7 +-
     .../sync/UserAccountDataSyncHandler.kt        |   2 +-
     .../accountdata/DefaultAccountDataService.kt  |  20 +-
     .../accountdata/UpdateUserAccountDataTask.kt  |   1 +
     6 files changed, 206 insertions(+), 13 deletions(-)
    
    diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
    index c84716c4d9..af15738cc8 100644
    --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
    +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
    @@ -33,6 +33,7 @@ import im.vector.matrix.android.common.CryptoTestHelper
     import im.vector.matrix.android.common.SessionTestParams
     import im.vector.matrix.android.common.TestConstants
     import im.vector.matrix.android.common.TestMatrixCallback
    +import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
     import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecureStorage
     import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
     import kotlinx.coroutines.Dispatchers
    @@ -164,9 +165,9 @@ class QuadSTests : InstrumentedTest {
                     TestMatrixCallback(storeCountDownLatch)
             )
     
    -        val secretAccountData = assertAccountData(aliceSession,"secret.of.life" )
    +        val secretAccountData = assertAccountData(aliceSession, "secret.of.life")
     
    -        val encryptedContent = secretAccountData.content.get("encrypted") as? Map<*,*>
    +        val encryptedContent = secretAccountData.content.get("encrypted") as? Map<*, *>
             Assert.assertNotNull("Element should be encrypted", encryptedContent)
             Assert.assertNotNull("Secret should be encrypted with default key", encryptedContent?.get(keyId))
     
    @@ -182,12 +183,12 @@ class QuadSTests : InstrumentedTest {
             var decryptedSecret: String? = null
     
             val decryptCountDownLatch = CountDownLatch(1)
    -        aliceSession.sharedSecretStorageService.getSecret("secret.of.life" ,
    -                 null, //default key
    +        aliceSession.sharedSecretStorageService.getSecret("secret.of.life",
    +                null, //default key
                     keySpec!!,
                     object : MatrixCallback {
                         override fun onFailure(failure: Throwable) {
    -                        fail("Fail to decrypt -> " +failure.localizedMessage)
    +                        fail("Fail to decrypt -> " + failure.localizedMessage)
                             decryptCountDownLatch.countDown()
                         }
     
    @@ -202,7 +203,136 @@ class QuadSTests : InstrumentedTest {
     
             Assert.assertEquals("Secret mismatch", clearSecret, decryptedSecret)
             mTestHelper.signout(aliceSession)
    +    }
     
    +    @Test
    +    fun test_SetDefaultLocalEcho() {
    +        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
    +
    +        val quadS = aliceSession.sharedSecretStorageService
    +
    +        val emptyKeySigner = object : KeySigner {
    +            override fun sign(canonicalJson: String): Map>? {
    +                return null
    +            }
    +        }
    +
    +        val TEST_KEY_ID = "my.test.Key"
    +
    +        val countDownLatch = CountDownLatch(1)
    +        quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner,
    +                TestMatrixCallback(countDownLatch))
    +
    +        mTestHelper.await(countDownLatch)
    +
    +        //Test that we don't need to wait for an account data sync to access directly the keyid from DB
    +        val defaultLatch = CountDownLatch(1)
    +        quadS.setDefaultKey(TEST_KEY_ID, TestMatrixCallback(defaultLatch))
    +        mTestHelper.await(defaultLatch)
    +
    +
    +        mTestHelper.signout(aliceSession)
    +    }
    +
    +    @Test
    +    fun test_StoreSecretWithMultipleKey() {
    +        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
    +        val keyId1 = "Key.1"
    +        val key1Info = generatedSecret(aliceSession, keyId1, true)
    +        val keyId2 = "Key2"
    +        val key2Info = generatedSecret(aliceSession, keyId2, true)
    +
    +        val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
    +
    +        val storeLatch = CountDownLatch(1)
    +        aliceSession.sharedSecretStorageService.storeSecret(
    +                "my.secret",
    +                mySecretText.toByteArray().toBase64NoPadding(),
    +                listOf(keyId1, keyId2),
    +                TestMatrixCallback(storeLatch)
    +        )
    +        mTestHelper.await(storeLatch)
    +
    +        val accountDataEvent = aliceSession.getAccountData("my.secret")
    +        val encryptedContent = accountDataEvent?.content?.get("encrypted") as? Map<*, *>
    +
    +        Assert.assertEquals("Content should contains two encryptions", 2, encryptedContent?.keys?.size ?: 0)
    +
    +        Assert.assertNotNull(encryptedContent?.get(keyId1))
    +        Assert.assertNotNull(encryptedContent?.get(keyId2))
    +
    +        // Assert that can decrypt with both keys
    +        val decryptCountDownLatch = CountDownLatch(2)
    +        aliceSession.sharedSecretStorageService.getSecret("my.secret",
    +                keyId1,
    +                Curve25519AesSha2KeySpec.fromRecoveryKey(key1Info.recoveryKey)!!,
    +                TestMatrixCallback(decryptCountDownLatch)
    +        )
    +
    +        aliceSession.sharedSecretStorageService.getSecret("my.secret",
    +                keyId2,
    +                Curve25519AesSha2KeySpec.fromRecoveryKey(key2Info.recoveryKey)!!,
    +                TestMatrixCallback(decryptCountDownLatch)
    +        )
    +
    +        mTestHelper.await(decryptCountDownLatch)
    +
    +        mTestHelper.signout(aliceSession)
    +    }
    +
    +    @Test
    +    fun test_GetSecretWithBadPassphrase() {
    +        val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
    +        val keyId1 = "Key.1"
    +        val passphrase = "The good pass phrase"
    +        val key1Info = generatedSecretFromPassphrase(aliceSession, passphrase, keyId1, true)
    +
    +        val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
    +
    +        val storeLatch = CountDownLatch(1)
    +        aliceSession.sharedSecretStorageService.storeSecret(
    +                "my.secret",
    +                mySecretText.toByteArray().toBase64NoPadding(),
    +                listOf(keyId1),
    +                TestMatrixCallback(storeLatch)
    +        )
    +        mTestHelper.await(storeLatch)
    +
    +        val decryptCountDownLatch = CountDownLatch(2)
    +        aliceSession.sharedSecretStorageService.getSecret("my.secret",
    +                keyId1,
    +                Curve25519AesSha2KeySpec.fromPassphrase(
    +                        "A bad passphrase",
    +                        key1Info.content?.passphrase?.salt ?: "",
    +                        key1Info.content?.passphrase?.iterations ?: 0,
    +                        null),
    +                object : MatrixCallback {
    +                    override fun onSuccess(data: String) {
    +                        decryptCountDownLatch.countDown()
    +                        fail("Should not be able to decrypt")
    +                    }
    +
    +                    override fun onFailure(failure: Throwable) {
    +                        Assert.assertTrue(true)
    +                        decryptCountDownLatch.countDown()
    +                    }
    +                }
    +        )
    +
    +        // Now try with correct key
    +        aliceSession.sharedSecretStorageService.getSecret("my.secret",
    +                keyId1,
    +                Curve25519AesSha2KeySpec.fromPassphrase(
    +                        passphrase,
    +                        key1Info.content?.passphrase?.salt ?: "",
    +                        key1Info.content?.passphrase?.iterations ?: 0,
    +                        null),
    +                TestMatrixCallback(decryptCountDownLatch)
    +        )
    +
    +        mTestHelper.await(decryptCountDownLatch)
    +
    +        mTestHelper.signout(aliceSession)
         }
     
         private fun assertAccountData(session: Session, type: String): UserAccountDataEvent {
    @@ -268,4 +398,50 @@ class QuadSTests : InstrumentedTest {
     
             return creationInfo!!
         }
    +
    +    private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SSSSKeyCreationInfo {
    +
    +        val quadS = session.sharedSecretStorageService
    +
    +        val emptyKeySigner = object : KeySigner {
    +            override fun sign(canonicalJson: String): Map>? {
    +                return null
    +            }
    +        }
    +
    +        var creationInfo: SSSSKeyCreationInfo? = null
    +
    +        val generateLatch = CountDownLatch(1)
    +
    +        quadS.generateKeyWithPassphrase(keyId, keyId,
    +                passphrase,
    +                emptyKeySigner,
    +                null,
    +                object : MatrixCallback {
    +
    +                    override fun onSuccess(data: SSSSKeyCreationInfo) {
    +                        creationInfo = data
    +                        generateLatch.countDown()
    +                    }
    +
    +                    override fun onFailure(failure: Throwable) {
    +                        Assert.fail("onFailure " + failure.localizedMessage)
    +                        generateLatch.countDown()
    +                    }
    +                })
    +
    +        mTestHelper.await(generateLatch)
    +
    +        Assert.assertNotNull(creationInfo)
    +
    +        assertAccountData(session, "m.secret_storage.key.$keyId")
    +        if (asDefault) {
    +            val setDefaultLatch = CountDownLatch(1)
    +            quadS.setDefaultKey(keyId, TestMatrixCallback(setDefaultLatch))
    +            mTestHelper.await(setDefaultLatch)
    +            assertAccountData(session, DefaultSharedSecureStorage.DEFAULT_KEY_ID)
    +        }
    +
    +        return creationInfo!!
    +    }
     }
    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
    index a832921dc7..f4ffdce71c 100644
    --- 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
    @@ -18,6 +18,7 @@ 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.session.events.model.Content
     import im.vector.matrix.android.api.util.Optional
     import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
     
    @@ -31,5 +32,5 @@ interface AccountDataService {
     
         fun getLiveAccountData(filterType: List): LiveData>
     
    -    fun updateAccountData(type: String, data: Any, callback: MatrixCallback? = null)
    +    fun updateAccountData(type: String, content: Content, callback: MatrixCallback? = null)
     }
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt
    index a4cf02aeae..2c6d6702eb 100644
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt
    @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.secrets
     import im.vector.matrix.android.api.MatrixCallback
     import im.vector.matrix.android.api.listeners.ProgressListener
     import im.vector.matrix.android.api.session.accountdata.AccountDataService
    +import im.vector.matrix.android.api.session.events.model.toContent
     import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
     import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent
     import im.vector.matrix.android.api.session.securestorage.KeyInfo
    @@ -82,7 +83,7 @@ internal class DefaultSharedSecureStorage @Inject constructor(
     
                 accountDataService.updateAccountData(
                         "$KEY_ID_BASE.$keyId",
    -                    signedContent,
    +                    signedContent.toContent(),
                         object : MatrixCallback {
                             override fun onFailure(failure: Throwable) {
                                 callback.onFailure(failure)
    @@ -136,7 +137,7 @@ internal class DefaultSharedSecureStorage @Inject constructor(
     
                 accountDataService.updateAccountData(
                         "$KEY_ID_BASE.$keyId",
    -                    signedContent,
    +                    signedContent.toContent(),
                         object : MatrixCallback {
                             override fun onFailure(failure: Throwable) {
                                 callback.onFailure(failure)
    @@ -254,7 +255,7 @@ internal class DefaultSharedSecureStorage @Inject constructor(
     
                     accountDataService.updateAccountData(
                             type = name,
    -                        data = mapOf(
    +                        content = mapOf(
                                     "encrypted" to encryptedContents
                             ),
                             callback = callback
    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 fc4f73630b..541d11cfc9 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
    @@ -210,7 +210,7 @@ internal class UserAccountDataSyncHandler @Inject constructor(
             }
         }
     
    -    private fun handleGenericAccountData(realm: Realm, type: String, content: Content?) {
    +    fun handleGenericAccountData(realm: Realm, type: String, content: Content?) {
             val existing = realm.where().equalTo(UserAccountDataEntityFields.TYPE, type)
                     .findFirst()
             if (existing != null) {
    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
    index 0bb57f0dae..ee9e1d2550 100644
    --- 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
    @@ -21,6 +21,7 @@ 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.session.events.model.Content
     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
    @@ -28,6 +29,7 @@ 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.UserAccountDataSyncHandler
     import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
     import im.vector.matrix.android.internal.task.TaskExecutor
     import im.vector.matrix.android.internal.task.configureWith
    @@ -37,6 +39,7 @@ internal class DefaultAccountDataService @Inject constructor(
             private val monarchy: Monarchy,
             @SessionId private val sessionId: String,
             private val updateUserAccountDataTask: UpdateUserAccountDataTask,
    +        private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
             private val taskExecutor: TaskExecutor
     ) : AccountDataService {
     
    @@ -87,13 +90,24 @@ internal class DefaultAccountDataService @Inject constructor(
             })
         }
     
    -    override fun updateAccountData(type: String, data: Any, callback: MatrixCallback?) {
    +    override fun updateAccountData(type: String, content: Content, callback: MatrixCallback?) {
             updateUserAccountDataTask.configureWith(UpdateUserAccountDataTask.AnyParams(
                     type = type,
    -                any = data
    +                any = content
             )) {
                 this.retryCount = 5
    -            callback?.let { this.callback = it }
    +            this.callback = object : MatrixCallback {
    +                override fun onSuccess(data: Unit) {
    +                    monarchy.runTransactionSync { realm ->
    +                        userAccountDataSyncHandler.handleGenericAccountData(realm, type, content)
    +                    }
    +                    callback?.onSuccess(data)
    +                }
    +
    +                override fun onFailure(failure: Throwable) {
    +                    callback?.onFailure(failure)
    +                }
    +            }
             }
                     .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 beb3a0fcc0..9f8a851ee2 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
    @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.user.accountdata
     
     import im.vector.matrix.android.internal.di.UserId
     import im.vector.matrix.android.internal.network.executeRequest
    +import im.vector.matrix.android.internal.session.sync.UserAccountDataSyncHandler
     import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
     import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
     import im.vector.matrix.android.internal.task.Task
    
    From e0eede1150f3f1c1cad5757d3931bd207e71c01e Mon Sep 17 00:00:00 2001
    From: Valere 
    Date: Thu, 13 Feb 2020 14:06:04 +0100
    Subject: [PATCH 12/22] cleaning
    
    ---
     .../internal/crypto/ssss/QuadSTests.kt        | 28 ++++++-------------
     .../securestorage/SSSSKeyCreationInfo.kt      |  2 +-
     .../api/session/securestorage/SSSSKeySpec.kt  |  1 -
     .../securestorage/SecretStorageKeyContent.kt  |  1 -
     .../securestorage/SharedSecretStorageError.kt |  4 ++-
     .../secrets/DefaultSharedSecretStorage.kt     | 12 ++------
     .../parsing/AccountDataJsonAdapterFactory.kt  |  3 +-
     .../android/internal/session/SessionModule.kt |  4 +--
     .../sync/UserAccountDataSyncHandler.kt        |  3 +-
     .../accountdata/UpdateUserAccountDataTask.kt  |  1 -
     .../settings/devtools/AccountDataViewModel.kt |  1 -
     11 files changed, 20 insertions(+), 40 deletions(-)
    
    diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
    index af15738cc8..badfd09d3f 100644
    --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
    +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
    @@ -34,7 +34,7 @@ import im.vector.matrix.android.common.SessionTestParams
     import im.vector.matrix.android.common.TestConstants
     import im.vector.matrix.android.common.TestMatrixCallback
     import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
    -import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecureStorage
    +import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecretStorage
     import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
     import kotlinx.coroutines.Dispatchers
     import kotlinx.coroutines.GlobalScope
    @@ -57,7 +57,6 @@ class QuadSTests : InstrumentedTest {
     
         @Test
         fun test_Generate4SKey() {
    -
             val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
     
             val aliceLatch = CountDownLatch(1)
    @@ -76,7 +75,6 @@ class QuadSTests : InstrumentedTest {
     
             quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner,
                     object : MatrixCallback {
    -
                         override fun onSuccess(data: SSSSKeyCreationInfo) {
                             recoveryKey = data.recoveryKey
                             aliceLatch.countDown()
    @@ -110,13 +108,13 @@ class QuadSTests : InstrumentedTest {
             Assert.assertNotNull("Key should be stored in account data", accountData)
             val parsed = SecretStorageKeyContent.fromJson(accountData!!.content)
             Assert.assertNotNull("Key Content cannot be parsed", parsed)
    -        Assert.assertEquals("Unexpected Algorithm", DefaultSharedSecureStorage.ALGORITHM_CURVE25519_AES_SHA2, parsed!!.algorithm)
    +        Assert.assertEquals("Unexpected Algorithm", DefaultSharedSecretStorage.ALGORITHM_CURVE25519_AES_SHA2, parsed!!.algorithm)
             Assert.assertEquals("Unexpected key name", "Test Key", parsed.name)
             Assert.assertNull("Key was not generated from passphrase", parsed.passphrase)
             Assert.assertNotNull("Pubkey should be defined", parsed.publicKey)
     
             val privateKeySpec = Curve25519AesSha2KeySpec.fromRecoveryKey(recoveryKey!!)
    -        DefaultSharedSecureStorage.withOlmDecryption { olmPkDecryption ->
    +        DefaultSharedSecretStorage.withOlmDecryption { olmPkDecryption ->
                 val pubKey = olmPkDecryption.setPrivateKey(privateKeySpec!!.privateKey)
                 Assert.assertEquals("Unexpected Public Key", pubKey, parsed.publicKey)
             }
    @@ -128,10 +126,10 @@ class QuadSTests : InstrumentedTest {
             val defaultDataLock = CountDownLatch(1)
     
             val liveDefAccountData = runBlocking(Dispatchers.Main) {
    -            aliceSession.getLiveAccountData(DefaultSharedSecureStorage.DEFAULT_KEY_ID)
    +            aliceSession.getLiveAccountData(DefaultSharedSecretStorage.DEFAULT_KEY_ID)
             }
             val accountDefDataObserver = Observer?> { t ->
    -            if (t?.getOrNull()?.type == DefaultSharedSecureStorage.DEFAULT_KEY_ID) {
    +            if (t?.getOrNull()?.type == DefaultSharedSecretStorage.DEFAULT_KEY_ID) {
                     defaultKeyAccountData = t.getOrNull()!!
                     defaultDataLock.countDown()
                 }
    @@ -140,11 +138,9 @@ class QuadSTests : InstrumentedTest {
     
             mTestHelper.await(defaultDataLock)
     
    -
             Assert.assertNotNull(defaultKeyAccountData?.content)
             Assert.assertEquals("Unexpected default key ${defaultKeyAccountData?.content}", TEST_KEY_ID, defaultKeyAccountData?.content?.get("key"))
     
    -
             mTestHelper.signout(aliceSession)
         }
     
    @@ -184,7 +180,7 @@ class QuadSTests : InstrumentedTest {
     
             val decryptCountDownLatch = CountDownLatch(1)
             aliceSession.sharedSecretStorageService.getSecret("secret.of.life",
    -                null, //default key
    +                null, // default key
                     keySpec!!,
                     object : MatrixCallback {
                         override fun onFailure(failure: Throwable) {
    @@ -200,7 +196,6 @@ class QuadSTests : InstrumentedTest {
             )
             mTestHelper.await(decryptCountDownLatch)
     
    -
             Assert.assertEquals("Secret mismatch", clearSecret, decryptedSecret)
             mTestHelper.signout(aliceSession)
         }
    @@ -225,12 +220,11 @@ class QuadSTests : InstrumentedTest {
     
             mTestHelper.await(countDownLatch)
     
    -        //Test that we don't need to wait for an account data sync to access directly the keyid from DB
    +        // Test that we don't need to wait for an account data sync to access directly the keyid from DB
             val defaultLatch = CountDownLatch(1)
             quadS.setDefaultKey(TEST_KEY_ID, TestMatrixCallback(defaultLatch))
             mTestHelper.await(defaultLatch)
     
    -
             mTestHelper.signout(aliceSession)
         }
     
    @@ -357,7 +351,6 @@ class QuadSTests : InstrumentedTest {
         }
     
         private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SSSSKeyCreationInfo {
    -
             val quadS = session.sharedSecretStorageService
     
             val emptyKeySigner = object : KeySigner {
    @@ -372,7 +365,6 @@ class QuadSTests : InstrumentedTest {
     
             quadS.generateKey(keyId, keyId, emptyKeySigner,
                     object : MatrixCallback {
    -
                         override fun onSuccess(data: SSSSKeyCreationInfo) {
                             creationInfo = data
                             generateLatch.countDown()
    @@ -393,14 +385,13 @@ class QuadSTests : InstrumentedTest {
                 val setDefaultLatch = CountDownLatch(1)
                 quadS.setDefaultKey(keyId, TestMatrixCallback(setDefaultLatch))
                 mTestHelper.await(setDefaultLatch)
    -            assertAccountData(session, DefaultSharedSecureStorage.DEFAULT_KEY_ID)
    +            assertAccountData(session, DefaultSharedSecretStorage.DEFAULT_KEY_ID)
             }
     
             return creationInfo!!
         }
     
         private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SSSSKeyCreationInfo {
    -
             val quadS = session.sharedSecretStorageService
     
             val emptyKeySigner = object : KeySigner {
    @@ -418,7 +409,6 @@ class QuadSTests : InstrumentedTest {
                     emptyKeySigner,
                     null,
                     object : MatrixCallback {
    -
                         override fun onSuccess(data: SSSSKeyCreationInfo) {
                             creationInfo = data
                             generateLatch.countDown()
    @@ -439,7 +429,7 @@ class QuadSTests : InstrumentedTest {
                 val setDefaultLatch = CountDownLatch(1)
                 quadS.setDefaultKey(keyId, TestMatrixCallback(setDefaultLatch))
                 mTestHelper.await(setDefaultLatch)
    -            assertAccountData(session, DefaultSharedSecureStorage.DEFAULT_KEY_ID)
    +            assertAccountData(session, DefaultSharedSecretStorage.DEFAULT_KEY_ID)
             }
     
             return creationInfo!!
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeyCreationInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeyCreationInfo.kt
    index 3c629290fc..5b608dd658 100644
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeyCreationInfo.kt
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeyCreationInfo.kt
    @@ -16,7 +16,7 @@
     
     package im.vector.matrix.android.api.session.securestorage
     
    -data class SSSSKeyCreationInfo (
    +data class SSSSKeyCreationInfo(
             val keyId: String = "",
             var content: SecretStorageKeyContent?,
             val recoveryKey: String = ""
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeySpec.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeySpec.kt
    index dcdba38d8e..9e61f7f8ff 100644
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeySpec.kt
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeySpec.kt
    @@ -64,4 +64,3 @@ data class Curve25519AesSha2KeySpec(
             return privateKey.contentHashCode()
         }
     }
    -
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SecretStorageKeyContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SecretStorageKeyContent.kt
    index 0aba3d700d..02c3e96658 100644
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SecretStorageKeyContent.kt
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SecretStorageKeyContent.kt
    @@ -98,4 +98,3 @@ data class SSSSPassphrase(
             @Json(name = "iterations") val iterations: Int,
             @Json(name = "salt") val salt: String?
     )
    -
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageError.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageError.kt
    index 1ff5cf12f3..f882375e5c 100644
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageError.kt
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageError.kt
    @@ -23,7 +23,9 @@ sealed class SharedSecretStorageError(message: String?) : Throwable(message) {
         data class UnknownAlgorithm(val keyId: String) : SharedSecretStorageError("Unknown algorithm $keyId")
         data class UnsupportedAlgorithm(val algorithm: String) : SharedSecretStorageError("Unknown algorithm $algorithm")
         data class SecretNotEncrypted(val secretName: String) : SharedSecretStorageError("Missing content for secret $secretName")
    -    data class SecretNotEncryptedWithKey(val secretName: String, val keyId: String) : SharedSecretStorageError("Missing content for secret $secretName with key $keyId")
    +    data class SecretNotEncryptedWithKey(val secretName: String, val keyId: String)
    +        : SharedSecretStorageError("Missing content for secret $secretName with key $keyId")
    +
         object BadKeyFormat : SharedSecretStorageError("Bad Key Format")
         object ParsingError : SharedSecretStorageError("parsing Error")
         data class OtherError(val reason: Throwable) : SharedSecretStorageError(reason.localizedMessage)
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt
    index 2c6d6702eb..fe6936f7fd 100644
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt
    @@ -42,7 +42,7 @@ import org.matrix.olm.OlmPkEncryption
     import org.matrix.olm.OlmPkMessage
     import javax.inject.Inject
     
    -internal class DefaultSharedSecureStorage @Inject constructor(
    +internal class DefaultSharedSecretStorage @Inject constructor(
             private val accountDataService: AccountDataService,
             private val coroutineDispatchers: MatrixCoroutineDispatchers,
             private val cryptoCoroutineScope: CoroutineScope
    @@ -52,7 +52,6 @@ internal class DefaultSharedSecureStorage @Inject constructor(
                                  keyName: String,
                                  keySigner: KeySigner,
                                  callback: MatrixCallback) {
    -
             cryptoCoroutineScope.launch(coroutineDispatchers.main) {
                 val pkDecryption = OlmPkDecryption()
                 val pubKey: String
    @@ -108,7 +107,6 @@ internal class DefaultSharedSecureStorage @Inject constructor(
                                                progressListener: ProgressListener?,
                                                callback: MatrixCallback) {
             cryptoCoroutineScope.launch(coroutineDispatchers.main) {
    -
                 val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener)
     
                 val pkDecryption = OlmPkDecryption()
    @@ -152,7 +150,6 @@ internal class DefaultSharedSecureStorage @Inject constructor(
                             }
                         }
                 )
    -
             }
         }
     
    @@ -191,13 +188,11 @@ internal class DefaultSharedSecureStorage @Inject constructor(
         }
     
         override fun storeSecret(name: String, secretBase64: String, keys: List?, callback: MatrixCallback) {
    -
             cryptoCoroutineScope.launch(coroutineDispatchers.main) {
                 val encryptedContents = HashMap()
                 try {
    -
                     if (keys == null || keys.isEmpty()) {
    -                    //use default key
    +                    // use default key
                         val key = getDefaultKey()
                         when (key) {
                             is KeyInfoResult.Success -> {
    @@ -263,7 +258,6 @@ internal class DefaultSharedSecureStorage @Inject constructor(
                 } catch (failure: Throwable) {
                     callback.onFailure(failure)
                 }
    -
             }
     
             // Add default key
    @@ -363,5 +357,3 @@ internal class DefaultSharedSecureStorage @Inject constructor(
             }
         }
     }
    -
    -
    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
    index e7290077dd..bf8ae84478 100644
    --- 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
    @@ -22,8 +22,7 @@ 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.
    +        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/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt
    index 02d8a35009..b06ddbe123 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
    @@ -36,7 +36,7 @@ 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.api.session.securestorage.SharedSecretStorageService
    -import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecureStorage
    +import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecretStorage
     import im.vector.matrix.android.internal.crypto.verification.VerificationMessageLiveObserver
     import im.vector.matrix.android.internal.database.LiveEntityObserver
     import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory
    @@ -272,5 +272,5 @@ internal abstract class SessionModule {
         abstract fun bindAccountDataServiceService(accountDataService: DefaultAccountDataService): AccountDataService
     
         @Binds
    -    abstract fun bindSharedSecuredSecretStorageService(service: DefaultSharedSecureStorage): SharedSecretStorageService
    +    abstract fun bindSharedSecuredSecretStorageService(service: DefaultSharedSecretStorage): SharedSecretStorageService
     }
    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 541d11cfc9..c530578538 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
    @@ -211,7 +211,8 @@ internal class UserAccountDataSyncHandler @Inject constructor(
         }
     
         fun handleGenericAccountData(realm: Realm, type: String, content: Content?) {
    -        val existing = realm.where().equalTo(UserAccountDataEntityFields.TYPE, type)
    +        val existing = realm.where()
    +                .equalTo(UserAccountDataEntityFields.TYPE, type)
                     .findFirst()
             if (existing != null) {
                 // Update current value
    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 9f8a851ee2..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
    @@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.session.user.accountdata
     
     import im.vector.matrix.android.internal.di.UserId
     import im.vector.matrix.android.internal.network.executeRequest
    -import im.vector.matrix.android.internal.session.sync.UserAccountDataSyncHandler
     import im.vector.matrix.android.internal.session.sync.model.accountdata.BreadcrumbsContent
     import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountData
     import im.vector.matrix.android.internal.task.Task
    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
    index 4a6b0f896a..b0b23a62d1 100644
    --- 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
    @@ -39,7 +39,6 @@ class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: A
                                                            private val session: Session)
         : VectorViewModel(initialState) {
     
    -
         init {
             session.rx().liveAccountData(emptyList())
                     .execute {
    
    From f99eca8014654985de4245b7418a85d7872e0903 Mon Sep 17 00:00:00 2001
    From: Valere 
    Date: Fri, 14 Feb 2020 10:15:32 +0100
    Subject: [PATCH 13/22] Code review
    
    ---
     .../java/im/vector/matrix/rx/RxSession.kt     |  4 +-
     .../internal/crypto/ssss/QuadSTests.kt        | 43 ++++++++++---------
     .../session/accountdata/AccountDataService.kt |  8 ++--
     .../SharedSecretStorageService.kt             | 32 +++++++++-----
     ...CreationInfo.kt => SsssKeyCreationInfo.kt} |  2 +-
     .../{SSSSKeySpec.kt => SsssKeySpec.kt}        |  0
     .../internal/crypto/CryptoConstants.kt        |  5 +++
     ...t => DefaultSharedSecretStorageService.kt} | 35 ++++++++-------
     .../database/model/UserAccountDataEntity.kt   |  5 ++-
     .../parsing/AccountDataJsonAdapterFactory.kt  | 28 ------------
     .../android/internal/session/SessionModule.kt |  6 +--
     .../accountdata/DefaultAccountDataService.kt  | 12 +++---
     .../settings/devtools/AccountDataFragment.kt  |  7 +++
     13 files changed, 91 insertions(+), 96 deletions(-)
     rename matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/{SSSSKeyCreationInfo.kt => SsssKeyCreationInfo.kt} (96%)
     rename matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/{SSSSKeySpec.kt => SsssKeySpec.kt} (100%)
     rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/{DefaultSharedSecretStorage.kt => DefaultSharedSecretStorageService.kt} (93%)
     delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/AccountDataJsonAdapterFactory.kt
    
    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 50f55577d0..0417504cb7 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
    @@ -124,9 +124,9 @@ class RxSession(private val session: Session) {
         }
     
         fun liveAccountData(filter: List): Observable> {
    -        return session.getLiveAccountData(filter).asObservable()
    +        return session.getLiveAccountDataEvents(filter).asObservable()
                     .startWithCallable {
    -                    session.getAccountData(filter)
    +                    session.getAccountDataEvents(filter)
                     }
         }
     }
    diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
    index badfd09d3f..f0e2161d4c 100644
    --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
    +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/ssss/QuadSTests.kt
    @@ -25,7 +25,7 @@ import im.vector.matrix.android.api.session.Session
     import im.vector.matrix.android.api.session.securestorage.Curve25519AesSha2KeySpec
     import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent
     import im.vector.matrix.android.api.session.securestorage.KeySigner
    -import im.vector.matrix.android.api.session.securestorage.SSSSKeyCreationInfo
    +import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
     import im.vector.matrix.android.api.session.securestorage.SecretStorageKeyContent
     import im.vector.matrix.android.api.util.Optional
     import im.vector.matrix.android.common.CommonTestHelper
    @@ -33,8 +33,9 @@ import im.vector.matrix.android.common.CryptoTestHelper
     import im.vector.matrix.android.common.SessionTestParams
     import im.vector.matrix.android.common.TestConstants
     import im.vector.matrix.android.common.TestMatrixCallback
    +import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2
     import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
    -import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecretStorage
    +import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecretStorageService
     import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAccountDataEvent
     import kotlinx.coroutines.Dispatchers
     import kotlinx.coroutines.GlobalScope
    @@ -74,8 +75,8 @@ class QuadSTests : InstrumentedTest {
             val TEST_KEY_ID = "my.test.Key"
     
             quadS.generateKey(TEST_KEY_ID, "Test Key", emptyKeySigner,
    -                object : MatrixCallback {
    -                    override fun onSuccess(data: SSSSKeyCreationInfo) {
    +                object : MatrixCallback {
    +                    override fun onSuccess(data: SsssKeyCreationInfo) {
                             recoveryKey = data.recoveryKey
                             aliceLatch.countDown()
                         }
    @@ -93,7 +94,7 @@ class QuadSTests : InstrumentedTest {
             var accountData: UserAccountDataEvent? = null
     
             val liveAccountData = runBlocking(Dispatchers.Main) {
    -            aliceSession.getLiveAccountData("m.secret_storage.key.$TEST_KEY_ID")
    +            aliceSession.getLiveAccountDataEvent("m.secret_storage.key.$TEST_KEY_ID")
             }
             val accountDataObserver = Observer?> { t ->
                 if (t?.getOrNull()?.type == "m.secret_storage.key.$TEST_KEY_ID") {
    @@ -108,13 +109,13 @@ class QuadSTests : InstrumentedTest {
             Assert.assertNotNull("Key should be stored in account data", accountData)
             val parsed = SecretStorageKeyContent.fromJson(accountData!!.content)
             Assert.assertNotNull("Key Content cannot be parsed", parsed)
    -        Assert.assertEquals("Unexpected Algorithm", DefaultSharedSecretStorage.ALGORITHM_CURVE25519_AES_SHA2, parsed!!.algorithm)
    +        Assert.assertEquals("Unexpected Algorithm", SSSS_ALGORITHM_CURVE25519_AES_SHA2, parsed!!.algorithm)
             Assert.assertEquals("Unexpected key name", "Test Key", parsed.name)
             Assert.assertNull("Key was not generated from passphrase", parsed.passphrase)
             Assert.assertNotNull("Pubkey should be defined", parsed.publicKey)
     
             val privateKeySpec = Curve25519AesSha2KeySpec.fromRecoveryKey(recoveryKey!!)
    -        DefaultSharedSecretStorage.withOlmDecryption { olmPkDecryption ->
    +        DefaultSharedSecretStorageService.withOlmDecryption { olmPkDecryption ->
                 val pubKey = olmPkDecryption.setPrivateKey(privateKeySpec!!.privateKey)
                 Assert.assertEquals("Unexpected Public Key", pubKey, parsed.publicKey)
             }
    @@ -126,10 +127,10 @@ class QuadSTests : InstrumentedTest {
             val defaultDataLock = CountDownLatch(1)
     
             val liveDefAccountData = runBlocking(Dispatchers.Main) {
    -            aliceSession.getLiveAccountData(DefaultSharedSecretStorage.DEFAULT_KEY_ID)
    +            aliceSession.getLiveAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
             }
             val accountDefDataObserver = Observer?> { t ->
    -            if (t?.getOrNull()?.type == DefaultSharedSecretStorage.DEFAULT_KEY_ID) {
    +            if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) {
                     defaultKeyAccountData = t.getOrNull()!!
                     defaultDataLock.countDown()
                 }
    @@ -247,7 +248,7 @@ class QuadSTests : InstrumentedTest {
             )
             mTestHelper.await(storeLatch)
     
    -        val accountDataEvent = aliceSession.getAccountData("my.secret")
    +        val accountDataEvent = aliceSession.getAccountDataEvent("my.secret")
             val encryptedContent = accountDataEvent?.content?.get("encrypted") as? Map<*, *>
     
             Assert.assertEquals("Content should contains two encryptions", 2, encryptedContent?.keys?.size ?: 0)
    @@ -334,7 +335,7 @@ class QuadSTests : InstrumentedTest {
             var accountData: UserAccountDataEvent? = null
     
             val liveAccountData = runBlocking(Dispatchers.Main) {
    -            session.getLiveAccountData(type)
    +            session.getLiveAccountDataEvent(type)
             }
             val accountDataObserver = Observer?> { t ->
                 if (t?.getOrNull()?.type == type) {
    @@ -350,7 +351,7 @@ class QuadSTests : InstrumentedTest {
             return accountData!!
         }
     
    -    private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SSSSKeyCreationInfo {
    +    private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
             val quadS = session.sharedSecretStorageService
     
             val emptyKeySigner = object : KeySigner {
    @@ -359,13 +360,13 @@ class QuadSTests : InstrumentedTest {
                 }
             }
     
    -        var creationInfo: SSSSKeyCreationInfo? = null
    +        var creationInfo: SsssKeyCreationInfo? = null
     
             val generateLatch = CountDownLatch(1)
     
             quadS.generateKey(keyId, keyId, emptyKeySigner,
    -                object : MatrixCallback {
    -                    override fun onSuccess(data: SSSSKeyCreationInfo) {
    +                object : MatrixCallback {
    +                    override fun onSuccess(data: SsssKeyCreationInfo) {
                             creationInfo = data
                             generateLatch.countDown()
                         }
    @@ -385,13 +386,13 @@ class QuadSTests : InstrumentedTest {
                 val setDefaultLatch = CountDownLatch(1)
                 quadS.setDefaultKey(keyId, TestMatrixCallback(setDefaultLatch))
                 mTestHelper.await(setDefaultLatch)
    -            assertAccountData(session, DefaultSharedSecretStorage.DEFAULT_KEY_ID)
    +            assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
             }
     
             return creationInfo!!
         }
     
    -    private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SSSSKeyCreationInfo {
    +    private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo {
             val quadS = session.sharedSecretStorageService
     
             val emptyKeySigner = object : KeySigner {
    @@ -400,7 +401,7 @@ class QuadSTests : InstrumentedTest {
                 }
             }
     
    -        var creationInfo: SSSSKeyCreationInfo? = null
    +        var creationInfo: SsssKeyCreationInfo? = null
     
             val generateLatch = CountDownLatch(1)
     
    @@ -408,8 +409,8 @@ class QuadSTests : InstrumentedTest {
                     passphrase,
                     emptyKeySigner,
                     null,
    -                object : MatrixCallback {
    -                    override fun onSuccess(data: SSSSKeyCreationInfo) {
    +                object : MatrixCallback {
    +                    override fun onSuccess(data: SsssKeyCreationInfo) {
                             creationInfo = data
                             generateLatch.countDown()
                         }
    @@ -429,7 +430,7 @@ class QuadSTests : InstrumentedTest {
                 val setDefaultLatch = CountDownLatch(1)
                 quadS.setDefaultKey(keyId, TestMatrixCallback(setDefaultLatch))
                 mTestHelper.await(setDefaultLatch)
    -            assertAccountData(session, DefaultSharedSecretStorage.DEFAULT_KEY_ID)
    +            assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID)
             }
     
             return creationInfo!!
    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
    index f4ffdce71c..7af7fea214 100644
    --- 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
    @@ -24,13 +24,13 @@ import im.vector.matrix.android.internal.session.sync.model.accountdata.UserAcco
     
     interface AccountDataService {
     
    -    fun getAccountData(type: String): UserAccountDataEvent?
    +    fun getAccountDataEvent(type: String): UserAccountDataEvent?
     
    -    fun getLiveAccountData(type: String): LiveData>
    +    fun getLiveAccountDataEvent(type: String): LiveData>
     
    -    fun getAccountData(filterType: List): List
    +    fun getAccountDataEvents(filterType: List): List
     
    -    fun getLiveAccountData(filterType: List): LiveData>
    +    fun getLiveAccountDataEvents(filterType: List): LiveData>
     
         fun updateAccountData(type: String, content: Content, callback: MatrixCallback? = null)
     }
    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
    index 9923aab606..02ccc11026 100644
    --- 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
    @@ -1,5 +1,5 @@
     /*
    - * Copyright 2019 New Vector Ltd
    + * Copyright 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.
    @@ -31,26 +31,38 @@ import im.vector.matrix.android.api.listeners.ProgressListener
     interface SharedSecretStorageService {
     
         /**
    -     * Add a key for encrypting secrets.
    +     * Generates a SSSS key for encrypting secrets.
    +     * Use the SsssKeyCreationInfo object returned by the callback to get more information about the created key (recovery key ...)
          *
    -     * @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
    +     * @param keyName a human readable name
    +     * @param keySigner Used to add a signature to the key (client should check key signature before storing secret)
          *
    -     * @return {string} the ID of the key
    +     * @param callback Get key creation info
          */
         fun generateKey(keyId: String,
                         keyName: String,
                         keySigner: KeySigner,
    -                    callback: MatrixCallback)
    +                    callback: MatrixCallback)
     
    +    /**
    +     * Generates a SSSS key using the given passphrase.
    +     * Use the SsssKeyCreationInfo object returned by the callback to get more information about the created key (recovery key, salt, iteration ...)
    +     *
    +     * @param keyId the ID of the key
    +     * @param keyName human readable key name
    +     * @param passphrase The passphrase used to generate the key
    +     * @param keySigner Used to add a signature to the key (client should check key signature before retrieving secret)
    +     * @param progressListener The derivation of the passphrase may take long depending on the device, use this to report progress
    +     *
    +     * @param callback Get key creation info
    +     */
         fun generateKeyWithPassphrase(keyId: String,
                                       keyName: String,
                                       passphrase: String,
                                       keySigner: KeySigner,
                                       progressListener: ProgressListener?,
    -                                  callback: MatrixCallback)
    +                                  callback: MatrixCallback)
     
         fun getKey(keyId: String): KeyInfoResult
     
    @@ -92,11 +104,9 @@ interface SharedSecretStorageService {
          *
          * @param name The name of the secret
          * @param keyId The id of the key that should be used to decrypt (null for default key)
    -     * @param privateKey the passphrase/secret
    +     * @param secretKey the secret key to use (@see #Curve25519AesSha2KeySpec)
          *
    -     * @return The decrypted value
          */
         @Throws
    -
         fun getSecret(name: String, keyId: String?, secretKey: SSSSKeySpec, callback: MatrixCallback)
     }
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeyCreationInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SsssKeyCreationInfo.kt
    similarity index 96%
    rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeyCreationInfo.kt
    rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SsssKeyCreationInfo.kt
    index 5b608dd658..1d5522b8bf 100644
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeyCreationInfo.kt
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SsssKeyCreationInfo.kt
    @@ -16,7 +16,7 @@
     
     package im.vector.matrix.android.api.session.securestorage
     
    -data class SSSSKeyCreationInfo(
    +data class SsssKeyCreationInfo(
             val keyId: String = "",
             var content: SecretStorageKeyContent?,
             val recoveryKey: String = ""
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeySpec.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SsssKeySpec.kt
    similarity index 100%
    rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SSSSKeySpec.kt
    rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SsssKeySpec.kt
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoConstants.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoConstants.kt
    index a3b0a567fe..fee81a853d 100644
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoConstants.kt
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoConstants.kt
    @@ -31,6 +31,11 @@ const val MXCRYPTO_ALGORITHM_MEGOLM = "m.megolm.v1.aes-sha2"
      */
     const val MXCRYPTO_ALGORITHM_MEGOLM_BACKUP = "m.megolm_backup.v1.curve25519-aes-sha2"
     
    +/**
    + * Secured Shared Storage algorithm constant
    + */
    +const val SSSS_ALGORITHM_CURVE25519_AES_SHA2 = "m.secret_storage.v1.curve25519-aes-sha2"
    +
     // TODO Refacto: use this constants everywhere
     const val ed25519 = "ed25519"
     const val curve25519 = "curve25519"
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
    similarity index 93%
    rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt
    rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
    index fe6936f7fd..f741021e6c 100644
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorage.kt
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt
    @@ -25,12 +25,13 @@ import im.vector.matrix.android.api.session.securestorage.EncryptedSecretContent
     import im.vector.matrix.android.api.session.securestorage.KeyInfo
     import im.vector.matrix.android.api.session.securestorage.KeyInfoResult
     import im.vector.matrix.android.api.session.securestorage.KeySigner
    -import im.vector.matrix.android.api.session.securestorage.SSSSKeyCreationInfo
    +import im.vector.matrix.android.api.session.securestorage.SsssKeyCreationInfo
     import im.vector.matrix.android.api.session.securestorage.SSSSKeySpec
     import im.vector.matrix.android.api.session.securestorage.SSSSPassphrase
     import im.vector.matrix.android.api.session.securestorage.SecretStorageKeyContent
     import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageError
     import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageService
    +import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2
     import im.vector.matrix.android.internal.crypto.keysbackup.generatePrivateKeyWithPassword
     import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey
     import im.vector.matrix.android.internal.extensions.foldToCallback
    @@ -42,7 +43,7 @@ import org.matrix.olm.OlmPkEncryption
     import org.matrix.olm.OlmPkMessage
     import javax.inject.Inject
     
    -internal class DefaultSharedSecretStorage @Inject constructor(
    +internal class DefaultSharedSecretStorageService @Inject constructor(
             private val accountDataService: AccountDataService,
             private val coroutineDispatchers: MatrixCoroutineDispatchers,
             private val cryptoCoroutineScope: CoroutineScope
    @@ -51,7 +52,7 @@ internal class DefaultSharedSecretStorage @Inject constructor(
         override fun generateKey(keyId: String,
                                  keyName: String,
                                  keySigner: KeySigner,
    -                             callback: MatrixCallback) {
    +                             callback: MatrixCallback) {
             cryptoCoroutineScope.launch(coroutineDispatchers.main) {
                 val pkDecryption = OlmPkDecryption()
                 val pubKey: String
    @@ -69,7 +70,7 @@ internal class DefaultSharedSecretStorage @Inject constructor(
     
                 val storageKeyContent = SecretStorageKeyContent(
                         name = keyName,
    -                    algorithm = ALGORITHM_CURVE25519_AES_SHA2,
    +                    algorithm = SSSS_ALGORITHM_CURVE25519_AES_SHA2,
                         passphrase = null,
                         publicKey = pubKey
                 )
    @@ -89,7 +90,7 @@ internal class DefaultSharedSecretStorage @Inject constructor(
                             }
     
                             override fun onSuccess(data: Unit) {
    -                            callback.onSuccess(SSSSKeyCreationInfo(
    +                            callback.onSuccess(SsssKeyCreationInfo(
                                         keyId = keyId,
                                         content = storageKeyContent,
                                         recoveryKey = computeRecoveryKey(privateKey)
    @@ -105,7 +106,7 @@ internal class DefaultSharedSecretStorage @Inject constructor(
                                                passphrase: String,
                                                keySigner: KeySigner,
                                                progressListener: ProgressListener?,
    -                                           callback: MatrixCallback) {
    +                                           callback: MatrixCallback) {
             cryptoCoroutineScope.launch(coroutineDispatchers.main) {
                 val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener)
     
    @@ -122,7 +123,7 @@ internal class DefaultSharedSecretStorage @Inject constructor(
                 }
     
                 val storageKeyContent = SecretStorageKeyContent(
    -                    algorithm = ALGORITHM_CURVE25519_AES_SHA2,
    +                    algorithm = SSSS_ALGORITHM_CURVE25519_AES_SHA2,
                         passphrase = SSSSPassphrase(algorithm = "m.pbkdf2", iterations = privatePart.iterations, salt = privatePart.salt),
                         publicKey = pubKey
                 )
    @@ -142,7 +143,7 @@ internal class DefaultSharedSecretStorage @Inject constructor(
                             }
     
                             override fun onSuccess(data: Unit) {
    -                            callback.onSuccess(SSSSKeyCreationInfo(
    +                            callback.onSuccess(SsssKeyCreationInfo(
                                         keyId = keyId,
                                         content = storageKeyContent,
                                         recoveryKey = computeRecoveryKey(privatePart.privateKey)
    @@ -154,11 +155,11 @@ internal class DefaultSharedSecretStorage @Inject constructor(
         }
     
         override fun hasKey(keyId: String): Boolean {
    -        return accountDataService.getAccountData("$KEY_ID_BASE.$keyId") != null
    +        return accountDataService.getAccountDataEvent("$KEY_ID_BASE.$keyId") != null
         }
     
         override fun getKey(keyId: String): KeyInfoResult {
    -        val accountData = accountDataService.getAccountData("$KEY_ID_BASE.$keyId")
    +        val accountData = accountDataService.getAccountDataEvent("$KEY_ID_BASE.$keyId")
                     ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(keyId))
             return SecretStorageKeyContent.fromJson(accountData.content)?.let {
                 KeyInfoResult.Success(
    @@ -180,7 +181,7 @@ internal class DefaultSharedSecretStorage @Inject constructor(
         }
     
         override fun getDefaultKey(): KeyInfoResult {
    -        val accountData = accountDataService.getAccountData(DEFAULT_KEY_ID)
    +        val accountData = accountDataService.getAccountDataEvent(DEFAULT_KEY_ID)
                     ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(DEFAULT_KEY_ID))
             val keyId = accountData.content["key"] as? String
                     ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(DEFAULT_KEY_ID))
    @@ -196,7 +197,7 @@ internal class DefaultSharedSecretStorage @Inject constructor(
                         val key = getDefaultKey()
                         when (key) {
                             is KeyInfoResult.Success -> {
    -                            if (key.keyInfo.content.algorithm == ALGORITHM_CURVE25519_AES_SHA2) {
    +                            if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_CURVE25519_AES_SHA2) {
                                     withOlmEncryption { olmEncrypt ->
                                         olmEncrypt.setRecipientKey(key.keyInfo.content.publicKey)
                                         val encryptedResult = olmEncrypt.encrypt(secretBase64)
    @@ -224,7 +225,7 @@ internal class DefaultSharedSecretStorage @Inject constructor(
                             val key = getKey(keyId)
                             when (key) {
                                 is KeyInfoResult.Success -> {
    -                                if (key.keyInfo.content.algorithm == ALGORITHM_CURVE25519_AES_SHA2) {
    +                                if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_CURVE25519_AES_SHA2) {
                                         withOlmEncryption { olmEncrypt ->
                                             olmEncrypt.setRecipientKey(key.keyInfo.content.publicKey)
                                             val encryptedResult = olmEncrypt.encrypt(secretBase64)
    @@ -264,7 +265,7 @@ internal class DefaultSharedSecretStorage @Inject constructor(
         }
     
         override fun getAlgorithmsForSecret(name: String): List {
    -        val accountData = accountDataService.getAccountData(name)
    +        val accountData = accountDataService.getAccountDataEvent(name)
                     ?: return listOf(KeyInfoResult.Error(SharedSecretStorageError.UnknownSecret(name)))
             val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *>
                     ?: return listOf(KeyInfoResult.Error(SharedSecretStorageError.SecretNotEncrypted(name)))
    @@ -279,7 +280,7 @@ internal class DefaultSharedSecretStorage @Inject constructor(
         }
     
         override fun getSecret(name: String, keyId: String?, secretKey: SSSSKeySpec, callback: MatrixCallback) {
    -        val accountData = accountDataService.getAccountData(name) ?: return Unit.also {
    +        val accountData = accountDataService.getAccountDataEvent(name) ?: return Unit.also {
                 callback.onFailure(SharedSecretStorageError.UnknownSecret(name))
             }
             val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *> ?: return Unit.also {
    @@ -299,7 +300,7 @@ internal class DefaultSharedSecretStorage @Inject constructor(
                     }
     
             val algorithm = key.keyInfo.content
    -        if (ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) {
    +        if (SSSS_ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) {
                 val keySpec = secretKey as? Curve25519AesSha2KeySpec ?: return Unit.also {
                     callback.onFailure(SharedSecretStorageError.BadKeyFormat)
                 }
    @@ -332,8 +333,6 @@ internal class DefaultSharedSecretStorage @Inject constructor(
             const val ENCRYPTED = "encrypted"
             const val DEFAULT_KEY_ID = "m.secret_storage.default_key"
     
    -        const val ALGORITHM_CURVE25519_AES_SHA2 = "m.secret_storage.v1.curve25519-aes-sha2"
    -
             fun withOlmEncryption(block: (OlmPkEncryption) -> Unit) {
                 val olmPkEncryption = OlmPkEncryption()
                 try {
    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
    index 9ffbcca527..90f73381dc 100644
    --- 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
    @@ -20,9 +20,10 @@ import io.realm.RealmObject
     import io.realm.annotations.Index
     
     /**
    - * Clients can store custom config data for their account on their homeserver.
    + * 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.
    + * Users may only view the account data for their own account.
    + * The account_data may be either global or scoped to a particular rooms.
      */
     internal open class UserAccountDataEntity(
             @Index var type: String? = null,
    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
    deleted file mode 100644
    index bf8ae84478..0000000000
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/network/parsing/AccountDataJsonAdapterFactory.kt
    +++ /dev/null
    @@ -1,28 +0,0 @@
    -/*
    - * 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/SessionModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionModule.kt
    index b06ddbe123..7352b79073 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
    @@ -36,7 +36,7 @@ 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.api.session.securestorage.SharedSecretStorageService
    -import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecretStorage
    +import im.vector.matrix.android.internal.crypto.secrets.DefaultSharedSecretStorageService
     import im.vector.matrix.android.internal.crypto.verification.VerificationMessageLiveObserver
     import im.vector.matrix.android.internal.database.LiveEntityObserver
     import im.vector.matrix.android.internal.database.SessionRealmConfigurationFactory
    @@ -269,8 +269,8 @@ internal abstract class SessionModule {
         abstract fun bindHomeServerCapabilitiesService(homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService
     
         @Binds
    -    abstract fun bindAccountDataServiceService(accountDataService: DefaultAccountDataService): AccountDataService
    +    abstract fun bindAccountDataService(accountDataService: DefaultAccountDataService): AccountDataService
     
         @Binds
    -    abstract fun bindSharedSecuredSecretStorageService(service: DefaultSharedSecretStorage): SharedSecretStorageService
    +    abstract fun bindSharedSecretStorageService(service: DefaultSharedSecretStorageService): SharedSecretStorageService
     }
    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
    index ee9e1d2550..b40c75992a 100644
    --- 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
    @@ -46,17 +46,17 @@ internal class DefaultAccountDataService @Inject constructor(
         private val moshi = MoshiProvider.providesMoshi()
         private val adapter = moshi.adapter>(JSON_DICT_PARAMETERIZED_TYPE)
     
    -    override fun getAccountData(type: String): UserAccountDataEvent? {
    -        return getAccountData(listOf(type)).firstOrNull()
    +    override fun getAccountDataEvent(type: String): UserAccountDataEvent? {
    +        return getAccountDataEvents(listOf(type)).firstOrNull()
         }
     
    -    override fun getLiveAccountData(type: String): LiveData> {
    -        return Transformations.map(getLiveAccountData(listOf(type))) {
    +    override fun getLiveAccountDataEvent(type: String): LiveData> {
    +        return Transformations.map(getLiveAccountDataEvents(listOf(type))) {
                 it.firstOrNull()?.toOptional()
             }
         }
     
    -    override fun getAccountData(filterType: List): List {
    +    override fun getAccountDataEvents(filterType: List): List {
             return monarchy.fetchAllCopiedSync { realm ->
                 realm.where(UserAccountDataEntity::class.java)
                         .apply {
    @@ -74,7 +74,7 @@ internal class DefaultAccountDataService @Inject constructor(
             } ?: emptyList()
         }
     
    -    override fun getLiveAccountData(filterType: List): LiveData> {
    +    override fun getLiveAccountDataEvents(filterType: List): LiveData> {
             return monarchy.findAllMappedWithChanges({ realm ->
                 realm.where(UserAccountDataEntity::class.java)
                         .apply {
    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
    index 0799ae270b..7a57a03deb 100644
    --- 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
    @@ -24,6 +24,7 @@ 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.UserAccountDataEvent
     import im.vector.riotx.R
    +import im.vector.riotx.core.extensions.cleanup
     import im.vector.riotx.core.extensions.configureWith
     import im.vector.riotx.core.platform.VectorBaseActivity
     import im.vector.riotx.core.platform.VectorBaseFragment
    @@ -58,6 +59,12 @@ class AccountDataFragment @Inject constructor(
             epoxyController.interactionListener = this
         }
     
    +    override fun onDestroyView() {
    +        super.onDestroyView()
    +        recyclerView.cleanup()
    +        epoxyController.interactionListener = null
    +    }
    +
         override fun didTap(data: UserAccountData) {
             val fb = data as? UserAccountDataEvent ?: return
             val jsonString = MoshiProvider.providesMoshi()
    
    From 35835be03e04afba53ec13085941956274c3b3a7 Mon Sep 17 00:00:00 2001
    From: Valere 
    Date: Fri, 14 Feb 2020 12:06:07 +0100
    Subject: [PATCH 14/22] klint
    
    ---
     .../android/internal/session/room/send/EncryptEventWorker.kt     | 1 -
     1 file changed, 1 deletion(-)
    
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt
    index 21080d9037..6f1593bc08 100644
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt
    @@ -20,7 +20,6 @@ import android.content.Context
     import androidx.work.CoroutineWorker
     import androidx.work.WorkerParameters
     import com.squareup.moshi.JsonClass
    -import im.vector.matrix.android.api.MatrixCallback
     import im.vector.matrix.android.api.failure.Failure
     import im.vector.matrix.android.api.session.crypto.CryptoService
     import im.vector.matrix.android.api.session.events.model.Event
    
    From ab437e249d4bb72ebd73ac1e9005f80599ae2a47 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Fri, 14 Feb 2020 12:43:27 +0100
    Subject: [PATCH 15/22] Update emoji for API change
    
    ---
     CHANGES.md | 5 ++---
     1 file changed, 2 insertions(+), 3 deletions(-)
    
    diff --git a/CHANGES.md b/CHANGES.md
    index e09aa3fed3..262fba061c 100644
    --- a/CHANGES.md
    +++ b/CHANGES.md
    @@ -15,7 +15,7 @@ Bugfix 🐛:
     Translations 🗣:
      -
     
    -SDK API changes 🔞:
    +SDK API changes ⚠️:
      - Javadoc improved for PushersService
      - PushersService.pushers() has been renamed to PushersService.getPushers()
     
    @@ -396,7 +396,7 @@ Bugfix 🐛:
     Translations 🗣:
      -
     
    -SDK API changes 🔞:
    +SDK API changes ⚠️:
      - 
     
     Build 🧱:
    @@ -404,4 +404,3 @@ Build 🧱:
     
     Other changes:
      -
    -
    
    From 030d6824e31a73ecfd8485efe5223169c7bb5b2c Mon Sep 17 00:00:00 2001
    From: onurays 
    Date: Fri, 14 Feb 2020 15:04:25 +0300
    Subject: [PATCH 16/22] Code review fixes.
    
    ---
     .../android/api/session/events/model/Event.kt |  5 ++
     .../home/room/detail/RoomDetailFragment.kt    | 12 ++--
     .../action/MessageActionsViewModel.kt         | 60 ++++++++++---------
     3 files changed, 42 insertions(+), 35 deletions(-)
    
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt
    index fb94d61c0b..d131960893 100644
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/events/model/Event.kt
    @@ -157,6 +157,11 @@ data class Event(
          */
         fun isRedacted() = unsignedData?.redactedEvent != null
     
    +    /**
    +     * Tells if the event is redacted by the user himself.
    +     */
    +    fun isRedactedBySameUser() = senderId == unsignedData?.redactedEvent?.senderId
    +
         override fun equals(other: Any?): Boolean {
             if (this === other) return true
             if (javaClass != other?.javaClass) return false
    diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
    index cbda6ab13e..3f574a320c 100644
    --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
    @@ -788,14 +788,14 @@ class RoomDetailFragment @Inject constructor(
                     .show()
         }
     
    -    private fun promptConfirmationToRedactEvent(eventId: String, askForReason: Boolean) {
    +    private fun promptConfirmationToRedactEvent(action: EventSharedAction.Redact) {
             val layout = requireActivity().layoutInflater.inflate(R.layout.dialog_delete_event, null)
             val reasonCheckBox = layout.findViewById(R.id.deleteEventReasonCheck)
             val reasonTextInputLayout = layout.findViewById(R.id.deleteEventReasonTextInputLayout)
             val reasonInput = layout.findViewById(R.id.deleteEventReasonInput)
     
    -        reasonCheckBox.isVisible = askForReason
    -        reasonTextInputLayout.isVisible = askForReason
    +        reasonCheckBox.isVisible = action.askForReason
    +        reasonTextInputLayout.isVisible = action.askForReason
     
             reasonCheckBox.setOnCheckedChangeListener { _, isChecked -> reasonTextInputLayout.isEnabled = isChecked }
     
    @@ -804,10 +804,10 @@ class RoomDetailFragment @Inject constructor(
                     .setView(layout)
                     .setPositiveButton(R.string.remove) { _, _ ->
                         val reason = reasonInput.text.toString()
    -                            .takeIf { askForReason }
    +                            .takeIf { action.askForReason }
                                 ?.takeIf { reasonCheckBox.isChecked }
                                 ?.takeIf { it.isNotBlank() }
    -                    roomDetailViewModel.handle(RoomDetailAction.RedactAction(eventId, reason))
    +                    roomDetailViewModel.handle(RoomDetailAction.RedactAction(action.eventId, reason))
                     }
                     .setNegativeButton(R.string.cancel, null)
                     .show()
    @@ -1125,7 +1125,7 @@ class RoomDetailFragment @Inject constructor(
                     showSnackWithMessage(getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
                 }
                 is EventSharedAction.Redact                     -> {
    -                promptConfirmationToRedactEvent(action.eventId, action.askForReason)
    +                promptConfirmationToRedactEvent(action)
                 }
                 is EventSharedAction.Share                      -> {
                     // TODO current data communication is too limited
    diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
    index 04841266ec..8a908c842f 100644
    --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
    @@ -168,27 +168,25 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
         }
     
         private fun computeMessageBody(timelineEvent: TimelineEvent): CharSequence {
    +        if (timelineEvent.root.isRedacted()) {
    +            return getRedactionReason(timelineEvent)
    +        }
    +        
             return when (timelineEvent.root.getClearType()) {
                 EventType.MESSAGE,
    -            EventType.ENCRYPTED,
                 EventType.STICKER     -> {
    -                when (timelineEvent.root.isRedacted()) {
    -                    true  -> getRedactionReason(timelineEvent)
    -                    false -> {
    -                        val messageContent: MessageContent? = timelineEvent.getLastMessageContent()
    -                        if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) {
    -                            val html = messageContent.formattedBody
    -                                    ?.takeIf { it.isNotBlank() }
    -                                    ?.let { htmlCompressor.compress(it) }
    -                                    ?: messageContent.body
    +                val messageContent: MessageContent? = timelineEvent.getLastMessageContent()
    +                if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) {
    +                    val html = messageContent.formattedBody
    +                            ?.takeIf { it.isNotBlank() }
    +                            ?.let { htmlCompressor.compress(it) }
    +                            ?: messageContent.body
     
    -                            eventHtmlRenderer.get().render(html)
    -                        } else if (messageContent is MessageVerificationRequestContent) {
    -                            stringProvider.getString(R.string.verification_request)
    -                        } else {
    -                            messageContent?.body
    -                        }
    -                    }
    +                    eventHtmlRenderer.get().render(html)
    +                } else if (messageContent is MessageVerificationRequestContent) {
    +                    stringProvider.getString(R.string.verification_request)
    +                } else {
    +                    messageContent?.body
                     }
                 }
                 EventType.STATE_ROOM_NAME,
    @@ -206,26 +204,30 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
             } ?: ""
         }
     
    -    private fun getRedactionReason(timelineEvent: TimelineEvent) =
    -            (timelineEvent
    +    private fun getRedactionReason(timelineEvent: TimelineEvent): String {
    +            return (timelineEvent
                         .root
                         .unsignedData
                         ?.redactedEvent
                         ?.content
                         ?.get("reason") as? String)
                         ?.takeIf { it.isNotBlank() }
    -                    ?.let { reason ->
    -                        when (timelineEvent.root.senderId == timelineEvent.root.unsignedData?.redactedEvent?.senderId) {
    -                            true  -> stringProvider.getString(R.string.event_redacted_by_user_reason_with_reason, reason)
    -                            false -> stringProvider.getString(R.string.event_redacted_by_admin_reason_with_reason, reason)
    -                        }
    -                    }
    -                    ?: run {
    -                        when (timelineEvent.root.senderId == timelineEvent.root.unsignedData?.redactedEvent?.senderId) {
    -                            true  -> stringProvider.getString(R.string.event_redacted_by_user_reason)
    -                            false -> stringProvider.getString(R.string.event_redacted_by_admin_reason)
    +                    .let { reason ->
    +                        if (reason == null) {
    +                            if (timelineEvent.root.isRedactedBySameUser()) {
    +                                stringProvider.getString(R.string.event_redacted_by_user_reason)
    +                            } else {
    +                                stringProvider.getString(R.string.event_redacted_by_admin_reason)
    +                            }
    +                        } else {
    +                            if (timelineEvent.root.isRedactedBySameUser()) {
    +                                stringProvider.getString(R.string.event_redacted_by_user_reason_with_reason, reason)
    +                            } else {
    +                                stringProvider.getString(R.string.event_redacted_by_admin_reason_with_reason, reason)
    +                            }
                             }
                         }
    +    }
     
         private fun actionsForEvent(timelineEvent: TimelineEvent): List {
             val messageContent: MessageContent? = timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
    
    From e4577d8fef1db7ec0c0333c5a15a38a70a1f146d Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Fri, 14 Feb 2020 14:34:22 +0100
    Subject: [PATCH 17/22] Small cleanup before merge
    
    ---
     CHANGES.md                                                      | 2 +-
     .../home/room/detail/timeline/action/MessageActionsViewModel.kt | 2 +-
     2 files changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/CHANGES.md b/CHANGES.md
    index e05c52c835..ffc80c76c8 100644
    --- a/CHANGES.md
    +++ b/CHANGES.md
    @@ -5,7 +5,7 @@ Features ✨:
      - Polls and Bot Buttons (MSC 2192 matrix-org/matrix-doc#2192)
     
     Improvements 🙌:
    - - Show confirmation dialog before deleting a message (#967)
    + - Show confirmation dialog before deleting a message (#967, #1003)
      - Open room member profile from reactions list and read receipts list (#875)
     
     Bugfix 🐛:
    diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
    index 8a908c842f..a36215007d 100644
    --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
    +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
    @@ -171,7 +171,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
             if (timelineEvent.root.isRedacted()) {
                 return getRedactionReason(timelineEvent)
             }
    -        
    +
             return when (timelineEvent.root.getClearType()) {
                 EventType.MESSAGE,
                 EventType.STICKER     -> {
    
    From f6f9c349ec97c875c1ea60c68b7d52683dc540ee Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Fri, 14 Feb 2020 14:52:10 +0100
    Subject: [PATCH 18/22] Add entry in changelog for the 2 mistakes
    
    ---
     CHANGES.md | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/CHANGES.md b/CHANGES.md
    index cc697ddaf8..a62f885edd 100644
    --- a/CHANGES.md
    +++ b/CHANGES.md
    @@ -11,6 +11,7 @@ Improvements 🙌:
     Bugfix 🐛:
      - Fix crash by removing all notifications after clearing cache (#878)
      - Fix issue with verification when other client declares it can only show QR code (#988)
    + - Fix too errors in the code (1941862499c9ec5268cc80882512ced379cafcfd, a250a895fe0a4acf08c671e03434edcd29ccd84f)
     
     Translations 🗣:
      -
    
    From e20d1724c97caf9d7acd0db57f941fa7965b507f Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Fri, 14 Feb 2020 15:14:48 +0100
    Subject: [PATCH 19/22] Prepare release 0.16.0
    
    ---
     CHANGES.md | 11 +----------
     1 file changed, 1 insertion(+), 10 deletions(-)
    
    diff --git a/CHANGES.md b/CHANGES.md
    index a62f885edd..5b7c04d3ac 100644
    --- a/CHANGES.md
    +++ b/CHANGES.md
    @@ -1,4 +1,4 @@
    -Changes in RiotX 0.16.0 (2020-XX-XX)
    +Changes in RiotX 0.16.0 (2020-02-14)
     ===================================================
     
     Features ✨:
    @@ -13,19 +13,10 @@ Bugfix 🐛:
      - Fix issue with verification when other client declares it can only show QR code (#988)
      - Fix too errors in the code (1941862499c9ec5268cc80882512ced379cafcfd, a250a895fe0a4acf08c671e03434edcd29ccd84f)
     
    -Translations 🗣:
    - -
    -
     SDK API changes ⚠️:
      - Javadoc improved for PushersService
      - PushersService.pushers() has been renamed to PushersService.getPushers()
     
    -Build 🧱:
    - -
    -
    -Other changes:
    - -
    -
     Changes in RiotX 0.15.0 (2020-02-10)
     ===================================================
     
    
    From 1660a0f846bda64aecb45161d604f8ed12106208 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Fri, 14 Feb 2020 15:16:00 +0100
    Subject: [PATCH 20/22] Version++
    
    ---
     CHANGES.md          | 24 ++++++++++++++++++++++++
     vector/build.gradle |  2 +-
     2 files changed, 25 insertions(+), 1 deletion(-)
    
    diff --git a/CHANGES.md b/CHANGES.md
    index 5b7c04d3ac..bebb419564 100644
    --- a/CHANGES.md
    +++ b/CHANGES.md
    @@ -1,3 +1,27 @@
    +Changes in RiotX 0.17.0 (2020-XX-XX)
    +===================================================
    +
    +Features ✨:
    + -
    +
    +Improvements 🙌:
    + -
    +
    +Bugfix 🐛:
    + -
    +
    +Translations 🗣:
    + -
    +
    +SDK API changes ⚠️:
    + -
    +
    +Build 🧱:
    + -
    +
    +Other changes:
    + -
    +
     Changes in RiotX 0.16.0 (2020-02-14)
     ===================================================
     
    diff --git a/vector/build.gradle b/vector/build.gradle
    index 0517482904..a091f7aaa2 100644
    --- a/vector/build.gradle
    +++ b/vector/build.gradle
    @@ -15,7 +15,7 @@ androidExtensions {
     }
     
     ext.versionMajor = 0
    -ext.versionMinor = 16
    +ext.versionMinor = 17
     ext.versionPatch = 0
     
     static def getGitTimestamp() {
    
    From cad818c34160180536758007d04a40d65bf89a67 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Fri, 14 Feb 2020 17:05:14 +0100
    Subject: [PATCH 21/22] ktlint
    
    ---
     .../android/internal/session/room/send/EncryptEventWorker.kt     | 1 -
     1 file changed, 1 deletion(-)
    
    diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt
    index 21080d9037..6f1593bc08 100644
    --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt
    +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt
    @@ -20,7 +20,6 @@ import android.content.Context
     import androidx.work.CoroutineWorker
     import androidx.work.WorkerParameters
     import com.squareup.moshi.JsonClass
    -import im.vector.matrix.android.api.MatrixCallback
     import im.vector.matrix.android.api.failure.Failure
     import im.vector.matrix.android.api.session.crypto.CryptoService
     import im.vector.matrix.android.api.session.events.model.Event
    
    From ecf07ff64ab49d298834a5bd4e8ee97c11f8e915 Mon Sep 17 00:00:00 2001
    From: Benoit Marty 
    Date: Mon, 17 Feb 2020 10:30:54 +0100
    Subject: [PATCH 22/22] Account creation: wrongly hints that an email can be
     used to create an account (#941)
    
    ---
     CHANGES.md                                                | 2 +-
     .../java/im/vector/riotx/features/login/LoginFragment.kt  | 8 +++++++-
     vector/src/main/res/layout/fragment_login.xml             | 4 ++--
     vector/src/main/res/values-bg/strings.xml                 | 2 +-
     vector/src/main/res/values-de/strings.xml                 | 2 +-
     vector/src/main/res/values-eu/strings.xml                 | 2 +-
     vector/src/main/res/values-fi/strings.xml                 | 2 +-
     vector/src/main/res/values-fr/strings.xml                 | 2 +-
     vector/src/main/res/values-hu/strings.xml                 | 2 +-
     vector/src/main/res/values-it/strings.xml                 | 2 +-
     vector/src/main/res/values-sq/strings.xml                 | 2 +-
     vector/src/main/res/values-zh-rTW/strings.xml             | 2 +-
     vector/src/main/res/values/strings.xml                    | 2 +-
     vector/src/main/res/values/strings_riotX.xml              | 4 +---
     14 files changed, 21 insertions(+), 17 deletions(-)
    
    diff --git a/CHANGES.md b/CHANGES.md
    index 3e771a79d4..aa353fe633 100644
    --- a/CHANGES.md
    +++ b/CHANGES.md
    @@ -9,7 +9,7 @@ Improvements 🙌:
      -
     
     Bugfix 🐛:
    - -
    + - Account creation: wrongly hints that an email can be used to create an account (#941)
     
     Translations 🗣:
      -
    diff --git a/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt b/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt
    index 93b1b1b525..3e45eeb406 100644
    --- a/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt
    +++ b/vector/src/main/java/im/vector/riotx/features/login/LoginFragment.kt
    @@ -40,7 +40,7 @@ import javax.inject.Inject
     
     /**
      * In this screen, in signin mode:
    - * - the user is asked for login and password to sign in to a homeserver.
    + * - the user is asked for login (or email) and password to sign in to a homeserver.
      * - He also can reset his password
      * In signup mode:
      * - the user is asked for login and password
    @@ -97,6 +97,12 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
                 SignMode.SignIn  -> R.string.login_connect_to
             }
     
    +        loginFieldTil.hint = getString(when (state.signMode) {
    +            SignMode.Unknown -> error("developer error")
    +            SignMode.SignUp  -> R.string.login_signup_username_hint
    +            SignMode.SignIn  -> R.string.login_signin_username_hint
    +        })
    +
             when (state.serverType) {
                 ServerType.MatrixOrg -> {
                     loginServerIcon.isVisible = true
    diff --git a/vector/src/main/res/layout/fragment_login.xml b/vector/src/main/res/layout/fragment_login.xml
    index fb445dfb89..3f2443440e 100644
    --- a/vector/src/main/res/layout/fragment_login.xml
    +++ b/vector/src/main/res/layout/fragment_login.xml
    @@ -50,8 +50,8 @@
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
                     android:layout_marginTop="32dp"
    -                android:hint="@string/login_signup_username_hint"
    -                app:errorEnabled="true">
    +                app:errorEnabled="true"
    +                tools:hint="@string/login_signin_username_hint">
     
                     Телефонния номер изглежда невалиден. Моля проверете го
     
         Регистрация в %1$s
    -    Потребителско име или имейл
    +    Потребителско име или имейл
         Парола
         Напред
         Това потребителско име е заето
    diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml
    index fe059320d3..6c3ad4e84e 100644
    --- a/vector/src/main/res/values-de/strings.xml
    +++ b/vector/src/main/res/values-de/strings.xml
    @@ -1941,7 +1941,7 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A
         Wir haben einen Code an %1$s gesendet. Gib diesen unten ein um dich zu verifizieren.
         Code eingeben
         Erneut senden
    -    Benutzername oder E-Mail-Adresse
    +    Benutzername oder E-Mail-Adresse
         Passwort
         Dieser Benutzername ist bereits belegt
         Dein Benutzerkonto ist noch nicht erstellt.
    diff --git a/vector/src/main/res/values-eu/strings.xml b/vector/src/main/res/values-eu/strings.xml
    index 91432ac66e..86fb1e0e12 100644
    --- a/vector/src/main/res/values-eu/strings.xml
    +++ b/vector/src/main/res/values-eu/strings.xml
    @@ -1909,7 +1909,7 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada.
         Telefono zenbakia baliogabea dirudi. Egiaztatu ezazu
     
         Erregistratu %1$s zerbitzarian
    -    Erabiltzaile-izena edo e-maila
    +    Erabiltzaile-izena edo e-maila
         Pasahitza
         Hurrengoa
         Erabiltzaile-izen hori hartuta dago
    diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml
    index 4966a5ea6a..fef747d546 100644
    --- a/vector/src/main/res/values-fi/strings.xml
    +++ b/vector/src/main/res/values-fi/strings.xml
    @@ -1962,7 +1962,7 @@ Jotta et menetä mitään, automaattiset päivitykset kannattaa pitää käytös
         Puhelinnumero vaikuttaa epäkelvolta. Tarkista numero
     
         Rekisteröidy palvelimelle %1$s
    -    Käyttäjätunnus tai sähköpostiosoite
    +    Käyttäjätunnus tai sähköpostiosoite
         Salasana
         Seuraava
         Käyttäjätunnus on varattu
    diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml
    index 86547b02fd..959266b9b7 100644
    --- a/vector/src/main/res/values-fr/strings.xml
    +++ b/vector/src/main/res/values-fr/strings.xml
    @@ -1918,7 +1918,7 @@ Si vous n’avez pas configuré de nouvelle méthode de récupération, un attaq
         Le numéro de téléphone n’a pas l’air d’être valide. Veuillez le vérifier
     
         S’inscrire sur %1$s
    -    Nom d’utilisateur ou e-mail
    +    Nom d’utilisateur ou e-mail
         Mot de passe
         Suivant
         Ce nom d’utilisateur est déjà pris
    diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml
    index 4598e445fa..64eee79075 100644
    --- a/vector/src/main/res/values-hu/strings.xml
    +++ b/vector/src/main/res/values-hu/strings.xml
    @@ -1913,7 +1913,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró
         A telefonszám érvénytelennek látszik. Kérlek ellenőrizd
     
         Bejelentkezés ide: %1$s
    -    Felhasználónév vagy e-mail
    +    Felhasználónév vagy e-mail
         Jelszó
         Következő
         A felhasználónév már használatban van
    diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml
    index 6c10f7b04f..4d1ac8edaf 100644
    --- a/vector/src/main/res/values-it/strings.xml
    +++ b/vector/src/main/res/values-it/strings.xml
    @@ -1963,7 +1963,7 @@
         Il numero di telefono non sembra valido. Ricontrollalo
     
         Registrati su %1$s
    -    Nome utente o email
    +    Nome utente o email
         Password
         Avanti
         Quel nome utente esiste già
    diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml
    index 397a245289..a8af77d59b 100644
    --- a/vector/src/main/res/values-sq/strings.xml
    +++ b/vector/src/main/res/values-sq/strings.xml
    @@ -1871,7 +1871,7 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani
         Numri i telefonit duket se është i vlefshëm. Ju lutemi, kontrollojeni
     
         Regjistrohuni te %1$s
    -    Emër përdoruesi ose email
    +    Emër përdoruesi ose email
         Fjalëkalim
         Pasuesi
         Ai emër përdoruesi është i zënë
    diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml
    index 9e36a2ec3a..d285c364b2 100644
    --- a/vector/src/main/res/values-zh-rTW/strings.xml
    +++ b/vector/src/main/res/values-zh-rTW/strings.xml
    @@ -1866,7 +1866,7 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意
         電話號碼似乎無效。請檢查
     
         註冊至 %1$s
    -    使用者名稱或電子郵件
    +    使用者名稱或電子郵件
         密碼
         下一個
         使用者名稱已被使用
    diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
    index 7693bc8cee..29fff11230 100644
    --- a/vector/src/main/res/values/strings.xml
    +++ b/vector/src/main/res/values/strings.xml
    @@ -1912,7 +1912,7 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming
     
         
         Sign up to %1$s
    -    Username or email
    +    Username or email
         Password
         Next
         That username is taken
    diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml
    index 15fd002e89..6f2af1e7ac 100644
    --- a/vector/src/main/res/values/strings_riotX.xml
    +++ b/vector/src/main/res/values/strings_riotX.xml
    @@ -23,9 +23,7 @@
     
         
         Remove…
    -    
    -
    -
    +    Username