From 5cb7e455b1eb4a4da1724812d423e1d798a2feb5 Mon Sep 17 00:00:00 2001 From: onurays Date: Fri, 13 Mar 2020 14:00:46 +0300 Subject: [PATCH 001/156] Implementation of /join command. --- CHANGES.md | 2 +- .../home/room/detail/RoomDetailFragment.kt | 7 +++++++ .../home/room/detail/RoomDetailViewEvents.kt | 1 + .../home/room/detail/RoomDetailViewModel.kt | 20 +++++++++++++++++-- 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 918cfd61de..04825e2ac7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features ✨: - Improvements 🙌: - - + - /join command implemented Bugfix 🐛: - 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 ad4e9694db..9916fa522b 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 @@ -297,11 +297,18 @@ class RoomDetailFragment @Inject constructor( is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it) is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it) is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it) + is RoomDetailViewEvents.JoinedToAnotherRoom -> handleJoinedToAnotherRoom(it) is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it) }.exhaustive } } + private fun handleJoinedToAnotherRoom(action: RoomDetailViewEvents.JoinedToAnotherRoom) { + updateComposerText("") + lockSendButton = false + navigator.openRoom(vectorBaseActivity, action.roomId) + } + override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) if (savedInstanceState == null) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt index fcbe7f37c0..df0ead3b38 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt @@ -50,6 +50,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents { abstract class SendMessageResult : RoomDetailViewEvents() object MessageSent : SendMessageResult() + data class JoinedToAnotherRoom(val roomId: String) : SendMessageResult() class SlashCommandError(val command: Command) : SendMessageResult() class SlashCommandUnknown(val command: String) : SendMessageResult() data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 4ab9125c4f..aa04b80202 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -379,8 +379,8 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented) } is ParsedCommand.JoinRoom -> { - // TODO - _viewEvents.post(RoomDetailViewEvents.SlashCommandNotImplemented) + handleJoinToAnotherRoomSlashCommand(slashCommandResult) + popDraft() } is ParsedCommand.PartRoom -> { // TODO @@ -512,6 +512,22 @@ class RoomDetailViewModel @AssistedInject constructor( room.deleteDraft(NoOpMatrixCallback()) } + private fun handleJoinToAnotherRoomSlashCommand(command: ParsedCommand.JoinRoom) { + session.joinRoom(command.roomAlias, command.reason, object : MatrixCallback { + override fun onSuccess(data: Unit) { + session.getRoomSummary(command.roomAlias) + ?.roomId + ?.let { + _viewEvents.post(RoomDetailViewEvents.JoinedToAnotherRoom(it)) + } + } + + override fun onFailure(failure: Throwable) { + _viewEvents.post(RoomDetailViewEvents.SlashCommandResultError(failure)) + } + }) + } + private fun legacyRiotQuoteText(quotedText: String?, myText: String): String { val messageParagraphs = quotedText?.split("\n\n".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray() return buildString { From 85a987ca8d6a9b2fdedffcb14ca579a2eef31c7a Mon Sep 17 00:00:00 2001 From: onurays Date: Fri, 13 Mar 2020 14:02:42 +0300 Subject: [PATCH 002/156] Fix typo. --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 04825e2ac7..bc25198b36 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features ✨: - Improvements 🙌: - - /join command implemented + - Implementation of /join command Bugfix 🐛: - From e3246a1f2c7ccdd7607d4c5d0fa0fdf261d8f2b4 Mon Sep 17 00:00:00 2001 From: onurays Date: Fri, 13 Mar 2020 18:41:04 +0300 Subject: [PATCH 003/156] Rename JoinedToAnotherRoom to JoinRoomCommandSucces. --- .../home/room/detail/RoomDetailFragment.kt | 16 ++++++++-------- .../home/room/detail/RoomDetailViewEvents.kt | 2 +- .../home/room/detail/RoomDetailViewModel.kt | 2 +- 3 files changed, 10 insertions(+), 10 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 9916fa522b..e67c27bd49 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 @@ -292,18 +292,18 @@ class RoomDetailFragment @Inject constructor( is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable) is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds) is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it) - is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it) - is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG) - is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it) - is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it) - is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it) - is RoomDetailViewEvents.JoinedToAnotherRoom -> handleJoinedToAnotherRoom(it) - is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it) + is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it) + is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG) + is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it) + is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it) + is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it) + is RoomDetailViewEvents.JoinRoomCommandSucces -> handleJoinedToAnotherRoom(it) + is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it) }.exhaustive } } - private fun handleJoinedToAnotherRoom(action: RoomDetailViewEvents.JoinedToAnotherRoom) { + private fun handleJoinedToAnotherRoom(action: RoomDetailViewEvents.JoinRoomCommandSucces) { updateComposerText("") lockSendButton = false navigator.openRoom(vectorBaseActivity, action.roomId) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt index df0ead3b38..20657a1e42 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt @@ -50,7 +50,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents { abstract class SendMessageResult : RoomDetailViewEvents() object MessageSent : SendMessageResult() - data class JoinedToAnotherRoom(val roomId: String) : SendMessageResult() + data class JoinRoomCommandSucces(val roomId: String) : SendMessageResult() class SlashCommandError(val command: Command) : SendMessageResult() class SlashCommandUnknown(val command: String) : SendMessageResult() data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index aa04b80202..4991a9ca01 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -518,7 +518,7 @@ class RoomDetailViewModel @AssistedInject constructor( session.getRoomSummary(command.roomAlias) ?.roomId ?.let { - _viewEvents.post(RoomDetailViewEvents.JoinedToAnotherRoom(it)) + _viewEvents.post(RoomDetailViewEvents.JoinRoomCommandSucces(it)) } } From eb74523905e9bf6bb045abf0c152963640d09506 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 16 Mar 2020 15:30:41 +0100 Subject: [PATCH 004/156] Fix / typo --- .../home/room/detail/RoomDetailFragment.kt | 16 ++++++++-------- .../home/room/detail/RoomDetailViewEvents.kt | 2 +- .../home/room/detail/RoomDetailViewModel.kt | 2 +- 3 files changed, 10 insertions(+), 10 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 e67c27bd49..f83adaf8a7 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 @@ -292,18 +292,18 @@ class RoomDetailFragment @Inject constructor( is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable) is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds) is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it) - is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it) - is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG) - is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it) - is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it) - is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it) - is RoomDetailViewEvents.JoinRoomCommandSucces -> handleJoinedToAnotherRoom(it) - is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it) + is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it) + is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG) + is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it) + is RoomDetailViewEvents.FileTooBigError -> displayFileTooBigError(it) + is RoomDetailViewEvents.DownloadFileState -> handleDownloadFileState(it) + is RoomDetailViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it) + is RoomDetailViewEvents.SendMessageResult -> renderSendMessageResult(it) }.exhaustive } } - private fun handleJoinedToAnotherRoom(action: RoomDetailViewEvents.JoinRoomCommandSucces) { + private fun handleJoinedToAnotherRoom(action: RoomDetailViewEvents.JoinRoomCommandSuccess) { updateComposerText("") lockSendButton = false navigator.openRoom(vectorBaseActivity, action.roomId) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt index 20657a1e42..b24c2ea23e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewEvents.kt @@ -50,7 +50,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents { abstract class SendMessageResult : RoomDetailViewEvents() object MessageSent : SendMessageResult() - data class JoinRoomCommandSucces(val roomId: String) : SendMessageResult() + data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult() class SlashCommandError(val command: Command) : SendMessageResult() class SlashCommandUnknown(val command: String) : SendMessageResult() data class SlashCommandHandled(@StringRes val messageRes: Int? = null) : SendMessageResult() diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 4991a9ca01..f7a6c09022 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -518,7 +518,7 @@ class RoomDetailViewModel @AssistedInject constructor( session.getRoomSummary(command.roomAlias) ?.roomId ?.let { - _viewEvents.post(RoomDetailViewEvents.JoinRoomCommandSucces(it)) + _viewEvents.post(RoomDetailViewEvents.JoinRoomCommandSuccess(it)) } } From 067a22883c627141004953c3858f4aac85606588 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 16 Mar 2020 16:00:22 +0100 Subject: [PATCH 005/156] WorkAround / crash android 10 gif from keyboard fixes #1136 --- CHANGES.md | 4 +++- .../features/attachments/AttachmentsHelper.kt | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d26237fd13..157f528747 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,9 @@ Improvements 🙌: - Implementation of /join command Bugfix 🐛: - - Message transitions in encrypted rooms are jarring #518 + - Message transitions in encrypted rooms are jarring #518 + - Images that failed to send are waiting to be sent forever #1145 + - Fix / Crashed when trying to send a gif from the Gboard #1136 Translations 🗣: - diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt index ba1197b787..c576ebe1b9 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt @@ -177,19 +177,28 @@ class AttachmentsHelper private constructor(private val context: Context, fun handleShareIntent(intent: Intent): Boolean { val type = intent.resolveType(context) ?: return false if (type.startsWith("image")) { - imagePicker.submit(IntentUtils.getPickerIntentForSharing(intent)) + imagePicker.submit(safeShareIntent(intent)) } else if (type.startsWith("video")) { - videoPicker.submit(IntentUtils.getPickerIntentForSharing(intent)) + videoPicker.submit(safeShareIntent(intent)) } else if (type.startsWith("audio")) { - videoPicker.submit(IntentUtils.getPickerIntentForSharing(intent)) + videoPicker.submit(safeShareIntent(intent)) } else if (type.startsWith("application") || type.startsWith("file") || type.startsWith("*")) { - filePicker.submit(IntentUtils.getPickerIntentForSharing(intent)) + filePicker.submit(safeShareIntent(intent)) } else { return false } return true } + private fun safeShareIntent(intent: Intent): Intent { + // Work around for getPickerIntentForSharing doing NPE in android 10 + return try { + IntentUtils.getPickerIntentForSharing(intent) + } catch (failure: Throwable) { + intent + } + } + private fun getPickerManagerForRequestCode(requestCode: Int): PickerManager? { return when (requestCode) { PICK_IMAGE_DEVICE -> imagePicker From 8827b4b5ef19960d4526128b0b5654be952536f2 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 13 Mar 2020 15:48:00 +0100 Subject: [PATCH 006/156] Fix / correctly update local echo failed state + endure Workmanager queues are reset after a clear cache --- .../android/internal/session/DefaultSession.kt | 1 + .../session/content/UploadContentWorker.kt | 10 ++++++++++ .../send/MultipleEventSendingDispatcherWorker.kt | 15 ++++++++++----- 3 files changed, 21 insertions(+), 5 deletions(-) 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 84b76345c8..25dc939196 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 @@ -193,6 +193,7 @@ internal class DefaultSession @Inject constructor( stopAnyBackgroundSync() liveEntityObservers.forEach { it.cancelProcess() } cacheService.get().clearCache(callback) + workManagerProvider.cancelAllWorks() } @Subscribe(threadMode = ThreadMode.MAIN) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt index 1dde25fd78..1c88f87804 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt @@ -77,6 +77,16 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter return Result.success(inputData) } + // Just defensive code to ensure that we never have an uncaught exception that could break the queue + return try { + internalDoWork(params) + } catch (failure: Throwable) { + Timber.e(failure) + handleFailure(params, failure) + } + } + + private suspend fun internalDoWork(params: Params): Result { val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt index 03db817dd6..8c31dd1682 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/MultipleEventSendingDispatcherWorker.kt @@ -23,6 +23,7 @@ import androidx.work.OneTimeWorkRequest import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.internal.di.WorkManagerProvider import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon import im.vector.matrix.android.internal.worker.SessionWorkerParams @@ -49,6 +50,7 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo @Inject lateinit var workManagerProvider: WorkManagerProvider @Inject lateinit var timelineSendEventWorkCommon: TimelineSendEventWorkCommon + @Inject lateinit var localEchoUpdater: LocalEchoUpdater override suspend fun doWork(): Result { Timber.v("Start dispatch sending multiple event work") @@ -57,14 +59,17 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo Timber.e("Work cancelled due to input error from parent") } - if (params.lastFailureMessage != null) { - // Transmit the error - return Result.success(inputData) - } - val sessionComponent = getSessionComponent(params.sessionId) ?: return Result.success() sessionComponent.inject(this) + if (params.lastFailureMessage != null) { + params.events.forEach { event -> + event.eventId?.let { localEchoUpdater.updateSendState(it, SendState.UNDELIVERED) } + } + // Transmit the error if needed? + return Result.success(inputData) + } + // Create a work for every event params.events.forEach { event -> if (params.isEncrypted) { From b37600f536c8c3d076e5eaca37f570d8a42ec8da Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 17 Mar 2020 10:43:48 +0100 Subject: [PATCH 007/156] QuickFix / key banner not clickable --- CHANGES.md | 1 + .../java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt | 1 + .../settings/KeysBackupSettingsRecyclerViewController.kt | 6 +++++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 157f528747..c2f7011c41 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ Bugfix 🐛: - Message transitions in encrypted rooms are jarring #518 - Images that failed to send are waiting to be sent forever #1145 - Fix / Crashed when trying to send a gif from the Gboard #1136 + - Fix / Cannot click on key backup banner when new keys are available Translations 🗣: - diff --git a/vector/src/main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt b/vector/src/main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt index 7ce394b954..8d314f9e58 100755 --- a/vector/src/main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt +++ b/vector/src/main/java/im/vector/riotx/core/ui/views/KeysBackupBanner.kt @@ -123,6 +123,7 @@ class KeysBackupBanner @JvmOverloads constructor( is State.Setup -> { delegate?.setupKeysBackup() } + is State.Update, is State.Recover -> { delegate?.recoverKeysBackup() } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt index 4f2d806ce3..1fec404f7d 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/settings/KeysBackupSettingsRecyclerViewController.kt @@ -31,10 +31,12 @@ import im.vector.riotx.core.epoxy.loadingItem import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.ui.list.GenericItem import im.vector.riotx.core.ui.list.genericItem +import im.vector.riotx.features.settings.VectorPreferences import java.util.UUID import javax.inject.Inject class KeysBackupSettingsRecyclerViewController @Inject constructor(private val stringProvider: StringProvider, + private val vectorPreferences: VectorPreferences, private val session: Session) : TypedEpoxyController() { var listener: Listener? = null @@ -149,7 +151,9 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor(private val s description(keyVersionResult?.algorithm ?: "") } - buildKeysBackupTrust(data.keysBackupVersionTrust) + if (vectorPreferences.developerMode()) { + buildKeysBackupTrust(data.keysBackupVersionTrust) + } } // Footer From a9ed55e6a239796b163bd0ec598f44c8a91bb381 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 17 Mar 2020 10:50:34 +0100 Subject: [PATCH 008/156] Fixes #757 --- vector/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 6c56211e57..f0c3bdc23d 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -1461,7 +1461,7 @@ Why choose Riot.im? Never lose encrypted messages Use Key Backup - New encrypted messages keys + New secure message keys Manage in Key Backup Backing up keys… From 78d90c8f0423a434b0b4ec87593b3eb2a29eafa8 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 17 Mar 2020 11:46:42 +0100 Subject: [PATCH 009/156] prepare release 0.18.1 --- CHANGES.md | 16 +--------------- vector/build.gradle | 4 ++-- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c2f7011c41..08b9c01efc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,6 @@ -Changes in RiotX 0.19.0 (2020-XX-XX) +Changes in RiotX 0.18.1 (2020-03-17) =================================================== -Features ✨: - - - Improvements 🙌: - Implementation of /join command @@ -13,17 +10,6 @@ Bugfix 🐛: - Fix / Crashed when trying to send a gif from the Gboard #1136 - Fix / Cannot click on key backup banner when new keys are available -Translations 🗣: - - - -SDK API changes ⚠️: - - - -Build 🧱: - - - -Other changes: - - Changes in RiotX 0.18.0 (2020-03-11) =================================================== diff --git a/vector/build.gradle b/vector/build.gradle index 2aae593271..2a5a8b3b34 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -15,8 +15,8 @@ androidExtensions { } ext.versionMajor = 0 -ext.versionMinor = 19 -ext.versionPatch = 0 +ext.versionMinor = 18 +ext.versionPatch = 1 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From 20dbe2dd0d4bdf9859ff25ba9d439667c9a440c3 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 17 Mar 2020 14:18:08 +0100 Subject: [PATCH 010/156] version ++ --- CHANGES.md | 24 ++++++++++++++++++++++++ vector/build.gradle | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 08b9c01efc..fc1d75f539 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,27 @@ +Changes in RiotX 0.19.0 (2020-XX-XX) +=================================================== + +Features ✨: + - + +Improvements 🙌: + - + +Bugfix 🐛: + - + +Translations 🗣: + - + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Other changes: + - + Changes in RiotX 0.18.1 (2020-03-17) =================================================== diff --git a/vector/build.gradle b/vector/build.gradle index 2a5a8b3b34..2aae593271 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -15,8 +15,8 @@ androidExtensions { } ext.versionMajor = 0 -ext.versionMinor = 18 -ext.versionPatch = 1 +ext.versionMinor = 19 +ext.versionPatch = 0 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From 06fc5c2dd99ad8fd85732835c8bb8148e4747deb Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 26 Feb 2020 11:44:23 +0100 Subject: [PATCH 011/156] Log tunning --- .../matrix/android/internal/crypto/DefaultCryptoService.kt | 2 +- .../im/vector/matrix/android/internal/crypto/MXOlmDevice.kt | 2 +- .../internal/session/room/timeline/TimelineEventDecryptor.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index bf923e9c58..8e4ecdaa4a 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -735,7 +735,7 @@ internal class DefaultCryptoService @Inject constructor( val userIds = getRoomUserIds(roomId) setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds) } catch (throwable: Throwable) { - Timber.e(throwable) + Timber.e(throwable, "## onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ") } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt index 47ec85ec8c..54f34c6c9d 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt @@ -770,7 +770,7 @@ internal class MXOlmDevice @Inject constructor( return session } } else { - Timber.w("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId") + Timber.v("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId") throw MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt index 2d6656c2e3..02c06b0e56 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt @@ -114,7 +114,7 @@ internal class TimelineEventDecryptor @Inject constructor( Timber.v("Successfully decrypted event $eventId") eventEntity.setDecryptionResult(result) } catch (e: MXCryptoError) { - Timber.w(e, "Failed to decrypt event $eventId") + Timber.v(e, "Failed to decrypt event $eventId") if (e is MXCryptoError.Base && e.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { // Keep track of unknown sessions to automatically try to decrypt on new session eventEntity.decryptionErrorCode = e.errorType.name From 757e90986e438e4b26148cf928d3e8e324182cea Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 26 Feb 2020 11:44:49 +0100 Subject: [PATCH 012/156] Key Req Dev tool initial commit --- .../api/session/crypto/CryptoService.kt | 3 + .../internal/crypto/DefaultCryptoService.kt | 5 + .../DefaultCrossSigningService.kt | 17 ++++ .../internal/crypto/store/IMXCryptoStore.kt | 4 + .../crypto/store/db/RealmCryptoStore.kt | 8 ++ .../im/vector/riotx/core/di/FragmentModule.kt | 12 +++ .../devtools/KeyRequestEpoxyController.kt | 92 +++++++++++++++++++ .../devtools/KeyRequestListFragment.kt | 57 ++++++++++++ .../devtools/KeyRequestListViewModel.kt | 69 ++++++++++++++ .../settings/devtools/KeyRequestsFragment.kt | 57 ++++++++++++ .../layout/fragment_devtool_keyrequests.xml | 22 +++++ vector/src/main/res/values/strings_riotX.xml | 2 + .../xml/vector_settings_advanced_settings.xml | 9 +- 13 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListViewModel.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt create mode 100644 vector/src/main/res/layout/fragment_devtool_keyrequests.xml diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt index 1360924270..13c619f0a5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt @@ -28,6 +28,7 @@ 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.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.NewSessionListener +import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult @@ -129,4 +130,6 @@ interface CryptoService { fun addNewSessionListener(newSessionListener: NewSessionListener) fun removeSessionListener(listener: NewSessionListener) + + fun getOutgoingRoomKeyRequest(): List } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 8e4ecdaa4a..38f29c1fc8 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -115,6 +115,7 @@ internal class DefaultCryptoService @Inject constructor( private val myDeviceInfoHolder: Lazy, // the crypto store private val cryptoStore: IMXCryptoStore, + // Olm device private val olmDevice: MXOlmDevice, // Set of parameters used to configure/customize the end-to-end crypto. @@ -1091,4 +1092,8 @@ internal class DefaultCryptoService @Inject constructor( override fun toString(): String { return "DefaultCryptoService of " + credentials.userId + " (" + credentials.deviceId + ")" } + + override fun getOutgoingRoomKeyRequest(): List { + return cryptoStore.getOutgoingRoomKeyRequests() + } } 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 acc9f4134d..29b844a62e 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 @@ -25,6 +25,7 @@ import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder +import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey import im.vector.matrix.android.internal.crypto.model.KeyUsage import im.vector.matrix.android.internal.crypto.model.rest.UploadSignatureQueryBuilder @@ -61,6 +62,7 @@ internal class DefaultCrossSigningService @Inject constructor( private val taskExecutor: TaskExecutor, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val cryptoCoroutineScope: CoroutineScope, + private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager, private val eventBus: EventBus) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener { private var olmUtility: OlmUtility? = null @@ -736,6 +738,7 @@ internal class DefaultCrossSigningService @Inject constructor( // If it's me, recheck trust of all users and devices? val users = ArrayList() if (otherUserId == userId && currentTrust != trusted) { + reRequestAllPendingRoomKeyRequest() cryptoStore.updateUsersTrust { users.add(it) checkUserTrust(it).isVerified() @@ -751,4 +754,18 @@ internal class DefaultCrossSigningService @Inject constructor( } } } + + private fun reRequestAllPendingRoomKeyRequest() { + Timber.d("## CrossSigning - reRequest pending outgoing room key requests") + cryptoStore.getOutgoingRoomKeyRequests().forEach { + it.requestBody?.let {requestBody -> + if (cryptoStore.getInboundGroupSession(requestBody.sessionId ?: "", requestBody.senderKey ?: "") == null) { + outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody) + } else { + outgoingRoomKeyRequestManager.cancelRoomKeyRequest(requestBody) + } + } + } + } + } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt index e89f4a49ed..29ec0d5f91 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt @@ -424,4 +424,8 @@ internal interface IMXCryptoStore { fun clearOtherUserTrust() fun updateUsersTrust(check: (String) -> Boolean) + + // Dev tools + + fun getOutgoingRoomKeyRequests() : List } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index a93203fc21..e61069461a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -1024,6 +1024,14 @@ internal class RealmCryptoStore @Inject constructor( } } + override fun getOutgoingRoomKeyRequests(): List { + return monarchy.fetchAllMappedSync( { realm -> + realm.where(OutgoingRoomKeyRequestEntity::class.java) + }, { + it.toOutgoingRoomKeyRequest() + }) + } + override fun getCrossSigningInfo(userId: String): MXCrossSigningInfo? { return doRealmQueryAndCopy(realmConfiguration) { realm -> realm.where(CrossSigningInfoEntity::class.java) 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 d7e89a62f6..1bccdb6c25 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 @@ -74,6 +74,8 @@ 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.devtools.KeyRequestListFragment +import im.vector.riotx.features.settings.devtools.KeyRequestsFragment import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment import im.vector.riotx.features.settings.push.PushGatewaysFragment import im.vector.riotx.features.share.IncomingShareFragment @@ -366,4 +368,14 @@ interface FragmentModule { @IntoMap @FragmentKey(AccountDataFragment::class) fun bindAccountDataFragment(fragment: AccountDataFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(KeyRequestListFragment::class) + fun bindKeyRequestListFragment(fragment: KeyRequestListFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(KeyRequestsFragment::class) + fun bindKeyRequestsFragment(fragment: KeyRequestsFragment): Fragment } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt new file mode 100644 index 0000000000..ca0c73409e --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt @@ -0,0 +1,92 @@ +/* + * 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.epoxy.TypedEpoxyController +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.loadingItem +import im.vector.riotx.core.extensions.exhaustive +import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.core.ui.list.genericItem +import im.vector.riotx.core.ui.list.genericItemHeader +import me.gujun.android.span.span +import javax.inject.Inject + +class KeyRequestEpoxyController @Inject constructor( + private val stringProvider: StringProvider +) : TypedEpoxyController() { + + interface InteractionListener { + //fun didTap(data: UserAccountData) + } + + var interactionListener: InteractionListener? = null + + override fun buildModels(data: KeyRequestListViewState?) { + data?.outgoingRoomKeyRequest?.let { async -> + when (async) { + is Uninitialized, + is Loading -> { + loadingItem { + id("loadingOutgoing") + loadingText(stringProvider.getString(R.string.loading)) + } + } + is Fail -> { + genericItem { + id("failOutgoing") + title(async.error.localizedMessage) + } + } + is Success -> { + val requestList = async.invoke().groupBy { it.roomId } + + requestList.forEach { + + genericItemHeader { + id(it.key) + text("roomId: ${it.key}") + } + it.value.forEach { roomKeyRequest -> + genericItem { + id(roomKeyRequest.requestId) + title(roomKeyRequest.requestId) + description( + span { + span("sessionId:") { + textStyle = "bold" + } + +"${roomKeyRequest.sessionId}" + span("\nstate:") { + textStyle = "bold" + } + +"\n${roomKeyRequest.state.name}" + } + ) + } + } + + } + } + }.exhaustive + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListFragment.kt new file mode 100644 index 0000000000..6f47de49c9 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListFragment.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 com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +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 +import im.vector.riotx.core.resources.ColorProvider +import kotlinx.android.synthetic.main.fragment_generic_recycler.* +import javax.inject.Inject + +class KeyRequestListFragment @Inject constructor( + val viewModelFactory: KeyRequestListViewModel.Factory, + private val epoxyController: KeyRequestEpoxyController, + private val colorProvider: ColorProvider +) : VectorBaseFragment() { + + override fun getLayoutResId() = R.layout.fragment_generic_recycler + + private val viewModel: KeyRequestListViewModel by fragmentViewModel(KeyRequestListViewModel::class) + + 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 onDestroyView() { + super.onDestroyView() + recyclerView.cleanup() +// epoxyController.interactionListener = null + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListViewModel.kt new file mode 100644 index 0000000000..6b09273f93 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListViewModel.kt @@ -0,0 +1,69 @@ +/* + * 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.Success +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.crypto.IncomingRoomKeyRequest +import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest +import im.vector.riotx.core.platform.EmptyAction +import im.vector.riotx.core.platform.EmptyViewEvents +import im.vector.riotx.core.platform.VectorViewModel + +data class KeyRequestListViewState( + val incomingRequests: Async> = Uninitialized, + val outgoingRoomKeyRequest: Async> = Uninitialized +) : MvRxState + +class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState: KeyRequestListViewState, + private val session: Session) + : VectorViewModel(initialState) { + + init { + session.cryptoService().getOutgoingRoomKeyRequest().let { + setState { + copy( + outgoingRoomKeyRequest = Success(it) + ) + } + } + } + + override fun handle(action: EmptyAction) {} + + @AssistedInject.Factory + interface Factory { + fun create(initialState: KeyRequestListViewState): KeyRequestListViewModel + } + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: KeyRequestListViewState): KeyRequestListViewModel? { + val fragment: KeyRequestListFragment = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.viewModelFactory.create(state) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt new file mode 100644 index 0000000000..110c9eacce --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.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.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.google.android.material.tabs.TabLayoutMediator +import im.vector.riotx.R +import im.vector.riotx.core.platform.VectorBaseActivity +import im.vector.riotx.core.platform.VectorBaseFragment +import kotlinx.android.synthetic.main.fragment_devtool_keyrequests.* +import javax.inject.Inject + +class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() { + + override fun getLayoutResId(): Int = R.layout.fragment_devtool_keyrequests + + override fun onResume() { + super.onResume() + (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.key_share_request) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + devToolKeyRequestPager.adapter = KeyReqPagerAdapter(requireActivity()) + + TabLayoutMediator(devToolKeyRequestTabs, devToolKeyRequestPager) { tab, _ -> + tab.text = "Outgoing" + }.attach() + + } + + private inner class KeyReqPagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) { + override fun getItemCount(): Int = 1 + + override fun createFragment(position: Int): Fragment { + return childFragmentManager.fragmentFactory.instantiate(requireContext().classLoader, KeyRequestListFragment::class.java.name) + } + } +} diff --git a/vector/src/main/res/layout/fragment_devtool_keyrequests.xml b/vector/src/main/res/layout/fragment_devtool_keyrequests.xml new file mode 100644 index 0000000000..ccd3cee660 --- /dev/null +++ b/vector/src/main/res/layout/fragment_devtool_keyrequests.xml @@ -0,0 +1,22 @@ + + + + + + + + \ 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 cb2a29de28..5cb94a49f7 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -7,6 +7,8 @@ + + Key Requests 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 34cd8743be..e92aae3ff9 100644 --- a/vector/src/main/res/xml/vector_settings_advanced_settings.xml +++ b/vector/src/main/res/xml/vector_settings_advanced_settings.xml @@ -65,13 +65,20 @@ app:fragment="im.vector.riotx.features.settings.push.PushRulesFragment" /> - + + + \ No newline at end of file From 9e63a3219c7f5f9ae5c6e0f725aad19d61ab326d Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 26 Feb 2020 12:19:32 +0100 Subject: [PATCH 013/156] Add Re-Request keys for fail to decrypt --- .../riotx/features/home/room/detail/RoomDetailAction.kt | 1 + .../features/home/room/detail/RoomDetailFragment.kt | 3 +++ .../features/home/room/detail/RoomDetailViewModel.kt | 9 +++++++++ .../room/detail/timeline/action/EventSharedAction.kt | 3 +++ .../detail/timeline/action/MessageActionsViewModel.kt | 6 +++++- vector/src/main/res/values/strings.xml | 2 +- 6 files changed, 22 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt index 2ef7b11b0e..3230686d58 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailAction.kt @@ -71,4 +71,5 @@ sealed class RoomDetailAction : VectorViewModelAction { data class DeclineVerificationRequest(val transactionId: String, val otherUserId: String) : RoomDetailAction() data class RequestVerification(val userId: String) : RoomDetailAction() data class ResumeVerification(val transactionId: String, val otherUserId: String?) : RoomDetailAction() + data class ReRequestKeys(val eventId: String) : RoomDetailAction() } 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 f83adaf8a7..ac16ee348d 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 @@ -1236,6 +1236,9 @@ class RoomDetailFragment @Inject constructor( is EventSharedAction.OnUrlLongClicked -> { onUrlLongClicked(action.url) } + is EventSharedAction.ReRequestKey -> { + roomDetailViewModel.handle(RoomDetailAction.ReRequestKeys(action.eventId)) + } else -> { Toast.makeText(context, "Action $action is not implemented yet", Toast.LENGTH_LONG).show() } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index f7a6c09022..2ad90f073a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -209,6 +209,7 @@ class RoomDetailViewModel @AssistedInject constructor( is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) is RoomDetailAction.RequestVerification -> handleRequestVerification(action) is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) + is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) } } @@ -886,6 +887,14 @@ class RoomDetailViewModel @AssistedInject constructor( } } + private fun handleReRequestKeys(action: RoomDetailAction.ReRequestKeys) { + // Check if this request is still active and handled by me + room.getTimeLineEvent(action.eventId)?.let { + session.cryptoService().reRequestRoomKeyForEvent(it.root) + _viewEvents.post(RoomDetailViewEvents.ShowMessage(stringProvider.getString(R.string.e2e_re_request_encryption_key_dialog_content))) + } + } + private fun handleReplyToOptions(action: RoomDetailAction.ReplyToOptions) { room.sendOptionsReply(action.eventId, action.optionIndex, action.optionValue) } 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 b9e2ab2093..00fc1f22c6 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 @@ -102,4 +102,7 @@ sealed class EventSharedAction(@StringRes val titleRes: Int, // An url in the event preview has been long clicked data class OnUrlLongClicked(val url: String) : EventSharedAction(0, 0) + + data class ReRequestKey(val eventId: String) : + EventSharedAction(R.string.e2e_re_request_encryption_key, R.drawable.key_small) } 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 1fe1db27d7..f1e9dc5bee 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 @@ -271,9 +271,13 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } } + if (timelineEvent.isEncrypted() && timelineEvent.root.mCryptoError != null) { + add(EventSharedAction.ReRequestKey(timelineEvent.eventId)) + } + if (vectorPreferences.developerMode()) { add(EventSharedAction.ViewSource(timelineEvent.root.toContentStringWithIndent())) - if (timelineEvent.isEncrypted()) { + if (timelineEvent.isEncrypted() && timelineEvent.root.mxDecryptionResult != null) { val decryptedContent = timelineEvent.root.toClearContentStringWithIndent() ?: stringProvider.getString(R.string.encryption_information_decryption_error) add(EventSharedAction.ViewDecryptedSource(decryptedContent)) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index f0c3bdc23d..76bcfe7290 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -301,7 +301,7 @@ You need to log back in to generate end-to-end encryption keys for this session and submit the public key to your homeserver.\nThis is a once off.\nSorry for the inconvenience. - Re-request encryption keys from your other sessions. + Re-request encryption keys from your other sessions. Key request sent. From 5e0235e48da1bd123209c6ac8e8a5fbd0111af7f Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 26 Feb 2020 14:02:46 +0100 Subject: [PATCH 014/156] Add option to recover from backup + hide if not applicable --- .../home/room/detail/RoomDetailFragment.kt | 6 +++ .../timeline/action/EventSharedAction.kt | 4 ++ .../action/MessageActionsViewModel.kt | 37 ++++++++++++++++++- vector/src/main/res/values/strings_riotX.xml | 3 ++ 4 files changed, 49 insertions(+), 1 deletion(-) 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 ac16ee348d..e748478e6a 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 @@ -124,6 +124,7 @@ import im.vector.riotx.features.attachments.preview.AttachmentsPreviewActivity import im.vector.riotx.features.attachments.preview.AttachmentsPreviewArgs import im.vector.riotx.features.attachments.toGroupedContentAttachmentData import im.vector.riotx.features.command.Command +import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreActivity import im.vector.riotx.features.crypto.util.toImageRes import im.vector.riotx.features.crypto.verification.VerificationBottomSheet import im.vector.riotx.features.home.AvatarRenderer @@ -1239,6 +1240,11 @@ class RoomDetailFragment @Inject constructor( is EventSharedAction.ReRequestKey -> { roomDetailViewModel.handle(RoomDetailAction.ReRequestKeys(action.eventId)) } + is EventSharedAction.UseKeyBackup -> { + context?.let { + startActivity(KeysBackupRestoreActivity.intent(it)) + } + } else -> { Toast.makeText(context, "Action $action is not implemented yet", Toast.LENGTH_LONG).show() } 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 00fc1f22c6..fe72d32fb2 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 @@ -105,4 +105,8 @@ sealed class EventSharedAction(@StringRes val titleRes: Int, data class ReRequestKey(val eventId: String) : EventSharedAction(R.string.e2e_re_request_encryption_key, R.drawable.key_small) + + object UseKeyBackup : + EventSharedAction(R.string.e2e_use_keybackup, R.drawable.shield) + } 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 f1e9dc5bee..da2da452ab 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 @@ -25,6 +25,7 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import dagger.Lazy import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.isTextMessage import im.vector.matrix.android.api.session.events.model.toModel @@ -209,6 +210,31 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } ?: "" } + private fun getRedactionReason(timelineEvent: TimelineEvent): String { + return (timelineEvent + .root + .unsignedData + ?.redactedEvent + ?.content + ?.get("reason") as? String) + ?.takeIf { it.isNotBlank() } + .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() ?: timelineEvent.root.getClearContent().toModel() @@ -272,7 +298,16 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } if (timelineEvent.isEncrypted() && timelineEvent.root.mCryptoError != null) { - add(EventSharedAction.ReRequestKey(timelineEvent.eventId)) + val keysBackupService = session.cryptoService().keysBackupService() + if (keysBackupService.state == KeysBackupState.NotTrusted + || (keysBackupService.state == KeysBackupState.ReadyToBackUp + && keysBackupService.canRestoreKeys()) + ) { + add(EventSharedAction.UseKeyBackup) + } + if (session.cryptoService().getCryptoDeviceInfo(session.myUserId).size > 1) { + add(EventSharedAction.ReRequestKey(timelineEvent.eventId)) + } } if (vectorPreferences.developerMode()) { diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 5cb94a49f7..7105d56a5c 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -9,6 +9,9 @@ Key Requests + + Unlock encrypted messages history + From 8ff31ac49d30f6b9cc2b759b759c215acf117eb0 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 26 Feb 2020 14:03:33 +0100 Subject: [PATCH 015/156] cleaning klint --- .../crypto/crosssigning/DefaultCrossSigningService.kt | 3 +-- .../android/internal/crypto/store/db/RealmCryptoStore.kt | 2 +- .../home/room/detail/timeline/action/EventSharedAction.kt | 1 - .../features/settings/devtools/KeyRequestEpoxyController.kt | 4 +--- .../features/settings/devtools/KeyRequestListFragment.kt | 1 - .../riotx/features/settings/devtools/KeyRequestsFragment.kt | 1 - 6 files changed, 3 insertions(+), 9 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 29b844a62e..6251f72418 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 @@ -758,7 +758,7 @@ internal class DefaultCrossSigningService @Inject constructor( private fun reRequestAllPendingRoomKeyRequest() { Timber.d("## CrossSigning - reRequest pending outgoing room key requests") cryptoStore.getOutgoingRoomKeyRequests().forEach { - it.requestBody?.let {requestBody -> + it.requestBody?.let { requestBody -> if (cryptoStore.getInboundGroupSession(requestBody.sessionId ?: "", requestBody.senderKey ?: "") == null) { outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody) } else { @@ -767,5 +767,4 @@ internal class DefaultCrossSigningService @Inject constructor( } } } - } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index e61069461a..58561a079c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -1025,7 +1025,7 @@ internal class RealmCryptoStore @Inject constructor( } override fun getOutgoingRoomKeyRequests(): List { - return monarchy.fetchAllMappedSync( { realm -> + return monarchy.fetchAllMappedSync({ realm -> realm.where(OutgoingRoomKeyRequestEntity::class.java) }, { it.toOutgoingRoomKeyRequest() 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 fe72d32fb2..e38c055d52 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 @@ -108,5 +108,4 @@ sealed class EventSharedAction(@StringRes val titleRes: Int, object UseKeyBackup : EventSharedAction(R.string.e2e_use_keybackup, R.drawable.shield) - } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt index ca0c73409e..4169ee71f4 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt @@ -35,7 +35,7 @@ class KeyRequestEpoxyController @Inject constructor( ) : TypedEpoxyController() { interface InteractionListener { - //fun didTap(data: UserAccountData) + // fun didTap(data: UserAccountData) } var interactionListener: InteractionListener? = null @@ -60,7 +60,6 @@ class KeyRequestEpoxyController @Inject constructor( val requestList = async.invoke().groupBy { it.roomId } requestList.forEach { - genericItemHeader { id(it.key) text("roomId: ${it.key}") @@ -83,7 +82,6 @@ class KeyRequestEpoxyController @Inject constructor( ) } } - } } }.exhaustive diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListFragment.kt index 6f47de49c9..71a4e19343 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListFragment.kt @@ -23,7 +23,6 @@ import com.airbnb.mvrx.withState 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 import im.vector.riotx.core.resources.ColorProvider import kotlinx.android.synthetic.main.fragment_generic_recycler.* diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt index 110c9eacce..8b78f52c1e 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt @@ -44,7 +44,6 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() { TabLayoutMediator(devToolKeyRequestTabs, devToolKeyRequestPager) { tab, _ -> tab.text = "Outgoing" }.attach() - } private inner class KeyReqPagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) { From b67735c31a84cb0a58478dc69240037922bdc7e6 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 26 Feb 2020 19:11:20 +0100 Subject: [PATCH 016/156] Incoming Secret Share request support crypto DB migration --- .../crypto/keysbackup/KeysBackupTest.kt | 9 +- .../api/session/crypto/CryptoService.kt | 6 +- ...istener.kt => GossipingRequestListener.kt} | 13 +- .../internal/crypto/DefaultCryptoService.kt | 21 +- ...tion.kt => IncomingRequestCancellation.kt} | 14 +- .../internal/crypto/IncomingRoomKeyRequest.kt | 2 +- .../crypto/IncomingRoomKeyRequestManager.kt | 289 ++++++++++++------ .../crypto/IncomingSecretShareRequest.kt | 79 +++++ ...ommon.kt => IncomingShareRequestCommon.kt} | 2 +- .../internal/crypto/OutgoingRoomKeyRequest.kt | 70 +---- .../crypto/OutgoingRoomKeyRequestManager.kt | 40 +-- .../internal/crypto/OutgoingSecretRequest.kt | 36 +++ .../internal/crypto/OutgoingShareRequest.kt | 25 ++ .../internal/crypto/ShareRequestState.kt | 79 +++++ .../crypto/ShareSecretCryptoProvider.kt | 64 ++++ .../crypto/algorithms/IMXDecrypting.kt | 3 + .../model/event/SecretSendEventContent.kt | 28 ++ ...KeyShare.kt => GossipingToDeviceObject.kt} | 2 +- .../crypto/model/rest/RoomKeyShareRequest.kt | 4 +- .../crypto/model/rest/SecretShareRequest.kt | 37 +++ ...llation.kt => ShareRequestCancellation.kt} | 7 +- .../internal/crypto/store/IMXCryptoStore.kt | 11 +- .../crypto/store/db/RealmCryptoStore.kt | 31 +- .../store/db/RealmCryptoStoreMigration.kt | 185 ++++++----- .../crypto/store/db/RealmCryptoStoreModule.kt | 6 +- .../db/model/IncomingSecretRequestEntity.kt | 37 +++ .../db/model/OutgoingRoomKeyRequestEntity.kt | 3 +- .../db/model/OutgoingSecretRequestEntity.kt | 63 ++++ .../crypto/keysrequest/KeyRequestHandler.kt | 30 +- .../NotificationDrawerManager.kt | 4 +- 30 files changed, 871 insertions(+), 329 deletions(-) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/keyshare/{RoomKeysRequestListener.kt => GossipingRequestListener.kt} (70%) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/{IncomingRoomKeyRequestCancellation.kt => IncomingRequestCancellation.kt} (77%) create mode 100755 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingSecretShareRequest.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/{IncomingRoomKeyRequestCommon.kt => IncomingShareRequestCommon.kt} (95%) create mode 100755 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingSecretRequest.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingShareRequest.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareRequestState.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/SecretSendEventContent.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/{RoomKeyShare.kt => GossipingToDeviceObject.kt} (94%) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SecretShareRequest.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/{RoomKeyShareCancellation.kt => ShareRequestCancellation.kt} (80%) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingSecretRequestEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingSecretRequestEntity.kt diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt index 77ba66d341..e599eda7f9 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt @@ -34,7 +34,6 @@ import im.vector.matrix.android.common.assertDictEquals import im.vector.matrix.android.common.assertListEquals import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import im.vector.matrix.android.internal.crypto.MegolmSessionData -import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo @@ -335,9 +334,9 @@ class KeysBackupTest : InstrumentedTest { // - Check the SDK sent key share requests val cryptoStore2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store val unsentRequest = cryptoStore2 - .getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.UNSENT)) + .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.UNSENT)) val sentRequest = cryptoStore2 - .getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.SENT)) + .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.SENT)) // Request is either sent or unsent assertTrue(unsentRequest != null || sentRequest != null) @@ -357,9 +356,9 @@ class KeysBackupTest : InstrumentedTest { // - There must be no more pending key share requests val unsentRequestAfterRestoration = cryptoStore2 - .getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.UNSENT)) + .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.UNSENT)) val sentRequestAfterRestoration = cryptoStore2 - .getOutgoingRoomKeyRequestByState(setOf(OutgoingRoomKeyRequest.RequestState.SENT)) + .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.SENT)) // Request is either sent or unsent assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt index 13c619f0a5..d6823d49cb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt @@ -22,7 +22,7 @@ import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.listeners.ProgressListener import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService -import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener +import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener import im.vector.matrix.android.api.session.crypto.verification.VerificationService import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Event @@ -91,9 +91,9 @@ interface CryptoService { fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) - fun addRoomKeysRequestListener(listener: RoomKeysRequestListener) + fun addRoomKeysRequestListener(listener: GossipingRequestListener) - fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener) + fun removeRoomKeysRequestListener(listener: GossipingRequestListener) fun getDevicesList(callback: MatrixCallback) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/keyshare/RoomKeysRequestListener.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/keyshare/GossipingRequestListener.kt similarity index 70% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/keyshare/RoomKeysRequestListener.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/keyshare/GossipingRequestListener.kt index 5bce27e1b4..1dad685f41 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/keyshare/RoomKeysRequestListener.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/keyshare/GossipingRequestListener.kt @@ -17,12 +17,13 @@ package im.vector.matrix.android.api.session.crypto.keyshare import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest -import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCancellation +import im.vector.matrix.android.internal.crypto.IncomingRequestCancellation +import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest /** * Room keys events listener */ -interface RoomKeysRequestListener { +interface GossipingRequestListener { /** * An room key request has been received. * @@ -30,10 +31,16 @@ interface RoomKeysRequestListener { */ fun onRoomKeyRequest(request: IncomingRoomKeyRequest) + /** + * Returns the secret value to be shared + * @return true if is handled + */ + fun onSecretShareRequest(request: IncomingSecretShareRequest) : Boolean + /** * A room key request cancellation has been received. * * @param request the cancellation request */ - fun onRoomKeyRequestCancellation(request: IncomingRoomKeyRequestCancellation) + fun onRoomKeyRequestCancellation(request: IncomingRequestCancellation) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 38f29c1fc8..2ea0a71c50 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -33,7 +33,7 @@ import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.listeners.ProgressListener import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.MXCryptoError -import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener +import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener 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.EventType @@ -308,7 +308,7 @@ internal class DefaultCryptoService @Inject constructor( deviceListManager.invalidateAllDeviceLists() deviceListManager.refreshOutdatedDeviceLists() } else { - incomingRoomKeyRequestManager.processReceivedRoomKeyRequests() + incomingRoomKeyRequestManager.processReceivedGossipingRequests() } }.fold( { @@ -369,7 +369,7 @@ internal class DefaultCryptoService @Inject constructor( // Make sure we process to-device messages before generating new one-time-keys #2782 deviceListManager.refreshOutdatedDeviceLists() oneTimeKeysUploader.maybeUploadOneTimeKeys() - incomingRoomKeyRequestManager.processReceivedRoomKeyRequests() + incomingRoomKeyRequestManager.processReceivedGossipingRequests() } } } @@ -694,8 +694,13 @@ internal class DefaultCryptoService @Inject constructor( EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> { onRoomKeyEvent(event) } + + EventType.REQUEST_SECRET, EventType.ROOM_KEY_REQUEST -> { - incomingRoomKeyRequestManager.onRoomKeyRequestEvent(event) + incomingRoomKeyRequestManager.onGossipingRequestEvent(event) + } + EventType.SEND_SECRET -> { + // incomingRoomKeyRequestManager.onGossipingRequestEvent(event) } else -> { // ignore @@ -1031,20 +1036,20 @@ internal class DefaultCryptoService @Inject constructor( } /** - * Add a RoomKeysRequestListener listener. + * Add a GossipingRequestListener listener. * * @param listener listener */ - override fun addRoomKeysRequestListener(listener: RoomKeysRequestListener) { + override fun addRoomKeysRequestListener(listener: GossipingRequestListener) { incomingRoomKeyRequestManager.addRoomKeysRequestListener(listener) } /** - * Add a RoomKeysRequestListener listener. + * Add a GossipingRequestListener listener. * * @param listener listener */ - override fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener) { + override fun removeRoomKeysRequestListener(listener: GossipingRequestListener) { incomingRoomKeyRequestManager.removeRoomKeysRequestListener(listener) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestCancellation.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRequestCancellation.kt similarity index 77% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestCancellation.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRequestCancellation.kt index 6779936f3a..7751f149f1 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestCancellation.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRequestCancellation.kt @@ -18,12 +18,12 @@ package im.vector.matrix.android.internal.crypto 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.internal.crypto.model.rest.RoomKeyShareCancellation +import im.vector.matrix.android.internal.crypto.model.rest.ShareRequestCancellation /** - * IncomingRoomKeyRequestCancellation describes the incoming room key cancellation. + * IncomingRequestCancellation describes the incoming room key cancellation. */ -data class IncomingRoomKeyRequestCancellation( +data class IncomingRequestCancellation( /** * The user id */ @@ -38,18 +38,18 @@ data class IncomingRoomKeyRequestCancellation( * The request id */ override val requestId: String? = null -) : IncomingRoomKeyRequestCommon { +) : IncomingShareRequestCommon { companion object { /** * Factory * * @param event the event */ - fun fromEvent(event: Event): IncomingRoomKeyRequestCancellation? { + fun fromEvent(event: Event): IncomingRequestCancellation? { return event.getClearContent() - .toModel() + .toModel() ?.let { - IncomingRoomKeyRequestCancellation( + IncomingRequestCancellation( userId = event.senderId, deviceId = it.requestingDeviceId, requestId = it.requestId diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequest.kt index 39b4678a27..8648041094 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequest.kt @@ -57,7 +57,7 @@ data class IncomingRoomKeyRequest( */ @Transient var ignore: Runnable? = null -) : IncomingRoomKeyRequestCommon { +) : IncomingShareRequestCommon { companion object { /** * Factory diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt index 92a117d64b..2e4b37189d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt @@ -17,9 +17,14 @@ package im.vector.matrix.android.internal.crypto import im.vector.matrix.android.api.auth.data.Credentials -import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener +import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService +import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShare +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.internal.crypto.model.rest.GossipingToDeviceObject import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.session.SessionScope import timber.log.Timber @@ -29,18 +34,21 @@ import javax.inject.Inject internal class IncomingRoomKeyRequestManager @Inject constructor( private val credentials: Credentials, private val cryptoStore: IMXCryptoStore, + private val crossSigningService: CrossSigningService, + private val secrSecretCryptoProvider: ShareSecretCryptoProvider, private val roomDecryptorProvider: RoomDecryptorProvider) { // list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations // we received in the current sync. - private val receivedRoomKeyRequests = ArrayList() - private val receivedRoomKeyRequestCancellations = ArrayList() + private val receiveGossipingRequests = ArrayList() + private val receivedRequestCancellations = ArrayList() // the listeners - private val roomKeysRequestListeners: MutableSet = HashSet() + private val gossipingRequestListeners: MutableSet = HashSet() init { - receivedRoomKeyRequests.addAll(cryptoStore.getPendingIncomingRoomKeyRequests()) + receiveGossipingRequests.addAll(cryptoStore.getPendingIncomingSecretShareRequests()) + receiveGossipingRequests.addAll(cryptoStore.getPendingIncomingRoomKeyRequests()) } /** @@ -49,101 +57,53 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( * * @param event the announcement event. */ - fun onRoomKeyRequestEvent(event: Event) { - when (val roomKeyShareAction = event.getClearContent()?.get("action") as? String) { - RoomKeyShare.ACTION_SHARE_REQUEST -> IncomingRoomKeyRequest.fromEvent(event)?.let { receivedRoomKeyRequests.add(it) } - RoomKeyShare.ACTION_SHARE_CANCELLATION -> IncomingRoomKeyRequestCancellation.fromEvent(event)?.let { receivedRoomKeyRequestCancellations.add(it) } - else -> Timber.e("## onRoomKeyRequestEvent() : unsupported action $roomKeyShareAction") + fun onGossipingRequestEvent(event: Event) { + val roomKeyShare = event.getClearContent().toModel() + when (roomKeyShare?.action) { + GossipingToDeviceObject.ACTION_SHARE_REQUEST -> { + if (event.getClearType() == EventType.REQUEST_SECRET) { + IncomingSecretShareRequest.fromEvent(event)?.let { + receiveGossipingRequests.add(it) + } + } else if (event.getClearType() == EventType.ROOM_KEY_REQUEST) { + IncomingRoomKeyRequest.fromEvent(event)?.let { + receiveGossipingRequests.add(it) + } + } + } + GossipingToDeviceObject.ACTION_SHARE_CANCELLATION -> IncomingRequestCancellation.fromEvent(event)?.let { receivedRequestCancellations.add(it) } + else -> Timber.e("## onGossipingRequestEvent() : unsupported action ${roomKeyShare?.action}") } } /** - * Process any m.room_key_request events which were queued up during the + * Process any m.room_key_request or m.secret.request events which were queued up during the * current sync. * It must be called on CryptoThread */ - fun processReceivedRoomKeyRequests() { - val roomKeyRequestsToProcess = receivedRoomKeyRequests.toList() - receivedRoomKeyRequests.clear() + fun processReceivedGossipingRequests() { + val roomKeyRequestsToProcess = receiveGossipingRequests.toList() + receiveGossipingRequests.clear() for (request in roomKeyRequestsToProcess) { - val userId = request.userId - val deviceId = request.deviceId - val body = request.requestBody - val roomId = body!!.roomId - val alg = body.algorithm - - Timber.v("m.room_key_request from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}") - if (userId == null || credentials.userId != userId) { - // TODO: determine if we sent this device the keys already: in - Timber.w("## processReceivedRoomKeyRequests() : Ignoring room key request from other user for now") - return - } - // TODO: should we queue up requests we don't yet have keys for, in case they turn up later? - // if we don't have a decryptor for this room/alg, we don't have - // the keys for the requested events, and can drop the requests. - val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg) - if (null == decryptor) { - Timber.w("## processReceivedRoomKeyRequests() : room key request for unknown $alg in room $roomId") - continue - } - if (!decryptor.hasKeysForKeyRequest(request)) { - Timber.w("## processReceivedRoomKeyRequests() : room key request for unknown session ${body.sessionId!!}") - cryptoStore.deleteIncomingRoomKeyRequest(request) - continue - } - - if (credentials.deviceId == deviceId && credentials.userId == userId) { - Timber.v("## processReceivedRoomKeyRequests() : oneself device - ignored") - cryptoStore.deleteIncomingRoomKeyRequest(request) - continue - } - request.share = Runnable { - decryptor.shareKeysWithDevice(request) - cryptoStore.deleteIncomingRoomKeyRequest(request) - } - request.ignore = Runnable { - cryptoStore.deleteIncomingRoomKeyRequest(request) - } - // if the device is verified already, share the keys - val device = cryptoStore.getUserDevice(userId, deviceId!!) - if (device != null) { - if (device.isVerified) { - Timber.v("## processReceivedRoomKeyRequests() : device is already verified: sharing keys") - cryptoStore.deleteIncomingRoomKeyRequest(request) - request.share?.run() - continue - } - - if (device.isBlocked) { - Timber.v("## processReceivedRoomKeyRequests() : device is blocked -> ignored") - cryptoStore.deleteIncomingRoomKeyRequest(request) - continue - } - } - - // If cross signing is available on account we automatically discard untrust devices request - if (cryptoStore.getMyCrossSigningInfo() != null) { - // At this point the device is unknown, we don't want to bother user with that - cryptoStore.deleteIncomingRoomKeyRequest(request) - continue - } - - cryptoStore.storeIncomingRoomKeyRequest(request) - onRoomKeyRequest(request) - } - - var receivedRoomKeyRequestCancellations: List? = null - - synchronized(this.receivedRoomKeyRequestCancellations) { - if (this.receivedRoomKeyRequestCancellations.isNotEmpty()) { - receivedRoomKeyRequestCancellations = this.receivedRoomKeyRequestCancellations.toList() - this.receivedRoomKeyRequestCancellations.clear() + if (request is IncomingRoomKeyRequest) { + processIncomingRoomKeyRequest(request) + } else if (request is IncomingSecretShareRequest) { + processIncomingSecretShareRequest(request) } } - if (null != receivedRoomKeyRequestCancellations) { - for (request in receivedRoomKeyRequestCancellations!!) { - Timber.v("## ## processReceivedRoomKeyRequests() : m.room_key_request cancellation for " + request.userId + var receivedRequestCancellations: List? = null + + synchronized(this.receivedRequestCancellations) { + if (this.receivedRequestCancellations.isNotEmpty()) { + receivedRequestCancellations = this.receivedRequestCancellations.toList() + this.receivedRequestCancellations.clear() + } + } + + if (null != receivedRequestCancellations) { + for (request in receivedRequestCancellations!!) { + Timber.v("## ## processReceivedGossipingRequests() : m.room_key_request cancellation for " + request.userId + ":" + request.deviceId + " id " + request.requestId) // we should probably only notify the app of cancellations we told it @@ -155,14 +115,122 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( } } + private fun processIncomingRoomKeyRequest(request: IncomingRoomKeyRequest) { + val userId = request.userId + val deviceId = request.deviceId + val body = request.requestBody + val roomId = body!!.roomId + val alg = body.algorithm + + Timber.v("m.room_key_request from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}") + if (userId == null || credentials.userId != userId) { + // TODO: determine if we sent this device the keys already: in + Timber.w("## processReceivedGossipingRequests() : Ignoring room key request from other user for now") + cryptoStore.deleteIncomingRoomKeyRequest(request) + return + } + // TODO: should we queue up requests we don't yet have keys for, in case they turn up later? + // if we don't have a decryptor for this room/alg, we don't have + // the keys for the requested events, and can drop the requests. + val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg) + if (null == decryptor) { + Timber.w("## processReceivedGossipingRequests() : room key request for unknown $alg in room $roomId") + return + } + if (!decryptor.hasKeysForKeyRequest(request)) { + Timber.w("## processReceivedGossipingRequests() : room key request for unknown session ${body.sessionId!!}") + cryptoStore.deleteIncomingRoomKeyRequest(request) + return + } + + if (credentials.deviceId == deviceId && credentials.userId == userId) { + Timber.v("## processReceivedGossipingRequests() : oneself device - ignored") + cryptoStore.deleteIncomingRoomKeyRequest(request) + return + } + request.share = Runnable { + decryptor.shareKeysWithDevice(request) + cryptoStore.deleteIncomingRoomKeyRequest(request) + } + request.ignore = Runnable { + cryptoStore.deleteIncomingRoomKeyRequest(request) + } + // if the device is verified already, share the keys + val device = cryptoStore.getUserDevice(userId, deviceId!!) + if (device != null) { + if (device.isVerified) { + Timber.v("## processReceivedGossipingRequests() : device is already verified: sharing keys") + cryptoStore.deleteIncomingRoomKeyRequest(request) + request.share?.run() + return + } + + if (device.isBlocked) { + Timber.v("## processReceivedGossipingRequests() : device is blocked -> ignored") + cryptoStore.deleteIncomingRoomKeyRequest(request) + return + } + } + + // If cross signing is available on account we automatically discard untrust devices request + if (cryptoStore.getMyCrossSigningInfo() != null) { + // At this point the device is unknown, we don't want to bother user with that + cryptoStore.deleteIncomingRoomKeyRequest(request) + return + } + + cryptoStore.storeIncomingRoomKeyRequest(request) + + // Legacy, pass to application layer to decide what to do + onRoomKeyRequest(request) + } + + private fun processIncomingSecretShareRequest(request: IncomingSecretShareRequest) { + val secretName = request.secretName ?: return Unit.also { + cryptoStore.deleteIncomingSecretRequest(request) + Timber.v("## processIncomingSecretShareRequest() : Missing secret name") + } + + val userId = request.userId + if (userId == null || credentials.userId != userId) { + Timber.e("## processIncomingSecretShareRequest() : Ignoring secret share request from other user") + cryptoStore.deleteIncomingRoomKeyRequest(request) + return + } + + when (secretName) { + SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned + USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user + else -> null + }?.let { secretValue -> + // TODO check if locally trusted and not outdated + if (cryptoStore.getUserDevice(userId, request.deviceId ?: "")?.trustLevel?.isLocallyVerified() == true) { + secrSecretCryptoProvider.shareSecretWithDevice(request, secretValue) + cryptoStore.deleteIncomingRoomKeyRequest(request) + } + return + } + + request.ignore = Runnable { + cryptoStore.deleteIncomingRoomKeyRequest(request) + } + + request.share = { secretValue -> + secrSecretCryptoProvider.shareSecretWithDevice(request, secretValue) + cryptoStore.deleteIncomingRoomKeyRequest(request) + } + + onShareRequest(request) + } + /** * Dispatch onRoomKeyRequest * * @param request the request */ private fun onRoomKeyRequest(request: IncomingRoomKeyRequest) { - synchronized(roomKeysRequestListeners) { - for (listener in roomKeysRequestListeners) { + synchronized(gossipingRequestListeners) { + for (listener in gossipingRequestListeners) { try { listener.onRoomKeyRequest(request) } catch (e: Exception) { @@ -172,14 +240,33 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( } } + /** + * Ask for a value to the listeners, and take the first one + */ + private fun onShareRequest(request: IncomingSecretShareRequest) { + synchronized(gossipingRequestListeners) { + for (listener in gossipingRequestListeners) { + try { + if (listener.onSecretShareRequest(request)) { + return + } + } catch (e: Exception) { + Timber.e(e, "## onRoomKeyRequest() failed") + } + } + } + // Not handled, ignore + request.ignore?.run() + } + /** * A room key request cancellation has been received. * * @param request the cancellation request */ - private fun onRoomKeyRequestCancellation(request: IncomingRoomKeyRequestCancellation) { - synchronized(roomKeysRequestListeners) { - for (listener in roomKeysRequestListeners) { + private fun onRoomKeyRequestCancellation(request: IncomingRequestCancellation) { + synchronized(gossipingRequestListeners) { + for (listener in gossipingRequestListeners) { try { listener.onRoomKeyRequestCancellation(request) } catch (e: Exception) { @@ -189,15 +276,15 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( } } - fun addRoomKeysRequestListener(listener: RoomKeysRequestListener) { - synchronized(roomKeysRequestListeners) { - roomKeysRequestListeners.add(listener) + fun addRoomKeysRequestListener(listener: GossipingRequestListener) { + synchronized(gossipingRequestListeners) { + gossipingRequestListeners.add(listener) } } - fun removeRoomKeysRequestListener(listener: RoomKeysRequestListener) { - synchronized(roomKeysRequestListeners) { - roomKeysRequestListeners.remove(listener) + fun removeRoomKeysRequestListener(listener: GossipingRequestListener) { + synchronized(gossipingRequestListeners) { + gossipingRequestListeners.remove(listener) } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingSecretShareRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingSecretShareRequest.kt new file mode 100755 index 0000000000..6deb5b4692 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingSecretShareRequest.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.matrix.android.internal.crypto + +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.internal.crypto.model.rest.SecretShareRequest + +/** + * IncomingRoomKeyRequest class defines the incoming room keys request. + */ +data class IncomingSecretShareRequest( + /** + * The user id + */ + override val userId: String? = null, + + /** + * The device id + */ + override val deviceId: String? = null, + + /** + * The request id + */ + override val requestId: String? = null, + + /** + * The request body + */ + val secretName: String? = null, + + /** + * The runnable to call to accept to share the keys + */ + @Transient + var share: ((String) -> Unit)? = null, + + /** + * The runnable to call to ignore the key share request. + */ + @Transient + var ignore: Runnable? = null + +) : IncomingShareRequestCommon { + companion object { + /** + * Factory + * + * @param event the event + */ + fun fromEvent(event: Event): IncomingSecretShareRequest? { + return event.getClearContent() + .toModel() + ?.let { + IncomingSecretShareRequest( + userId = event.senderId, + deviceId = it.requestingDeviceId, + requestId = it.requestId, + secretName = it.secretName + ) + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestCommon.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingShareRequestCommon.kt similarity index 95% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestCommon.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingShareRequestCommon.kt index a7b1c6b117..8999f835c8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestCommon.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingShareRequestCommon.kt @@ -16,7 +16,7 @@ package im.vector.matrix.android.internal.crypto -interface IncomingRoomKeyRequestCommon { +interface IncomingShareRequestCommon { /** * The user id */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequest.kt index a66b0242ec..2b9f8003ee 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequest.kt @@ -25,14 +25,14 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody class OutgoingRoomKeyRequest( // RequestBody var requestBody: RoomKeyRequestBody?, // list of recipients for the request - var recipients: List>, // Unique id for this request. Used for both + override var recipients: List>, // Unique id for this request. Used for both // an id within the request for later pairing with a cancellation, and for // the transaction id when sending the to_device messages to our local - var requestId: String, // current state of this request - var state: RequestState) { + override var requestId: String, // current state of this request + override var state: ShareRequestState) : OutgoingShareRequest { // transaction id for the cancellation, if any - var cancellationTxnId: String? = null + override var cancellationTxnId: String? = null /** * Used only for log. @@ -53,66 +53,4 @@ class OutgoingRoomKeyRequest( get() = if (null != requestBody) { requestBody!!.sessionId } else null - - /** - * possible states for a room key request - * - * - * The state machine looks like: - *
-     *
-     *      |
-     *      V
-     *    UNSENT  -----------------------------+
-     *      |                                  |
-     *      | (send successful)                | (cancellation requested)
-     *      V                                  |
-     *     SENT                                |
-     *      |--------------------------------  |  --------------+
-     *      |                                  |                |
-     *      |                                  |                | (cancellation requested with intent
-     *      |                                  |                | to resend a new request)
-     *      | (cancellation requested)         |                |
-     *      V                                  |                V
-     *  CANCELLATION_PENDING                   | CANCELLATION_PENDING_AND_WILL_RESEND
-     *      |                                  |                |
-     *      | (cancellation sent)              |                | (cancellation sent. Create new request
-     *      |                                  |                |  in the UNSENT state)
-     *      V                                  |                |
-     *  (deleted)  <---------------------------+----------------+
-     *  
- */ - - enum class RequestState { - /** - * request not yet sent - */ - UNSENT, - /** - * request sent, awaiting reply - */ - SENT, - /** - * reply received, cancellation not yet sent - */ - CANCELLATION_PENDING, - /** - * Cancellation not yet sent, once sent, a new request will be done - */ - CANCELLATION_PENDING_AND_WILL_RESEND, - /** - * sending failed - */ - FAILED; - - companion object { - fun from(state: Int) = when (state) { - 0 -> UNSENT - 1 -> SENT - 2 -> CANCELLATION_PENDING - 3 -> CANCELLATION_PENDING_AND_WILL_RESEND - else /*4*/ -> FAILED - } - } - } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequestManager.kt index b59c93ba83..5102afef7c 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequestManager.kt @@ -21,7 +21,7 @@ import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody -import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareCancellation +import im.vector.matrix.android.internal.crypto.model.rest.ShareRequestCancellation import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareRequest import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask @@ -89,9 +89,9 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor( */ fun sendRoomKeyRequest(requestBody: RoomKeyRequestBody?, recipients: List>) { val req = cryptoStore.getOrAddOutgoingRoomKeyRequest( - OutgoingRoomKeyRequest(requestBody, recipients, makeTxnId(), OutgoingRoomKeyRequest.RequestState.UNSENT)) + OutgoingRoomKeyRequest(requestBody, recipients, makeTxnId(), ShareRequestState.UNSENT)) - if (req?.state == OutgoingRoomKeyRequest.RequestState.UNSENT) { + if (req?.state == ShareRequestState.UNSENT) { startTimer() } } @@ -132,20 +132,20 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor( Timber.v("cancelRoomKeyRequest: requestId: " + req.requestId + " state: " + req.state + " andResend: " + andResend) when (req.state) { - OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING, - OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> { + ShareRequestState.CANCELLATION_PENDING, + ShareRequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> { // nothing to do here } - OutgoingRoomKeyRequest.RequestState.UNSENT, - OutgoingRoomKeyRequest.RequestState.FAILED -> { + ShareRequestState.UNSENT, + ShareRequestState.FAILED -> { Timber.v("## cancelRoomKeyRequest() : deleting unnecessary room key request for $requestBody") cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId) } - OutgoingRoomKeyRequest.RequestState.SENT -> { + ShareRequestState.SENT -> { if (andResend) { - req.state = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND + req.state = ShareRequestState.CANCELLATION_PENDING_AND_WILL_RESEND } else { - req.state = OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING + req.state = ShareRequestState.CANCELLATION_PENDING } req.cancellationTxnId = makeTxnId() cryptoStore.updateOutgoingRoomKeyRequest(req) @@ -187,9 +187,9 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor( Timber.v("## sendOutgoingRoomKeyRequests() : Looking for queued outgoing room key requests") val outgoingRoomKeyRequest = cryptoStore.getOutgoingRoomKeyRequestByState( - setOf(OutgoingRoomKeyRequest.RequestState.UNSENT, - OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING, - OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND)) + setOf(ShareRequestState.UNSENT, + ShareRequestState.CANCELLATION_PENDING, + ShareRequestState.CANCELLATION_PENDING_AND_WILL_RESEND)) if (null == outgoingRoomKeyRequest) { Timber.v("## sendOutgoingRoomKeyRequests() : No more outgoing room key requests") @@ -197,7 +197,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor( return } - if (OutgoingRoomKeyRequest.RequestState.UNSENT === outgoingRoomKeyRequest.state) { + if (ShareRequestState.UNSENT === outgoingRoomKeyRequest.state) { sendOutgoingRoomKeyRequest(outgoingRoomKeyRequest) } else { sendOutgoingRoomKeyRequestCancellation(outgoingRoomKeyRequest) @@ -220,8 +220,8 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor( ) sendMessageToDevices(requestMessage, request.recipients, request.requestId, object : MatrixCallback { - private fun onDone(state: OutgoingRoomKeyRequest.RequestState) { - if (request.state !== OutgoingRoomKeyRequest.RequestState.UNSENT) { + private fun onDone(state: ShareRequestState) { + if (request.state !== ShareRequestState.UNSENT) { Timber.v("## sendOutgoingRoomKeyRequest() : Cannot update room key request from UNSENT as it was already updated to ${request.state}") } else { request.state = state @@ -234,12 +234,12 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor( override fun onSuccess(data: Unit) { Timber.v("## sendOutgoingRoomKeyRequest succeed") - onDone(OutgoingRoomKeyRequest.RequestState.SENT) + onDone(ShareRequestState.SENT) } override fun onFailure(failure: Throwable) { Timber.e("## sendOutgoingRoomKeyRequest failed") - onDone(OutgoingRoomKeyRequest.RequestState.FAILED) + onDone(ShareRequestState.FAILED) } }) } @@ -254,7 +254,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor( + " to " + request.recipients + " cancellation id " + request.cancellationTxnId) - val roomKeyShareCancellation = RoomKeyShareCancellation( + val roomKeyShareCancellation = ShareRequestCancellation( requestingDeviceId = cryptoStore.getDeviceId(), requestId = request.cancellationTxnId ) @@ -268,7 +268,7 @@ internal class OutgoingRoomKeyRequestManager @Inject constructor( override fun onSuccess(data: Unit) { Timber.v("## sendOutgoingRoomKeyRequestCancellation() : done") - val resend = request.state === OutgoingRoomKeyRequest.RequestState.CANCELLATION_PENDING_AND_WILL_RESEND + val resend = request.state === ShareRequestState.CANCELLATION_PENDING_AND_WILL_RESEND onDone() diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingSecretRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingSecretRequest.kt new file mode 100755 index 0000000000..a5305c1a25 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingSecretRequest.kt @@ -0,0 +1,36 @@ +/* + * 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 + +/** + * Represents an outgoing room key request + */ +class OutgoingSecretRequest( + // Secret Name + var secretName: String?, + // list of recipients for the request + override var recipients: List>, + // Unique id for this request. Used for both + // an id within the request for later pairing with a cancellation, and for + // the transaction id when sending the to_device messages to our local + override var requestId: String, + // current state of this request + override var state: ShareRequestState) : OutgoingShareRequest { + + // transaction id for the cancellation, if any + override var cancellationTxnId: String? = null +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingShareRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingShareRequest.kt new file mode 100644 index 0000000000..b56ba22da6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingShareRequest.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.crypto + +interface OutgoingShareRequest { + var recipients: List> + var requestId: String + var state: ShareRequestState + // transaction id for the cancellation, if any + var cancellationTxnId: String? +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareRequestState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareRequestState.kt new file mode 100644 index 0000000000..7d3ff72e9b --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareRequestState.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.matrix.android.internal.crypto + +/** + * possible states for a room key request + * + * + * The state machine looks like: + *
+ *
+ *      |
+ *      V
+ *    UNSENT  -----------------------------+
+ *      |                                  |
+ *      | (send successful)                | (cancellation requested)
+ *      V                                  |
+ *     SENT                                |
+ *      |--------------------------------  |  --------------+
+ *      |                                  |                |
+ *      |                                  |                | (cancellation requested with intent
+ *      |                                  |                | to resend a new request)
+ *      | (cancellation requested)         |                |
+ *      V                                  |                V
+ *  CANCELLATION_PENDING                   | CANCELLATION_PENDING_AND_WILL_RESEND
+ *      |                                  |                |
+ *      | (cancellation sent)              |                | (cancellation sent. Create new request
+ *      |                                  |                |  in the UNSENT state)
+ *      V                                  |                |
+ *  (deleted)  <---------------------------+----------------+
+ *  
+ */ + +enum class ShareRequestState { + /** + * request not yet sent + */ + UNSENT, + /** + * request sent, awaiting reply + */ + SENT, + /** + * reply received, cancellation not yet sent + */ + CANCELLATION_PENDING, + /** + * Cancellation not yet sent, once sent, a new request will be done + */ + CANCELLATION_PENDING_AND_WILL_RESEND, + /** + * sending failed + */ + FAILED; + + companion object { + fun from(state: Int) = when (state) { + 0 -> UNSENT + 1 -> SENT + 2 -> CANCELLATION_PENDING + 3 -> CANCELLATION_PENDING_AND_WILL_RESEND + else /*4*/ -> FAILED + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt new file mode 100644 index 0000000000..e40caf4eed --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt @@ -0,0 +1,64 @@ +/* + * 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 + +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter +import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap +import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent +import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore +import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask +import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +internal class ShareSecretCryptoProvider @Inject constructor( + val messageEncrypter: MessageEncrypter, + val sendToDeviceTask: SendToDeviceTask, + val deviceListManager: DeviceListManager, + val cryptoCoroutineScope: CoroutineScope, + val cryptoStore: IMXCryptoStore, + val coroutineDispatchers: MatrixCoroutineDispatchers +) { + fun shareSecretWithDevice(request: IncomingSecretShareRequest, secretValue: String) { + val userId = request.userId ?: return + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + runCatching { deviceListManager.downloadKeys(listOf(userId), false) } + .mapCatching { + val deviceId = request.deviceId + val deviceInfo = cryptoStore.getUserDevice(userId, deviceId ?: "") ?: throw RuntimeException() + + Timber.i("## shareSecretWithDevice() : sharing secret ${request.secretName} with device $userId:$deviceId") + + val payloadJson = mutableMapOf("type" to EventType.SEND_SECRET) + payloadJson["content"] = SecretSendEventContent( + requestId = request.requestId ?: "", + secretValue = secretValue + ) + + val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) + val sendToDeviceMap = MXUsersDevicesMap() + sendToDeviceMap.setObject(userId, deviceId, encodedPayload) + Timber.i("## shareSecretWithDevice() : sending to $userId:$deviceId") + val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) + sendToDeviceTask.execute(sendToDeviceParams) + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXDecrypting.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXDecrypting.kt index 8a66029026..ff740fb1f9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXDecrypting.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXDecrypting.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.crypto.algorithms import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest +import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.keysbackup.DefaultKeysBackupService @@ -65,4 +66,6 @@ internal interface IMXDecrypting { * @param request keyRequest */ fun shareKeysWithDevice(request: IncomingRoomKeyRequest) {} + + fun shareSecretWithDevice(request: IncomingSecretShareRequest, secretValue : String) {} } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/SecretSendEventContent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/SecretSendEventContent.kt new file mode 100644 index 0000000000..4a856b32a1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/event/SecretSendEventContent.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package im.vector.matrix.android.internal.crypto.model.event + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Class representing an encrypted event content + */ +@JsonClass(generateAdapter = true) +data class SecretSendEventContent( + @Json(name = "request_id") val requestId: String, + @Json(name = "secret") val secretValue: String +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShare.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/GossipingToDeviceObject.kt similarity index 94% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShare.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/GossipingToDeviceObject.kt index 4ea95d84ae..e25f0a8e14 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShare.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/GossipingToDeviceObject.kt @@ -19,7 +19,7 @@ package im.vector.matrix.android.internal.crypto.model.rest * Interface representing an room key action request * Note: this class cannot be abstract because of [org.matrix.androidsdk.core.JsonUtils.toRoomKeyShare] */ -internal interface RoomKeyShare : SendToDeviceObject { +internal interface GossipingToDeviceObject : SendToDeviceObject { val action: String? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareRequest.kt index d92bc03aab..e61743251c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareRequest.kt @@ -25,7 +25,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) internal data class RoomKeyShareRequest( @Json(name = "action") - override val action: String? = RoomKeyShare.ACTION_SHARE_REQUEST, + override val action: String? = GossipingToDeviceObject.ACTION_SHARE_REQUEST, @Json(name = "requesting_device_id") override val requestingDeviceId: String? = null, @@ -35,4 +35,4 @@ internal data class RoomKeyShareRequest( @Json(name = "body") val body: RoomKeyRequestBody? = null -) : RoomKeyShare +) : GossipingToDeviceObject diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SecretShareRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SecretShareRequest.kt new file mode 100644 index 0000000000..093387926e --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SecretShareRequest.kt @@ -0,0 +1,37 @@ +/* + * 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.model.rest + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * Class representing a room key request content + */ +@JsonClass(generateAdapter = true) +internal data class SecretShareRequest( + @Json(name = "action") + override val action: String? = GossipingToDeviceObject.ACTION_SHARE_REQUEST, + + @Json(name = "requesting_device_id") + override val requestingDeviceId: String? = null, + + @Json(name = "request_id") + override val requestId: String? = null, + + @Json(name = "name") + val secretName: String? = null +) : GossipingToDeviceObject diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareCancellation.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/ShareRequestCancellation.kt similarity index 80% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareCancellation.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/ShareRequestCancellation.kt index b394993338..10be81be7d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareCancellation.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/ShareRequestCancellation.kt @@ -17,18 +17,19 @@ package im.vector.matrix.android.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.crypto.model.rest.GossipingToDeviceObject.Companion.ACTION_SHARE_CANCELLATION /** * Class representing a room key request cancellation content */ @JsonClass(generateAdapter = true) -internal data class RoomKeyShareCancellation( +internal data class ShareRequestCancellation( @Json(name = "action") - override val action: String? = RoomKeyShare.ACTION_SHARE_CANCELLATION, + override val action: String? = ACTION_SHARE_CANCELLATION, @Json(name = "requesting_device_id") override val requestingDeviceId: String? = null, @Json(name = "request_id") override val requestId: String? = null -) : RoomKeyShare +) : GossipingToDeviceObject diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt index 29ec0d5f91..b05b6cfa59 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt @@ -20,10 +20,12 @@ package im.vector.matrix.android.internal.crypto.store import androidx.lifecycle.LiveData import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.api.util.Optional +import im.vector.matrix.android.internal.crypto.IncomingShareRequestCommon import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest -import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCommon +import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest +import im.vector.matrix.android.internal.crypto.ShareRequestState import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper @@ -116,6 +118,7 @@ internal interface IMXCryptoStore { * @return the pending IncomingRoomKeyRequest requests */ fun getPendingIncomingRoomKeyRequests(): List + fun getPendingIncomingSecretShareRequests(): List /** * Indicate if the store contains data for the passed account. @@ -355,7 +358,7 @@ internal interface IMXCryptoStore { * @param states the states * @return an OutgoingRoomKeyRequest or null */ - fun getOutgoingRoomKeyRequestByState(states: Set): OutgoingRoomKeyRequest? + fun getOutgoingRoomKeyRequestByState(states: Set): OutgoingRoomKeyRequest? /** * Update an existing outgoing request. @@ -383,7 +386,9 @@ internal interface IMXCryptoStore { * * @param incomingRoomKeyRequest the incoming key request */ - fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequestCommon) + fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingShareRequestCommon) + + fun deleteIncomingSecretRequest(request: IncomingSecretShareRequest) /** * Search an IncomingRoomKeyRequest diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index 58561a079c..1b4410d48c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -24,9 +24,11 @@ import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningIn import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.toOptional import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest -import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCommon +import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest +import im.vector.matrix.android.internal.crypto.IncomingShareRequestCommon import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest +import im.vector.matrix.android.internal.crypto.ShareRequestState import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo @@ -46,6 +48,8 @@ import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntity import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.IncomingRoomKeyRequestEntity import im.vector.matrix.android.internal.crypto.store.db.model.IncomingRoomKeyRequestEntityFields +import im.vector.matrix.android.internal.crypto.store.db.model.IncomingSecretRequestEntity +import im.vector.matrix.android.internal.crypto.store.db.model.IncomingSecretRequestEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntity import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity import im.vector.matrix.android.internal.crypto.store.db.model.OlmInboundGroupSessionEntity @@ -832,7 +836,7 @@ internal class RealmCryptoStore @Inject constructor( return request } - override fun getOutgoingRoomKeyRequestByState(states: Set): OutgoingRoomKeyRequest? { + override fun getOutgoingRoomKeyRequestByState(states: Set): OutgoingRoomKeyRequest? { val statesIndex = states.map { it.ordinal }.toTypedArray() return doRealmQueryAndCopy(realmConfiguration) { it.where() @@ -889,7 +893,7 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequestCommon) { + override fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingShareRequestCommon) { doRealmTransaction(realmConfiguration) { it.where() .equalTo(IncomingRoomKeyRequestEntityFields.USER_ID, incomingRoomKeyRequest.userId) @@ -900,6 +904,18 @@ internal class RealmCryptoStore @Inject constructor( } } + override fun deleteIncomingSecretRequest(request: IncomingSecretShareRequest) { + doRealmTransaction(realmConfiguration) { + it.where() + .equalTo(IncomingSecretRequestEntityFields.USER_ID, request.userId) + .equalTo(IncomingSecretRequestEntityFields.DEVICE_ID, request.deviceId) + .equalTo(IncomingSecretRequestEntityFields.REQUEST_ID, request.requestId) + .equalTo(IncomingSecretRequestEntityFields.SECRET_NAME, request.secretName) + .findAll() + .deleteAllFromRealm() + } + } + override fun getIncomingRoomKeyRequest(userId: String, deviceId: String, requestId: String): IncomingRoomKeyRequest? { return doRealmQueryAndCopy(realmConfiguration) { it.where() @@ -922,6 +938,15 @@ internal class RealmCryptoStore @Inject constructor( .toMutableList() } + override fun getPendingIncomingSecretShareRequests(): List { + return doRealmQueryAndCopyList(realmConfiguration) { + it.where() + .findAll() + }.map { + it.toIncomingSecretShareRequest() + } + } + /* ========================================================================================== * Cross Signing * ========================================================================================== */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt index 6839f6995b..6d10e9d8aa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -23,7 +23,9 @@ import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.store.db.model.CrossSigningInfoEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMetadataEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityFields +import im.vector.matrix.android.internal.crypto.store.db.model.IncomingSecretRequestEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntityFields +import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingSecretRequestEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.TrustLevelEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields import im.vector.matrix.android.internal.di.SerializeNulls @@ -34,102 +36,125 @@ import timber.log.Timber internal object RealmCryptoStoreMigration : RealmMigration { // Version 1L added Cross Signing info persistence - const val CRYPTO_STORE_SCHEMA_VERSION = 1L + const val CRYPTO_STORE_SCHEMA_VERSION = 2L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Crypto from $oldVersion to $newVersion") - if (oldVersion <= 0) { - Timber.d("Step 0 -> 1") - Timber.d("Create KeyInfoEntity") + if (oldVersion <= 0) migrateTo1(realm) + if (oldVersion <= 1) migrateTo2(realm) + } - val trustLevelentityEntitySchema = realm.schema.create("TrustLevelEntity") - .addField(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, Boolean::class.java) - .setNullable(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, true) - .addField(TrustLevelEntityFields.LOCALLY_VERIFIED, Boolean::class.java) - .setNullable(TrustLevelEntityFields.LOCALLY_VERIFIED, true) + private fun migrateTo1(realm: DynamicRealm) { + Timber.d("Step 0 -> 1") + Timber.d("Create KeyInfoEntity") - val keyInfoEntitySchema = realm.schema.create("KeyInfoEntity") - .addField(KeyInfoEntityFields.PUBLIC_KEY_BASE64, String::class.java) - .addField(KeyInfoEntityFields.SIGNATURES, String::class.java) - .addRealmListField(KeyInfoEntityFields.USAGES.`$`, String::class.java) - .addRealmObjectField(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelentityEntitySchema) + val trustLevelentityEntitySchema = realm.schema.create("TrustLevelEntity") + .addField(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, Boolean::class.java) + .setNullable(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, true) + .addField(TrustLevelEntityFields.LOCALLY_VERIFIED, Boolean::class.java) + .setNullable(TrustLevelEntityFields.LOCALLY_VERIFIED, true) - Timber.d("Create CrossSigningInfoEntity") + val keyInfoEntitySchema = realm.schema.create("KeyInfoEntity") + .addField(KeyInfoEntityFields.PUBLIC_KEY_BASE64, String::class.java) + .addField(KeyInfoEntityFields.SIGNATURES, String::class.java) + .addRealmListField(KeyInfoEntityFields.USAGES.`$`, String::class.java) + .addRealmObjectField(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelentityEntitySchema) - val crossSigningInfoSchema = realm.schema.create("CrossSigningInfoEntity") - .addField(CrossSigningInfoEntityFields.USER_ID, String::class.java) - .addPrimaryKey(CrossSigningInfoEntityFields.USER_ID) - .addRealmListField(CrossSigningInfoEntityFields.CROSS_SIGNING_KEYS.`$`, keyInfoEntitySchema) + Timber.d("Create CrossSigningInfoEntity") - Timber.d("Updating UserEntity table") - realm.schema.get("UserEntity") - ?.addRealmObjectField(UserEntityFields.CROSS_SIGNING_INFO_ENTITY.`$`, crossSigningInfoSchema) + val crossSigningInfoSchema = realm.schema.create("CrossSigningInfoEntity") + .addField(CrossSigningInfoEntityFields.USER_ID, String::class.java) + .addPrimaryKey(CrossSigningInfoEntityFields.USER_ID) + .addRealmListField(CrossSigningInfoEntityFields.CROSS_SIGNING_KEYS.`$`, keyInfoEntitySchema) - Timber.d("Updating CryptoMetadataEntity table") - realm.schema.get("CryptoMetadataEntity") - ?.addField(CryptoMetadataEntityFields.X_SIGN_MASTER_PRIVATE_KEY, String::class.java) - ?.addField(CryptoMetadataEntityFields.X_SIGN_USER_PRIVATE_KEY, String::class.java) - ?.addField(CryptoMetadataEntityFields.X_SIGN_SELF_SIGNED_PRIVATE_KEY, String::class.java) + Timber.d("Updating UserEntity table") + realm.schema.get("UserEntity") + ?.addRealmObjectField(UserEntityFields.CROSS_SIGNING_INFO_ENTITY.`$`, crossSigningInfoSchema) - val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build() - val listMigrationAdapter = moshi.adapter>(Types.newParameterizedType( - List::class.java, - String::class.java, - Any::class.java - )) - val mapMigrationAdapter = moshi.adapter(Types.newParameterizedType( - Map::class.java, - String::class.java, - Any::class.java - )) + Timber.d("Updating CryptoMetadataEntity table") + realm.schema.get("CryptoMetadataEntity") + ?.addField(CryptoMetadataEntityFields.X_SIGN_MASTER_PRIVATE_KEY, String::class.java) + ?.addField(CryptoMetadataEntityFields.X_SIGN_USER_PRIVATE_KEY, String::class.java) + ?.addField(CryptoMetadataEntityFields.X_SIGN_SELF_SIGNED_PRIVATE_KEY, String::class.java) - realm.schema.get("DeviceInfoEntity") - ?.addField(DeviceInfoEntityFields.USER_ID, String::class.java) - ?.addField(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, String::class.java) - ?.addField(DeviceInfoEntityFields.KEYS_MAP_JSON, String::class.java) - ?.addField(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, String::class.java) - ?.addField(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, String::class.java) - ?.addField(DeviceInfoEntityFields.IS_BLOCKED, Boolean::class.java) - ?.setNullable(DeviceInfoEntityFields.IS_BLOCKED, true) - ?.addRealmObjectField(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelentityEntitySchema) - ?.transform { obj -> + val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build() + val listMigrationAdapter = moshi.adapter>(Types.newParameterizedType( + List::class.java, + String::class.java, + Any::class.java + )) + val mapMigrationAdapter = moshi.adapter(Types.newParameterizedType( + Map::class.java, + String::class.java, + Any::class.java + )) - val oldSerializedData = obj.getString("deviceInfoData") - deserializeFromRealm(oldSerializedData)?.let { oldDevice -> + realm.schema.get("DeviceInfoEntity") + ?.addField(DeviceInfoEntityFields.USER_ID, String::class.java) + ?.addField(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, String::class.java) + ?.addField(DeviceInfoEntityFields.KEYS_MAP_JSON, String::class.java) + ?.addField(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, String::class.java) + ?.addField(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, String::class.java) + ?.addField(DeviceInfoEntityFields.IS_BLOCKED, Boolean::class.java) + ?.setNullable(DeviceInfoEntityFields.IS_BLOCKED, true) + ?.addRealmObjectField(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelentityEntitySchema) + ?.transform { obj -> - val trustLevel = realm.createObject("TrustLevelEntity") - when (oldDevice.verified) { - MXDeviceInfo.DEVICE_VERIFICATION_UNKNOWN -> { - obj.setNull(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`) - } - MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED -> { - trustLevel.setNull(TrustLevelEntityFields.LOCALLY_VERIFIED) - trustLevel.setNull(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED) - obj.setBoolean(DeviceInfoEntityFields.IS_BLOCKED, oldDevice.isBlocked) - obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel) - } - MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED -> { - trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, false) - trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false) - obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel) - } - MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED -> { - trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, true) - trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false) - obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel) - } + val oldSerializedData = obj.getString("deviceInfoData") + deserializeFromRealm(oldSerializedData)?.let { oldDevice -> + + val trustLevel = realm.createObject("TrustLevelEntity") + when (oldDevice.verified) { + MXDeviceInfo.DEVICE_VERIFICATION_UNKNOWN -> { + obj.setNull(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`) + } + MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED -> { + trustLevel.setNull(TrustLevelEntityFields.LOCALLY_VERIFIED) + trustLevel.setNull(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED) + obj.setBoolean(DeviceInfoEntityFields.IS_BLOCKED, oldDevice.isBlocked) + obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel) + } + MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED -> { + trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, false) + trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false) + obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel) + } + MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED -> { + trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, true) + trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false) + obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel) } - - obj.setString(DeviceInfoEntityFields.USER_ID, oldDevice.userId) - obj.setString(DeviceInfoEntityFields.IDENTITY_KEY, oldDevice.identityKey()) - obj.setString(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, listMigrationAdapter.toJson(oldDevice.algorithms)) - obj.setString(DeviceInfoEntityFields.KEYS_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.keys)) - obj.setString(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.signatures)) - obj.setString(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.unsigned)) } + + obj.setString(DeviceInfoEntityFields.USER_ID, oldDevice.userId) + obj.setString(DeviceInfoEntityFields.IDENTITY_KEY, oldDevice.identityKey()) + obj.setString(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, listMigrationAdapter.toJson(oldDevice.algorithms)) + obj.setString(DeviceInfoEntityFields.KEYS_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.keys)) + obj.setString(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.signatures)) + obj.setString(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.unsigned)) } - ?.removeField("deviceInfoData") - } + } + ?.removeField("deviceInfoData") + } + + private fun migrateTo2(realm: DynamicRealm) { + Timber.d("Step 1 -> 2") + + realm.schema.create("IncomingSecretRequestEntity") + .addField(IncomingSecretRequestEntityFields.DEVICE_ID, String::class.java) + .addField(IncomingSecretRequestEntityFields.SECRET_NAME, String::class.java) + .addField(IncomingSecretRequestEntityFields.REQUEST_ID, String::class.java) + .addField(IncomingSecretRequestEntityFields.USER_ID, String::class.java) + + + realm.schema.create("OutgoingSecretRequestEntity") + .addField(OutgoingSecretRequestEntityFields.REQUEST_ID, String::class.java) + .addPrimaryKey(OutgoingSecretRequestEntityFields.REQUEST_ID) + .addField(OutgoingSecretRequestEntityFields.SECRET_NAME, String::class.java) + .addField(OutgoingSecretRequestEntityFields.CANCELLATION_TXN_ID, String::class.java) + .addField(OutgoingSecretRequestEntityFields.RECIPIENTS_DATA, String::class.java) + .addField(OutgoingSecretRequestEntityFields.STATE, Int::class.java) + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreModule.kt index 1053cc5f43..d9d4496d4e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreModule.kt @@ -21,11 +21,13 @@ import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMetadataEnt import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntity import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntity import im.vector.matrix.android.internal.crypto.store.db.model.IncomingRoomKeyRequestEntity +import im.vector.matrix.android.internal.crypto.store.db.model.IncomingSecretRequestEntity import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntity import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity import im.vector.matrix.android.internal.crypto.store.db.model.OlmInboundGroupSessionEntity import im.vector.matrix.android.internal.crypto.store.db.model.OlmSessionEntity import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingRoomKeyRequestEntity +import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingSecretRequestEntity import im.vector.matrix.android.internal.crypto.store.db.model.TrustLevelEntity import im.vector.matrix.android.internal.crypto.store.db.model.UserEntity import io.realm.annotations.RealmModule @@ -46,6 +48,8 @@ import io.realm.annotations.RealmModule UserEntity::class, KeyInfoEntity::class, CrossSigningInfoEntity::class, - TrustLevelEntity::class + TrustLevelEntity::class, + IncomingSecretRequestEntity::class, + OutgoingSecretRequestEntity::class ]) internal class RealmCryptoStoreModule diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingSecretRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingSecretRequestEntity.kt new file mode 100644 index 0000000000..e1eb274e3d --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingSecretRequestEntity.kt @@ -0,0 +1,37 @@ +/* + * 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.store.db.model + +import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest +import io.realm.RealmObject + +internal open class IncomingSecretRequestEntity( + var requestId: String? = null, + var userId: String? = null, + var deviceId: String? = null, + var secretName: String? = null +) : RealmObject() { + + fun toIncomingSecretShareRequest(): IncomingSecretShareRequest { + return IncomingSecretShareRequest( + requestId = requestId, + userId = userId, + deviceId = deviceId, + secretName = secretName + ) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingRoomKeyRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingRoomKeyRequestEntity.kt index 86fc177f2b..7b4b515c85 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingRoomKeyRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingRoomKeyRequestEntity.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.crypto.store.db.model import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest +import im.vector.matrix.android.internal.crypto.ShareRequestState import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm @@ -51,7 +52,7 @@ internal open class OutgoingRoomKeyRequestEntity( ), getRecipients()!!, requestId!!, - OutgoingRoomKeyRequest.RequestState.from(state) + ShareRequestState.from(state) ).apply { this.cancellationTxnId = cancellationTxnId } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingSecretRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingSecretRequestEntity.kt new file mode 100644 index 0000000000..e6ce91fc10 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingSecretRequestEntity.kt @@ -0,0 +1,63 @@ +/* + * 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.store.db.model + +import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest +import im.vector.matrix.android.internal.crypto.ShareRequestState +import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm +import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey + +internal open class OutgoingSecretRequestEntity( + @PrimaryKey var requestId: String? = null, + var cancellationTxnId: String? = null, + // Serialized Json + var recipientsData: String? = null, + // RoomKeyRequestBody fields + var secretName: String? = null, + // State + var state: Int = 0 +) : RealmObject() { + + /** + * Convert to OutgoingRoomKeyRequest + */ + fun toOutgoingSecretRequest(): OutgoingSecretRequest { + val cancellationTxnId = this.cancellationTxnId + return OutgoingSecretRequest( + secretName, + getRecipients() ?: emptyList(), + requestId!!, + ShareRequestState.from(state) + ).apply { + this.cancellationTxnId = cancellationTxnId + } + } + + private fun getRecipients(): List>? { + return try { + deserializeFromRealm(recipientsData) + } catch (failure: Throwable) { + null + } + } + + fun putRecipients(recipients: List>?) { + recipientsData = serializeForRealm(recipients) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt index 534de09ce0..d9860e6bad 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt @@ -22,13 +22,14 @@ package im.vector.riotx.features.crypto.keysrequest import android.content.Context import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener +import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationService import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest -import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequestCancellation +import im.vector.matrix.android.internal.crypto.IncomingRequestCancellation +import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap @@ -54,7 +55,7 @@ import javax.inject.Singleton @Singleton class KeyRequestHandler @Inject constructor(private val context: Context) - : RoomKeysRequestListener, + : GossipingRequestListener, VerificationService.Listener { private val alertsToRequests = HashMap>() @@ -73,6 +74,13 @@ class KeyRequestHandler @Inject constructor(private val context: Context) session = null } + override fun onSecretShareRequest(request: IncomingSecretShareRequest) : Boolean { + // By default riotX will not prompt if the SDK has decided that the request should not be fulfilled + Timber.v("## onSecretShareRequest() : Ignoring $request") + request.ignore?.run() + return true + } + /** * Handle incoming key request. * @@ -194,20 +202,6 @@ class KeyRequestHandler @Inject constructor(private val context: Context) denyAllRequests(mappingKey) } - // TODO send to the new profile page -// alert.addButton( -// context.getString(R.string.start_verification_short_label), -// Runnable { -// alert.weakCurrentActivity?.get()?.let { -// val intent = SASVerificationActivity.outgoingIntent(it, -// session?.myUserId ?: "", -// userId, deviceId) -// it.startActivity(intent) -// } -// }, -// false -// ) - alert.addButton(context.getString(R.string.share_without_verifying_short_label), Runnable { shareAllSessions(mappingKey) }) @@ -238,7 +232,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context) * * @param request the cancellation request. */ - override fun onRoomKeyRequestCancellation(request: IncomingRoomKeyRequestCancellation) { + override fun onRoomKeyRequestCancellation(request: IncomingRequestCancellation) { // see if we can find the request in the queue val userId = request.userId val deviceId = request.deviceId diff --git a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt index 0f1d5f466e..94d3860cca 100644 --- a/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/riotx/features/notifications/NotificationDrawerManager.kt @@ -187,7 +187,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context fun refreshNotificationDrawer() { // Implement last throttler - Timber.w("refreshNotificationDrawer()") + Timber.v("refreshNotificationDrawer()") backgroundHandler.removeCallbacksAndMessages(null) backgroundHandler.postDelayed( { @@ -197,7 +197,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context @WorkerThread private fun refreshNotificationDrawerBg() { - Timber.w("refreshNotificationDrawerBg()") + Timber.v("refreshNotificationDrawerBg()") val session = activeSessionHolder.getSafeActiveSession() ?: return From d5137897c1aa8cb189fc7c8c3c2529c19c25019a Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 26 Feb 2020 18:58:44 +0100 Subject: [PATCH 017/156] Fix / crash No JsonAdapter for GossipingToDeviceObject --- .../internal/crypto/IncomingRoomKeyRequestManager.kt | 3 ++- .../crypto/model/rest/GossipingToDeviceObject.kt | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt index 2e4b37189d..98c73d75f5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestList import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.internal.crypto.model.rest.GossipingDefaultContent import im.vector.matrix.android.internal.crypto.model.rest.GossipingToDeviceObject import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.session.SessionScope @@ -58,7 +59,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( * @param event the announcement event. */ fun onGossipingRequestEvent(event: Event) { - val roomKeyShare = event.getClearContent().toModel() + val roomKeyShare = event.getClearContent().toModel() when (roomKeyShare?.action) { GossipingToDeviceObject.ACTION_SHARE_REQUEST -> { if (event.getClearType() == EventType.REQUEST_SECRET) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/GossipingToDeviceObject.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/GossipingToDeviceObject.kt index e25f0a8e14..def8c7f620 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/GossipingToDeviceObject.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/GossipingToDeviceObject.kt @@ -15,6 +15,9 @@ */ package im.vector.matrix.android.internal.crypto.model.rest +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + /** * Interface representing an room key action request * Note: this class cannot be abstract because of [org.matrix.androidsdk.core.JsonUtils.toRoomKeyShare] @@ -32,3 +35,10 @@ internal interface GossipingToDeviceObject : SendToDeviceObject { const val ACTION_SHARE_CANCELLATION = "request_cancellation" } } + +@JsonClass(generateAdapter = true) +data class GossipingDefaultContent( + @Json(name = "action") override val action: String?, + @Json(name = "requesting_device_id") override val requestingDeviceId: String?, + @Json(name = "m.request_id") override val requestId: String? = null +) : GossipingToDeviceObject From 3639007985eadebc32ccf06d1e3ea47132230df9 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 28 Feb 2020 18:23:49 +0100 Subject: [PATCH 018/156] Cancel transaction if failed to decrypt other part events --- .../verification/VerificationService.kt | 3 +++ .../tasks/RoomVerificationUpdateTask.kt | 1 + .../DefaultVerificationService.kt | 23 +++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationService.kt index 5cad8d985c..75033082d6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationService.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.api.session.crypto.verification import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.LocalEcho /** @@ -136,4 +137,6 @@ interface VerificationService { return age in tooInThePast..tooInTheFuture } } + + fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt index 51c5015a1d..3fb2573bed 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt @@ -85,6 +85,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor( ) } catch (e: MXCryptoError) { Timber.e("## SAS Failed to decrypt event: ${event.eventId}") + params.verificationService.onPotentiallyInterestingEventRoomFailToDecrypt(event) } } Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt index 3b9d62a5dd..d7000cfc97 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt @@ -35,6 +35,7 @@ import im.vector.matrix.android.api.session.crypto.verification.safeValueOf import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.LocalEcho +import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageRelationContent @@ -54,6 +55,7 @@ import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAct import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap +import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationKey @@ -354,6 +356,27 @@ internal class DefaultVerificationService @Inject constructor( */ } + override fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event) { + // When Should/Can we cancel?? + val relationContent = event.content.toModel()?.relatesTo + if (relationContent?.type == RelationType.REFERENCE) { + val relatedId = relationContent.eventId ?: return + // at least if request was sent by me, I can safely cancel without interfering + pendingRequests[event.senderId]?.firstOrNull { + it.transactionId == relatedId && !it.isIncoming + }?.let { pr -> + verificationTransportRoomMessageFactory.createTransport(event.roomId ?: "", null) + .cancelTransaction( + relatedId, + event.senderId ?: "", + event.getSenderKey() ?: "", + CancelCode.InvalidMessage + ) + updatePendingRequest(pr.copy(cancelConclusion = CancelCode.InvalidMessage)) + } + } + } + private suspend fun onRoomStartRequestReceived(event: Event) { val startReq = event.getClearContent().toModel() ?.copy( From fc6225a7acfeb790f48f23822b725afa6cfa2a65 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 6 Mar 2020 18:29:15 +0100 Subject: [PATCH 019/156] Gossiping refactoring --- matrix-sdk-android/build.gradle | 3 +- .../crypto/gossiping/KeyShareTests.kt | 210 +++++++++ .../crypto/keysbackup/KeysBackupTest.kt | 85 ++-- .../crypto/verification/qrcode/QrCodeTest.kt | 1 + .../android/api/crypto/MXCryptoConfig.kt | 11 +- .../matrix/android/api/extensions/Try.kt | 25 + .../api/session/crypto/CryptoService.kt | 5 + .../crosssigning/CrossSigningService.kt | 3 + .../SharedSecretStorageService.kt | 3 + .../crypto/CancelGossipRequestWorker.kt | 119 +++++ .../internal/crypto/DefaultCryptoService.kt | 147 +++++- .../internal/crypto/GossipingRequestState.kt | 43 ++ .../crypto/IncomingRequestCancellation.kt | 6 +- .../internal/crypto/IncomingRoomKeyRequest.kt | 8 +- .../crypto/IncomingRoomKeyRequestManager.kt | 129 ++++-- .../crypto/IncomingSecretShareRequest.kt | 7 +- .../crypto/IncomingShareRequestCommon.kt | 2 + ...Request.kt => OutgoingGossipingRequest.kt} | 8 +- .../crypto/OutgoingGossipingRequestManager.kt | 185 ++++++++ .../internal/crypto/OutgoingRoomKeyRequest.kt | 17 +- .../crypto/OutgoingRoomKeyRequestManager.kt | 320 ------------- .../internal/crypto/OutgoingSecretRequest.kt | 10 +- .../crypto/SendGossipRequestWorker.kt | 150 ++++++ .../crypto/ShareSecretCryptoProvider.kt | 10 + .../actions/MegolmSessionDataImporter.kt | 6 +- .../crypto/algorithms/IMXDecrypting.kt | 2 + .../algorithms/megolm/MXMegolmDecryption.kt | 41 +- .../megolm/MXMegolmDecryptionFactory.kt | 6 +- .../crypto/algorithms/olm/MXOlmDecryption.kt | 4 + .../DefaultCrossSigningService.kt | 253 ++++++---- .../internal/crypto/model/CryptoDeviceInfo.kt | 2 +- .../model/rest/GossipingToDeviceObject.kt | 2 +- .../crypto/model/rest/RoomKeyRequestBody.kt | 15 +- .../crypto/model/rest/RoomKeyShareRequest.kt | 2 +- .../DefaultSharedSecretStorageService.kt | 11 + .../internal/crypto/store/IMXCryptoStore.kt | 54 ++- .../internal/crypto/store/db/Helper.kt | 7 +- .../crypto/store/db/RealmCryptoStore.kt | 432 +++++++++++++----- .../store/db/RealmCryptoStoreMigration.kt | 46 +- .../crypto/store/db/RealmCryptoStoreModule.kt | 18 +- .../store/db/model/GossipingEventEntity.kt | 88 ++++ .../model/IncomingGossipingRequestEntity.kt | 90 ++++ .../db/model/IncomingRoomKeyRequestEntity.kt | 112 ++--- .../db/model/IncomingSecretRequestEntity.kt | 74 +-- .../model/OutgoingGossipingRequestEntity.kt | 107 +++++ .../db/model/OutgoingRoomKeyRequestEntity.kt | 154 +++---- .../db/model/OutgoingSecretRequestEntity.kt | 126 ++--- .../tasks/RoomVerificationUpdateTask.kt | 1 - .../DefaultQrCodeVerificationTransaction.kt | 1 + .../internal/session/SessionComponent.kt | 5 + vector/build.gradle | 3 +- .../im/vector/riotx/core/di/FragmentModule.kt | 21 +- .../GossipingEventsEpoxyController.kt | 203 ++++++++ .../GossipingEventsPaperTrailFragment.kt | 68 +++ .../GossipingEventsPaperTrailViewModel.kt | 79 ++++ .../IncomingKeyRequestListFragment.kt | 57 +++ .../devtools/KeyRequestEpoxyController.kt | 78 +++- .../devtools/KeyRequestListViewModel.kt | 33 +- .../settings/devtools/KeyRequestsFragment.kt | 70 ++- ...t.kt => OutgoingKeyRequestListFragment.kt} | 5 +- .../main/res/menu/menu_common_gossiping.xml | 10 + vector/src/main/res/values/strings_riotX.xml | 2 + 62 files changed, 2811 insertions(+), 984 deletions(-) create mode 100644 matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/extensions/Try.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CancelGossipRequestWorker.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingRequestState.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/{OutgoingShareRequest.kt => OutgoingGossipingRequest.kt} (81%) create mode 100755 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt delete mode 100755 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequestManager.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipRequestWorker.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/GossipingEventEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsEpoxyController.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/settings/devtools/IncomingKeyRequestListFragment.kt rename vector/src/main/java/im/vector/riotx/features/settings/devtools/{KeyRequestListFragment.kt => OutgoingKeyRequestListFragment.kt} (95%) create mode 100644 vector/src/main/res/menu/menu_common_gossiping.xml diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index f980279a8d..97a1d977cc 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -97,6 +97,7 @@ dependencies { def coroutines_version = "1.3.2" def markwon_version = '3.1.0' def daggerVersion = '2.25.4' + def work_version = '2.3.2' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" @@ -126,7 +127,7 @@ dependencies { kapt 'dk.ilios:realmfieldnameshelper:1.1.1' // Work - implementation "androidx.work:work-runtime-ktx:2.3.3" + implementation "androidx.work:work-runtime-ktx:$work_version" // FP implementation "io.arrow-kt:arrow-core:$arrow_version" diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt new file mode 100644 index 0000000000..fca406ac31 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt @@ -0,0 +1,210 @@ +/* + * 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.gossiping + +import android.util.Log +import androidx.test.ext.junit.runners.AndroidJUnit4 +import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams +import im.vector.matrix.android.common.CommonTestHelper +import im.vector.matrix.android.common.SessionTestParams +import im.vector.matrix.android.common.TestConstants +import im.vector.matrix.android.internal.crypto.GossipingRequestState +import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestState +import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel +import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent +import junit.framework.TestCase.assertNotNull +import junit.framework.TestCase.assertTrue +import junit.framework.TestCase.fail +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.junit.Assert +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.JVM) +class KeyShareTests : InstrumentedTest { + + private val mTestHelper = CommonTestHelper(context()) + +// @Before +// fun setup() { +// mockkStatic(Log::class) +// every { Log.v(any(), any()) } returns 0 +// every { Log.d(any(), any()) } returns 0 +// every { Log.i(any(), any()) } returns 0 +// every { Log.e(any(), any()) } returns 0 +//// every { Log.println(any(), any(), any()) } returns 0 +//// every { Log.wtf(any(), any(), any()) } returns 0 +// } + + @Test + fun test_DoNotSelfShareIfNotTrusted() { + val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + + // Create an encrypted room and add a message + val roomId = mTestHelper.doSync { + aliceSession.createRoom( + CreateRoomParams(RoomDirectoryVisibility.PRIVATE).enableEncryptionWithAlgorithm(true), + it + ) + } + val room = aliceSession.getRoom(roomId) + assertNotNull(room) + Thread.sleep(4_000) + assertTrue(room?.isEncrypted() == true) + val sentEventId = mTestHelper.sendTextMessage(room!!, "My Message", 1).first().eventId + + // Open a new sessionx + + val aliceSession2 = mTestHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true)) + + val roomSecondSessionPOV = aliceSession2.getRoom(roomId) + + val receivedEvent = roomSecondSessionPOV?.getTimeLineEvent(sentEventId) + assertNotNull(receivedEvent) + assert(receivedEvent!!.isEncrypted()) + + try { + aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo") + fail("should fail") + } catch (failure: Throwable) { + } + + val outgoingRequestBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequest() + // Try to request + aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root) + + val waitLatch = CountDownLatch(1) + val eventMegolmSessionId = receivedEvent.root.content.toModel()?.sessionId + + var outGoingRequestId: String? = null + + retryPeriodicallyWithLatch(waitLatch) { + aliceSession2.cryptoService().getOutgoingRoomKeyRequest() + .filter { req -> + // filter out request that was known before + !outgoingRequestBefore.any { req.requestId == it.requestId } + } + .let { + val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId } + outGoingRequestId = outgoing?.requestId + outgoing != null + } + } + mTestHelper.await(waitLatch) + + Log.v("TEST", "=======> Outgoing requet Id is $outGoingRequestId") + + val outgoingRequestAfter = aliceSession2.cryptoService().getOutgoingRoomKeyRequest() + + //We should have a new request + Assert.assertTrue(outgoingRequestAfter.size > outgoingRequestBefore.size) + Assert.assertNotNull(outgoingRequestAfter.first { it.sessionId == eventMegolmSessionId }) + + // The first session should see an incoming request + // the request should be refused, because the device is not trusted + waitWithLatch { latch -> + retryPeriodicallyWithLatch(latch) { + // DEBUG LOGS + aliceSession.cryptoService().getIncomingRoomKeyRequest().let { + Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)") + Log.v("TEST", "=========================") + it.forEach { keyRequest -> + Log.v("TEST", "[ts${keyRequest.localCreationTimestamp}] requestId ${keyRequest.requestId}, for sessionId ${keyRequest.requestBody?.sessionId} is ${keyRequest.state}") + } + Log.v("TEST", "=========================") + } + + val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequest().firstOrNull { it.requestId == outGoingRequestId } + incoming?.state == GossipingRequestState.REJECTED + } + } + + try { + aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo") + fail("should fail") + } catch (failure: Throwable) { + } + + // Mark the device as trusted + aliceSession.cryptoService().setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId, + aliceSession2.sessionParams.credentials.deviceId ?: "") + + // Re request + aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root) + + waitWithLatch { latch -> + retryPeriodicallyWithLatch(latch) { + aliceSession.cryptoService().getIncomingRoomKeyRequest().let { + Log.v("TEST", "Incoming request Session 1") + Log.v("TEST", "=========================") + it.forEach { + Log.v("TEST", "requestId ${it.requestId}, for sessionId ${it.requestBody?.sessionId} is ${it.state}") + } + Log.v("TEST", "=========================") + + + it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == GossipingRequestState.ACCEPTED } + } + } + } + + Thread.sleep(6_000) + waitWithLatch { latch -> + retryPeriodicallyWithLatch(latch) { + aliceSession2.cryptoService().getOutgoingRoomKeyRequest().let { + it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == OutgoingGossipingRequestState.CANCELLED } + } + } + } + + try { + aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo") + } catch (failure: Throwable) { + fail("should have been able to decrypt") + } + + mTestHelper.signOutAndClose(aliceSession) + mTestHelper.signOutAndClose(aliceSession2) + } + + fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) { + GlobalScope.launch { + while (true) { + delay(1000) + if (condition()) { + latch.countDown() + return@launch + } + } + } + } + + fun waitWithLatch(block: (CountDownLatch) -> Unit) { + val latch = CountDownLatch(1) + block(latch) + mTestHelper.await(latch) + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt index e599eda7f9..d584482774 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.internal.crypto.keysbackup +import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.api.listeners.ProgressListener @@ -34,6 +35,7 @@ import im.vector.matrix.android.common.assertDictEquals import im.vector.matrix.android.common.assertListEquals import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import im.vector.matrix.android.internal.crypto.MegolmSessionData +import im.vector.matrix.android.internal.crypto.ShareRequestState import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo @@ -41,12 +43,15 @@ import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersio import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper +import io.mockk.every +import io.mockk.mockkStatic import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Assert.fail +import org.junit.BeforeClass import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -325,46 +330,46 @@ class KeysBackupTest : InstrumentedTest { * - Restore must be successful * - *** There must be no more pending key share requests */ - @Test - fun restoreKeysBackupAndKeyShareRequestTest() { - fail("Check with Valere for this test. I think we do not send key share request") - - val testData = createKeysBackupScenarioWithPassword(null) - - // - Check the SDK sent key share requests - val cryptoStore2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store - val unsentRequest = cryptoStore2 - .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.UNSENT)) - val sentRequest = cryptoStore2 - .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.SENT)) - - // Request is either sent or unsent - assertTrue(unsentRequest != null || sentRequest != null) - - // - Restore the e2e backup from the homeserver - val importRoomKeysResult = mTestHelper.doSync { - testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, - testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, - null, - null, - null, - it - ) - } - - checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) - - // - There must be no more pending key share requests - val unsentRequestAfterRestoration = cryptoStore2 - .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.UNSENT)) - val sentRequestAfterRestoration = cryptoStore2 - .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.SENT)) - - // Request is either sent or unsent - assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null) - - testData.cleanUp(mTestHelper) - } +// @Test +// fun restoreKeysBackupAndKeyShareRequestTest() { +// fail("Check with Valere for this test. I think we do not send key share request") +// +// val testData = createKeysBackupScenarioWithPassword(null) +// +// // - Check the SDK sent key share requests +// val cryptoStore2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store +// val unsentRequest = cryptoStore2 +// .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.UNSENT)) +// val sentRequest = cryptoStore2 +// .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.SENT)) +// +// // Request is either sent or unsent +// assertTrue(unsentRequest != null || sentRequest != null) +// +// // - Restore the e2e backup from the homeserver +// val importRoomKeysResult = mTestHelper.doSync { +// testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, +// testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, +// null, +// null, +// null, +// it +// ) +// } +// +// checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) +// +// // - There must be no more pending key share requests +// val unsentRequestAfterRestoration = cryptoStore2 +// .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.UNSENT)) +// val sentRequestAfterRestoration = cryptoStore2 +// .getOutgoingRoomKeyRequestByState(setOf(ShareRequestState.SENT)) +// +// // Request is either sent or unsent +// assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null) +// +// testData.cleanUp(mTestHelper) +// } /** * - Do an e2e backup to the homeserver with a recovery key diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/qrcode/QrCodeTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/qrcode/QrCodeTest.kt index d19fad4b59..ff1780865c 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/qrcode/QrCodeTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/qrcode/QrCodeTest.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto.verification.qrcode import androidx.test.ext.junit.runners.AndroidJUnit4 import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64NoPadding import org.amshove.kluent.shouldBeNull import org.amshove.kluent.shouldEqual import org.amshove.kluent.shouldEqualTo diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/crypto/MXCryptoConfig.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/crypto/MXCryptoConfig.kt index dc08023d99..a8d576bae9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/crypto/MXCryptoConfig.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/crypto/MXCryptoConfig.kt @@ -23,5 +23,14 @@ data class MXCryptoConfig( // Tell whether the encryption of the event content is enabled for the invited members. // SDK clients can disable this by settings it to false. // Note that the encryption for the invited members will be blocked if the history visibility is "joined". - var enableEncryptionForInvitedMembers: Boolean = true + var enableEncryptionForInvitedMembers: Boolean = true, + + /** + * If set to true, the SDK will automatically ignore room key request (gossiping) + * coming from your other untrusted sessions (or blocked). + * If set to false, the request will be forwarded to the application layer; in this + * case the application can decide to prompt the user. + */ + var discardRoomKeyRequestsFromUntrustedDevices : Boolean = true + ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/extensions/Try.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/extensions/Try.kt new file mode 100644 index 0000000000..3afcac08c1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/extensions/Try.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.api.extensions + +inline fun tryThis(operation: () -> A): A? { + return try { + operation() + } catch (any: Throwable) { + null + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt index d6823d49cb..ab8417b542 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt @@ -26,6 +26,7 @@ import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestList import im.vector.matrix.android.api.session.crypto.verification.VerificationService 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.internal.crypto.IncomingRoomKeyRequest import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest @@ -87,6 +88,8 @@ interface CryptoService { fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? + fun requestRoomKeyForEvent(event: Event) + fun reRequestRoomKeyForEvent(event: Event) fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) @@ -132,4 +135,6 @@ interface CryptoService { fun removeSessionListener(listener: NewSessionListener) fun getOutgoingRoomKeyRequest(): List + fun getIncomingRoomKeyRequest(): List + fun getGossipingEventsTrail(): List } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt index ff4745ef46..181508402d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt @@ -68,4 +68,7 @@ interface CrossSigningService { fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult + + fun onSecretSSKGossip(sskPrivateKey: String): Boolean + fun onSecretUSKGossip(uskPrivateKey: String): Boolean } 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 596d8d3e5d..ad0c6f10fa 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 @@ -111,6 +111,9 @@ interface SharedSecretStorageService { fun checkShouldBeAbleToAccessSecrets(secretNames: List, keyId: String?) : IntegrityResult + + fun requestSecret(name: String, myOtherDeviceId: String) + data class KeyRef( val keyId: String?, val keySpec: SsssKeySpec? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CancelGossipRequestWorker.kt new file mode 100644 index 0000000000..a87dae3690 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CancelGossipRequestWorker.kt @@ -0,0 +1,119 @@ +/* + * 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 + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.Data +import androidx.work.WorkerParameters +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.auth.data.Credentials +import im.vector.matrix.android.api.failure.shouldBeRetried +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.LocalEcho +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap +import im.vector.matrix.android.internal.crypto.model.rest.ShareRequestCancellation +import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore +import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask +import im.vector.matrix.android.internal.worker.WorkerParamsFactory +import im.vector.matrix.android.internal.worker.getSessionComponent +import org.greenrobot.eventbus.EventBus +import timber.log.Timber +import javax.inject.Inject + +internal class CancelGossipRequestWorker(context: Context, + params: WorkerParameters) + : CoroutineWorker(context, params) { + + @JsonClass(generateAdapter = true) + internal data class Params( + val sessionId: String, + val requestId: String, + val recipients: Map> + ) { + companion object { + fun fromRequest(sessionId: String, request: OutgoingGossipingRequest): Params { + return Params( + sessionId = sessionId, + requestId = request.requestId, + recipients = request.recipients + ) + } + } + } + + @Inject lateinit var sendToDeviceTask: SendToDeviceTask + @Inject lateinit var cryptoStore: IMXCryptoStore + @Inject lateinit var eventBus: EventBus + @Inject lateinit var credentials: Credentials + + override suspend fun doWork(): Result { + + val errorOutputData = Data.Builder().putBoolean("failed", true).build() + val params = WorkerParamsFactory.fromData(inputData) + ?: return Result.success(errorOutputData) + + val sessionComponent = getSessionComponent(params.sessionId) + ?: return Result.success(errorOutputData).also { + // TODO, can this happen? should I update local echo? + Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}") + } + sessionComponent.inject(this) + + val localId = LocalEcho.createLocalEchoId() + val contentMap = MXUsersDevicesMap() + val toDeviceContent = ShareRequestCancellation( + requestingDeviceId = credentials.deviceId, + requestId = params.requestId + ) + cryptoStore.saveGossipingEvent(Event( + type = EventType.ROOM_KEY_REQUEST, + content = toDeviceContent.toContent(), + senderId = credentials.userId + ).also { + it.ageLocalTs = System.currentTimeMillis() + }) + + params.recipients.forEach { userToDeviceMap -> + userToDeviceMap.value.forEach { deviceId -> + contentMap.setObject(userToDeviceMap.key, deviceId, toDeviceContent) + } + } + + try { + cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLING) + sendToDeviceTask.execute( + SendToDeviceTask.Params( + eventType = EventType.ROOM_KEY_REQUEST, + contentMap = contentMap, + transactionId = localId + ) + ) + cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLED) + return Result.success() + } catch (exception: Throwable) { + return if (exception.shouldBeRetried()) { + Result.retry() + } else { + cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.FAILED_TO_CANCEL) + Result.success(errorOutputData) + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 2ea0a71c50..7b7490f233 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -33,6 +33,8 @@ import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.listeners.ProgressListener import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.MXCryptoError +import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Event @@ -47,6 +49,7 @@ import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAct import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmEncryptionFactory +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.keysbackup.DefaultKeysBackupService @@ -55,7 +58,9 @@ import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap +import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent +import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse @@ -80,6 +85,7 @@ import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembers import im.vector.matrix.android.internal.session.room.membership.RoomMemberHelper import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.TaskThread import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.util.JsonCanonicalizer import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers @@ -137,7 +143,7 @@ internal class DefaultCryptoService @Inject constructor( // private val incomingRoomKeyRequestManager: IncomingRoomKeyRequestManager, // - private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager, + private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager, // Actions private val setDeviceVerificationAction: SetDeviceVerificationAction, private val megolmSessionDataImporter: MegolmSessionDataImporter, @@ -189,6 +195,7 @@ internal class DefaultCryptoService @Inject constructor( override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback) { setDeviceNameTask .configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) { + this.executionThread = TaskThread.CRYPTO this.callback = object : MatrixCallback { override fun onSuccess(data: Unit) { // bg refresh of crypto device @@ -207,6 +214,7 @@ internal class DefaultCryptoService @Inject constructor( override fun deleteDevice(deviceId: String, callback: MatrixCallback) { deleteDeviceTask .configureWith(DeleteDeviceTask.Params(deviceId)) { + this.executionThread = TaskThread.CRYPTO this.callback = callback } .executeBy(taskExecutor) @@ -215,6 +223,7 @@ internal class DefaultCryptoService @Inject constructor( override fun deleteDeviceWithUserPassword(deviceId: String, authSession: String?, password: String, callback: MatrixCallback) { deleteDeviceWithUserPasswordTask .configureWith(DeleteDeviceWithUserPasswordTask.Params(deviceId, authSession, password)) { + this.executionThread = TaskThread.CRYPTO this.callback = callback } .executeBy(taskExecutor) @@ -231,6 +240,7 @@ internal class DefaultCryptoService @Inject constructor( override fun getDevicesList(callback: MatrixCallback) { getDevicesTask .configureWith { + this.executionThread = TaskThread.CRYPTO this.callback = callback } .executeBy(taskExecutor) @@ -239,6 +249,7 @@ internal class DefaultCryptoService @Inject constructor( override fun getDeviceInfo(deviceId: String, callback: MatrixCallback) { getDeviceInfoTask .configureWith(GetDeviceInfoTask.Params(deviceId)) { + this.executionThread = TaskThread.CRYPTO this.callback = callback } .executeBy(taskExecutor) @@ -301,7 +312,6 @@ internal class DefaultCryptoService @Inject constructor( runCatching { uploadDeviceKeys() oneTimeKeysUploader.maybeUploadOneTimeKeys() - outgoingRoomKeyRequestManager.start() keysBackupService.checkAndStartKeysBackup() if (isInitialSync) { // refresh the devices list for each known room members @@ -329,8 +339,6 @@ internal class DefaultCryptoService @Inject constructor( fun close() = runBlocking(coroutineDispatchers.crypto) { cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module")) - outgoingRoomKeyRequestManager.stop() - olmDevice.release() cryptoStore.close() } @@ -689,18 +697,24 @@ internal class DefaultCryptoService @Inject constructor( * @param event the event */ fun onToDeviceEvent(event: Event) { + // event have already been decrypted cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { when (event.getClearType()) { EventType.ROOM_KEY, EventType.FORWARDED_ROOM_KEY -> { + cryptoStore.saveGossipingEvent(event) + // Keys are imported directly, not waiting for end of sync onRoomKeyEvent(event) } - EventType.REQUEST_SECRET, EventType.ROOM_KEY_REQUEST -> { + // save audit trail + cryptoStore.saveGossipingEvent(event) + // Requests are stacked, and will be handled one by one at the end of the sync (onSyncComplete) incomingRoomKeyRequestManager.onGossipingRequestEvent(event) } EventType.SEND_SECRET -> { - // incomingRoomKeyRequestManager.onGossipingRequestEvent(event) + cryptoStore.saveGossipingEvent(event) + onSecretSendReceived(event) } else -> { // ignore @@ -716,6 +730,7 @@ internal class DefaultCryptoService @Inject constructor( */ private fun onRoomKeyEvent(event: Event) { val roomKeyContent = event.getClearContent().toModel() ?: return + Timber.v("## onRoomKeyEvent() : type<${event.type}> , sessionId<${roomKeyContent.sessionId}>") if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) { Timber.e("## onRoomKeyEvent() : missing fields") return @@ -728,6 +743,78 @@ internal class DefaultCryptoService @Inject constructor( alg.onRoomKeyEvent(event, keysBackupService) } + private fun onSecretSendReceived(event: Event) { + if (!event.isEncrypted()) { + // secret send messages must be encrypted + Timber.e("## onSecretSend() :Received unencrypted secret send event") + return + } + + // Was that sent by us? + if (event.senderId != credentials.userId) { + Timber.e("## onSecretSend() : Ignore secret from other user ${event.senderId}") + return + } + + val encryptedEventContent = event.getClearContent().toModel() + ?: return Unit.also { + Timber.e("## onSecretSend() :Received malformed secret send event") + } + + val senderDevice = encryptedEventContent.senderKey + + val device = senderDevice?.let { cryptoStore.getUserDevice(event.senderId, it) } + ?: return Unit.also { + Timber.e("## processIncomingSecretShareRequest() : Received secret share request from unknown device ${senderDevice}") + } + + try { + val result = decryptEvent(event, "gossip") + event.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } catch (failure: Throwable) { + Timber.i("## onSecretSend() :Failed to decrypt secret share: $device") + } + + val secretContent = event.getClearContent().toModel() ?: return + + val existingRequest = cryptoStore + .getPendingIncomingGossipingRequests() + .firstOrNull { it.requestId == secretContent.requestId } as? IncomingSecretShareRequest + + if (existingRequest == null) { + Timber.i("## onSecretSend() :Received secret from unknown request id: ${secretContent.requestId} from device ") + return + } + + if (device.isBlocked || !device.isVerified) { + // Ignore secrets from this + Timber.i("## onSecretSend() :Received secret from untrusted/blocked device: ${device}") + return + } + + when (existingRequest.secretName) { + SELF_SIGNING_KEY_SSSS_NAME -> { + if (device.trustLevel?.isLocallyVerified() == true) { + crossSigningService.onSecretSSKGossip(secretContent.secretValue) + return + } + } + USER_SIGNING_KEY_SSSS_NAME -> { + if (device.trustLevel?.isLocallyVerified() == true) { + cryptoStore.storePrivateKeysInfo(null, null, secretContent.secretValue) + } + } + else -> { + // Ask to application layer? + } + } + } + /** * Handle an m.room.encryption event. * @@ -1003,14 +1090,14 @@ internal class DefaultCryptoService @Inject constructor( setRoomBlacklistUnverifiedDevices(roomId, false) } - // TODO Check if this method is still necessary +// TODO Check if this method is still necessary /** * Cancel any earlier room key request * * @param requestBody requestBody */ override fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) { - outgoingRoomKeyRequestManager.cancelRoomKeyRequest(requestBody) + outgoingGossipingRequestManager.cancelRoomKeyRequest(requestBody) } /** @@ -1019,20 +1106,36 @@ internal class DefaultCryptoService @Inject constructor( * @param event the event to decrypt again. */ override fun reRequestRoomKeyForEvent(event: Event) { - val wireContent = event.content - if (wireContent == null) { + val wireContent = event.content.toModel() ?: return Unit.also { Timber.e("## reRequestRoomKeyForEvent Failed to re-request key, null content") - return } val requestBody = RoomKeyRequestBody( - algorithm = wireContent["algorithm"]?.toString(), + algorithm = wireContent.algorithm, roomId = event.roomId, - senderKey = wireContent["sender_key"]?.toString(), - sessionId = wireContent["session_id"]?.toString() + senderKey = wireContent.senderKey, + sessionId = wireContent.sessionId ) - outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody) + outgoingGossipingRequestManager.resendRoomKeyRequest(requestBody) + } + + override fun requestRoomKeyForEvent(event: Event) { + val wireContent = event.content.toModel() ?: return Unit.also { + Timber.e("## requestRoomKeyForEvent Failed to request key, null content eventId: ${event.eventId}") + } + + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + if (!isStarted()) { + Timber.v("## requestRoomKeyForEvent() : wait after e2e init") + internalStart(false) + } + roomDecryptorProvider + .getOrCreateRoomDecryptor(event.roomId, wireContent.algorithm) + ?.requestKeysForEvent(event) ?: run { + Timber.v("## requestRoomKeyForEvent() : No room decryptor for roomId:${event.roomId} algorithm:${wireContent.algorithm}") + } + } } /** @@ -1090,9 +1193,9 @@ internal class DefaultCryptoService @Inject constructor( override fun removeSessionListener(listener: NewSessionListener) { roomDecryptorProvider.removeSessionListener(listener) } - /* ========================================================================================== - * DEBUG INFO - * ========================================================================================== */ +/* ========================================================================================== + * DEBUG INFO + * ========================================================================================== */ override fun toString(): String { return "DefaultCryptoService of " + credentials.userId + " (" + credentials.deviceId + ")" @@ -1101,4 +1204,12 @@ internal class DefaultCryptoService @Inject constructor( override fun getOutgoingRoomKeyRequest(): List { return cryptoStore.getOutgoingRoomKeyRequests() } + + override fun getIncomingRoomKeyRequest(): List { + return cryptoStore.getIncomingRoomKeyRequests() + } + + override fun getGossipingEventsTrail(): List { + return cryptoStore.getGossipingEventsTrail() + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingRequestState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingRequestState.kt new file mode 100644 index 0000000000..b218a2e387 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingRequestState.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.crypto + +enum class GossipRequestType { + KEY, + SECRET +} + +enum class GossipingRequestState { + NONE, + PENDING, + REJECTED, + ACCEPTED, + // USER_REJECTED, + UNABLE_TO_PROCESS, + CANCELLED_BY_REQUESTER, + RE_REQUESTED +} + +enum class OutgoingGossipingRequestState { + UNSENT, + SENDING, + SENT, + CANCELLING, + CANCELLED, + FAILED_TO_SEND, + FAILED_TO_CANCEL +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRequestCancellation.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRequestCancellation.kt index 7751f149f1..98e1e95423 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRequestCancellation.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRequestCancellation.kt @@ -37,7 +37,8 @@ data class IncomingRequestCancellation( /** * The request id */ - override val requestId: String? = null + override val requestId: String? = null, + override val localCreationTimestamp: Long? ) : IncomingShareRequestCommon { companion object { /** @@ -52,7 +53,8 @@ data class IncomingRequestCancellation( IncomingRequestCancellation( userId = event.senderId, deviceId = it.requestingDeviceId, - requestId = it.requestId + requestId = it.requestId, + localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis() ) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequest.kt index 8648041094..13f3d38677 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequest.kt @@ -46,6 +46,8 @@ data class IncomingRoomKeyRequest( */ val requestBody: RoomKeyRequestBody? = null, + val state: GossipingRequestState = GossipingRequestState.NONE, + /** * The runnable to call to accept to share the keys */ @@ -56,7 +58,8 @@ data class IncomingRoomKeyRequest( * The runnable to call to ignore the key share request. */ @Transient - var ignore: Runnable? = null + var ignore: Runnable? = null, + override val localCreationTimestamp: Long? ) : IncomingShareRequestCommon { companion object { /** @@ -72,7 +75,8 @@ data class IncomingRoomKeyRequest( userId = event.senderId, deviceId = it.requestingDeviceId, requestId = it.requestId, - requestBody = it.body ?: RoomKeyRequestBody() + requestBody = it.body ?: RoomKeyRequestBody(), + localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis() ) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt index 98c73d75f5..4fdd011aee 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt @@ -17,7 +17,7 @@ package im.vector.matrix.android.internal.crypto import im.vector.matrix.android.api.auth.data.Credentials -import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService +import im.vector.matrix.android.api.crypto.MXCryptoConfig import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.keyshare.GossipingRequestListener @@ -35,8 +35,8 @@ import javax.inject.Inject internal class IncomingRoomKeyRequestManager @Inject constructor( private val credentials: Credentials, private val cryptoStore: IMXCryptoStore, - private val crossSigningService: CrossSigningService, - private val secrSecretCryptoProvider: ShareSecretCryptoProvider, + private val cryptoConfig: MXCryptoConfig, + private val secretSecretCryptoProvider: ShareSecretCryptoProvider, private val roomDecryptorProvider: RoomDecryptorProvider) { // list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations @@ -48,8 +48,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( private val gossipingRequestListeners: MutableSet = HashSet() init { - receiveGossipingRequests.addAll(cryptoStore.getPendingIncomingSecretShareRequests()) - receiveGossipingRequests.addAll(cryptoStore.getPendingIncomingRoomKeyRequests()) + receiveGossipingRequests.addAll(cryptoStore.getPendingIncomingGossipingRequests()) } /** @@ -59,21 +58,37 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( * @param event the announcement event. */ fun onGossipingRequestEvent(event: Event) { + Timber.v("## onGossipingRequestEvent type ${event.type} from user ${event.senderId}") val roomKeyShare = event.getClearContent().toModel() + val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it } when (roomKeyShare?.action) { GossipingToDeviceObject.ACTION_SHARE_REQUEST -> { if (event.getClearType() == EventType.REQUEST_SECRET) { IncomingSecretShareRequest.fromEvent(event)?.let { + // save in DB + cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs) receiveGossipingRequests.add(it) } } else if (event.getClearType() == EventType.ROOM_KEY_REQUEST) { IncomingRoomKeyRequest.fromEvent(event)?.let { - receiveGossipingRequests.add(it) + if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) { + // ignore, it was sent by me as * + Timber.v("## onGossipingRequestEvent type ${event.type} ignore remote echo") + } else { + cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs) + receiveGossipingRequests.add(it) + } } } } - GossipingToDeviceObject.ACTION_SHARE_CANCELLATION -> IncomingRequestCancellation.fromEvent(event)?.let { receivedRequestCancellations.add(it) } - else -> Timber.e("## onGossipingRequestEvent() : unsupported action ${roomKeyShare?.action}") + GossipingToDeviceObject.ACTION_SHARE_CANCELLATION -> { + IncomingRequestCancellation.fromEvent(event)?.let { + receivedRequestCancellations.add(it) + } + } + else -> { + Timber.e("## onGossipingRequestEvent() : unsupported action ${roomKeyShare?.action}") + } } } @@ -83,6 +98,8 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( * It must be called on CryptoThread */ fun processReceivedGossipingRequests() { + Timber.v("## processReceivedGossipingRequests()") + val roomKeyRequestsToProcess = receiveGossipingRequests.toList() receiveGossipingRequests.clear() for (request in roomKeyRequestsToProcess) { @@ -102,16 +119,25 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( } } - if (null != receivedRequestCancellations) { - for (request in receivedRequestCancellations!!) { - Timber.v("## ## processReceivedGossipingRequests() : m.room_key_request cancellation for " + request.userId - + ":" + request.deviceId + " id " + request.requestId) - - // we should probably only notify the app of cancellations we told it - // about, but we don't currently have a record of that, so we just pass - // everything through. - onRoomKeyRequestCancellation(request) - cryptoStore.deleteIncomingRoomKeyRequest(request) + receivedRequestCancellations?.forEach { request -> + Timber.v("## processReceivedGossipingRequests() : m.room_key_request cancellation $request") + // we should probably only notify the app of cancellations we told it + // about, but we don't currently have a record of that, so we just pass + // everything through. + if (request.userId == credentials.userId && request.deviceId == credentials.deviceId) { + // ignore remote echo + return@forEach + } + val matchingIncoming = cryptoStore.getIncomingRoomKeyRequest(request.userId ?: "", request.deviceId ?: "", request.requestId ?: "") + if (matchingIncoming == null) { + // ignore that? + return@forEach + } else { + // If it was accepted from this device, keep the information, do not mark as cancelled + if (matchingIncoming.state != GossipingRequestState.ACCEPTED) { + onRoomKeyRequestCancellation(request) + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.CANCELLED_BY_REQUESTER) + } } } } @@ -123,11 +149,11 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( val roomId = body!!.roomId val alg = body.algorithm - Timber.v("m.room_key_request from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}") + Timber.v("## processIncomingRoomKeyRequest from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}") if (userId == null || credentials.userId != userId) { // TODO: determine if we sent this device the keys already: in Timber.w("## processReceivedGossipingRequests() : Ignoring room key request from other user for now") - cryptoStore.deleteIncomingRoomKeyRequest(request) + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) return } // TODO: should we queue up requests we don't yet have keys for, in case they turn up later? @@ -136,89 +162,110 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg) if (null == decryptor) { Timber.w("## processReceivedGossipingRequests() : room key request for unknown $alg in room $roomId") + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) return } if (!decryptor.hasKeysForKeyRequest(request)) { Timber.w("## processReceivedGossipingRequests() : room key request for unknown session ${body.sessionId!!}") - cryptoStore.deleteIncomingRoomKeyRequest(request) + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) return } if (credentials.deviceId == deviceId && credentials.userId == userId) { Timber.v("## processReceivedGossipingRequests() : oneself device - ignored") - cryptoStore.deleteIncomingRoomKeyRequest(request) + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) return } request.share = Runnable { decryptor.shareKeysWithDevice(request) - cryptoStore.deleteIncomingRoomKeyRequest(request) + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED) } request.ignore = Runnable { - cryptoStore.deleteIncomingRoomKeyRequest(request) + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) } // if the device is verified already, share the keys val device = cryptoStore.getUserDevice(userId, deviceId!!) if (device != null) { if (device.isVerified) { Timber.v("## processReceivedGossipingRequests() : device is already verified: sharing keys") - cryptoStore.deleteIncomingRoomKeyRequest(request) request.share?.run() return } if (device.isBlocked) { Timber.v("## processReceivedGossipingRequests() : device is blocked -> ignored") - cryptoStore.deleteIncomingRoomKeyRequest(request) + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) return } } - // If cross signing is available on account we automatically discard untrust devices request - if (cryptoStore.getMyCrossSigningInfo() != null) { + // As per config we automatically discard untrusted devices request + if (cryptoConfig.discardRoomKeyRequestsFromUntrustedDevices) { + Timber.v("## processReceivedGossipingRequests() : discardRoomKeyRequestsFromUntrustedDevices") // At this point the device is unknown, we don't want to bother user with that - cryptoStore.deleteIncomingRoomKeyRequest(request) + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) return } - cryptoStore.storeIncomingRoomKeyRequest(request) - - // Legacy, pass to application layer to decide what to do + // Pass to application layer to decide what to do onRoomKeyRequest(request) } private fun processIncomingSecretShareRequest(request: IncomingSecretShareRequest) { val secretName = request.secretName ?: return Unit.also { - cryptoStore.deleteIncomingSecretRequest(request) + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) Timber.v("## processIncomingSecretShareRequest() : Missing secret name") } val userId = request.userId if (userId == null || credentials.userId != userId) { - Timber.e("## processIncomingSecretShareRequest() : Ignoring secret share request from other user") - cryptoStore.deleteIncomingRoomKeyRequest(request) + Timber.e("## processIncomingSecretShareRequest() : Ignoring secret share request from other users") + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) return } + val deviceId = request.deviceId + ?: return Unit.also { + Timber.e("## processIncomingSecretShareRequest() : Malformed request, no ") + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) + } + + val device = cryptoStore.getUserDevice(userId, deviceId) + ?: return Unit.also { + Timber.e("## processIncomingSecretShareRequest() : Received secret share request from unknown device ${request.deviceId}") + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) + } + + if (!device.isVerified || device.isBlocked) { + Timber.v("## processIncomingSecretShareRequest() : Ignoring secret share request from untrusted/blocked session $device") + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) + return + } + + val isDeviceLocallyVerified = cryptoStore.getUserDevice(userId, deviceId)?.trustLevel?.isLocallyVerified() + + //Should SDK always Silently reject any request for the master key? when (secretName) { SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user else -> null }?.let { secretValue -> // TODO check if locally trusted and not outdated - if (cryptoStore.getUserDevice(userId, request.deviceId ?: "")?.trustLevel?.isLocallyVerified() == true) { - secrSecretCryptoProvider.shareSecretWithDevice(request, secretValue) - cryptoStore.deleteIncomingRoomKeyRequest(request) + Timber.i("## processIncomingSecretShareRequest() : Sharing secret $secretName with $device locally trusted") + if (isDeviceLocallyVerified == true) { + secretSecretCryptoProvider.shareSecretWithDevice(request, secretValue) + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED) } return } request.ignore = Runnable { - cryptoStore.deleteIncomingRoomKeyRequest(request) + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) } request.share = { secretValue -> - secrSecretCryptoProvider.shareSecretWithDevice(request, secretValue) - cryptoStore.deleteIncomingRoomKeyRequest(request) + secretSecretCryptoProvider.shareSecretWithDevice(request, secretValue) + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED) } onShareRequest(request) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingSecretShareRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingSecretShareRequest.kt index 6deb5b4692..2fcd3e22d5 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingSecretShareRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingSecretShareRequest.kt @@ -54,7 +54,9 @@ data class IncomingSecretShareRequest( * The runnable to call to ignore the key share request. */ @Transient - var ignore: Runnable? = null + var ignore: Runnable? = null, + + override val localCreationTimestamp: Long? ) : IncomingShareRequestCommon { companion object { @@ -71,7 +73,8 @@ data class IncomingSecretShareRequest( userId = event.senderId, deviceId = it.requestingDeviceId, requestId = it.requestId, - secretName = it.secretName + secretName = it.secretName, + localCreationTimestamp = event.ageLocalTs ?: System.currentTimeMillis() ) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingShareRequestCommon.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingShareRequestCommon.kt index 8999f835c8..f39a0d80d1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingShareRequestCommon.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingShareRequestCommon.kt @@ -31,4 +31,6 @@ interface IncomingShareRequestCommon { * The request id */ val requestId: String? + + val localCreationTimestamp: Long? } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingShareRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequest.kt similarity index 81% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingShareRequest.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequest.kt index b56ba22da6..30fc5fdb4a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingShareRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequest.kt @@ -16,10 +16,10 @@ package im.vector.matrix.android.internal.crypto -interface OutgoingShareRequest { - var recipients: List> +interface OutgoingGossipingRequest { + var recipients: Map> var requestId: String - var state: ShareRequestState + var state: OutgoingGossipingRequestState // transaction id for the cancellation, if any - var cancellationTxnId: String? + //var cancellationTxnId: String? } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt new file mode 100755 index 0000000000..e7c5ef89c8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt @@ -0,0 +1,185 @@ +/* + * Copyright 2016 OpenMarket Ltd + * Copyright 2018 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 + +import androidx.work.BackoffPolicy +import androidx.work.Data +import androidx.work.ExistingWorkPolicy +import androidx.work.ListenableWorker +import androidx.work.OneTimeWorkRequest +import im.vector.matrix.android.api.session.events.model.LocalEcho +import im.vector.matrix.android.api.util.Cancelable +import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody +import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore +import im.vector.matrix.android.internal.di.SessionId +import im.vector.matrix.android.internal.di.WorkManagerProvider +import im.vector.matrix.android.internal.session.SessionScope +import im.vector.matrix.android.internal.util.CancelableWork +import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers +import im.vector.matrix.android.internal.worker.WorkerParamsFactory +import im.vector.matrix.android.internal.worker.startChain +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import timber.log.Timber +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +@SessionScope +internal class OutgoingGossipingRequestManager @Inject constructor( + @SessionId private val sessionId: String, + private val cryptoStore: IMXCryptoStore, + private val coroutineDispatchers: MatrixCoroutineDispatchers, + private val cryptoCoroutineScope: CoroutineScope, + private val workManagerProvider: WorkManagerProvider) { + + /** + * Send off a room key request, if we haven't already done so. + * + * + * The `requestBody` is compared (with a deep-equality check) against + * previous queued or sent requests and if it matches, no change is made. + * Otherwise, a request is added to the pending list, and a job is started + * in the background to send it. + * + * @param requestBody requestBody + * @param recipients recipients + */ + fun sendRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map>) { + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + cryptoStore.getOrAddOutgoingRoomKeyRequest(requestBody, recipients)?.let { + // Don't resend if it's already done, you need to cancel first (reRequest) + if (it.state == OutgoingGossipingRequestState.SENDING || it.state == OutgoingGossipingRequestState.SENT) { + Timber.v("## sendOutgoingRoomKeyRequest() : we already request for that session: $it") + return@launch + } + + sendOutgoingGossipingRequest(it) + } + } + } + + fun sendSecretShareRequest(secretName: String, recipients: Map>) { + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + cryptoStore.getOrAddOutgoingSecretShareRequest(secretName, recipients)?.let { + // TODO check if there is already one that is being sent? + if (it.state == OutgoingGossipingRequestState.SENDING || it.state == OutgoingGossipingRequestState.SENT) { + Timber.v("## sendOutgoingRoomKeyRequest() : we already request for that session: $it") + return@launch + } + + sendOutgoingGossipingRequest(it) + } + } + } + + /** + * Cancel room key requests, if any match the given details + * + * @param requestBody requestBody + */ + fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) { + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + cancelRoomKeyRequest(requestBody, false) + } + } + + /** + * Cancel room key requests, if any match the given details, and resend + * + * @param requestBody requestBody + */ + fun resendRoomKeyRequest(requestBody: RoomKeyRequestBody) { + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + cancelRoomKeyRequest(requestBody, true) + } + } + + /** + * Cancel room key requests, if any match the given details, and resend + * + * @param requestBody requestBody + * @param andResend true to resend the key request + */ + private fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody, andResend: Boolean) { + val req = cryptoStore.getOutgoingRoomKeyRequest(requestBody) + ?: // no request was made for this key + return Unit.also { + Timber.v("## cancelRoomKeyRequest() Unknown request") + } + + sendOutgoingRoomKeyRequestCancellation(req, andResend) + } + + /** + * Send the outgoing key request. + * + * @param request the request + */ + private fun sendOutgoingGossipingRequest(request: OutgoingGossipingRequest) { + Timber.v("## sendOutgoingRoomKeyRequest() : Requesting keys ${request}") + + val params = SendGossipRequestWorker.Params( + sessionId = sessionId, + keyShareRequest = request as? OutgoingRoomKeyRequest, + secretShareRequest = request as? OutgoingSecretRequest + ) + cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.SENDING) + val workRequest = createWork(WorkerParamsFactory.toData(params), true) + postWork(workRequest) + } + + /** + * Given a OutgoingRoomKeyRequest, cancel it and delete the request record + * + * @param request the request + */ + private fun sendOutgoingRoomKeyRequestCancellation(request: OutgoingRoomKeyRequest, resend: Boolean = false) { + Timber.v("$request") + val params = CancelGossipRequestWorker.Params.fromRequest(sessionId, request) + cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.CANCELLING) + + val workRequest = createWork(WorkerParamsFactory.toData(params), true) + postWork(workRequest) + + if (resend) { + val reSendParams = SendGossipRequestWorker.Params( + sessionId = sessionId, + keyShareRequest = request.copy(requestId = LocalEcho.createLocalEchoId()) + ) + val reSendWorkRequest = createWork(WorkerParamsFactory.toData(reSendParams), true) + postWork(reSendWorkRequest) + } + } + + private inline fun createWork(data: Data, startChain: Boolean): OneTimeWorkRequest { + return workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setConstraints(WorkManagerProvider.workConstraints) + .startChain(startChain) + .setInputData(data) + .setBackoffCriteria(BackoffPolicy.LINEAR, 10_000L, TimeUnit.MILLISECONDS) + .build() + } + + private fun postWork(workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND): Cancelable { + workManagerProvider.workManager + .beginUniqueWork(this::class.java.name, policy, workRequest) + .enqueue() + + return CancelableWork(workManagerProvider.workManager, workRequest.id) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequest.kt index 2b9f8003ee..7e359a677c 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequest.kt @@ -17,22 +17,27 @@ package im.vector.matrix.android.internal.crypto +import com.squareup.moshi.JsonClass import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody /** * Represents an outgoing room key request */ -class OutgoingRoomKeyRequest( +@JsonClass(generateAdapter = true) +data class OutgoingRoomKeyRequest( // RequestBody - var requestBody: RoomKeyRequestBody?, // list of recipients for the request - override var recipients: List>, // Unique id for this request. Used for both + var requestBody: RoomKeyRequestBody?, + // list of recipients for the request + override var recipients: Map>, + // Unique id for this request. Used for both // an id within the request for later pairing with a cancellation, and for // the transaction id when sending the to_device messages to our local override var requestId: String, // current state of this request - override var state: ShareRequestState) : OutgoingShareRequest { + override var state: OutgoingGossipingRequestState + // transaction id for the cancellation, if any + // override var cancellationTxnId: String? = null +) : OutgoingGossipingRequest { - // transaction id for the cancellation, if any - override var cancellationTxnId: String? = null /** * Used only for log. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequestManager.kt deleted file mode 100755 index 5102afef7c..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequestManager.kt +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright 2016 OpenMarket Ltd - * Copyright 2018 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 - -import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap -import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody -import im.vector.matrix.android.internal.crypto.model.rest.ShareRequestCancellation -import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareRequest -import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore -import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask -import im.vector.matrix.android.internal.session.SessionScope -import im.vector.matrix.android.internal.task.TaskExecutor -import im.vector.matrix.android.internal.task.TaskThread -import im.vector.matrix.android.internal.task.configureWith -import im.vector.matrix.android.internal.util.createBackgroundHandler -import timber.log.Timber -import java.util.concurrent.atomic.AtomicBoolean -import javax.inject.Inject - -@SessionScope -internal class OutgoingRoomKeyRequestManager @Inject constructor( - private val cryptoStore: IMXCryptoStore, - private val sendToDeviceTask: SendToDeviceTask, - private val taskExecutor: TaskExecutor) { - - // running - private var isClientRunning: Boolean = false - - // transaction counter - private var txnCtr: Int = 0 - - // sanity check to ensure that we don't end up with two concurrent runs - // of sendOutgoingRoomKeyRequestsTimer - private val sendOutgoingRoomKeyRequestsRunning = AtomicBoolean(false) - - /** - * Called when the client is started. Sets background processes running. - */ - fun start() { - isClientRunning = true - startTimer() - } - - /** - * Called when the client is stopped. Stops any running background processes. - */ - fun stop() { - isClientRunning = false - stopTimer() - } - - /** - * Make up a new transaction id - * - * @return {string} a new, unique, transaction id - */ - private fun makeTxnId(): String { - return "m" + System.currentTimeMillis() + "." + txnCtr++ - } - - /** - * Send off a room key request, if we haven't already done so. - * - * - * The `requestBody` is compared (with a deep-equality check) against - * previous queued or sent requests and if it matches, no change is made. - * Otherwise, a request is added to the pending list, and a job is started - * in the background to send it. - * - * @param requestBody requestBody - * @param recipients recipients - */ - fun sendRoomKeyRequest(requestBody: RoomKeyRequestBody?, recipients: List>) { - val req = cryptoStore.getOrAddOutgoingRoomKeyRequest( - OutgoingRoomKeyRequest(requestBody, recipients, makeTxnId(), ShareRequestState.UNSENT)) - - if (req?.state == ShareRequestState.UNSENT) { - startTimer() - } - } - - /** - * Cancel room key requests, if any match the given details - * - * @param requestBody requestBody - */ - fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) { - BACKGROUND_HANDLER.post { - cancelRoomKeyRequest(requestBody, false) - } - } - - /** - * Cancel room key requests, if any match the given details, and resend - * - * @param requestBody requestBody - */ - fun resendRoomKeyRequest(requestBody: RoomKeyRequestBody) { - BACKGROUND_HANDLER.post { - cancelRoomKeyRequest(requestBody, true) - } - } - - /** - * Cancel room key requests, if any match the given details, and resend - * - * @param requestBody requestBody - * @param andResend true to resend the key request - */ - private fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody, andResend: Boolean) { - val req = cryptoStore.getOutgoingRoomKeyRequest(requestBody) - ?: // no request was made for this key - return - - Timber.v("cancelRoomKeyRequest: requestId: " + req.requestId + " state: " + req.state + " andResend: " + andResend) - - when (req.state) { - ShareRequestState.CANCELLATION_PENDING, - ShareRequestState.CANCELLATION_PENDING_AND_WILL_RESEND -> { - // nothing to do here - } - ShareRequestState.UNSENT, - ShareRequestState.FAILED -> { - Timber.v("## cancelRoomKeyRequest() : deleting unnecessary room key request for $requestBody") - cryptoStore.deleteOutgoingRoomKeyRequest(req.requestId) - } - ShareRequestState.SENT -> { - if (andResend) { - req.state = ShareRequestState.CANCELLATION_PENDING_AND_WILL_RESEND - } else { - req.state = ShareRequestState.CANCELLATION_PENDING - } - req.cancellationTxnId = makeTxnId() - cryptoStore.updateOutgoingRoomKeyRequest(req) - sendOutgoingRoomKeyRequestCancellation(req) - } - } - } - - /** - * Start the background timer to send queued requests, if the timer isn't already running. - */ - private fun startTimer() { - if (sendOutgoingRoomKeyRequestsRunning.get()) { - return - } - BACKGROUND_HANDLER.postDelayed(Runnable { - if (sendOutgoingRoomKeyRequestsRunning.get()) { - Timber.v("## startTimer() : RoomKeyRequestSend already in progress!") - return@Runnable - } - - sendOutgoingRoomKeyRequestsRunning.set(true) - sendOutgoingRoomKeyRequests() - }, SEND_KEY_REQUESTS_DELAY_MS.toLong()) - } - - private fun stopTimer() { - BACKGROUND_HANDLER.removeCallbacksAndMessages(null) - } - - // look for and send any queued requests. Runs itself recursively until - // there are no more requests, or there is an error (in which case, the - // timer will be restarted before the promise resolves). - private fun sendOutgoingRoomKeyRequests() { - if (!isClientRunning) { - sendOutgoingRoomKeyRequestsRunning.set(false) - return - } - - Timber.v("## sendOutgoingRoomKeyRequests() : Looking for queued outgoing room key requests") - val outgoingRoomKeyRequest = cryptoStore.getOutgoingRoomKeyRequestByState( - setOf(ShareRequestState.UNSENT, - ShareRequestState.CANCELLATION_PENDING, - ShareRequestState.CANCELLATION_PENDING_AND_WILL_RESEND)) - - if (null == outgoingRoomKeyRequest) { - Timber.v("## sendOutgoingRoomKeyRequests() : No more outgoing room key requests") - sendOutgoingRoomKeyRequestsRunning.set(false) - return - } - - if (ShareRequestState.UNSENT === outgoingRoomKeyRequest.state) { - sendOutgoingRoomKeyRequest(outgoingRoomKeyRequest) - } else { - sendOutgoingRoomKeyRequestCancellation(outgoingRoomKeyRequest) - } - } - - /** - * Send the outgoing key request. - * - * @param request the request - */ - private fun sendOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest) { - Timber.v("## sendOutgoingRoomKeyRequest() : Requesting keys " + request.requestBody - + " from " + request.recipients + " id " + request.requestId) - - val requestMessage = RoomKeyShareRequest( - requestingDeviceId = cryptoStore.getDeviceId(), - requestId = request.requestId, - body = request.requestBody - ) - - sendMessageToDevices(requestMessage, request.recipients, request.requestId, object : MatrixCallback { - private fun onDone(state: ShareRequestState) { - if (request.state !== ShareRequestState.UNSENT) { - Timber.v("## sendOutgoingRoomKeyRequest() : Cannot update room key request from UNSENT as it was already updated to ${request.state}") - } else { - request.state = state - cryptoStore.updateOutgoingRoomKeyRequest(request) - } - - sendOutgoingRoomKeyRequestsRunning.set(false) - startTimer() - } - - override fun onSuccess(data: Unit) { - Timber.v("## sendOutgoingRoomKeyRequest succeed") - onDone(ShareRequestState.SENT) - } - - override fun onFailure(failure: Throwable) { - Timber.e("## sendOutgoingRoomKeyRequest failed") - onDone(ShareRequestState.FAILED) - } - }) - } - - /** - * Given a OutgoingRoomKeyRequest, cancel it and delete the request record - * - * @param request the request - */ - private fun sendOutgoingRoomKeyRequestCancellation(request: OutgoingRoomKeyRequest) { - Timber.v("## sendOutgoingRoomKeyRequestCancellation() : Sending cancellation for key request for " + request.requestBody - + " to " + request.recipients - + " cancellation id " + request.cancellationTxnId) - - val roomKeyShareCancellation = ShareRequestCancellation( - requestingDeviceId = cryptoStore.getDeviceId(), - requestId = request.cancellationTxnId - ) - - sendMessageToDevices(roomKeyShareCancellation, request.recipients, request.cancellationTxnId, object : MatrixCallback { - private fun onDone() { - cryptoStore.deleteOutgoingRoomKeyRequest(request.requestId) - sendOutgoingRoomKeyRequestsRunning.set(false) - startTimer() - } - - override fun onSuccess(data: Unit) { - Timber.v("## sendOutgoingRoomKeyRequestCancellation() : done") - val resend = request.state === ShareRequestState.CANCELLATION_PENDING_AND_WILL_RESEND - - onDone() - - // Resend the request with a new ID - if (resend) { - sendRoomKeyRequest(request.requestBody, request.recipients) - } - } - - override fun onFailure(failure: Throwable) { - Timber.e("## sendOutgoingRoomKeyRequestCancellation failed") - onDone() - } - }) - } - - /** - * Send a SendToDeviceObject to a list of recipients - * - * @param message the message - * @param recipients the recipients. - * @param transactionId the transaction id - * @param callback the asynchronous callback. - */ - private fun sendMessageToDevices(message: Any, - recipients: List>, - transactionId: String?, - callback: MatrixCallback) { - val contentMap = MXUsersDevicesMap() - - for (recipient in recipients) { - // TODO Change this two hard coded key to something better - contentMap.setObject(recipient["userId"], recipient["deviceId"], message) - } - sendToDeviceTask - .configureWith(SendToDeviceTask.Params(EventType.ROOM_KEY_REQUEST, contentMap, transactionId)) { - this.callback = callback - this.callbackThread = TaskThread.CALLER - this.executionThread = TaskThread.CALLER - } - .executeBy(taskExecutor) - } - - companion object { - private const val SEND_KEY_REQUESTS_DELAY_MS = 500 - - private val BACKGROUND_HANDLER = createBackgroundHandler("OutgoingRoomKeyRequest") - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingSecretRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingSecretRequest.kt index a5305c1a25..1497796743 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingSecretRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingSecretRequest.kt @@ -16,21 +16,23 @@ package im.vector.matrix.android.internal.crypto +import com.squareup.moshi.JsonClass + /** * Represents an outgoing room key request */ +@JsonClass(generateAdapter = true) class OutgoingSecretRequest( // Secret Name - var secretName: String?, + val secretName: String?, // list of recipients for the request - override var recipients: List>, + override var recipients: Map>, // Unique id for this request. Used for both // an id within the request for later pairing with a cancellation, and for // the transaction id when sending the to_device messages to our local override var requestId: String, // current state of this request - override var state: ShareRequestState) : OutgoingShareRequest { + override var state: OutgoingGossipingRequestState) : OutgoingGossipingRequest { // transaction id for the cancellation, if any - override var cancellationTxnId: String? = null } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipRequestWorker.kt new file mode 100644 index 0000000000..cffd58cbf2 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipRequestWorker.kt @@ -0,0 +1,150 @@ +/* + * 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 + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.Data +import androidx.work.WorkerParameters +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.auth.data.Credentials +import im.vector.matrix.android.api.failure.shouldBeRetried +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.LocalEcho +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap +import im.vector.matrix.android.internal.crypto.model.rest.GossipingToDeviceObject +import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareRequest +import im.vector.matrix.android.internal.crypto.model.rest.SecretShareRequest +import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore +import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask +import im.vector.matrix.android.internal.database.mapper.ContentMapper +import im.vector.matrix.android.internal.worker.WorkerParamsFactory +import im.vector.matrix.android.internal.worker.getSessionComponent +import org.greenrobot.eventbus.EventBus +import timber.log.Timber +import javax.inject.Inject + +internal class SendGossipRequestWorker(context: Context, + params: WorkerParameters) + : CoroutineWorker(context, params) { + + @JsonClass(generateAdapter = true) + internal data class Params( + val sessionId: String, + val keyShareRequest: OutgoingRoomKeyRequest? = null, + val secretShareRequest: OutgoingSecretRequest? = null + ) + + @Inject lateinit var sendToDeviceTask: SendToDeviceTask + @Inject lateinit var cryptoStore: IMXCryptoStore + @Inject lateinit var eventBus: EventBus + @Inject lateinit var credentials: Credentials + + override suspend fun doWork(): Result { + + val errorOutputData = Data.Builder().putBoolean("failed", true).build() + val params = WorkerParamsFactory.fromData(inputData) + ?: return Result.success(errorOutputData) + + val sessionComponent = getSessionComponent(params.sessionId) + ?: return Result.success(errorOutputData).also { + // TODO, can this happen? should I update local echo? + Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}") + } + sessionComponent.inject(this) + + val localId = LocalEcho.createLocalEchoId() + val contentMap = MXUsersDevicesMap() + val eventType: String + val requestId: String + when { + params.keyShareRequest != null -> { + eventType = EventType.ROOM_KEY_REQUEST + requestId = params.keyShareRequest.requestId + val toDeviceContent = RoomKeyShareRequest( + requestingDeviceId = credentials.deviceId, + requestId = params.keyShareRequest.requestId, + action = GossipingToDeviceObject.ACTION_SHARE_REQUEST, + body = params.keyShareRequest.requestBody + ) + cryptoStore.saveGossipingEvent(Event( + type = eventType, + content = toDeviceContent.toContent(), + senderId = credentials.userId + ).also { + it.ageLocalTs = System.currentTimeMillis() + }) + + params.keyShareRequest.recipients.forEach { userToDeviceMap -> + userToDeviceMap.value.forEach { deviceId -> + contentMap.setObject(userToDeviceMap.key, deviceId, toDeviceContent) + } + } + } + params.secretShareRequest != null -> { + eventType = EventType.REQUEST_SECRET + requestId = params.secretShareRequest.requestId + val toDeviceContent = SecretShareRequest( + requestingDeviceId = credentials.deviceId, + requestId = params.secretShareRequest.requestId, + action = GossipingToDeviceObject.ACTION_SHARE_REQUEST, + secretName = params.secretShareRequest.secretName + ) + + cryptoStore.saveGossipingEvent(Event( + type = eventType, + content = toDeviceContent.toContent(), + senderId = credentials.userId + ).also { + it.ageLocalTs = System.currentTimeMillis() + }) + + params.secretShareRequest.recipients.forEach { userToDeviceMap -> + userToDeviceMap.value.forEach { deviceId -> + contentMap.setObject(userToDeviceMap.key, deviceId, toDeviceContent) + } + } + } + else -> { + return Result.success(errorOutputData).also { + Timber.e("Unknown empty gossiping request: ${params}") + } + } + } + try { + cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.SENDING) + sendToDeviceTask.execute( + SendToDeviceTask.Params( + eventType = eventType, + contentMap = contentMap, + transactionId = localId + ) + ) + cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.SENT) + return Result.success() + } catch (exception: Throwable) { + return if (exception.shouldBeRetried()) { + Result.retry() + } else { + cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.FAILED_TO_SEND) + Result.success(errorOutputData) + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt index e40caf4eed..78e587c0f1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt @@ -16,8 +16,10 @@ package im.vector.matrix.android.internal.crypto +import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter +import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmDecryptionFactory import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore @@ -25,6 +27,7 @@ import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import timber.log.Timber import javax.inject.Inject @@ -32,6 +35,7 @@ internal class ShareSecretCryptoProvider @Inject constructor( val messageEncrypter: MessageEncrypter, val sendToDeviceTask: SendToDeviceTask, val deviceListManager: DeviceListManager, + private val olmDecryptionFactory: MXOlmDecryptionFactory, val cryptoCoroutineScope: CoroutineScope, val cryptoStore: IMXCryptoStore, val coroutineDispatchers: MatrixCoroutineDispatchers @@ -61,4 +65,10 @@ internal class ShareSecretCryptoProvider @Inject constructor( } } } + + fun decryptEvent(event: Event): MXEventDecryptionResult { + return runBlocking(coroutineDispatchers.crypto) { + olmDecryptionFactory.create().decryptEvent(event, ShareSecretCryptoProvider::class.java.name ?: "") + } + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/MegolmSessionDataImporter.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/MegolmSessionDataImporter.kt index 6f41116b90..cac4659ae5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/MegolmSessionDataImporter.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/MegolmSessionDataImporter.kt @@ -20,7 +20,7 @@ import androidx.annotation.WorkerThread import im.vector.matrix.android.api.listeners.ProgressListener import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MegolmSessionData -import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager +import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.RoomDecryptorProvider import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody @@ -30,7 +30,7 @@ import javax.inject.Inject internal class MegolmSessionDataImporter @Inject constructor(private val olmDevice: MXOlmDevice, private val roomDecryptorProvider: RoomDecryptorProvider, - private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager, + private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager, private val cryptoStore: IMXCryptoStore) { /** @@ -73,7 +73,7 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi sessionId = megolmSessionData.sessionId ) - outgoingRoomKeyRequestManager.cancelRoomKeyRequest(roomKeyRequestBody) + outgoingGossipingRequestManager.cancelRoomKeyRequest(roomKeyRequestBody) // Have another go at decrypting events sent with this session decrypting.onNewSession(megolmSessionData.senderKey!!, sessionId!!) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXDecrypting.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXDecrypting.kt index ff740fb1f9..e9176ad6d9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXDecrypting.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/IMXDecrypting.kt @@ -68,4 +68,6 @@ internal interface IMXDecrypting { fun shareKeysWithDevice(request: IncomingRoomKeyRequest) {} fun shareSecretWithDevice(request: IncomingSecretShareRequest, secretValue : String) {} + + fun requestKeysForEvent(event: Event) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index c8a1d628d7..36b4e40957 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -26,7 +26,7 @@ import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.NewSessionListener -import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager +import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting @@ -46,7 +46,7 @@ import timber.log.Timber internal class MXMegolmDecryption(private val userId: String, private val olmDevice: MXOlmDevice, private val deviceListManager: DeviceListManager, - private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager, + private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager, private val messageEncrypter: MessageEncrypter, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, private val cryptoStore: IMXCryptoStore, @@ -144,25 +144,26 @@ internal class MXMegolmDecryption(private val userId: String, * * @param event the event */ - private fun requestKeysForEvent(event: Event) { - val sender = event.senderId!! - val encryptedEventContent = event.content.toModel()!! + override fun requestKeysForEvent(event: Event) { + val sender = event.senderId ?: return + val encryptedEventContent = event.content.toModel() + val senderDevice = encryptedEventContent?.deviceId ?: return - val recipients = ArrayList>() - - val selfMap = HashMap() - // TODO Replace this hard coded keys (see OutgoingRoomKeyRequestManager) - selfMap["userId"] = userId - selfMap["deviceId"] = "*" - recipients.add(selfMap) - - if (sender != userId) { - val senderMap = HashMap() - senderMap["userId"] = sender - senderMap["deviceId"] = encryptedEventContent.deviceId!! - recipients.add(senderMap) + val recipients = if (event.senderId != userId) { + mapOf( + userId to listOf("*") + ) + } else { + // for the case where you share the key with a device that has a broken olm session + // The other user might Re-shares a megolm session key with devices if the key has already been + // sent to them. + mapOf( + userId to listOf("*"), + sender to listOf(senderDevice) + ) } + val requestBody = RoomKeyRequestBody( roomId = event.roomId, algorithm = encryptedEventContent.algorithm, @@ -170,7 +171,7 @@ internal class MXMegolmDecryption(private val userId: String, sessionId = encryptedEventContent.sessionId ) - outgoingRoomKeyRequestManager.sendRoomKeyRequest(requestBody, recipients) + outgoingGossipingRequestManager.sendRoomKeyRequest(requestBody, recipients) } /** @@ -271,7 +272,7 @@ internal class MXMegolmDecryption(private val userId: String, senderKey = senderKey ) - outgoingRoomKeyRequestManager.cancelRoomKeyRequest(content) + outgoingGossipingRequestManager.cancelRoomKeyRequest(content) onNewSession(senderKey, roomKeyContent.sessionId) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt index 7cddd27779..e8044186d8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryptionFactory.kt @@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.crypto.algorithms.megolm import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.MXOlmDevice -import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager +import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore @@ -32,7 +32,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor( @UserId private val userId: String, private val olmDevice: MXOlmDevice, private val deviceListManager: DeviceListManager, - private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager, + private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager, private val messageEncrypter: MessageEncrypter, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, private val cryptoStore: IMXCryptoStore, @@ -46,7 +46,7 @@ internal class MXMegolmDecryptionFactory @Inject constructor( userId, olmDevice, deviceListManager, - outgoingRoomKeyRequestManager, + outgoingGossipingRequestManager, messageEncrypter, ensureOlmSessionsForDevicesAction, cryptoStore, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmDecryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmDecryption.kt index e0454aea0d..0a8ef3993b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmDecryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/olm/MXOlmDecryption.kt @@ -210,4 +210,8 @@ internal class MXOlmDecryption( return res["payload"] } + + override fun requestKeysForEvent(event: Event) { + // nop + } } 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 6251f72418..661af7fb49 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 @@ -25,7 +25,7 @@ import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder -import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager +import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey import im.vector.matrix.android.internal.crypto.model.KeyUsage import im.vector.matrix.android.internal.crypto.model.rest.UploadSignatureQueryBuilder @@ -43,6 +43,7 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.withoutPrefix import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.greenrobot.eventbus.EventBus import org.matrix.olm.OlmPkSigning import org.matrix.olm.OlmUtility @@ -62,7 +63,7 @@ internal class DefaultCrossSigningService @Inject constructor( private val taskExecutor: TaskExecutor, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val cryptoCoroutineScope: CoroutineScope, - private val outgoingRoomKeyRequestManager: OutgoingRoomKeyRequestManager, + private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager, private val eventBus: EventBus) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener { private var olmUtility: OlmUtility? = null @@ -299,6 +300,58 @@ internal class DefaultCrossSigningService @Inject constructor( cryptoStore.clearOtherUserTrust() } + override fun onSecretSSKGossip(sskPrivateKey: String): Boolean { + Timber.i("## CrossSigning - onSecretSSKGossip") + return runBlocking(coroutineDispatchers.crypto) { + val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return@runBlocking false + + sskPrivateKey.fromBase64NoPadding() + .let { privateKeySeed -> + val pkSigning = OlmPkSigning() + try { + if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { + selfSigningPkSigning?.releaseSigning() + selfSigningPkSigning = pkSigning + Timber.i("## CrossSigning - Loading SSK success") + cryptoStore.storePrivateKeysInfo(null, null, sskPrivateKey) + return@runBlocking true + } else { + pkSigning.releaseSigning() + } + } catch (failure: Throwable) { + pkSigning.releaseSigning() + } + } + return@runBlocking false + } + } + + override fun onSecretUSKGossip(uskPrivateKey: String): Boolean { + Timber.i("## CrossSigning - onSecretUSKGossip") + return runBlocking(coroutineDispatchers.crypto) { + val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return@runBlocking false + + uskPrivateKey.fromBase64NoPadding() + .let { privateKeySeed -> + val pkSigning = OlmPkSigning() + try { + if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { + userPkSigning?.releaseSigning() + userPkSigning = pkSigning + Timber.i("## CrossSigning - Loading USK success") + cryptoStore.storePrivateKeysInfo(null, uskPrivateKey, null) + return@runBlocking true + } else { + pkSigning.releaseSigning() + } + } catch (failure: Throwable) { + pkSigning.releaseSigning() + } + } + return@runBlocking false + } + } + override fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?, uskKeyPrivateKey: String?, sskPrivateKey: String? @@ -549,97 +602,103 @@ internal class DefaultCrossSigningService @Inject constructor( } override fun trustUser(otherUserId: String, callback: MatrixCallback) { - Timber.d("## CrossSigning - Mark user $userId as trusted ") - // We should have this user keys - val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey() - if (otherMasterKeys == null) { - callback.onFailure(Throwable("## CrossSigning - Other master signing key is not known")) - return - } - val myKeys = getUserCrossSigningKeys(userId) - if (myKeys == null) { - callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account")) - return - } - val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey - if (userPubKey == null || userPkSigning == null) { - callback.onFailure(Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey")) - return - } + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + Timber.d("## CrossSigning - Mark user $userId as trusted ") + // We should have this user keys + val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey() + if (otherMasterKeys == null) { + callback.onFailure(Throwable("## CrossSigning - Other master signing key is not known")) + return@launch + } + val myKeys = getUserCrossSigningKeys(userId) + if (myKeys == null) { + callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account")) + return@launch + } + val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey + if (userPubKey == null || userPkSigning == null) { + callback.onFailure(Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey")) + return@launch + } - // Sign the other MasterKey with our UserSigning key - val newSignature = JsonCanonicalizer.getCanonicalJson(Map::class.java, - otherMasterKeys.signalableJSONDictionary()).let { userPkSigning?.sign(it) } + // Sign the other MasterKey with our UserSigning key + val newSignature = JsonCanonicalizer.getCanonicalJson(Map::class.java, + otherMasterKeys.signalableJSONDictionary()).let { userPkSigning?.sign(it) } - if (newSignature == null) { - // race?? - callback.onFailure(Throwable("## CrossSigning - Failed to sign")) - return + if (newSignature == null) { + // race?? + callback.onFailure(Throwable("## CrossSigning - Failed to sign")) + return@launch + } + + cryptoStore.setUserKeysAsTrusted(otherUserId, true) + // TODO update local copy with new signature directly here? kind of local echo of trust? + + Timber.d("## CrossSigning - Upload signature of $userId MSK signed by USK") + val uploadQuery = UploadSignatureQueryBuilder() + .withSigningKeyInfo(otherMasterKeys.copyForSignature(userId, userPubKey, newSignature)) + .build() + uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) { + this.executionThread = TaskThread.CRYPTO + this.callback = callback + }.executeBy(taskExecutor) } - - cryptoStore.setUserKeysAsTrusted(otherUserId, true) - // TODO update local copy with new signature directly here? kind of local echo of trust? - - Timber.d("## CrossSigning - Upload signature of $userId MSK signed by USK") - val uploadQuery = UploadSignatureQueryBuilder() - .withSigningKeyInfo(otherMasterKeys.copyForSignature(userId, userPubKey, newSignature)) - .build() - uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) { - this.executionThread = TaskThread.CRYPTO - this.callback = callback - }.executeBy(taskExecutor) } override fun markMyMasterKeyAsTrusted() { - cryptoStore.markMyMasterKeyAsLocallyTrusted(true) - checkSelfTrust() + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + cryptoStore.markMyMasterKeyAsLocallyTrusted(true) + checkSelfTrust() + } } override fun trustDevice(deviceId: String, callback: MatrixCallback) { - // This device should be yours - val device = cryptoStore.getUserDevice(userId, deviceId) - if (device == null) { - callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours")) - return + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + // This device should be yours + val device = cryptoStore.getUserDevice(userId, deviceId) + if (device == null) { + callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours")) + return@launch + } + + val myKeys = getUserCrossSigningKeys(userId) + if (myKeys == null) { + callback.onFailure(Throwable("CrossSigning is not setup for this account")) + return@launch + } + + val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey + if (ssPubKey == null || selfSigningPkSigning == null) { + callback.onFailure(Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey")) + return@launch + } + + // Sign with self signing + val newSignature = selfSigningPkSigning?.sign(device.canonicalSignable()) + + if (newSignature == null) { + // race?? + callback.onFailure(Throwable("Failed to sign")) + return@launch + } + val toUpload = device.copy( + signatures = mapOf( + userId + to + mapOf( + "ed25519:$ssPubKey" to newSignature + ) + ) + ) + + val uploadQuery = UploadSignatureQueryBuilder() + .withDeviceInfo(toUpload) + .build() + uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) { + this.executionThread = TaskThread.CRYPTO + this.callback = callback + }.executeBy(taskExecutor) } - - val myKeys = getUserCrossSigningKeys(userId) - if (myKeys == null) { - callback.onFailure(Throwable("CrossSigning is not setup for this account")) - return - } - - val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey - if (ssPubKey == null || selfSigningPkSigning == null) { - callback.onFailure(Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey")) - return - } - - // Sign with self signing - val newSignature = selfSigningPkSigning?.sign(device.canonicalSignable()) - - if (newSignature == null) { - // race?? - callback.onFailure(Throwable("Failed to sign")) - return - } - val toUpload = device.copy( - signatures = mapOf( - userId - to - mapOf( - "ed25519:$ssPubKey" to newSignature - ) - ) - ) - - val uploadQuery = UploadSignatureQueryBuilder() - .withDeviceInfo(toUpload) - .build() - uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) { - this.executionThread = TaskThread.CRYPTO - this.callback = callback - }.executeBy(taskExecutor) } override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult { @@ -738,7 +797,7 @@ internal class DefaultCrossSigningService @Inject constructor( // If it's me, recheck trust of all users and devices? val users = ArrayList() if (otherUserId == userId && currentTrust != trusted) { - reRequestAllPendingRoomKeyRequest() +// reRequestAllPendingRoomKeyRequest() cryptoStore.updateUsersTrust { users.add(it) checkUserTrust(it).isVerified() @@ -755,16 +814,18 @@ internal class DefaultCrossSigningService @Inject constructor( } } - private fun reRequestAllPendingRoomKeyRequest() { - Timber.d("## CrossSigning - reRequest pending outgoing room key requests") - cryptoStore.getOutgoingRoomKeyRequests().forEach { - it.requestBody?.let { requestBody -> - if (cryptoStore.getInboundGroupSession(requestBody.sessionId ?: "", requestBody.senderKey ?: "") == null) { - outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody) - } else { - outgoingRoomKeyRequestManager.cancelRoomKeyRequest(requestBody) - } - } - } - } +// private fun reRequestAllPendingRoomKeyRequest() { +// cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { +// Timber.d("## CrossSigning - reRequest pending outgoing room key requests") +// cryptoStore.getOutgoingRoomKeyRequests().forEach { +// it.requestBody?.let { requestBody -> +// if (cryptoStore.getInboundGroupSession(requestBody.sessionId ?: "", requestBody.senderKey ?: "") == null) { +// outgoingRoomKeyRequestManager.resendRoomKeyRequest(requestBody) +// } else { +// outgoingRoomKeyRequestManager.cancelRoomKeyRequest(requestBody) +// } +// } +// } +// } +// } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/CryptoDeviceInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/CryptoDeviceInfo.kt index e3e8f3de27..b124f7590e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/CryptoDeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/CryptoDeviceInfo.kt @@ -33,7 +33,7 @@ data class CryptoDeviceInfo( ) : CryptoInfo { val isVerified: Boolean - get() = trustLevel?.isVerified() ?: false + get() = trustLevel?.isVerified() == true val isUnknown: Boolean get() = trustLevel == null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/GossipingToDeviceObject.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/GossipingToDeviceObject.kt index def8c7f620..deaccdef16 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/GossipingToDeviceObject.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/GossipingToDeviceObject.kt @@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass * Interface representing an room key action request * Note: this class cannot be abstract because of [org.matrix.androidsdk.core.JsonUtils.toRoomKeyShare] */ -internal interface GossipingToDeviceObject : SendToDeviceObject { +interface GossipingToDeviceObject : SendToDeviceObject { val action: String? diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyRequestBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyRequestBody.kt index 3eb6600e5e..5def4ae2a3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyRequestBody.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyRequestBody.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.di.MoshiProvider /** * Class representing an room key request body content @@ -35,4 +36,16 @@ data class RoomKeyRequestBody( @Json(name = "session_id") val sessionId: String? = null -) +) { + fun toJson(): String { + return MoshiProvider.providesMoshi().adapter(RoomKeyRequestBody::class.java).toJson(this) + } + + companion object { + fun fromJson(json: String?): RoomKeyRequestBody? { + return json?.let { MoshiProvider.providesMoshi().adapter(RoomKeyRequestBody::class.java).fromJson(it) } + } + } +} + + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareRequest.kt index e61743251c..c2fc6fe96b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyShareRequest.kt @@ -23,7 +23,7 @@ import com.squareup.moshi.JsonClass * Class representing a room key request content */ @JsonClass(generateAdapter = true) -internal data class RoomKeyShareRequest( +data class RoomKeyShareRequest( @Json(name = "action") override val action: String? = GossipingToDeviceObject.ACTION_SHARE_REQUEST, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt index 649f60887d..1bd55dd35d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt @@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.securestorage.SharedSecretStorageSer 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.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2 import im.vector.matrix.android.internal.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2 import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64 @@ -41,6 +42,7 @@ import im.vector.matrix.android.internal.crypto.keysbackup.generatePrivateKeyWit import im.vector.matrix.android.internal.crypto.keysbackup.util.computeRecoveryKey import im.vector.matrix.android.internal.crypto.tools.HkdfSha256 import im.vector.matrix.android.internal.crypto.tools.withOlmDecryption +import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.CoroutineScope @@ -55,7 +57,9 @@ import javax.inject.Inject import kotlin.experimental.and internal class DefaultSharedSecretStorageService @Inject constructor( + @UserId private val userId: String, private val accountDataService: AccountDataService, + private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val cryptoCoroutineScope: CoroutineScope ) : SharedSecretStorageService { @@ -429,4 +433,11 @@ internal class DefaultSharedSecretStorageService @Inject constructor( return IntegrityResult.Success(keyInfo.content.passphrase != null) } + + override fun requestSecret(name: String, myOtherDeviceId: String) { + outgoingGossipingRequestManager.sendSecretShareRequest( + name, + mapOf(userId to listOf(myOtherDeviceId)) + ) + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt index b05b6cfa59..c5d09bc111 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt @@ -19,13 +19,15 @@ package im.vector.matrix.android.internal.crypto.store import androidx.lifecycle.LiveData import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo +import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.util.Optional -import im.vector.matrix.android.internal.crypto.IncomingShareRequestCommon +import im.vector.matrix.android.internal.crypto.GossipingRequestState import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest -import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest +import im.vector.matrix.android.internal.crypto.IncomingShareRequestCommon import im.vector.matrix.android.internal.crypto.NewSessionListener +import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestState import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest -import im.vector.matrix.android.internal.crypto.ShareRequestState +import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper @@ -118,7 +120,10 @@ internal interface IMXCryptoStore { * @return the pending IncomingRoomKeyRequest requests */ fun getPendingIncomingRoomKeyRequests(): List - fun getPendingIncomingSecretShareRequests(): List + + fun getPendingIncomingGossipingRequests(): List + fun storeIncomingGossipingRequest(request: IncomingShareRequestCommon, ageLocalTS: Long?) +// fun getPendingIncomingSecretShareRequests(): List /** * Indicate if the store contains data for the passed account. @@ -190,8 +195,8 @@ internal interface IMXCryptoStore { fun storeUserDevices(userId: String, devices: Map?) fun storeUserCrossSigningKeys(userId: String, masterKey: CryptoCrossSigningKey?, - selfSigningKey: CryptoCrossSigningKey?, - userSigningKey: CryptoCrossSigningKey?) + selfSigningKey: CryptoCrossSigningKey?, + userSigningKey: CryptoCrossSigningKey?) /** * Retrieve the known devices for a user. @@ -209,6 +214,7 @@ internal interface IMXCryptoStore { // TODO temp fun getLiveDeviceList(): LiveData> + /** * Store the crypto algorithm for a room. * @@ -350,45 +356,49 @@ internal interface IMXCryptoStore { * @param request the request * @return either the same instance as passed in, or the existing one. */ - fun getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): OutgoingRoomKeyRequest? + fun getOrAddOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map>): OutgoingRoomKeyRequest? + fun getOrAddOutgoingSecretShareRequest(secretName: String, recipients: Map>): OutgoingSecretRequest? + + fun saveGossipingEvent(event: Event) /** * Look for room key requests by state. * * @param states the states * @return an OutgoingRoomKeyRequest or null */ - fun getOutgoingRoomKeyRequestByState(states: Set): OutgoingRoomKeyRequest? +// fun getOutgoingRoomKeyRequestByState(states: Set): OutgoingRoomKeyRequest? +// fun getOutgoingSecretShareRequestByState(states: Set): OutgoingSecretRequest? /** * Update an existing outgoing request. * * @param request the request */ - fun updateOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest) +// fun updateOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest) /** * Delete an outgoing room key request. * * @param transactionId the transaction id. */ - fun deleteOutgoingRoomKeyRequest(transactionId: String) +// fun deleteOutgoingRoomKeyRequest(transactionId: String) /** * Store an incomingRoomKeyRequest instance * * @param incomingRoomKeyRequest the incoming key request */ - fun storeIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequest?) +// fun storeIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequest?) /** * Delete an incomingRoomKeyRequest instance * * @param incomingRoomKeyRequest the incoming key request */ - fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingShareRequestCommon) +// fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingShareRequestCommon) - fun deleteIncomingSecretRequest(request: IncomingSecretShareRequest) + fun updateGossipingRequestState(request: IncomingShareRequestCommon, state: GossipingRequestState) /** * Search an IncomingRoomKeyRequest @@ -400,6 +410,8 @@ internal interface IMXCryptoStore { */ fun getIncomingRoomKeyRequest(userId: String, deviceId: String, requestId: String): IncomingRoomKeyRequest? + fun updateOutgoingGossipingRequestState(requestId: String, state: OutgoingGossipingRequestState) + fun addNewSessionListener(listener: NewSessionListener) fun removeSessionListener(listener: NewSessionListener) @@ -411,20 +423,21 @@ internal interface IMXCryptoStore { /** * Gets the current crosssigning info */ - fun getMyCrossSigningInfo() : MXCrossSigningInfo? + fun getMyCrossSigningInfo(): MXCrossSigningInfo? + fun setMyCrossSigningInfo(info: MXCrossSigningInfo?) - fun getCrossSigningInfo(userId: String) : MXCrossSigningInfo? - fun getLiveCrossSigningInfo(userId: String) : LiveData> + fun getCrossSigningInfo(userId: String): MXCrossSigningInfo? + fun getLiveCrossSigningInfo(userId: String): LiveData> fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?) fun markMyMasterKeyAsLocallyTrusted(trusted: Boolean) fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) - fun getCrossSigningPrivateKeys() : PrivateKeysInfo? + fun getCrossSigningPrivateKeys(): PrivateKeysInfo? fun setUserKeysAsTrusted(userId: String, trusted: Boolean = true) - fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified : Boolean) + fun setDeviceTrust(userId: String, deviceId: String, crossSignedVerified: Boolean, locallyVerified: Boolean) fun clearOtherUserTrust() @@ -432,5 +445,8 @@ internal interface IMXCryptoStore { // Dev tools - fun getOutgoingRoomKeyRequests() : List + fun getOutgoingRoomKeyRequests(): List + fun getOutgoingSecretRequest(secretName: String): OutgoingSecretRequest? + fun getIncomingRoomKeyRequests(): List + fun getGossipingEventsTrail(): List } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/Helper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/Helper.kt index 81988fe209..642c466e42 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/Helper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/Helper.kt @@ -59,7 +59,12 @@ fun doRealmQueryAndCopyList(realmConfiguration: RealmConfigura */ fun doRealmTransaction(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { Realm.getInstance(realmConfiguration).use { realm -> - realm.executeTransaction { action.invoke(realm) } + realm.executeTransaction { action.invoke(it) } + } +} +fun doRealmTransactionAsync(realmConfiguration: RealmConfiguration, action: (Realm) -> Unit) { + Realm.getInstance(realmConfiguration).use { realm -> + realm.executeTransactionAsync { action.invoke(it) } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index 1b4410d48c..83b8459d72 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -21,14 +21,21 @@ import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.LocalEcho +import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.api.util.toOptional +import im.vector.matrix.android.internal.crypto.GossipRequestType +import im.vector.matrix.android.internal.crypto.GossipingRequestState import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest import im.vector.matrix.android.internal.crypto.IncomingShareRequestCommon import im.vector.matrix.android.internal.crypto.NewSessionListener +import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestState import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest -import im.vector.matrix.android.internal.crypto.ShareRequestState +import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo @@ -46,18 +53,17 @@ import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntity import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntity import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityFields -import im.vector.matrix.android.internal.crypto.store.db.model.IncomingRoomKeyRequestEntity -import im.vector.matrix.android.internal.crypto.store.db.model.IncomingRoomKeyRequestEntityFields -import im.vector.matrix.android.internal.crypto.store.db.model.IncomingSecretRequestEntity -import im.vector.matrix.android.internal.crypto.store.db.model.IncomingSecretRequestEntityFields +import im.vector.matrix.android.internal.crypto.store.db.model.GossipingEventEntity +import im.vector.matrix.android.internal.crypto.store.db.model.IncomingGossipingRequestEntity +import im.vector.matrix.android.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntity import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity import im.vector.matrix.android.internal.crypto.store.db.model.OlmInboundGroupSessionEntity import im.vector.matrix.android.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.OlmSessionEntity import im.vector.matrix.android.internal.crypto.store.db.model.OlmSessionEntityFields -import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingRoomKeyRequestEntity -import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingRoomKeyRequestEntityFields +import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingGossipingRequestEntity +import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.TrustLevelEntity import im.vector.matrix.android.internal.crypto.store.db.model.UserEntity import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields @@ -66,7 +72,9 @@ import im.vector.matrix.android.internal.crypto.store.db.query.delete import im.vector.matrix.android.internal.crypto.store.db.query.get import im.vector.matrix.android.internal.crypto.store.db.query.getById import im.vector.matrix.android.internal.crypto.store.db.query.getOrCreate +import im.vector.matrix.android.internal.database.mapper.ContentMapper import im.vector.matrix.android.internal.di.CryptoDatabase +import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.session.SessionScope import io.realm.Realm import io.realm.RealmConfiguration @@ -801,152 +809,334 @@ internal class RealmCryptoStore @Inject constructor( } override fun getOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody): OutgoingRoomKeyRequest? { - return doRealmQueryAndCopy(realmConfiguration) { - it.where() - .equalTo(OutgoingRoomKeyRequestEntityFields.REQUEST_BODY_ALGORITHM, requestBody.algorithm) - .equalTo(OutgoingRoomKeyRequestEntityFields.REQUEST_BODY_ROOM_ID, requestBody.roomId) - .equalTo(OutgoingRoomKeyRequestEntityFields.REQUEST_BODY_SENDER_KEY, requestBody.senderKey) - .equalTo(OutgoingRoomKeyRequestEntityFields.REQUEST_BODY_SESSION_ID, requestBody.sessionId) - .findFirst() + return monarchy.fetchAllCopiedSync { realm -> + realm.where() + .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) + }.mapNotNull { + it.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest + }.firstOrNull { + it.requestBody?.algorithm == requestBody.algorithm + it.requestBody?.roomId == requestBody.roomId + it.requestBody?.senderKey == requestBody.senderKey + it.requestBody?.sessionId == requestBody.sessionId } - ?.toOutgoingRoomKeyRequest() } - override fun getOrAddOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest): OutgoingRoomKeyRequest? { - if (request.requestBody == null) { - return null - } + override fun getOutgoingSecretRequest(secretName: String): OutgoingSecretRequest? { +// return monarchy.fetchAllCopiedSync { realm -> +//// realm.where() +//// .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.SECRET.name) +//// .equalTo(GossipingEventEntityFields.SENDER, credentials.userId) +//// }.mapNotNull { +//// ContentMapper.map(it.content)?.toModel() +//// }.firstOrNull { +//// it.secretName == secretName +//// } + TODO("not implemented") + } - val existingOne = getOutgoingRoomKeyRequest(request.requestBody!!) - - if (existingOne != null) { - return existingOne + override fun getIncomingRoomKeyRequests(): List { + return monarchy.fetchAllCopiedSync { realm -> + realm.where() + .equalTo(IncomingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) + }.mapNotNull { + it.toIncomingGossipingRequest() as? IncomingRoomKeyRequest } + } + + override fun getGossipingEventsTrail(): List { + return monarchy.fetchAllCopiedSync { realm -> + realm.where() + }.map { + it.toModel() + } + } + + override fun getOrAddOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map>): OutgoingRoomKeyRequest? { + // Insert the request and return the one passed in parameter + var request: OutgoingRoomKeyRequest? = null + doRealmTransaction(realmConfiguration) { realm -> + + val existing = realm.where() + .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) + .findAll() + .mapNotNull { + it.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest + }.firstOrNull { + it.requestBody?.algorithm == requestBody.algorithm + && it.requestBody?.sessionId == requestBody.sessionId + && it.requestBody?.senderKey == requestBody.senderKey + && it.requestBody?.roomId == requestBody.roomId + } + + if (existing == null) { + request = realm.createObject(OutgoingGossipingRequestEntity::class.java).apply { + this.requestId = LocalEcho.createLocalEchoId() + this.setRecipients(recipients) + this.requestState = OutgoingGossipingRequestState.UNSENT + this.type = GossipRequestType.KEY + this.requestedInfoStr = requestBody.toJson() + }.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest + } else { + request = existing + } + + } + return request + } + + override fun getOrAddOutgoingSecretShareRequest(secretName: String, recipients: Map>): OutgoingSecretRequest? { + + var request: OutgoingSecretRequest? = null // Insert the request and return the one passed in parameter - doRealmTransaction(realmConfiguration) { - it.createObject(OutgoingRoomKeyRequestEntity::class.java, request.requestId).apply { - putRequestBody(request.requestBody) - putRecipients(request.recipients) - cancellationTxnId = request.cancellationTxnId - state = request.state.ordinal + doRealmTransaction(realmConfiguration) { realm -> + val existing = realm.where() + .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.SECRET.name) + .equalTo(OutgoingGossipingRequestEntityFields.REQUESTED_INFO_STR, secretName) + .findAll() + .mapNotNull { + it.toOutgoingGossipingRequest() as? OutgoingSecretRequest + }.firstOrNull() + if (existing == null) { + request = realm.createObject(OutgoingGossipingRequestEntity::class.java).apply { + this.type = GossipRequestType.SECRET + setRecipients(recipients) + this.requestState = OutgoingGossipingRequestState.UNSENT + this.requestId = LocalEcho.createLocalEchoId() + this.requestedInfoStr = secretName + }.toOutgoingGossipingRequest() as? OutgoingSecretRequest + } else { + request = existing } } return request } - override fun getOutgoingRoomKeyRequestByState(states: Set): OutgoingRoomKeyRequest? { - val statesIndex = states.map { it.ordinal }.toTypedArray() - return doRealmQueryAndCopy(realmConfiguration) { - it.where() - .`in`(OutgoingRoomKeyRequestEntityFields.STATE, statesIndex) - .findFirst() + override fun saveGossipingEvent(event: Event) { + val now = System.currentTimeMillis() + val ageLocalTs = event.unsignedData?.age?.let { now - it } ?: now + val entity = GossipingEventEntity( + type = event.type, + sender = event.senderId, + ageLocalTs = ageLocalTs, + content = ContentMapper.map(event.content) + ).apply { + sendState = SendState.SYNCED + decryptionResultJson = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).toJson(event.mxDecryptionResult) + decryptionErrorCode = event.mCryptoError?.name } - ?.toOutgoingRoomKeyRequest() - } - - override fun updateOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest) { - doRealmTransaction(realmConfiguration) { - val obj = OutgoingRoomKeyRequestEntity().apply { - requestId = request.requestId - cancellationTxnId = request.cancellationTxnId - state = request.state.ordinal - putRecipients(request.recipients) - putRequestBody(request.requestBody) - } - - it.insertOrUpdate(obj) + doRealmTransaction(realmConfiguration) { realm -> + realm.insertOrUpdate(entity) } } - override fun deleteOutgoingRoomKeyRequest(transactionId: String) { - doRealmTransaction(realmConfiguration) { - it.where() - .equalTo(OutgoingRoomKeyRequestEntityFields.REQUEST_ID, transactionId) - .findFirst() - ?.deleteFromRealm() +// override fun getOutgoingRoomKeyRequestByState(states: Set): OutgoingRoomKeyRequest? { +// val statesIndex = states.map { it.ordinal }.toTypedArray() +// return doRealmQueryAndCopy(realmConfiguration) { realm -> +// realm.where() +// .equalTo(GossipingEventEntityFields.SENDER, credentials.userId) +// .findAll() +// .filter {entity -> +// states.any { it == entity.requestState} +// } +// }.mapNotNull { +// ContentMapper.map(it.content)?.toModel() +// } +// ?.toOutgoingRoomKeyRequest() +// } +// +// override fun getOutgoingSecretShareRequestByState(states: Set): OutgoingSecretRequest? { +// val statesIndex = states.map { it.ordinal }.toTypedArray() +// return doRealmQueryAndCopy(realmConfiguration) { +// it.where() +// .`in`(OutgoingSecretRequestEntityFields.STATE, statesIndex) +// .findFirst() +// } +// ?.toOutgoingSecretRequest() +// } + +// override fun updateOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest) { +// doRealmTransaction(realmConfiguration) { +// val obj = OutgoingRoomKeyRequestEntity().apply { +// requestId = request.requestId +// cancellationTxnId = request.cancellationTxnId +// state = request.state.ordinal +// putRecipients(request.recipients) +// putRequestBody(request.requestBody) +// } +// +// it.insertOrUpdate(obj) +// } +// } + +// override fun deleteOutgoingRoomKeyRequest(transactionId: String) { +// doRealmTransaction(realmConfiguration) { +// it.where() +// .equalTo(OutgoingRoomKeyRequestEntityFields.REQUEST_ID, transactionId) +// .findFirst() +// ?.deleteFromRealm() +// } +// } + +// override fun storeIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequest?) { +// if (incomingRoomKeyRequest == null) { +// return +// } +// +// doRealmTransaction(realmConfiguration) { +// // Delete any previous store request with the same parameters +// it.where() +// .equalTo(IncomingRoomKeyRequestEntityFields.USER_ID, incomingRoomKeyRequest.userId) +// .equalTo(IncomingRoomKeyRequestEntityFields.DEVICE_ID, incomingRoomKeyRequest.deviceId) +// .equalTo(IncomingRoomKeyRequestEntityFields.REQUEST_ID, incomingRoomKeyRequest.requestId) +// .findAll() +// .deleteAllFromRealm() +// +// // Then store it +// it.createObject(IncomingRoomKeyRequestEntity::class.java).apply { +// userId = incomingRoomKeyRequest.userId +// deviceId = incomingRoomKeyRequest.deviceId +// requestId = incomingRoomKeyRequest.requestId +// putRequestBody(incomingRoomKeyRequest.requestBody) +// } +// } +// } + +// override fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingShareRequestCommon) { +// doRealmTransaction(realmConfiguration) { +// it.where() +// .equalTo(GossipingEventEntityFields.TYPE, EventType.ROOM_KEY_REQUEST) +// .notEqualTo(GossipingEventEntityFields.SENDER, credentials.userId) +// .findAll() +// .filter { +// ContentMapper.map(it.content).toModel()?.let { +// +// } +// } +//// .equalTo(IncomingRoomKeyRequestEntityFields.USER_ID, incomingRoomKeyRequest.userId) +//// .equalTo(IncomingRoomKeyRequestEntityFields.DEVICE_ID, incomingRoomKeyRequest.deviceId) +//// .equalTo(IncomingRoomKeyRequestEntityFields.REQUEST_ID, incomingRoomKeyRequest.requestId) +//// .findAll() +//// .deleteAllFromRealm() +// } +// } + + override fun updateGossipingRequestState(request: IncomingShareRequestCommon, state: GossipingRequestState) { + doRealmTransaction(realmConfiguration) { realm -> + realm.where() + .equalTo(IncomingGossipingRequestEntityFields.OTHER_USER_ID, request.userId) + .equalTo(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, request.deviceId) + .equalTo(IncomingGossipingRequestEntityFields.REQUEST_ID, request.requestId) + .findAll().forEach { + it.requestState = state + } } } - override fun storeIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequest?) { - if (incomingRoomKeyRequest == null) { - return - } - - doRealmTransaction(realmConfiguration) { - // Delete any previous store request with the same parameters - it.where() - .equalTo(IncomingRoomKeyRequestEntityFields.USER_ID, incomingRoomKeyRequest.userId) - .equalTo(IncomingRoomKeyRequestEntityFields.DEVICE_ID, incomingRoomKeyRequest.deviceId) - .equalTo(IncomingRoomKeyRequestEntityFields.REQUEST_ID, incomingRoomKeyRequest.requestId) - .findAll() - .deleteAllFromRealm() - - // Then store it - it.createObject(IncomingRoomKeyRequestEntity::class.java).apply { - userId = incomingRoomKeyRequest.userId - deviceId = incomingRoomKeyRequest.deviceId - requestId = incomingRoomKeyRequest.requestId - putRequestBody(incomingRoomKeyRequest.requestBody) - } - } - } - - override fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingShareRequestCommon) { - doRealmTransaction(realmConfiguration) { - it.where() - .equalTo(IncomingRoomKeyRequestEntityFields.USER_ID, incomingRoomKeyRequest.userId) - .equalTo(IncomingRoomKeyRequestEntityFields.DEVICE_ID, incomingRoomKeyRequest.deviceId) - .equalTo(IncomingRoomKeyRequestEntityFields.REQUEST_ID, incomingRoomKeyRequest.requestId) - .findAll() - .deleteAllFromRealm() - } - } - - override fun deleteIncomingSecretRequest(request: IncomingSecretShareRequest) { - doRealmTransaction(realmConfiguration) { - it.where() - .equalTo(IncomingSecretRequestEntityFields.USER_ID, request.userId) - .equalTo(IncomingSecretRequestEntityFields.DEVICE_ID, request.deviceId) - .equalTo(IncomingSecretRequestEntityFields.REQUEST_ID, request.requestId) - .equalTo(IncomingSecretRequestEntityFields.SECRET_NAME, request.secretName) - .findAll() - .deleteAllFromRealm() + override fun updateOutgoingGossipingRequestState(requestId: String, state: OutgoingGossipingRequestState) { + doRealmTransaction(realmConfiguration) { realm -> + realm.where() + .equalTo(OutgoingGossipingRequestEntityFields.REQUEST_ID, requestId) + .findAll().forEach { + it.requestState = state + } } } override fun getIncomingRoomKeyRequest(userId: String, deviceId: String, requestId: String): IncomingRoomKeyRequest? { - return doRealmQueryAndCopy(realmConfiguration) { - it.where() - .equalTo(IncomingRoomKeyRequestEntityFields.USER_ID, userId) - .equalTo(IncomingRoomKeyRequestEntityFields.DEVICE_ID, deviceId) - .equalTo(IncomingRoomKeyRequestEntityFields.REQUEST_ID, requestId) - .findFirst() - } - ?.toIncomingRoomKeyRequest() + return doRealmQueryAndCopyList(realmConfiguration) { realm -> + realm.where() + .equalTo(IncomingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) + .equalTo(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, deviceId) + .equalTo(IncomingGossipingRequestEntityFields.OTHER_USER_ID, userId) + .findAll() + }.mapNotNull { entity -> + entity.toIncomingGossipingRequest() as? IncomingRoomKeyRequest + }.firstOrNull() } - override fun getPendingIncomingRoomKeyRequests(): MutableList { + override fun getPendingIncomingRoomKeyRequests(): List { return doRealmQueryAndCopyList(realmConfiguration) { - it.where() + it.where() + .equalTo(IncomingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) + .equalTo(IncomingGossipingRequestEntityFields.REQUEST_STATE_STR, GossipingRequestState.PENDING.name) .findAll() } - .map { - it.toIncomingRoomKeyRequest() + .map { entity -> + IncomingRoomKeyRequest( + userId = entity.otherUserId, + deviceId = entity.otherDeviceId, + requestId = entity.requestId, + requestBody = entity.getRequestedKeyInfo(), + localCreationTimestamp = entity.localCreationTimestamp + ) } - .toMutableList() } - override fun getPendingIncomingSecretShareRequests(): List { + override fun getPendingIncomingGossipingRequests(): List { return doRealmQueryAndCopyList(realmConfiguration) { - it.where() + it.where() + .equalTo(IncomingGossipingRequestEntityFields.REQUEST_STATE_STR, GossipingRequestState.PENDING.name) .findAll() - }.map { - it.toIncomingSecretShareRequest() + } + .mapNotNull { entity -> + when (entity.type) { + GossipRequestType.KEY -> { + IncomingRoomKeyRequest( + userId = entity.otherUserId, + deviceId = entity.otherDeviceId, + requestId = entity.requestId, + requestBody = entity.getRequestedKeyInfo(), + localCreationTimestamp = entity.localCreationTimestamp + ) + } + GossipRequestType.SECRET -> { + IncomingSecretShareRequest( + userId = entity.otherUserId, + deviceId = entity.otherDeviceId, + requestId = entity.requestId, + secretName = entity.getRequestedSecretName(), + localCreationTimestamp = entity.localCreationTimestamp + ) + } + } + + } + } + + override fun storeIncomingGossipingRequest(request: IncomingShareRequestCommon, ageLocalTS: Long?) { + doRealmTransactionAsync(realmConfiguration) { realm -> + + // After a clear cache, we might have a + + realm.createObject(IncomingGossipingRequestEntity::class.java).let { + it.otherDeviceId = request.deviceId + it.otherUserId = request.userId + it.requestId = request.requestId ?: "" + it.requestState = GossipingRequestState.PENDING + it.localCreationTimestamp = ageLocalTS ?: System.currentTimeMillis() + if (request is IncomingSecretShareRequest) { + it.type = GossipRequestType.SECRET + it.requestedInfoStr = request.secretName + } else if (request is IncomingRoomKeyRequest) { + it.type = GossipRequestType.KEY + it.requestedInfoStr = request.requestBody?.toJson() + } + } } } +// override fun getPendingIncomingSecretShareRequests(): List { +// return doRealmQueryAndCopyList(realmConfiguration) { +// it.where() +// .findAll() +// }.map { +// it.toIncomingSecretShareRequest() +// } +// } + /* ========================================================================================== * Cross Signing * ========================================================================================== */ @@ -1051,10 +1241,12 @@ internal class RealmCryptoStore @Inject constructor( override fun getOutgoingRoomKeyRequests(): List { return monarchy.fetchAllMappedSync({ realm -> - realm.where(OutgoingRoomKeyRequestEntity::class.java) - }, { - it.toOutgoingRoomKeyRequest() - }) + realm + .where(OutgoingGossipingRequestEntity::class.java) + .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) + }, { entity -> + entity.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest + }).filterNotNull() } override fun getCrossSigningInfo(userId: String): MXCrossSigningInfo? { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt index 6d10e9d8aa..80173c050c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -23,9 +23,10 @@ import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.store.db.model.CrossSigningInfoEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMetadataEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntityFields -import im.vector.matrix.android.internal.crypto.store.db.model.IncomingSecretRequestEntityFields +import im.vector.matrix.android.internal.crypto.store.db.model.GossipingEventEntityFields +import im.vector.matrix.android.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntityFields -import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingSecretRequestEntityFields +import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.TrustLevelEntityFields import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields import im.vector.matrix.android.internal.di.SerializeNulls @@ -140,21 +141,38 @@ internal object RealmCryptoStoreMigration : RealmMigration { private fun migrateTo2(realm: DynamicRealm) { Timber.d("Step 1 -> 2") + realm.schema.remove("OutgoingRoomKeyRequestEntity") + realm.schema.remove("IncomingRoomKeyRequestEntity") - realm.schema.create("IncomingSecretRequestEntity") - .addField(IncomingSecretRequestEntityFields.DEVICE_ID, String::class.java) - .addField(IncomingSecretRequestEntityFields.SECRET_NAME, String::class.java) - .addField(IncomingSecretRequestEntityFields.REQUEST_ID, String::class.java) - .addField(IncomingSecretRequestEntityFields.USER_ID, String::class.java) + //Not need to migrate existing request, just start fresh? + + realm.schema.create("GossipingEventEntity") + .addField(GossipingEventEntityFields.TYPE, String::class.java) + .addIndex(GossipingEventEntityFields.TYPE) + .addField(GossipingEventEntityFields.CONTENT, String::class.java) + .addField(GossipingEventEntityFields.SENDER, String::class.java) + .addField(GossipingEventEntityFields.DECRYPTION_RESULT_JSON, String::class.java) + .addField(GossipingEventEntityFields.DECRYPTION_ERROR_CODE, String::class.java) + .addField(GossipingEventEntityFields.AGE_LOCAL_TS, Long::class.java) + .addField(GossipingEventEntityFields.SEND_STATE_STR, Long::class.java) - realm.schema.create("OutgoingSecretRequestEntity") - .addField(OutgoingSecretRequestEntityFields.REQUEST_ID, String::class.java) - .addPrimaryKey(OutgoingSecretRequestEntityFields.REQUEST_ID) - .addField(OutgoingSecretRequestEntityFields.SECRET_NAME, String::class.java) - .addField(OutgoingSecretRequestEntityFields.CANCELLATION_TXN_ID, String::class.java) - .addField(OutgoingSecretRequestEntityFields.RECIPIENTS_DATA, String::class.java) - .addField(OutgoingSecretRequestEntityFields.STATE, Int::class.java) + realm.schema.create("IncomingGossipingRequestEntity") + .addField(IncomingGossipingRequestEntityFields.REQUEST_ID, String::class.java) + .addField(IncomingGossipingRequestEntityFields.TYPE_STR, String::class.java) + .addField(IncomingGossipingRequestEntityFields.OTHER_USER_ID, String::class.java) + .addField(IncomingGossipingRequestEntityFields.REQUESTED_INFO_STR, String::class.java) + .addField(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, String::class.java) + .addField(IncomingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java) + .addField(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, Long::class.java) + .setNullable(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, true) + + realm.schema.create("OutgoingGossipingRequestEntity") + .addField(OutgoingGossipingRequestEntityFields.REQUEST_ID, String::class.java) + .addField(OutgoingGossipingRequestEntityFields.TYPE_STR, String::class.java) + .addField(OutgoingGossipingRequestEntityFields.REQUESTED_INFO_STR, String::class.java) + .addField(OutgoingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java) + .addField(OutgoingGossipingRequestEntityFields.RECIPIENTS_DATA, String::class.java) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreModule.kt index d9d4496d4e..0d48f4671b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreModule.kt @@ -20,14 +20,13 @@ import im.vector.matrix.android.internal.crypto.store.db.model.CrossSigningInfoE import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMetadataEntity import im.vector.matrix.android.internal.crypto.store.db.model.CryptoRoomEntity import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntity -import im.vector.matrix.android.internal.crypto.store.db.model.IncomingRoomKeyRequestEntity -import im.vector.matrix.android.internal.crypto.store.db.model.IncomingSecretRequestEntity +import im.vector.matrix.android.internal.crypto.store.db.model.GossipingEventEntity +import im.vector.matrix.android.internal.crypto.store.db.model.IncomingGossipingRequestEntity import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntity import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity import im.vector.matrix.android.internal.crypto.store.db.model.OlmInboundGroupSessionEntity import im.vector.matrix.android.internal.crypto.store.db.model.OlmSessionEntity -import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingRoomKeyRequestEntity -import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingSecretRequestEntity +import im.vector.matrix.android.internal.crypto.store.db.model.OutgoingGossipingRequestEntity import im.vector.matrix.android.internal.crypto.store.db.model.TrustLevelEntity import im.vector.matrix.android.internal.crypto.store.db.model.UserEntity import io.realm.annotations.RealmModule @@ -40,16 +39,19 @@ import io.realm.annotations.RealmModule CryptoMetadataEntity::class, CryptoRoomEntity::class, DeviceInfoEntity::class, - IncomingRoomKeyRequestEntity::class, +// IncomingRoomKeyRequestEntity::class, KeysBackupDataEntity::class, OlmInboundGroupSessionEntity::class, OlmSessionEntity::class, - OutgoingRoomKeyRequestEntity::class, +// OutgoingRoomKeyRequestEntity::class, UserEntity::class, KeyInfoEntity::class, CrossSigningInfoEntity::class, TrustLevelEntity::class, - IncomingSecretRequestEntity::class, - OutgoingSecretRequestEntity::class +// IncomingSecretRequestEntity::class, +// OutgoingSecretRequestEntity::class, + GossipingEventEntity::class, + IncomingGossipingRequestEntity::class, + OutgoingGossipingRequestEntity::class ]) internal class RealmCryptoStoreModule diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/GossipingEventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/GossipingEventEntity.kt new file mode 100644 index 0000000000..1c5c4d267b --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/GossipingEventEntity.kt @@ -0,0 +1,88 @@ +/* + * 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.store.db.model + +import com.squareup.moshi.JsonDataException +import im.vector.matrix.android.api.session.crypto.MXCryptoError +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.room.send.SendState +import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult +import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult +import im.vector.matrix.android.internal.database.mapper.ContentMapper +import im.vector.matrix.android.internal.di.MoshiProvider +import io.realm.RealmObject +import io.realm.annotations.Index +import timber.log.Timber + +/** + * Keep track of gossiping event received in toDevice messages + * (room key request, or sss secret sharing, as well as cancellations) + * + */ +internal open class GossipingEventEntity(@Index var type: String = "", + var content: String? = null, + @Index var sender: String? = null, + var decryptionResultJson: String? = null, + var decryptionErrorCode: String? = null, + var ageLocalTs: Long? = null) : RealmObject() { + + private var sendStateStr: String = SendState.UNKNOWN.name + + var sendState: SendState + get() { + return SendState.valueOf(sendStateStr) + } + set(value) { + sendStateStr = value.name + } + + companion object + + fun setDecryptionResult(result: MXEventDecryptionResult) { + val decryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + val adapter = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java) + decryptionResultJson = adapter.toJson(decryptionResult) + decryptionErrorCode = null + } + + fun toModel(): Event { + return Event( + type = this.type, + content = ContentMapper.map(this.content), + senderId = this.sender + ).also { + it.ageLocalTs = this.ageLocalTs + it.sendState = this.sendState + this.decryptionResultJson?.let { json -> + try { + it.mxDecryptionResult = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java).fromJson(json) + } catch (t: JsonDataException) { + Timber.e(t, "Failed to parse decryption result") + } + } + // TODO get the full crypto error object + it.mCryptoError = this.decryptionErrorCode?.let { errorCode -> + MXCryptoError.ErrorType.valueOf(errorCode) + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt new file mode 100644 index 0000000000..1482a18d74 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt @@ -0,0 +1,90 @@ +/* + * 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.store.db.model + +import im.vector.matrix.android.api.extensions.tryThis +import im.vector.matrix.android.internal.crypto.GossipRequestType +import im.vector.matrix.android.internal.crypto.GossipingRequestState +import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest +import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest +import im.vector.matrix.android.internal.crypto.IncomingShareRequestCommon +import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.Index + +internal open class IncomingGossipingRequestEntity(@Index var requestId: String = "", + @Index var typeStr: String? = null, + var otherUserId: String? = null, + var requestedInfoStr: String? = null, + var otherDeviceId: String? = null, + var localCreationTimestamp: Long? = null +) : RealmObject() { + + fun getRequestedSecretName(): String? = if (type == GossipRequestType.SECRET) { + requestedInfoStr + } else null + + fun getRequestedKeyInfo(): RoomKeyRequestBody? = if (type == GossipRequestType.KEY) { + RoomKeyRequestBody.fromJson(requestedInfoStr) + } else null + + var type: GossipRequestType + get() { + return tryThis { typeStr?.let { GossipRequestType.valueOf(it) } } ?: GossipRequestType.KEY + } + set(value) { + typeStr = value.name + } + + private var requestStateStr: String = GossipingRequestState.NONE.name + + var requestState: GossipingRequestState + get() { + return tryThis { GossipingRequestState.valueOf(requestStateStr) } + ?: GossipingRequestState.NONE + } + set(value) { + requestStateStr = value.name + } + + companion object + + fun toIncomingGossipingRequest(): IncomingShareRequestCommon { + return when (type) { + GossipRequestType.KEY -> { + IncomingRoomKeyRequest( + requestBody = getRequestedKeyInfo(), + deviceId = otherDeviceId, + userId = otherUserId, + requestId = requestId, + state = requestState, + localCreationTimestamp = localCreationTimestamp + ) + } + GossipRequestType.SECRET -> { + IncomingSecretShareRequest( + secretName = getRequestedSecretName(), + deviceId = otherDeviceId, + userId = otherUserId, + requestId = requestId, + localCreationTimestamp = localCreationTimestamp + ) + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingRoomKeyRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingRoomKeyRequestEntity.kt index 38cece99ac..ef3ba75f23 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingRoomKeyRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingRoomKeyRequestEntity.kt @@ -1,56 +1,56 @@ -/* - * Copyright 2018 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.store.db.model - -import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest -import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody -import io.realm.RealmObject - -internal open class IncomingRoomKeyRequestEntity( - var requestId: String? = null, - var userId: String? = null, - var deviceId: String? = null, - // RoomKeyRequestBody fields - var requestBodyAlgorithm: String? = null, - var requestBodyRoomId: String? = null, - var requestBodySenderKey: String? = null, - var requestBodySessionId: String? = null -) : RealmObject() { - - fun toIncomingRoomKeyRequest(): IncomingRoomKeyRequest { - return IncomingRoomKeyRequest( - requestId = requestId, - userId = userId, - deviceId = deviceId, - requestBody = RoomKeyRequestBody( - algorithm = requestBodyAlgorithm, - roomId = requestBodyRoomId, - senderKey = requestBodySenderKey, - sessionId = requestBodySessionId - ) - ) - } - - fun putRequestBody(requestBody: RoomKeyRequestBody?) { - requestBody?.let { - requestBodyAlgorithm = it.algorithm - requestBodyRoomId = it.roomId - requestBodySenderKey = it.senderKey - requestBodySessionId = it.sessionId - } - } -} +///* +// * Copyright 2018 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.store.db.model +// +//import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest +//import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody +//import io.realm.RealmObject +// +//internal open class IncomingRoomKeyRequestEntity( +// var requestId: String? = null, +// var userId: String? = null, +// var deviceId: String? = null, +// // RoomKeyRequestBody fields +// var requestBodyAlgorithm: String? = null, +// var requestBodyRoomId: String? = null, +// var requestBodySenderKey: String? = null, +// var requestBodySessionId: String? = null +//) : RealmObject() { +// +// fun toIncomingRoomKeyRequest(): IncomingRoomKeyRequest { +// return IncomingRoomKeyRequest( +// requestId = requestId, +// userId = userId, +// deviceId = deviceId, +// requestBody = RoomKeyRequestBody( +// algorithm = requestBodyAlgorithm, +// roomId = requestBodyRoomId, +// senderKey = requestBodySenderKey, +// sessionId = requestBodySessionId +// ) +// ) +// } +// +// fun putRequestBody(requestBody: RoomKeyRequestBody?) { +// requestBody?.let { +// requestBodyAlgorithm = it.algorithm +// requestBodyRoomId = it.roomId +// requestBodySenderKey = it.senderKey +// requestBodySessionId = it.sessionId +// } +// } +//} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingSecretRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingSecretRequestEntity.kt index e1eb274e3d..81095dfb52 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingSecretRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingSecretRequestEntity.kt @@ -1,37 +1,37 @@ -/* - * 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.store.db.model - -import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest -import io.realm.RealmObject - -internal open class IncomingSecretRequestEntity( - var requestId: String? = null, - var userId: String? = null, - var deviceId: String? = null, - var secretName: String? = null -) : RealmObject() { - - fun toIncomingSecretShareRequest(): IncomingSecretShareRequest { - return IncomingSecretShareRequest( - requestId = requestId, - userId = userId, - deviceId = deviceId, - secretName = secretName - ) - } -} +///* +// * 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.store.db.model +// +//import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest +//import io.realm.RealmObject +// +//internal open class IncomingSecretRequestEntity( +// var requestId: String? = null, +// var userId: String? = null, +// var deviceId: String? = null, +// var secretName: String? = null +//) : RealmObject() { +// +// fun toIncomingSecretShareRequest(): IncomingSecretShareRequest { +// return IncomingSecretShareRequest( +// requestId = requestId, +// userId = userId, +// deviceId = deviceId, +// secretName = secretName +// ) +// } +//} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt new file mode 100644 index 0000000000..e6be88790d --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt @@ -0,0 +1,107 @@ +/* + * 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.store.db.model + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Types +import im.vector.matrix.android.api.extensions.tryThis +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.internal.crypto.GossipRequestType +import im.vector.matrix.android.internal.crypto.GossipingRequestState +import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequest +import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestState +import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest +import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest +import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody +import im.vector.matrix.android.internal.di.MoshiProvider +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.Index + +internal open class OutgoingGossipingRequestEntity( + @Index var requestId: String? = null, +// var cancellationTxnId: String? = null, + // Serialized Json + var recipientsData: String? = null, + var requestedInfoStr: String? = null, + @Index var typeStr: String? = null, + var sourceEvents: RealmList = RealmList() +) : RealmObject() { + + fun getRequestedSecretName(): String? = if (type == GossipRequestType.SECRET) { + requestedInfoStr + } else null + + fun getRequestedKeyInfo(): RoomKeyRequestBody? = if (type == GossipRequestType.KEY) { + RoomKeyRequestBody.fromJson(requestedInfoStr) + } else null + + var type: GossipRequestType + get() { + return tryThis { typeStr?.let { GossipRequestType.valueOf(it) } } ?: GossipRequestType.KEY + } + set(value) { + typeStr = value.name + } + + private var requestStateStr: String = OutgoingGossipingRequestState.UNSENT.name + + var requestState: OutgoingGossipingRequestState + get() { + return tryThis { OutgoingGossipingRequestState.valueOf(requestStateStr) } + ?: OutgoingGossipingRequestState.UNSENT + } + set(value) { + requestStateStr = value.name + } + + companion object { + + private val recipientsDataMapper: JsonAdapter>> = + MoshiProvider.providesMoshi().adapter>>(Types.newParameterizedType(Map::class.java, String::class.java, List::class.java)) + } + + fun toOutgoingGossipingRequest(): OutgoingGossipingRequest { + return when (type) { + GossipRequestType.KEY -> { + OutgoingRoomKeyRequest( + requestBody = getRequestedKeyInfo(), + recipients = getRecipients() ?: emptyMap(), + requestId = requestId ?: "", + state = requestState + ) + } + GossipRequestType.SECRET -> { + OutgoingSecretRequest( + secretName = getRequestedSecretName(), + recipients = getRecipients() ?: emptyMap(), + requestId = requestId ?: "", + state = requestState + ) + } + } + } + + private fun getRecipients(): Map>? { + return this.recipientsData?.let { recipientsDataMapper.fromJson(it) } + } + + fun setRecipients(recipients: Map>) { + this.recipientsData = recipientsDataMapper.toJson(recipients) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingRoomKeyRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingRoomKeyRequestEntity.kt index 7b4b515c85..9e1ac89bd3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingRoomKeyRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingRoomKeyRequestEntity.kt @@ -1,77 +1,77 @@ -/* - * Copyright 2018 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.store.db.model - -import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest -import im.vector.matrix.android.internal.crypto.ShareRequestState -import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody -import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm -import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm -import io.realm.RealmObject -import io.realm.annotations.PrimaryKey - -internal open class OutgoingRoomKeyRequestEntity( - @PrimaryKey var requestId: String? = null, - var cancellationTxnId: String? = null, - // Serialized Json - var recipientsData: String? = null, - // RoomKeyRequestBody fields - var requestBodyAlgorithm: String? = null, - var requestBodyRoomId: String? = null, - var requestBodySenderKey: String? = null, - var requestBodySessionId: String? = null, - // State - var state: Int = 0 -) : RealmObject() { - - /** - * Convert to OutgoingRoomKeyRequest - */ - fun toOutgoingRoomKeyRequest(): OutgoingRoomKeyRequest { - val cancellationTxnId = this.cancellationTxnId - return OutgoingRoomKeyRequest( - RoomKeyRequestBody( - algorithm = requestBodyAlgorithm, - roomId = requestBodyRoomId, - senderKey = requestBodySenderKey, - sessionId = requestBodySessionId - ), - getRecipients()!!, - requestId!!, - ShareRequestState.from(state) - ).apply { - this.cancellationTxnId = cancellationTxnId - } - } - - private fun getRecipients(): List>? { - return deserializeFromRealm(recipientsData) - } - - fun putRecipients(recipients: List>?) { - recipientsData = serializeForRealm(recipients) - } - - fun putRequestBody(requestBody: RoomKeyRequestBody?) { - requestBody?.let { - requestBodyAlgorithm = it.algorithm - requestBodyRoomId = it.roomId - requestBodySenderKey = it.senderKey - requestBodySessionId = it.sessionId - } - } -} +///* +// * Copyright 2018 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.store.db.model +// +//import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest +//import im.vector.matrix.android.internal.crypto.ShareRequestState +//import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody +//import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm +//import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm +//import io.realm.RealmObject +//import io.realm.annotations.PrimaryKey +// +//internal open class OutgoingRoomKeyRequestEntity( +// @PrimaryKey var requestId: String? = null, +// var cancellationTxnId: String? = null, +// // Serialized Json +// var recipientsData: String? = null, +// // RoomKeyRequestBody fields +// var requestBodyAlgorithm: String? = null, +// var requestBodyRoomId: String? = null, +// var requestBodySenderKey: String? = null, +// var requestBodySessionId: String? = null, +// // State +// var state: Int = 0 +//) : RealmObject() { +// +// /** +// * Convert to OutgoingRoomKeyRequest +// */ +// fun toOutgoingRoomKeyRequest(): OutgoingRoomKeyRequest { +// val cancellationTxnId = this.cancellationTxnId +// return OutgoingRoomKeyRequest( +// RoomKeyRequestBody( +// algorithm = requestBodyAlgorithm, +// roomId = requestBodyRoomId, +// senderKey = requestBodySenderKey, +// sessionId = requestBodySessionId +// ), +// getRecipients()!!, +// requestId!!, +// ShareRequestState.from(state) +// ).apply { +// this.cancellationTxnId = cancellationTxnId +// } +// } +// +// private fun getRecipients(): List>? { +// return deserializeFromRealm(recipientsData) +// } +// +// fun putRecipients(recipients: List>?) { +// recipientsData = serializeForRealm(recipients) +// } +// +// fun putRequestBody(requestBody: RoomKeyRequestBody?) { +// requestBody?.let { +// requestBodyAlgorithm = it.algorithm +// requestBodyRoomId = it.roomId +// requestBodySenderKey = it.senderKey +// requestBodySessionId = it.sessionId +// } +// } +//} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingSecretRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingSecretRequestEntity.kt index e6ce91fc10..4a4a2d8155 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingSecretRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingSecretRequestEntity.kt @@ -1,63 +1,63 @@ -/* - * 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.store.db.model - -import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest -import im.vector.matrix.android.internal.crypto.ShareRequestState -import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm -import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm -import io.realm.RealmObject -import io.realm.annotations.PrimaryKey - -internal open class OutgoingSecretRequestEntity( - @PrimaryKey var requestId: String? = null, - var cancellationTxnId: String? = null, - // Serialized Json - var recipientsData: String? = null, - // RoomKeyRequestBody fields - var secretName: String? = null, - // State - var state: Int = 0 -) : RealmObject() { - - /** - * Convert to OutgoingRoomKeyRequest - */ - fun toOutgoingSecretRequest(): OutgoingSecretRequest { - val cancellationTxnId = this.cancellationTxnId - return OutgoingSecretRequest( - secretName, - getRecipients() ?: emptyList(), - requestId!!, - ShareRequestState.from(state) - ).apply { - this.cancellationTxnId = cancellationTxnId - } - } - - private fun getRecipients(): List>? { - return try { - deserializeFromRealm(recipientsData) - } catch (failure: Throwable) { - null - } - } - - fun putRecipients(recipients: List>?) { - recipientsData = serializeForRealm(recipients) - } -} +///* +// * 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.store.db.model +// +//import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest +//import im.vector.matrix.android.internal.crypto.ShareRequestState +//import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm +//import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm +//import io.realm.RealmObject +//import io.realm.annotations.PrimaryKey +// +//internal open class OutgoingSecretRequestEntity( +// @PrimaryKey var requestId: String? = null, +// var cancellationTxnId: String? = null, +// // Serialized Json +// var recipientsData: String? = null, +// // RoomKeyRequestBody fields +// var secretName: String? = null, +// // State +// var state: Int = 0 +//) : RealmObject() { +// +// /** +// * Convert to OutgoingRoomKeyRequest +// */ +// fun toOutgoingSecretRequest(): OutgoingSecretRequest { +// val cancellationTxnId = this.cancellationTxnId +// return OutgoingSecretRequest( +// secretName, +// getRecipients() ?: emptyList(), +// requestId!!, +// ShareRequestState.from(state) +// ).apply { +// this.cancellationTxnId = cancellationTxnId +// } +// } +// +// private fun getRecipients(): List>? { +// return try { +// deserializeFromRealm(recipientsData) +// } catch (failure: Throwable) { +// null +// } +// } +// +// fun putRecipients(recipients: List>?) { +// recipientsData = serializeForRealm(recipients) +// } +//} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt index 3fb2573bed..53ccb8b6f0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt @@ -61,7 +61,6 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor( params.events.forEach { event -> Timber.d("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}") - Timber.v("## SAS Verification live observer: received msgId: $event") // If the request is in the future by more than 5 minutes or more than 10 minutes in the past, // the message should be ignored by the receiver. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt index f56e261416..f6c9b3d50b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt @@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.crypto.verification.QrCodeVerificati import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction +import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64 import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64Safe import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt index 1b07377fa1..2185d3b278 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt @@ -20,7 +20,9 @@ import dagger.BindsInstance import dagger.Component import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.internal.crypto.CancelGossipRequestWorker import im.vector.matrix.android.internal.crypto.CryptoModule +import im.vector.matrix.android.internal.crypto.SendGossipRequestWorker import im.vector.matrix.android.internal.crypto.verification.SendVerificationMessageWorker import im.vector.matrix.android.internal.di.MatrixComponent import im.vector.matrix.android.internal.di.SessionAssistedInjectModule @@ -106,6 +108,9 @@ internal interface SessionComponent { fun inject(worker: SendVerificationMessageWorker) + fun inject(worker: SendGossipRequestWorker) + fun inject(worker: CancelGossipRequestWorker) + @Component.Factory interface Factory { fun create( diff --git a/vector/build.gradle b/vector/build.gradle index 2aae593271..f26ad40279 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -249,6 +249,7 @@ dependencies { def moshi_version = '1.8.0' def daggerVersion = '2.25.4' def autofill_version = "1.0.0" + def work_version = '2.3.2' implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android-rx") @@ -296,7 +297,7 @@ dependencies { implementation 'com.airbnb.android:mvrx:1.3.0' // Work - implementation "androidx.work:work-runtime-ktx:2.3.3" + implementation "androidx.work:work-runtime-ktx:$work_version" // Paging implementation "androidx.paging:paging-runtime-ktx:2.1.1" 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 1bccdb6c25..f112fae83c 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 @@ -74,7 +74,9 @@ 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.devtools.KeyRequestListFragment +import im.vector.riotx.features.settings.devtools.GossipingEventsPaperTrailFragment +import im.vector.riotx.features.settings.devtools.IncomingKeyRequestListFragment +import im.vector.riotx.features.settings.devtools.OutgoingKeyRequestListFragment import im.vector.riotx.features.settings.devtools.KeyRequestsFragment import im.vector.riotx.features.settings.ignored.VectorSettingsIgnoredUsersFragment import im.vector.riotx.features.settings.push.PushGatewaysFragment @@ -371,11 +373,24 @@ interface FragmentModule { @Binds @IntoMap - @FragmentKey(KeyRequestListFragment::class) - fun bindKeyRequestListFragment(fragment: KeyRequestListFragment): Fragment + @FragmentKey(OutgoingKeyRequestListFragment::class) + fun bindOutgoingKeyRequestListFragment(fragment: OutgoingKeyRequestListFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(IncomingKeyRequestListFragment::class) + fun bindIncomingKeyRequestListFragment(fragment: IncomingKeyRequestListFragment): Fragment @Binds @IntoMap @FragmentKey(KeyRequestsFragment::class) fun bindKeyRequestsFragment(fragment: KeyRequestsFragment): Fragment + + + @Binds + @IntoMap + @FragmentKey(GossipingEventsPaperTrailFragment::class) + fun bindGossipingEventsPaperTrailFragment(fragment: GossipingEventsPaperTrailFragment): Fragment + + } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsEpoxyController.kt new file mode 100644 index 0000000000..f9224b5a4b --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsEpoxyController.kt @@ -0,0 +1,203 @@ +/* + * 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.epoxy.TypedEpoxyController +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toModel +import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent +import im.vector.matrix.android.internal.crypto.model.event.OlmEventContent +import im.vector.matrix.android.internal.crypto.model.rest.ForwardedRoomKeyContent +import im.vector.matrix.android.internal.crypto.model.rest.GossipingToDeviceObject +import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareRequest +import im.vector.riotx.R +import im.vector.riotx.core.date.VectorDateFormatter +import im.vector.riotx.core.epoxy.loadingItem +import im.vector.riotx.core.extensions.exhaustive +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.resources.DateProvider +import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.core.ui.list.genericFooterItem +import im.vector.riotx.core.ui.list.genericItem +import im.vector.riotx.core.ui.list.genericItemHeader +import me.gujun.android.span.span +import javax.inject.Inject + +class GossipingEventsEpoxyController @Inject constructor( + private val stringProvider: StringProvider, + private val vectorDateFormatter: VectorDateFormatter, + private val colorProvider: ColorProvider +) : TypedEpoxyController() { + + interface InteractionListener { + fun didTap(event: Event) + } + + var interactionListener: InteractionListener? = null + + override fun buildModels(data: GossipingEventsPaperTrailState?) { + when (val async = data?.events) { + is Uninitialized, + is Loading -> { + loadingItem { + id("loadingOutgoing") + loadingText(stringProvider.getString(R.string.loading)) + } + } + is Fail -> { + genericItem { + id("failOutgoing") + title(async.error.localizedMessage) + } + } + is Success -> { + val eventList = async.invoke() + if (eventList.isEmpty()) { + genericFooterItem { + id("empty") + text(stringProvider.getString(R.string.no_result_placeholder)) + } + return + } + + eventList.forEachIndexed { _, event -> + genericItem { + id(event.hashCode()) + title( + if (event.isEncrypted()) { + "${event.getClearType()} [encrypted]" + } else { + event.type + } + ) + description( + span { + +vectorDateFormatter.formatMessageDay(DateProvider.toLocalDateTime(event.ageLocalTs)) + +" ${vectorDateFormatter.formatMessageHour(DateProvider.toLocalDateTime(event.ageLocalTs))}" + span("\nfrom: ") { + textStyle = "bold" + } + +"${event.senderId}" + apply { + if (event.getClearType() == EventType.ROOM_KEY_REQUEST) { + val content = event.getClearContent().toModel() + span("\nreqId:") { + textStyle = "bold" + } + +" ${content?.requestId}" + span("\naction:") { + textStyle = "bold" + } + +" ${content?.action}" + if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { + span("\nsessionId:") { + textStyle = "bold" + } + +" ${content.body?.sessionId}" + } + span("\nrequestedBy: ") { + textStyle = "bold" + } + +"${content?.requestingDeviceId}" + } else if (event.getClearType() == EventType.FORWARDED_ROOM_KEY) { + val encryptedContent = event.content.toModel() + val content = event.getClearContent().toModel() + if (event.mxDecryptionResult == null) { + span("**Failed to Decrypt** ${event.mCryptoError}") { + textColor = colorProvider.getColor(R.color.vector_error_color) + } + } + span("\nsessionId:") { + textStyle = "bold" + } + +" ${content?.sessionId}" + span("\nFrom Device (sender key):") { + textStyle = "bold" + } + +" ${encryptedContent?.senderKey}" + } + } + } + ) + } + } + } + } + } + + private fun buildOutgoing(data: KeyRequestListViewState?) { + data?.outgoingRoomKeyRequest?.let { async -> + when (async) { + is Uninitialized, + is Loading -> { + loadingItem { + id("loadingOutgoing") + loadingText(stringProvider.getString(R.string.loading)) + } + } + is Fail -> { + genericItem { + id("failOutgoing") + title(async.error.localizedMessage) + } + } + is Success -> { + + if (async.invoke().isEmpty()) { + genericFooterItem { + id("empty") + text(stringProvider.getString(R.string.no_result_placeholder)) + } + return + } + + val requestList = async.invoke().groupBy { it.roomId } + + requestList.forEach { + genericItemHeader { + id(it.key) + text("roomId: ${it.key}") + } + it.value.forEach { roomKeyRequest -> + genericItem { + id(roomKeyRequest.requestId) + title(roomKeyRequest.requestId) + description( + span { + span("sessionId:\n") { + textStyle = "bold" + } + +"${roomKeyRequest.sessionId}" + span("\nstate:") { + textStyle = "bold" + } + +"\n${roomKeyRequest.state.name}" + } + ) + } + } + } + } + }.exhaustive + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailFragment.kt new file mode 100644 index 0000000000..e845061a70 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailFragment.kt @@ -0,0 +1,68 @@ +/* + * 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.MenuItem +import android.view.View +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +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.VectorBaseFragment +import im.vector.riotx.core.resources.ColorProvider +import kotlinx.android.synthetic.main.fragment_generic_recycler.* +import javax.inject.Inject + +class GossipingEventsPaperTrailFragment @Inject constructor( + val viewModelFactory: GossipingEventsPaperTrailViewModel.Factory, + private val epoxyController: GossipingEventsEpoxyController +) : VectorBaseFragment() { + + override fun getLayoutResId() = R.layout.fragment_generic_recycler + + override fun getMenuRes(): Int = R.menu.menu_common_gossiping + + private val viewModel: GossipingEventsPaperTrailViewModel by fragmentViewModel(GossipingEventsPaperTrailViewModel::class) + + 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 onDestroyView() { + super.onDestroyView() + recyclerView.cleanup() +// epoxyController.interactionListener = null + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == R.id.refresh) { + viewModel.refresh() + return true + } else { + return super.onOptionsItemSelected(item) + } + } + +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailViewModel.kt new file mode 100644 index 0000000000..f248ab1482 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailViewModel.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 com.airbnb.mvrx.Async +import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Success +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.api.session.events.model.Event +import im.vector.riotx.core.platform.EmptyAction +import im.vector.riotx.core.platform.EmptyViewEvents +import im.vector.riotx.core.platform.VectorViewModel +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +data class GossipingEventsPaperTrailState( + val events: Async> = Uninitialized +) : MvRxState + +class GossipingEventsPaperTrailViewModel @AssistedInject constructor(@Assisted initialState: GossipingEventsPaperTrailState, + private val session: Session) + : VectorViewModel(initialState) { + + init { + refresh() + } + + fun refresh() { + setState { + copy(events = Loading()) + } + GlobalScope.launch { + session.cryptoService().getGossipingEventsTrail().let { + val sorted = it.sortedByDescending { it.ageLocalTs } + setState { + copy(events = Success(sorted)) + } + } + } + } + + override fun handle(action: EmptyAction) {} + + @AssistedInject.Factory + interface Factory { + fun create(initialState: GossipingEventsPaperTrailState): GossipingEventsPaperTrailViewModel + } + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: GossipingEventsPaperTrailState): GossipingEventsPaperTrailViewModel? { + val fragment: GossipingEventsPaperTrailFragment = (viewModelContext as FragmentViewModelContext).fragment() + + return fragment.viewModelFactory.create(state) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/IncomingKeyRequestListFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/IncomingKeyRequestListFragment.kt new file mode 100644 index 0000000000..653fbe36af --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/IncomingKeyRequestListFragment.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 com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +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.VectorBaseFragment +import im.vector.riotx.core.resources.ColorProvider +import kotlinx.android.synthetic.main.fragment_generic_recycler.* +import javax.inject.Inject + +class IncomingKeyRequestListFragment @Inject constructor( + val viewModelFactory: KeyRequestListViewModel.Factory, + private val epoxyController: KeyRequestEpoxyController, + private val colorProvider: ColorProvider +) : VectorBaseFragment() { + + override fun getLayoutResId() = R.layout.fragment_generic_recycler + + private val viewModel: KeyRequestListViewModel by fragmentViewModel(KeyRequestListViewModel::class) + + override fun invalidate() = withState(viewModel) { state -> + epoxyController.outgoing = false + epoxyController.setData(state) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + recyclerView.configureWith(epoxyController, showDivider = true) +// epoxyController.interactionListener = this + } + + override fun onDestroyView() { + super.onDestroyView() + recyclerView.cleanup() +// epoxyController.interactionListener = null + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt index 4169ee71f4..f1c18806d9 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt @@ -25,6 +25,7 @@ import im.vector.riotx.R import im.vector.riotx.core.epoxy.loadingItem import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.core.ui.list.genericFooterItem import im.vector.riotx.core.ui.list.genericItem import im.vector.riotx.core.ui.list.genericItemHeader import me.gujun.android.span.span @@ -38,9 +39,75 @@ class KeyRequestEpoxyController @Inject constructor( // fun didTap(data: UserAccountData) } + var outgoing = true + var interactionListener: InteractionListener? = null override fun buildModels(data: KeyRequestListViewState?) { + if (outgoing) { + buildOutgoing(data) + } else { + buildIncoming(data) + } + } + + private fun buildIncoming(data: KeyRequestListViewState?) { + data?.incomingRequests?.let { async -> + when (async) { + is Uninitialized, + is Loading -> { + loadingItem { + id("loadingOutgoing") + loadingText(stringProvider.getString(R.string.loading)) + } + } + is Fail -> { + genericItem { + id("failOutgoing") + title(async.error.localizedMessage) + } + } + is Success -> { + + if (async.invoke().isEmpty()) { + genericFooterItem { + id("empty") + text(stringProvider.getString(R.string.no_result_placeholder)) + } + return + } + val requestList = async.invoke().groupBy { it.userId } + + requestList.forEach { + genericItemHeader { + id(it.key) + text("From user: ${it.key}") + } + it.value.forEach { roomKeyRequest -> + genericItem { + id(roomKeyRequest.requestId) + title(roomKeyRequest.requestId) + description( + span { + span("sessionId:") { + textStyle = "bold" + } + span("\nFrom device:") { + textStyle = "bold" + } + +"${roomKeyRequest.deviceId}" + +"\n${roomKeyRequest.state.name}" + } + ) + } + } + } + } + }.exhaustive + } + } + + private fun buildOutgoing(data: KeyRequestListViewState?) { data?.outgoingRoomKeyRequest?.let { async -> when (async) { is Uninitialized, @@ -57,6 +124,15 @@ class KeyRequestEpoxyController @Inject constructor( } } is Success -> { + + if (async.invoke().isEmpty()) { + genericFooterItem { + id("empty") + text(stringProvider.getString(R.string.no_result_placeholder)) + } + return + } + val requestList = async.invoke().groupBy { it.roomId } requestList.forEach { @@ -70,7 +146,7 @@ class KeyRequestEpoxyController @Inject constructor( title(roomKeyRequest.requestId) description( span { - span("sessionId:") { + span("sessionId:\n") { textStyle = "bold" } +"${roomKeyRequest.sessionId}" diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListViewModel.kt index 6b09273f93..6812467b96 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListViewModel.kt @@ -31,6 +31,8 @@ import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest import im.vector.riotx.core.platform.EmptyAction import im.vector.riotx.core.platform.EmptyViewEvents import im.vector.riotx.core.platform.VectorViewModel +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch data class KeyRequestListViewState( val incomingRequests: Async> = Uninitialized, @@ -42,11 +44,24 @@ class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState : VectorViewModel(initialState) { init { - session.cryptoService().getOutgoingRoomKeyRequest().let { - setState { - copy( - outgoingRoomKeyRequest = Success(it) - ) + refresh() + } + + fun refresh() { + GlobalScope.launch { + session.cryptoService().getOutgoingRoomKeyRequest().let { + setState { + copy( + outgoingRoomKeyRequest = Success(it) + ) + } + } + session.cryptoService().getIncomingRoomKeyRequest().let { + setState { + copy( + incomingRequests = Success(it) + ) + } } } } @@ -58,12 +73,16 @@ class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState fun create(initialState: KeyRequestListViewState): KeyRequestListViewModel } + companion object : MvRxViewModelFactory { @JvmStatic override fun create(viewModelContext: ViewModelContext, state: KeyRequestListViewState): KeyRequestListViewModel? { - val fragment: KeyRequestListFragment = (viewModelContext as FragmentViewModelContext).fragment() - return fragment.viewModelFactory.create(state) + val context = viewModelContext as FragmentViewModelContext + val factory = (context.fragment as? IncomingKeyRequestListFragment)?.viewModelFactory + ?: (context.fragment as? OutgoingKeyRequestListFragment)?.viewModelFactory + + return factory?.create(state) } } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt index 8b78f52c1e..b15d754181 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt @@ -16,11 +16,14 @@ package im.vector.riotx.features.settings.devtools +import android.content.Context import android.os.Bundle +import android.view.MenuItem import android.view.View import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentActivity import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2 +import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_IDLE import com.google.android.material.tabs.TabLayoutMediator import im.vector.riotx.R import im.vector.riotx.core.platform.VectorBaseActivity @@ -31,26 +34,79 @@ import javax.inject.Inject class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() { override fun getLayoutResId(): Int = R.layout.fragment_devtool_keyrequests + override fun getMenuRes(): Int = R.menu.menu_common_gossiping override fun onResume() { super.onResume() (activity as? VectorBaseActivity)?.supportActionBar?.setTitle(R.string.key_share_request) } + private var mPagerAdapter: KeyReqPagerAdapter? = null + + private val pageAdapterListener = object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + + invalidateOptionsMenu() + } + + override fun onPageScrollStateChanged(state: Int) { + childFragmentManager.fragments.forEach { + setHasOptionsMenu(state == SCROLL_STATE_IDLE) + } + invalidateOptionsMenu() + } + } + + override fun onDestroy() { + invalidateOptionsMenu() + super.onDestroy() + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - devToolKeyRequestPager.adapter = KeyReqPagerAdapter(requireActivity()) + mPagerAdapter = KeyReqPagerAdapter(this) + devToolKeyRequestPager.adapter = mPagerAdapter + devToolKeyRequestPager.registerOnPageChangeCallback(pageAdapterListener) - TabLayoutMediator(devToolKeyRequestTabs, devToolKeyRequestPager) { tab, _ -> - tab.text = "Outgoing" + TabLayoutMediator(devToolKeyRequestTabs, devToolKeyRequestPager) { tab, position -> + when (position) { + 0 -> { + tab.text = "Outgoing" + } + 1 -> { + tab.text = "Incoming" + } + 2 -> { + tab.text = "Audit Trail" + } + } }.attach() } - private inner class KeyReqPagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) { - override fun getItemCount(): Int = 1 + override fun onDestroyView() { + devToolKeyRequestPager.unregisterOnPageChangeCallback(pageAdapterListener) + mPagerAdapter = null + super.onDestroyView() + } + + private inner class KeyReqPagerAdapter(fa: Fragment) : FragmentStateAdapter(fa) { + override fun getItemCount(): Int = 3 + + override fun createFragment(position: Int): Fragment { - return childFragmentManager.fragmentFactory.instantiate(requireContext().classLoader, KeyRequestListFragment::class.java.name) + return when (position) { + 0 -> { + childFragmentManager.fragmentFactory.instantiate(requireContext().classLoader, OutgoingKeyRequestListFragment::class.java.name) + } + 1 -> { + childFragmentManager.fragmentFactory.instantiate(requireContext().classLoader, IncomingKeyRequestListFragment::class.java.name) + } + else -> { + childFragmentManager.fragmentFactory.instantiate(requireContext().classLoader, GossipingEventsPaperTrailFragment::class.java.name) + } + } } } + } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/OutgoingKeyRequestListFragment.kt similarity index 95% rename from vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListFragment.kt rename to vector/src/main/java/im/vector/riotx/features/settings/devtools/OutgoingKeyRequestListFragment.kt index 71a4e19343..d79bb9934b 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/OutgoingKeyRequestListFragment.kt @@ -17,6 +17,7 @@ package im.vector.riotx.features.settings.devtools import android.os.Bundle +import android.view.MenuItem import android.view.View import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -28,14 +29,13 @@ import im.vector.riotx.core.resources.ColorProvider import kotlinx.android.synthetic.main.fragment_generic_recycler.* import javax.inject.Inject -class KeyRequestListFragment @Inject constructor( +class OutgoingKeyRequestListFragment @Inject constructor( val viewModelFactory: KeyRequestListViewModel.Factory, private val epoxyController: KeyRequestEpoxyController, private val colorProvider: ColorProvider ) : VectorBaseFragment() { override fun getLayoutResId() = R.layout.fragment_generic_recycler - private val viewModel: KeyRequestListViewModel by fragmentViewModel(KeyRequestListViewModel::class) override fun invalidate() = withState(viewModel) { state -> @@ -53,4 +53,5 @@ class KeyRequestListFragment @Inject constructor( recyclerView.cleanup() // epoxyController.interactionListener = null } + } diff --git a/vector/src/main/res/menu/menu_common_gossiping.xml b/vector/src/main/res/menu/menu_common_gossiping.xml new file mode 100644 index 0000000000..08b23c760c --- /dev/null +++ b/vector/src/main/res/menu/menu_common_gossiping.xml @@ -0,0 +1,10 @@ + + + + diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 7105d56a5c..643160ef21 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -12,6 +12,8 @@ Unlock encrypted messages history + Refresh + From d3d6d4466587a522dcafc99db1b79e0f099b9c7f Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 9 Mar 2020 10:50:49 +0100 Subject: [PATCH 020/156] Post rebase fix --- matrix-sdk-android/build.gradle | 2 +- .../crypto/gossiping/KeyShareTests.kt | 7 ++--- .../crypto/keysbackup/KeysBackupTest.kt | 5 ---- .../crypto/verification/qrcode/QrCodeTest.kt | 1 - .../SharedSecretStorageService.kt | 1 - .../crypto/CancelGossipRequestWorker.kt | 1 - .../internal/crypto/DefaultCryptoService.kt | 4 +-- .../crypto/IncomingRoomKeyRequestManager.kt | 2 +- .../crypto/OutgoingGossipingRequest.kt | 2 +- .../crypto/OutgoingGossipingRequestManager.kt | 2 +- .../internal/crypto/OutgoingRoomKeyRequest.kt | 1 - .../crypto/SendGossipRequestWorker.kt | 4 +-- .../algorithms/megolm/MXMegolmDecryption.kt | 1 - .../DefaultCrossSigningService.kt | 4 +-- .../crypto/model/rest/RoomKeyRequestBody.kt | 2 -- .../crypto/store/db/RealmCryptoStore.kt | 29 +++++++++---------- .../store/db/RealmCryptoStoreMigration.kt | 4 +-- .../model/IncomingGossipingRequestEntity.kt | 1 - .../db/model/IncomingRoomKeyRequestEntity.kt | 16 +++++----- .../db/model/IncomingSecretRequestEntity.kt | 14 ++++----- .../model/OutgoingGossipingRequestEntity.kt | 3 -- .../db/model/OutgoingRoomKeyRequestEntity.kt | 24 +++++++-------- .../db/model/OutgoingSecretRequestEntity.kt | 22 +++++++------- .../DefaultQrCodeVerificationTransaction.kt | 1 - vector/build.gradle | 2 +- .../im/vector/riotx/core/di/FragmentModule.kt | 3 -- .../GossipingEventsEpoxyController.kt | 2 -- .../GossipingEventsPaperTrailFragment.kt | 2 -- .../devtools/KeyRequestEpoxyController.kt | 2 -- .../devtools/KeyRequestListViewModel.kt | 1 - .../settings/devtools/KeyRequestsFragment.kt | 6 ---- .../OutgoingKeyRequestListFragment.kt | 2 -- 32 files changed, 65 insertions(+), 108 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 97a1d977cc..5f614763d5 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -97,7 +97,7 @@ dependencies { def coroutines_version = "1.3.2" def markwon_version = '3.1.0' def daggerVersion = '2.25.4' - def work_version = '2.3.2' + def work_version = '2.3.3' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt index fca406ac31..01f6ab0f22 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt @@ -55,8 +55,8 @@ class KeyShareTests : InstrumentedTest { // every { Log.d(any(), any()) } returns 0 // every { Log.i(any(), any()) } returns 0 // every { Log.e(any(), any()) } returns 0 -//// every { Log.println(any(), any(), any()) } returns 0 -//// every { Log.wtf(any(), any(), any()) } returns 0 +// // every { Log.println(any(), any(), any()) } returns 0 +// // every { Log.wtf(any(), any(), any()) } returns 0 // } @Test @@ -119,7 +119,7 @@ class KeyShareTests : InstrumentedTest { val outgoingRequestAfter = aliceSession2.cryptoService().getOutgoingRoomKeyRequest() - //We should have a new request + // We should have a new request Assert.assertTrue(outgoingRequestAfter.size > outgoingRequestBefore.size) Assert.assertNotNull(outgoingRequestAfter.first { it.sessionId == eventMegolmSessionId }) @@ -165,7 +165,6 @@ class KeyShareTests : InstrumentedTest { } Log.v("TEST", "=========================") - it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == GossipingRequestState.ACCEPTED } } } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt index d584482774..3042a3c68f 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackupTest.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.crypto.keysbackup -import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.api.listeners.ProgressListener @@ -35,7 +34,6 @@ import im.vector.matrix.android.common.assertDictEquals import im.vector.matrix.android.common.assertListEquals import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import im.vector.matrix.android.internal.crypto.MegolmSessionData -import im.vector.matrix.android.internal.crypto.ShareRequestState import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo @@ -43,15 +41,12 @@ import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersio import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper -import io.mockk.every -import io.mockk.mockkStatic import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Assert.fail -import org.junit.BeforeClass import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/qrcode/QrCodeTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/qrcode/QrCodeTest.kt index ff1780865c..d19fad4b59 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/qrcode/QrCodeTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/qrcode/QrCodeTest.kt @@ -18,7 +18,6 @@ package im.vector.matrix.android.internal.crypto.verification.qrcode import androidx.test.ext.junit.runners.AndroidJUnit4 import im.vector.matrix.android.InstrumentedTest -import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64NoPadding import org.amshove.kluent.shouldBeNull import org.amshove.kluent.shouldEqual import org.amshove.kluent.shouldEqualTo 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 ad0c6f10fa..d32e459dd6 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 @@ -111,7 +111,6 @@ interface SharedSecretStorageService { fun checkShouldBeAbleToAccessSecrets(secretNames: List, keyId: String?) : IntegrityResult - fun requestSecret(name: String, myOtherDeviceId: String) data class KeyRef( diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CancelGossipRequestWorker.kt index a87dae3690..54f89dc8b9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CancelGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CancelGossipRequestWorker.kt @@ -64,7 +64,6 @@ internal class CancelGossipRequestWorker(context: Context, @Inject lateinit var credentials: Credentials override suspend fun doWork(): Result { - val errorOutputData = Data.Builder().putBoolean("failed", true).build() val params = WorkerParamsFactory.fromData(inputData) ?: return Result.success(errorOutputData) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 7b7490f233..77dc9ca7fb 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -765,7 +765,7 @@ internal class DefaultCryptoService @Inject constructor( val device = senderDevice?.let { cryptoStore.getUserDevice(event.senderId, it) } ?: return Unit.also { - Timber.e("## processIncomingSecretShareRequest() : Received secret share request from unknown device ${senderDevice}") + Timber.e("## processIncomingSecretShareRequest() : Received secret share request from unknown device $senderDevice") } try { @@ -793,7 +793,7 @@ internal class DefaultCryptoService @Inject constructor( if (device.isBlocked || !device.isVerified) { // Ignore secrets from this - Timber.i("## onSecretSend() :Received secret from untrusted/blocked device: ${device}") + Timber.i("## onSecretSend() :Received secret from untrusted/blocked device: $device") return } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt index 4fdd011aee..3de8e44e3f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt @@ -244,7 +244,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( val isDeviceLocallyVerified = cryptoStore.getUserDevice(userId, deviceId)?.trustLevel?.isLocallyVerified() - //Should SDK always Silently reject any request for the master key? + // Should SDK always Silently reject any request for the master key? when (secretName) { SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequest.kt index 30fc5fdb4a..2fb0c7094b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequest.kt @@ -21,5 +21,5 @@ interface OutgoingGossipingRequest { var requestId: String var state: OutgoingGossipingRequestState // transaction id for the cancellation, if any - //var cancellationTxnId: String? + // var cancellationTxnId: String? } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt index e7c5ef89c8..c78b68b3a0 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt @@ -131,7 +131,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor( * @param request the request */ private fun sendOutgoingGossipingRequest(request: OutgoingGossipingRequest) { - Timber.v("## sendOutgoingRoomKeyRequest() : Requesting keys ${request}") + Timber.v("## sendOutgoingRoomKeyRequest() : Requesting keys $request") val params = SendGossipRequestWorker.Params( sessionId = sessionId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequest.kt index 7e359a677c..b01c9d9b3f 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingRoomKeyRequest.kt @@ -38,7 +38,6 @@ data class OutgoingRoomKeyRequest( // override var cancellationTxnId: String? = null ) : OutgoingGossipingRequest { - /** * Used only for log. * diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipRequestWorker.kt index cffd58cbf2..fb9c45da45 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipRequestWorker.kt @@ -33,7 +33,6 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareRequest import im.vector.matrix.android.internal.crypto.model.rest.SecretShareRequest import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask -import im.vector.matrix.android.internal.database.mapper.ContentMapper import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent import org.greenrobot.eventbus.EventBus @@ -57,7 +56,6 @@ internal class SendGossipRequestWorker(context: Context, @Inject lateinit var credentials: Credentials override suspend fun doWork(): Result { - val errorOutputData = Data.Builder().putBoolean("failed", true).build() val params = WorkerParamsFactory.fromData(inputData) ?: return Result.success(errorOutputData) @@ -123,7 +121,7 @@ internal class SendGossipRequestWorker(context: Context, } else -> { return Result.success(errorOutputData).also { - Timber.e("Unknown empty gossiping request: ${params}") + Timber.e("Unknown empty gossiping request: $params") } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 36b4e40957..d5b3b3b034 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -163,7 +163,6 @@ internal class MXMegolmDecryption(private val userId: String, ) } - val requestBody = RoomKeyRequestBody( roomId = event.roomId, algorithm = encryptedEventContent.algorithm, 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 661af7fb49..556aefa84b 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 @@ -305,7 +305,7 @@ internal class DefaultCrossSigningService @Inject constructor( return runBlocking(coroutineDispatchers.crypto) { val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return@runBlocking false - sskPrivateKey.fromBase64NoPadding() + sskPrivateKey.fromBase64() .let { privateKeySeed -> val pkSigning = OlmPkSigning() try { @@ -331,7 +331,7 @@ internal class DefaultCrossSigningService @Inject constructor( return runBlocking(coroutineDispatchers.crypto) { val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return@runBlocking false - uskPrivateKey.fromBase64NoPadding() + uskPrivateKey.fromBase64() .let { privateKeySeed -> val pkSigning = OlmPkSigning() try { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyRequestBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyRequestBody.kt index 5def4ae2a3..0b7c3a201f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyRequestBody.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/RoomKeyRequestBody.kt @@ -47,5 +47,3 @@ data class RoomKeyRequestBody( } } } - - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index 83b8459d72..2da182a358 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -824,14 +824,14 @@ internal class RealmCryptoStore @Inject constructor( override fun getOutgoingSecretRequest(secretName: String): OutgoingSecretRequest? { // return monarchy.fetchAllCopiedSync { realm -> -//// realm.where() -//// .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.SECRET.name) -//// .equalTo(GossipingEventEntityFields.SENDER, credentials.userId) -//// }.mapNotNull { -//// ContentMapper.map(it.content)?.toModel() -//// }.firstOrNull { -//// it.secretName == secretName -//// } +// // realm.where() +// // .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.SECRET.name) +// // .equalTo(GossipingEventEntityFields.SENDER, credentials.userId) +// // }.mapNotNull { +// // ContentMapper.map(it.content)?.toModel() +// // }.firstOrNull { +// // it.secretName == secretName +// // } TODO("not implemented") } @@ -880,13 +880,11 @@ internal class RealmCryptoStore @Inject constructor( } else { request = existing } - } return request } override fun getOrAddOutgoingSecretShareRequest(secretName: String, recipients: Map>): OutgoingSecretRequest? { - var request: OutgoingSecretRequest? = null // Insert the request and return the one passed in parameter @@ -1015,11 +1013,11 @@ internal class RealmCryptoStore @Inject constructor( // // } // } -//// .equalTo(IncomingRoomKeyRequestEntityFields.USER_ID, incomingRoomKeyRequest.userId) -//// .equalTo(IncomingRoomKeyRequestEntityFields.DEVICE_ID, incomingRoomKeyRequest.deviceId) -//// .equalTo(IncomingRoomKeyRequestEntityFields.REQUEST_ID, incomingRoomKeyRequest.requestId) -//// .findAll() -//// .deleteAllFromRealm() +// // .equalTo(IncomingRoomKeyRequestEntityFields.USER_ID, incomingRoomKeyRequest.userId) +// // .equalTo(IncomingRoomKeyRequestEntityFields.DEVICE_ID, incomingRoomKeyRequest.deviceId) +// // .equalTo(IncomingRoomKeyRequestEntityFields.REQUEST_ID, incomingRoomKeyRequest.requestId) +// // .findAll() +// // .deleteAllFromRealm() // } // } @@ -1102,7 +1100,6 @@ internal class RealmCryptoStore @Inject constructor( ) } } - } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt index 80173c050c..a4c4004a9d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -144,7 +144,7 @@ internal object RealmCryptoStoreMigration : RealmMigration { realm.schema.remove("OutgoingRoomKeyRequestEntity") realm.schema.remove("IncomingRoomKeyRequestEntity") - //Not need to migrate existing request, just start fresh? + // Not need to migrate existing request, just start fresh? realm.schema.create("GossipingEventEntity") .addField(GossipingEventEntityFields.TYPE, String::class.java) @@ -156,7 +156,6 @@ internal object RealmCryptoStoreMigration : RealmMigration { .addField(GossipingEventEntityFields.AGE_LOCAL_TS, Long::class.java) .addField(GossipingEventEntityFields.SEND_STATE_STR, Long::class.java) - realm.schema.create("IncomingGossipingRequestEntity") .addField(IncomingGossipingRequestEntityFields.REQUEST_ID, String::class.java) .addField(IncomingGossipingRequestEntityFields.TYPE_STR, String::class.java) @@ -167,7 +166,6 @@ internal object RealmCryptoStoreMigration : RealmMigration { .addField(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, Long::class.java) .setNullable(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, true) - realm.schema.create("OutgoingGossipingRequestEntity") .addField(OutgoingGossipingRequestEntityFields.REQUEST_ID, String::class.java) .addField(OutgoingGossipingRequestEntityFields.TYPE_STR, String::class.java) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt index 1482a18d74..1e8f4e6258 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt @@ -23,7 +23,6 @@ import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest import im.vector.matrix.android.internal.crypto.IncomingShareRequestCommon import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody -import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.Index diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingRoomKeyRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingRoomKeyRequestEntity.kt index ef3ba75f23..31d7d3374e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingRoomKeyRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingRoomKeyRequestEntity.kt @@ -1,4 +1,4 @@ -///* +// /* // * Copyright 2018 New Vector Ltd // * // * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,13 +14,13 @@ // * limitations under the License. // */ // -//package im.vector.matrix.android.internal.crypto.store.db.model +// package im.vector.matrix.android.internal.crypto.store.db.model // -//import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest -//import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody -//import io.realm.RealmObject +// import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest +// import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody +// import io.realm.RealmObject // -//internal open class IncomingRoomKeyRequestEntity( +// internal open class IncomingRoomKeyRequestEntity( // var requestId: String? = null, // var userId: String? = null, // var deviceId: String? = null, @@ -29,7 +29,7 @@ // var requestBodyRoomId: String? = null, // var requestBodySenderKey: String? = null, // var requestBodySessionId: String? = null -//) : RealmObject() { +// ) : RealmObject() { // // fun toIncomingRoomKeyRequest(): IncomingRoomKeyRequest { // return IncomingRoomKeyRequest( @@ -53,4 +53,4 @@ // requestBodySessionId = it.sessionId // } // } -//} +// } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingSecretRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingSecretRequestEntity.kt index 81095dfb52..9f2175329c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingSecretRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingSecretRequestEntity.kt @@ -1,4 +1,4 @@ -///* +// /* // * Copyright (c) 2020 New Vector Ltd // * // * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,17 +14,17 @@ // * limitations under the License. // */ // -//package im.vector.matrix.android.internal.crypto.store.db.model +// package im.vector.matrix.android.internal.crypto.store.db.model // -//import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest -//import io.realm.RealmObject +// import im.vector.matrix.android.internal.crypto.IncomingSecretShareRequest +// import io.realm.RealmObject // -//internal open class IncomingSecretRequestEntity( +// internal open class IncomingSecretRequestEntity( // var requestId: String? = null, // var userId: String? = null, // var deviceId: String? = null, // var secretName: String? = null -//) : RealmObject() { +// ) : RealmObject() { // // fun toIncomingSecretShareRequest(): IncomingSecretShareRequest { // return IncomingSecretShareRequest( @@ -34,4 +34,4 @@ // secretName = secretName // ) // } -//} +// } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt index e6be88790d..0bce2a1528 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt @@ -19,10 +19,7 @@ package im.vector.matrix.android.internal.crypto.store.db.model import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Types import im.vector.matrix.android.api.extensions.tryThis -import im.vector.matrix.android.api.session.events.model.toContent -import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.internal.crypto.GossipRequestType -import im.vector.matrix.android.internal.crypto.GossipingRequestState import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequest import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestState import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingRoomKeyRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingRoomKeyRequestEntity.kt index 9e1ac89bd3..4eee322a6a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingRoomKeyRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingRoomKeyRequestEntity.kt @@ -1,4 +1,4 @@ -///* +// /* // * Copyright 2018 New Vector Ltd // * // * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,17 +14,17 @@ // * limitations under the License. // */ // -//package im.vector.matrix.android.internal.crypto.store.db.model +// package im.vector.matrix.android.internal.crypto.store.db.model // -//import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest -//import im.vector.matrix.android.internal.crypto.ShareRequestState -//import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody -//import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm -//import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm -//import io.realm.RealmObject -//import io.realm.annotations.PrimaryKey +// import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest +// import im.vector.matrix.android.internal.crypto.ShareRequestState +// import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody +// import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm +// import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm +// import io.realm.RealmObject +// import io.realm.annotations.PrimaryKey // -//internal open class OutgoingRoomKeyRequestEntity( +// internal open class OutgoingRoomKeyRequestEntity( // @PrimaryKey var requestId: String? = null, // var cancellationTxnId: String? = null, // // Serialized Json @@ -36,7 +36,7 @@ // var requestBodySessionId: String? = null, // // State // var state: Int = 0 -//) : RealmObject() { +// ) : RealmObject() { // // /** // * Convert to OutgoingRoomKeyRequest @@ -74,4 +74,4 @@ // requestBodySessionId = it.sessionId // } // } -//} +// } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingSecretRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingSecretRequestEntity.kt index 4a4a2d8155..4a1c8ce46c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingSecretRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingSecretRequestEntity.kt @@ -1,4 +1,4 @@ -///* +// /* // * Copyright (c) 2020 New Vector Ltd // * // * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,16 +14,16 @@ // * limitations under the License. // */ // -//package im.vector.matrix.android.internal.crypto.store.db.model +// package im.vector.matrix.android.internal.crypto.store.db.model // -//import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest -//import im.vector.matrix.android.internal.crypto.ShareRequestState -//import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm -//import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm -//import io.realm.RealmObject -//import io.realm.annotations.PrimaryKey +// import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest +// import im.vector.matrix.android.internal.crypto.ShareRequestState +// import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm +// import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm +// import io.realm.RealmObject +// import io.realm.annotations.PrimaryKey // -//internal open class OutgoingSecretRequestEntity( +// internal open class OutgoingSecretRequestEntity( // @PrimaryKey var requestId: String? = null, // var cancellationTxnId: String? = null, // // Serialized Json @@ -32,7 +32,7 @@ // var secretName: String? = null, // // State // var state: Int = 0 -//) : RealmObject() { +// ) : RealmObject() { // // /** // * Convert to OutgoingRoomKeyRequest @@ -60,4 +60,4 @@ // fun putRecipients(recipients: List>?) { // recipientsData = serializeForRealm(recipients) // } -//} +// } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt index f6c9b3d50b..f56e261416 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt @@ -22,7 +22,6 @@ import im.vector.matrix.android.api.session.crypto.verification.QrCodeVerificati import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction -import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64 import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64Safe import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore diff --git a/vector/build.gradle b/vector/build.gradle index f26ad40279..66ec6808c8 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -249,7 +249,7 @@ dependencies { def moshi_version = '1.8.0' def daggerVersion = '2.25.4' def autofill_version = "1.0.0" - def work_version = '2.3.2' + def work_version = '2.3.3' implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android-rx") 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 f112fae83c..4e60a1bdf7 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 @@ -386,11 +386,8 @@ interface FragmentModule { @FragmentKey(KeyRequestsFragment::class) fun bindKeyRequestsFragment(fragment: KeyRequestsFragment): Fragment - @Binds @IntoMap @FragmentKey(GossipingEventsPaperTrailFragment::class) fun bindGossipingEventsPaperTrailFragment(fragment: GossipingEventsPaperTrailFragment): Fragment - - } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsEpoxyController.kt index f9224b5a4b..5cce238e82 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsEpoxyController.kt @@ -24,7 +24,6 @@ import com.airbnb.mvrx.Uninitialized import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel -import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.event.OlmEventContent import im.vector.matrix.android.internal.crypto.model.rest.ForwardedRoomKeyContent import im.vector.matrix.android.internal.crypto.model.rest.GossipingToDeviceObject @@ -161,7 +160,6 @@ class GossipingEventsEpoxyController @Inject constructor( } } is Success -> { - if (async.invoke().isEmpty()) { genericFooterItem { id("empty") diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailFragment.kt index e845061a70..bac98aeec6 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailFragment.kt @@ -25,7 +25,6 @@ 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.VectorBaseFragment -import im.vector.riotx.core.resources.ColorProvider import kotlinx.android.synthetic.main.fragment_generic_recycler.* import javax.inject.Inject @@ -64,5 +63,4 @@ class GossipingEventsPaperTrailFragment @Inject constructor( return super.onOptionsItemSelected(item) } } - } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt index f1c18806d9..b5e1303d89 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestEpoxyController.kt @@ -68,7 +68,6 @@ class KeyRequestEpoxyController @Inject constructor( } } is Success -> { - if (async.invoke().isEmpty()) { genericFooterItem { id("empty") @@ -124,7 +123,6 @@ class KeyRequestEpoxyController @Inject constructor( } } is Success -> { - if (async.invoke().isEmpty()) { genericFooterItem { id("empty") diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListViewModel.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListViewModel.kt index 6812467b96..3b273adaf4 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestListViewModel.kt @@ -73,7 +73,6 @@ class KeyRequestListViewModel @AssistedInject constructor(@Assisted initialState fun create(initialState: KeyRequestListViewState): KeyRequestListViewModel } - companion object : MvRxViewModelFactory { @JvmStatic diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt index b15d754181..4331c70078 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt @@ -16,9 +16,7 @@ package im.vector.riotx.features.settings.devtools -import android.content.Context import android.os.Bundle -import android.view.MenuItem import android.view.View import androidx.fragment.app.Fragment import androidx.viewpager2.adapter.FragmentStateAdapter @@ -45,7 +43,6 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() { private val pageAdapterListener = object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { - invalidateOptionsMenu() } @@ -92,8 +89,6 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() { private inner class KeyReqPagerAdapter(fa: Fragment) : FragmentStateAdapter(fa) { override fun getItemCount(): Int = 3 - - override fun createFragment(position: Int): Fragment { return when (position) { 0 -> { @@ -108,5 +103,4 @@ class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() { } } } - } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/OutgoingKeyRequestListFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/OutgoingKeyRequestListFragment.kt index d79bb9934b..658497c23d 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/OutgoingKeyRequestListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/OutgoingKeyRequestListFragment.kt @@ -17,7 +17,6 @@ package im.vector.riotx.features.settings.devtools import android.os.Bundle -import android.view.MenuItem import android.view.View import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -53,5 +52,4 @@ class OutgoingKeyRequestListFragment @Inject constructor( recyclerView.cleanup() // epoxyController.interactionListener = null } - } From 5e2f888eaf83ca44dc8a42b9f4e8bd25ce378f90 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 9 Mar 2020 17:08:43 +0100 Subject: [PATCH 021/156] Request secret from mobile to web --- .../crosssigning/CrossSigningService.kt | 4 +- .../internal/crypto/DefaultCryptoService.kt | 42 +++------- .../crypto/IncomingRoomKeyRequestManager.kt | 11 ++- .../DefaultCrossSigningService.kt | 77 +++++++++---------- .../internal/crypto/store/IMXCryptoStore.kt | 1 + .../crypto/store/db/RealmCryptoStore.kt | 27 ++++--- ...comingSASDefaultVerificationTransaction.kt | 3 + ...tgoingSASDefaultVerificationTransaction.kt | 3 + .../DefaultVerificationService.kt | 7 ++ .../DefaultVerificationTransaction.kt | 9 +++ .../SASDefaultVerificationTransaction.kt | 3 + .../DefaultQrCodeVerificationTransaction.kt | 3 + 12 files changed, 102 insertions(+), 88 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt index 181508402d..fe3f643124 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt @@ -69,6 +69,6 @@ interface CrossSigningService { otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult - fun onSecretSSKGossip(sskPrivateKey: String): Boolean - fun onSecretUSKGossip(uskPrivateKey: String): Boolean + fun onSecretSSKGossip(sskPrivateKey: String) + fun onSecretUSKGossip(uskPrivateKey: String) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 77dc9ca7fb..78e07c9428 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -49,7 +49,6 @@ import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAct import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmEncryptionFactory -import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.keysbackup.DefaultKeysBackupService @@ -756,54 +755,35 @@ internal class DefaultCryptoService @Inject constructor( return } - val encryptedEventContent = event.getClearContent().toModel() - ?: return Unit.also { - Timber.e("## onSecretSend() :Received malformed secret send event") - } - - val senderDevice = encryptedEventContent.senderKey - - val device = senderDevice?.let { cryptoStore.getUserDevice(event.senderId, it) } - ?: return Unit.also { - Timber.e("## processIncomingSecretShareRequest() : Received secret share request from unknown device $senderDevice") - } - - try { - val result = decryptEvent(event, "gossip") - event.mxDecryptionResult = OlmDecryptionResult( - payload = result.clearEvent, - senderKey = result.senderCurve25519Key, - keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) }, - forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain - ) - } catch (failure: Throwable) { - Timber.i("## onSecretSend() :Failed to decrypt secret share: $device") - } - val secretContent = event.getClearContent().toModel() ?: return val existingRequest = cryptoStore - .getPendingIncomingGossipingRequests() - .firstOrNull { it.requestId == secretContent.requestId } as? IncomingSecretShareRequest + .getOutgoingSecretKeyRequests().firstOrNull { it.requestId == secretContent.requestId } if (existingRequest == null) { - Timber.i("## onSecretSend() :Received secret from unknown request id: ${secretContent.requestId} from device ") + Timber.i("## onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}") return } - if (device.isBlocked || !device.isVerified) { - // Ignore secrets from this - Timber.i("## onSecretSend() :Received secret from untrusted/blocked device: $device") + val deviceId = event.mxDecryptionResult?.payload?.get("sender_device") as? String + + val device = deviceId?.let { cryptoStore.getUserDevice(event.senderId, it) } + + if (device == null || !device.isVerified || device.isBlocked) { + // Ignore secret from untrusted session? + Timber.i("## onSecretSend() :Received secret from untrusted device $deviceId ") return } when (existingRequest.secretName) { +// "m.key.self_signing", SELF_SIGNING_KEY_SSSS_NAME -> { if (device.trustLevel?.isLocallyVerified() == true) { crossSigningService.onSecretSSKGossip(secretContent.secretValue) return } } +// "m.key.user_signing", USER_SIGNING_KEY_SSSS_NAME -> { if (device.trustLevel?.isLocallyVerified() == true) { cryptoStore.storePrivateKeysInfo(null, null, secretContent.secretValue) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt index 3de8e44e3f..b1de344d03 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt @@ -65,9 +65,14 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( GossipingToDeviceObject.ACTION_SHARE_REQUEST -> { if (event.getClearType() == EventType.REQUEST_SECRET) { IncomingSecretShareRequest.fromEvent(event)?.let { - // save in DB - cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs) - receiveGossipingRequests.add(it) + if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) { + // ignore, it was sent by me as * + Timber.v("## onGossipingRequestEvent type ${event.type} ignore remote echo") + } else { + // save in DB + cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs) + receiveGossipingRequests.add(it) + } } } else if (event.getClearType() == EventType.ROOM_KEY_REQUEST) { IncomingRoomKeyRequest.fromEvent(event)?.let { 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 556aefa84b..f23534c334 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 @@ -43,7 +43,6 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.withoutPrefix import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import org.greenrobot.eventbus.EventBus import org.matrix.olm.OlmPkSigning import org.matrix.olm.OlmUtility @@ -300,56 +299,50 @@ internal class DefaultCrossSigningService @Inject constructor( cryptoStore.clearOtherUserTrust() } - override fun onSecretSSKGossip(sskPrivateKey: String): Boolean { + override fun onSecretSSKGossip(sskPrivateKey: String) { Timber.i("## CrossSigning - onSecretSSKGossip") - return runBlocking(coroutineDispatchers.crypto) { - val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return@runBlocking false + val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return - sskPrivateKey.fromBase64() - .let { privateKeySeed -> - val pkSigning = OlmPkSigning() - try { - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { - selfSigningPkSigning?.releaseSigning() - selfSigningPkSigning = pkSigning - Timber.i("## CrossSigning - Loading SSK success") - cryptoStore.storePrivateKeysInfo(null, null, sskPrivateKey) - return@runBlocking true - } else { - pkSigning.releaseSigning() - } - } catch (failure: Throwable) { + sskPrivateKey.fromBase64() + .let { privateKeySeed -> + val pkSigning = OlmPkSigning() + try { + if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { + selfSigningPkSigning?.releaseSigning() + selfSigningPkSigning = pkSigning + Timber.i("## CrossSigning - Loading SSK success") + cryptoStore.storePrivateKeysInfo(null, null, sskPrivateKey) + return + } else { pkSigning.releaseSigning() } + } catch (failure: Throwable) { + Timber.e("## CrossSigning - onSecretSSKGossip() ${failure.localizedMessage}") + pkSigning.releaseSigning() } - return@runBlocking false - } + } } - override fun onSecretUSKGossip(uskPrivateKey: String): Boolean { - Timber.i("## CrossSigning - onSecretUSKGossip") - return runBlocking(coroutineDispatchers.crypto) { - val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return@runBlocking false + override fun onSecretUSKGossip(uskPrivateKey: String) { + val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return - uskPrivateKey.fromBase64() - .let { privateKeySeed -> - val pkSigning = OlmPkSigning() - try { - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { - userPkSigning?.releaseSigning() - userPkSigning = pkSigning - Timber.i("## CrossSigning - Loading USK success") - cryptoStore.storePrivateKeysInfo(null, uskPrivateKey, null) - return@runBlocking true - } else { - pkSigning.releaseSigning() - } - } catch (failure: Throwable) { + uskPrivateKey.fromBase64() + .let { privateKeySeed -> + val pkSigning = OlmPkSigning() + try { + if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { + userPkSigning?.releaseSigning() + userPkSigning = pkSigning + Timber.i("## CrossSigning - Loading USK success") + cryptoStore.storePrivateKeysInfo(null, uskPrivateKey, null) + return + } else { pkSigning.releaseSigning() } + } catch (failure: Throwable) { + pkSigning.releaseSigning() } - return@runBlocking false - } + } } override fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?, @@ -767,7 +760,7 @@ internal class DefaultCrossSigningService @Inject constructor( Timber.d("## CrossSigning - onUsersDeviceUpdate for ${userIds.size} users") userIds.forEach { otherUserId -> checkUserTrust(otherUserId).let { - Timber.d("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}") + Timber.v("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}") setUserKeysAsTrusted(otherUserId, it.isVerified()) } @@ -775,7 +768,7 @@ internal class DefaultCrossSigningService @Inject constructor( val devices = cryptoStore.getUserDeviceList(otherUserId) devices?.forEach { device -> val updatedTrust = checkDeviceTrust(otherUserId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false) - Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust") + Timber.v("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust") cryptoStore.setDeviceTrust(otherUserId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified()) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt index c5d09bc111..5f118ff695 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt @@ -446,6 +446,7 @@ internal interface IMXCryptoStore { // Dev tools fun getOutgoingRoomKeyRequests(): List + fun getOutgoingSecretKeyRequests(): List fun getOutgoingSecretRequest(secretName: String): OutgoingSecretRequest? fun getIncomingRoomKeyRequests(): List fun getGossipingEventsTrail(): List diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index 2da182a358..d4e188b048 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -823,16 +823,13 @@ internal class RealmCryptoStore @Inject constructor( } override fun getOutgoingSecretRequest(secretName: String): OutgoingSecretRequest? { -// return monarchy.fetchAllCopiedSync { realm -> -// // realm.where() -// // .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.SECRET.name) -// // .equalTo(GossipingEventEntityFields.SENDER, credentials.userId) -// // }.mapNotNull { -// // ContentMapper.map(it.content)?.toModel() -// // }.firstOrNull { -// // it.secretName == secretName -// // } - TODO("not implemented") + return monarchy.fetchAllCopiedSync { realm -> + realm.where() + .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.SECRET.name) + .equalTo(OutgoingGossipingRequestEntityFields.REQUESTED_INFO_STR, secretName) + }.mapNotNull { + it.toOutgoingGossipingRequest() as? OutgoingSecretRequest + }.firstOrNull() } override fun getIncomingRoomKeyRequests(): List { @@ -1246,6 +1243,16 @@ internal class RealmCryptoStore @Inject constructor( }).filterNotNull() } + override fun getOutgoingSecretKeyRequests(): List { + return monarchy.fetchAllMappedSync({ realm -> + realm + .where(OutgoingGossipingRequestEntity::class.java) + .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.SECRET.name) + }, { entity -> + entity.toOutgoingGossipingRequest() as? OutgoingSecretRequest + }).filterNotNull() + } + override fun getCrossSigningInfo(userId: String): MXCrossSigningInfo? { return doRealmQueryAndCopy(realmConfiguration) { realm -> realm.where(CrossSigningInfoEntity::class.java) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt index f88e6e4f35..989ddc9804 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerif import im.vector.matrix.android.api.session.crypto.verification.SasMode import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import timber.log.Timber @@ -33,6 +34,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction( override val deviceId: String?, private val cryptoStore: IMXCryptoStore, crossSigningService: CrossSigningService, + outgoingGossipingRequestManager: OutgoingGossipingRequestManager, deviceFingerprint: String, transactionId: String, otherUserID: String, @@ -43,6 +45,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction( deviceId, cryptoStore, crossSigningService, + outgoingGossipingRequestManager, deviceFingerprint, transactionId, otherUserID, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt index 85ab8f0bf6..6c7e8f29d3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.crypto.verification.CancelCode import im.vector.matrix.android.api.session.crypto.verification.OutgoingSasVerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import timber.log.Timber @@ -30,6 +31,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction( deviceId: String?, cryptoStore: IMXCryptoStore, crossSigningService: CrossSigningService, + outgoingGossipingRequestManager: OutgoingGossipingRequestManager, deviceFingerprint: String, transactionId: String, otherUserId: String, @@ -40,6 +42,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction( deviceId, cryptoStore, crossSigningService, + outgoingGossipingRequestManager, deviceFingerprint, transactionId, otherUserId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt index d7000cfc97..f6364c2125 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt @@ -51,6 +51,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageVerificati import im.vector.matrix.android.api.session.room.model.message.ValidVerificationDone import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder +import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo @@ -88,6 +89,7 @@ internal class DefaultVerificationService @Inject constructor( @UserId private val userId: String, @DeviceId private val deviceId: String?, private val cryptoStore: IMXCryptoStore, + private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager, private val myDeviceInfoHolder: Lazy, private val deviceListManager: DeviceListManager, private val setDeviceVerificationAction: SetDeviceVerificationAction, @@ -505,6 +507,7 @@ internal class DefaultVerificationService @Inject constructor( deviceId, cryptoStore, crossSigningService, + outgoingGossipingRequestManager, myDeviceInfoHolder.get().myDevice.fingerprint()!!, startReq.transactionId, otherUserId, @@ -804,6 +807,7 @@ internal class DefaultVerificationService @Inject constructor( senderId, readyReq.fromDevice, crossSigningService, + outgoingGossipingRequestManager, cryptoStore, qrCodeData, userId, @@ -985,6 +989,7 @@ internal class DefaultVerificationService @Inject constructor( deviceId, cryptoStore, crossSigningService, + outgoingGossipingRequestManager, myDeviceInfoHolder.get().myDevice.fingerprint()!!, txID, otherUserId, @@ -1160,6 +1165,7 @@ internal class DefaultVerificationService @Inject constructor( deviceId, cryptoStore, crossSigningService, + outgoingGossipingRequestManager, myDeviceInfoHolder.get().myDevice.fingerprint()!!, transactionId, otherUserId, @@ -1296,6 +1302,7 @@ internal class DefaultVerificationService @Inject constructor( otherUserId, otherDeviceId, crossSigningService, + outgoingGossipingRequestManager, cryptoStore, qrCodeData, userId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt index 6396447352..83668284a4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt @@ -17,8 +17,11 @@ package im.vector.matrix.android.internal.crypto.verification import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService +import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState +import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel import timber.log.Timber @@ -29,6 +32,7 @@ import timber.log.Timber internal abstract class DefaultVerificationTransaction( private val setDeviceVerificationAction: SetDeviceVerificationAction, private val crossSigningService: CrossSigningService, + private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager, private val userId: String, override val transactionId: String, override val otherUserId: String, @@ -70,6 +74,11 @@ internal abstract class DefaultVerificationTransaction( // Mark my keys as trusted locally crossSigningService.markMyMasterKeyAsTrusted() } + + outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) + outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) +// outgoingGossipingRequestManager.sendSecretShareRequest("m.key.self_signing", mapOf(userId to listOf(otherDeviceId ?: "*"))) +// outgoingGossipingRequestManager.sendSecretShareRequest("m.key.user_signing", mapOf(userId to listOf(otherDeviceId ?: "*"))) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt index 18532a2e26..cfb3d7e38e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.crypto.verification.SasMode import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.model.MXKey import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore @@ -42,6 +43,7 @@ internal abstract class SASDefaultVerificationTransaction( open val deviceId: String?, private val cryptoStore: IMXCryptoStore, crossSigningService: CrossSigningService, + outgoingGossipingRequestManager: OutgoingGossipingRequestManager, private val deviceFingerprint: String, transactionId: String, otherUserId: String, @@ -50,6 +52,7 @@ internal abstract class SASDefaultVerificationTransaction( ) : DefaultVerificationTransaction( setDeviceVerificationAction, crossSigningService, + outgoingGossipingRequestManager, userId, transactionId, otherUserId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt index f56e261416..c55d95b36e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.crypto.verification.CancelCode import im.vector.matrix.android.api.session.crypto.verification.QrCodeVerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64 import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64Safe @@ -36,6 +37,7 @@ internal class DefaultQrCodeVerificationTransaction( override val otherUserId: String, override var otherDeviceId: String?, private val crossSigningService: CrossSigningService, + outgoingGossipingRequestManager: OutgoingGossipingRequestManager, private val cryptoStore: IMXCryptoStore, // Not null only if other user is able to scan QR code private val qrCodeData: QrCodeData?, @@ -45,6 +47,7 @@ internal class DefaultQrCodeVerificationTransaction( ) : DefaultVerificationTransaction( setDeviceVerificationAction, crossSigningService, + outgoingGossipingRequestManager, userId, transactionId, otherUserId, From 75549c41e0a03c78b4453f2ac7a2e12e79623a56 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 9 Mar 2020 17:51:10 +0100 Subject: [PATCH 022/156] View source in audit + clean --- .../android/api/session/events/model/Event.kt | 4 +-- .../internal/crypto/DefaultCryptoService.kt | 2 -- .../crypto/model/rest/SecretShareRequest.kt | 2 +- .../DefaultVerificationTransaction.kt | 9 +++-- .../GossipingEventsEpoxyController.kt | 35 +++++++++++++++++++ .../GossipingEventsPaperTrailFragment.kt | 27 +++++++++++--- 6 files changed, 65 insertions(+), 14 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 d131960893..a60d0fd9ac 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 @@ -142,12 +142,12 @@ data class Event( } fun toContentStringWithIndent(): String { - val contentMap = toContent().toMutableMap() + val contentMap = toContent() return JSONObject(contentMap).toString(4) } fun toClearContentStringWithIndent(): String? { - val contentMap = this.mxDecryptionResult?.payload?.toMutableMap() + val contentMap = this.mxDecryptionResult?.payload val adapter = MoshiProvider.providesMoshi().adapter(Map::class.java) return contentMap?.let { JSONObject(adapter.toJson(it)).toString(4) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 78e07c9428..6ab950a1a0 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -776,14 +776,12 @@ internal class DefaultCryptoService @Inject constructor( } when (existingRequest.secretName) { -// "m.key.self_signing", SELF_SIGNING_KEY_SSSS_NAME -> { if (device.trustLevel?.isLocallyVerified() == true) { crossSigningService.onSecretSSKGossip(secretContent.secretValue) return } } -// "m.key.user_signing", USER_SIGNING_KEY_SSSS_NAME -> { if (device.trustLevel?.isLocallyVerified() == true) { cryptoStore.storePrivateKeysInfo(null, null, secretContent.secretValue) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SecretShareRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SecretShareRequest.kt index 093387926e..86ae042166 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SecretShareRequest.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SecretShareRequest.kt @@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass * Class representing a room key request content */ @JsonClass(generateAdapter = true) -internal data class SecretShareRequest( +data class SecretShareRequest( @Json(name = "action") override val action: String? = GossipingToDeviceObject.ACTION_SHARE_REQUEST, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt index 83668284a4..2563748662 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt @@ -74,11 +74,10 @@ internal abstract class DefaultVerificationTransaction( // Mark my keys as trusted locally crossSigningService.markMyMasterKeyAsTrusted() } - - outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) - outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) -// outgoingGossipingRequestManager.sendSecretShareRequest("m.key.self_signing", mapOf(userId to listOf(otherDeviceId ?: "*"))) -// outgoingGossipingRequestManager.sendSecretShareRequest("m.key.user_signing", mapOf(userId to listOf(otherDeviceId ?: "*"))) + if (!crossSigningService.canCrossSign()) { + outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) + outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) + } } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsEpoxyController.kt index 5cce238e82..8760f9ebb2 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsEpoxyController.kt @@ -25,9 +25,11 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.internal.crypto.model.event.OlmEventContent +import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent import im.vector.matrix.android.internal.crypto.model.rest.ForwardedRoomKeyContent import im.vector.matrix.android.internal.crypto.model.rest.GossipingToDeviceObject import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShareRequest +import im.vector.matrix.android.internal.crypto.model.rest.SecretShareRequest import im.vector.riotx.R import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.core.epoxy.loadingItem @@ -35,6 +37,7 @@ import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.DateProvider import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.core.ui.list.GenericItem import im.vector.riotx.core.ui.list.genericFooterItem import im.vector.riotx.core.ui.list.genericItem import im.vector.riotx.core.ui.list.genericItemHeader @@ -81,6 +84,7 @@ class GossipingEventsEpoxyController @Inject constructor( eventList.forEachIndexed { _, event -> genericItem { id(event.hashCode()) + itemClickAction(GenericItem.Action("view").apply { perform = Runnable { interactionListener?.didTap(event) } }) title( if (event.isEncrypted()) { "${event.getClearType()} [encrypted]" @@ -133,6 +137,37 @@ class GossipingEventsEpoxyController @Inject constructor( textStyle = "bold" } +" ${encryptedContent?.senderKey}" + } else if (event.getClearType() == EventType.SEND_SECRET) { + val content = event.getClearContent().toModel() + + span("\nrequestId:") { + textStyle = "bold" + } + +" ${content?.requestId}" + span("\nFrom Device:") { + textStyle = "bold" + } + +" ${event.mxDecryptionResult?.payload?.get("sender_device")}" + } else if (event.getClearType() == EventType.REQUEST_SECRET) { + val content = event.getClearContent().toModel() + span("\nreqId:") { + textStyle = "bold" + } + +" ${content?.requestId}" + span("\naction:") { + textStyle = "bold" + } + +" ${content?.action}" + if (content?.action == GossipingToDeviceObject.ACTION_SHARE_REQUEST) { + span("\nsecretName:") { + textStyle = "bold" + } + +" ${content.secretName}" + } + span("\nrequestedBy: ") { + textStyle = "bold" + } + +"${content?.requestingDeviceId}" } } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailFragment.kt index bac98aeec6..48e48c125c 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailFragment.kt @@ -21,17 +21,22 @@ import android.view.MenuItem import android.view.View import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import im.vector.matrix.android.api.session.events.model.Event 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.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 GossipingEventsPaperTrailFragment @Inject constructor( val viewModelFactory: GossipingEventsPaperTrailViewModel.Factory, - private val epoxyController: GossipingEventsEpoxyController -) : VectorBaseFragment() { + private val epoxyController: GossipingEventsEpoxyController, + private val colorProvider: ColorProvider +) : VectorBaseFragment(), GossipingEventsEpoxyController.InteractionListener { override fun getLayoutResId() = R.layout.fragment_generic_recycler @@ -46,13 +51,13 @@ class GossipingEventsPaperTrailFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) recyclerView.configureWith(epoxyController, showDivider = true) -// epoxyController.interactionListener = this + epoxyController.interactionListener = this } override fun onDestroyView() { super.onDestroyView() recyclerView.cleanup() -// epoxyController.interactionListener = null + epoxyController.interactionListener = null } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -63,4 +68,18 @@ class GossipingEventsPaperTrailFragment @Inject constructor( return super.onOptionsItemSelected(item) } } + + override fun didTap(event: Event) { + if (event.isEncrypted()) { + event.toClearContentStringWithIndent() + } else { + event.toContentStringWithIndent() + }?.let { + JSonViewerDialog.newInstance( + it, + -1, + jsonViewerStyler(colorProvider) + ).show(childFragmentManager, "JSON_VIEWER") + } + } } From 6933159245dda1c4a37723974f9d874422c58158 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 10 Mar 2020 09:58:10 +0100 Subject: [PATCH 023/156] Remove refresh menu --- .../devtools/GossipingEventsPaperTrailFragment.kt | 12 ------------ .../devtools/IncomingKeyRequestListFragment.kt | 2 -- .../settings/devtools/KeyRequestsFragment.kt | 1 - vector/src/main/res/menu/menu_common_gossiping.xml | 10 ---------- 4 files changed, 25 deletions(-) delete mode 100644 vector/src/main/res/menu/menu_common_gossiping.xml diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailFragment.kt index 48e48c125c..d7ffd8adfa 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/GossipingEventsPaperTrailFragment.kt @@ -17,7 +17,6 @@ package im.vector.riotx.features.settings.devtools import android.os.Bundle -import android.view.MenuItem import android.view.View import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -40,8 +39,6 @@ class GossipingEventsPaperTrailFragment @Inject constructor( override fun getLayoutResId() = R.layout.fragment_generic_recycler - override fun getMenuRes(): Int = R.menu.menu_common_gossiping - private val viewModel: GossipingEventsPaperTrailViewModel by fragmentViewModel(GossipingEventsPaperTrailViewModel::class) override fun invalidate() = withState(viewModel) { state -> @@ -60,15 +57,6 @@ class GossipingEventsPaperTrailFragment @Inject constructor( epoxyController.interactionListener = null } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == R.id.refresh) { - viewModel.refresh() - return true - } else { - return super.onOptionsItemSelected(item) - } - } - override fun didTap(event: Event) { if (event.isEncrypted()) { event.toClearContentStringWithIndent() diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/IncomingKeyRequestListFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/IncomingKeyRequestListFragment.kt index 653fbe36af..7a5b08752e 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/IncomingKeyRequestListFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/IncomingKeyRequestListFragment.kt @@ -46,12 +46,10 @@ class IncomingKeyRequestListFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) recyclerView.configureWith(epoxyController, showDivider = true) -// epoxyController.interactionListener = this } override fun onDestroyView() { super.onDestroyView() recyclerView.cleanup() -// epoxyController.interactionListener = null } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt index 4331c70078..76174558b5 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devtools/KeyRequestsFragment.kt @@ -32,7 +32,6 @@ import javax.inject.Inject class KeyRequestsFragment @Inject constructor() : VectorBaseFragment() { override fun getLayoutResId(): Int = R.layout.fragment_devtool_keyrequests - override fun getMenuRes(): Int = R.menu.menu_common_gossiping override fun onResume() { super.onResume() diff --git a/vector/src/main/res/menu/menu_common_gossiping.xml b/vector/src/main/res/menu/menu_common_gossiping.xml deleted file mode 100644 index 08b23c760c..0000000000 --- a/vector/src/main/res/menu/menu_common_gossiping.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - From 009d691d5b4391d11ef124928166c89c68830a60 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 12 Mar 2020 15:39:43 +0100 Subject: [PATCH 024/156] post merge fix --- vector/src/main/res/values/strings_riotX.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 643160ef21..45fc3a3781 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -25,7 +25,6 @@ When rooms are upgraded Troubleshoot Set notification importance by event - Refresh From b8a9397e730f1047fbc328200f9d638b2c7a2608 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 12 Mar 2020 15:56:46 +0100 Subject: [PATCH 025/156] hide reRequest behind developer mode --- .../action/MessageActionsViewModel.kt | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 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 da2da452ab..5212e1469d 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 @@ -297,20 +297,20 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } } - if (timelineEvent.isEncrypted() && timelineEvent.root.mCryptoError != null) { - val keysBackupService = session.cryptoService().keysBackupService() - if (keysBackupService.state == KeysBackupState.NotTrusted - || (keysBackupService.state == KeysBackupState.ReadyToBackUp - && keysBackupService.canRestoreKeys()) - ) { - add(EventSharedAction.UseKeyBackup) - } - if (session.cryptoService().getCryptoDeviceInfo(session.myUserId).size > 1) { - add(EventSharedAction.ReRequestKey(timelineEvent.eventId)) - } - } - if (vectorPreferences.developerMode()) { + if (timelineEvent.isEncrypted() && timelineEvent.root.mCryptoError != null) { + val keysBackupService = session.cryptoService().keysBackupService() + if (keysBackupService.state == KeysBackupState.NotTrusted + || (keysBackupService.state == KeysBackupState.ReadyToBackUp + && keysBackupService.canRestoreKeys()) + ) { + add(EventSharedAction.UseKeyBackup) + } + if (session.cryptoService().getCryptoDeviceInfo(session.myUserId).size > 1) { + add(EventSharedAction.ReRequestKey(timelineEvent.eventId)) + } + } + add(EventSharedAction.ViewSource(timelineEvent.root.toContentStringWithIndent())) if (timelineEvent.isEncrypted() && timelineEvent.root.mxDecryptionResult != null) { val decryptedContent = timelineEvent.root.toClearContentStringWithIndent() From 1bf8fef2924579337547583916a42497fd787451 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 12 Mar 2020 15:57:01 +0100 Subject: [PATCH 026/156] Fix realm migration --- .../crypto/store/db/RealmCryptoStoreMigration.kt | 14 ++++++++++---- .../crypto/store/db/RealmCryptoStoreModule.kt | 4 ---- .../crypto/store/db/model/GossipingEventEntity.kt | 4 ++-- .../db/model/IncomingGossipingRequestEntity.kt | 2 +- .../db/model/OutgoingGossipingRequestEntity.kt | 6 +----- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt index a4c4004a9d..932316aab8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -151,14 +151,18 @@ internal object RealmCryptoStoreMigration : RealmMigration { .addIndex(GossipingEventEntityFields.TYPE) .addField(GossipingEventEntityFields.CONTENT, String::class.java) .addField(GossipingEventEntityFields.SENDER, String::class.java) + .addIndex(GossipingEventEntityFields.SENDER) .addField(GossipingEventEntityFields.DECRYPTION_RESULT_JSON, String::class.java) .addField(GossipingEventEntityFields.DECRYPTION_ERROR_CODE, String::class.java) .addField(GossipingEventEntityFields.AGE_LOCAL_TS, Long::class.java) - .addField(GossipingEventEntityFields.SEND_STATE_STR, Long::class.java) + .setNullable(GossipingEventEntityFields.AGE_LOCAL_TS, true) + .addField(GossipingEventEntityFields.SEND_STATE_STR, String::class.java) realm.schema.create("IncomingGossipingRequestEntity") .addField(IncomingGossipingRequestEntityFields.REQUEST_ID, String::class.java) + .addIndex(IncomingGossipingRequestEntityFields.REQUEST_ID) .addField(IncomingGossipingRequestEntityFields.TYPE_STR, String::class.java) + .addIndex(IncomingGossipingRequestEntityFields.TYPE_STR) .addField(IncomingGossipingRequestEntityFields.OTHER_USER_ID, String::class.java) .addField(IncomingGossipingRequestEntityFields.REQUESTED_INFO_STR, String::class.java) .addField(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, String::class.java) @@ -168,9 +172,11 @@ internal object RealmCryptoStoreMigration : RealmMigration { realm.schema.create("OutgoingGossipingRequestEntity") .addField(OutgoingGossipingRequestEntityFields.REQUEST_ID, String::class.java) - .addField(OutgoingGossipingRequestEntityFields.TYPE_STR, String::class.java) - .addField(OutgoingGossipingRequestEntityFields.REQUESTED_INFO_STR, String::class.java) - .addField(OutgoingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java) + .addIndex(OutgoingGossipingRequestEntityFields.REQUEST_ID) .addField(OutgoingGossipingRequestEntityFields.RECIPIENTS_DATA, String::class.java) + .addField(OutgoingGossipingRequestEntityFields.REQUESTED_INFO_STR, String::class.java) + .addField(OutgoingGossipingRequestEntityFields.TYPE_STR, String::class.java) + .addIndex(OutgoingGossipingRequestEntityFields.TYPE_STR) + .addField(OutgoingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreModule.kt index 0d48f4671b..3da91c6268 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreModule.kt @@ -39,17 +39,13 @@ import io.realm.annotations.RealmModule CryptoMetadataEntity::class, CryptoRoomEntity::class, DeviceInfoEntity::class, -// IncomingRoomKeyRequestEntity::class, KeysBackupDataEntity::class, OlmInboundGroupSessionEntity::class, OlmSessionEntity::class, -// OutgoingRoomKeyRequestEntity::class, UserEntity::class, KeyInfoEntity::class, CrossSigningInfoEntity::class, TrustLevelEntity::class, -// IncomingSecretRequestEntity::class, -// OutgoingSecretRequestEntity::class, GossipingEventEntity::class, IncomingGossipingRequestEntity::class, OutgoingGossipingRequestEntity::class diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/GossipingEventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/GossipingEventEntity.kt index 1c5c4d267b..131ddfafc6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/GossipingEventEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/GossipingEventEntity.kt @@ -33,7 +33,7 @@ import timber.log.Timber * (room key request, or sss secret sharing, as well as cancellations) * */ -internal open class GossipingEventEntity(@Index var type: String = "", +internal open class GossipingEventEntity(@Index var type: String? = "", var content: String? = null, @Index var sender: String? = null, var decryptionResultJson: String? = null, @@ -66,7 +66,7 @@ internal open class GossipingEventEntity(@Index var type: String = "", fun toModel(): Event { return Event( - type = this.type, + type = this.type ?: "", content = ContentMapper.map(this.content), senderId = this.sender ).also { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt index 1e8f4e6258..bb7d497a0e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/IncomingGossipingRequestEntity.kt @@ -26,7 +26,7 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import io.realm.RealmObject import io.realm.annotations.Index -internal open class IncomingGossipingRequestEntity(@Index var requestId: String = "", +internal open class IncomingGossipingRequestEntity(@Index var requestId: String? = "", @Index var typeStr: String? = null, var otherUserId: String? = null, var requestedInfoStr: String? = null, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt index 0bce2a1528..4a14ad3baa 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt @@ -26,18 +26,14 @@ import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest import im.vector.matrix.android.internal.crypto.OutgoingSecretRequest import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.di.MoshiProvider -import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.Index internal open class OutgoingGossipingRequestEntity( @Index var requestId: String? = null, -// var cancellationTxnId: String? = null, - // Serialized Json var recipientsData: String? = null, var requestedInfoStr: String? = null, - @Index var typeStr: String? = null, - var sourceEvents: RealmList = RealmList() + @Index var typeStr: String? = null ) : RealmObject() { fun getRequestedSecretName(): String? = if (type == GossipRequestType.SECRET) { From 8051d9e3be792356bbc752d9e26ad1efcb95d114 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 12 Mar 2020 16:09:41 +0100 Subject: [PATCH 027/156] cleaning --- .../crypto/gossiping/KeyShareTests.kt | 11 --- .../crypto/IncomingRoomKeyRequestManager.kt | 12 +-- .../internal/crypto/ShareRequestState.kt | 79 ------------------- .../internal/crypto/store/IMXCryptoStore.kt | 36 --------- .../crypto/store/db/RealmCryptoStore.kt | 6 +- .../model/OutgoingGossipingRequestEntity.kt | 6 +- 6 files changed, 15 insertions(+), 135 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareRequestState.kt diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt index 01f6ab0f22..1dc0907b65 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt @@ -48,17 +48,6 @@ class KeyShareTests : InstrumentedTest { private val mTestHelper = CommonTestHelper(context()) -// @Before -// fun setup() { -// mockkStatic(Log::class) -// every { Log.v(any(), any()) } returns 0 -// every { Log.d(any(), any()) } returns 0 -// every { Log.i(any(), any()) } returns 0 -// every { Log.e(any(), any()) } returns 0 -// // every { Log.println(any(), any(), any()) } returns 0 -// // every { Log.wtf(any(), any(), any()) } returns 0 -// } - @Test fun test_DoNotSelfShareIfNotTrusted() { val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt index b1de344d03..0bb89154f1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt @@ -41,14 +41,14 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( // list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations // we received in the current sync. - private val receiveGossipingRequests = ArrayList() + private val receivedGossipingRequests = ArrayList() private val receivedRequestCancellations = ArrayList() // the listeners private val gossipingRequestListeners: MutableSet = HashSet() init { - receiveGossipingRequests.addAll(cryptoStore.getPendingIncomingGossipingRequests()) + receivedGossipingRequests.addAll(cryptoStore.getPendingIncomingGossipingRequests()) } /** @@ -71,7 +71,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( } else { // save in DB cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs) - receiveGossipingRequests.add(it) + receivedGossipingRequests.add(it) } } } else if (event.getClearType() == EventType.ROOM_KEY_REQUEST) { @@ -81,7 +81,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( Timber.v("## onGossipingRequestEvent type ${event.type} ignore remote echo") } else { cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs) - receiveGossipingRequests.add(it) + receivedGossipingRequests.add(it) } } } @@ -105,8 +105,8 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( fun processReceivedGossipingRequests() { Timber.v("## processReceivedGossipingRequests()") - val roomKeyRequestsToProcess = receiveGossipingRequests.toList() - receiveGossipingRequests.clear() + val roomKeyRequestsToProcess = receivedGossipingRequests.toList() + receivedGossipingRequests.clear() for (request in roomKeyRequestsToProcess) { if (request is IncomingRoomKeyRequest) { processIncomingRoomKeyRequest(request) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareRequestState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareRequestState.kt deleted file mode 100644 index 7d3ff72e9b..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareRequestState.kt +++ /dev/null @@ -1,79 +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 - -/** - * possible states for a room key request - * - * - * The state machine looks like: - *
- *
- *      |
- *      V
- *    UNSENT  -----------------------------+
- *      |                                  |
- *      | (send successful)                | (cancellation requested)
- *      V                                  |
- *     SENT                                |
- *      |--------------------------------  |  --------------+
- *      |                                  |                |
- *      |                                  |                | (cancellation requested with intent
- *      |                                  |                | to resend a new request)
- *      | (cancellation requested)         |                |
- *      V                                  |                V
- *  CANCELLATION_PENDING                   | CANCELLATION_PENDING_AND_WILL_RESEND
- *      |                                  |                |
- *      | (cancellation sent)              |                | (cancellation sent. Create new request
- *      |                                  |                |  in the UNSENT state)
- *      V                                  |                |
- *  (deleted)  <---------------------------+----------------+
- *  
- */ - -enum class ShareRequestState { - /** - * request not yet sent - */ - UNSENT, - /** - * request sent, awaiting reply - */ - SENT, - /** - * reply received, cancellation not yet sent - */ - CANCELLATION_PENDING, - /** - * Cancellation not yet sent, once sent, a new request will be done - */ - CANCELLATION_PENDING_AND_WILL_RESEND, - /** - * sending failed - */ - FAILED; - - companion object { - fun from(state: Int) = when (state) { - 0 -> UNSENT - 1 -> SENT - 2 -> CANCELLATION_PENDING - 3 -> CANCELLATION_PENDING_AND_WILL_RESEND - else /*4*/ -> FAILED - } - } -} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt index 5f118ff695..29efc02fdb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt @@ -361,42 +361,6 @@ internal interface IMXCryptoStore { fun getOrAddOutgoingSecretShareRequest(secretName: String, recipients: Map>): OutgoingSecretRequest? fun saveGossipingEvent(event: Event) - /** - * Look for room key requests by state. - * - * @param states the states - * @return an OutgoingRoomKeyRequest or null - */ -// fun getOutgoingRoomKeyRequestByState(states: Set): OutgoingRoomKeyRequest? -// fun getOutgoingSecretShareRequestByState(states: Set): OutgoingSecretRequest? - - /** - * Update an existing outgoing request. - * - * @param request the request - */ -// fun updateOutgoingRoomKeyRequest(request: OutgoingRoomKeyRequest) - - /** - * Delete an outgoing room key request. - * - * @param transactionId the transaction id. - */ -// fun deleteOutgoingRoomKeyRequest(transactionId: String) - - /** - * Store an incomingRoomKeyRequest instance - * - * @param incomingRoomKeyRequest the incoming key request - */ -// fun storeIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingRoomKeyRequest?) - - /** - * Delete an incomingRoomKeyRequest instance - * - * @param incomingRoomKeyRequest the incoming key request - */ -// fun deleteIncomingRoomKeyRequest(incomingRoomKeyRequest: IncomingShareRequestCommon) fun updateGossipingRequestState(request: IncomingShareRequestCommon, state: GossipingRequestState) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index d4e188b048..542b8a25de 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -1240,7 +1240,8 @@ internal class RealmCryptoStore @Inject constructor( .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) }, { entity -> entity.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest - }).filterNotNull() + }) + .filterNotNull() } override fun getOutgoingSecretKeyRequests(): List { @@ -1250,7 +1251,8 @@ internal class RealmCryptoStore @Inject constructor( .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.SECRET.name) }, { entity -> entity.toOutgoingGossipingRequest() as? OutgoingSecretRequest - }).filterNotNull() + }) + .filterNotNull() } override fun getCrossSigningInfo(userId: String): MXCrossSigningInfo? { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt index 4a14ad3baa..19049c099c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/OutgoingGossipingRequestEntity.kt @@ -66,7 +66,11 @@ internal open class OutgoingGossipingRequestEntity( companion object { private val recipientsDataMapper: JsonAdapter>> = - MoshiProvider.providesMoshi().adapter>>(Types.newParameterizedType(Map::class.java, String::class.java, List::class.java)) + MoshiProvider + .providesMoshi() + .adapter>>( + Types.newParameterizedType(Map::class.java, String::class.java, List::class.java) + ) } fun toOutgoingGossipingRequest(): OutgoingGossipingRequest { From b71d8185a2c387a64bd8e1b0a4eecb16231ace9e Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 13 Mar 2020 11:57:43 +0100 Subject: [PATCH 028/156] Fix / gossiping sent to soon results in not getting keys Overall improovment of logs --- .../internal/crypto/DefaultCryptoService.kt | 19 +++-------- .../internal/crypto/DeviceListManager.kt | 6 ++-- .../crypto/OutgoingGossipingRequestManager.kt | 4 +++ .../DefaultCrossSigningService.kt | 21 ++++++++---- .../internal/crypto/store/IMXCryptoStore.kt | 3 ++ .../crypto/store/db/RealmCryptoStore.kt | 16 ++++++++++ .../tasks/RoomVerificationUpdateTask.kt | 2 +- .../DefaultVerificationTransaction.kt | 21 +++++++----- .../verification/VerificationTransport.kt | 3 +- .../VerificationTransportRoomMessage.kt | 32 ++++++++++++++++--- .../VerificationTransportToDevice.kt | 3 +- .../DefaultQrCodeVerificationTransaction.kt | 5 ++- .../room/EventRelationsAggregationTask.kt | 2 +- .../riotx/core/platform/VectorBaseFragment.kt | 2 +- 14 files changed, 93 insertions(+), 46 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 6ab950a1a0..9e702ee9ac 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -743,6 +743,7 @@ internal class DefaultCryptoService @Inject constructor( } private fun onSecretSendReceived(event: Event) { + Timber.i("## onSecretSend() : onSecretSendReceived ${event.content?.get("sender_key")}") if (!event.isEncrypted()) { // secret send messages must be encrypted Timber.e("## onSecretSend() :Received unencrypted secret send event") @@ -765,30 +766,18 @@ internal class DefaultCryptoService @Inject constructor( return } - val deviceId = event.mxDecryptionResult?.payload?.get("sender_device") as? String - - val device = deviceId?.let { cryptoStore.getUserDevice(event.senderId, it) } - - if (device == null || !device.isVerified || device.isBlocked) { - // Ignore secret from untrusted session? - Timber.i("## onSecretSend() :Received secret from untrusted device $deviceId ") - return - } - when (existingRequest.secretName) { SELF_SIGNING_KEY_SSSS_NAME -> { - if (device.trustLevel?.isLocallyVerified() == true) { crossSigningService.onSecretSSKGossip(secretContent.secretValue) return - } } USER_SIGNING_KEY_SSSS_NAME -> { - if (device.trustLevel?.isLocallyVerified() == true) { - cryptoStore.storePrivateKeysInfo(null, null, secretContent.secretValue) - } + crossSigningService.onSecretUSKGossip(secretContent.secretValue) + return } else -> { // Ask to application layer? + Timber.v("## onSecretSend() : secret not handled by SDK") } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt index 04b301ba9e..37a5ee18e1 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt @@ -361,13 +361,13 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM // Handle cross signing keys update val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also { - Timber.d("## CrossSigning : Got keys for $userId : MSK ${it?.unpaddedBase64PublicKey}") + Timber.v("## CrossSigning : Got keys for $userId : MSK ${it?.unpaddedBase64PublicKey}") } val selfSigningKey = response.selfSigningKeys?.get(userId)?.toCryptoModel()?.also { - Timber.d("## CrossSigning : Got keys for $userId : SSK ${it.unpaddedBase64PublicKey}") + Timber.v("## CrossSigning : Got keys for $userId : SSK ${it.unpaddedBase64PublicKey}") } val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also { - Timber.d("## CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}") + Timber.v("## CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}") } cryptoStore.storeUserCrossSigningKeys( userId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt index c78b68b3a0..7c83ccc9bf 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt @@ -34,6 +34,7 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.startChain import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import timber.log.Timber import java.util.concurrent.TimeUnit @@ -75,6 +76,9 @@ internal class OutgoingGossipingRequestManager @Inject constructor( fun sendSecretShareRequest(secretName: String, recipients: Map>) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + // A bit dirty, but for better stability give other party some time to mark + // devices trusted :/ + delay(1500) cryptoStore.getOrAddOutgoingSecretShareRequest(secretName, recipients)?.let { // TODO check if there is already one that is being sent? if (it.state == OutgoingGossipingRequestState.SENDING || it.state == OutgoingGossipingRequestState.SENT) { 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 f23534c334..74a4c6bee8 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 @@ -301,7 +301,9 @@ internal class DefaultCrossSigningService @Inject constructor( override fun onSecretSSKGossip(sskPrivateKey: String) { Timber.i("## CrossSigning - onSecretSSKGossip") - val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return + val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { + Timber.e("## CrossSigning - onSecretSSKGossip() received secret but public key is not known") + } sskPrivateKey.fromBase64() .let { privateKeySeed -> @@ -311,9 +313,10 @@ internal class DefaultCrossSigningService @Inject constructor( selfSigningPkSigning?.releaseSigning() selfSigningPkSigning = pkSigning Timber.i("## CrossSigning - Loading SSK success") - cryptoStore.storePrivateKeysInfo(null, null, sskPrivateKey) + cryptoStore.storeSSKPrivateKey(sskPrivateKey) return } else { + Timber.e("## CrossSigning - onSecretSSKGossip() private key do not match public key") pkSigning.releaseSigning() } } catch (failure: Throwable) { @@ -324,19 +327,23 @@ internal class DefaultCrossSigningService @Inject constructor( } override fun onSecretUSKGossip(uskPrivateKey: String) { - val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return + Timber.i("## CrossSigning - onSecretUSKGossip") + val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { + Timber.e("## CrossSigning - onSecretUSKGossip() received secret but public key is not knwow ") + } uskPrivateKey.fromBase64() .let { privateKeySeed -> val pkSigning = OlmPkSigning() try { - if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { + if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { userPkSigning?.releaseSigning() userPkSigning = pkSigning Timber.i("## CrossSigning - Loading USK success") - cryptoStore.storePrivateKeysInfo(null, uskPrivateKey, null) + cryptoStore.storeUSKPrivateKey(uskPrivateKey) return } else { + Timber.e("## CrossSigning - onSecretUSKGossip() private key do not match public key") pkSigning.releaseSigning() } } catch (failure: Throwable) { @@ -444,7 +451,7 @@ internal class DefaultCrossSigningService @Inject constructor( * Will not force a download of the key, but will verify signatures trust chain */ override fun checkUserTrust(otherUserId: String): UserTrustResult { - Timber.d("## CrossSigning checkUserTrust for $otherUserId") + Timber.v("## CrossSigning checkUserTrust for $otherUserId") if (otherUserId == userId) { return checkSelfTrust() } @@ -799,7 +806,7 @@ internal class DefaultCrossSigningService @Inject constructor( users.forEach { cryptoStore.getUserDeviceList(it)?.forEach { device -> val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false) - Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust") + Timber.v("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust") cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified()) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt index 29efc02fdb..5594cbdf17 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt @@ -398,6 +398,9 @@ internal interface IMXCryptoStore { fun markMyMasterKeyAsLocallyTrusted(trusted: Boolean) fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) + fun storeSSKPrivateKey(ssk: String?) + fun storeUSKPrivateKey(usk: String?) + fun getCrossSigningPrivateKeys(): PrivateKeysInfo? fun setUserKeysAsTrusted(userId: String, trusted: Boolean = true) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index 542b8a25de..20cc327b3d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -371,7 +371,23 @@ internal class RealmCryptoStore @Inject constructor( doRealmTransaction(realmConfiguration) { realm -> realm.where().findFirst()?.apply { xSignMasterPrivateKey = msk + xSignUserPrivateKey = usk xSignSelfSignedPrivateKey = ssk + } + } + } + + override fun storeSSKPrivateKey(ssk: String?) { + doRealmTransaction(realmConfiguration) { realm -> + realm.where().findFirst()?.apply { + xSignSelfSignedPrivateKey = ssk + } + } + } + + override fun storeUSKPrivateKey(usk: String?) { + doRealmTransaction(realmConfiguration) { realm -> + realm.where().findFirst()?.apply { xSignUserPrivateKey = usk } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt index 53ccb8b6f0..014234885a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt @@ -60,7 +60,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor( // TODO ignore initial sync or back pagination? params.events.forEach { event -> - Timber.d("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}") + Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.type} from ${event.senderId}") // If the request is in the future by more than 5 minutes or more than 10 minutes in the past, // the message should be ignored by the receiver. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt index 2563748662..c4e5ed1e7e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt @@ -58,6 +58,13 @@ internal abstract class DefaultVerificationTransaction( protected fun trust(canTrustOtherUserMasterKey: Boolean, toVerifyDeviceIds: List, eventuallyMarkMyMasterKeyAsTrusted: Boolean) { + Timber.d("## Verification: trust ($otherUserId,$otherDeviceId) , verifiedDevices:$toVerifyDeviceIds, Mark myMSK trusted $eventuallyMarkMyMasterKeyAsTrusted") + + // TODO what if the otherDevice is not in this list? and should we + toVerifyDeviceIds.forEach { + setDeviceVerified(otherUserId, it) + } + // If not me sign his MSK and upload the signature if (canTrustOtherUserMasterKey) { // we should trust this master key @@ -74,10 +81,6 @@ internal abstract class DefaultVerificationTransaction( // Mark my keys as trusted locally crossSigningService.markMyMasterKeyAsTrusted() } - if (!crossSigningService.canCrossSign()) { - outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) - outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) - } } } @@ -91,11 +94,13 @@ internal abstract class DefaultVerificationTransaction( }) } - // TODO what if the otherDevice is not in this list? and should we - toVerifyDeviceIds.forEach { - setDeviceVerified(otherUserId, it) + transport.done(transactionId) { + if (otherUserId == userId) { + outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) + outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) + } } - transport.done(transactionId) + state = VerificationTxState.Verified } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransport.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransport.kt index cedcf2865d..75ffa5e082 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransport.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransport.kt @@ -46,7 +46,8 @@ internal interface VerificationTransport { otherUserDeviceId: String?, code: CancelCode) - fun done(transactionId: String) + fun done(transactionId: String, + onDone: (() -> Unit)?) /** * Creates an accept message suitable for this transport diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt index 7a6e3b40ac..b7b7335011 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportRoomMessage.kt @@ -22,8 +22,8 @@ import androidx.work.ExistingWorkPolicy import androidx.work.Operation import androidx.work.WorkInfo import im.vector.matrix.android.R -import im.vector.matrix.android.api.session.crypto.verification.ValidVerificationInfoRequest import im.vector.matrix.android.api.session.crypto.verification.CancelCode +import im.vector.matrix.android.api.session.crypto.verification.ValidVerificationInfoRequest import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Event @@ -107,7 +107,7 @@ internal class VerificationTransportRoomMessage( // }, listenerExecutor) val workLiveData = workManagerProvider.workManager - .getWorkInfosForUniqueWorkLiveData("${roomId}_VerificationWork") + .getWorkInfosForUniqueWorkLiveData(uniqueQueueName()) val observer = object : Observer> { override fun onChanged(workInfoList: List?) { @@ -228,7 +228,8 @@ internal class VerificationTransportRoomMessage( enqueueSendWork(workerParams) } - override fun done(transactionId: String) { + override fun done(transactionId: String, + onDone: (() -> Unit)?) { Timber.d("## SAS sending done for $transactionId") val event = createEventAndLocalEcho( type = EventType.KEY_VERIFICATION_DONE, @@ -244,7 +245,26 @@ internal class VerificationTransportRoomMessage( sessionId = sessionId, event = event )) - enqueueSendWork(workerParams) + val enqueueInfo = enqueueSendWork(workerParams) + + val workLiveData = workManagerProvider.workManager + .getWorkInfosForUniqueWorkLiveData(uniqueQueueName()) + val observer = object : Observer> { + override fun onChanged(workInfoList: List?) { + workInfoList + ?.filter { it.state == WorkInfo.State.SUCCEEDED } + ?.firstOrNull { it.id == enqueueInfo.second } + ?.let { _ -> + onDone?.invoke() + workLiveData.removeObserver(this) + } + } + } + + // TODO listen to DB to get synced info + GlobalScope.launch(Dispatchers.Main) { + workLiveData.observeForever(observer) + } } private fun enqueueSendWork(workerParams: Data): Pair { @@ -254,10 +274,12 @@ internal class VerificationTransportRoomMessage( .setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS) .build() return workManagerProvider.workManager - .beginUniqueWork("${roomId}_VerificationWork", ExistingWorkPolicy.APPEND, workRequest) + .beginUniqueWork(uniqueQueueName(), ExistingWorkPolicy.APPEND, workRequest) .enqueue() to workRequest.id } + private fun uniqueQueueName() = "${roomId}_VerificationWork" + override fun createAccept(tid: String, keyAgreementProtocol: String, hash: String, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportToDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportToDevice.kt index d07a83d7b1..290fc88878 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/VerificationTransportToDevice.kt @@ -145,7 +145,7 @@ internal class VerificationTransportToDevice( .executeBy(taskExecutor) } - override fun done(transactionId: String) { + override fun done(transactionId: String, onDone: (() -> Unit)?) { val otherUserId = tx?.otherUserId ?: return val otherUserDeviceId = tx?.otherDeviceId ?: return val cancelMessage = KeyVerificationDone(transactionId) @@ -155,6 +155,7 @@ internal class VerificationTransportToDevice( .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_DONE, contentMap, transactionId)) { this.callback = object : MatrixCallback { override fun onSuccess(data: Unit) { + onDone?.invoke() Timber.v("## SAS verification [$transactionId] done") } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt index c55d95b36e..8fc12f000a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt @@ -184,13 +184,12 @@ internal class DefaultQrCodeVerificationTransaction( // qrCodeData.sharedSecret will be used to send the start request start(otherQrCodeData.sharedSecret) - // Trust the other user trust(canTrustOtherUserMasterKey, toVerifyDeviceIds.distinct(), eventuallyMarkMyMasterKeyAsTrusted = true) } - private fun start(remoteSecret: String) { + private fun start(remoteSecret: String, onDone: (() -> Unit)? = null) { if (state != VerificationTxState.None) { Timber.e("## Verification QR: start verification from invalid state") // should I cancel?? @@ -208,7 +207,7 @@ internal class DefaultQrCodeVerificationTransaction( startMessage, VerificationTxState.Started, CancelCode.User, - null + onDone ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt index 98046a4a36..d466ecc321 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt @@ -238,7 +238,7 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor( forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain ) } catch (e: MXCryptoError) { - Timber.w("Failed to decrypt e2e replace") + Timber.v("Failed to decrypt e2e replace") // TODO -> we should keep track of this and retry, or aggregation will be broken } } diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt index cbb0e904e4..6eb316456a 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseFragment.kt @@ -171,7 +171,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector { override fun invalidate() { // no-ops by default - Timber.w("invalidate() method has not been implemented") + Timber.v("invalidate() method has not been implemented") } protected fun setArguments(args: Parcelable? = null) { From 572b174cfe5fbb05bff0b2802249ee38c0736b02 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 17 Mar 2020 14:42:55 +0100 Subject: [PATCH 029/156] code quality --- .../crypto/verification/DefaultVerificationTransaction.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt index c4e5ed1e7e..cb83fdfe8b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt @@ -58,7 +58,8 @@ internal abstract class DefaultVerificationTransaction( protected fun trust(canTrustOtherUserMasterKey: Boolean, toVerifyDeviceIds: List, eventuallyMarkMyMasterKeyAsTrusted: Boolean) { - Timber.d("## Verification: trust ($otherUserId,$otherDeviceId) , verifiedDevices:$toVerifyDeviceIds, Mark myMSK trusted $eventuallyMarkMyMasterKeyAsTrusted") + Timber.d("## Verification: trust ($otherUserId,$otherDeviceId) , verifiedDevices:$toVerifyDeviceIds") + Timber.d("## Verification: trust Mark myMSK trusted $eventuallyMarkMyMasterKeyAsTrusted") // TODO what if the otherDevice is not in this list? and should we toVerifyDeviceIds.forEach { From 68151d838f7f34b10b036669456ecd64507aacae Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 17 Mar 2020 15:10:27 +0100 Subject: [PATCH 030/156] Update changes --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index fc1d75f539..b07a6b567f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,7 @@ Changes in RiotX 0.19.0 (2020-XX-XX) =================================================== Features ✨: - - + - Cross-Signing | Support SSSS secret sharing (#944) Improvements 🙌: - From 286a5081ff792da7efc482bb94a703de118e5ef2 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 18 Mar 2020 10:07:57 +0100 Subject: [PATCH 031/156] Verif / handle concurrent start Fixes #794 --- CHANGES.md | 2 +- .../matrix/android/common/CommonTestHelper.kt | 19 ++++ .../matrix/android/common/TestConstants.kt | 4 +- .../crypto/gossiping/KeyShareTests.kt | 37 ++----- .../internal/crypto/verification/SASTest.kt | 97 +++++++++++++++++++ .../DefaultVerificationService.kt | 34 ++++++- 6 files changed, 160 insertions(+), 33 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b07a6b567f..180598a980 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features ✨: - Cross-Signing | Support SSSS secret sharing (#944) Improvements 🙌: - - + - Verification DM / Handle concurrent .start after .ready (#794) Bugfix 🐛: - diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt index 386787b882..08c81f56c0 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt @@ -38,6 +38,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineSettings import im.vector.matrix.android.api.session.sync.SyncState import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals @@ -269,6 +270,24 @@ class CommonTestHelper(context: Context) { assertTrue(latch.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)) } + fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) { + GlobalScope.launch { + while (true) { + delay(1000) + if (condition()) { + latch.countDown() + return@launch + } + } + } + } + + fun waitWithLatch(block: (CountDownLatch) -> Unit) { + val latch = CountDownLatch(1) + block(latch) + await(latch) + } + // Transform a method with a MatrixCallback to a synchronous method inline fun doSync(block: (MatrixCallback) -> Unit): T { val lock = CountDownLatch(1) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt index 2346898ca7..deaa721e40 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/TestConstants.kt @@ -22,8 +22,8 @@ object TestConstants { const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080" - // Time out to use when waiting for server response. 10s - private const val AWAIT_TIME_OUT_MILLIS = 10_000 + // Time out to use when waiting for server response. 20s + private const val AWAIT_TIME_OUT_MILLIS = 20_000 // Time out to use when waiting for server response, when the debugger is connected. 10 minutes private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60_000 diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt index 1dc0907b65..c42e94e340 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt @@ -32,9 +32,6 @@ import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventConten import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.assertTrue import junit.framework.TestCase.fail -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import org.junit.Assert import org.junit.FixMethodOrder import org.junit.Test @@ -90,10 +87,10 @@ class KeyShareTests : InstrumentedTest { var outGoingRequestId: String? = null - retryPeriodicallyWithLatch(waitLatch) { + mTestHelper.retryPeriodicallyWithLatch(waitLatch) { aliceSession2.cryptoService().getOutgoingRoomKeyRequest() .filter { req -> - // filter out request that was known before + // filter out request thwat was known before !outgoingRequestBefore.any { req.requestId == it.requestId } } .let { @@ -114,8 +111,8 @@ class KeyShareTests : InstrumentedTest { // The first session should see an incoming request // the request should be refused, because the device is not trusted - waitWithLatch { latch -> - retryPeriodicallyWithLatch(latch) { + mTestHelper.waitWithLatch { latch -> + mTestHelper.retryPeriodicallyWithLatch(latch) { // DEBUG LOGS aliceSession.cryptoService().getIncomingRoomKeyRequest().let { Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)") @@ -144,8 +141,8 @@ class KeyShareTests : InstrumentedTest { // Re request aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root) - waitWithLatch { latch -> - retryPeriodicallyWithLatch(latch) { + mTestHelper.waitWithLatch { latch -> + mTestHelper.retryPeriodicallyWithLatch(latch) { aliceSession.cryptoService().getIncomingRoomKeyRequest().let { Log.v("TEST", "Incoming request Session 1") Log.v("TEST", "=========================") @@ -160,8 +157,8 @@ class KeyShareTests : InstrumentedTest { } Thread.sleep(6_000) - waitWithLatch { latch -> - retryPeriodicallyWithLatch(latch) { + mTestHelper.waitWithLatch { latch -> + mTestHelper.retryPeriodicallyWithLatch(latch) { aliceSession2.cryptoService().getOutgoingRoomKeyRequest().let { it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == OutgoingGossipingRequestState.CANCELLED } } @@ -177,22 +174,4 @@ class KeyShareTests : InstrumentedTest { mTestHelper.signOutAndClose(aliceSession) mTestHelper.signOutAndClose(aliceSession2) } - - fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) { - GlobalScope.launch { - while (true) { - delay(1000) - if (condition()) { - latch.countDown() - return@launch - } - } - } - } - - fun waitWithLatch(block: (CountDownLatch) -> Unit) { - val latch = CountDownLatch(1) - block(latch) - mTestHelper.await(latch) - } } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt index eefb040987..1ac70d7f2b 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.internal.crypto.verification +import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import im.vector.matrix.android.InstrumentedTest import im.vector.matrix.android.api.session.Session @@ -23,6 +24,7 @@ import im.vector.matrix.android.api.session.crypto.verification.CancelCode import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.OutgoingSasVerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.SasMode +import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod import im.vector.matrix.android.api.session.crypto.verification.VerificationService import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction @@ -355,6 +357,7 @@ class SASTest : InstrumentedTest { val aliceAcceptedLatch = CountDownLatch(1) val aliceListener = object : VerificationService.Listener { override fun transactionUpdated(tx: VerificationTransaction) { + Log.v("TEST", "== aliceTx state ${tx.state} => ${(tx as? OutgoingSasVerificationTransaction)?.uxState}") if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) { val at = tx as SASDefaultVerificationTransaction accepted = at.accepted @@ -367,7 +370,9 @@ class SASTest : InstrumentedTest { val bobListener = object : VerificationService.Listener { override fun transactionUpdated(tx: VerificationTransaction) { + Log.v("TEST", "== bobTx state ${tx.state} => ${(tx as? IncomingSasVerificationTransaction)?.uxState}") if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) { + bobVerificationService.removeListener(this) val at = tx as IncomingSasVerificationTransaction at.performAccept() } @@ -515,4 +520,96 @@ class SASTest : InstrumentedTest { assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified) cryptoTestData.cleanUp(mTestHelper) } + + @Test + fun test_ConcurrentStart() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession + + val aliceVerificationService = aliceSession.cryptoService().verificationService() + val bobVerificationService = bobSession!!.cryptoService().verificationService() + + val req = aliceVerificationService.requestKeyVerificationInDMs( + listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), + bobSession.myUserId, + cryptoTestData.roomId + ) + + var requestID : String? = null + + mTestHelper.waitWithLatch { + mTestHelper.retryPeriodicallyWithLatch(it) { + val prAlicePOV = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId)?.firstOrNull() + requestID = prAlicePOV?.transactionId + Log.v("TEST", "== alicePOV is $prAlicePOV") + prAlicePOV?.transactionId != null && prAlicePOV.localId == req.localId + } + } + + Log.v("TEST", "== requestID is $requestID") + + mTestHelper.waitWithLatch { + mTestHelper.retryPeriodicallyWithLatch(it) { + val prBobPOV = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId)?.firstOrNull() + Log.v("TEST", "== prBobPOV is $prBobPOV") + prBobPOV?.transactionId == requestID + } + } + + bobVerificationService.readyPendingVerification( + listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), + aliceSession.myUserId, + requestID!! + ) + + // wait for alice to get the ready + mTestHelper.waitWithLatch { + mTestHelper.retryPeriodicallyWithLatch(it) { + val prAlicePOV = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId)?.firstOrNull() + Log.v("TEST", "== prAlicePOV is $prAlicePOV") + prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null + } + } + + // Start concurrent! + aliceVerificationService.beginKeyVerificationInDMs( + VerificationMethod.SAS, + requestID!!, + cryptoTestData.roomId, + bobSession.myUserId, + bobSession.sessionParams.credentials.deviceId!!, + null) + + bobVerificationService.beginKeyVerificationInDMs( + VerificationMethod.SAS, + requestID!!, + cryptoTestData.roomId, + aliceSession.myUserId, + aliceSession.sessionParams.credentials.deviceId!!, + null) + + // we should reach SHOW SAS on both + var alicePovTx: SasVerificationTransaction? + var bobPovTx: SasVerificationTransaction? + + mTestHelper.waitWithLatch { + mTestHelper.retryPeriodicallyWithLatch(it) { + alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID!!) as? SasVerificationTransaction + Log.v("TEST", "== alicePovTx is $alicePovTx") + alicePovTx?.state == VerificationTxState.ShortCodeReady + } + } + // wait for alice to get the ready + mTestHelper.waitWithLatch { + mTestHelper.retryPeriodicallyWithLatch(it) { + bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID!!) as? SasVerificationTransaction + Log.v("TEST", "== bobPovTx is $bobPovTx") + bobPovTx?.state == VerificationTxState.ShortCodeReady + } + } + + cryptoTestData.cleanUp(mTestHelper) + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt index f6364c2125..7276debfe2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt @@ -457,7 +457,29 @@ internal class DefaultVerificationService @Inject constructor( Timber.d("## SAS onStartRequestReceived ${startReq.transactionId}") if (checkKeysAreDownloaded(otherUserId!!, startReq.fromDevice) != null) { val tid = startReq.transactionId - val existing = getExistingTransaction(otherUserId, tid) + var existing = getExistingTransaction(otherUserId, tid) + + // After the m.key.verification.ready event is sent, either party can send an + // m.key.verification.start event to begin the verification. If both parties + // send an m.key.verification.start event, and they both specify the same + // verification method, then the event sent by the user whose user ID is the + // smallest is used, and the other m.key.verification.start event is ignored. + // In the case of a single user verifying two of their devices, the device ID is + // compared instead . + if (existing != null && !existing.isIncoming) { + val readyRequest = getExistingVerificationRequest(otherUserId, tid) + if (readyRequest?.isReady == true) { + if (isOtherPrioritary(otherUserId, existing.otherDeviceId ?: "")) { + // The other is prioritary! + // I should replace my outgoing with an incoming + removeTransaction(otherUserId, tid) + existing = null + } else { + // i am prioritary, ignore this start event! + return null + } + } + } when (startReq) { is ValidVerificationInfoStart.SasVerificationInfoStart -> { @@ -532,6 +554,16 @@ internal class DefaultVerificationService @Inject constructor( } } + private fun isOtherPrioritary(otherUserId: String, otherDeviceId: String) : Boolean { + if (userId < otherUserId) { + return false + } else if (userId > otherUserId) { + return true + } else { + return otherDeviceId < deviceId ?: "" + } + } + // TODO Refacto: It could just return a boolean private suspend fun checkKeysAreDownloaded(otherUserId: String, otherDeviceId: String): MXUsersDevicesMap? { From 6fe77eba724ccad37e899365dc35cbee64874a5f Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 18 Mar 2020 11:25:49 +0100 Subject: [PATCH 032/156] code review --- .../crypto/gossiping/KeyShareTests.kt | 2 +- .../DefaultVerificationService.kt | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt index c42e94e340..c8d2df38ce 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt @@ -90,7 +90,7 @@ class KeyShareTests : InstrumentedTest { mTestHelper.retryPeriodicallyWithLatch(waitLatch) { aliceSession2.cryptoService().getOutgoingRoomKeyRequest() .filter { req -> - // filter out request thwat was known before + // filter out request that was known before !outgoingRequestBefore.any { req.requestId == it.requestId } } .let { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt index 7276debfe2..400b2c520e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt @@ -455,7 +455,7 @@ internal class DefaultVerificationService @Inject constructor( startReq: ValidVerificationInfoStart, txConfigure: (DefaultVerificationTransaction) -> Unit): CancelCode? { Timber.d("## SAS onStartRequestReceived ${startReq.transactionId}") - if (checkKeysAreDownloaded(otherUserId!!, startReq.fromDevice) != null) { + if (otherUserId?.let { checkKeysAreDownloaded(it, startReq.fromDevice) } != null) { val tid = startReq.transactionId var existing = getExistingTransaction(otherUserId, tid) @@ -469,15 +469,15 @@ internal class DefaultVerificationService @Inject constructor( if (existing != null && !existing.isIncoming) { val readyRequest = getExistingVerificationRequest(otherUserId, tid) if (readyRequest?.isReady == true) { - if (isOtherPrioritary(otherUserId, existing.otherDeviceId ?: "")) { - // The other is prioritary! - // I should replace my outgoing with an incoming - removeTransaction(otherUserId, tid) - existing = null - } else { - // i am prioritary, ignore this start event! - return null - } + if (isOtherPrioritary(otherUserId, existing.otherDeviceId ?: "")) { + // The other is prioritary! + // I should replace my outgoing with an incoming + removeTransaction(otherUserId, tid) + existing = null + } else { + // i am prioritary, ignore this start event! + return null + } } } @@ -554,7 +554,7 @@ internal class DefaultVerificationService @Inject constructor( } } - private fun isOtherPrioritary(otherUserId: String, otherDeviceId: String) : Boolean { + private fun isOtherPrioritary(otherUserId: String, otherDeviceId: String): Boolean { if (userId < otherUserId) { return false } else if (userId > otherUserId) { From 6f5bebedf82c6da567f3d3c856ada1abca2e4fb8 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 18 Mar 2020 11:53:38 +0100 Subject: [PATCH 033/156] Fixes #841 --- .../helper/MessageInformationDataFactory.kt | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 2a7261665a..0758e34495 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -22,7 +22,9 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.ReferencesAggregatedContent +import im.vector.matrix.android.api.session.room.model.message.MessageVerificationRequestContent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent +import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited import im.vector.matrix.android.internal.session.room.VerificationState import im.vector.riotx.core.date.VectorDateFormatter @@ -61,6 +63,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses || event.getDisambiguatedDisplayName() != nextEvent?.getDisambiguatedDisplayName() || (nextEvent.root.getClearType() != EventType.MESSAGE && nextEvent.root.getClearType() != EventType.ENCRYPTED) || isNextMessageReceivedMoreThanOneHourAgo + || isTileTypeMessage(nextEvent) val time = dateFormatter.formatMessageHour(date) val avatarUrl = event.senderAvatar @@ -88,7 +91,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses myVote = it.aggregatedContent?.myVote, isClosed = it.closedTime ?: Long.MAX_VALUE > System.currentTimeMillis(), votes = it.aggregatedContent?.votes - ?.groupBy({ it.optionIndex }, { it.userId }) + ?.groupBy({ it.optionIndex }, { it.userId }) ?.mapValues { it.value.size } ) }, @@ -111,4 +114,19 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses sentByMe = event.root.senderId == session.myUserId ) } + + /** + * Tiles type message never show the sender information (like verification request), so we should repeat it for next message + * even if same sender + */ + private fun isTileTypeMessage(event: TimelineEvent?): Boolean { + return when (event?.root?.getClearType()) { + EventType.KEY_VERIFICATION_DONE, + EventType.KEY_VERIFICATION_CANCEL -> true + EventType.MESSAGE -> { + event.getLastMessageContent() is MessageVerificationRequestContent + } + else -> false + } + } } From 14acbb2b4d714dc9e987f228d3838302aee9b332 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 18 Mar 2020 12:06:37 +0100 Subject: [PATCH 034/156] Update changes --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index b07a6b567f..73cbb4e9af 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ Improvements 🙌: - Bugfix 🐛: - - + - Missing avatar/displayname after verification request message (#841) Translations 🗣: - From 0c1f30208d85cf53ae9aebf79e8876d858d616a2 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 19 Mar 2020 11:23:15 +0100 Subject: [PATCH 035/156] Fix / Epoxy divider not showing --- .../im/vector/riotx/core/extensions/RecyclerView.kt | 11 ++++++++++- .../timeline/reactions/ViewReactionsBottomSheet.kt | 2 +- .../devices/DeviceVerificationInfoBottomSheet.kt | 2 +- .../im/vector/riotx/features/themes/ThemeUtils.kt | 8 +++++--- .../main/res/drawable/divider_horizontal_black.xml | 5 +++++ .../src/main/res/drawable/divider_horizontal_dark.xml | 5 +++++ .../main/res/drawable/divider_horizontal_light.xml | 5 +++++ vector/src/main/res/values/theme_dark.xml | 2 -- vector/src/main/res/values/theme_light.xml | 2 -- 9 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 vector/src/main/res/drawable/divider_horizontal_black.xml create mode 100644 vector/src/main/res/drawable/divider_horizontal_dark.xml create mode 100644 vector/src/main/res/drawable/divider_horizontal_light.xml diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt b/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt index 51f3ce611a..3b3132229c 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/RecyclerView.kt @@ -16,10 +16,13 @@ package im.vector.riotx.core.extensions +import androidx.core.content.ContextCompat import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.EpoxyController +import im.vector.riotx.R +import im.vector.riotx.features.themes.ThemeUtils /** * Apply a Vertical LinearLayout Manager to the recyclerView and set the adapter from the epoxy controller @@ -40,7 +43,13 @@ fun RecyclerView.configureWith(epoxyController: EpoxyController, itemAnimator?.let { this.itemAnimator = it } } if (showDivider) { - addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) + addItemDecoration( + DividerItemDecoration(context, DividerItemDecoration.VERTICAL).apply { + ContextCompat.getDrawable(context, ThemeUtils.getResourceId(context, R.drawable.divider_horizontal_light))?.let { + setDrawable(it) + } + } + ) } setHasFixedSize(hasFixedSize) adapter = epoxyController.adapter diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt index aac48202c4..f7c9ff04e4 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/reactions/ViewReactionsBottomSheet.kt @@ -58,7 +58,7 @@ class ViewReactionsBottomSheet : VectorBaseBottomSheetDialogFragment(), ViewReac override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java) - recyclerView.configureWith(epoxyController, hasFixedSize = false) + recyclerView.configureWith(epoxyController, hasFixedSize = false, showDivider = true) bottomSheetTitle.text = context?.getString(R.string.reactions) epoxyController.listener = this } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheet.kt index 9863134cb7..8e18472499 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/devices/DeviceVerificationInfoBottomSheet.kt @@ -62,7 +62,7 @@ class DeviceVerificationInfoBottomSheet : VectorBaseBottomSheetDialogFragment(), super.onActivityCreated(savedInstanceState) recyclerView.configureWith( epoxyController, - showDivider = true, + showDivider = false, hasFixedSize = false) epoxyController.callback = this bottomSheetTitle.isVisible = false diff --git a/vector/src/main/java/im/vector/riotx/features/themes/ThemeUtils.kt b/vector/src/main/java/im/vector/riotx/features/themes/ThemeUtils.kt index 40a14b3e6f..1f835164db 100644 --- a/vector/src/main/java/im/vector/riotx/features/themes/ThemeUtils.kt +++ b/vector/src/main/java/im/vector/riotx/features/themes/ThemeUtils.kt @@ -62,10 +62,10 @@ object ThemeUtils { */ fun setApplicationTheme(context: Context, aTheme: String) { when (aTheme) { - THEME_DARK_VALUE -> context.setTheme(R.style.AppTheme_Dark) - THEME_BLACK_VALUE -> context.setTheme(R.style.AppTheme_Black) + THEME_DARK_VALUE -> context.setTheme(R.style.AppTheme_Dark) + THEME_BLACK_VALUE -> context.setTheme(R.style.AppTheme_Black) THEME_STATUS_VALUE -> context.setTheme(R.style.AppTheme_Status) - else -> context.setTheme(R.style.AppTheme_Light) + else -> context.setTheme(R.style.AppTheme_Light) } // Clear the cache @@ -170,6 +170,7 @@ object ThemeUtils { R.drawable.bg_search_edit_text_light -> R.drawable.bg_search_edit_text_dark R.drawable.bg_unread_notification_light -> R.drawable.bg_unread_notification_dark R.drawable.vector_label_background_light -> R.drawable.vector_label_background_dark + R.drawable.divider_horizontal_light -> R.drawable.divider_horizontal_dark else -> { Timber.w("Warning, missing case for wanted drawable in dark theme") resourceId @@ -181,6 +182,7 @@ object ThemeUtils { R.drawable.bg_search_edit_text_light -> R.drawable.bg_search_edit_text_black R.drawable.bg_unread_notification_light -> R.drawable.bg_unread_notification_black R.drawable.vector_label_background_light -> R.drawable.vector_label_background_black + R.drawable.divider_horizontal_light -> R.drawable.divider_horizontal_black else -> { Timber.w("Warning, missing case for wanted drawable in black theme") resourceId diff --git a/vector/src/main/res/drawable/divider_horizontal_black.xml b/vector/src/main/res/drawable/divider_horizontal_black.xml new file mode 100644 index 0000000000..43a68bbe2d --- /dev/null +++ b/vector/src/main/res/drawable/divider_horizontal_black.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/vector/src/main/res/drawable/divider_horizontal_dark.xml b/vector/src/main/res/drawable/divider_horizontal_dark.xml new file mode 100644 index 0000000000..24a9307799 --- /dev/null +++ b/vector/src/main/res/drawable/divider_horizontal_dark.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/vector/src/main/res/drawable/divider_horizontal_light.xml b/vector/src/main/res/drawable/divider_horizontal_light.xml new file mode 100644 index 0000000000..4b215ecaa4 --- /dev/null +++ b/vector/src/main/res/drawable/divider_horizontal_light.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/vector/src/main/res/values/theme_dark.xml b/vector/src/main/res/values/theme_dark.xml index 9bfdcc8231..09775d4d41 100644 --- a/vector/src/main/res/values/theme_dark.xml +++ b/vector/src/main/res/values/theme_dark.xml @@ -36,8 +36,6 @@ @drawable/highlighted_message_background_dark - @color/riotx_header_panel_border_mobile_dark - @color/riotx_accent @color/primary_color_dark_light diff --git a/vector/src/main/res/values/theme_light.xml b/vector/src/main/res/values/theme_light.xml index 3a479c9368..c63dfa9057 100644 --- a/vector/src/main/res/values/theme_light.xml +++ b/vector/src/main/res/values/theme_light.xml @@ -35,8 +35,6 @@ @drawable/highlighted_message_background_light - @color/riotx_header_panel_border_mobile_light - @color/riotx_accent From 6db0de321c704ebc43220b4a6905ba2944239f3a Mon Sep 17 00:00:00 2001 From: onurays Date: Fri, 20 Mar 2020 12:12:59 +0300 Subject: [PATCH 036/156] Initial implementation of multipicker. --- .gitignore | 134 +++++++++++++++++ matrix-sdk-android/build.gradle | 2 +- multipicker/.gitignore | 1 + multipicker/build.gradle | 54 +++++++ multipicker/consumer-rules.pro | 0 multipicker/proguard-rules.pro | 21 +++ .../multipicker/ExampleInstrumentedTest.kt | 39 +++++ multipicker/src/main/AndroidManifest.xml | 2 + .../vector/riotx/multipicker/AudioPicker.kt | 106 ++++++++++++++ .../vector/riotx/multipicker/ContactPicker.kt | 135 ++++++++++++++++++ .../im/vector/riotx/multipicker/FilePicker.kt | 86 +++++++++++ .../vector/riotx/multipicker/ImagePicker.kt | 124 ++++++++++++++++ .../vector/riotx/multipicker/MultiPicker.kt | 46 ++++++ .../im/vector/riotx/multipicker/Picker.kt | 38 +++++ .../vector/riotx/multipicker/VideoPicker.kt | 115 +++++++++++++++ .../entity/MultiPickerAudioType.kt | 27 ++++ .../multipicker/entity/MultiPickerBaseType.kt | 26 ++++ .../entity/MultiPickerContactType.kt | 32 +++++ .../multipicker/entity/MultiPickerFileType.kt | 26 ++++ .../entity/MultiPickerImageType.kt | 29 ++++ .../entity/MultiPickerVideoType.kt | 30 ++++ .../riotx/multipicker/ExampleUnitTest.kt | 32 +++++ settings.gradle | 1 + vector/build.gradle | 1 + vector/src/main/AndroidManifest.xml | 1 + .../home/room/detail/RoomDetailFragment.kt | 33 ++++- 26 files changed, 1133 insertions(+), 8 deletions(-) create mode 100644 multipicker/.gitignore create mode 100644 multipicker/build.gradle create mode 100644 multipicker/consumer-rules.pro create mode 100644 multipicker/proguard-rules.pro create mode 100644 multipicker/src/androidTest/java/im/vector/riotx/multipicker/ExampleInstrumentedTest.kt create mode 100644 multipicker/src/main/AndroidManifest.xml create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerAudioType.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerBaseType.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerFileType.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerImageType.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerVideoType.kt create mode 100644 multipicker/src/test/java/im/vector/riotx/multipicker/ExampleUnitTest.kt diff --git a/.gitignore b/.gitignore index 4a264a28d8..ecfdcb7510 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,137 @@ /tmp ktlint +multipicker/build/.transforms/3e0e90edbb388b6989e862c9faf75e87.bin +multipicker/build/.transforms/55ef863ef687bb455ca3376fbf042ba5.bin +multipicker/build/.transforms/3e0e90edbb388b6989e862c9faf75e87/classes/classes.dex +multipicker/build/.transforms/55ef863ef687bb455ca3376fbf042ba5/classes/classes.dex +multipicker/build/generated/source/buildConfig/debug/im/vector/riotx/multipicker/BuildConfig.java +multipicker/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml +multipicker/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output.json +multipicker/build/intermediates/annotation_processor_list/debug/annotationProcessors.json +multipicker/build/intermediates/annotations_typedef_file/debug/extractDebugAnnotations/typedefs.txt +multipicker/build/intermediates/compile_library_classes/debug/classes.jar +multipicker/build/intermediates/compile_only_not_namespaced_r_class_jar/debug/R.jar +multipicker/build/intermediates/consumer_proguard_file/debug/proguard.txt +multipicker/build/intermediates/incremental/debug-mergeJavaRes/merge-state +multipicker/build/intermediates/incremental/debug-mergeNativeLibs/merge-state +multipicker/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +multipicker/build/intermediates/incremental/mergeDebugShaders/merger.xml +multipicker/build/intermediates/incremental/packageDebugAssets/merger.xml +multipicker/build/intermediates/incremental/packageDebugResources/compile-file-map.properties +multipicker/build/intermediates/incremental/packageDebugResources/merger.xml +multipicker/build/intermediates/javac/debug/classes/im/vector/riotx/multipicker/BuildConfig.class +multipicker/build/intermediates/library_java_res/debug/res.jar +multipicker/build/intermediates/library_manifest/debug/AndroidManifest.xml +multipicker/build/intermediates/local_only_symbol_list/debug/parseDebugLibraryResources/R-def.txt +multipicker/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt +multipicker/build/intermediates/merged_java_res/debug/out.jar +multipicker/build/intermediates/merged_manifests/debug/output.json +multipicker/build/intermediates/packaged-classes/debug/classes.jar +multipicker/build/intermediates/res/symbol-table-with-package/debug/package-aware-r.txt +multipicker/build/intermediates/runtime_library_classes/debug/classes.jar +multipicker/build/intermediates/symbols/debug/R.txt +multipicker/build/kotlin/compileDebugKotlin/build-history.bin +multipicker/build/kotlin/compileDebugKotlin/last-build.bin +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/counters.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab_i.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.keystream +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.keystream.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.len +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.values +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.values.at +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.values.s +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab_i +multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab_i.len +multipicker/build/outputs/aar/multipicker-debug.aar +multipicker/build/outputs/logs/manifest-merger-debug-report.txt +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/AudioPicker.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/ContactPicker.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/FilePicker.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/ImagePicker.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$AUDIO$2.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$CONTACT$2.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$FILE$2.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$IMAGE$2.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$VIDEO$2.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/Picker.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/VideoPicker.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerAudioType.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerBaseType.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerContactType.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerFileType.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerImageType.class +multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerVideoType.class +multipicker/build/tmp/kotlin-classes/debug/META-INF/multipicker_debug.kotlin_module diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 5f614763d5..ca6c2ce6d9 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -119,7 +119,7 @@ dependencies { implementation "ru.noties.markwon:core:$markwon_version" // Image - implementation 'androidx.exifinterface:exifinterface:1.1.0' + implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01' implementation 'id.zelory:compressor:3.0.0' // Database diff --git a/multipicker/.gitignore b/multipicker/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/multipicker/.gitignore @@ -0,0 +1 @@ +/build diff --git a/multipicker/build.gradle b/multipicker/build.gradle new file mode 100644 index 0000000000..950e76020f --- /dev/null +++ b/multipicker/build.gradle @@ -0,0 +1,54 @@ +/* + * 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. + */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.3" + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'consumer-rules.pro' + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.core:core-ktx:1.2.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + implementation 'androidx.exifinterface:exifinterface:1.3.0-alpha01' +} diff --git a/multipicker/consumer-rules.pro b/multipicker/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/multipicker/proguard-rules.pro b/multipicker/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/multipicker/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/multipicker/src/androidTest/java/im/vector/riotx/multipicker/ExampleInstrumentedTest.kt b/multipicker/src/androidTest/java/im/vector/riotx/multipicker/ExampleInstrumentedTest.kt new file mode 100644 index 0000000000..25bf17559f --- /dev/null +++ b/multipicker/src/androidTest/java/im/vector/riotx/multipicker/ExampleInstrumentedTest.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.riotx.multipicker + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 +import junit.framework.Assert.assertEquals + +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("im.vector.riotx.multipicker.test", appContext.packageName) + } +} diff --git a/multipicker/src/main/AndroidManifest.xml b/multipicker/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..068a7c9a19 --- /dev/null +++ b/multipicker/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt new file mode 100644 index 0000000000..19aba7bf1a --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt @@ -0,0 +1,106 @@ +/* + * 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.multipicker + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.media.MediaMetadataRetriever +import android.net.Uri +import android.provider.MediaStore +import androidx.fragment.app.Fragment +import im.vector.riotx.multipicker.entity.MultiPickerAudioType + +class AudioPicker(override val requestCode: Int) : Picker(requestCode) { + + override fun startWith(activity: Activity) { + activity.startActivityForResult(createIntent(), requestCode) + } + + override fun startWith(fragment: Fragment) { + fragment.startActivityForResult(createIntent(), requestCode) + } + + override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { + if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { + return emptyList() + } + + val audioList = mutableListOf() + + val selectedUriList = mutableListOf() + val dataUri = data?.data + val clipData = data?.clipData + + if (clipData != null) { + for (i in 0 until clipData.itemCount) { + selectedUriList.add(clipData.getItemAt(i).uri) + } + } else if (dataUri != null) { + selectedUriList.add(dataUri) + } + + selectedUriList.forEach { selectedUri -> + val projection = arrayOf( + MediaStore.Audio.Media.DISPLAY_NAME, + MediaStore.Audio.Media.SIZE + ) + + context.contentResolver.query( + selectedUri, + projection, + null, + null, + null + )?.use { cursor -> + val nameColumn = cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME) + val sizeColumn = cursor.getColumnIndex(MediaStore.Audio.Media.SIZE) + + if (cursor.moveToNext()) { + val name = cursor.getString(nameColumn) + val size = cursor.getLong(sizeColumn) + var duration = 0L + + context.contentResolver.openFileDescriptor(selectedUri, "r")?.use { pfd -> + val mediaMetadataRetriever = MediaMetadataRetriever() + mediaMetadataRetriever.setDataSource(pfd.fileDescriptor) + duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong() + } + + audioList.add( + MultiPickerAudioType( + name, + size, + context.contentResolver.getType(selectedUri), + selectedUri, + duration + ) + ) + } + } + } + return audioList + } + + private fun createIntent(): Intent { + return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) + type = "audio/*" + } + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt new file mode 100644 index 0000000000..aebde6f439 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt @@ -0,0 +1,135 @@ +/* + * 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.multipicker + +import android.app.Activity +import android.content.ContentResolver +import android.content.Context +import android.content.Intent +import android.provider.ContactsContract +import androidx.fragment.app.Fragment +import im.vector.riotx.multipicker.entity.MultiPickerContactType + +class ContactPicker(override val requestCode: Int) : Picker(requestCode) { + + override fun startWith(activity: Activity) { + activity.startActivityForResult(createIntent(), requestCode) + } + + override fun startWith(fragment: Fragment) { + fragment.startActivityForResult(createIntent(), requestCode) + } + + override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { + if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { + return emptyList() + } + + val contactList = mutableListOf() + + data?.data?.let { selectedUri -> + val projection = arrayOf( + ContactsContract.Contacts.DISPLAY_NAME, + ContactsContract.Contacts.PHOTO_URI, + ContactsContract.Contacts._ID + ) + + context.contentResolver.query( + selectedUri, + projection, + null, + null, + null + )?.use { cursor -> + if (cursor.moveToFirst()) { + val idColumn = cursor.getColumnIndex(ContactsContract.Contacts._ID) + val nameColumn = cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME) + val photoUriColumn = cursor.getColumnIndex(ContactsContract.Contacts.PHOTO_URI) + + val contactId = cursor.getInt(idColumn) + var name = cursor.getString(nameColumn) + var photoUri = cursor.getString(photoUriColumn) + var phoneNumberList = mutableListOf() + var emailList = mutableListOf() + + getRawContactId(context.contentResolver, contactId)?.let { rawContactId -> + val selection = ContactsContract.Data.RAW_CONTACT_ID + " = ?" + val selectionArgs = arrayOf(rawContactId.toString()) + + context.contentResolver.query( + ContactsContract.Data.CONTENT_URI, + arrayOf( + ContactsContract.Data.MIMETYPE, + ContactsContract.Data.DATA1 + ), + selection, + selectionArgs, + null + )?.use { cursor -> + while (cursor.moveToNext()) { + val mimeType = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.MIMETYPE)) + val contactData = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.DATA1)) + + if (mimeType == ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) { + name = contactData + } + if (mimeType == ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) { + phoneNumberList.add(contactData) + } + if (mimeType == ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) { + emailList.add(contactData) + } + } + } + } + contactList.add( + MultiPickerContactType( + name, + photoUri, + phoneNumberList, + emailList + ) + ) + } + } + } + + return contactList + } + + private fun getRawContactId(contentResolver: ContentResolver, contactId: Int): Int? { + val projection = arrayOf(ContactsContract.RawContacts._ID) + val selection = ContactsContract.RawContacts.CONTACT_ID + " = ?" + val selectionArgs = arrayOf(contactId.toString() + "") + return contentResolver.query( + ContactsContract.RawContacts.CONTENT_URI, + projection, + selection, + selectionArgs, + null + )?.use { cursor -> + return if (cursor.moveToFirst()) cursor.getInt(cursor.getColumnIndex(ContactsContract.RawContacts._ID)) else null + } + } + + private fun createIntent(): Intent { + return Intent(Intent.ACTION_PICK).apply { + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) + type = ContactsContract.Contacts.CONTENT_TYPE + } + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt new file mode 100644 index 0000000000..cf6b74c62d --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt @@ -0,0 +1,86 @@ +/* + * 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.multipicker + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.provider.OpenableColumns +import androidx.fragment.app.Fragment +import im.vector.riotx.multipicker.entity.MultiPickerFileType + +class FilePicker(override val requestCode: Int) : Picker(requestCode) { + + override fun startWith(activity: Activity) { + activity.startActivityForResult(createIntent(), requestCode) + } + + override fun startWith(fragment: Fragment) { + fragment.startActivityForResult(createIntent(), requestCode) + } + + override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { + if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { + return emptyList() + } + + val fileList = mutableListOf() + + val selectedUriList = mutableListOf() + val dataUri = data?.data + val clipData = data?.clipData + + if (clipData != null) { + for (i in 0 until clipData.itemCount) { + selectedUriList.add(clipData.getItemAt(i).uri) + } + } else if (dataUri != null) { + selectedUriList.add(dataUri) + } + + selectedUriList.forEach { selectedUri -> + context.contentResolver.query(selectedUri, null, null, null, null) + ?.use { cursor -> + val nameColumn = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + val sizeColumn = cursor.getColumnIndex(OpenableColumns.SIZE) + if (cursor.moveToFirst()) { + val name = cursor.getString(nameColumn) + val size = cursor.getLong(sizeColumn) + + fileList.add( + MultiPickerFileType( + name, + size, + context.contentResolver.getType(selectedUri), + selectedUri + ) + ) + } + } + } + return fileList + } + + private fun createIntent(): Intent { + return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) + type = "*/*" + } + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt new file mode 100644 index 0000000000..79832a2ad1 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt @@ -0,0 +1,124 @@ +/* + * 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.multipicker + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.graphics.BitmapFactory +import android.graphics.ImageDecoder +import android.net.Uri +import android.os.Build +import android.provider.MediaStore +import androidx.exifinterface.media.ExifInterface +import androidx.fragment.app.Fragment +import im.vector.riotx.multipicker.entity.MultiPickerImageType + +class ImagePicker(override val requestCode: Int) : Picker(requestCode) { + + override fun startWith(activity: Activity) { + activity.startActivityForResult(createIntent(), requestCode) + } + + override fun startWith(fragment: Fragment) { + fragment.startActivityForResult(createIntent(), requestCode) + } + + override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { + if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { + return emptyList() + } + + val imageList = mutableListOf() + + val selectedUriList = mutableListOf() + val dataUri = data?.data + val clipData = data?.clipData + + if (clipData != null) { + for (i in 0 until clipData.itemCount) { + selectedUriList.add(clipData.getItemAt(i).uri) + } + } else if (dataUri != null) { + selectedUriList.add(dataUri) + } + + selectedUriList.forEach { selectedUri -> + val projection = arrayOf( + MediaStore.Images.Media.DISPLAY_NAME, + MediaStore.Images.Media.SIZE + ) + + context.contentResolver.query( + selectedUri, + projection, + null, + null, + null + )?.use { cursor -> + val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME) + val sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE) + + if (cursor.moveToNext()) { + val name = cursor.getString(nameColumn) + val size = cursor.getLong(sizeColumn) + + var orientation = 0 + + val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, selectedUri)) + } else { + context.contentResolver.openInputStream(selectedUri)?.use { inputStream -> + BitmapFactory.decodeStream(inputStream) + } + } + + context.contentResolver.openInputStream(selectedUri)?.use { inputStream -> + try { + ExifInterface(inputStream).let { + orientation = it.rotationDegrees + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + imageList.add( + MultiPickerImageType( + name, + size, + context.contentResolver.getType(selectedUri), + selectedUri, + bitmap?.width ?: 0, + bitmap?.height ?: 0, + orientation + ) + ) + } + } + } + return imageList + } + + private fun createIntent(): Intent { + return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) + type = "image/*" + } + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt new file mode 100644 index 0000000000..d10346c903 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt @@ -0,0 +1,46 @@ +/* + * 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.multipicker + +class MultiPicker { + + companion object Type { + val IMAGE by lazy { MultiPicker() } + val FILE by lazy { MultiPicker() } + val VIDEO by lazy { MultiPicker() } + val AUDIO by lazy { MultiPicker() } + val CONTACT by lazy { MultiPicker() } + + const val REQUEST_CODE_PICK_IMAGE = 5000 + const val REQUEST_CODE_PICK_VIDEO = 5001 + const val REQUEST_CODE_PICK_FILE = 5002 + const val REQUEST_CODE_PICK_AUDIO = 5003 + const val REQUEST_CODE_PICK_CONTACT = 5004 + + @Suppress("UNCHECKED_CAST") + fun get(type: MultiPicker): T { + return when (type) { + IMAGE -> ImagePicker(REQUEST_CODE_PICK_IMAGE) as T + VIDEO -> VideoPicker(REQUEST_CODE_PICK_VIDEO) as T + FILE -> FilePicker(REQUEST_CODE_PICK_FILE) as T + AUDIO -> AudioPicker(REQUEST_CODE_PICK_AUDIO) as T + CONTACT -> ContactPicker(REQUEST_CODE_PICK_CONTACT) as T + else -> throw IllegalArgumentException("Unsupported type $type") + } + } + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt new file mode 100644 index 0000000000..62dff35062 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt @@ -0,0 +1,38 @@ +/* + * 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.multipicker + +import android.app.Activity +import android.content.Context +import android.content.Intent +import androidx.fragment.app.Fragment + +abstract class Picker(open val requestCode: Int) { + + protected var single = false + + abstract fun startWith(activity: Activity) + + abstract fun startWith(fragment: Fragment) + + abstract fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List + + fun single(): Picker { + single = true + return this + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt new file mode 100644 index 0000000000..8066c0b43e --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt @@ -0,0 +1,115 @@ +/* + * 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.multipicker + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.media.MediaMetadataRetriever +import android.net.Uri +import android.provider.MediaStore +import androidx.fragment.app.Fragment +import im.vector.riotx.multipicker.entity.MultiPickerVideoType + +class VideoPicker(override val requestCode: Int) : Picker(requestCode) { + + override fun startWith(activity: Activity) { + activity.startActivityForResult(createIntent(), requestCode) + } + + override fun startWith(fragment: Fragment) { + fragment.startActivityForResult(createIntent(), requestCode) + } + + override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { + if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { + return emptyList() + } + + val videoList = mutableListOf() + + val selectedUriList = mutableListOf() + val dataUri = data?.data + val clipData = data?.clipData + + if (clipData != null) { + for (i in 0 until clipData.itemCount) { + selectedUriList.add(clipData.getItemAt(i).uri) + } + } else if (dataUri != null) { + selectedUriList.add(dataUri) + } + + selectedUriList.forEach { selectedUri -> + val projection = arrayOf( + MediaStore.Video.Media.DISPLAY_NAME, + MediaStore.Video.Media.SIZE + ) + + context.contentResolver.query( + selectedUri, + projection, + null, + null, + null + )?.use { cursor -> + val nameColumn = cursor.getColumnIndex(MediaStore.Video.Media.DISPLAY_NAME) + val sizeColumn = cursor.getColumnIndex(MediaStore.Video.Media.SIZE) + + if (cursor.moveToNext()) { + val name = cursor.getString(nameColumn) + val size = cursor.getLong(sizeColumn) + var duration = 0L + var width = 0 + var height = 0 + var orientation = 0 + + context.contentResolver.openFileDescriptor(selectedUri, "r")?.use { pfd -> + val mediaMetadataRetriever = MediaMetadataRetriever() + mediaMetadataRetriever.setDataSource(pfd.fileDescriptor) + duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong() + width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH).toInt() + height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT).toInt() + orientation = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION).toInt() + } + + videoList.add( + MultiPickerVideoType( + name, + size, + context.contentResolver.getType(selectedUri), + selectedUri, + width, + height, + orientation, + duration + ) + ) + } + } + } + return videoList + } + + private fun createIntent(): Intent { + return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) + type = "video/*" + } + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerAudioType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerAudioType.kt new file mode 100644 index 0000000000..6afe022024 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerAudioType.kt @@ -0,0 +1,27 @@ +/* + * 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.multipicker.entity + +import android.net.Uri + +data class MultiPickerAudioType( + override val displayName: String?, + override val size: Long, + override val mimeType: String?, + override val contentUri: Uri, + val duration: Long +) : MultiPickerBaseType diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerBaseType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerBaseType.kt new file mode 100644 index 0000000000..777e4d8441 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerBaseType.kt @@ -0,0 +1,26 @@ +/* + * 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.multipicker.entity + +import android.net.Uri + +interface MultiPickerBaseType { + val displayName: String? + val size: Long + val mimeType: String? + val contentUri: Uri +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt new file mode 100644 index 0000000000..565304a546 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt @@ -0,0 +1,32 @@ +/* + * 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.multipicker.entity + +data class MultiPickerContactType( + val displayName: String, + val photoUri: String?, + val phoneNumberList: List, + val emailList: List +) { + private val FORMAT_CONTACT = "Name: %s, Photo: %s, Phones: %s, Emails: %s" + + override fun toString(): String { + val phoneNumberString = phoneNumberList.joinToString(separator = ", ", prefix = "[", postfix = "]") + val emailString = emailList.joinToString(separator = ", ", prefix = "[", postfix = "]") + return String.format(FORMAT_CONTACT, displayName, photoUri, phoneNumberString, emailString) + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerFileType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerFileType.kt new file mode 100644 index 0000000000..5417520d28 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerFileType.kt @@ -0,0 +1,26 @@ +/* + * 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.multipicker.entity + +import android.net.Uri + +data class MultiPickerFileType( + override val displayName: String?, + override val size: Long, + override val mimeType: String?, + override val contentUri: Uri +) : MultiPickerBaseType diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerImageType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerImageType.kt new file mode 100644 index 0000000000..b1aef171b4 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerImageType.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.riotx.multipicker.entity + +import android.net.Uri + +data class MultiPickerImageType( + override val displayName: String?, + override val size: Long, + override val mimeType: String?, + override val contentUri: Uri, + val width: Int, + val height: Int, + val orientation: Int +) : MultiPickerBaseType diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerVideoType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerVideoType.kt new file mode 100644 index 0000000000..ba9a8d233e --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerVideoType.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.riotx.multipicker.entity + +import android.net.Uri + +data class MultiPickerVideoType( + override val displayName: String?, + override val size: Long, + override val mimeType: String?, + override val contentUri: Uri, + val width: Int, + val height: Int, + val orientation: Int, + val duration: Long +) : MultiPickerBaseType diff --git a/multipicker/src/test/java/im/vector/riotx/multipicker/ExampleUnitTest.kt b/multipicker/src/test/java/im/vector/riotx/multipicker/ExampleUnitTest.kt new file mode 100644 index 0000000000..07e464699f --- /dev/null +++ b/multipicker/src/test/java/im/vector/riotx/multipicker/ExampleUnitTest.kt @@ -0,0 +1,32 @@ +/* + * 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.multipicker + +import junit.framework.Assert.assertEquals +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/settings.gradle b/settings.gradle index d020abade4..04307e89d9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ include ':vector', ':matrix-sdk-android', ':matrix-sdk-android-rx', ':diff-match-patch' +include ':multipicker' diff --git a/vector/build.gradle b/vector/build.gradle index 66ec6808c8..447ad7a767 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -254,6 +254,7 @@ dependencies { implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android-rx") implementation project(":diff-match-patch") + implementation project(":multipicker") implementation 'com.android.support:multidex:1.0.3' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 2e56e20ce7..092817a6cc 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ + 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 e748478e6a..30f9551612 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 @@ -156,6 +156,7 @@ import im.vector.riotx.features.reactions.EmojiReactionPickerActivity import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.share.SharedData import im.vector.riotx.features.themes.ThemeUtils +import im.vector.riotx.multipicker.MultiPicker import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import kotlinx.android.parcel.Parcelize @@ -290,9 +291,9 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.observeViewEvents { when (it) { - is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable) - is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds) - is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it) + is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable) + is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds) + is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it) is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it) is RoomDetailViewEvents.ShowMessage -> showSnackWithMessage(it.message, Snackbar.LENGTH_LONG) is RoomDetailViewEvents.NavigateToEvent -> navigateToEvent(it) @@ -516,6 +517,24 @@ class RoomDetailFragment @Inject constructor( } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + when (requestCode) { + MultiPicker.REQUEST_CODE_PICK_IMAGE -> { + MultiPicker.get(MultiPicker.IMAGE).getSelectedFiles(requireContext(), requestCode, resultCode, data) + } + MultiPicker.REQUEST_CODE_PICK_VIDEO -> { + MultiPicker.get(MultiPicker.VIDEO).getSelectedFiles(requireContext(), requestCode, resultCode, data) + } + MultiPicker.REQUEST_CODE_PICK_FILE -> { + MultiPicker.get(MultiPicker.FILE).getSelectedFiles(requireContext(), requestCode, resultCode, data) + } + MultiPicker.REQUEST_CODE_PICK_AUDIO -> { + MultiPicker.get(MultiPicker.AUDIO).getSelectedFiles(requireContext(), requestCode, resultCode, data) + } + MultiPicker.REQUEST_CODE_PICK_CONTACT -> { + MultiPicker.get(MultiPicker.CONTACT).getSelectedFiles(requireContext(), requestCode, resultCode, data) + } + } + val hasBeenHandled = attachmentsHelper.onActivityResult(requestCode, resultCode, data) if (!hasBeenHandled && resultCode == RESULT_OK && data != null) { when (requestCode) { @@ -1351,10 +1370,10 @@ class RoomDetailFragment @Inject constructor( private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { when (type) { AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera() - AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile() - AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery() - AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio() - AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact() + AttachmentTypeSelectorView.Type.FILE -> MultiPicker.get(MultiPicker.FILE).startWith(this) + AttachmentTypeSelectorView.Type.GALLERY -> MultiPicker.get(MultiPicker.IMAGE).startWith(this) + AttachmentTypeSelectorView.Type.AUDIO -> MultiPicker.get(MultiPicker.AUDIO).startWith(this) + AttachmentTypeSelectorView.Type.CONTACT -> MultiPicker.get(MultiPicker.CONTACT).startWith(this) AttachmentTypeSelectorView.Type.STICKER -> vectorBaseActivity.notImplemented("Adding stickers") }.exhaustive } From 5b875e057171e1fc3f3ee3dc3d24ff8307294272 Mon Sep 17 00:00:00 2001 From: onurays Date: Sun, 22 Mar 2020 18:27:59 +0300 Subject: [PATCH 037/156] CameraPicker & incoming share implementation. --- .gitignore | 135 +----------------- multipicker/src/main/AndroidManifest.xml | 16 ++- .../vector/riotx/multipicker/AudioPicker.kt | 7 + .../vector/riotx/multipicker/CameraPicker.kt | 132 +++++++++++++++++ .../im/vector/riotx/multipicker/FilePicker.kt | 7 + .../vector/riotx/multipicker/ImagePicker.kt | 7 + .../vector/riotx/multipicker/MultiPicker.kt | 3 + .../multipicker/MultiPickerFileProvider.kt | 22 +++ .../im/vector/riotx/multipicker/Picker.kt | 9 ++ .../vector/riotx/multipicker/VideoPicker.kt | 7 + .../entity/MultiPickerContactType.kt | 4 +- .../res/xml/multipicker_provider_paths.xml | 6 + .../home/room/detail/RoomDetailFragment.kt | 10 +- 13 files changed, 227 insertions(+), 138 deletions(-) create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt create mode 100644 multipicker/src/main/res/xml/multipicker_provider_paths.xml diff --git a/.gitignore b/.gitignore index ecfdcb7510..ab97ec340a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,137 +14,4 @@ /tmp ktlint -multipicker/build/.transforms/3e0e90edbb388b6989e862c9faf75e87.bin -multipicker/build/.transforms/55ef863ef687bb455ca3376fbf042ba5.bin -multipicker/build/.transforms/3e0e90edbb388b6989e862c9faf75e87/classes/classes.dex -multipicker/build/.transforms/55ef863ef687bb455ca3376fbf042ba5/classes/classes.dex -multipicker/build/generated/source/buildConfig/debug/im/vector/riotx/multipicker/BuildConfig.java -multipicker/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/AndroidManifest.xml -multipicker/build/intermediates/aapt_friendly_merged_manifests/debug/aapt/output.json -multipicker/build/intermediates/annotation_processor_list/debug/annotationProcessors.json -multipicker/build/intermediates/annotations_typedef_file/debug/extractDebugAnnotations/typedefs.txt -multipicker/build/intermediates/compile_library_classes/debug/classes.jar -multipicker/build/intermediates/compile_only_not_namespaced_r_class_jar/debug/R.jar -multipicker/build/intermediates/consumer_proguard_file/debug/proguard.txt -multipicker/build/intermediates/incremental/debug-mergeJavaRes/merge-state -multipicker/build/intermediates/incremental/debug-mergeNativeLibs/merge-state -multipicker/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml -multipicker/build/intermediates/incremental/mergeDebugShaders/merger.xml -multipicker/build/intermediates/incremental/packageDebugAssets/merger.xml -multipicker/build/intermediates/incremental/packageDebugResources/compile-file-map.properties -multipicker/build/intermediates/incremental/packageDebugResources/merger.xml -multipicker/build/intermediates/javac/debug/classes/im/vector/riotx/multipicker/BuildConfig.class -multipicker/build/intermediates/library_java_res/debug/res.jar -multipicker/build/intermediates/library_manifest/debug/AndroidManifest.xml -multipicker/build/intermediates/local_only_symbol_list/debug/parseDebugLibraryResources/R-def.txt -multipicker/build/intermediates/manifest_merge_blame_file/debug/manifest-merger-blame-debug-report.txt -multipicker/build/intermediates/merged_java_res/debug/out.jar -multipicker/build/intermediates/merged_manifests/debug/output.json -multipicker/build/intermediates/packaged-classes/debug/classes.jar -multipicker/build/intermediates/res/symbol-table-with-package/debug/package-aware-r.txt -multipicker/build/intermediates/runtime_library_classes/debug/classes.jar -multipicker/build/intermediates/symbols/debug/R.txt -multipicker/build/kotlin/compileDebugKotlin/build-history.bin -multipicker/build/kotlin/compileDebugKotlin/last-build.bin -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/inputs/source-to-output.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/constants.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/proto.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/subtypes.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/jvm/kotlin/supertypes.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/counters.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/file-to-id.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/id-to-file.tab_i.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.keystream -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.keystream.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.len -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.values -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.values.at -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab.values.s -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab_i -multipicker/build/kotlin/compileDebugKotlin/caches-jvm/lookups/lookups.tab_i.len -multipicker/build/outputs/aar/multipicker-debug.aar -multipicker/build/outputs/logs/manifest-merger-debug-report.txt -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/AudioPicker.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/ContactPicker.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/FilePicker.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/ImagePicker.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$AUDIO$2.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$CONTACT$2.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$FILE$2.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$IMAGE$2.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type$VIDEO$2.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker$Type.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/MultiPicker.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/Picker.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/VideoPicker.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerAudioType.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerBaseType.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerContactType.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerFileType.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerImageType.class -multipicker/build/tmp/kotlin-classes/debug/im/vector/riotx/multipicker/entity/MultiPickerVideoType.class -multipicker/build/tmp/kotlin-classes/debug/META-INF/multipicker_debug.kotlin_module + diff --git a/multipicker/src/main/AndroidManifest.xml b/multipicker/src/main/AndroidManifest.xml index 068a7c9a19..6f714ab388 100644 --- a/multipicker/src/main/AndroidManifest.xml +++ b/multipicker/src/main/AndroidManifest.xml @@ -1,2 +1,16 @@ + package="im.vector.riotx.multipicker"> + + + + + + + + diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt index 19aba7bf1a..8fb23ec49d 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt @@ -52,6 +52,13 @@ class AudioPicker(override val requestCode: Int) : Picker( } } else if (dataUri != null) { selectedUriList.add(dataUri) + } else { + data?.extras?.get(Intent.EXTRA_STREAM)?.let { + when (it) { + is List<*> -> selectedUriList.addAll(it as List) + else -> selectedUriList.add(it as Uri) + } + } } selectedUriList.forEach { selectedUri -> diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt new file mode 100644 index 0000000000..6962f2098a --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt @@ -0,0 +1,132 @@ +/* + * 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.multipicker + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.graphics.BitmapFactory +import android.graphics.ImageDecoder +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.MediaStore +import androidx.core.content.FileProvider +import androidx.exifinterface.media.ExifInterface +import androidx.fragment.app.Fragment +import im.vector.riotx.multipicker.entity.MultiPickerImageType +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +class CameraPicker(val requestCode: Int) { + + fun startWithExpectingFile(activity: Activity): Uri? { + val photoUri = createPhotoUri(activity) + val intent = createIntent().apply { + putExtra(MediaStore.EXTRA_OUTPUT, photoUri) + } + activity.startActivityForResult(intent, requestCode) + return photoUri + } + + fun startWithExpectingFile(fragment: Fragment): Uri? { + val photoUri = createPhotoUri(fragment.requireContext()) + val intent = createIntent().apply { + putExtra(MediaStore.EXTRA_OUTPUT, photoUri) + } + fragment.startActivityForResult(intent, requestCode) + return photoUri + } + + fun getTakenPhoto(context: Context, requestCode: Int, resultCode: Int, photoUri: Uri): MultiPickerImageType? { + if (requestCode == this.requestCode && resultCode == Activity.RESULT_OK) { + val projection = arrayOf( + MediaStore.Images.Media.DISPLAY_NAME, + MediaStore.Images.Media.SIZE + ) + + context.contentResolver.query( + photoUri, + projection, + null, + null, + null + )?.use { cursor -> + val nameColumn = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME) + val sizeColumn = cursor.getColumnIndex(MediaStore.Images.Media.SIZE) + + if (cursor.moveToNext()) { + val name = cursor.getString(nameColumn) + val size = cursor.getLong(sizeColumn) + + var orientation = 0 + + val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, photoUri)) + } else { + context.contentResolver.openInputStream(photoUri)?.use { inputStream -> + BitmapFactory.decodeStream(inputStream) + } + } + + context.contentResolver.openInputStream(photoUri)?.use { inputStream -> + try { + ExifInterface(inputStream).let { + orientation = it.rotationDegrees + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + return MultiPickerImageType( + name, + size, + context.contentResolver.getType(photoUri), + photoUri, + bitmap?.width ?: 0, + bitmap?.height ?: 0, + orientation + ) + } + } + } + return null + } + + private fun createIntent(): Intent { + return Intent(MediaStore.ACTION_IMAGE_CAPTURE) + } + + private fun createPhotoUri(context: Context): Uri { + val file = createImageFile(context) + val authority = context.packageName + ".multipicker.fileprovider" + return FileProvider.getUriForFile(context, authority, file) + } + + private fun createImageFile(context: Context): File { + val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val storageDir: File = context.filesDir + return File.createTempFile( + "JPEG_${timeStamp}_", /* prefix */ + ".jpg", /* suffix */ + storageDir /* directory */ + ) + } +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt index cf6b74c62d..9d3292c115 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt @@ -51,6 +51,13 @@ class FilePicker(override val requestCode: Int) : Picker(re } } else if (dataUri != null) { selectedUriList.add(dataUri) + } else { + data?.extras?.get(Intent.EXTRA_STREAM)?.let { + when (it) { + is List<*> -> selectedUriList.addAll(it as List) + else -> selectedUriList.add(it as Uri) + } + } } selectedUriList.forEach { selectedUri -> diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt index 79832a2ad1..f33ceb816c 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt @@ -55,6 +55,13 @@ class ImagePicker(override val requestCode: Int) : Picker( } } else if (dataUri != null) { selectedUriList.add(dataUri) + } else { + data?.extras?.get(Intent.EXTRA_STREAM)?.let { + when (it) { + is List<*> -> selectedUriList.addAll(it as List) + else -> selectedUriList.add(it as Uri) + } + } } selectedUriList.forEach { selectedUri -> diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt index d10346c903..24769e11c3 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPicker.kt @@ -24,12 +24,14 @@ class MultiPicker { val VIDEO by lazy { MultiPicker() } val AUDIO by lazy { MultiPicker() } val CONTACT by lazy { MultiPicker() } + val CAMERA by lazy { MultiPicker() } const val REQUEST_CODE_PICK_IMAGE = 5000 const val REQUEST_CODE_PICK_VIDEO = 5001 const val REQUEST_CODE_PICK_FILE = 5002 const val REQUEST_CODE_PICK_AUDIO = 5003 const val REQUEST_CODE_PICK_CONTACT = 5004 + const val REQUEST_CODE_TAKE_PHOTO = 5005 @Suppress("UNCHECKED_CAST") fun get(type: MultiPicker): T { @@ -39,6 +41,7 @@ class MultiPicker { FILE -> FilePicker(REQUEST_CODE_PICK_FILE) as T AUDIO -> AudioPicker(REQUEST_CODE_PICK_AUDIO) as T CONTACT -> ContactPicker(REQUEST_CODE_PICK_CONTACT) as T + CAMERA -> CameraPicker(REQUEST_CODE_TAKE_PHOTO) as T else -> throw IllegalArgumentException("Unsupported type $type") } } diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt new file mode 100644 index 0000000000..832d721eef --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt @@ -0,0 +1,22 @@ +/* + * 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.multipicker + +import androidx.core.content.FileProvider + +class MultiPickerFileProvider : FileProvider() { +} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt index 62dff35062..f162dd7608 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt @@ -19,6 +19,7 @@ package im.vector.riotx.multipicker import android.app.Activity import android.content.Context import android.content.Intent +import android.net.Uri import androidx.fragment.app.Fragment abstract class Picker(open val requestCode: Int) { @@ -29,8 +30,16 @@ abstract class Picker(open val requestCode: Int) { abstract fun startWith(fragment: Fragment) + open fun startWithExpectingFile(activity: Activity): Uri? = null + + open fun startWithExpectingFile(fragment: Fragment): Uri? = null + abstract fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List + fun getIncomingFiles(context: Context, data: Intent?): List { + return getSelectedFiles(context, requestCode, Activity.RESULT_OK, data) + } + fun single(): Picker { single = true return this diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt index 8066c0b43e..92d1e9c240 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt @@ -52,6 +52,13 @@ class VideoPicker(override val requestCode: Int) : Picker( } } else if (dataUri != null) { selectedUriList.add(dataUri) + } else { + data?.extras?.get(Intent.EXTRA_STREAM)?.let { + when (it) { + is List<*> -> selectedUriList.addAll(it as List) + else -> selectedUriList.add(it as Uri) + } + } } selectedUriList.forEach { selectedUri -> diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt index 565304a546..5a9c064867 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/entity/MultiPickerContactType.kt @@ -22,11 +22,11 @@ data class MultiPickerContactType( val phoneNumberList: List, val emailList: List ) { - private val FORMAT_CONTACT = "Name: %s, Photo: %s, Phones: %s, Emails: %s" + private val CONTACT_FORMAT = "Name: %s, Photo: %s, Phones: %s, Emails: %s" override fun toString(): String { val phoneNumberString = phoneNumberList.joinToString(separator = ", ", prefix = "[", postfix = "]") val emailString = emailList.joinToString(separator = ", ", prefix = "[", postfix = "]") - return String.format(FORMAT_CONTACT, displayName, photoUri, phoneNumberString, emailString) + return String.format(CONTACT_FORMAT, displayName, photoUri, phoneNumberString, emailString) } } diff --git a/multipicker/src/main/res/xml/multipicker_provider_paths.xml b/multipicker/src/main/res/xml/multipicker_provider_paths.xml new file mode 100644 index 0000000000..ff9b81ce98 --- /dev/null +++ b/multipicker/src/main/res/xml/multipicker_provider_paths.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file 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 30f9551612..13de137e8f 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 @@ -533,6 +533,11 @@ class RoomDetailFragment @Inject constructor( MultiPicker.REQUEST_CODE_PICK_CONTACT -> { MultiPicker.get(MultiPicker.CONTACT).getSelectedFiles(requireContext(), requestCode, resultCode, data) } + MultiPicker.REQUEST_CODE_TAKE_PHOTO -> { + cameraPhotoUri?.let { + MultiPicker.get(MultiPicker.CAMERA).getTakenPhoto(requireContext(), requestCode, resultCode, it) + } + } } val hasBeenHandled = attachmentsHelper.onActivityResult(requestCode, resultCode, data) @@ -1367,9 +1372,12 @@ class RoomDetailFragment @Inject constructor( } } + private var cameraPhotoUri: Uri? = null private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { when (type) { - AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera() + AttachmentTypeSelectorView.Type.CAMERA -> { + cameraPhotoUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this) + } AttachmentTypeSelectorView.Type.FILE -> MultiPicker.get(MultiPicker.FILE).startWith(this) AttachmentTypeSelectorView.Type.GALLERY -> MultiPicker.get(MultiPicker.IMAGE).startWith(this) AttachmentTypeSelectorView.Type.AUDIO -> MultiPicker.get(MultiPicker.AUDIO).startWith(this) From 4f70c40b1a858c593f3dd669a5a80033d09e765a Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 23 Mar 2020 11:13:26 +0100 Subject: [PATCH 038/156] Refactor + share secret window implementation --- .../matrix/android/common/CommonTestHelper.kt | 4 +- .../crypto/gossiping/KeyShareTests.kt | 110 +++++++++++++++ .../internal/crypto/DefaultCryptoService.kt | 12 +- .../internal/crypto/GossipingRequestState.kt | 1 + ....kt => IncomingGossipingRequestManager.kt} | 37 ++++- .../internal/crypto/SendGossipWorker.kt | 127 ++++++++++++++++++ .../crypto/ShareSecretCryptoProvider.kt | 50 ++++--- .../DefaultCrossSigningService.kt | 4 +- ...comingSASDefaultVerificationTransaction.kt | 3 + ...tgoingSASDefaultVerificationTransaction.kt | 3 + .../DefaultVerificationService.kt | 7 + .../DefaultVerificationTransaction.kt | 6 +- .../SASDefaultVerificationTransaction.kt | 3 + .../DefaultQrCodeVerificationTransaction.kt | 3 + .../internal/session/SessionComponent.kt | 4 + .../room/timeline/TimelineEventDecryptor.kt | 2 +- 16 files changed, 340 insertions(+), 36 deletions(-) rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/{IncomingRoomKeyRequestManager.kt => IncomingGossipingRequestManager.kt} (91%) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipWorker.kt diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt index 08c81f56c0..413e4aac3c 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt @@ -266,8 +266,8 @@ class CommonTestHelper(context: Context) { * @param latch * @throws InterruptedException */ - fun await(latch: CountDownLatch) { - assertTrue(latch.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)) + fun await(latch: CountDownLatch, timout: Long? = TestConstants.timeOutMillis) { + assertTrue(latch.await(timout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)) } fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) { diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt index c8d2df38ce..da8ef790ab 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt @@ -19,6 +19,12 @@ package im.vector.matrix.android.internal.crypto.gossiping import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction +import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction +import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod +import im.vector.matrix.android.api.session.crypto.verification.VerificationService +import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction +import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams @@ -28,7 +34,11 @@ import im.vector.matrix.android.common.TestConstants import im.vector.matrix.android.internal.crypto.GossipingRequestState import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestState import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel +import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo +import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent +import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth +import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.assertTrue import junit.framework.TestCase.fail @@ -174,4 +184,104 @@ class KeyShareTests : InstrumentedTest { mTestHelper.signOutAndClose(aliceSession) mTestHelper.signOutAndClose(aliceSession2) } + + @Test + fun test_ShareSSSSSecret() { + + val aliceSession1 = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + + mTestHelper.doSync { + aliceSession1.cryptoService().crossSigningService() + .initializeCrossSigning(UserPasswordAuth( + user = aliceSession1.myUserId, + password = TestConstants.PASSWORD + ), it) + } + + val aliceSession2 = mTestHelper.logIntoAccount(aliceSession1.myUserId, SessionTestParams(true)) + + val aliceVerificationService1 = aliceSession1.cryptoService().verificationService() + val aliceVerificationService2 = aliceSession2.cryptoService().verificationService() + + //force keys download + mTestHelper.doSync> { + aliceSession1.cryptoService().downloadKeys(listOf(aliceSession1.myUserId), true, it) + } + mTestHelper.doSync> { + aliceSession2.cryptoService().downloadKeys(listOf(aliceSession2.myUserId), true, it) + } + + var session1ShortCode: String? = null + var session2ShortCode: String? = null + + aliceVerificationService1.addListener(object : VerificationService.Listener { + override fun transactionUpdated(tx: VerificationTransaction) { + Log.d("TEST", "AA: tx incoming?:${tx.isIncoming} state ${tx.state}") + if (tx is SasVerificationTransaction) { + if (tx.state == VerificationTxState.OnStarted) { + (tx as IncomingSasVerificationTransaction).performAccept() + } + if (tx.state == VerificationTxState.ShortCodeReady) { + session1ShortCode = tx.getDecimalCodeRepresentation() + tx.userHasVerifiedShortCode() + } + } + } + }) + + aliceVerificationService2.addListener(object : VerificationService.Listener { + override fun transactionUpdated(tx: VerificationTransaction) { + Log.d("TEST", "BB: tx incoming?:${tx.isIncoming} state ${tx.state}") + if (tx is SasVerificationTransaction) { + if (tx.state == VerificationTxState.ShortCodeReady) { + session2ShortCode = tx.getDecimalCodeRepresentation() + tx.userHasVerifiedShortCode() + } + } + } + }) + + val txId: String = "m.testVerif12" + aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.credentials.deviceId + ?: "", txId) + + + waitWithLatch { latch -> + retryPeriodicallyWithLatch(latch) { + aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.credentials.deviceId ?: "")?.isVerified == true + } + } + + assertNotNull(session1ShortCode) + Log.d("TEST", "session1ShortCode: $session1ShortCode") + assertNotNull(session2ShortCode) + Log.d("TEST", "session2ShortCode: $session2ShortCode") + assertEquals(session1ShortCode, session2ShortCode) + + // SSK and USK private keys should have been shared + + waitWithLatch(300_000) { latch -> + retryPeriodicallyWithLatch(latch) { + aliceSession2.cryptoService().crossSigningService().canCrossSign() + } + } + } + + fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) { + GlobalScope.launch { + while (true) { + delay(1000) + if (condition()) { + latch.countDown() + return@launch + } + } + } + } + + fun waitWithLatch(timeout: Long? = null, block: (CountDownLatch) -> Unit) { + val latch = CountDownLatch(1) + block(latch) + mTestHelper.await(latch, timeout) + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 9e702ee9ac..83fb53424e 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -140,7 +140,7 @@ internal class DefaultCryptoService @Inject constructor( private val crossSigningService: DefaultCrossSigningService, // - private val incomingRoomKeyRequestManager: IncomingRoomKeyRequestManager, + private val incomingGossipingRequestManager: IncomingGossipingRequestManager, // private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager, // Actions @@ -317,7 +317,7 @@ internal class DefaultCryptoService @Inject constructor( deviceListManager.invalidateAllDeviceLists() deviceListManager.refreshOutdatedDeviceLists() } else { - incomingRoomKeyRequestManager.processReceivedGossipingRequests() + incomingGossipingRequestManager.processReceivedGossipingRequests() } }.fold( { @@ -376,7 +376,7 @@ internal class DefaultCryptoService @Inject constructor( // Make sure we process to-device messages before generating new one-time-keys #2782 deviceListManager.refreshOutdatedDeviceLists() oneTimeKeysUploader.maybeUploadOneTimeKeys() - incomingRoomKeyRequestManager.processReceivedGossipingRequests() + incomingGossipingRequestManager.processReceivedGossipingRequests() } } } @@ -709,7 +709,7 @@ internal class DefaultCryptoService @Inject constructor( // save audit trail cryptoStore.saveGossipingEvent(event) // Requests are stacked, and will be handled one by one at the end of the sync (onSyncComplete) - incomingRoomKeyRequestManager.onGossipingRequestEvent(event) + incomingGossipingRequestManager.onGossipingRequestEvent(event) } EventType.SEND_SECRET -> { cryptoStore.saveGossipingEvent(event) @@ -1111,7 +1111,7 @@ internal class DefaultCryptoService @Inject constructor( * @param listener listener */ override fun addRoomKeysRequestListener(listener: GossipingRequestListener) { - incomingRoomKeyRequestManager.addRoomKeysRequestListener(listener) + incomingGossipingRequestManager.addRoomKeysRequestListener(listener) } /** @@ -1120,7 +1120,7 @@ internal class DefaultCryptoService @Inject constructor( * @param listener listener */ override fun removeRoomKeysRequestListener(listener: GossipingRequestListener) { - incomingRoomKeyRequestManager.removeRoomKeysRequestListener(listener) + incomingGossipingRequestManager.removeRoomKeysRequestListener(listener) } /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingRequestState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingRequestState.kt index b218a2e387..16e8c35992 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingRequestState.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingRequestState.kt @@ -26,6 +26,7 @@ enum class GossipingRequestState { PENDING, REJECTED, ACCEPTED, + FAILED_TO_ACCEPTED, // USER_REJECTED, UNABLE_TO_PROCESS, CANCELLED_BY_REQUESTER, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt similarity index 91% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt index 0bb89154f1..8dfd264b11 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt @@ -32,7 +32,7 @@ import timber.log.Timber import javax.inject.Inject @SessionScope -internal class IncomingRoomKeyRequestManager @Inject constructor( +internal class IncomingGossipingRequestManager @Inject constructor( private val credentials: Credentials, private val cryptoStore: IMXCryptoStore, private val cryptoConfig: MXCryptoConfig, @@ -51,6 +51,32 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( receivedGossipingRequests.addAll(cryptoStore.getPendingIncomingGossipingRequests()) } + // Recently verified devices (map of deviceId and timestamp) + private val recentlyVerifiedDevices = HashMap() + + /** + * Called when a session has been verified. + * This information can be used by the manager to decide whether or not to fullfil gossiping requests + */ + fun onVerificationCompleteForDevice(deviceId: String) { + // For now we just keep an in memory cache + synchronized(recentlyVerifiedDevices) { + recentlyVerifiedDevices[deviceId] = System.currentTimeMillis() + } + } + + private fun hasBeenVerifiedLessThanFiveMinutesFromNow(deviceId: String): Boolean { + val verifTimestamp: Long? + synchronized(recentlyVerifiedDevices) { + verifTimestamp = recentlyVerifiedDevices[deviceId] + } + if (verifTimestamp == null) return false + + val age = System.currentTimeMillis() - verifTimestamp + + return age < FIVE_MINUTES_IN_MILLIS + } + /** * Called when we get an m.room_key_request event * It must be called on CryptoThread @@ -257,9 +283,12 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( }?.let { secretValue -> // TODO check if locally trusted and not outdated Timber.i("## processIncomingSecretShareRequest() : Sharing secret $secretName with $device locally trusted") - if (isDeviceLocallyVerified == true) { + if (isDeviceLocallyVerified == true && hasBeenVerifiedLessThanFiveMinutesFromNow(deviceId)) { secretSecretCryptoProvider.shareSecretWithDevice(request, secretValue) cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED) + } else { + Timber.v("## processIncomingSecretShareRequest() : Can't share secret $secretName with $device, verification too old") + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) } return } @@ -340,4 +369,8 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( gossipingRequestListeners.remove(listener) } } + + companion object { + private const val FIVE_MINUTES_IN_MILLIS = 5 * 60 * 1000 + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipWorker.kt new file mode 100644 index 0000000000..96791005d1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipWorker.kt @@ -0,0 +1,127 @@ +/* + * 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 + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.Data +import androidx.work.WorkerParameters +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.auth.data.Credentials +import im.vector.matrix.android.api.failure.shouldBeRetried +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.LocalEcho +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter +import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap +import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent +import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore +import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask +import im.vector.matrix.android.internal.worker.WorkerParamsFactory +import im.vector.matrix.android.internal.worker.getSessionComponent +import org.greenrobot.eventbus.EventBus +import timber.log.Timber +import javax.inject.Inject + +internal class SendGossipWorker(context: Context, + params: WorkerParameters) + : CoroutineWorker(context, params) { + + @JsonClass(generateAdapter = true) + internal data class Params( + val sessionId: String, + val secretValue: String, + val request: IncomingSecretShareRequest + ) + + @Inject lateinit var sendToDeviceTask: SendToDeviceTask + @Inject lateinit var cryptoStore: IMXCryptoStore + @Inject lateinit var eventBus: EventBus + @Inject lateinit var credentials: Credentials +// @Inject lateinit var secretSecretCryptoProvider: ShareSecretCryptoProvider + @Inject lateinit var messageEncrypter: MessageEncrypter + + override suspend fun doWork(): Result { + val errorOutputData = Data.Builder().putBoolean("failed", true).build() + val params = WorkerParamsFactory.fromData(inputData) + ?: return Result.success(errorOutputData) + + val sessionComponent = getSessionComponent(params.sessionId) + ?: return Result.success(errorOutputData).also { + // TODO, can this happen? should I update local echo? + Timber.e("Unknown Session, cannot send message, sessionId: ${params.sessionId}") + } + sessionComponent.inject(this) + + val localId = LocalEcho.createLocalEchoId() + val eventType: String = EventType.SEND_SECRET + + val toDeviceContent = SecretSendEventContent( + requestId = params.request.requestId ?: "", + secretValue = params.secretValue + ) + + val requestingUserId = params.request.userId ?: "" + val requestingDeviceId = params.request.deviceId ?: "" + val deviceInfo = cryptoStore.getUserDevice(requestingUserId, requestingDeviceId) + ?: return Result.success(errorOutputData).also { + cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED) + Timber.e("Unknown deviceInfo, cannot send message, sessionId: ${params.request.deviceId}") + } + + val payloadJson = mutableMapOf("type" to EventType.SEND_SECRET) + payloadJson["content"] = toDeviceContent.toContent() + + val sendToDeviceMap = MXUsersDevicesMap() + + try { + val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) + sendToDeviceMap.setObject(requestingUserId, requestingDeviceId, encodedPayload) + } catch (failure: Throwable) { + Timber.e("## Fail to encrypt gossip + ${failure.localizedMessage}") + } + + + cryptoStore.saveGossipingEvent(Event( + type = eventType, + content = toDeviceContent.toContent(), + senderId = credentials.userId + ).also { + it.ageLocalTs = System.currentTimeMillis() + }) + + try { + sendToDeviceTask.execute( + SendToDeviceTask.Params( + eventType = eventType, + contentMap = sendToDeviceMap, + transactionId = localId + ) + ) + cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.ACCEPTED) + return Result.success() + } catch (exception: Throwable) { + return if (exception.shouldBeRetried()) { + Result.retry() + } else { + cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED) + Result.success(errorOutputData) + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt index 78e587c0f1..7a7f6acb5f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt @@ -18,16 +18,17 @@ package im.vector.matrix.android.internal.crypto import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmDecryptionFactory import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask +import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import timber.log.Timber import javax.inject.Inject @@ -35,6 +36,7 @@ internal class ShareSecretCryptoProvider @Inject constructor( val messageEncrypter: MessageEncrypter, val sendToDeviceTask: SendToDeviceTask, val deviceListManager: DeviceListManager, + @UserId val myUserId: String, private val olmDecryptionFactory: MXOlmDecryptionFactory, val cryptoCoroutineScope: CoroutineScope, val cryptoStore: IMXCryptoStore, @@ -43,32 +45,38 @@ internal class ShareSecretCryptoProvider @Inject constructor( fun shareSecretWithDevice(request: IncomingSecretShareRequest, secretValue: String) { val userId = request.userId ?: return cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - runCatching { deviceListManager.downloadKeys(listOf(userId), false) } - .mapCatching { - val deviceId = request.deviceId - val deviceInfo = cryptoStore.getUserDevice(userId, deviceId ?: "") ?: throw RuntimeException() + // runCatching { deviceListManager.downloadKeys(listOf(userId), false) } +// .mapCatching { + val deviceId = request.deviceId + val deviceInfo = cryptoStore.getUserDevice(userId, deviceId ?: "") ?: return@launch - Timber.i("## shareSecretWithDevice() : sharing secret ${request.secretName} with device $userId:$deviceId") - val payloadJson = mutableMapOf("type" to EventType.SEND_SECRET) - payloadJson["content"] = SecretSendEventContent( - requestId = request.requestId ?: "", - secretValue = secretValue - ) + Timber.i("## shareSecretWithDevice() : sharing secret ${request.secretName} with device $userId:$deviceId") - val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) - val sendToDeviceMap = MXUsersDevicesMap() - sendToDeviceMap.setObject(userId, deviceId, encodedPayload) - Timber.i("## shareSecretWithDevice() : sending to $userId:$deviceId") - val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) - sendToDeviceTask.execute(sendToDeviceParams) - } + val payloadJson = mutableMapOf("type" to EventType.SEND_SECRET) + val secretContent = SecretSendEventContent( + requestId = request.requestId ?: "", + secretValue = secretValue + ) + payloadJson["content"] = secretContent.toContent() + + cryptoStore.saveGossipingEvent(Event( + type = EventType.SEND_SECRET, + content = secretContent.toContent(), + senderId = myUserId + )) + + val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) + val sendToDeviceMap = MXUsersDevicesMap() + sendToDeviceMap.setObject(userId, deviceId, encodedPayload) + Timber.i("## shareSecretWithDevice() : sending to $userId:$deviceId") + val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) + sendToDeviceTask.execute(sendToDeviceParams) +// } } } fun decryptEvent(event: Event): MXEventDecryptionResult { - return runBlocking(coroutineDispatchers.crypto) { - olmDecryptionFactory.create().decryptEvent(event, ShareSecretCryptoProvider::class.java.name ?: "") - } + return olmDecryptionFactory.create().decryptEvent(event, ShareSecretCryptoProvider::class.java.name ?: "") } } 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 74a4c6bee8..45af09f05b 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 @@ -25,7 +25,6 @@ import im.vector.matrix.android.api.util.Optional import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder -import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey import im.vector.matrix.android.internal.crypto.model.KeyUsage import im.vector.matrix.android.internal.crypto.model.rest.UploadSignatureQueryBuilder @@ -62,7 +61,6 @@ internal class DefaultCrossSigningService @Inject constructor( private val taskExecutor: TaskExecutor, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val cryptoCoroutineScope: CoroutineScope, - private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager, private val eventBus: EventBus) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener { private var olmUtility: OlmUtility? = null @@ -598,7 +596,7 @@ internal class DefaultCrossSigningService @Inject constructor( } override fun canCrossSign(): Boolean { - return checkSelfTrust().isVerified() && cryptoStore.getCrossSigningPrivateKeys()?.selfSigned != null + return cryptoStore.getCrossSigningPrivateKeys()?.selfSigned != null } override fun trustUser(otherUserId: String, callback: MatrixCallback) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt index 989ddc9804..e3a765f95c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASDefaultVerificationTransaction.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerif import im.vector.matrix.android.api.session.crypto.verification.SasMode import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.internal.crypto.IncomingGossipingRequestManager import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore @@ -35,6 +36,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction( private val cryptoStore: IMXCryptoStore, crossSigningService: CrossSigningService, outgoingGossipingRequestManager: OutgoingGossipingRequestManager, + incomingGossipingRequestManager: IncomingGossipingRequestManager, deviceFingerprint: String, transactionId: String, otherUserID: String, @@ -46,6 +48,7 @@ internal class DefaultIncomingSASDefaultVerificationTransaction( cryptoStore, crossSigningService, outgoingGossipingRequestManager, + incomingGossipingRequestManager, deviceFingerprint, transactionId, otherUserID, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt index 6c7e8f29d3..7fd97d0231 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultOutgoingSASDefaultVerificationTransaction.kt @@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.crypto.verification.CancelCode import im.vector.matrix.android.api.session.crypto.verification.OutgoingSasVerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.internal.crypto.IncomingGossipingRequestManager import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore @@ -32,6 +33,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction( cryptoStore: IMXCryptoStore, crossSigningService: CrossSigningService, outgoingGossipingRequestManager: OutgoingGossipingRequestManager, + incomingGossipingRequestManager: IncomingGossipingRequestManager, deviceFingerprint: String, transactionId: String, otherUserId: String, @@ -43,6 +45,7 @@ internal class DefaultOutgoingSASDefaultVerificationTransaction( cryptoStore, crossSigningService, outgoingGossipingRequestManager, + incomingGossipingRequestManager, deviceFingerprint, transactionId, otherUserId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt index 400b2c520e..a0420b0125 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt @@ -50,6 +50,7 @@ import im.vector.matrix.android.api.session.room.model.message.MessageVerificati import im.vector.matrix.android.api.session.room.model.message.MessageVerificationStartContent import im.vector.matrix.android.api.session.room.model.message.ValidVerificationDone import im.vector.matrix.android.internal.crypto.DeviceListManager +import im.vector.matrix.android.internal.crypto.IncomingGossipingRequestManager import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction @@ -90,6 +91,7 @@ internal class DefaultVerificationService @Inject constructor( @DeviceId private val deviceId: String?, private val cryptoStore: IMXCryptoStore, private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager, + private val incomingGossipingRequestManager: IncomingGossipingRequestManager, private val myDeviceInfoHolder: Lazy, private val deviceListManager: DeviceListManager, private val setDeviceVerificationAction: SetDeviceVerificationAction, @@ -530,6 +532,7 @@ internal class DefaultVerificationService @Inject constructor( cryptoStore, crossSigningService, outgoingGossipingRequestManager, + incomingGossipingRequestManager, myDeviceInfoHolder.get().myDevice.fingerprint()!!, startReq.transactionId, otherUserId, @@ -840,6 +843,7 @@ internal class DefaultVerificationService @Inject constructor( readyReq.fromDevice, crossSigningService, outgoingGossipingRequestManager, + incomingGossipingRequestManager, cryptoStore, qrCodeData, userId, @@ -1022,6 +1026,7 @@ internal class DefaultVerificationService @Inject constructor( cryptoStore, crossSigningService, outgoingGossipingRequestManager, + incomingGossipingRequestManager, myDeviceInfoHolder.get().myDevice.fingerprint()!!, txID, otherUserId, @@ -1198,6 +1203,7 @@ internal class DefaultVerificationService @Inject constructor( cryptoStore, crossSigningService, outgoingGossipingRequestManager, + incomingGossipingRequestManager, myDeviceInfoHolder.get().myDevice.fingerprint()!!, transactionId, otherUserId, @@ -1335,6 +1341,7 @@ internal class DefaultVerificationService @Inject constructor( otherDeviceId, crossSigningService, outgoingGossipingRequestManager, + incomingGossipingRequestManager, cryptoStore, qrCodeData, userId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt index cb83fdfe8b..973b9944cb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationTransaction.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState +import im.vector.matrix.android.internal.crypto.IncomingGossipingRequestManager import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel @@ -33,6 +34,7 @@ internal abstract class DefaultVerificationTransaction( private val setDeviceVerificationAction: SetDeviceVerificationAction, private val crossSigningService: CrossSigningService, private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager, + private val incomingGossipingRequestManager: IncomingGossipingRequestManager, private val userId: String, override val transactionId: String, override val otherUserId: String, @@ -86,6 +88,8 @@ internal abstract class DefaultVerificationTransaction( } if (otherUserId == userId) { + incomingGossipingRequestManager.onVerificationCompleteForDevice(otherDeviceId!!) + // If me it's reasonable to sign and upload the device signature // Notice that i might not have the private keys, so may not be able to do it crossSigningService.trustDevice(otherDeviceId!!, object : MatrixCallback { @@ -96,7 +100,7 @@ internal abstract class DefaultVerificationTransaction( } transport.done(transactionId) { - if (otherUserId == userId) { + if (otherUserId == userId && !crossSigningService.canCrossSign()) { outgoingGossipingRequestManager.sendSecretShareRequest(SELF_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) outgoingGossipingRequestManager.sendSecretShareRequest(USER_SIGNING_KEY_SSSS_NAME, mapOf(userId to listOf(otherDeviceId ?: "*"))) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt index cfb3d7e38e..a878ad06eb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/SASDefaultVerificationTransaction.kt @@ -24,6 +24,7 @@ import im.vector.matrix.android.api.session.crypto.verification.SasMode import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.internal.crypto.IncomingGossipingRequestManager import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.model.MXKey @@ -44,6 +45,7 @@ internal abstract class SASDefaultVerificationTransaction( private val cryptoStore: IMXCryptoStore, crossSigningService: CrossSigningService, outgoingGossipingRequestManager: OutgoingGossipingRequestManager, + incomingGossipingRequestManager: IncomingGossipingRequestManager, private val deviceFingerprint: String, transactionId: String, otherUserId: String, @@ -53,6 +55,7 @@ internal abstract class SASDefaultVerificationTransaction( setDeviceVerificationAction, crossSigningService, outgoingGossipingRequestManager, + incomingGossipingRequestManager, userId, transactionId, otherUserId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt index 8fc12f000a..41d8ce7f44 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/DefaultQrCodeVerificationTransaction.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.crypto.verification.CancelCode import im.vector.matrix.android.api.session.crypto.verification.QrCodeVerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.internal.crypto.IncomingGossipingRequestManager import im.vector.matrix.android.internal.crypto.OutgoingGossipingRequestManager import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64 @@ -38,6 +39,7 @@ internal class DefaultQrCodeVerificationTransaction( override var otherDeviceId: String?, private val crossSigningService: CrossSigningService, outgoingGossipingRequestManager: OutgoingGossipingRequestManager, + incomingGossipingRequestManager: IncomingGossipingRequestManager, private val cryptoStore: IMXCryptoStore, // Not null only if other user is able to scan QR code private val qrCodeData: QrCodeData?, @@ -48,6 +50,7 @@ internal class DefaultQrCodeVerificationTransaction( setDeviceVerificationAction, crossSigningService, outgoingGossipingRequestManager, + incomingGossipingRequestManager, userId, transactionId, otherUserId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt index 2185d3b278..22ebb4273a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/SessionComponent.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.internal.crypto.CancelGossipRequestWorker import im.vector.matrix.android.internal.crypto.CryptoModule import im.vector.matrix.android.internal.crypto.SendGossipRequestWorker +import im.vector.matrix.android.internal.crypto.SendGossipWorker import im.vector.matrix.android.internal.crypto.verification.SendVerificationMessageWorker import im.vector.matrix.android.internal.di.MatrixComponent import im.vector.matrix.android.internal.di.SessionAssistedInjectModule @@ -109,8 +110,11 @@ internal interface SessionComponent { fun inject(worker: SendVerificationMessageWorker) fun inject(worker: SendGossipRequestWorker) + fun inject(worker: CancelGossipRequestWorker) + fun inject(worker: SendGossipWorker) + @Component.Factory interface Factory { fun create( diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt index 02c06b0e56..ebffef33f3 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineEventDecryptor.kt @@ -128,7 +128,7 @@ internal class TimelineEventDecryptor @Inject constructor( } } } catch (t: Throwable) { - Timber.e(t, "Failed to decrypt event $eventId") + Timber.e( "Failed to decrypt event $eventId, ${t.localizedMessage}") } finally { synchronized(existingRequests) { existingRequests.remove(request) From f7fd23b153c3821b7e98efdf7047eca9a9405f23 Mon Sep 17 00:00:00 2001 From: onurays Date: Mon, 23 Mar 2020 16:31:32 +0300 Subject: [PATCH 039/156] App integration to the new multipicker library. --- .../session/content/ContentAttachmentData.kt | 4 +- .../internal/session/content/FileUploader.kt | 4 +- .../session/content/ThumbnailExtractor.kt | 68 ++++--- .../session/content/UploadContentWorker.kt | 188 ++++++++---------- .../room/send/LocalEchoEventFactory.kt | 16 +- .../vector/riotx/multipicker/AudioPicker.kt | 1 + .../vector/riotx/multipicker/CameraPicker.kt | 1 - .../im/vector/riotx/multipicker/FilePicker.kt | 1 + .../vector/riotx/multipicker/ImagePicker.kt | 1 + .../multipicker/MultiPickerFileProvider.kt | 3 +- .../vector/riotx/multipicker/VideoPicker.kt | 1 + vector/build.gradle | 3 - .../features/attachments/AttachmentsHelper.kt | 182 ++++++++--------- .../features/attachments/AttachmentsMapper.kt | 43 ++-- .../attachments/AttachmentsPickerCallback.kt | 96 --------- .../attachments/PickerManagerFactory.kt | 134 ------------- .../preview/AttachmentPreviewControllers.kt | 4 +- .../preview/AttachmentPreviewItems.kt | 3 +- .../preview/AttachmentsPreviewAction.kt | 3 +- .../preview/AttachmentsPreviewFragment.kt | 8 +- .../preview/AttachmentsPreviewViewModel.kt | 2 +- .../home/room/detail/RoomDetailFragment.kt | 40 +--- .../home/room/detail/RoomDetailViewModel.kt | 2 +- .../features/share/IncomingShareFragment.kt | 6 +- 24 files changed, 265 insertions(+), 549 deletions(-) delete mode 100644 vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsPickerCallback.kt delete mode 100644 vector/src/main/java/im/vector/riotx/features/attachments/PickerManagerFactory.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt index e32bb9f21f..b80a17b017 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/content/ContentAttachmentData.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.api.session.content +import android.net.Uri import android.os.Parcelable import androidx.exifinterface.media.ExifInterface import kotlinx.android.parcel.Parcelize @@ -29,8 +30,7 @@ data class ContentAttachmentData( val width: Long? = 0, val exifOrientation: Int = ExifInterface.ORIENTATION_UNDEFINED, val name: String? = null, - val queryUri: String, - val path: String, + val queryUri: Uri, private val mimeType: String?, val type: Type ) : Parcelable { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt index 4071c9224f..4fa0cb5013 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/FileUploader.kt @@ -53,9 +53,9 @@ internal class FileUploader @Inject constructor(@Authenticated suspend fun uploadByteArray(byteArray: ByteArray, filename: String?, - mimeType: String, + mimeType: String?, progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse { - val uploadBody = byteArray.toRequestBody(mimeType.toMediaTypeOrNull()) + val uploadBody = byteArray.toRequestBody(mimeType?.toMediaTypeOrNull()) return upload(uploadBody, filename, progressListener) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt index 083cac0278..2ce249ab80 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt @@ -16,12 +16,14 @@ package im.vector.matrix.android.internal.session.content +import android.content.Context import android.graphics.Bitmap -import android.media.ThumbnailUtils -import android.provider.MediaStore +import android.media.MediaMetadataRetriever import im.vector.matrix.android.api.session.content.ContentAttachmentData +import timber.log.Timber import java.io.ByteArrayOutputStream -import java.io.File +import kotlin.math.max +import kotlin.math.roundToInt internal object ThumbnailExtractor { @@ -33,34 +35,48 @@ internal object ThumbnailExtractor { val mimeType: String ) - fun extractThumbnail(attachment: ContentAttachmentData): ThumbnailData? { - val file = File(attachment.path) - if (!file.exists() || !file.isFile) { - return null - } + fun extractThumbnail(context: Context, attachment: ContentAttachmentData): ThumbnailData? { return if (attachment.type == ContentAttachmentData.Type.VIDEO) { - extractVideoThumbnail(attachment) + extractVideoThumbnail(context, attachment) } else { null } } - private fun extractVideoThumbnail(attachment: ContentAttachmentData): ThumbnailData? { - val thumbnail = ThumbnailUtils.createVideoThumbnail(attachment.path, MediaStore.Video.Thumbnails.MINI_KIND) ?: return null - val outputStream = ByteArrayOutputStream() - thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) - val thumbnailWidth = thumbnail.width - val thumbnailHeight = thumbnail.height - val thumbnailSize = outputStream.size() - val thumbnailData = ThumbnailData( - width = thumbnailWidth, - height = thumbnailHeight, - size = thumbnailSize.toLong(), - bytes = outputStream.toByteArray(), - mimeType = "image/jpeg" - ) - thumbnail.recycle() - outputStream.reset() - return thumbnailData + private fun extractVideoThumbnail(context: Context, attachment: ContentAttachmentData): ThumbnailData? { + val mediaMetadataRetriever = MediaMetadataRetriever() + try { + mediaMetadataRetriever.setDataSource(context, attachment.queryUri) + var thumbnail = mediaMetadataRetriever.frameAtTime + // Scale down the bitmap if it's too large. + val width: Int = thumbnail.width + val height: Int = thumbnail.height + val max = max(width, height) + if (max > 512) { + val scale = 512f / max + val w = (scale * width).roundToInt() + val h = (scale * height).roundToInt() + thumbnail = Bitmap.createScaledBitmap(thumbnail, w, h, true) + } + + val outputStream = ByteArrayOutputStream() + thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) + val thumbnailWidth = thumbnail.width + val thumbnailHeight = thumbnail.height + val thumbnailSize = outputStream.size() + val thumbnailData = ThumbnailData( + width = thumbnailWidth, + height = thumbnailHeight, + size = thumbnailSize.toLong(), + bytes = outputStream.toByteArray(), + mimeType = "image/jpeg" + ) + thumbnail.recycle() + outputStream.reset() + return thumbnailData + } catch (e: Exception) { + Timber.e(e, "Cannot extract video thumbnail") + return null + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt index 1c88f87804..21ab649c23 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/UploadContentWorker.kt @@ -17,12 +17,9 @@ package im.vector.matrix.android.internal.session.content import android.content.Context -import android.graphics.BitmapFactory import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass -import id.zelory.compressor.Compressor -import id.zelory.compressor.constraint.default import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.toContent @@ -41,8 +38,6 @@ import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.getSessionComponent import timber.log.Timber import java.io.ByteArrayInputStream -import java.io.File -import java.io.FileInputStream import javax.inject.Inject private data class NewImageAttributes( @@ -94,8 +89,84 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter var newImageAttributes: NewImageAttributes? = null - val attachmentFile = try { - File(attachment.path) + try { + val inputStream = context.contentResolver.openInputStream(attachment.queryUri) ?: return Result.success() + + inputStream.use { + var uploadedThumbnailUrl: String? = null + var uploadedThumbnailEncryptedFileInfo: EncryptedFileInfo? = null + + ThumbnailExtractor.extractThumbnail(context, params.attachment)?.let { thumbnailData -> + val thumbnailProgressListener = object : ProgressRequestBody.Listener { + override fun onProgress(current: Long, total: Long) { + notifyTracker(params) { contentUploadStateTracker.setProgressThumbnail(it, current, total) } + } + } + + try { + val contentUploadResponse = if (params.isEncrypted) { + Timber.v("Encrypt thumbnail") + notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) } + val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType) + uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo + fileUploader.uploadByteArray(encryptionResult.encryptedByteArray, + "thumb_${attachment.name}", + "application/octet-stream", + thumbnailProgressListener) + } else { + fileUploader.uploadByteArray(thumbnailData.bytes, + "thumb_${attachment.name}", + thumbnailData.mimeType, + thumbnailProgressListener) + } + + uploadedThumbnailUrl = contentUploadResponse.contentUri + } catch (t: Throwable) { + Timber.e(t) + return handleFailure(params, t) + } + } + + val progressListener = object : ProgressRequestBody.Listener { + override fun onProgress(current: Long, total: Long) { + notifyTracker(params) { + if (isStopped) { + contentUploadStateTracker.setFailure(it, Throwable("Cancelled")) + } else { + contentUploadStateTracker.setProgress(it, current, total) + } + } + } + } + + var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null + + return try { + val contentUploadResponse = if (params.isEncrypted) { + Timber.v("Encrypt file") + notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) } + + val encryptionResult = MXEncryptedAttachments.encryptAttachment(inputStream, attachment.getSafeMimeType()) + uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo + + fileUploader + .uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener) + } else { + fileUploader + .uploadByteArray(inputStream.readBytes(), attachment.name, attachment.getSafeMimeType(), progressListener) + } + + handleSuccess(params, + contentUploadResponse.contentUri, + uploadedFileEncryptedFileInfo, + uploadedThumbnailUrl, + uploadedThumbnailEncryptedFileInfo, + newImageAttributes) + } catch (t: Throwable) { + Timber.e(t) + handleFailure(params, t) + } + } } catch (e: Exception) { Timber.e(e) notifyTracker(params) { contentUploadStateTracker.setFailure(it, e) } @@ -106,109 +177,6 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter ) ) ) - } - .let { originalFile -> - if (attachment.type == ContentAttachmentData.Type.IMAGE) { - if (params.compressBeforeSending) { - Compressor.compress(context, originalFile) { - default( - width = MAX_IMAGE_SIZE, - height = MAX_IMAGE_SIZE - ) - }.also { compressedFile -> - // Update the params - val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } - BitmapFactory.decodeFile(compressedFile.absolutePath, options) - val fileSize = compressedFile.length().toInt() - - newImageAttributes = NewImageAttributes( - options.outWidth, - options.outHeight, - fileSize - ) - } - } else { - // TODO Fix here the image rotation issue - originalFile - } - } else { - // Other type - originalFile - } - } - - var uploadedThumbnailUrl: String? = null - var uploadedThumbnailEncryptedFileInfo: EncryptedFileInfo? = null - - ThumbnailExtractor.extractThumbnail(params.attachment)?.let { thumbnailData -> - val thumbnailProgressListener = object : ProgressRequestBody.Listener { - override fun onProgress(current: Long, total: Long) { - notifyTracker(params) { contentUploadStateTracker.setProgressThumbnail(it, current, total) } - } - } - - try { - val contentUploadResponse = if (params.isEncrypted) { - Timber.v("Encrypt thumbnail") - notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) } - val encryptionResult = MXEncryptedAttachments.encryptAttachment(ByteArrayInputStream(thumbnailData.bytes), thumbnailData.mimeType) - uploadedThumbnailEncryptedFileInfo = encryptionResult.encryptedFileInfo - fileUploader.uploadByteArray(encryptionResult.encryptedByteArray, - "thumb_${attachment.name}", - "application/octet-stream", - thumbnailProgressListener) - } else { - fileUploader.uploadByteArray(thumbnailData.bytes, - "thumb_${attachment.name}", - thumbnailData.mimeType, - thumbnailProgressListener) - } - - uploadedThumbnailUrl = contentUploadResponse.contentUri - } catch (t: Throwable) { - Timber.e(t) - return handleFailure(params, t) - } - } - - val progressListener = object : ProgressRequestBody.Listener { - override fun onProgress(current: Long, total: Long) { - notifyTracker(params) { - if (isStopped) { - contentUploadStateTracker.setFailure(it, Throwable("Cancelled")) - } else { - contentUploadStateTracker.setProgress(it, current, total) - } - } - } - } - - var uploadedFileEncryptedFileInfo: EncryptedFileInfo? = null - - return try { - val contentUploadResponse = if (params.isEncrypted) { - Timber.v("Encrypt file") - notifyTracker(params) { contentUploadStateTracker.setEncrypting(it) } - - val encryptionResult = MXEncryptedAttachments.encryptAttachment(FileInputStream(attachmentFile), attachment.getSafeMimeType()) - uploadedFileEncryptedFileInfo = encryptionResult.encryptedFileInfo - - fileUploader - .uploadByteArray(encryptionResult.encryptedByteArray, attachment.name, "application/octet-stream", progressListener) - } else { - fileUploader - .uploadFile(attachmentFile, attachment.name, attachment.getSafeMimeType(), progressListener) - } - - handleSuccess(params, - contentUploadResponse.contentUri, - uploadedFileEncryptedFileInfo, - uploadedThumbnailUrl, - uploadedThumbnailEncryptedFileInfo, - newImageAttributes) - } catch (t: Throwable) { - Timber.e(t) - handleFailure(params, t) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt index f10c40ded5..a4a6eb6972 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/LocalEchoEventFactory.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.internal.session.room.send +import android.content.Context import android.graphics.Bitmap import android.media.MediaMetadataRetriever import androidx.exifinterface.media.ExifInterface @@ -74,6 +75,7 @@ import javax.inject.Inject * The transactionId is used as loc */ internal class LocalEchoEventFactory @Inject constructor( + private val context: Context, @UserId private val userId: String, private val stringProvider: StringProvider, private val textPillsUtils: TextPillsUtils, @@ -266,14 +268,14 @@ internal class LocalEchoEventFactory @Inject constructor( height = height?.toInt() ?: 0, size = attachment.size.toInt() ), - url = attachment.path + url = attachment.queryUri.toString() ) return createEvent(roomId, content) } private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData): Event { val mediaDataRetriever = MediaMetadataRetriever() - mediaDataRetriever.setDataSource(attachment.path) + mediaDataRetriever.setDataSource(context, attachment.queryUri) // Use frame to calculate height and width as we are sure to get the right ones val firstFrame: Bitmap? = mediaDataRetriever.frameAtTime @@ -281,7 +283,7 @@ internal class LocalEchoEventFactory @Inject constructor( val width = firstFrame?.width ?: 0 mediaDataRetriever.release() - val thumbnailInfo = ThumbnailExtractor.extractThumbnail(attachment)?.let { + val thumbnailInfo = ThumbnailExtractor.extractThumbnail(context, attachment)?.let { ThumbnailInfo( width = it.width, height = it.height, @@ -299,10 +301,10 @@ internal class LocalEchoEventFactory @Inject constructor( size = attachment.size, duration = attachment.duration?.toInt() ?: 0, // Glide will be able to use the local path and extract a thumbnail. - thumbnailUrl = attachment.path, + thumbnailUrl = attachment.queryUri.toString(), thumbnailInfo = thumbnailInfo ), - url = attachment.path + url = attachment.queryUri.toString() ) return createEvent(roomId, content) } @@ -315,7 +317,7 @@ internal class LocalEchoEventFactory @Inject constructor( mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() } ?: "audio/mpeg", size = attachment.size ), - url = attachment.path + url = attachment.queryUri.toString() ) return createEvent(roomId, content) } @@ -329,7 +331,7 @@ internal class LocalEchoEventFactory @Inject constructor( ?: "application/octet-stream", size = attachment.size ), - url = attachment.path + url = attachment.queryUri.toString() ) return createEvent(roomId, content) } diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt index 8fb23ec49d..23873aae1c 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt @@ -54,6 +54,7 @@ class AudioPicker(override val requestCode: Int) : Picker( selectedUriList.add(dataUri) } else { data?.extras?.get(Intent.EXTRA_STREAM)?.let { + @Suppress("UNCHECKED_CAST") when (it) { is List<*> -> selectedUriList.addAll(it as List) else -> selectedUriList.add(it as Uri) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt index 6962f2098a..81e665a43f 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt @@ -23,7 +23,6 @@ import android.graphics.BitmapFactory import android.graphics.ImageDecoder import android.net.Uri import android.os.Build -import android.os.Environment import android.provider.MediaStore import androidx.core.content.FileProvider import androidx.exifinterface.media.ExifInterface diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt index 9d3292c115..0e1169755e 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt @@ -53,6 +53,7 @@ class FilePicker(override val requestCode: Int) : Picker(re selectedUriList.add(dataUri) } else { data?.extras?.get(Intent.EXTRA_STREAM)?.let { + @Suppress("UNCHECKED_CAST") when (it) { is List<*> -> selectedUriList.addAll(it as List) else -> selectedUriList.add(it as Uri) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt index f33ceb816c..8bf589800d 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt @@ -57,6 +57,7 @@ class ImagePicker(override val requestCode: Int) : Picker( selectedUriList.add(dataUri) } else { data?.extras?.get(Intent.EXTRA_STREAM)?.let { + @Suppress("UNCHECKED_CAST") when (it) { is List<*> -> selectedUriList.addAll(it as List) else -> selectedUriList.add(it as Uri) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt index 832d721eef..1549b43fd7 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt @@ -18,5 +18,4 @@ package im.vector.riotx.multipicker import androidx.core.content.FileProvider -class MultiPickerFileProvider : FileProvider() { -} +class MultiPickerFileProvider : FileProvider() diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt index 92d1e9c240..d4b8d6a985 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt @@ -54,6 +54,7 @@ class VideoPicker(override val requestCode: Int) : Picker( selectedUriList.add(dataUri) } else { data?.extras?.get(Intent.EXTRA_STREAM)?.let { + @Suppress("UNCHECKED_CAST") when (it) { is List<*> -> selectedUriList.addAll(it as List) else -> selectedUriList.add(it as Uri) diff --git a/vector/build.gradle b/vector/build.gradle index 447ad7a767..2887ebba46 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -348,9 +348,6 @@ dependencies { // Badge for compatibility implementation 'me.leolin:ShortcutBadger:1.1.22@aar' - // File picker - implementation 'com.kbeanie:multipicker:1.6@aar' - // DI implementation "com.google.dagger:dagger:$daggerVersion" kapt "com.google.dagger:dagger-compiler:$daggerVersion" diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt index c576ebe1b9..daea538e12 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsHelper.kt @@ -18,20 +18,13 @@ package im.vector.riotx.features.attachments import android.app.Activity import android.content.Context import android.content.Intent +import android.net.Uri import android.os.Bundle import androidx.fragment.app.Fragment -import com.kbeanie.multipicker.api.Picker.PICK_AUDIO -import com.kbeanie.multipicker.api.Picker.PICK_CONTACT -import com.kbeanie.multipicker.api.Picker.PICK_FILE -import com.kbeanie.multipicker.api.Picker.PICK_IMAGE_CAMERA -import com.kbeanie.multipicker.api.Picker.PICK_IMAGE_DEVICE -import com.kbeanie.multipicker.core.ImagePickerImpl -import com.kbeanie.multipicker.core.PickerManager -import com.kbeanie.multipicker.utils.IntentUtils import im.vector.matrix.android.BuildConfig import im.vector.matrix.android.api.session.content.ContentAttachmentData import im.vector.riotx.core.platform.Restorable -import im.vector.riotx.features.attachments.AttachmentsHelper.Callback +import im.vector.riotx.multipicker.MultiPicker import timber.log.Timber private const val CAPTURE_PATH_KEY = "CAPTURE_PATH_KEY" @@ -39,20 +32,8 @@ private const val PENDING_TYPE_KEY = "PENDING_TYPE_KEY" /** * This class helps to handle attachments by providing simple methods. - * The process is asynchronous and you must implement [Callback] methods to get the data or a failure. */ -class AttachmentsHelper private constructor(private val context: Context, - private val pickerManagerFactory: PickerManagerFactory) : Restorable { - - companion object { - fun create(fragment: Fragment, callback: Callback): AttachmentsHelper { - return AttachmentsHelper(fragment.requireContext(), FragmentPickerManagerFactory(fragment, callback)) - } - - fun create(activity: Activity, callback: Callback): AttachmentsHelper { - return AttachmentsHelper(activity, ActivityPickerManagerFactory(activity, callback)) - } - } +class AttachmentsHelper(val context: Context, val callback: Callback) : Restorable { interface Callback { fun onContactAttachmentReady(contactAttachment: ContactAttachment) { @@ -66,39 +47,15 @@ class AttachmentsHelper private constructor(private val context: Context, } // Capture path allows to handle camera image picking. It must be restored if the activity gets killed. - private var capturePath: String? = null + private var captureUri: Uri? = null // The pending type is set if we have to handle permission request. It must be restored if the activity gets killed. var pendingType: AttachmentTypeSelectorView.Type? = null - private val imagePicker by lazy { - pickerManagerFactory.createImagePicker() - } - - private val videoPicker by lazy { - pickerManagerFactory.createVideoPicker() - } - - private val cameraImagePicker by lazy { - pickerManagerFactory.createCameraImagePicker() - } - - private val filePicker by lazy { - pickerManagerFactory.createFilePicker() - } - - private val audioPicker by lazy { - pickerManagerFactory.createAudioPicker() - } - - private val contactPicker by lazy { - pickerManagerFactory.createContactPicker() - } - // Restorable override fun onSaveInstanceState(outState: Bundle) { - capturePath?.also { - outState.putString(CAPTURE_PATH_KEY, it) + captureUri?.also { + outState.putParcelable(CAPTURE_PATH_KEY, it) } pendingType?.also { outState.putSerializable(PENDING_TYPE_KEY, it) @@ -106,10 +63,7 @@ class AttachmentsHelper private constructor(private val context: Context, } override fun onRestoreInstanceState(savedInstanceState: Bundle?) { - capturePath = savedInstanceState?.getString(CAPTURE_PATH_KEY) - if (capturePath != null) { - cameraImagePicker.reinitialize(capturePath) - } + captureUri = savedInstanceState?.getParcelable(CAPTURE_PATH_KEY) as? Uri pendingType = savedInstanceState?.getSerializable(PENDING_TYPE_KEY) as? AttachmentTypeSelectorView.Type } @@ -118,36 +72,36 @@ class AttachmentsHelper private constructor(private val context: Context, /** * Starts the process for handling file picking */ - fun selectFile() { - filePicker.pickFile() + fun selectFile(fragment: Fragment) { + MultiPicker.get(MultiPicker.FILE).startWith(fragment) } /** * Starts the process for handling image picking */ - fun selectGallery() { - imagePicker.pickImage() + fun selectGallery(fragment: Fragment) { + MultiPicker.get(MultiPicker.IMAGE).startWith(fragment) } /** * Starts the process for handling audio picking */ - fun selectAudio() { - audioPicker.pickAudio() + fun selectAudio(fragment: Fragment) { + MultiPicker.get(MultiPicker.AUDIO).startWith(fragment) } /** * Starts the process for handling capture image picking */ - fun openCamera() { - capturePath = cameraImagePicker.pickImage() + fun openCamera(fragment: Fragment) { + captureUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(fragment) } /** * Starts the process for handling contact picking */ - fun selectContact() { - contactPicker.pickContact() + fun selectContact(fragment: Fragment) { + MultiPicker.get(MultiPicker.CONTACT).startWith(fragment) } /** @@ -157,14 +111,58 @@ class AttachmentsHelper private constructor(private val context: Context, */ fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { if (resultCode == Activity.RESULT_OK) { - val pickerManager = getPickerManagerForRequestCode(requestCode) - if (pickerManager != null) { - if (pickerManager is ImagePickerImpl) { - pickerManager.reinitialize(capturePath) + when (requestCode) { + MultiPicker.REQUEST_CODE_PICK_FILE -> { + callback.onContentAttachmentsReady( + MultiPicker.get(MultiPicker.FILE) + .getSelectedFiles(context, requestCode, resultCode, data) + .map { it.toContentAttachmentData() } + ) } - pickerManager.submit(data) - return true + MultiPicker.REQUEST_CODE_PICK_AUDIO -> { + callback.onContentAttachmentsReady( + MultiPicker.get(MultiPicker.AUDIO) + .getSelectedFiles(context, requestCode, resultCode, data) + .map { it.toContentAttachmentData() } + ) + } + MultiPicker.REQUEST_CODE_PICK_CONTACT -> { + MultiPicker.get(MultiPicker.CONTACT) + .getSelectedFiles(context, requestCode, resultCode, data) + .firstOrNull() + ?.toContactAttachment() + ?.let { + callback.onContactAttachmentReady(it) + } + } + MultiPicker.REQUEST_CODE_PICK_IMAGE -> { + callback.onContentAttachmentsReady( + MultiPicker.get(MultiPicker.IMAGE) + .getSelectedFiles(context, requestCode, resultCode, data) + .map { it.toContentAttachmentData() } + ) + } + MultiPicker.REQUEST_CODE_TAKE_PHOTO -> { + captureUri?.let { captureUri -> + MultiPicker.get(MultiPicker.CAMERA) + .getTakenPhoto(context, requestCode, resultCode, captureUri) + ?.let { + callback.onContentAttachmentsReady( + listOf(it).map { it.toContentAttachmentData() } + ) + } + } + } + MultiPicker.REQUEST_CODE_PICK_VIDEO -> { + callback.onContentAttachmentsReady( + MultiPicker.get(MultiPicker.VIDEO) + .getSelectedFiles(context, requestCode, resultCode, data) + .map { it.toContentAttachmentData() } + ) + } + else -> return false } + return true } return false } @@ -174,39 +172,35 @@ class AttachmentsHelper private constructor(private val context: Context, * * @return true if it can handle the intent data, false otherwise */ - fun handleShareIntent(intent: Intent): Boolean { + fun handleShareIntent(context: Context, intent: Intent): Boolean { val type = intent.resolveType(context) ?: return false if (type.startsWith("image")) { - imagePicker.submit(safeShareIntent(intent)) + callback.onContentAttachmentsReady( + MultiPicker.get(MultiPicker.IMAGE).getIncomingFiles(context, intent).map { + it.toContentAttachmentData() + } + ) } else if (type.startsWith("video")) { - videoPicker.submit(safeShareIntent(intent)) + callback.onContentAttachmentsReady( + MultiPicker.get(MultiPicker.VIDEO).getIncomingFiles(context, intent).map { + it.toContentAttachmentData() + } + ) } else if (type.startsWith("audio")) { - videoPicker.submit(safeShareIntent(intent)) + callback.onContentAttachmentsReady( + MultiPicker.get(MultiPicker.AUDIO).getIncomingFiles(context, intent).map { + it.toContentAttachmentData() + } + ) } else if (type.startsWith("application") || type.startsWith("file") || type.startsWith("*")) { - filePicker.submit(safeShareIntent(intent)) + callback.onContentAttachmentsReady( + MultiPicker.get(MultiPicker.FILE).getIncomingFiles(context, intent).map { + it.toContentAttachmentData() + } + ) } else { return false } return true } - - private fun safeShareIntent(intent: Intent): Intent { - // Work around for getPickerIntentForSharing doing NPE in android 10 - return try { - IntentUtils.getPickerIntentForSharing(intent) - } catch (failure: Throwable) { - intent - } - } - - private fun getPickerManagerForRequestCode(requestCode: Int): PickerManager? { - return when (requestCode) { - PICK_IMAGE_DEVICE -> imagePicker - PICK_IMAGE_CAMERA -> cameraImagePicker - PICK_FILE -> filePicker - PICK_CONTACT -> contactPicker - PICK_AUDIO -> audioPicker - else -> null - } - } } diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt index a3de5084de..02b712b8a7 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsMapper.kt @@ -16,51 +16,48 @@ package im.vector.riotx.features.attachments -import com.kbeanie.multipicker.api.entity.ChosenAudio -import com.kbeanie.multipicker.api.entity.ChosenContact -import com.kbeanie.multipicker.api.entity.ChosenFile -import com.kbeanie.multipicker.api.entity.ChosenImage -import com.kbeanie.multipicker.api.entity.ChosenVideo import im.vector.matrix.android.api.session.content.ContentAttachmentData +import im.vector.riotx.multipicker.entity.MultiPickerAudioType +import im.vector.riotx.multipicker.entity.MultiPickerBaseType +import im.vector.riotx.multipicker.entity.MultiPickerContactType +import im.vector.riotx.multipicker.entity.MultiPickerFileType +import im.vector.riotx.multipicker.entity.MultiPickerImageType +import im.vector.riotx.multipicker.entity.MultiPickerVideoType import timber.log.Timber -fun ChosenContact.toContactAttachment(): ContactAttachment { +fun MultiPickerContactType.toContactAttachment(): ContactAttachment { return ContactAttachment( displayName = displayName, photoUri = photoUri, - emails = emails.toList(), - phones = phones.toList() + emails = emailList.toList(), + phones = phoneNumberList.toList() ) } -fun ChosenFile.toContentAttachmentData(): ContentAttachmentData { +fun MultiPickerFileType.toContentAttachmentData(): ContentAttachmentData { if (mimeType == null) Timber.w("No mimeType") return ContentAttachmentData( - path = originalPath, mimeType = mimeType, type = mapType(), size = size, - date = createdAt?.time ?: System.currentTimeMillis(), name = displayName, - queryUri = queryUri + queryUri = contentUri ) } -fun ChosenAudio.toContentAttachmentData(): ContentAttachmentData { +fun MultiPickerAudioType.toContentAttachmentData(): ContentAttachmentData { if (mimeType == null) Timber.w("No mimeType") return ContentAttachmentData( - path = originalPath, mimeType = mimeType, type = mapType(), size = size, - date = createdAt?.time ?: System.currentTimeMillis(), name = displayName, duration = duration, - queryUri = queryUri + queryUri = contentUri ) } -private fun ChosenFile.mapType(): ContentAttachmentData.Type { +private fun MultiPickerBaseType.mapType(): ContentAttachmentData.Type { return when { mimeType?.startsWith("image/") == true -> ContentAttachmentData.Type.IMAGE mimeType?.startsWith("video/") == true -> ContentAttachmentData.Type.VIDEO @@ -69,10 +66,9 @@ private fun ChosenFile.mapType(): ContentAttachmentData.Type { } } -fun ChosenImage.toContentAttachmentData(): ContentAttachmentData { +fun MultiPickerImageType.toContentAttachmentData(): ContentAttachmentData { if (mimeType == null) Timber.w("No mimeType") return ContentAttachmentData( - path = originalPath, mimeType = mimeType, type = mapType(), name = displayName, @@ -80,23 +76,20 @@ fun ChosenImage.toContentAttachmentData(): ContentAttachmentData { height = height.toLong(), width = width.toLong(), exifOrientation = orientation, - date = createdAt?.time ?: System.currentTimeMillis(), - queryUri = queryUri + queryUri = contentUri ) } -fun ChosenVideo.toContentAttachmentData(): ContentAttachmentData { +fun MultiPickerVideoType.toContentAttachmentData(): ContentAttachmentData { if (mimeType == null) Timber.w("No mimeType") return ContentAttachmentData( - path = originalPath, mimeType = mimeType, type = ContentAttachmentData.Type.VIDEO, size = size, - date = createdAt?.time ?: System.currentTimeMillis(), height = height.toLong(), width = width.toLong(), duration = duration, name = displayName, - queryUri = queryUri + queryUri = contentUri ) } diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsPickerCallback.kt b/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsPickerCallback.kt deleted file mode 100644 index 62956e08c8..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/attachments/AttachmentsPickerCallback.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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.riotx.features.attachments - -import com.kbeanie.multipicker.api.callbacks.AudioPickerCallback -import com.kbeanie.multipicker.api.callbacks.ContactPickerCallback -import com.kbeanie.multipicker.api.callbacks.FilePickerCallback -import com.kbeanie.multipicker.api.callbacks.ImagePickerCallback -import com.kbeanie.multipicker.api.callbacks.VideoPickerCallback -import com.kbeanie.multipicker.api.entity.ChosenAudio -import com.kbeanie.multipicker.api.entity.ChosenContact -import com.kbeanie.multipicker.api.entity.ChosenFile -import com.kbeanie.multipicker.api.entity.ChosenImage -import com.kbeanie.multipicker.api.entity.ChosenVideo - -/** - * This class delegates the PickerManager callbacks to an [AttachmentsHelper.Callback] - */ -class AttachmentsPickerCallback(private val callback: AttachmentsHelper.Callback) - : ImagePickerCallback, - FilePickerCallback, - VideoPickerCallback, - AudioPickerCallback, - ContactPickerCallback { - - override fun onContactChosen(contact: ChosenContact?) { - if (contact == null) { - callback.onAttachmentsProcessFailed() - } else { - val contactAttachment = contact.toContactAttachment() - callback.onContactAttachmentReady(contactAttachment) - } - } - - override fun onAudiosChosen(audios: MutableList?) { - if (audios.isNullOrEmpty()) { - callback.onAttachmentsProcessFailed() - } else { - val attachments = audios.map { - it.toContentAttachmentData() - } - callback.onContentAttachmentsReady(attachments) - } - } - - override fun onFilesChosen(files: MutableList?) { - if (files.isNullOrEmpty()) { - callback.onAttachmentsProcessFailed() - } else { - val attachments = files.map { - it.toContentAttachmentData() - } - callback.onContentAttachmentsReady(attachments) - } - } - - override fun onImagesChosen(images: MutableList?) { - if (images.isNullOrEmpty()) { - callback.onAttachmentsProcessFailed() - } else { - val attachments = images.map { - it.toContentAttachmentData() - } - callback.onContentAttachmentsReady(attachments) - } - } - - override fun onVideosChosen(videos: MutableList?) { - if (videos.isNullOrEmpty()) { - callback.onAttachmentsProcessFailed() - } else { - val attachments = videos.map { - it.toContentAttachmentData() - } - callback.onContentAttachmentsReady(attachments) - } - } - - override fun onError(error: String?) { - callback.onAttachmentsProcessFailed() - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/PickerManagerFactory.kt b/vector/src/main/java/im/vector/riotx/features/attachments/PickerManagerFactory.kt deleted file mode 100644 index 6c03f21ab3..0000000000 --- a/vector/src/main/java/im/vector/riotx/features/attachments/PickerManagerFactory.kt +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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.riotx.features.attachments - -import android.app.Activity -import androidx.fragment.app.Fragment -import com.kbeanie.multipicker.api.AudioPicker -import com.kbeanie.multipicker.api.CameraImagePicker -import com.kbeanie.multipicker.api.ContactPicker -import com.kbeanie.multipicker.api.FilePicker -import com.kbeanie.multipicker.api.ImagePicker -import com.kbeanie.multipicker.api.VideoPicker - -/** - * Factory for creating different pickers. It allows to use with fragment or activity builders. - */ -interface PickerManagerFactory { - - fun createImagePicker(): ImagePicker - - fun createCameraImagePicker(): CameraImagePicker - - fun createVideoPicker(): VideoPicker - - fun createFilePicker(): FilePicker - - fun createAudioPicker(): AudioPicker - - fun createContactPicker(): ContactPicker -} - -class ActivityPickerManagerFactory(private val activity: Activity, callback: AttachmentsHelper.Callback) : PickerManagerFactory { - - private val attachmentsPickerCallback = AttachmentsPickerCallback(callback) - - override fun createImagePicker(): ImagePicker { - return ImagePicker(activity).also { - it.setImagePickerCallback(attachmentsPickerCallback) - it.allowMultiple() - } - } - - override fun createCameraImagePicker(): CameraImagePicker { - return CameraImagePicker(activity).also { - it.setImagePickerCallback(attachmentsPickerCallback) - } - } - - override fun createVideoPicker(): VideoPicker { - return VideoPicker(activity).also { - it.setVideoPickerCallback(attachmentsPickerCallback) - it.allowMultiple() - } - } - - override fun createFilePicker(): FilePicker { - return FilePicker(activity).also { - it.allowMultiple() - it.setFilePickerCallback(attachmentsPickerCallback) - } - } - - override fun createAudioPicker(): AudioPicker { - return AudioPicker(activity).also { - it.allowMultiple() - it.setAudioPickerCallback(attachmentsPickerCallback) - } - } - - override fun createContactPicker(): ContactPicker { - return ContactPicker(activity).also { - it.setContactPickerCallback(attachmentsPickerCallback) - } - } -} - -class FragmentPickerManagerFactory(private val fragment: Fragment, callback: AttachmentsHelper.Callback) : PickerManagerFactory { - - private val attachmentsPickerCallback = AttachmentsPickerCallback(callback) - - override fun createImagePicker(): ImagePicker { - return ImagePicker(fragment).also { - it.setImagePickerCallback(attachmentsPickerCallback) - it.allowMultiple() - } - } - - override fun createCameraImagePicker(): CameraImagePicker { - return CameraImagePicker(fragment).also { - it.setImagePickerCallback(attachmentsPickerCallback) - } - } - - override fun createVideoPicker(): VideoPicker { - return VideoPicker(fragment).also { - it.setVideoPickerCallback(attachmentsPickerCallback) - it.allowMultiple() - } - } - - override fun createFilePicker(): FilePicker { - return FilePicker(fragment).also { - it.allowMultiple() - it.setFilePickerCallback(attachmentsPickerCallback) - } - } - - override fun createAudioPicker(): AudioPicker { - return AudioPicker(fragment).also { - it.allowMultiple() - it.setAudioPickerCallback(attachmentsPickerCallback) - } - } - - override fun createContactPicker(): ContactPicker { - return ContactPicker(fragment).also { - it.setContactPickerCallback(attachmentsPickerCallback) - } - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewControllers.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewControllers.kt index 34f018aaf9..60ee722116 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewControllers.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewControllers.kt @@ -25,7 +25,7 @@ class AttachmentBigPreviewController @Inject constructor() : TypedEpoxyControlle override fun buildModels(data: AttachmentsPreviewViewState) { data.attachments.forEach { attachmentBigPreviewItem { - id(it.path) + id(it.queryUri.toString()) attachment(it) } } @@ -43,7 +43,7 @@ class AttachmentMiniaturePreviewController @Inject constructor() : TypedEpoxyCon override fun buildModels(data: AttachmentsPreviewViewState) { data.attachments.forEachIndexed { index, contentAttachmentData -> attachmentMiniaturePreviewItem { - id(contentAttachmentData.path) + id(contentAttachmentData.queryUri.toString()) attachment(contentAttachmentData) checked(data.currentAttachmentIndex == index) clickListener { _ -> diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewItems.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewItems.kt index 3b43fa6e20..373298bf31 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewItems.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentPreviewItems.kt @@ -33,11 +33,10 @@ abstract class AttachmentPreviewItem : VectorE abstract val attachment: ContentAttachmentData override fun bind(holder: H) { - val path = attachment.path if (attachment.type == ContentAttachmentData.Type.VIDEO || attachment.type == ContentAttachmentData.Type.IMAGE) { Glide.with(holder.view.context) .asBitmap() - .load(path) + .load(attachment.queryUri) .apply(RequestOptions().frame(0)) .into(holder.imageView) } else { diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewAction.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewAction.kt index 5acc59b035..aef724331f 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewAction.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewAction.kt @@ -17,10 +17,11 @@ package im.vector.riotx.features.attachments.preview +import android.net.Uri import im.vector.riotx.core.platform.VectorViewModelAction sealed class AttachmentsPreviewAction : VectorViewModelAction { object RemoveCurrentAttachment : AttachmentsPreviewAction() data class SetCurrentAttachment(val index: Int): AttachmentsPreviewAction() - data class UpdatePathOfCurrentAttachment(val newPath: String): AttachmentsPreviewAction() + data class UpdatePathOfCurrentAttachment(val newUri: Uri): AttachmentsPreviewAction() } diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt index e52b497df4..f059da7d85 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt @@ -172,9 +172,9 @@ class AttachmentsPreviewFragment @Inject constructor( } private fun handleCropResult(result: Intent) { - val resultPath = UCrop.getOutput(result)?.path - if (resultPath != null) { - viewModel.handle(AttachmentsPreviewAction.UpdatePathOfCurrentAttachment(resultPath)) + val resultUri = UCrop.getOutput(result) + if (resultUri != null) { + viewModel.handle(AttachmentsPreviewAction.UpdatePathOfCurrentAttachment(resultUri)) } else { Toast.makeText(requireContext(), "Cannot retrieve cropped value", Toast.LENGTH_SHORT).show() } @@ -203,7 +203,7 @@ class AttachmentsPreviewFragment @Inject constructor( val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState val destinationFile = File(requireContext().cacheDir, "${currentAttachment.name}_edited_image_${System.currentTimeMillis()}") // Note: using currentAttachment.queryUri.toUri() make the app crash when sharing from Google Photos - val uri = File(currentAttachment.path).toUri() + val uri = currentAttachment.queryUri UCrop.of(uri, destinationFile.toUri()) .withOptions( UCrop.Options() diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewViewModel.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewViewModel.kt index 1f6c8c2f8b..d1e44fa963 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewViewModel.kt @@ -62,7 +62,7 @@ class AttachmentsPreviewViewModel @AssistedInject constructor(@Assisted initialS private fun handleUpdatePathOfCurrentAttachment(action: AttachmentsPreviewAction.UpdatePathOfCurrentAttachment) = withState { val attachments = it.attachments.mapIndexed { index, contentAttachmentData -> if (index == it.currentAttachmentIndex) { - contentAttachmentData.copy(path = action.newPath) + contentAttachmentData.copy(queryUri = action.newUri) } else { contentAttachmentData } 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 13de137e8f..779b7fb089 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 @@ -251,7 +251,7 @@ class RoomDetailFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java) - attachmentsHelper = AttachmentsHelper.create(this, this).register() + attachmentsHelper = AttachmentsHelper(requireContext(), this).register() keyboardStateUtils = KeyboardStateUtils(requireActivity()) setupToolbar(roomToolbar) setupRecyclerView() @@ -517,29 +517,6 @@ class RoomDetailFragment @Inject constructor( } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - when (requestCode) { - MultiPicker.REQUEST_CODE_PICK_IMAGE -> { - MultiPicker.get(MultiPicker.IMAGE).getSelectedFiles(requireContext(), requestCode, resultCode, data) - } - MultiPicker.REQUEST_CODE_PICK_VIDEO -> { - MultiPicker.get(MultiPicker.VIDEO).getSelectedFiles(requireContext(), requestCode, resultCode, data) - } - MultiPicker.REQUEST_CODE_PICK_FILE -> { - MultiPicker.get(MultiPicker.FILE).getSelectedFiles(requireContext(), requestCode, resultCode, data) - } - MultiPicker.REQUEST_CODE_PICK_AUDIO -> { - MultiPicker.get(MultiPicker.AUDIO).getSelectedFiles(requireContext(), requestCode, resultCode, data) - } - MultiPicker.REQUEST_CODE_PICK_CONTACT -> { - MultiPicker.get(MultiPicker.CONTACT).getSelectedFiles(requireContext(), requestCode, resultCode, data) - } - MultiPicker.REQUEST_CODE_TAKE_PHOTO -> { - cameraPhotoUri?.let { - MultiPicker.get(MultiPicker.CAMERA).getTakenPhoto(requireContext(), requestCode, resultCode, it) - } - } - } - val hasBeenHandled = attachmentsHelper.onActivityResult(requestCode, resultCode, data) if (!hasBeenHandled && resultCode == RESULT_OK && data != null) { when (requestCode) { @@ -689,7 +666,7 @@ class RoomDetailFragment @Inject constructor( private fun sendUri(uri: Uri): Boolean { roomDetailViewModel.preventAttachmentPreview = true val shareIntent = Intent(Intent.ACTION_SEND, uri) - val isHandled = attachmentsHelper.handleShareIntent(shareIntent) + val isHandled = attachmentsHelper.handleShareIntent(requireContext(), shareIntent) if (!isHandled) { roomDetailViewModel.preventAttachmentPreview = false Toast.makeText(requireContext(), R.string.error_handling_incoming_share, Toast.LENGTH_SHORT).show() @@ -1372,16 +1349,13 @@ class RoomDetailFragment @Inject constructor( } } - private var cameraPhotoUri: Uri? = null private fun launchAttachmentProcess(type: AttachmentTypeSelectorView.Type) { when (type) { - AttachmentTypeSelectorView.Type.CAMERA -> { - cameraPhotoUri = MultiPicker.get(MultiPicker.CAMERA).startWithExpectingFile(this) - } - AttachmentTypeSelectorView.Type.FILE -> MultiPicker.get(MultiPicker.FILE).startWith(this) - AttachmentTypeSelectorView.Type.GALLERY -> MultiPicker.get(MultiPicker.IMAGE).startWith(this) - AttachmentTypeSelectorView.Type.AUDIO -> MultiPicker.get(MultiPicker.AUDIO).startWith(this) - AttachmentTypeSelectorView.Type.CONTACT -> MultiPicker.get(MultiPicker.CONTACT).startWith(this) + AttachmentTypeSelectorView.Type.CAMERA -> attachmentsHelper.openCamera(this) + AttachmentTypeSelectorView.Type.FILE -> attachmentsHelper.selectFile(this) + AttachmentTypeSelectorView.Type.GALLERY -> attachmentsHelper.selectGallery(this) + AttachmentTypeSelectorView.Type.AUDIO -> attachmentsHelper.selectAudio(this) + AttachmentTypeSelectorView.Type.CONTACT -> attachmentsHelper.selectContact(this) AttachmentTypeSelectorView.Type.STICKER -> vectorBaseActivity.notImplemented("Adding stickers") }.exhaustive } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 2ad90f073a..cef172da73 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -610,7 +610,7 @@ class RoomDetailViewModel @AssistedInject constructor( when (val tooBigFile = attachments.find { it.size > maxUploadFileSize }) { null -> room.sendMedias(attachments, action.compressBeforeSending, emptySet()) else -> _viewEvents.post(RoomDetailViewEvents.FileTooBigError( - tooBigFile.name ?: tooBigFile.path, + tooBigFile.name ?: tooBigFile.queryUri.toString(), tooBigFile.size, maxUploadFileSize )) diff --git a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareFragment.kt b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareFragment.kt index 74821ab2fe..aa665b5653 100644 --- a/vector/src/main/java/im/vector/riotx/features/share/IncomingShareFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/share/IncomingShareFragment.kt @@ -72,18 +72,18 @@ class IncomingShareFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) setupRecyclerView() setupToolbar(incomingShareToolbar) - attachmentsHelper = AttachmentsHelper.create(this, this).register() + attachmentsHelper = AttachmentsHelper(requireContext(), this).register() val intent = vectorBaseActivity.intent val isShareManaged = when (intent?.action) { Intent.ACTION_SEND -> { - var isShareManaged = attachmentsHelper.handleShareIntent(intent) + var isShareManaged = attachmentsHelper.handleShareIntent(requireContext(), intent) if (!isShareManaged) { isShareManaged = handleTextShare(intent) } isShareManaged } - Intent.ACTION_SEND_MULTIPLE -> attachmentsHelper.handleShareIntent(intent) + Intent.ACTION_SEND_MULTIPLE -> attachmentsHelper.handleShareIntent(requireContext(), intent) else -> false } From e36367c040570895d881788e1054f61dfa0b67d3 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 23 Mar 2020 16:27:17 +0100 Subject: [PATCH 040/156] Fix / sending secret encryption + refactoring --- .../matrix/android/common/CommonTestHelper.kt | 4 +- .../crypto/gossiping/KeyShareTests.kt | 34 ++------ .../internal/crypto/GossipingRequestState.kt | 1 + .../internal/crypto/GossipingWorkManager.kt | 57 +++++++++++++ .../crypto/IncomingGossipingRequestManager.kt | 74 +++++++++++------ .../crypto/OutgoingGossipingRequestManager.kt | 49 +++-------- .../internal/crypto/SendGossipWorker.kt | 26 ++++-- .../crypto/ShareSecretCryptoProvider.kt | 82 ------------------- .../DefaultCrossSigningService.kt | 3 +- 9 files changed, 150 insertions(+), 180 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingWorkManager.kt delete mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt index 413e4aac3c..3cf03fff53 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt @@ -282,10 +282,10 @@ class CommonTestHelper(context: Context) { } } - fun waitWithLatch(block: (CountDownLatch) -> Unit) { + fun waitWithLatch(timout: Long? = TestConstants.timeOutMillis, block: (CountDownLatch) -> Unit) { val latch = CountDownLatch(1) block(latch) - await(latch) + await(latch, timout) } // Transform a method with a MatrixCallback to a synchronous method diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt index da8ef790ab..cc8abdb5f8 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/gossiping/KeyShareTests.kt @@ -216,7 +216,7 @@ class KeyShareTests : InstrumentedTest { aliceVerificationService1.addListener(object : VerificationService.Listener { override fun transactionUpdated(tx: VerificationTransaction) { - Log.d("TEST", "AA: tx incoming?:${tx.isIncoming} state ${tx.state}") + Log.d("#TEST", "AA: tx incoming?:${tx.isIncoming} state ${tx.state}") if (tx is SasVerificationTransaction) { if (tx.state == VerificationTxState.OnStarted) { (tx as IncomingSasVerificationTransaction).performAccept() @@ -231,7 +231,7 @@ class KeyShareTests : InstrumentedTest { aliceVerificationService2.addListener(object : VerificationService.Listener { override fun transactionUpdated(tx: VerificationTransaction) { - Log.d("TEST", "BB: tx incoming?:${tx.isIncoming} state ${tx.state}") + Log.d("#TEST", "BB: tx incoming?:${tx.isIncoming} state ${tx.state}") if (tx is SasVerificationTransaction) { if (tx.state == VerificationTxState.ShortCodeReady) { session2ShortCode = tx.getDecimalCodeRepresentation() @@ -246,42 +246,26 @@ class KeyShareTests : InstrumentedTest { ?: "", txId) - waitWithLatch { latch -> - retryPeriodicallyWithLatch(latch) { + mTestHelper.waitWithLatch { latch -> + mTestHelper.retryPeriodicallyWithLatch(latch) { aliceSession1.cryptoService().getDeviceInfo(aliceSession1.myUserId, aliceSession2.sessionParams.credentials.deviceId ?: "")?.isVerified == true } } assertNotNull(session1ShortCode) - Log.d("TEST", "session1ShortCode: $session1ShortCode") + Log.d("#TEST", "session1ShortCode: $session1ShortCode") assertNotNull(session2ShortCode) - Log.d("TEST", "session2ShortCode: $session2ShortCode") + Log.d("#TEST", "session2ShortCode: $session2ShortCode") assertEquals(session1ShortCode, session2ShortCode) // SSK and USK private keys should have been shared - waitWithLatch(300_000) { latch -> - retryPeriodicallyWithLatch(latch) { + mTestHelper.waitWithLatch(60_000) { latch -> + mTestHelper.retryPeriodicallyWithLatch(latch) { + Log.d("#TEST", "CAN XS :${ aliceSession2.cryptoService().crossSigningService().getMyCrossSigningKeys()}") aliceSession2.cryptoService().crossSigningService().canCrossSign() } } } - fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) { - GlobalScope.launch { - while (true) { - delay(1000) - if (condition()) { - latch.countDown() - return@launch - } - } - } - } - - fun waitWithLatch(timeout: Long? = null, block: (CountDownLatch) -> Unit) { - val latch = CountDownLatch(1) - block(latch) - mTestHelper.await(latch, timeout) - } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingRequestState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingRequestState.kt index 16e8c35992..f2e45ef109 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingRequestState.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingRequestState.kt @@ -25,6 +25,7 @@ enum class GossipingRequestState { NONE, PENDING, REJECTED, + ACCEPTING, ACCEPTED, FAILED_TO_ACCEPTED, // USER_REJECTED, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingWorkManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingWorkManager.kt new file mode 100644 index 0000000000..b7c782c5b4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/GossipingWorkManager.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.crypto + +import androidx.work.BackoffPolicy +import androidx.work.Data +import androidx.work.ExistingWorkPolicy +import androidx.work.ListenableWorker +import androidx.work.OneTimeWorkRequest +import im.vector.matrix.android.api.util.Cancelable +import im.vector.matrix.android.internal.di.WorkManagerProvider +import im.vector.matrix.android.internal.session.SessionScope +import im.vector.matrix.android.internal.util.CancelableWork +import im.vector.matrix.android.internal.worker.startChain +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +@SessionScope +internal class GossipingWorkManager @Inject constructor( + private val workManagerProvider: WorkManagerProvider +) { + + inline fun createWork(data: Data, startChain: Boolean): OneTimeWorkRequest { + return workManagerProvider.matrixOneTimeWorkRequestBuilder() + .setConstraints(WorkManagerProvider.workConstraints) + .startChain(startChain) + .setInputData(data) + .setBackoffCriteria(BackoffPolicy.LINEAR, 10_000L, TimeUnit.MILLISECONDS) + .build() + } + + // Prevent sending queue to stay broken after app restart + // The unique queue id will stay the same as long as this object is instanciated + val queueSuffixApp = System.currentTimeMillis() + + fun postWork(workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND): Cancelable { + workManagerProvider.workManager + .beginUniqueWork(this::class.java.name + "_$queueSuffixApp", policy, workRequest) + .enqueue() + + return CancelableWork(workManagerProvider.workManager, workRequest.id) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt index 8dfd264b11..608ac35a04 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingGossipingRequestManager.kt @@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.crypto import im.vector.matrix.android.api.auth.data.Credentials +import im.vector.matrix.android.api.auth.data.sessionId import im.vector.matrix.android.api.crypto.MXCryptoConfig import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME @@ -27,16 +28,19 @@ import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.internal.crypto.model.rest.GossipingDefaultContent import im.vector.matrix.android.internal.crypto.model.rest.GossipingToDeviceObject import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore +import im.vector.matrix.android.internal.di.SessionId import im.vector.matrix.android.internal.session.SessionScope +import im.vector.matrix.android.internal.worker.WorkerParamsFactory import timber.log.Timber import javax.inject.Inject @SessionScope internal class IncomingGossipingRequestManager @Inject constructor( + @SessionId private val sessionId: String, private val credentials: Credentials, private val cryptoStore: IMXCryptoStore, private val cryptoConfig: MXCryptoConfig, - private val secretSecretCryptoProvider: ShareSecretCryptoProvider, + private val gossipingWorkManager: GossipingWorkManager, private val roomDecryptorProvider: RoomDecryptorProvider) { // list of IncomingRoomKeyRequests/IncomingRoomKeyRequestCancellations @@ -84,7 +88,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( * @param event the announcement event. */ fun onGossipingRequestEvent(event: Event) { - Timber.v("## onGossipingRequestEvent type ${event.type} from user ${event.senderId}") + Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} from user ${event.senderId}") val roomKeyShare = event.getClearContent().toModel() val ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it } when (roomKeyShare?.action) { @@ -93,7 +97,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( IncomingSecretShareRequest.fromEvent(event)?.let { if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) { // ignore, it was sent by me as * - Timber.v("## onGossipingRequestEvent type ${event.type} ignore remote echo") + Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo") } else { // save in DB cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs) @@ -104,7 +108,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( IncomingRoomKeyRequest.fromEvent(event)?.let { if (event.senderId == credentials.userId && it.deviceId == credentials.deviceId) { // ignore, it was sent by me as * - Timber.v("## onGossipingRequestEvent type ${event.type} ignore remote echo") + Timber.v("## GOSSIP onGossipingRequestEvent type ${event.type} ignore remote echo") } else { cryptoStore.storeIncomingGossipingRequest(it, ageLocalTs) receivedGossipingRequests.add(it) @@ -118,7 +122,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( } } else -> { - Timber.e("## onGossipingRequestEvent() : unsupported action ${roomKeyShare?.action}") + Timber.e("## GOSSIP onGossipingRequestEvent() : unsupported action ${roomKeyShare?.action}") } } } @@ -129,7 +133,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( * It must be called on CryptoThread */ fun processReceivedGossipingRequests() { - Timber.v("## processReceivedGossipingRequests()") + Timber.v("## GOSSIP processReceivedGossipingRequests()") val roomKeyRequestsToProcess = receivedGossipingRequests.toList() receivedGossipingRequests.clear() @@ -151,7 +155,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( } receivedRequestCancellations?.forEach { request -> - Timber.v("## processReceivedGossipingRequests() : m.room_key_request cancellation $request") + Timber.v("## GOSSIP processReceivedGossipingRequests() : m.room_key_request cancellation $request") // we should probably only notify the app of cancellations we told it // about, but we don't currently have a record of that, so we just pass // everything through. @@ -180,10 +184,10 @@ internal class IncomingGossipingRequestManager @Inject constructor( val roomId = body!!.roomId val alg = body.algorithm - Timber.v("## processIncomingRoomKeyRequest from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}") + Timber.v("## GOSSIP processIncomingRoomKeyRequest from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}") if (userId == null || credentials.userId != userId) { // TODO: determine if we sent this device the keys already: in - Timber.w("## processReceivedGossipingRequests() : Ignoring room key request from other user for now") + Timber.w("## GOSSIP processReceivedGossipingRequests() : Ignoring room key request from other user for now") cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) return } @@ -192,18 +196,18 @@ internal class IncomingGossipingRequestManager @Inject constructor( // the keys for the requested events, and can drop the requests. val decryptor = roomDecryptorProvider.getRoomDecryptor(roomId, alg) if (null == decryptor) { - Timber.w("## processReceivedGossipingRequests() : room key request for unknown $alg in room $roomId") + Timber.w("## GOSSIP processReceivedGossipingRequests() : room key request for unknown $alg in room $roomId") cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) return } if (!decryptor.hasKeysForKeyRequest(request)) { - Timber.w("## processReceivedGossipingRequests() : room key request for unknown session ${body.sessionId!!}") + Timber.w("## GOSSIP processReceivedGossipingRequests() : room key request for unknown session ${body.sessionId!!}") cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) return } if (credentials.deviceId == deviceId && credentials.userId == userId) { - Timber.v("## processReceivedGossipingRequests() : oneself device - ignored") + Timber.v("## GOSSIP processReceivedGossipingRequests() : oneself device - ignored") cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) return } @@ -218,13 +222,13 @@ internal class IncomingGossipingRequestManager @Inject constructor( val device = cryptoStore.getUserDevice(userId, deviceId!!) if (device != null) { if (device.isVerified) { - Timber.v("## processReceivedGossipingRequests() : device is already verified: sharing keys") + Timber.v("## GOSSIP processReceivedGossipingRequests() : device is already verified: sharing keys") request.share?.run() return } if (device.isBlocked) { - Timber.v("## processReceivedGossipingRequests() : device is blocked -> ignored") + Timber.v("## GOSSIP processReceivedGossipingRequests() : device is blocked -> ignored") cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) return } @@ -245,30 +249,30 @@ internal class IncomingGossipingRequestManager @Inject constructor( private fun processIncomingSecretShareRequest(request: IncomingSecretShareRequest) { val secretName = request.secretName ?: return Unit.also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) - Timber.v("## processIncomingSecretShareRequest() : Missing secret name") + Timber.v("## GOSSIP processIncomingSecretShareRequest() : Missing secret name") } val userId = request.userId if (userId == null || credentials.userId != userId) { - Timber.e("## processIncomingSecretShareRequest() : Ignoring secret share request from other users") + Timber.e("## GOSSIP processIncomingSecretShareRequest() : Ignoring secret share request from other users") cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) return } val deviceId = request.deviceId ?: return Unit.also { - Timber.e("## processIncomingSecretShareRequest() : Malformed request, no ") + Timber.e("## GOSSIP processIncomingSecretShareRequest() : Malformed request, no ") cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) } val device = cryptoStore.getUserDevice(userId, deviceId) ?: return Unit.also { - Timber.e("## processIncomingSecretShareRequest() : Received secret share request from unknown device ${request.deviceId}") + Timber.e("## GOSSIP processIncomingSecretShareRequest() : Received secret share request from unknown device ${request.deviceId}") cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) } if (!device.isVerified || device.isBlocked) { - Timber.v("## processIncomingSecretShareRequest() : Ignoring secret share request from untrusted/blocked session $device") + Timber.v("## GOSSIP processIncomingSecretShareRequest() : Ignoring secret share request from untrusted/blocked session $device") cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) return } @@ -281,13 +285,20 @@ internal class IncomingGossipingRequestManager @Inject constructor( USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user else -> null }?.let { secretValue -> - // TODO check if locally trusted and not outdated - Timber.i("## processIncomingSecretShareRequest() : Sharing secret $secretName with $device locally trusted") + Timber.i("## GOSSIP processIncomingSecretShareRequest() : Sharing secret $secretName with $device locally trusted") if (isDeviceLocallyVerified == true && hasBeenVerifiedLessThanFiveMinutesFromNow(deviceId)) { - secretSecretCryptoProvider.shareSecretWithDevice(request, secretValue) - cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED) + + val params = SendGossipWorker.Params( + sessionId = sessionId, + secretValue = secretValue, + request = request + ) + + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING) + val workRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(params), true) + gossipingWorkManager.postWork(workRequest) } else { - Timber.v("## processIncomingSecretShareRequest() : Can't share secret $secretName with $device, verification too old") + Timber.v("## GOSSIP processIncomingSecretShareRequest() : Can't share secret $secretName with $device, verification too old") cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) } return @@ -298,7 +309,16 @@ internal class IncomingGossipingRequestManager @Inject constructor( } request.share = { secretValue -> - secretSecretCryptoProvider.shareSecretWithDevice(request, secretValue) + + val params = SendGossipWorker.Params( + sessionId = userId, + secretValue = secretValue, + request = request + ) + + cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING) + val workRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(params), true) + gossipingWorkManager.postWork(workRequest) cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED) } @@ -333,7 +353,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( return } } catch (e: Exception) { - Timber.e(e, "## onRoomKeyRequest() failed") + Timber.e(e, "## GOSSIP onRoomKeyRequest() failed") } } } @@ -352,7 +372,7 @@ internal class IncomingGossipingRequestManager @Inject constructor( try { listener.onRoomKeyRequestCancellation(request) } catch (e: Exception) { - Timber.e(e, "## onRoomKeyRequestCancellation() failed") + Timber.e(e, "## GOSSIP onRoomKeyRequestCancellation() failed") } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt index 7c83ccc9bf..c06f10b106 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/OutgoingGossipingRequestManager.kt @@ -17,27 +17,17 @@ package im.vector.matrix.android.internal.crypto -import androidx.work.BackoffPolicy -import androidx.work.Data -import androidx.work.ExistingWorkPolicy -import androidx.work.ListenableWorker -import androidx.work.OneTimeWorkRequest import im.vector.matrix.android.api.session.events.model.LocalEcho -import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.di.SessionId -import im.vector.matrix.android.internal.di.WorkManagerProvider import im.vector.matrix.android.internal.session.SessionScope -import im.vector.matrix.android.internal.util.CancelableWork import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.worker.WorkerParamsFactory -import im.vector.matrix.android.internal.worker.startChain import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.inject.Inject @SessionScope @@ -46,7 +36,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor( private val cryptoStore: IMXCryptoStore, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val cryptoCoroutineScope: CoroutineScope, - private val workManagerProvider: WorkManagerProvider) { + private val gossipingWorkManager: GossipingWorkManager) { /** * Send off a room key request, if we haven't already done so. @@ -65,7 +55,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor( cryptoStore.getOrAddOutgoingRoomKeyRequest(requestBody, recipients)?.let { // Don't resend if it's already done, you need to cancel first (reRequest) if (it.state == OutgoingGossipingRequestState.SENDING || it.state == OutgoingGossipingRequestState.SENT) { - Timber.v("## sendOutgoingRoomKeyRequest() : we already request for that session: $it") + Timber.v("## GOSSIP sendOutgoingRoomKeyRequest() : we already request for that session: $it") return@launch } @@ -82,7 +72,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor( cryptoStore.getOrAddOutgoingSecretShareRequest(secretName, recipients)?.let { // TODO check if there is already one that is being sent? if (it.state == OutgoingGossipingRequestState.SENDING || it.state == OutgoingGossipingRequestState.SENT) { - Timber.v("## sendOutgoingRoomKeyRequest() : we already request for that session: $it") + Timber.v("## GOSSIP sendSecretShareRequest() : we already request for that session: $it") return@launch } @@ -123,7 +113,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor( val req = cryptoStore.getOutgoingRoomKeyRequest(requestBody) ?: // no request was made for this key return Unit.also { - Timber.v("## cancelRoomKeyRequest() Unknown request") + Timber.v("## GOSSIP cancelRoomKeyRequest() Unknown request") } sendOutgoingRoomKeyRequestCancellation(req, andResend) @@ -135,7 +125,7 @@ internal class OutgoingGossipingRequestManager @Inject constructor( * @param request the request */ private fun sendOutgoingGossipingRequest(request: OutgoingGossipingRequest) { - Timber.v("## sendOutgoingRoomKeyRequest() : Requesting keys $request") + Timber.v("## GOSSIP sendOutgoingRoomKeyRequest() : Requesting keys $request") val params = SendGossipRequestWorker.Params( sessionId = sessionId, @@ -143,8 +133,8 @@ internal class OutgoingGossipingRequestManager @Inject constructor( secretShareRequest = request as? OutgoingSecretRequest ) cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.SENDING) - val workRequest = createWork(WorkerParamsFactory.toData(params), true) - postWork(workRequest) + val workRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(params), true) + gossipingWorkManager.postWork(workRequest) } /** @@ -157,33 +147,16 @@ internal class OutgoingGossipingRequestManager @Inject constructor( val params = CancelGossipRequestWorker.Params.fromRequest(sessionId, request) cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.CANCELLING) - val workRequest = createWork(WorkerParamsFactory.toData(params), true) - postWork(workRequest) + val workRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(params), true) + gossipingWorkManager.postWork(workRequest) if (resend) { val reSendParams = SendGossipRequestWorker.Params( sessionId = sessionId, keyShareRequest = request.copy(requestId = LocalEcho.createLocalEchoId()) ) - val reSendWorkRequest = createWork(WorkerParamsFactory.toData(reSendParams), true) - postWork(reSendWorkRequest) + val reSendWorkRequest = gossipingWorkManager.createWork(WorkerParamsFactory.toData(reSendParams), true) + gossipingWorkManager.postWork(reSendWorkRequest) } } - - private inline fun createWork(data: Data, startChain: Boolean): OneTimeWorkRequest { - return workManagerProvider.matrixOneTimeWorkRequestBuilder() - .setConstraints(WorkManagerProvider.workConstraints) - .startChain(startChain) - .setInputData(data) - .setBackoffCriteria(BackoffPolicy.LINEAR, 10_000L, TimeUnit.MILLISECONDS) - .build() - } - - private fun postWork(workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND): Cancelable { - workManagerProvider.workManager - .beginUniqueWork(this::class.java.name, policy, workRequest) - .enqueue() - - return CancelableWork(workManagerProvider.workManager, workRequest.id) - } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipWorker.kt index 96791005d1..75a9eedea5 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/SendGossipWorker.kt @@ -27,6 +27,7 @@ import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.LocalEcho import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent @@ -53,8 +54,8 @@ internal class SendGossipWorker(context: Context, @Inject lateinit var cryptoStore: IMXCryptoStore @Inject lateinit var eventBus: EventBus @Inject lateinit var credentials: Credentials -// @Inject lateinit var secretSecretCryptoProvider: ShareSecretCryptoProvider @Inject lateinit var messageEncrypter: MessageEncrypter + @Inject lateinit var ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction override suspend fun doWork(): Result { val errorOutputData = Data.Builder().putBoolean("failed", true).build() @@ -84,11 +85,26 @@ internal class SendGossipWorker(context: Context, Timber.e("Unknown deviceInfo, cannot send message, sessionId: ${params.request.deviceId}") } - val payloadJson = mutableMapOf("type" to EventType.SEND_SECRET) - payloadJson["content"] = toDeviceContent.toContent() - val sendToDeviceMap = MXUsersDevicesMap() + val devicesByUser = mapOf(requestingUserId to listOf(deviceInfo)) + val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser) + val olmSessionResult = usersDeviceMap.getObject(requestingUserId, requestingDeviceId) + if (olmSessionResult?.sessionId == null) { + // no session with this device, probably because there + // were no one-time keys. + return Result.success(errorOutputData).also { + cryptoStore.updateGossipingRequestState(params.request, GossipingRequestState.FAILED_TO_ACCEPTED) + Timber.e("no session with this device, probably because there were no one-time keys.") + } + } + + + val payloadJson = mapOf( + "type" to EventType.SEND_SECRET, + "content" to toDeviceContent.toContent() + ) + try { val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) sendToDeviceMap.setObject(requestingUserId, requestingDeviceId, encodedPayload) @@ -108,7 +124,7 @@ internal class SendGossipWorker(context: Context, try { sendToDeviceTask.execute( SendToDeviceTask.Params( - eventType = eventType, + eventType = EventType.ENCRYPTED, contentMap = sendToDeviceMap, transactionId = localId ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt deleted file mode 100644 index 7a7f6acb5f..0000000000 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/ShareSecretCryptoProvider.kt +++ /dev/null @@ -1,82 +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 - -import im.vector.matrix.android.api.session.events.model.Event -import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.events.model.toContent -import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter -import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmDecryptionFactory -import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap -import im.vector.matrix.android.internal.crypto.model.event.SecretSendEventContent -import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore -import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask -import im.vector.matrix.android.internal.di.UserId -import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import timber.log.Timber -import javax.inject.Inject - -internal class ShareSecretCryptoProvider @Inject constructor( - val messageEncrypter: MessageEncrypter, - val sendToDeviceTask: SendToDeviceTask, - val deviceListManager: DeviceListManager, - @UserId val myUserId: String, - private val olmDecryptionFactory: MXOlmDecryptionFactory, - val cryptoCoroutineScope: CoroutineScope, - val cryptoStore: IMXCryptoStore, - val coroutineDispatchers: MatrixCoroutineDispatchers -) { - fun shareSecretWithDevice(request: IncomingSecretShareRequest, secretValue: String) { - val userId = request.userId ?: return - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - // runCatching { deviceListManager.downloadKeys(listOf(userId), false) } -// .mapCatching { - val deviceId = request.deviceId - val deviceInfo = cryptoStore.getUserDevice(userId, deviceId ?: "") ?: return@launch - - - Timber.i("## shareSecretWithDevice() : sharing secret ${request.secretName} with device $userId:$deviceId") - - val payloadJson = mutableMapOf("type" to EventType.SEND_SECRET) - val secretContent = SecretSendEventContent( - requestId = request.requestId ?: "", - secretValue = secretValue - ) - payloadJson["content"] = secretContent.toContent() - - cryptoStore.saveGossipingEvent(Event( - type = EventType.SEND_SECRET, - content = secretContent.toContent(), - senderId = myUserId - )) - - val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) - val sendToDeviceMap = MXUsersDevicesMap() - sendToDeviceMap.setObject(userId, deviceId, encodedPayload) - Timber.i("## shareSecretWithDevice() : sending to $userId:$deviceId") - val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) - sendToDeviceTask.execute(sendToDeviceParams) -// } - } - } - - fun decryptEvent(event: Event): MXEventDecryptionResult { - return olmDecryptionFactory.create().decryptEvent(event, ShareSecretCryptoProvider::class.java.name ?: "") - } -} 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 45af09f05b..409c1732a4 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 @@ -596,7 +596,8 @@ internal class DefaultCrossSigningService @Inject constructor( } override fun canCrossSign(): Boolean { - return cryptoStore.getCrossSigningPrivateKeys()?.selfSigned != null + return checkSelfTrust().isVerified() && cryptoStore.getCrossSigningPrivateKeys()?.selfSigned != null + && cryptoStore.getCrossSigningPrivateKeys()?.user != null } override fun trustUser(otherUserId: String, callback: MatrixCallback) { From 49e5fafb2d89683771448bdc0488626bc7786b85 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 23 Mar 2020 16:27:32 +0100 Subject: [PATCH 041/156] New sign in detection flow --- .../internal/crypto/DefaultCryptoService.kt | 16 ++-- .../DefaultVerificationService.kt | 1 + .../im/vector/riotx/core/di/FragmentModule.kt | 12 +++ .../verification/VerificationBottomSheet.kt | 50 +++++++++- .../VerificationBottomSheetViewEvents.kt | 1 + .../VerificationBottomSheetViewModel.kt | 51 +++++++++- .../cancel/VerificationCancelController.kt | 96 +++++++++++++++++++ .../cancel/VerificationCancelFragment.kt | 66 +++++++++++++ .../cancel/VerificationNotMeController.kt | 83 ++++++++++++++++ .../cancel/VerificationNotMeFragment.kt | 66 +++++++++++++ .../VerificationChooseMethodController.kt | 17 ++++ .../VerificationChooseMethodFragment.kt | 4 + .../VerificationChooseMethodViewModel.kt | 10 +- .../request/VerificationRequestController.kt | 47 +++++++-- .../request/VerificationRequestFragment.kt | 4 + .../riotx/features/home/HomeDetailFragment.kt | 37 +++++++ .../home/HomeSharedActionViewModel.kt | 8 ++ .../UnknwonDeviceDetectorSharedViewModel.kt | 75 +++++++++++++++ .../features/navigation/DefaultNavigator.kt | 4 +- .../riotx/features/navigation/Navigator.kt | 2 +- .../settings/VectorSettingsActivity.kt | 3 + .../CrossSigningEpoxyController.kt | 11 +++ vector/src/main/res/values/strings_riotX.xml | 14 +++ 23 files changed, 651 insertions(+), 27 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelController.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationNotMeController.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationNotMeFragment.kt create mode 100644 vector/src/main/java/im/vector/riotx/features/home/UnknwonDeviceDetectorSharedViewModel.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index 83fb53424e..d0a08b17ab 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -239,7 +239,7 @@ internal class DefaultCryptoService @Inject constructor( override fun getDevicesList(callback: MatrixCallback) { getDevicesTask .configureWith { - this.executionThread = TaskThread.CRYPTO +// this.executionThread = TaskThread.CRYPTO this.callback = callback } .executeBy(taskExecutor) @@ -729,30 +729,30 @@ internal class DefaultCryptoService @Inject constructor( */ private fun onRoomKeyEvent(event: Event) { val roomKeyContent = event.getClearContent().toModel() ?: return - Timber.v("## onRoomKeyEvent() : type<${event.type}> , sessionId<${roomKeyContent.sessionId}>") + Timber.v("## GOSSIP onRoomKeyEvent() : type<${event.type}> , sessionId<${roomKeyContent.sessionId}>") if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.algorithm.isNullOrEmpty()) { - Timber.e("## onRoomKeyEvent() : missing fields") + Timber.e("## GOSSIP onRoomKeyEvent() : missing fields") return } val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(roomKeyContent.roomId, roomKeyContent.algorithm) if (alg == null) { - Timber.e("## onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}") + Timber.e("## GOSSIP onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}") return } alg.onRoomKeyEvent(event, keysBackupService) } private fun onSecretSendReceived(event: Event) { - Timber.i("## onSecretSend() : onSecretSendReceived ${event.content?.get("sender_key")}") + Timber.i("## GOSSIP onSecretSend() : onSecretSendReceived ${event.content?.get("sender_key")}") if (!event.isEncrypted()) { // secret send messages must be encrypted - Timber.e("## onSecretSend() :Received unencrypted secret send event") + Timber.e("## GOSSIP onSecretSend() :Received unencrypted secret send event") return } // Was that sent by us? if (event.senderId != credentials.userId) { - Timber.e("## onSecretSend() : Ignore secret from other user ${event.senderId}") + Timber.e("## GOSSIP onSecretSend() : Ignore secret from other user ${event.senderId}") return } @@ -762,7 +762,7 @@ internal class DefaultCryptoService @Inject constructor( .getOutgoingSecretKeyRequests().firstOrNull { it.requestId == secretContent.requestId } if (existingRequest == null) { - Timber.i("## onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}") + Timber.i("## GOSSIP onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}") return } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt index a0420b0125..86289301e4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt @@ -757,6 +757,7 @@ internal class DefaultVerificationService @Inject constructor( private suspend fun onReadyReceived(event: Event) { val readyReq = event.getClearContent().toModel()?.asValidObject() + Timber.v("## SAS onReadyReceived $readyReq") if (readyReq == null || event.senderId == null) { // ignore 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 4e60a1bdf7..4deaef32ab 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 @@ -26,6 +26,8 @@ import im.vector.riotx.features.attachments.preview.AttachmentsPreviewFragment import im.vector.riotx.features.createdirect.CreateDirectRoomDirectoryUsersFragment import im.vector.riotx.features.createdirect.CreateDirectRoomKnownUsersFragment import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupSettingsFragment +import im.vector.riotx.features.crypto.verification.cancel.VerificationCancelFragment +import im.vector.riotx.features.crypto.verification.cancel.VerificationNotMeFragment import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment @@ -336,6 +338,16 @@ interface FragmentModule { @FragmentKey(VerificationConclusionFragment::class) fun bindVerificationConclusionFragment(fragment: VerificationConclusionFragment): Fragment + @Binds + @IntoMap + @FragmentKey(VerificationCancelFragment::class) + fun bindVerificationCancelFragment(fragment: VerificationCancelFragment): Fragment + + @Binds + @IntoMap + @FragmentKey(VerificationNotMeFragment::class) + fun bindVerificationNotMeFragment(fragment: VerificationNotMeFragment): Fragment + @Binds @IntoMap @FragmentKey(QrCodeScannerFragment::class) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt index e1218ec4a9..9edf57e39d 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -16,9 +16,11 @@ package im.vector.riotx.features.crypto.verification import android.app.Activity +import android.app.Dialog import android.content.Intent import android.os.Bundle import android.os.Parcelable +import android.view.KeyEvent import android.view.View import android.widget.ImageView import android.widget.TextView @@ -39,14 +41,18 @@ import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.extensions.commitTransaction import im.vector.riotx.core.extensions.exhaustive +import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseBottomSheetDialogFragment import im.vector.riotx.features.crypto.quads.SharedSecureStorageActivity +import im.vector.riotx.features.crypto.verification.cancel.VerificationCancelFragment +import im.vector.riotx.features.crypto.verification.cancel.VerificationNotMeFragment import im.vector.riotx.features.crypto.verification.choose.VerificationChooseMethodFragment import im.vector.riotx.features.crypto.verification.conclusion.VerificationConclusionFragment import im.vector.riotx.features.crypto.verification.emoji.VerificationEmojiCodeFragment import im.vector.riotx.features.crypto.verification.qrconfirmation.VerificationQrScannedByOtherFragment import im.vector.riotx.features.crypto.verification.request.VerificationRequestFragment import im.vector.riotx.features.home.AvatarRenderer +import im.vector.riotx.features.settings.VectorSettingsActivity import kotlinx.android.parcel.Parcelize import timber.log.Timber import javax.inject.Inject @@ -58,6 +64,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { data class VerificationArgs( val otherUserId: String, val verificationId: String? = null, + val verificationLocalId: String? = null, val roomId: String? = null, // Special mode where UX should show loading wheel until other session sends a request/tx val selfVerificationMode: Boolean = false @@ -80,13 +87,17 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { lateinit var otherUserNameText: TextView @BindView(R.id.verificationRequestShield) - lateinit var otherUserShield: View + lateinit var otherUserShield: ImageView @BindView(R.id.verificationRequestAvatar) lateinit var otherUserAvatarImageView: ImageView override fun getLayoutResId() = R.layout.bottom_sheet_verification + init { + isCancelable = false + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -110,10 +121,27 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { .show() Unit } + VerificationBottomSheetViewEvents.GoToSettings -> { + dismiss() + (activity as? VectorBaseActivity)?.navigator?.openSettings(requireContext(), VectorSettingsActivity.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY) + } }.exhaustive } } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return super.onCreateDialog(savedInstanceState).apply { + setOnKeyListener { _, keyCode, keyEvent -> + if (keyCode == KeyEvent.KEYCODE_BACK && keyEvent.action == KeyEvent.ACTION_UP) { + viewModel.queryCancel() + true + } else { + false + } + } + } + } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (resultCode == Activity.RESULT_OK && requestCode == SECRET_REQUEST_CODE) { data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)?.let { @@ -127,15 +155,17 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { state.otherUserMxItem?.let { matrixItem -> if (state.isMe) { + + avatarRenderer.render(matrixItem, otherUserAvatarImageView) if (state.sasTransactionState == VerificationTxState.Verified || state.qrTransactionState == VerificationTxState.Verified || state.verifiedFromPrivateKeys) { - otherUserAvatarImageView.setImageResource(R.drawable.ic_shield_trusted) + otherUserShield.setImageResource(R.drawable.ic_shield_trusted) } else { - otherUserAvatarImageView.setImageResource(R.drawable.ic_shield_warning) + otherUserShield.setImageResource(R.drawable.ic_shield_warning) } otherUserNameText.text = getString(R.string.complete_security) - otherUserShield.isVisible = false + otherUserShield.isVisible = true } else { avatarRenderer.render(matrixItem, otherUserAvatarImageView) @@ -149,6 +179,18 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { } } + if (state.userThinkItsNotHim) { + otherUserNameText.text = getString(R.string.dialog_title_warning) + showFragment(VerificationNotMeFragment::class, Bundle()) + return@withState + } + + if (state.userWantsToCancel) { + otherUserNameText.text = getString(R.string.are_you_sure) + showFragment(VerificationCancelFragment::class, Bundle()) + return@withState + } + if (state.selfVerificationMode && state.verifiedFromPrivateKeys) { showFragment(VerificationConclusionFragment::class, Bundle().apply { putParcelable(MvRx.KEY_ARG, VerificationConclusionFragment.Args(true, null, state.isMe)) diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewEvents.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewEvents.kt index d7c02a8d3b..7e3a5441de 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewEvents.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewEvents.kt @@ -24,5 +24,6 @@ import im.vector.riotx.core.platform.VectorViewEvents sealed class VerificationBottomSheetViewEvents : VectorViewEvents { object Dismiss : VerificationBottomSheetViewEvents() object AccessSecretStore : VerificationBottomSheetViewEvents() + object GoToSettings : VerificationBottomSheetViewEvents() data class ModalError(val errorMessage: CharSequence) : VerificationBottomSheetViewEvents() } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt index db8dd895b4..fab59fe5af 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -31,7 +31,9 @@ import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import im.vector.matrix.android.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME +import im.vector.matrix.android.api.session.crypto.verification.CancelCode import im.vector.matrix.android.api.session.crypto.verification.IncomingSasVerificationTransaction +import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest import im.vector.matrix.android.api.session.crypto.verification.QrCodeVerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.SasVerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationMethod @@ -44,7 +46,6 @@ import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64 import im.vector.matrix.android.internal.crypto.crosssigning.isVerified -import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.platform.VectorViewModel import timber.log.Timber @@ -60,7 +61,10 @@ data class VerificationBottomSheetViewState( // true when we display the loading and we wait for the other (incoming request) val selfVerificationMode: Boolean = false, val verifiedFromPrivateKeys: Boolean = false, - val isMe: Boolean = false + val isMe: Boolean = false, + val currentDeviceCanCrossSign: Boolean = false, + val userWantsToCancel: Boolean = false, + val userThinkItsNotHim: Boolean = false ) : MvRxState class VerificationBottomSheetViewModel @AssistedInject constructor( @@ -111,7 +115,8 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( pendingRequest = if (pr != null) Success(pr) else Uninitialized, selfVerificationMode = selfVerificationMode, roomId = args.roomId, - isMe = args.otherUserId == session.myUserId + isMe = args.otherUserId == session.myUserId, + currentDeviceCanCrossSign = session.cryptoService().crossSigningService().canCrossSign() ) } @@ -137,6 +142,46 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( args: VerificationBottomSheet.VerificationArgs): VerificationBottomSheetViewModel } + fun queryCancel() { + setState { + copy(userWantsToCancel = true) + } + } + + fun confirmCancel() = withState { state -> + session.cryptoService() + .verificationService() + .getExistingTransaction(state.otherUserMxItem?.id ?: "", state.transactionId ?: "") + ?.cancel(CancelCode.User) + _viewEvents.post(VerificationBottomSheetViewEvents.Dismiss) + } + + fun continueFromCancel() { + setState { + copy(userWantsToCancel = false) + } + } + + fun continueFromWasNotMe() { + setState { + copy(userThinkItsNotHim = false) + } + } + + fun itWasNotMe() { + setState { + copy(userThinkItsNotHim = true) + } + } + + fun goToSettings() = withState { state -> + session.cryptoService() + .verificationService() + .getExistingTransaction(state.otherUserMxItem?.id ?: "", state.transactionId ?: "") + ?.cancel(CancelCode.User) + _viewEvents.post(VerificationBottomSheetViewEvents.GoToSettings) + } + companion object : MvRxViewModelFactory { override fun create(viewModelContext: ViewModelContext, state: VerificationBottomSheetViewState): VerificationBottomSheetViewModel? { diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelController.kt new file mode 100644 index 0000000000..1beea4ae9f --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelController.kt @@ -0,0 +1,96 @@ +/* + * 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.crypto.verification.cancel + +import com.airbnb.epoxy.EpoxyController +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.dividerItem +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewState +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem +import javax.inject.Inject + +class VerificationCancelController @Inject constructor( + private val stringProvider: StringProvider, + private val colorProvider: ColorProvider +) : EpoxyController() { + + var listener: Listener? = null + + private var viewState: VerificationBottomSheetViewState? = null + + fun update(viewState: VerificationBottomSheetViewState) { + this.viewState = viewState + requestModelBuild() + } + + override fun buildModels() { + val state = viewState ?: return + + if (state.isMe) { + if (state.currentDeviceCanCrossSign) { + bottomSheetVerificationNoticeItem { + id("notice") + notice(stringProvider.getString(R.string.verify_cancel_self_verification_from_trusted)) + } + } else { + bottomSheetVerificationNoticeItem { + id("notice") + notice(stringProvider.getString(R.string.verify_cancel_self_verification_from_untrusted)) + } + } + } else { + bottomSheetVerificationNoticeItem { + id("notice") + notice(stringProvider.getString(R.string.verify_cancel_self_verification_from_untrusted)) + } + } + + dividerItem { + id("sep0") + } + + bottomSheetVerificationActionItem { + id("cancel") + title(stringProvider.getString(R.string.cancel)) + titleColor(colorProvider.getColor(R.color.riotx_destructive_accent)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColor(R.color.riotx_destructive_accent)) + listener { listener?.onTapCancel() } + } + + dividerItem { + id("sep1") + } + + bottomSheetVerificationActionItem { + id("continue") + title(stringProvider.getString(R.string._continue)) + titleColor(colorProvider.getColor(R.color.riotx_positive_accent)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColor(R.color.riotx_positive_accent)) + listener { listener?.onTapContinue() } + } + } + + interface Listener { + fun onTapCancel() + fun onTapContinue() + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelFragment.kt new file mode 100644 index 0000000000..0c5c070156 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationCancelFragment.kt @@ -0,0 +1,66 @@ +/* + * 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.crypto.verification.cancel + +import android.os.Bundle +import android.view.View +import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState +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.VectorBaseFragment +import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewModel +import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.* +import javax.inject.Inject + +class VerificationCancelFragment @Inject constructor( + val controller: VerificationCancelController +) : VectorBaseFragment(), VerificationCancelController.Listener { + + private val viewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) + + override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupRecyclerView() + } + + override fun onDestroyView() { + bottomSheetVerificationRecyclerView.cleanup() + controller.listener = null + super.onDestroyView() + } + + private fun setupRecyclerView() { + bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true) + controller.listener = this + } + + override fun invalidate() = withState(viewModel) { state -> + controller.update(state) + } + + override fun onTapCancel() { + viewModel.confirmCancel() + } + + override fun onTapContinue() { + viewModel.continueFromCancel() + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationNotMeController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationNotMeController.kt new file mode 100644 index 0000000000..3929c1a166 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationNotMeController.kt @@ -0,0 +1,83 @@ +/* + * 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.crypto.verification.cancel + +import com.airbnb.epoxy.EpoxyController +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.dividerItem +import im.vector.riotx.core.resources.ColorProvider +import im.vector.riotx.core.resources.StringProvider +import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewState +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationActionItem +import im.vector.riotx.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem +import im.vector.riotx.features.html.EventHtmlRenderer +import javax.inject.Inject + +class VerificationNotMeController @Inject constructor( + private val stringProvider: StringProvider, + private val colorProvider: ColorProvider, + private val eventHtmlRenderer: EventHtmlRenderer +) : EpoxyController() { + + var listener: Listener? = null + + private var viewState: VerificationBottomSheetViewState? = null + + fun update(viewState: VerificationBottomSheetViewState) { + this.viewState = viewState + requestModelBuild() + } + + override fun buildModels() { + + bottomSheetVerificationNoticeItem { + id("notice") + notice(eventHtmlRenderer.render(stringProvider.getString(R.string.verify_not_me_self_verification))) + } + + dividerItem { + id("sep0") + } + + bottomSheetVerificationActionItem { + id("skip") + title(stringProvider.getString(R.string.skip)) + titleColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) + listener { listener?.onTapSkip() } + } + + dividerItem { + id("sep1") + } + + bottomSheetVerificationActionItem { + id("settings") + title(stringProvider.getString(R.string.settings)) + titleColor(colorProvider.getColor(R.color.riotx_positive_accent)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColor(R.color.riotx_positive_accent)) + listener { listener?.onTapSettings() } + } + } + + interface Listener { + fun onTapSkip() + fun onTapSettings() + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationNotMeFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationNotMeFragment.kt new file mode 100644 index 0000000000..b764639078 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/cancel/VerificationNotMeFragment.kt @@ -0,0 +1,66 @@ +/* + * 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.crypto.verification.cancel + +import android.os.Bundle +import android.view.View +import com.airbnb.mvrx.parentFragmentViewModel +import com.airbnb.mvrx.withState +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.VectorBaseFragment +import im.vector.riotx.features.crypto.verification.VerificationBottomSheetViewModel +import kotlinx.android.synthetic.main.bottom_sheet_verification_child_fragment.* +import javax.inject.Inject + +class VerificationNotMeFragment @Inject constructor( + val controller: VerificationNotMeController +) : VectorBaseFragment(), VerificationNotMeController.Listener { + + private val viewModel by parentFragmentViewModel(VerificationBottomSheetViewModel::class) + + override fun getLayoutResId() = R.layout.bottom_sheet_verification_child_fragment + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupRecyclerView() + } + + override fun onDestroyView() { + bottomSheetVerificationRecyclerView.cleanup() + controller.listener = null + super.onDestroyView() + } + + private fun setupRecyclerView() { + bottomSheetVerificationRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true) + controller.listener = this + } + + override fun invalidate() = withState(viewModel) { state -> + controller.update(state) + } + + override fun onTapSkip() { + viewModel.continueFromWasNotMe() + } + + override fun onTapSettings() { + viewModel.goToSettings() + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt index 87bb843291..919869500f 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodController.kt @@ -95,10 +95,27 @@ class VerificationChooseMethodController @Inject constructor( listener { listener?.doVerifyBySas() } } } + + if (state.isMe && state.canCrossSign) { + dividerItem { + id("sep_notMe") + } + + bottomSheetVerificationActionItem { + id("wasnote") + title(stringProvider.getString(R.string.verify_new_session_was_not_me)) + titleColor(colorProvider.getColor(R.color.riotx_destructive_accent)) + subTitle(stringProvider.getString(R.string.verify_new_session_compromized)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) + listener { listener?.onClickOnWasNotMe() } + } + } } interface Listener { fun openCamera() fun doVerifyBySas() + fun onClickOnWasNotMe() } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodFragment.kt index e0b7f97383..eb32f5b0e3 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodFragment.kt @@ -89,6 +89,10 @@ class VerificationChooseMethodFragment @Inject constructor( } } + override fun onClickOnWasNotMe() { + sharedViewModel.itWasNotMe() + } + private fun doOpenQRCodeScanner() { QrCodeScannerActivity.startForResult(this) } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt index c7fdf77123..3c3009ed01 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt @@ -39,7 +39,9 @@ data class VerificationChooseMethodViewState( val otherCanShowQrCode: Boolean = false, val otherCanScanQrCode: Boolean = false, val qrCodeText: String? = null, - val SASModeAvailable: Boolean = false + val SASModeAvailable: Boolean = false, + val isMe: Boolean = false, + val canCrossSign: Boolean = false ) : MvRxState class VerificationChooseMethodViewModel @AssistedInject constructor( @@ -61,6 +63,10 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( } } + override fun verificationRequestCreated(pr: PendingVerificationRequest) { + verificationRequestUpdated(pr) + } + override fun verificationRequestUpdated(pr: PendingVerificationRequest) = withState { state -> val pvr = session.cryptoService().verificationService().getExistingVerificationRequest(state.otherUserId, state.transactionId) @@ -103,6 +109,8 @@ class VerificationChooseMethodViewModel @AssistedInject constructor( val qrCodeVerificationTransaction = verificationService.getExistingTransaction(args.otherUserId, args.verificationId ?: "") return VerificationChooseMethodViewState(otherUserId = args.otherUserId, + isMe = session.myUserId == pvr?.otherUserId, + canCrossSign = session.cryptoService().crossSigningService().canCrossSign(), transactionId = args.verificationId ?: "", otherCanShowQrCode = pvr?.otherCanShowQrCode().orFalse(), otherCanScanQrCode = pvr?.otherCanScanQrCode().orFalse(), diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt index 05ed2f1799..9eb464ab06 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestController.kt @@ -84,11 +84,17 @@ class VerificationRequestController @Inject constructor( listener { listener?.onClickDismiss() } } } else { - val styledText = matrixItem.let { - stringProvider.getString(R.string.verification_request_notice, it.id) - .toSpannable() - .colorizeMatchingText(it.id, colorProvider.getColorFromAttribute(R.attr.vctr_notice_text_color)) - } + val styledText = + if (state.isMe) { + stringProvider.getString(R.string.verify_new_session_notice) + } else { + matrixItem.let { + stringProvider.getString(R.string.verification_request_notice, it.id) + .toSpannable() + .colorizeMatchingText(it.id, colorProvider.getColorFromAttribute(R.attr.vctr_notice_text_color)) + } + } + bottomSheetVerificationNoticeItem { id("notice") @@ -119,18 +125,43 @@ class VerificationRequestController @Inject constructor( } is Success -> { if (!pr.invoke().isReady) { - bottomSheetVerificationWaitingItem { - id("waiting") - title(stringProvider.getString(R.string.verification_request_waiting_for, matrixItem.getBestName())) + if (state.isMe) { + bottomSheetVerificationWaitingItem { + id("waiting") + title(stringProvider.getString(R.string.verification_request_waiting)) + } + } else { + bottomSheetVerificationWaitingItem { + id("waiting") + title(stringProvider.getString(R.string.verification_request_waiting_for, matrixItem.getBestName())) + } } } } } } + + if (state.isMe && state.currentDeviceCanCrossSign) { + + dividerItem { + id("sep_notMe") + } + + bottomSheetVerificationActionItem { + id("wasnote") + title(stringProvider.getString(R.string.verify_new_session_was_not_me)) + titleColor(colorProvider.getColor(R.color.riotx_destructive_accent)) + subTitle(stringProvider.getString(R.string.verify_new_session_compromized)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)) + listener { listener?.onClickOnWasNotMe() } + } + } } interface Listener { fun onClickOnVerificationStart() + fun onClickOnWasNotMe() fun onClickRecoverFromPassphrase() fun onClickDismiss() } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt index 64000d07a1..b6c3659988 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/request/VerificationRequestFragment.kt @@ -69,4 +69,8 @@ class VerificationRequestFragment @Inject constructor( override fun onClickDismiss() { viewModel.handle(VerificationAction.SkipVerification) } + + override fun onClickOnWasNotMe() { + viewModel.itWasNotMe() + } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt index 85f14e99a8..40b92923ec 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt @@ -1,3 +1,4 @@ + /* * Copyright 2019 New Vector Ltd * @@ -19,8 +20,10 @@ package im.vector.riotx.features.home import android.os.Bundle import android.view.LayoutInflater import android.view.View +import androidx.core.content.ContextCompat import androidx.core.view.forEachIndexed import androidx.lifecycle.Observer +import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.bottomnavigation.BottomNavigationItemView @@ -32,11 +35,13 @@ import im.vector.riotx.R import im.vector.riotx.core.extensions.commitTransactionNow import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.core.platform.ToolbarConfigurable +import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.ui.views.KeysBackupBanner import im.vector.riotx.features.home.room.list.RoomListFragment import im.vector.riotx.features.home.room.list.RoomListParams import im.vector.riotx.features.home.room.list.UnreadCounterBadgeView +import im.vector.riotx.features.popup.PopupAlertManager import im.vector.riotx.features.workers.signout.SignOutViewModel import kotlinx.android.synthetic.main.fragment_home_detail.* import timber.log.Timber @@ -54,6 +59,8 @@ class HomeDetailFragment @Inject constructor( private val unreadCounterBadgeViews = arrayListOf() private val viewModel: HomeDetailViewModel by fragmentViewModel() + private val unknownDeviceDetectorSharedViewModel : UnknownDeviceDetectorSharedViewModel by activityViewModel() + private lateinit var sharedActionViewModel: HomeSharedActionViewModel override fun getLayoutResId() = R.layout.fragment_home_detail @@ -77,6 +84,36 @@ class HomeDetailFragment @Inject constructor( viewModel.selectSubscribe(this, HomeDetailViewState::displayMode) { displayMode -> switchDisplayMode(displayMode) } + + unknownDeviceDetectorSharedViewModel.subscribe { + it.unknownSessions.invoke()?.let { unknownDevices -> + Timber.v("## Detector - ${unknownDevices.size} Unknown sessions") + unknownDevices.forEachIndexed { index, deviceInfo -> + Timber.v("## Detector - #${index} deviceId:${deviceInfo.deviceId} lastSeenTs:${deviceInfo.lastSeenTs}") + } + if (it.canCrossSign && unknownDevices.isNotEmpty()) { + val newest = unknownDevices.first() + val uid = "ND_${newest.deviceId}" + PopupAlertManager.cancelAlert(uid) + PopupAlertManager.postVectorAlert( + PopupAlertManager.VectorAlert( + uid = uid, + title = getString(R.string.new_session), + description = getString(R.string.new_session_review), + iconId = R.drawable.ic_shield_warning + ).apply { + colorInt = ContextCompat.getColor(requireActivity(), R.color.riotx_accent) + contentAction = Runnable { + (weakCurrentActivity?.get() as? VectorBaseActivity) + ?.navigator + ?.requestSessionVerification(requireContext(), newest.deviceId ?: "") + } + dismissedAction = Runnable {} + } + ) + } + } + } } private fun onGroupChange(groupSummary: GroupSummary?) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeSharedActionViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeSharedActionViewModel.kt index ecbe460b90..7aa9cfa0a1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeSharedActionViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeSharedActionViewModel.kt @@ -16,7 +16,15 @@ package im.vector.riotx.features.home +import androidx.lifecycle.MutableLiveData +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.util.NoOpCancellable +import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo +import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse +import im.vector.matrix.rx.rx +import im.vector.matrix.rx.singleBuilder import im.vector.riotx.core.platform.VectorSharedActionViewModel +import io.reactivex.android.schedulers.AndroidSchedulers import javax.inject.Inject class HomeSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel() { diff --git a/vector/src/main/java/im/vector/riotx/features/home/UnknwonDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/UnknwonDeviceDetectorSharedViewModel.kt new file mode 100644 index 0000000000..addcf33684 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/home/UnknwonDeviceDetectorSharedViewModel.kt @@ -0,0 +1,75 @@ +/* + * 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.home + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Uninitialized +import com.airbnb.mvrx.ViewModelContext +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.util.NoOpCancellable +import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo +import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse +import im.vector.matrix.rx.rx +import im.vector.matrix.rx.singleBuilder +import im.vector.riotx.core.di.HasScreenInjector +import im.vector.riotx.core.platform.EmptyAction +import im.vector.riotx.core.platform.EmptyViewEvents +import im.vector.riotx.core.platform.VectorViewModel +import io.reactivex.android.schedulers.AndroidSchedulers + +data class UnknownDevicesState( + val unknownSessions: Async?> = Uninitialized, + val canCrossSign: Boolean = false +) : MvRxState + +class UnknownDeviceDetectorSharedViewModel(session: Session, initialState: UnknownDevicesState) : VectorViewModel(initialState) { + + init { + session.rx().liveUserCryptoDevices(session.myUserId) + .observeOn(AndroidSchedulers.mainThread()) + .switchMap { deviceList -> + singleBuilder { + session.cryptoService().getDevicesList(it) + NoOpCancellable + }.map { resp -> + resp.devices?.filter { info -> + deviceList.firstOrNull { info.deviceId == it.deviceId }?.isVerified?.not() ?: false + }?.sortedByDescending { it.lastSeenTs } + } + .toObservable() + } + .execute { async -> + copy(unknownSessions = async) + } + + session.rx().liveCrossSigningInfo(session.myUserId) + .execute { + copy(canCrossSign = session.cryptoService().crossSigningService().canCrossSign()) + } + } + + override fun handle(action: EmptyAction) {} + + companion object : MvRxViewModelFactory { + override fun create(viewModelContext: ViewModelContext, state: UnknownDevicesState): UnknownDeviceDetectorSharedViewModel? { + val session = (viewModelContext.activity as HasScreenInjector).injector().activeSessionHolder().getActiveSession() + return UnknownDeviceDetectorSharedViewModel(session, state) + } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt index a080cabf1b..2e91090ec4 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt @@ -83,12 +83,12 @@ class DefaultNavigator @Inject constructor( } } - override fun requestSessionVerification(context: Context) { + override fun requestSessionVerification(context: Context, otherSessionId: String) { val session = sessionHolder.getSafeActiveSession() ?: return val pr = session.cryptoService().verificationService().requestKeyVerification( supportedVerificationMethodsProvider.provide(), session.myUserId, - session.cryptoService().getUserDevices(session.myUserId).map { it.deviceId } + listOf(otherSessionId) ) if (context is VectorBaseActivity) { VerificationBottomSheet.withArgs( diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt index fcb3d7bb44..65ef08dd05 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt @@ -30,7 +30,7 @@ interface Navigator { fun performDeviceVerification(context: Context, otherUserId: String, sasTransactionId: String) - fun requestSessionVerification(context: Context) + fun requestSessionVerification(context: Context, otherSessionId: String) fun waitSessionVerification(context: Context) diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt index 909d40a74c..5db14fdbd2 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsActivity.kt @@ -57,6 +57,8 @@ class VectorSettingsActivity : VectorBaseActivity(), when (intent.getIntExtra(EXTRA_DIRECT_ACCESS, EXTRA_DIRECT_ACCESS_ROOT)) { EXTRA_DIRECT_ACCESS_ADVANCED_SETTINGS -> replaceFragment(R.id.vector_settings_page, VectorSettingsAdvancedSettingsFragment::class.java, null, FRAGMENT_TAG) + EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY -> + replaceFragment(R.id.vector_settings_page, VectorSettingsSecurityPrivacyFragment::class.java, null, FRAGMENT_TAG) else -> replaceFragment(R.id.vector_settings_page, VectorSettingsRootFragment::class.java, null, FRAGMENT_TAG) } @@ -116,6 +118,7 @@ class VectorSettingsActivity : VectorBaseActivity(), const val EXTRA_DIRECT_ACCESS_ROOT = 0 const val EXTRA_DIRECT_ACCESS_ADVANCED_SETTINGS = 1 + const val EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY = 2 private const val FRAGMENT_TAG = "VectorSettingsPreferencesFragment" } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningEpoxyController.kt index cf74e83b1f..e33b12d19a 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/crosssigning/CrossSigningEpoxyController.kt @@ -78,6 +78,17 @@ class CrossSigningEpoxyController @Inject constructor( interactionListener?.onResetCrossSigningKeys() } } + + bottomSheetVerificationActionItem { + id("verify") + title(stringProvider.getString(R.string.complete_security)) + titleColor(colorProvider.getColor(R.color.riotx_positive_accent)) + iconRes(R.drawable.ic_arrow_right) + iconColor(colorProvider.getColor(R.color.riotx_positive_accent)) + listener { + interactionListener?.verifySession() + } + } } } else if (data.xSigningIsEnableInAccount) { genericItem { diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 45fc3a3781..c18932da1b 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -14,6 +14,20 @@ Refresh + + New Session + Tap to review & verify + Use this session to verify your new one, granting it access to encrypted messages. + This wasn’t me + Your account may be compromised + + If you cancel, you won’t be able to read encrypted messages on this device, and other users won’t trust it + If you cancel, you won’t be able to read encrypted messages on your new device, and other users won’t trust it + + + One of the following may be compromised:\n\n- Your password\n- Your homeserver\n- This device, or the other device\n- The internet connection either device is using\n\nWe recommend you change your password & recovery key in Settings immediately. + + From 6cc8d1b20584407c08f030d51c6605a3440e7f12 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 23 Mar 2020 18:43:52 +0100 Subject: [PATCH 042/156] Fix / concurrent start broke QR verification --- .../DefaultVerificationService.kt | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt index 86289301e4..846fc80930 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt @@ -456,7 +456,7 @@ internal class DefaultVerificationService @Inject constructor( private suspend fun handleStart(otherUserId: String?, startReq: ValidVerificationInfoStart, txConfigure: (DefaultVerificationTransaction) -> Unit): CancelCode? { - Timber.d("## SAS onStartRequestReceived ${startReq.transactionId}") + Timber.d("## SAS onStartRequestReceived ${startReq}") if (otherUserId?.let { checkKeysAreDownloaded(it, startReq.fromDevice) } != null) { val tid = startReq.transactionId var existing = getExistingTransaction(otherUserId, tid) @@ -468,15 +468,17 @@ internal class DefaultVerificationService @Inject constructor( // smallest is used, and the other m.key.verification.start event is ignored. // In the case of a single user verifying two of their devices, the device ID is // compared instead . - if (existing != null && !existing.isIncoming) { + if (existing is DefaultOutgoingSASDefaultVerificationTransaction) { val readyRequest = getExistingVerificationRequest(otherUserId, tid) if (readyRequest?.isReady == true) { if (isOtherPrioritary(otherUserId, existing.otherDeviceId ?: "")) { + Timber.d("## SAS concurrent start isOtherPrioritary, clear") // The other is prioritary! // I should replace my outgoing with an incoming removeTransaction(otherUserId, tid) existing = null } else { + Timber.d("## SAS concurrent start i am prioritary, ignore") // i am prioritary, ignore this start event! return null } @@ -547,7 +549,7 @@ internal class DefaultVerificationService @Inject constructor( existing.onStartReceived(startReq) return null } else { - Timber.w("## SAS onStartRequestReceived - unexpected message ${startReq.transactionId}") + Timber.w("## SAS onStartRequestReceived - unexpected message ${startReq.transactionId} / ${existing}") return CancelCode.UnexpectedMessage } } @@ -838,18 +840,18 @@ internal class DefaultVerificationService @Inject constructor( if (readyReq.methods.contains(VERIFICATION_METHOD_RECIPROCATE)) { // Create the pending transaction val tx = DefaultQrCodeVerificationTransaction( - setDeviceVerificationAction, - readyReq.transactionId, - senderId, - readyReq.fromDevice, - crossSigningService, - outgoingGossipingRequestManager, - incomingGossipingRequestManager, - cryptoStore, - qrCodeData, - userId, - deviceId ?: "", - false) + setDeviceVerificationAction = setDeviceVerificationAction, + transactionId = readyReq.transactionId, + otherUserId = senderId, + otherDeviceId = readyReq.fromDevice, + crossSigningService = crossSigningService, + outgoingGossipingRequestManager = outgoingGossipingRequestManager, + incomingGossipingRequestManager = incomingGossipingRequestManager, + cryptoStore = cryptoStore, + qrCodeData = qrCodeData, + userId = userId, + deviceId = deviceId ?: "", + isIncoming = false) tx.transport = transportCreator.invoke(tx) @@ -1006,13 +1008,11 @@ internal class DefaultVerificationService @Inject constructor( } private fun addTransaction(tx: DefaultVerificationTransaction) { - tx.otherUserId.let { otherUserId -> - synchronized(txMap) { - val txInnerMap = txMap.getOrPut(otherUserId) { HashMap() } - txInnerMap[tx.transactionId] = tx - dispatchTxAdded(tx) - tx.addListener(this) - } + synchronized(txMap) { + val txInnerMap = txMap.getOrPut(tx.otherUserId) { HashMap() } + txInnerMap[tx.transactionId] = tx + dispatchTxAdded(tx) + tx.addListener(this) } } @@ -1336,18 +1336,18 @@ internal class DefaultVerificationService @Inject constructor( if (VERIFICATION_METHOD_RECIPROCATE in result) { // Create the pending transaction val tx = DefaultQrCodeVerificationTransaction( - setDeviceVerificationAction, - transactionId, - otherUserId, - otherDeviceId, - crossSigningService, - outgoingGossipingRequestManager, - incomingGossipingRequestManager, - cryptoStore, - qrCodeData, - userId, - deviceId ?: "", - false) + setDeviceVerificationAction = setDeviceVerificationAction, + transactionId = transactionId, + otherUserId = otherUserId, + otherDeviceId = otherDeviceId, + crossSigningService = crossSigningService, + outgoingGossipingRequestManager = outgoingGossipingRequestManager, + incomingGossipingRequestManager = incomingGossipingRequestManager, + cryptoStore = cryptoStore, + qrCodeData = qrCodeData, + userId = userId, + deviceId = deviceId ?: "", + isIncoming = false) tx.transport = transportCreator.invoke(tx) From 3b62402cfea651be44a9f5ff1e7cc84c0bc16a3f Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 23 Mar 2020 19:15:29 +0100 Subject: [PATCH 043/156] Fix / ensure keys trust is updated before checking devices --- .../DefaultCrossSigningService.kt | 51 +++++++++++++------ 1 file changed, 35 insertions(+), 16 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 409c1732a4..40c2340ce6 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 @@ -770,6 +770,27 @@ internal class DefaultCrossSigningService @Inject constructor( setUserKeysAsTrusted(otherUserId, it.isVerified()) } +// // TODO if my keys have changes, i should recheck all devices of all users? +// val devices = cryptoStore.getUserDeviceList(otherUserId) +// devices?.forEach { device -> +// val updatedTrust = checkDeviceTrust(otherUserId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false) +// Timber.v("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust") +// cryptoStore.setDeviceTrust(otherUserId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified()) +// } +// +// if (otherUserId == userId) { +// // It's me, i should check if a newly trusted device is signing my master key +// // 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)) + } + + // now check device trust + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + userIds.forEach { otherUserId -> // TODO if my keys have changes, i should recheck all devices of all users? val devices = cryptoStore.getUserDeviceList(otherUserId) devices?.forEach { device -> @@ -790,24 +811,22 @@ internal class DefaultCrossSigningService @Inject constructor( } private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) { - cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() - cryptoStore.setUserKeysAsTrusted(otherUserId, trusted) - // If it's me, recheck trust of all users and devices? - val users = ArrayList() - if (otherUserId == userId && currentTrust != trusted) { + val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() + cryptoStore.setUserKeysAsTrusted(otherUserId, trusted) + // If it's me, recheck trust of all users and devices? + val users = ArrayList() + if (otherUserId == userId && currentTrust != trusted) { // reRequestAllPendingRoomKeyRequest() - cryptoStore.updateUsersTrust { - users.add(it) - checkUserTrust(it).isVerified() - } + cryptoStore.updateUsersTrust { + users.add(it) + checkUserTrust(it).isVerified() + } - users.forEach { - cryptoStore.getUserDeviceList(it)?.forEach { device -> - val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false) - Timber.v("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust") - cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified()) - } + users.forEach { + cryptoStore.getUserDeviceList(it)?.forEach { device -> + val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false) + Timber.v("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust") + cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified()) } } } From 2651f82337a8110d6f241a0eee0f377299ff456b Mon Sep 17 00:00:00 2001 From: onurays Date: Tue, 24 Mar 2020 11:03:41 +0300 Subject: [PATCH 044/156] Refactor duplicated code. --- .gitignore | 1 - .../session/content/ThumbnailExtractor.kt | 8 ++-- .../multipicker/ExampleInstrumentedTest.kt | 39 ------------------- .../vector/riotx/multipicker/AudioPicker.kt | 22 +---------- .../im/vector/riotx/multipicker/FilePicker.kt | 22 +---------- .../vector/riotx/multipicker/ImagePicker.kt | 22 +---------- .../im/vector/riotx/multipicker/Picker.kt | 23 +++++++++++ .../vector/riotx/multipicker/VideoPicker.kt | 22 +---------- .../riotx/multipicker/ExampleUnitTest.kt | 32 --------------- .../preview/AttachmentsPreviewFragment.kt | 1 - 10 files changed, 32 insertions(+), 160 deletions(-) delete mode 100644 multipicker/src/androidTest/java/im/vector/riotx/multipicker/ExampleInstrumentedTest.kt delete mode 100644 multipicker/src/test/java/im/vector/riotx/multipicker/ExampleUnitTest.kt diff --git a/.gitignore b/.gitignore index ab97ec340a..4a264a28d8 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,3 @@ /tmp ktlint - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt index 2ce249ab80..ad23ff8d78 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/content/ThumbnailExtractor.kt @@ -44,6 +44,7 @@ internal object ThumbnailExtractor { } private fun extractVideoThumbnail(context: Context, attachment: ContentAttachmentData): ThumbnailData? { + var thumbnailData: ThumbnailData? = null val mediaMetadataRetriever = MediaMetadataRetriever() try { mediaMetadataRetriever.setDataSource(context, attachment.queryUri) @@ -64,7 +65,7 @@ internal object ThumbnailExtractor { val thumbnailWidth = thumbnail.width val thumbnailHeight = thumbnail.height val thumbnailSize = outputStream.size() - val thumbnailData = ThumbnailData( + thumbnailData = ThumbnailData( width = thumbnailWidth, height = thumbnailHeight, size = thumbnailSize.toLong(), @@ -73,10 +74,11 @@ internal object ThumbnailExtractor { ) thumbnail.recycle() outputStream.reset() - return thumbnailData } catch (e: Exception) { Timber.e(e, "Cannot extract video thumbnail") - return null + } finally { + mediaMetadataRetriever.release() } + return thumbnailData } } diff --git a/multipicker/src/androidTest/java/im/vector/riotx/multipicker/ExampleInstrumentedTest.kt b/multipicker/src/androidTest/java/im/vector/riotx/multipicker/ExampleInstrumentedTest.kt deleted file mode 100644 index 25bf17559f..0000000000 --- a/multipicker/src/androidTest/java/im/vector/riotx/multipicker/ExampleInstrumentedTest.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.riotx.multipicker - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 -import junit.framework.Assert.assertEquals - -import org.junit.Test -import org.junit.runner.RunWith - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("im.vector.riotx.multipicker.test", appContext.packageName) - } -} diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt index 23873aae1c..0f45f90d2b 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt @@ -42,27 +42,7 @@ class AudioPicker(override val requestCode: Int) : Picker( val audioList = mutableListOf() - val selectedUriList = mutableListOf() - val dataUri = data?.data - val clipData = data?.clipData - - if (clipData != null) { - for (i in 0 until clipData.itemCount) { - selectedUriList.add(clipData.getItemAt(i).uri) - } - } else if (dataUri != null) { - selectedUriList.add(dataUri) - } else { - data?.extras?.get(Intent.EXTRA_STREAM)?.let { - @Suppress("UNCHECKED_CAST") - when (it) { - is List<*> -> selectedUriList.addAll(it as List) - else -> selectedUriList.add(it as Uri) - } - } - } - - selectedUriList.forEach { selectedUri -> + getSelectedUriList(data).forEach { selectedUri -> val projection = arrayOf( MediaStore.Audio.Media.DISPLAY_NAME, MediaStore.Audio.Media.SIZE diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt index 0e1169755e..41b20341d0 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt @@ -41,27 +41,7 @@ class FilePicker(override val requestCode: Int) : Picker(re val fileList = mutableListOf() - val selectedUriList = mutableListOf() - val dataUri = data?.data - val clipData = data?.clipData - - if (clipData != null) { - for (i in 0 until clipData.itemCount) { - selectedUriList.add(clipData.getItemAt(i).uri) - } - } else if (dataUri != null) { - selectedUriList.add(dataUri) - } else { - data?.extras?.get(Intent.EXTRA_STREAM)?.let { - @Suppress("UNCHECKED_CAST") - when (it) { - is List<*> -> selectedUriList.addAll(it as List) - else -> selectedUriList.add(it as Uri) - } - } - } - - selectedUriList.forEach { selectedUri -> + getSelectedUriList(data).forEach { selectedUri -> context.contentResolver.query(selectedUri, null, null, null, null) ?.use { cursor -> val nameColumn = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt index 8bf589800d..bd27244ed5 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt @@ -45,27 +45,7 @@ class ImagePicker(override val requestCode: Int) : Picker( val imageList = mutableListOf() - val selectedUriList = mutableListOf() - val dataUri = data?.data - val clipData = data?.clipData - - if (clipData != null) { - for (i in 0 until clipData.itemCount) { - selectedUriList.add(clipData.getItemAt(i).uri) - } - } else if (dataUri != null) { - selectedUriList.add(dataUri) - } else { - data?.extras?.get(Intent.EXTRA_STREAM)?.let { - @Suppress("UNCHECKED_CAST") - when (it) { - is List<*> -> selectedUriList.addAll(it as List) - else -> selectedUriList.add(it as Uri) - } - } - } - - selectedUriList.forEach { selectedUri -> + getSelectedUriList(data).forEach { selectedUri -> val projection = arrayOf( MediaStore.Images.Media.DISPLAY_NAME, MediaStore.Images.Media.SIZE diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt index f162dd7608..58754cd74e 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt @@ -44,4 +44,27 @@ abstract class Picker(open val requestCode: Int) { single = true return this } + + protected fun getSelectedUriList(data: Intent?): List { + val selectedUriList = mutableListOf() + val dataUri = data?.data + val clipData = data?.clipData + + if (clipData != null) { + for (i in 0 until clipData.itemCount) { + selectedUriList.add(clipData.getItemAt(i).uri) + } + } else if (dataUri != null) { + selectedUriList.add(dataUri) + } else { + data?.extras?.get(Intent.EXTRA_STREAM)?.let { + @Suppress("UNCHECKED_CAST") + when (it) { + is List<*> -> selectedUriList.addAll(it as List) + else -> selectedUriList.add(it as Uri) + } + } + } + return selectedUriList + } } diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt index d4b8d6a985..739c24c6c7 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt @@ -42,27 +42,7 @@ class VideoPicker(override val requestCode: Int) : Picker( val videoList = mutableListOf() - val selectedUriList = mutableListOf() - val dataUri = data?.data - val clipData = data?.clipData - - if (clipData != null) { - for (i in 0 until clipData.itemCount) { - selectedUriList.add(clipData.getItemAt(i).uri) - } - } else if (dataUri != null) { - selectedUriList.add(dataUri) - } else { - data?.extras?.get(Intent.EXTRA_STREAM)?.let { - @Suppress("UNCHECKED_CAST") - when (it) { - is List<*> -> selectedUriList.addAll(it as List) - else -> selectedUriList.add(it as Uri) - } - } - } - - selectedUriList.forEach { selectedUri -> + getSelectedUriList(data).forEach { selectedUri -> val projection = arrayOf( MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.SIZE diff --git a/multipicker/src/test/java/im/vector/riotx/multipicker/ExampleUnitTest.kt b/multipicker/src/test/java/im/vector/riotx/multipicker/ExampleUnitTest.kt deleted file mode 100644 index 07e464699f..0000000000 --- a/multipicker/src/test/java/im/vector/riotx/multipicker/ExampleUnitTest.kt +++ /dev/null @@ -1,32 +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.multipicker - -import junit.framework.Assert.assertEquals -import org.junit.Test - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt index f059da7d85..3b1972ffbc 100644 --- a/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/attachments/preview/AttachmentsPreviewFragment.kt @@ -202,7 +202,6 @@ class AttachmentsPreviewFragment @Inject constructor( private fun doHandleEditAction() = withState(viewModel) { val currentAttachment = it.attachments.getOrNull(it.currentAttachmentIndex) ?: return@withState val destinationFile = File(requireContext().cacheDir, "${currentAttachment.name}_edited_image_${System.currentTimeMillis()}") - // Note: using currentAttachment.queryUri.toUri() make the app crash when sharing from Google Photos val uri = currentAttachment.queryUri UCrop.of(uri, destinationFile.toUri()) .withOptions( From 727d86236bf966f4f67a0b5398bd5188041c0a7b Mon Sep 17 00:00:00 2001 From: onurays Date: Tue, 24 Mar 2020 11:31:27 +0300 Subject: [PATCH 045/156] ImageUtils created with helper functions. --- multipicker/src/main/AndroidManifest.xml | 2 +- .../vector/riotx/multipicker/AudioPicker.kt | 10 +--- .../vector/riotx/multipicker/CameraPicker.kt | 22 ++------ .../vector/riotx/multipicker/ContactPicker.kt | 10 +--- .../im/vector/riotx/multipicker/FilePicker.kt | 12 +---- .../vector/riotx/multipicker/ImagePicker.kt | 32 ++---------- .../im/vector/riotx/multipicker/Picker.kt | 14 +++-- .../vector/riotx/multipicker/VideoPicker.kt | 10 +--- .../{ => provider}/MultiPickerFileProvider.kt | 2 +- .../riotx/multipicker/utils/ImageUtils.kt | 52 +++++++++++++++++++ 10 files changed, 75 insertions(+), 91 deletions(-) rename multipicker/src/main/java/im/vector/riotx/multipicker/{ => provider}/MultiPickerFileProvider.kt (93%) create mode 100644 multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt diff --git a/multipicker/src/main/AndroidManifest.xml b/multipicker/src/main/AndroidManifest.xml index 6f714ab388..e1f12697e0 100644 --- a/multipicker/src/main/AndroidManifest.xml +++ b/multipicker/src/main/AndroidManifest.xml @@ -3,7 +3,7 @@ diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt index 0f45f90d2b..752cd84e1e 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt @@ -27,14 +27,6 @@ import im.vector.riotx.multipicker.entity.MultiPickerAudioType class AudioPicker(override val requestCode: Int) : Picker(requestCode) { - override fun startWith(activity: Activity) { - activity.startActivityForResult(createIntent(), requestCode) - } - - override fun startWith(fragment: Fragment) { - fragment.startActivityForResult(createIntent(), requestCode) - } - override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() @@ -84,7 +76,7 @@ class AudioPicker(override val requestCode: Int) : Picker( return audioList } - private fun createIntent(): Intent { + override fun createIntent(): Intent { return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt index 81e665a43f..d8c16279cc 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt @@ -28,6 +28,7 @@ import androidx.core.content.FileProvider import androidx.exifinterface.media.ExifInterface import androidx.fragment.app.Fragment import im.vector.riotx.multipicker.entity.MultiPickerImageType +import im.vector.riotx.multipicker.utils.ImageUtils import java.io.File import java.text.SimpleDateFormat import java.util.Date @@ -74,25 +75,8 @@ class CameraPicker(val requestCode: Int) { val name = cursor.getString(nameColumn) val size = cursor.getLong(sizeColumn) - var orientation = 0 - - val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, photoUri)) - } else { - context.contentResolver.openInputStream(photoUri)?.use { inputStream -> - BitmapFactory.decodeStream(inputStream) - } - } - - context.contentResolver.openInputStream(photoUri)?.use { inputStream -> - try { - ExifInterface(inputStream).let { - orientation = it.rotationDegrees - } - } catch (e: Exception) { - e.printStackTrace() - } - } + val bitmap = ImageUtils.getBitmap(context, photoUri) + val orientation = ImageUtils.getOrientation(context, photoUri) return MultiPickerImageType( name, diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt index aebde6f439..968c5623e5 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt @@ -26,14 +26,6 @@ import im.vector.riotx.multipicker.entity.MultiPickerContactType class ContactPicker(override val requestCode: Int) : Picker(requestCode) { - override fun startWith(activity: Activity) { - activity.startActivityForResult(createIntent(), requestCode) - } - - override fun startWith(fragment: Fragment) { - fragment.startActivityForResult(createIntent(), requestCode) - } - override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() @@ -126,7 +118,7 @@ class ContactPicker(override val requestCode: Int) : Picker(requestCode) { - override fun startWith(activity: Activity) { - activity.startActivityForResult(createIntent(), requestCode) - } - - override fun startWith(fragment: Fragment) { - fragment.startActivityForResult(createIntent(), requestCode) - } - override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() @@ -64,7 +54,7 @@ class FilePicker(override val requestCode: Int) : Picker(re return fileList } - private fun createIntent(): Intent { + override fun createIntent(): Intent { return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt index bd27244ed5..452724f4c7 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt @@ -27,17 +27,10 @@ import android.provider.MediaStore import androidx.exifinterface.media.ExifInterface import androidx.fragment.app.Fragment import im.vector.riotx.multipicker.entity.MultiPickerImageType +import im.vector.riotx.multipicker.utils.ImageUtils class ImagePicker(override val requestCode: Int) : Picker(requestCode) { - override fun startWith(activity: Activity) { - activity.startActivityForResult(createIntent(), requestCode) - } - - override fun startWith(fragment: Fragment) { - fragment.startActivityForResult(createIntent(), requestCode) - } - override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() @@ -65,25 +58,8 @@ class ImagePicker(override val requestCode: Int) : Picker( val name = cursor.getString(nameColumn) val size = cursor.getLong(sizeColumn) - var orientation = 0 - - val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, selectedUri)) - } else { - context.contentResolver.openInputStream(selectedUri)?.use { inputStream -> - BitmapFactory.decodeStream(inputStream) - } - } - - context.contentResolver.openInputStream(selectedUri)?.use { inputStream -> - try { - ExifInterface(inputStream).let { - orientation = it.rotationDegrees - } - } catch (e: Exception) { - e.printStackTrace() - } - } + val bitmap = ImageUtils.getBitmap(context, selectedUri) + val orientation = ImageUtils.getOrientation(context, selectedUri) imageList.add( MultiPickerImageType( @@ -102,7 +78,7 @@ class ImagePicker(override val requestCode: Int) : Picker( return imageList } - private fun createIntent(): Intent { + override fun createIntent(): Intent { return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt index 58754cd74e..c1784ee054 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt @@ -26,10 +26,6 @@ abstract class Picker(open val requestCode: Int) { protected var single = false - abstract fun startWith(activity: Activity) - - abstract fun startWith(fragment: Fragment) - open fun startWithExpectingFile(activity: Activity): Uri? = null open fun startWithExpectingFile(fragment: Fragment): Uri? = null @@ -45,6 +41,16 @@ abstract class Picker(open val requestCode: Int) { return this } + abstract fun createIntent(): Intent + + fun startWith(activity: Activity) { + activity.startActivityForResult(createIntent(), requestCode) + } + + fun startWith(fragment: Fragment) { + fragment.startActivityForResult(createIntent(), requestCode) + } + protected fun getSelectedUriList(data: Intent?): List { val selectedUriList = mutableListOf() val dataUri = data?.data diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt index 739c24c6c7..c2a441c15a 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt @@ -27,14 +27,6 @@ import im.vector.riotx.multipicker.entity.MultiPickerVideoType class VideoPicker(override val requestCode: Int) : Picker(requestCode) { - override fun startWith(activity: Activity) { - activity.startActivityForResult(createIntent(), requestCode) - } - - override fun startWith(fragment: Fragment) { - fragment.startActivityForResult(createIntent(), requestCode) - } - override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() @@ -93,7 +85,7 @@ class VideoPicker(override val requestCode: Int) : Picker( return videoList } - private fun createIntent(): Intent { + override fun createIntent(): Intent { return Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) putExtra(Intent.EXTRA_ALLOW_MULTIPLE, !single) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/provider/MultiPickerFileProvider.kt similarity index 93% rename from multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt rename to multipicker/src/main/java/im/vector/riotx/multipicker/provider/MultiPickerFileProvider.kt index 1549b43fd7..048b2ca199 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/MultiPickerFileProvider.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/provider/MultiPickerFileProvider.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package im.vector.riotx.multipicker +package im.vector.riotx.multipicker.provider import androidx.core.content.FileProvider diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt new file mode 100644 index 0000000000..76f4b677a0 --- /dev/null +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/utils/ImageUtils.kt @@ -0,0 +1,52 @@ +/* + * 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.multipicker.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.ImageDecoder +import android.net.Uri +import android.os.Build +import androidx.exifinterface.media.ExifInterface + +object ImageUtils { + + fun getBitmap(context: Context, uri: Uri): Bitmap? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.contentResolver, uri)) + } else { + context.contentResolver.openInputStream(uri)?.use { inputStream -> + BitmapFactory.decodeStream(inputStream) + } + } + } + + fun getOrientation(context: Context, uri: Uri): Int { + var orientation = 0 + context.contentResolver.openInputStream(uri)?.use { inputStream -> + try { + ExifInterface(inputStream).let { + orientation = it.rotationDegrees + } + } catch (e: Exception) { + e.printStackTrace() + } + } + return orientation + } +} From fcd290410e99c62a3b9a05891b462a6f9b22d164 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 24 Mar 2020 10:06:15 +0100 Subject: [PATCH 046/156] Also cancel pending request on back --- .../crypto/verification/VerificationService.kt | 2 ++ .../verification/DefaultVerificationService.kt | 12 ++++++++++++ .../verification/VerificationBottomSheetViewModel.kt | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationService.kt index 75033082d6..4482101434 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/verification/VerificationService.kt @@ -60,6 +60,8 @@ interface VerificationService { roomId: String, localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest + fun cancelVerificationRequest(request: PendingVerificationRequest) + /** * Request a key verification from another user using toDevice events. */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt index 846fc80930..1a7549d696 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultVerificationService.kt @@ -1102,6 +1102,18 @@ internal class DefaultVerificationService @Inject constructor( return verificationRequest } + override fun cancelVerificationRequest(request: PendingVerificationRequest) { + if (request.roomId != null) { + val transport = verificationTransportRoomMessageFactory.createTransport(request.roomId, null) + transport.cancelTransaction(request.transactionId ?: "", request.otherUserId, null, CancelCode.User) + } else { + val transport = verificationTransportToDeviceFactory.createTransport(null) + request.targetDevices?.forEach { deviceId -> + transport.cancelTransaction(request.transactionId ?: "", request.otherUserId, deviceId, CancelCode.User) + } + } + } + override fun requestKeyVerification(methods: List, otherUserId: String, otherDevices: List?): PendingVerificationRequest { // TODO refactor this with the DM one Timber.i("## Requesting verification to user: $otherUserId with device list $otherDevices") diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt index fab59fe5af..515657eb13 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -149,6 +149,10 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( } fun confirmCancel() = withState { state -> + session.cryptoService() + .verificationService().getExistingVerificationRequest(state.otherUserMxItem?.id ?: "", state.transactionId)?.let { + session.cryptoService().verificationService().cancelVerificationRequest(it) + } session.cryptoService() .verificationService() .getExistingTransaction(state.otherUserMxItem?.id ?: "", state.transactionId ?: "") From 6e85b20b0e8a4fda636ae397161ae14df3128712 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 24 Mar 2020 10:06:30 +0100 Subject: [PATCH 047/156] Update copy for riotX default device Name --- .../main/res/layout/bottom_sheet_generic_list_with_title.xml | 1 + vector/src/main/res/values/strings.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/layout/bottom_sheet_generic_list_with_title.xml b/vector/src/main/res/layout/bottom_sheet_generic_list_with_title.xml index 80d877ac2d..6a4b6d3eaa 100644 --- a/vector/src/main/res/layout/bottom_sheet_generic_list_with_title.xml +++ b/vector/src/main/res/layout/bottom_sheet_generic_list_with_title.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + android:paddingTop="8dp" android:orientation="vertical"> This is not a valid Matrix server address Cannot reach a homeserver at this URL, please check it Your device is using an outdated TLS security protocol, vulnerable to attack, for your security you will not be able to connect - Mobile + RiotX Android Invalid username/password The access token specified was not recognised From 22642e71a363088222b7b43c5053d36c871ad323 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 24 Mar 2020 10:06:36 +0100 Subject: [PATCH 048/156] cleaning --- .../crosssigning/DefaultCrossSigningService.kt | 16 ---------------- 1 file changed, 16 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 40c2340ce6..389fa1ea50 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 @@ -769,23 +769,7 @@ internal class DefaultCrossSigningService @Inject constructor( Timber.v("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}") setUserKeysAsTrusted(otherUserId, it.isVerified()) } - -// // TODO if my keys have changes, i should recheck all devices of all users? -// val devices = cryptoStore.getUserDeviceList(otherUserId) -// devices?.forEach { device -> -// val updatedTrust = checkDeviceTrust(otherUserId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false) -// Timber.v("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust") -// cryptoStore.setDeviceTrust(otherUserId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified()) -// } -// -// if (otherUserId == userId) { -// // It's me, i should check if a newly trusted device is signing my master key -// // 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)) } // now check device trust From d20b1cb64ab25edc4b4d28d15e88eef6d3bece8d Mon Sep 17 00:00:00 2001 From: onurays Date: Tue, 24 Mar 2020 12:15:14 +0300 Subject: [PATCH 049/156] Add documentation. --- .../vector/riotx/multipicker/AudioPicker.kt | 2 -- .../vector/riotx/multipicker/CameraPicker.kt | 23 ++++++++++++--- .../vector/riotx/multipicker/ContactPicker.kt | 10 ++++++- .../im/vector/riotx/multipicker/FilePicker.kt | 9 ++++++ .../vector/riotx/multipicker/ImagePicker.kt | 15 ++++++---- .../im/vector/riotx/multipicker/Picker.kt | 28 ++++++++++++++++--- .../vector/riotx/multipicker/VideoPicker.kt | 11 ++++++-- .../home/room/detail/RoomDetailFragment.kt | 1 - 8 files changed, 79 insertions(+), 20 deletions(-) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt index 752cd84e1e..a9c89a9ec0 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt @@ -20,9 +20,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.media.MediaMetadataRetriever -import android.net.Uri import android.provider.MediaStore -import androidx.fragment.app.Fragment import im.vector.riotx.multipicker.entity.MultiPickerAudioType class AudioPicker(override val requestCode: Int) : Picker(requestCode) { diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt index d8c16279cc..d7a4d55d22 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt @@ -19,13 +19,9 @@ package im.vector.riotx.multipicker import android.app.Activity import android.content.Context import android.content.Intent -import android.graphics.BitmapFactory -import android.graphics.ImageDecoder import android.net.Uri -import android.os.Build import android.provider.MediaStore import androidx.core.content.FileProvider -import androidx.exifinterface.media.ExifInterface import androidx.fragment.app.Fragment import im.vector.riotx.multipicker.entity.MultiPickerImageType import im.vector.riotx.multipicker.utils.ImageUtils @@ -34,8 +30,16 @@ import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +/** + * Implementation of taking a photo with Camera + */ class CameraPicker(val requestCode: Int) { + /** + * Start camera by using an Activity + * @param activity Activity to handle onActivityResult(). + * @return Uri of taken photo or null if the operation is cancelled. + */ fun startWithExpectingFile(activity: Activity): Uri? { val photoUri = createPhotoUri(activity) val intent = createIntent().apply { @@ -45,6 +49,11 @@ class CameraPicker(val requestCode: Int) { return photoUri } + /** + * Start camera by using a Fragment + * @param fragment Fragment to handle onActivityResult(). + * @return Uri of taken photo or null if the operation is cancelled. + */ fun startWithExpectingFile(fragment: Fragment): Uri? { val photoUri = createPhotoUri(fragment.requireContext()) val intent = createIntent().apply { @@ -54,6 +63,12 @@ class CameraPicker(val requestCode: Int) { return photoUri } + /** + * Call this function from onActivityResult(int, int, Intent). + * @return Taken photo or null if request code is wrong + * or result code is not Activity.RESULT_OK + * or user cancelled the operation. + */ fun getTakenPhoto(context: Context, requestCode: Int, resultCode: Int, photoUri: Uri): MultiPickerImageType? { if (requestCode == this.requestCode && resultCode == Activity.RESULT_OK) { val projection = arrayOf( diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt index 968c5623e5..b0ae0e4cda 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ContactPicker.kt @@ -21,11 +21,19 @@ import android.content.ContentResolver import android.content.Context import android.content.Intent import android.provider.ContactsContract -import androidx.fragment.app.Fragment import im.vector.riotx.multipicker.entity.MultiPickerContactType +/** + * Contact Picker implementation + */ class ContactPicker(override val requestCode: Int) : Picker(requestCode) { + /** + * Call this function from onActivityResult(int, int, Intent). + * Returns selected contact or empty list if request code is wrong + * or result code is not Activity.RESULT_OK + * or user did not select any files. + */ override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt index 20f62faf24..e8c74fad19 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/FilePicker.kt @@ -22,8 +22,17 @@ import android.content.Intent import android.provider.OpenableColumns import im.vector.riotx.multipicker.entity.MultiPickerFileType +/** + * Implementation of selecting any type of files + */ class FilePicker(override val requestCode: Int) : Picker(requestCode) { + /** + * Call this function from onActivityResult(int, int, Intent). + * Returns selected files or empty list if request code is wrong + * or result code is not Activity.RESULT_OK + * or user did not select any files. + */ override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt index 452724f4c7..d7bf383f03 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/ImagePicker.kt @@ -19,18 +19,21 @@ package im.vector.riotx.multipicker import android.app.Activity import android.content.Context import android.content.Intent -import android.graphics.BitmapFactory -import android.graphics.ImageDecoder -import android.net.Uri -import android.os.Build import android.provider.MediaStore -import androidx.exifinterface.media.ExifInterface -import androidx.fragment.app.Fragment import im.vector.riotx.multipicker.entity.MultiPickerImageType import im.vector.riotx.multipicker.utils.ImageUtils +/** + * Image Picker implementation + */ class ImagePicker(override val requestCode: Int) : Picker(requestCode) { + /** + * Call this function from onActivityResult(int, int, Intent). + * Returns selected image files or empty list if request code is wrong + * or result code is not Activity.RESULT_OK + * or user did not select any files. + */ override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt index c1784ee054..ff20c1303b 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/Picker.kt @@ -22,20 +22,32 @@ import android.content.Intent import android.net.Uri import androidx.fragment.app.Fragment +/** + * Abstract class to provide all types of Pickers + */ abstract class Picker(open val requestCode: Int) { protected var single = false - open fun startWithExpectingFile(activity: Activity): Uri? = null - - open fun startWithExpectingFile(fragment: Fragment): Uri? = null - + /** + * Call this function from onActivityResult(int, int, Intent). + * @return selected files or empty list if request code is wrong + * or result code is not Activity.RESULT_OK + * or user did not select any files. + */ abstract fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List + /** + * Use this function to retrieve files which are shared from another application or internally + * by using android.intent.action.SEND or android.intent.action.SEND_MULTIPLE actions. + */ fun getIncomingFiles(context: Context, data: Intent?): List { return getSelectedFiles(context, requestCode, Activity.RESULT_OK, data) } + /** + * Call this function to disable multiple selection of files. + */ fun single(): Picker { single = true return this @@ -43,10 +55,18 @@ abstract class Picker(open val requestCode: Int) { abstract fun createIntent(): Intent + /** + * Start Storage Access Framework UI by using an Activity. + * @param activity Activity to handle onActivityResult(). + */ fun startWith(activity: Activity) { activity.startActivityForResult(createIntent(), requestCode) } + /** + * Start Storage Access Framework UI by using a Fragment. + * @param fragment Fragment to handle onActivityResult(). + */ fun startWith(fragment: Fragment) { fragment.startActivityForResult(createIntent(), requestCode) } diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt index c2a441c15a..b85ffacd48 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/VideoPicker.kt @@ -20,13 +20,20 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.media.MediaMetadataRetriever -import android.net.Uri import android.provider.MediaStore -import androidx.fragment.app.Fragment import im.vector.riotx.multipicker.entity.MultiPickerVideoType +/** + * Video Picker implementation + */ class VideoPicker(override val requestCode: Int) : Picker(requestCode) { + /** + * Call this function from onActivityResult(int, int, Intent). + * Returns selected video files or empty list if request code is wrong + * or result code is not Activity.RESULT_OK + * or user did not select any files. + */ override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() 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 779b7fb089..f58d7be718 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 @@ -156,7 +156,6 @@ import im.vector.riotx.features.reactions.EmojiReactionPickerActivity import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.share.SharedData import im.vector.riotx.features.themes.ThemeUtils -import im.vector.riotx.multipicker.MultiPicker import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import kotlinx.android.parcel.Parcelize From e583c037516403169931287cc08b732933883c16 Mon Sep 17 00:00:00 2001 From: onurays Date: Tue, 24 Mar 2020 12:32:37 +0300 Subject: [PATCH 050/156] Add documentation. --- .../main/java/im/vector/riotx/multipicker/AudioPicker.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt index a9c89a9ec0..05e4c337b6 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/AudioPicker.kt @@ -23,8 +23,17 @@ import android.media.MediaMetadataRetriever import android.provider.MediaStore import im.vector.riotx.multipicker.entity.MultiPickerAudioType +/** + * Audio file picker implementation + */ class AudioPicker(override val requestCode: Int) : Picker(requestCode) { + /** + * Call this function from onActivityResult(int, int, Intent). + * Returns selected audio files or empty list if request code is wrong + * or result code is not Activity.RESULT_OK + * or user did not select any files. + */ override fun getSelectedFiles(context: Context, requestCode: Int, resultCode: Int, data: Intent?): List { if (requestCode != this.requestCode && resultCode != Activity.RESULT_OK) { return emptyList() From 6bf89aeac91f9cb5ce45144a5233f2b5e063054b Mon Sep 17 00:00:00 2001 From: onurays Date: Tue, 24 Mar 2020 12:37:37 +0300 Subject: [PATCH 051/156] Remove JPEG_ prefix from file name. --- .../src/main/java/im/vector/riotx/multipicker/CameraPicker.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt index d7a4d55d22..240d809373 100644 --- a/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt +++ b/multipicker/src/main/java/im/vector/riotx/multipicker/CameraPicker.kt @@ -122,7 +122,7 @@ class CameraPicker(val requestCode: Int) { val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) val storageDir: File = context.filesDir return File.createTempFile( - "JPEG_${timeStamp}_", /* prefix */ + "${timeStamp}_", /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ) From b56a41bec7727552573a07f94bdfa969308e429b Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 24 Mar 2020 14:56:57 +0100 Subject: [PATCH 052/156] Custom alert design --- vector/build.gradle | 2 +- .../java/im/vector/riotx/VectorApplication.kt | 4 +- .../vector/riotx/core/di/VectorComponent.kt | 3 + .../crypto/keysrequest/KeyRequestHandler.kt | 18 ++-- .../IncomingVerificationRequestHandler.kt | 28 ++++-- .../riotx/features/home/HomeActivity.kt | 7 +- .../riotx/features/home/HomeDetailFragment.kt | 16 ++-- .../UnknwonDeviceDetectorSharedViewModel.kt | 8 +- .../VectorActivityLifecycleCallbacks.kt | 4 +- .../riotx/features/popup/PopupAlertManager.kt | 58 ++++------- .../riotx/features/popup/VectorAlert.kt | 95 +++++++++++++++++++ .../layout/alerter_verification_layout.xml | 92 ++++++++++++++++++ 12 files changed, 266 insertions(+), 69 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotx/features/popup/VectorAlert.kt create mode 100644 vector/src/main/res/layout/alerter_verification_layout.xml diff --git a/vector/build.gradle b/vector/build.gradle index 66ec6808c8..263c561921 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -323,7 +323,7 @@ dependencies { implementation 'com.nulab-inc:zxcvbn:1.2.7' //Alerter - implementation 'com.tapadoo.android:alerter:4.0.3' + implementation 'com.tapadoo.android:alerter:5.1.2' implementation 'com.otaliastudios:autocomplete:1.1.0' diff --git a/vector/src/main/java/im/vector/riotx/VectorApplication.kt b/vector/src/main/java/im/vector/riotx/VectorApplication.kt index 81cf1402b0..bd85596924 100644 --- a/vector/src/main/java/im/vector/riotx/VectorApplication.kt +++ b/vector/src/main/java/im/vector/riotx/VectorApplication.kt @@ -48,6 +48,7 @@ import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks import im.vector.riotx.features.notifications.NotificationDrawerManager import im.vector.riotx.features.notifications.NotificationUtils import im.vector.riotx.features.notifications.PushRuleTriggerListener +import im.vector.riotx.features.popup.PopupAlertManager import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotx.features.session.SessionListener import im.vector.riotx.features.settings.VectorPreferences @@ -77,6 +78,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration. @Inject lateinit var notificationUtils: NotificationUtils @Inject lateinit var appStateHandler: AppStateHandler @Inject lateinit var rxConfig: RxConfig + @Inject lateinit var popupAlertManager: PopupAlertManager lateinit var vectorComponent: VectorComponent private var fontThreadHandler: Handler? = null @@ -102,7 +104,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration. BigImageViewer.initialize(GlideImageLoader.with(applicationContext)) EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() - registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks()) + registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks(popupAlertManager)) val fontRequest = FontRequest( "com.google.android.gms.fonts", "com.google.android.gms", diff --git a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt index 4ae92b29b1..2652f58b04 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/VectorComponent.kt @@ -45,6 +45,7 @@ import im.vector.riotx.features.notifications.NotificationBroadcastReceiver import im.vector.riotx.features.notifications.NotificationDrawerManager import im.vector.riotx.features.notifications.NotificationUtils import im.vector.riotx.features.notifications.PushRuleTriggerListener +import im.vector.riotx.features.popup.PopupAlertManager import im.vector.riotx.features.rageshake.BugReporter import im.vector.riotx.features.rageshake.VectorFileLogger import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler @@ -128,6 +129,8 @@ interface VectorComponent { fun emojiDataSource(): EmojiDataSource + fun alertManager() : PopupAlertManager + @Component.Factory interface Factory { fun create(@BindsInstance context: Context): VectorComponent diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt index d9860e6bad..8fe128ef73 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysrequest/KeyRequestHandler.kt @@ -36,7 +36,9 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.riotx.R +import im.vector.riotx.features.popup.DefaultVectorAlert import im.vector.riotx.features.popup.PopupAlertManager +import im.vector.riotx.features.popup.VectorAlert import timber.log.Timber import java.text.DateFormat import java.text.SimpleDateFormat @@ -54,7 +56,7 @@ import javax.inject.Singleton */ @Singleton -class KeyRequestHandler @Inject constructor(private val context: Context) +class KeyRequestHandler @Inject constructor(private val context: Context, private val popupAlertManager: PopupAlertManager) : GossipingRequestListener, VerificationService.Listener { @@ -118,9 +120,9 @@ class KeyRequestHandler @Inject constructor(private val context: Context) } if (deviceInfo.isUnknown) { - session?.cryptoService()?.setDeviceVerification(DeviceTrustLevel(false, false), userId, deviceId) + session?.cryptoService()?.setDeviceVerification(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false), userId, deviceId) - deviceInfo.trustLevel = DeviceTrustLevel(false, false) + deviceInfo.trustLevel = DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false) // can we get more info on this device? session?.cryptoService()?.getDevicesList(object : MatrixCallback { @@ -188,7 +190,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context) } } - val alert = PopupAlertManager.VectorAlert( + val alert = DefaultVectorAlert( alertManagerId(userId, deviceId), context.getString(R.string.key_share_request), dialogText, @@ -210,7 +212,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context) denyAllRequests(mappingKey) }) - PopupAlertManager.postVectorAlert(alert) + popupAlertManager.postVectorAlert(alert) } private fun denyAllRequests(mappingKey: String) { @@ -250,7 +252,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context) && it.requestId == request.requestId } if (alertsToRequests[alertMgrUniqueKey]?.isEmpty() == true) { - PopupAlertManager.cancelAlert(alertMgrUniqueKey) + popupAlertManager.cancelAlert(alertMgrUniqueKey) alertsToRequests.remove(keyForMap(userId, deviceId)) } } @@ -261,7 +263,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context) if (state == VerificationTxState.Verified) { // ok it's verified, see if we have key request for that shareAllSessions("${tx.otherDeviceId}${tx.otherUserId}") - PopupAlertManager.cancelAlert("ikr_${tx.otherDeviceId}${tx.otherUserId}") + popupAlertManager.cancelAlert("ikr_${tx.otherDeviceId}${tx.otherUserId}") } } // should do it with QR tx also @@ -271,7 +273,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context) override fun markedAsManuallyVerified(userId: String, deviceId: String) { // accept related requests shareAllSessions(keyForMap(userId, deviceId)) - PopupAlertManager.cancelAlert(alertManagerId(userId, deviceId)) + popupAlertManager.cancelAlert(alertManagerId(userId, deviceId)) } private fun keyForMap(userId: String, deviceId: String) = "$deviceId$userId" diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/IncomingVerificationRequestHandler.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/IncomingVerificationRequestHandler.kt index e7e26f52a4..9f12e9eb55 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/IncomingVerificationRequestHandler.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/IncomingVerificationRequestHandler.kt @@ -17,15 +17,17 @@ package im.vector.riotx.features.crypto.verification import android.content.Context import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest import im.vector.matrix.android.api.session.crypto.verification.VerificationService import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState -import im.vector.matrix.android.api.session.crypto.verification.PendingVerificationRequest +import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.R import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.features.home.room.detail.RoomDetailActivity import im.vector.riotx.features.home.room.detail.RoomDetailArgs import im.vector.riotx.features.popup.PopupAlertManager +import im.vector.riotx.features.popup.VerificationVectorAlert import im.vector.riotx.features.themes.ThemeUtils import javax.inject.Inject import javax.inject.Singleton @@ -34,7 +36,9 @@ import javax.inject.Singleton * Listens to the VerificationManager and add a new notification when an incoming request is detected. */ @Singleton -class IncomingVerificationRequestHandler @Inject constructor(private val context: Context) : VerificationService.Listener { +class IncomingVerificationRequestHandler @Inject constructor( + private val context: Context, + private val popupAlertManager: PopupAlertManager) : VerificationService.Listener { private var session: Session? = null @@ -58,7 +62,7 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context val name = session?.getUser(tx.otherUserId)?.displayName ?: tx.otherUserId - val alert = PopupAlertManager.VectorAlert( + val alert = VerificationVectorAlert( uid, context.getString(R.string.sas_incoming_request_notif_title), context.getString(R.string.sas_incoming_request_notif_content, name), @@ -68,12 +72,15 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context // TODO a bit too hugly :/ activity.supportFragmentManager.findFragmentByTag(VerificationBottomSheet.WAITING_SELF_VERIF_TAG)?.let { false.also { - PopupAlertManager.cancelAlert(uid) + popupAlertManager.cancelAlert(uid) } } ?: true } else true }) .apply { + + matrixItem = session?.getUser(tx.otherUserId ?: "")?.toMatrixItem() + contentAction = Runnable { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let { it.navigator.performDeviceVerification(it, tx.otherUserId, tx.transactionId) @@ -99,11 +106,11 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context // 10mn expiration expirationTimestamp = System.currentTimeMillis() + (10 * 60 * 1000L) } - PopupAlertManager.postVectorAlert(alert) + popupAlertManager.postVectorAlert(alert) } is VerificationTxState.TerminalTxState -> { // cancel related notification - PopupAlertManager.cancelAlert(uid) + popupAlertManager.cancelAlert(uid) } else -> Unit } @@ -115,7 +122,7 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context val name = session?.getUser(pr.otherUserId)?.displayName ?: pr.otherUserId - val alert = PopupAlertManager.VectorAlert( + val alert = VerificationVectorAlert( uniqueIdForVerificationRequest(pr), context.getString(R.string.sas_incoming_request_notif_title), "$name(${pr.otherUserId})", @@ -128,6 +135,9 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context } else true }) .apply { + + matrixItem = session?.getUser(pr.otherUserId)?.toMatrixItem() + contentAction = Runnable { (weakCurrentActivity?.get() as? VectorBaseActivity)?.let { val roomId = pr.roomId @@ -148,14 +158,14 @@ class IncomingVerificationRequestHandler @Inject constructor(private val context // 5mn expiration expirationTimestamp = System.currentTimeMillis() + (5 * 60 * 1000L) } - PopupAlertManager.postVectorAlert(alert) + popupAlertManager.postVectorAlert(alert) } } override fun verificationRequestUpdated(pr: PendingVerificationRequest) { // If an incoming request is readied (by another device?) we should discard the alert if (pr.isIncoming && (pr.isReady || pr.handledByOtherSession)) { - PopupAlertManager.cancelAlert(uniqueIdForVerificationRequest(pr)) + popupAlertManager.cancelAlert(uniqueIdForVerificationRequest(pr)) } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt index dfe80de9de..9a9c1241a1 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeActivity.kt @@ -42,6 +42,8 @@ import im.vector.riotx.core.pushers.PushersManager import im.vector.riotx.features.disclaimer.showDisclaimerDialog import im.vector.riotx.features.notifications.NotificationDrawerManager import im.vector.riotx.features.popup.PopupAlertManager +import im.vector.riotx.features.popup.VectorAlert +import im.vector.riotx.features.popup.VerificationVectorAlert import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.workers.signout.SignOutViewModel @@ -60,6 +62,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { @Inject lateinit var pushManager: PushersManager @Inject lateinit var notificationDrawerManager: NotificationDrawerManager @Inject lateinit var vectorPreferences: VectorPreferences + @Inject lateinit var popupAlertManager: PopupAlertManager private val drawerListener = object : DrawerLayout.SimpleDrawerListener() { override fun onDrawerStateChanged(newState: Int) { @@ -149,8 +152,8 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { if (crossSigningEnabledOnAccount && myCrossSigningKeys?.isTrusted() == false) { // We need to ask sharedActionViewModel.hasDisplayedCompleteSecurityPrompt = true - PopupAlertManager.postVectorAlert( - PopupAlertManager.VectorAlert( + popupAlertManager.postVectorAlert( + VerificationVectorAlert( uid = "completeSecurity", title = getString(R.string.new_signin), description = getString(R.string.complete_security), diff --git a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt index 40b92923ec..6fbe6f58d3 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/HomeDetailFragment.kt @@ -42,6 +42,7 @@ import im.vector.riotx.features.home.room.list.RoomListFragment import im.vector.riotx.features.home.room.list.RoomListParams import im.vector.riotx.features.home.room.list.UnreadCounterBadgeView import im.vector.riotx.features.popup.PopupAlertManager +import im.vector.riotx.features.popup.VectorAlert import im.vector.riotx.features.workers.signout.SignOutViewModel import kotlinx.android.synthetic.main.fragment_home_detail.* import timber.log.Timber @@ -53,7 +54,8 @@ private const val INDEX_ROOMS = 2 class HomeDetailFragment @Inject constructor( val homeDetailViewModelFactory: HomeDetailViewModel.Factory, - private val avatarRenderer: AvatarRenderer + private val avatarRenderer: AvatarRenderer, + private val alertManager: PopupAlertManager ) : VectorBaseFragment(), KeysBackupBanner.Delegate { private val unreadCounterBadgeViews = arrayListOf() @@ -89,19 +91,21 @@ class HomeDetailFragment @Inject constructor( it.unknownSessions.invoke()?.let { unknownDevices -> Timber.v("## Detector - ${unknownDevices.size} Unknown sessions") unknownDevices.forEachIndexed { index, deviceInfo -> - Timber.v("## Detector - #${index} deviceId:${deviceInfo.deviceId} lastSeenTs:${deviceInfo.lastSeenTs}") + Timber.v("## Detector - #${index} deviceId:${deviceInfo.second.deviceId} lastSeenTs:${deviceInfo.second.lastSeenTs}") } if (it.canCrossSign && unknownDevices.isNotEmpty()) { - val newest = unknownDevices.first() + val newest = unknownDevices.first().second + val user = unknownDevices.first().first val uid = "ND_${newest.deviceId}" - PopupAlertManager.cancelAlert(uid) - PopupAlertManager.postVectorAlert( - PopupAlertManager.VectorAlert( + alertManager.cancelAlert(uid) + alertManager.postVectorAlert( + VectorAlert( uid = uid, title = getString(R.string.new_session), description = getString(R.string.new_session_review), iconId = R.drawable.ic_shield_warning ).apply { + matrixItem = user colorInt = ContextCompat.getColor(requireActivity(), R.color.riotx_accent) contentAction = Runnable { (weakCurrentActivity?.get() as? VectorBaseActivity) diff --git a/vector/src/main/java/im/vector/riotx/features/home/UnknwonDeviceDetectorSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/UnknwonDeviceDetectorSharedViewModel.kt index addcf33684..6ba194bb54 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/UnknwonDeviceDetectorSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/UnknwonDeviceDetectorSharedViewModel.kt @@ -22,7 +22,9 @@ import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Uninitialized import com.airbnb.mvrx.ViewModelContext import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.NoOpCancellable +import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.rx.rx @@ -34,7 +36,8 @@ import im.vector.riotx.core.platform.VectorViewModel import io.reactivex.android.schedulers.AndroidSchedulers data class UnknownDevicesState( - val unknownSessions: Async?> = Uninitialized, + val unknownSessions: Async>?> = Uninitialized, + val verifiedSessions: Async>?> = Uninitialized, val canCrossSign: Boolean = false ) : MvRxState @@ -51,6 +54,9 @@ class UnknownDeviceDetectorSharedViewModel(session: Session, initialState: Unkno resp.devices?.filter { info -> deviceList.firstOrNull { info.deviceId == it.deviceId }?.isVerified?.not() ?: false }?.sortedByDescending { it.lastSeenTs } + ?.map { + session.getUser(it.user_id ?: "")?.toMatrixItem() to it + } } .toObservable() } diff --git a/vector/src/main/java/im/vector/riotx/features/lifecycle/VectorActivityLifecycleCallbacks.kt b/vector/src/main/java/im/vector/riotx/features/lifecycle/VectorActivityLifecycleCallbacks.kt index bf932a74be..6074309f13 100644 --- a/vector/src/main/java/im/vector/riotx/features/lifecycle/VectorActivityLifecycleCallbacks.kt +++ b/vector/src/main/java/im/vector/riotx/features/lifecycle/VectorActivityLifecycleCallbacks.kt @@ -22,12 +22,12 @@ import android.os.Bundle import im.vector.riotx.features.popup.PopupAlertManager import javax.inject.Inject -class VectorActivityLifecycleCallbacks @Inject constructor() : Application.ActivityLifecycleCallbacks { +class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager: PopupAlertManager) : Application.ActivityLifecycleCallbacks { override fun onActivityPaused(activity: Activity) { } override fun onActivityResumed(activity: Activity) { - PopupAlertManager.onNewActivityDisplayed(activity) + popupAlertManager.onNewActivityDisplayed(activity) } override fun onActivityStarted(activity: Activity) { diff --git a/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt index aa198eba02..1876d83617 100644 --- a/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt +++ b/vector/src/main/java/im/vector/riotx/features/popup/PopupAlertManager.kt @@ -20,20 +20,23 @@ import android.os.Build import android.os.Handler import android.os.Looper import android.view.View -import androidx.annotation.ColorInt -import androidx.annotation.ColorRes -import androidx.annotation.DrawableRes +import android.widget.ImageView import com.tapadoo.alerter.Alerter import com.tapadoo.alerter.OnHideAlertListener +import dagger.Lazy import im.vector.riotx.R +import im.vector.riotx.features.home.AvatarRenderer import timber.log.Timber import java.lang.ref.WeakReference +import javax.inject.Inject +import javax.inject.Singleton /** * Responsible of displaying important popup alerts on top of the screen. * Alerts are stacked and will be displayed sequentially */ -object PopupAlertManager { +@Singleton +class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy) { private var weakCurrentActivity: WeakReference? = null private var currentAlerter: VectorAlert? = null @@ -160,9 +163,19 @@ object PopupAlertManager { clearLightStatusBar() alert.weakCurrentActivity = WeakReference(activity) - Alerter.create(activity) - .setTitle(alert.title) + val alerter = if (alert is VerificationVectorAlert) Alerter.create(activity, R.layout.alerter_verification_layout) + else Alerter.create(activity) + + alerter.setTitle(alert.title) .setText(alert.description) + .also { al -> + if (alert is VerificationVectorAlert) { + val tvCustomView = al.getLayoutContainer() + tvCustomView?.findViewById(R.id.ivUserAvatar)?.let { imageView -> + alert.matrixItem?.let { avatarRenderer.get().render(it, imageView) } + } + } + } .apply { if (!animate) { setEnterAnimation(R.anim.anim_alerter_no_anim) @@ -226,37 +239,4 @@ object PopupAlertManager { displayNextIfPossible() }, 500) } - - /** - * Dataclass to describe an important alert with actions. - */ - data class VectorAlert(val uid: String, - val title: String, - val description: String, - @DrawableRes val iconId: Int?, - val shouldBeDisplayedIn: ((Activity) -> Boolean)? = null) { - - data class Button(val title: String, val action: Runnable, val autoClose: Boolean) - - // will be set by manager, and accessible by actions at runtime - var weakCurrentActivity: WeakReference? = null - - val actions = ArrayList. - Verwenden Sie die alte Überprüfung. + Es ist nichts aufgetaucht\? Noch nicht alle Clients unterstützen die interaktive Verifikation. Nutze die alte Verifikation.. + Verwende die alte Verifizierungsmethode. - Das Gerät kennt diese Transaktion nicht + Die Sitzung kennt diese Transaktion nicht Die Hash-Verpflichtung stimmte nicht überein Die SAS stimmte nicht überein - Wiederherstellungsschlüssel passt nicht - Der Benutzer Wiederherstellungsschlüssel passt nicht - Informieren Sie sich hier über ungelesene Nachrichten - Ihre direkte Konversation wird hier angezeigt + Schlüssel-Ungleichheit + Benutzer-Ungleichheit + Informiere dich hier über ungelesene Nachrichten + Deine direkten Konversationen werden hier angezeigt Fehlerhaftes Ereignis, kann nicht angezeigt werden Beim Abrufen der Vertrauensinformationen ist ein Fehler aufgetreten Beim Abrufen der Schlüsselsicherungsdaten ist ein Fehler aufgetreten @@ -1576,7 +1572,7 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Play Store Beschreibung Matrix SDK Version Sonstige Hinweise Dritter - Sie sehen diesen Raum bereits! + Du siehst diesen Raum bereits! Schnelle Reaktionen @@ -1595,8 +1591,8 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Token registrieren Mache einen Vorschlag - Bitte schreiben Sie Ihren Vorschlag unten. - Beschreiben Sie hier Ihren Vorschlag + Bitte schreibe unten deine Anmerkungen. + Beschreibe hier deine Anmerkung Versteckte Ereignisse in der Zeitleiste anzeigen Direkte Nachrichten @@ -1610,22 +1606,22 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Keine Änderungen gefunden Gespräche filtern… - Senden Sie eine neue Direktnachricht + Sende eine neue Direktnachricht Das Raumverzeichnis anzeigen Link in die Zwischenablage kopiert - Nach Matrix-ID hinzufügen + Mit Matrix-ID hinzufügen Raum erstellen… Bearbeitungsverlauf anzeigen Die andere Partei hat die Überprüfung abgebrochen. \n%s - Das Gerät kann sich nicht auf eine Schlüsselvereinbarung, eine Hash-, eine MAC- oder eine SAS-Methode einigen - Die neueste Featureliste befindet sich immer in %1$s. Wenn Sie Fehler finden, senden Sie uns bitte einen Bericht im Menü oben links von \"Startseite\". Wir werden den Fehler so schnell wie möglich beheben. - Wenn Sie Fehler finden, senden Sie uns bitte einen Bericht im Menü oben links von \"Startseite\". Wir werden diese so schnell wie möglich beheben. + Die Sitzung kann sich nicht auf eine Schlüsselvereinbarung, eine Hash-, eine MAC- oder eine SAS-Methode einigen + Die neueste Feature-Liste befindet sich immer in %1$s. Wenn du Fehler findest, sende uns bitte einen Bericht im Menü oben links von \"Startseite\". Wir werden den Fehler so schnell wie möglich beheben. + Wenn du Fehler findest, sende uns bitte einen Bericht im Menü oben links von der \"Startseite\". Wir werden diese so schnell wie möglich beheben. - Importieren Sie e2e-Schlüssel aus der Datei \"%1$s\". + E2E-Schlüssel aus der Datei \"%1$s\" importieren. Vielen Dank, der Vorschlag wurde erfolgreich gesendet Der Vorschlag konnte nicht gesendet werden (%s) @@ -1642,14 +1638,14 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Benutze die alte App - Können Sie nicht finden, wonach Sie suchen\? + Kannst du nicht finden, wonach du suchst\? Erstelle einen neuen Raum Name oder ID (#beispiel:matrix.org) - Aktivieren Sie das Streichen, um in der Zeitleiste zu antworten + Aktiviere Wischen, um in der Zeitleiste zu antworten - Kein Ergebnis gefunden. Verwenden Sie Nach Matrix-ID hinzufügen, um auf dem Server zu suchen. - Beginnen Sie mit der Eingabe, um Ergebnisse zu erhalten + Kein Ergebnis gefunden. Verwende \'Mit Matrix-ID hinzufügen\', um auf dem Server zu suchen. + Beginne mit der Eingabe, um Ergebnisse zu erhalten Filtern nach Benutzername oder ID… Raum betreten… @@ -1664,13 +1660,13 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Kein Integrationsserver konfiguriert. Anruf aufgrund eines falsch konfigurierten Servers fehlgeschlagen - Versuchen Sie es mit %s + Versuche es mit %s Nicht erneut fragen - Richten Sie eine E-Mail für die Kontowiederherstellung ein, die später von Personen, die Sie kennen, optional gefunden werden kann. - Richten Sie ein Telefon ein und lassen Sie es später optional von Personen erkennen, die Sie kennen. - Legen Sie eine E-Mail für die Kontowiederherstellung fest. Verwenden Sie eine spätere E-Mail oder ein späteres Telefon, um von Personen, die Sie kennen, optional gefunden zu werden. - Legen Sie eine E-Mail für die Kontowiederherstellung fest. Verwenden Sie eine spätere E-Mail oder ein späteres Telefon, um von Personen, die Sie kennen, optional gefunden zu werden. + Richte eine E-Mail für die Kontowiederherstellung ein. Optional, kannst du später einrichten, dass Personen dich über diese Adresse finden. + Richte eine Telefonnummer ein. Später kannst du einrichten, dass Personen dich über diese finden. + Lege eine E-Mail-Adresse für die Kontowiederherstellung fest. Später kann optional eine E-Mail-Adresse oder eine Telefonnummer dazu verwendet werden, um von anderen Personen gefunden zu werden. + Lege eine E-Mail-Adresse für die Kontowiederherstellung fest. Später kann optional eine E-Mail-Adresse oder eine Telefonnummer dazu verwendet werden, um von anderen Personen gefunden zu werden. Fallback-Call-Assist-Server zulassen Optimiert für die Batterie Optimiert für die Echtzeit @@ -1680,14 +1676,14 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Bevorzugtes Synchronisationsintervall Fund - Öffentlicher Name (sichtbar für Personen, mit denen Sie kommunizieren) - Der öffentliche Name eines Geräts ist für Personen sichtbar, mit denen Sie kommunizieren - Um fortzufahren, müssen Sie die Bedingungen dieses Dienstes akzeptieren. + Öffentlicher Name (sichtbar für Personen, mit denen du kommunizierst) + Der öffentliche Name der Sitzung ist für Personen sichtbar, mit denen du kommunizierst + Um fortzufahren, musst du die Nutzungsbedingungen akzeptieren. - Sie verwenden keinen Identity Server - Es ist kein Identitätsserver konfiguriert. Sie müssen Ihr Kennwort zurücksetzen. + Du verwendest keinen Identitätsserver + Es ist kein Identitätsserver konfiguriert. Du musst dein Kennwort zurücksetzen. - Sie versuchen anscheinend, eine Verbindung zu einem anderen Homeserver herzustellen. Möchten Sie sich abmelden\? + Du versuchst anscheinend, eine Verbindung zu einem anderen Heimserver herzustellen. Möchtest du dich abmelden\? Push-Key: App-Anzeigename: @@ -1695,26 +1691,26 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Nutzungsbedingungen Nutzungsbedingungen überprüfen Für andere auffindbar sein - Verwenden Sie Bots, Bridges, Widgets und Sticker-Packs + Verwende Bots, Bridges, Widgets und Sticker-Pakete Gelesen von Identitätsserver - Trennen Sie den Identitätsserver - Konfigurieren Sie den Identitätsserver + Verbindung zum Identitätsserver trennen + Identitätsserver konfigurieren Identitätsserver ändern Erkennbare E-Mail-Adressen - Erkennungsoptionen werden angezeigt, sobald Sie eine E-Mail hinzugefügt haben. + Erkennungsoptionen werden angezeigt, sobald du eine E-Mail hinzugefügt hast. ausstehend Gib einen neuen Identitätsserver ein Konnte keine Verbindung zum Heimserver herstellen Latn - Bitte frage den Administrator deines Home-Servers (%1$s) um einen TURN server einzurichten, damit Anrufe zuverlässig funktionieren. -\n -\nAlternativ kannst du einen öffentlichen Server auf %2$s nutzen doch wird das nicht zu zuverlässig sein und es wird deine IP-Adresse mit dem Server teilen. Du kannst dies auch in den Einstellungen konfigurieren. + Bitte frage die Administration deines Heimservers (%1$s) um einen TURN-Server einzurichten, damit Anrufe zuverlässig funktionieren. +\n +\nAlternativ kannst du einen öffentlichen Server auf %2$s nutzen, doch wird das nicht zu zuverlässig sein und es wird deine IP-Adresse mit dem Server geteilt. Du kannst dies auch in den Einstellungen konfigurieren. Dies ist keine Adresse eines Matrixservers Kann Home-Server nicht bei dieser URL erreichen. Bitte überprüfen Wir nutzen %s als Assistenten wenn dein Home-Server keinen anbietet (Deine IP-Adresse wird während des Anrufs geteilt) @@ -1725,14 +1721,14 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Hintergrundsynchronisierungsmodus (experimentell) - Riot wird sich im Hintergrund auf eine Art synchronisieren die die Ressourcen des Geräts schont (Akku). + Riot wird sich im Hintergrund auf eine Art synchronisieren die Ressourcen des Geräts schont (Akku). \nAbhängig von dem Ressourcen-Statuses deines Geräts kann dein System die Synchronisierung verschieben. - Riot wird sich im Hintergrund periodisch zu einem bestimmten Zeitpunkt synchronisieren (konfigurierbar). -\nDies wird Funk- und Akkunutzung beeinflussen. Es wird wird eine permanente Benachrichtigung geben, die sagt, dass Riot auf Ereignisse lauscht. + Riot wird sich im Hintergrund periodisch zu einem bestimmten Zeitpunkt synchronisieren (konfigurierbar). +\nDies wird Funk- und Akkunutzung beeinflussen. Es wird eine permanente Benachrichtigung geben, die sagt, dass Riot auf Ereignisse lauscht. %s \nDie Synchronisierung kann aufgrund deiner Ressourcen (Akku) oder Gerätezustands (schlafend) verschoben werden. Integrationen - Benutze einen Integrations-Manager um Bots, Brpcken, Widgets und Sticker-Pakete zu verwalten. + Benutze einen Integrations-Manager um Bots, Brücken, Widgets und Sticker-Pakete zu verwalten. \nIntegrations-Manager erhalten Konfigurationsdaten und können Widgets verändern, Raum-Einladungen senden und in deinem Namen Berechtigungslevel setzen. Erlaube Integrationen Widget @@ -1757,7 +1753,7 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Mikrofon benutzen Lese DRM-geschützte Medien - Frühere Versionen von Riot hatten einen Sicherheitsproblem, welches dem Identitätsserver (%1$s) Zugriff auf deinen Account geben konnte. Wenn du %2$s vertraust, kannst du dies ignorieren – ansonsten logge dich bitte aus und wieder ein. + Frühere Versionen von Riot hatten ein Sicherheitsproblem, welches dem Identitätsserver (%1$s) Zugriff auf deinen Account geben konnte. Wenn du %2$s vertraust, kannst du dies ignorieren – ansonsten logge dich bitte aus und wieder ein. \n \nWeitere Details gibt es hier (Englisch): \nhttps://medium.com/@RiotChat/36b4792ea0d6 @@ -1766,7 +1762,7 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Verwalte deine Erkennungseinstellungen. Zugriff für mich zurückziehen - Gerätename: + Sitzungsname: Format: RiotX ist ein neuer Client für das Matrix-Protokoll (matrix.org): Ein offenes Netzwerk für sichere, dezentrale Kommunikation. @@ -1778,12 +1774,12 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A \n \nNicht alle Features in Riot sind bisher in RiotX implementiert. Hauptfunktionen, die noch fehlen (und bald kommen!): • Raum-Einstellungen (Raum-Mitglieder auflisten, etc.) • Anrufe • Widgets • … - Du nutzt aktuell %1$s um zu entdecken und von dir bekannten Kontakten entdeckt zu werden. + Du nutzt aktuell %1$s um vorhandene Kontakte zu finden und um von dir bekannten Kontakten gefunden zu werden. Du benutzt aktuell keinen Identitätsserver. Um zu entdecken und um von dir bekannten Kontakten entdeckt zu werden, richte unten einen ein. Entdeckbare Telefonnummern Bitte gebe Adresse des Identitätsserver ein Identitätsserver hat keine Nutzungsbedingungen - Der Identitätsserver den du ausgewählt hast, hat keine Nutzungsbedingungen. Fahre nur fort, wenn du dem Besitzer des Dienstes vertraust + Der Identitätsserver den du ausgewählt hast, hat keine Nutzungsbedingungen. Fahre nur fort, wenn du dem/r Besitzer!n des Dienstes vertraust Eine Textnachricht wurde an %s gesendet. Bitte gebe den Verifizierungscode ein, den sie enthält. Aktiviere ausführliche Logs. @@ -1825,7 +1821,7 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Kontakt Kamera Audio - Gallerie + Galerie Sticker Es ist Spam Es ist unangebracht @@ -1886,8 +1882,8 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Fortfahren Eine Trennung von deinem Identitätsserver würde bedeuten, dass du weder von anderen Nutzern gefunden werden, noch diese per E-Mail oder Telefonnummer einladen kannst. - Derzeitig teilst du deine E-Mail-Adresse oder Telefonnummer über den Identitätsserver %1$s. Du müsstest dich erneut zu %2$s verbinden um dies zu unterbinden. - Stimme den Nutzungsbedingungen des Identitätsservers (%s) zu, um zu erlauben per E-Mail oder Telefonnummer gefunden werden zu können. + Du teilst deine Email Adressen oder Telefonnummern momentan auf dem Identitätsserver %1$s. Du wirst dich erneut mit %2$s verbinden müssen, um mit dem Teilen aufzuhören. + Stimme den Nutzungsbedingungen des Identitätsservers (%s) zu, um zu erlauben per E-Mail oder Telefonnummer gefunden zu werden. gelesen von %1$s, %2$s und %3$d anderen Zu teilende Daten nicht verarbeitbar @@ -1916,7 +1912,7 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Neues Passwort Achtung! - Eine Änderung deines Passworts wird alle Ende-zu-Ende-Verschlüsselungsschlüssel zurücksetzen. Dein verschlüsselter Chatverlauf wird dadurch unlesbar. Richte eine Schlüsselsicherung ein oder exportiere deine Raumschlüssel von einem anderen Gerät bevor du dein Passwort zurücksetzt. + Eine Änderung deines Passworts wird alle Ende-zu-Ende-Verschlüsselungsschlüssel zurücksetzen. Dein verschlüsselter Chatverlauf wird dadurch unlesbar. Richte eine Schlüsselsicherung ein oder exportiere deine Raumschlüssel von einer anderen Sitzung bevor du dein Passwort zurücksetzt. Fortfahren Diese E-Mail-Adresse ist mit keinem Benutzerkonto verknüpft @@ -1959,4 +1955,322 @@ Wenn du diese neue Wiederherstellungsmethode nicht eingerichtet hast, kann ein A Wir haben dir eine E-Mail an %1$s gesendet. \nBitte öffne den darin enthaltenen Link, um mit der Benutzerkontoerstellung fortzufahren. Der eingegebene Code ist nicht korrekt. Bitte überprüfe deine Eingabe. + Beginne zu Tippen um eine Reaktion zu finden. + + %1$s hat den Raum auf \"nur-einladen\" gestellt. + Befreie deine Kommunikation + Premium-Hosting für Organisationen + Gib die Adresse des Modular Riot oder Servers ein, den du verwenden möchtest + Gibt die Adresse eines Servers oder eines Riot ein, zu dem du dich verbinden möchtest + + Die Anwendung kann sich nicht bei diesem Heimserver anmelden. Der Heimserver unterstützt die folgenden Anmeldetypen:%1$s. +\n +\nMöchtest du dich mit einem Webclient anmelden\? + Eine Bestätigungs-E-Mail wird an dich gesendet, um dein neues Passwort zu bestätigen. + Weiter + Du wurdest von allen Sitzungen abgemeldet und erhälst keine Push-Benachrichtigungen mehr. Um Benachrichtigungen wieder zu aktivieren, melde dich auf jedem Gerät erneut an. + Warnung + Lege eine E-Mail-Adresse fest, um dein Konto wiederherzustellen. Später kannst du optional zulassen, dass Personen dich anhand dieser E-Mail-Adresse entdecken. + Weiter + + Lege Telefonnummer fest + Lege eine Telefonnummer fest, damit Personen dich anhand dieser entdecken können. + Bitte verwende das internationale Format. + Weiter + + Weiter + + Internationale Telefonnummern müssen mit \'+\' beginnen + Die Telefonnummer scheint ungültig zu sein. Bitte prüfen + + Anmelden bei %1$s + Benutzername + Weiter + Warnung + Bitte löse das Captcha + Veralteter Heimserver + Auf diesem Heimserver läuft eine zu alte Version, um eine Verbindung herzustellen. Bitten die Heimserver-Administration um ein Upgrade. + + + Es wurden zu viele Anfragen gesendet. Versuche es erneut in %1$d Sekunde… + Es wurden zu viele Anfragen gesendet. Versuche es erneut in %1$d Sekunden… + + + Gesehen von + + Du bist abgemeldet + Dies kann verschiedene Gründe haben: +\n +\n• Du hast dein Passwort in einer anderen Sitzung geändert. +\n +\n• Du hast diese Sitzung aus einer anderen Sitzung heraus gelöscht. +\n +\n• Die Administration deines Servers hat deinen Zugriff aus Sicherheitsgründen ungültig gemacht. + Melde dich erneut an + + Du bist abgemeldet + Anmelden + Deine Heimserver-Administration (%1$s) hat dich von deinem Konto %2$s (%3$s) abgemeldet. + Melden dich an, um ausschließlich auf diesem Gerät gespeicherte Verschlüsselungsschlüssel wiederherzustellen. Du benötigst sie, um deine verschlüsselten Nachrichten auf jedem Gerät zu lesen. + Anmelden + Passwort + Persönliche Daten löschen + Warnung: Persönliche Daten (einschließlich Verschlüsselungsschlüssel) werden weiterhin auf diesem Gerät gespeichert. +\n +\nDeaktiviere diese Option, wenn dieses Gerät nicht mehr verwenden wird oder sich bei einem anderen Konto angemeldet werden soll. + Alle Daten löschen + + Daten löschen + Alle aktuell auf diesem Gerät gespeicherten Daten löschen\? +\nMelde dich erneut an, um auf deine Kontodaten und Nachrichten zuzugreifen. + Du verlierst den Zugriff auf verschlüsselte Nachrichten, außer, du meldest dich an, um den Verschlüsselungsschlüssel wiederherzustellen. + Daten löschen + Die aktuelle Sitzung gehört dem/der Benutzer!n%1$s. Die angegebenen Anmeldeinformationen sind von Benutzer!n %2$s. Dies wird nicht von RiotX unterstützt. +\nBitte zuerst die Daten löschen und dann erneut anmelden. + + matrix.to-Link fehlerhaft + Die Beschreibung ist zu kurz + + Initiale Synchronisierung… + + Alle meine Sitzungen anzeigen + Erweiterte Einstellungen + Entwicklungsmodus + Der Entwicklungsmodus aktiviert versteckte Funktionen und kann die Anwendung weniger stabil machen. Nur für Entwickler!nnen! + Wutschütteln + Erkennungsschwelle + Schüttel dein Telefon, um die Erkennungsschwelle zu testen + Schütteln erkannt! + Einstellungen + Aktuelle Sitzung + Andere Sitzungen + + Zeigt nur die ersten Ergebnisse, gib mehr Buchstaben ein… + + Ausfallsicher + RiotX kann häufiger abstürzen, wenn ein unerwarteter Fehler auftritt + + Überprüfe die angegebenen Benutzer-ID + Stellt einer Klartextnachricht ¯\\_(ツ)_/¯ voran + + Aktiviere Verschlüsselung + Nach der Aktivierung kann die Verschlüsselung nicht deaktiviert werden. + + Deine E-Mail-Domain ist nicht berechtigt, sich auf diesem Server zu registrieren + + Nicht vertrauenswürdige Anmeldung + Sie passen + Sie passen nicht + Verifiziere diese/n Benutzer!n, indem du bestätigst, dass diese einzigartigen Emoji in der selben Reihenfolge auf dem Bildschirm deines Gegenübers angezeigt werden. + Für ultimative Sicherheit verwende ein anderes vertrauenswürdiges Kommunikationsmittel oder mache es persönlich. + Suche nach dem grünen Schild, um sicherzustellen, dass ein/e Benutzer!n vertrauenswürdig ist. Vertraue allen Benutzer!nnen in einem Raum, um sicherzustellen, dass der Raum sicher ist. + + Nicht sicher + Eine der folgenden Möglichkeiten kann beeinträchtigt sein: +\n +\n - Dein Heimserver +\n - Der Heimserver mit dem dein Gegenüber verbunden ist +\n - Deine oder die Internetverbindung des Gegenüber +\n - Dein Gerät oder das Gerät des Gegenüber + + Video. + Bild. + Audio + Datei + + Warten… + %s brach ab + Du hast abgebrochen + %s hat akzeptiert + Du hast akzeptiert + Verifizierung gesendet + Verifizierung angefragt + + + Verifiziere diese Sitzung + Manuelle Verifizierung + + Ich + + Scanne den Code mit dem Gerät des Gegenüber, um sich gegenseitig zu überprüfen + Scanne ihren/seinen Code + Kann nicht scannen + Wenn ihr nicht am selben Ort seid, vergleicht Emoji stattdessen + + Verifizieren via Emoji-Vergleich + + Mit Emoji verifizieren + Wenn du den obigen Code nicht scannen kannst, verifiziert, indem ihr eine kurze, eindeutige Auswahl an Emoji vergleicht. + + QR-Code-Bild + + %s verifizieren + %s verifiziert + Warte auf %s… + Für zusätzliche Sicherheit überprüfe %s, indem ihr auf beiden Geräten einen einzigartigen Code überprüft. +\n +\nFür maximale Sicherheit macht dies persönlich. + Nachrichten in diesem Raum sind nicht Ende-zu-Ende verschlüsselt. + Nachrichten in diesem Raum sind Ende-zu-Ende verschlüsselt. +\n +\nDeine Nachrichten sind gesichert und nur du und dein Gegenüber haben die eindeutigen Schlüssel, um sie zu entsperren. + Sicherheit + Mehr erfahren + Mehr + Raum Einstellungen + Benachrichtigungen + + Eine Person + %1$d Personen + + Uploads + Raum verlassen + Verlasse den Raum… + + Admins + Moderierende + benutzerdefiniert + Eingeladen + Nutzer!n + + Admin in %1$s + Moderation in %1$s + Springen & als gelesen markieren + + RiotX kann keine Ereignisse vom Typ \'%1$s\' + RiotX beherrscht keine Nachrichten vom Typ \'%1$s\' + RiotX ist beim verarbeiten des Ereignisinhalts mit der ID \'%1$s\' auf ein Problem gestoßen + + Nicht ignorieren + + Diese Sitzung kann diese Verifizierung nicht mit deinen anderen Sitzungen teilen. +\nDie Überprüfung wird lokal gespeichert und in einer zukünftigen Version der App freigegeben. + + Neueste Räume + Andere Räume + + Sendet die angegebene Nachricht in Regenbogenfarben + Sendet das angegebene Emote in Regenbogenfarben + + Zeitleiste + + Nachrichteneditor + + Aktivieren Ende-zu-Ende-Verschlüsselung + Einmal aktiviert kann die Verschlüsselung nicht rückgängig gemacht werden. + + Verschlüsselung aktivieren\? + Nach der Aktivierung kann die Verschlüsselung für einen Raum nicht deaktiviert werden. In einem verschlüsselten Raum gesendete Nachrichten können vom Server nicht gesehen werden, nur von den Teilnehmenden des Raums. Durch die Verschlüsselung funktionieren viele Bots und Bridges möglicherweise nicht ordnungsgemäß. + Verschlüsselung aktivieren + + Um sicher zu gehen, verifiziere %s, indem ein einmaligen Code überprüft wird. + Um sicher zu sein, tut dies persönlich oder verwendet einen anderen Kommunikationsweg. + + Vergleiche die einzigartigen Emoji und stell sicher, dass sie in derselben Reihenfolge angezeigt werden. + Vergleiche den Code mit dem Code auf dem Bildschirm deines Gegenübers. + Nachrichten mit diesem Gegenüber sind Ende-zu-Ende verschlüsselt und können nicht von Dritten gelesen werden. + Deine neue Sitzung ist jetzt verifiziert. Sie hat Zugriff auf deine verschlüsselten Nachrichten, und andere Benutzer!nnen sehen sie als vertrauenswürdig an. + + + Cross-Signing + Cross-Signing ist aktiviert +\nPrivate Schlüssel auf dem Gerät. + Cross-Signing ist aktiviert +\nSchlüssel sind vertrauenswürdig. +\nPrivate Schlüssel sind nicht bekannt + Cross-Signing ist aktiviert +\nSchlüssel sind nicht vertrauenswürdig + Cross-Signing ist nicht aktiviert + + + Aktive Sitzungen + Zeige alle Sitzungen + Verwalte Sitzungen + Diese Sitzung abmelden + + Keine kryptografischen Informationen verfügbar + + Diese Sitzung ist für sichere Nachrichtenübertragung vertrauenswürdig, da du sie überprüft hast: + Verifiziere diese Sitzung, um sie als vertrauenswürdig zu markieren, und gewähren ihr Zugriff auf verschlüsselte Nachrichten. Wenn du dich nicht bei dieser Sitzung angemeldet hast, ist dein Konto möglicherweise gefährdet: + + + Eine aktive Sitzung + %d aktive Sitzungen + + + Verifiziere diese Sitzung + Andere Benutzer!nnen vertrauen ihr möglicherweise nicht + Vollständige Sicherheit + + Öffne eine vorhandene Sitzung und verwende sie, um diese zu überprüfen und ihr Zugriff auf verschlüsselte Nachrichten zu gewähren. + + + Verifizieren + Verifiziert + Warnung + + Sitzungen konnten nicht abgerufen werden + Sitzungen + Vertraut + Nicht vertraut + + Diese Sitzung ist für sichere Nachrichtenübertragung vertrauenswürdig, weil %1$s (%2$s) sie verifiziert hat: + %1$s (%2$s) hat sich in einer neuen Sitzung angemeldet: + Bis diese/r Benutzer!n dieser Sitzung vertraut, werden an und von ihr/ihm gesendete Nachrichten mit Warnungen gekennzeichnet. Alternativ kannst du dies manuell überprüfen. + + + Initialisiere Cross-Signing + Schlüssel zurücksetzen + + QR-Code + + Hat dein Gegenüber den QR-Code erfolgreich gescannt\? + Ja + Nein + + Verbindung zum Server wurde unterbrochen + + Entwicklungswerkzeuge + Kontodaten + + %d Stimme + %d Stimmen + + + %d Stimme - Endergebnis + %d Stimmen - Endergebnis + + Ausgewählte Option + Erstellt eine einfache Umfrage + Kann nicht auf eine vorhandene Sitzung zugegriffen werden\? + Verwende deinen Wiederherstellungsschlüssel oder deine Passphrase + + Neue Anmeldung + + Kann keine Geheimnisse im Speicher finden + Gib die geheime Speicherpassphrase ein + Warnung: + Du solltest nur von einem vertrauenswürdigen Gerät auf den geheimen Speicher zugreifen + Greife auf deinen sicheren Nachrichtenverlauf und deine Cross-Signing-Identität zu, um andere Sitzungen zu überprüfen, indem du deine Passphrase eingibst + + Entfernen… + Möchtest du diesen Anhang an %1$s senden\? + + Sende Bild in Originalgröße + Sende Bilder in Originalgröße + + + Entfernen bestätigen + Möchtest du dieses Ereignis wirklich entfernen (löschen)\? Beachte, dass beim Löschen eines Raumnamens oder einer Themenänderung die Änderung rückgängig gemacht werden kann. + Grund hinzufügen + Grund für das Editieren + + Ereignis gelöscht von Benutzer!n, Grund: %1$s + Ereignis vom Raumadministration moderiert, Grund: %1$s + + Schlüssel sind bereits aktuell! + + Spoiler + Benutzerdefiniert (%1$d) in %2$s + diff --git a/vector/src/main/res/values-es/strings.xml b/vector/src/main/res/values-es/strings.xml index 9ae69b7e52..ec37ea2825 100644 --- a/vector/src/main/res/values-es/strings.xml +++ b/vector/src/main/res/values-es/strings.xml @@ -1482,7 +1482,7 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Ok ¿No aparece nada\? No todas las aplicaciones cliente soportan verificación interactiva. Usa la verificación clásica. - Usar verificación clásica + Usar verificación clásica. Verificación de clave Solicitud cancelada @@ -1597,7 +1597,7 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Por favor, pídele al administrador de tu servidor doméstico (%1$s) que configure un servidor TURN para que las llamadas funcionen de forma fiable. \n \nAlternativamente, puedes intentar usar el servidor público en %2$s, pero no será tan confiable, y compartirá tu dirección IP con ese servidor. También puedes cambiar esto en Configuración. - Configura un correo electrónico para la recuperación de la cuenta, y más tarde configúralo ser descubierto opcionalmente por personas que te conozcan + Configura un correo electrónico para la recuperación de la cuenta, y opcionalmente para encontrar personas conocidas Configura un correo electrónico para la recuperación de la cuenta. Usa el correo electrónico o el teléfono más tarde para ser descubierto opcionalmente por personas que te conozcan. Configura un correo electrónico para la recuperación de la cuenta. Usa el correo electrónico o el teléfono más tarde para ser descubierto opcionalmente por personas que te conozcan. Esta no es una dirección de servidor Matrix válida @@ -1878,4 +1878,36 @@ Ten en cuenta que esta acción reiniciará la aplicación y puede tardar algo de Empieza Selecciona un servidor + Como el correo electrónico, las cuantas tienen un hogar, aunque se puede hablar con cualquiera + Alojamiento de pago para organizaciones + Saber más + Otro + Ajustes avanzados y de personalización + + Continuar + Conectarse a %1$s + Conectarse a Modular + Conectarse a un servidor externo + Iniciar sesión en %1$s + Registrarse + Iniciar sesión + Dirección + Alojamiento de pago para organizaciones + Introduzca la dirección de Modular Riot o servidor que quieres usar + Introduzca la dirección del servidor Riot al que quieres conectarte + + Se produjo un error al cargar la pagina: %1$s (%2$d) + "La aplicación no es capaz de iniciar sesión en este servidor. Este solo soporta el acceso mediante: %1$s. +\n +\n¿Quieres acceder usando un cliente web\?" + Lo sentimos, este servidor no acepta nuevas cuentas. + La aplicación no fue capaz de crear una cuenta en este servidor. +\n +\n¿Quieres registrarte usando un cliente web\? + + La dirección de coreo electrónico no está asociada a ninguna cuenta. + + Reiniciar contraseña en %1$s + ¡Las claves ya están al día! + diff --git a/vector/src/main/res/values-eu/strings.xml b/vector/src/main/res/values-eu/strings.xml index dd8ead5f27..af69503897 100644 --- a/vector/src/main/res/values-eu/strings.xml +++ b/vector/src/main/res/values-eu/strings.xml @@ -285,7 +285,7 @@ Baimendu sarbidea hurrengo laster-leihoan zure telefonotik fitxategiak bidali ah Debekua kendu Berrezarri erabiltzaile arrunt gisa Bihurtu moderatzaile - Bihurtu kudeatzaile + Bihurtu administratzaile Ezkutatu kide honen mezu guztiak Erakutsi kide honen mezu guztiak Erabiltzailearen IDa, izena edo e-maila @@ -635,7 +635,7 @@ Orain egin dezakezu edo gero aplikazioaren ezarpenetatik. ERABILTZAILE DIREKTORIOA (%s) Datuak gordetzeko modua - Itxura + Azala Letra-tamaina Oso txikia @@ -645,9 +645,9 @@ Orain egin dezakezu edo gero aplikazioaren ezarpenetatik. Oso handia Handiena Erraldoia - Itxura argia - Itxura iluna - Itxura beltza + Azal argia + Azal iluna + Azal beltza Sinkronizatzen… Entzun gertaerak @@ -991,7 +991,7 @@ Kontuan izan ekintza honek aplikazioa berrabiaraziko duela eta denbora bat behar Errore bat gertatu da - Status.im itxura + Status.im azala %s bertsioa Sortu esportatutako gakoak zifratzeko pasaesaldi bat. Pasaesaldi hori gakoak inportatzeko sartu beharko duzu. @@ -1291,7 +1291,7 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. Ezikusi Ziur saioa amaitu nahi duzula\? - Hasi saioa urrats batean + Hasi saioa Single sign-on bidez URL-a ez dago eskuragarri, egiaztatu mesedez Zure gailuak zaharkitutako TLS segurtasun protokolo bat darabil, erasotu daitekeena, zure segurtasunerako ezin izango zara konektatu Bidali mezua Sartu tekla sakatuta @@ -1553,12 +1553,12 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. RiotX - Hurrengo belaunaldiko Matrix bezeroa Matrix-erako bezero azkarrago eta arinago bat azken Android tresnak erabiliz eginak - RiotX bezero berri bat da Matrix protokoloarentzako (Matrix.org): komunikazioa seguru eta deszentralizatuarentzako sare libre bat. RiotX Android plataformarako Riot bezeroaren berridazketa oso bat da, erabat berridatzitako Android SDK-n oinarritua. -\n -\nAbisua: hau beta bertsio bat da. RiotX garapen aktiboan dago eta baditu mugak zein akatsak (gehiegi ez espero dugu). Iruzkin guztiak ongi etorriak dira! -\n -\nRiotX bezeroak honakoa ahalbidetzen du: • Badagoen kontu batean saioa hasi • Gelak sortu eta gela publikoetara elkartu • Gonbidapenak onartu edo ukatu • Erabiltzailearen gelak zerrendatu • Gelaren xehetasunak ikusi • Testuzko mezuak bidali • Eranskinak bidali • Zifratutako geletan mezuak irakurri eta idatzi • Zifratzea: E2Egakoen babeskopia, gailuaren egiaztaketa aurreratua, gakoa partekatzeko eskaria eta erantzuna • Push jakinarazpena • Gai argia, iluna eta beltza -\n + RiotX bezero berri bat da Matrix protokoloarentzako (Matrix.org): komunikazioa seguru eta deszentralizatuarentzako sare libre bat. RiotX Android plataformarako Riot bezeroaren berridazketa oso bat da, erabat berridatzitako Android SDK-n oinarritua. +\n +\nAbisua: hau beta bertsio bat da. RiotX garapen aktiboan dago eta baditu mugak zein akatsak (gehiegi ez espero dugu). Iruzkin guztiak ongi etorriak dira! +\n +\nRiotX bezeroak honakoa ahalbidetzen du: • Badagoen kontu batean saioa hasi • Gelak sortu eta gela publikoetara elkartu • Gonbidapenak onartu edo ukatu • Erabiltzailearen gelak zerrendatu • Gelaren xehetasunak ikusi • Testuzko mezuak bidali • Eranskinak bidali • Zifratutako geletan mezuak irakurri eta idatzi • Zifratzea: E2Egakoen babeskopia, gailuaren egiaztaketa aurreratua, gakoa partekatzeko eskaria eta erantzuna • Push jakinarazpena • Azal argia, iluna eta beltza +\n \nEz dira oraindik Riot bezeroaren ezaugarri guztiak ezarri RiotX bezeroan. Falta diren (eta laster etorriko direnen) artean nabarmenak dira: • Gelaren ezarpenak (gelako kideak zerrendatzea, eta abar.) • Deiak • Trepetak • … aplikazio_pantaila_izena: @@ -1784,7 +1784,7 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. Zure pantaila-izena Zure abatarraren URL-a Zure erabiltzaile ID-a - Zure gaia + Zure azala Trepetaren ID-a Gelaren ID-a @@ -2079,8 +2079,8 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. Saltatu irakurragirira - RiotX aplikazioak oraindik ez ditu \'%1$s\' motako gertaerak kudeatzen - RiotX aplikazioak oraindik ez ditu \'%1$s\' motako mezuak kudeatzen + RiotX aplikazioak ez ditu \'%1$s\' motako gertaerak kudeatzen + RiotX aplikazioak ez ditu \'%1$s\' motako mezuak kudeatzen RiotX aplikazioak arazo bat izan du \'%1$s\' id-a duen edukia erakusteko Utzi ezikusteari @@ -2144,7 +2144,7 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. Beste erabiltzaile batzuk ez fidagarritzat jo lezakete Bete segurtasuna - Ireki aurreko saio bat eta erabili hori saio hau egiaztatzeko, mezu zifratuetara sarbidea emanez. Ez baduzu bat erabiltzerik, erabili berreskuratze gakoa edo pasa-esaldia. + Ireki aurreko saio bat eta erabili hori saio hau egiaztatzeko, mezu zifratuetara sarbidea emanez. Egiaztatu @@ -2166,8 +2166,50 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. QR kodea + Beste erabiltzaileak QR kodea ongi eskaneatu du\? Bai Ez Zerbitzariarekin konexioa galdu da + Erabiltzaile-izena + Garapen tresnak + Kontuaren datuak + + boto %d + %d boto + + + boto %d - Azken emaitza + %d boto - Azken emaitza + + Hautatutako aukera + Inkesta sinplea sortzen du + Ezin zara badagoen saio batera sartu\? + Erabili berreskuratze gakoa edo pasa-esaldia + + Saio berria + + Ezin izan da sekreturik aurkitu biltegian + Sartu biltegi sekretuko pasa-esaldia + Abisua: + Biltegi sekretura gailu fidagarri batetik konektatu beharko zinateke beti + Atzitu zure mezu seguruen historiala eta zeharkako sinatzerako identitatea beste saioak egiaztatzeko zure pasa-esaldia sartuz + + Kendu… + Eranskin hau %1$s gelara bidali nahi duzu\? + + Bidali irudia jatorrizko tamainan + Bidali irudiak jatorrizko tamainan + + + Berretsi kentzea + Ziur gertaera hau kendu (ezabatu) nahi duzula\? Jakin gelaren izenaren edo mintzagaiaren aldaketa ezabatzen baduzu, aldaketa desegin daitekeela. + Eman arrazoi bat + Kentzeko arrazoia + + Erabiltzaileak kendu du gertaera, arrazoia: %1$s + Gelako moderatzaile batek kendu du gertaera, arrazoia: %1$s + + Gakoak egunean daude jada! + diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index 7f8a2d0c3a..5ec62b8158 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -3,9 +3,9 @@ fa IR - قالب روشن - قالب تیره - قالب سیاه + زمینهٔ روشن + زمینهٔ تیره + زمینهٔ سیاه گوش دادن به رویدادها پیام‌ها @@ -32,7 +32,7 @@ تماس فعال صوتی تصویری - اطلاعات دستگاه + اطّلاعات نشست به هر حال ارسال کنید یا دعوت @@ -106,7 +106,7 @@ شماره تلفن نامعتبر به نظر می‌رسد گذرواژه‌ها مطابقت ندارد گذرواژه را فراموش کردید؟ - از گزینه‌های کارگزار سفارشی استفاده کنید (پیشرفته) + استفاده از گزینه‌های کارساز سفارشی (پیش‌رفته) برای ادامه‌ی ثبت‌نام، لطفاً ایمیل خود را بررسی کنید من آدرس ایمیلم را تایید کرده‌ام امکان ورود به سیستم وجود ندارد: خطای شبکه @@ -141,13 +141,13 @@ پسندیده‌ها افراد - جامعه‌ها + اجتماع‌ها - جستجوی اتاق‌ها - جستجوی پسندها - جستجوی افراد - جستجوی اتاق‌ها - جستجوی جامعه‌ها + پالایش نام‌های اتاق + پالایش برگزیده‌ها + پالایش افراد + پالایش نام‌های اتاق + پالایش نام‌های اجتماع دعوت‌ها هشدارهای سیستمی @@ -160,8 +160,8 @@ فهرست اتاق‌ها اتاقی نیست - اتاق عمومی در دسترس نیست - جامعه‌ها + هیچ اتاق عمومی‌ای موجود نیست + اجتماع‌ها ارسال رخدادنگارها ارسال رخدادنگارهای خطا ارسال تصویر صفحه @@ -273,8 +273,8 @@ ابزارهای مدیر تماس - پیام‌های شخصی - دستگاه‌ها + گپ‌های مستقیم + نشست‌ها دعوت ترک این اتاق @@ -296,21 +296,21 @@ آیا می‌خواهید از حساب کاربری خود خارج شوید؟ علامت‌گذاری به عنوان خوانده شده ورود با سامانه‌های احراز هویت مرکزی - قالب Status.im + زمینهٔ Status.im راه‌اندازی سرویس - اعلان‌های باصدا - اعلان‌های بی‌صدا + آگاهی‌های پرصدا + آگاهی‌های صامت تاریخچه - جزئیات جامعه + جزییات اجتماع ارسال استیکر پشتیبان‌گیری کلید بازیابی پشتیبان کلید پشتیبان‌گیری از کلید هنوز به پایان نرسیده است، لطفاً صبر کنید… در صورتی که الآن از حساب خود خارج شوید، پیام‌های رمز خود را از دست خواهید داد پشتیبان‌گیری کلید در جریان است. در صورتی که الآن از حساب خود خارج شوید، پیام‌های رمز خود را از دست خواهید داد. - برای جلوگیری از گم شدن پیام‌های رمز، پشتیبان امن کلید باید روی تمام دستگاه‌های شما فعال باشد. + برای از دست ندادن دسترسی به پیام‌های رمزشده، باید پشتیبان کلید امن روی تمام نشست‌هایتان فعّال باشد. پیام‌های رمز خود را نمی‌خواهم پشتیبان‌گیری از کلیدها… استفاده از پشتیبان کلید @@ -332,14 +332,326 @@ \nمی‌توانید در ادامه ایمیل خود را در تنظیمات برنامه به پروفایل خود اضافه کنید. کارگزار می‌خواهد اطمینان یابد که شما ربات نیستید نام کاربری قبلاً استفاده شده است - دریافت کلیدهای رمزنگاری از سایر دستگاه‌های شما. + بازدرخواست کلیدهای رمزنگاری از دیگر نشست‌هایتان. درخواست کلید ارسال شد. ارسال درخواست - لطفاً برنامه را روی یکی از دستگاه‌های دیگرتان که به این پیام دسترسی داشته است، اجرا کنید تا کلیدها منتقل شوند. + لطفاً ریوت را روی افزاره‌ای دیگر که می‌تواند پیام را رمزگشایی کند، اجرا کنید تا بتواند کلیدها را به این نشست بفرستد. - مشاهده گیرندگان + فهرست رسیدهای خواندن آیا مطمئن هستید؟ + عدم اتصال + بررسی + رد کردن + + دوباره از من نپرس + + برای بازیابی یک ایمیل وارد کنید، و بعدا در صورت دلخواه می توانید از آن برای شناسایی دوستان خود استفاده کنید. + ثبت شماره تلفن (بعدا در صورت دلخواه می توانید از آن برای شناسایی دوستان هود استفاده کنید). + نشانی رایانامهٔ پیوسته به حسابتان را برای بازنشانی گذواژه‌تان وارد کنید: + Latn + + شناسهٔ کاربری، نام یا رایانامه + فرستادن پاسخی رمزشده… + فرستادن یک پاسخ (رمزنشده)… + پالایش اعضای اتاق + اتاق‌ها + اتاق‌ها + + ۱ اتاق + %d اتاق + + + %1$s اتاق برای %2$s پیدا شد + %1$s اتاق برای %2$s پیدا شد + + گپ مستقیم + نگارش + نگارش %s + شرایط و ضوابط + تذکّرهای سوم‌شخص + حق رونوشت + سیاست محرمانگی + + عکس نمایه + نام نمایشی + رایانامه + افزودن نشانی رایانامه + تلفن + افزودن شماره تلفن + اطّلاعات برنامه + نمایش اطّلاعات برنامه در تنظیمات سامانه. + تأیید گذرواژه‌تان + نمی‌توانید این کار را از ریوت همراه انجام دهید + نیاز به تأیید هویت است + + + تنظمیات پیش‌رفتهٔ آگاهی + آگاهی‌های رفع‌اشکال + آگاهی‌ها در تنظیمات سامانه به کار افتاده‌اند. + آگاهی‌ّا در تنظیمات سامانه از کار افتاده‌اند. +\nلطفاً تنظیمات سامانه را بررسی کنید. + آگاهی‌ها برای حسابتان به کار افتاده‌اند. + آگاهی‌ها برای حسابتان از کار افتاده‌اند. +\nلطفاً تتظیمات حساب را بررسی کنید. + آگاهی‌ها برای این نشست به کار افتاده‌اند. + آگاهی‌ها برای این نشست از کار افتاده‌اند. +\nلطفاً تنظینات ریوت را بررسی کنید. + برخی آگاهی‌ها در تنظیمات سفارشیتان از کار افتاده‌اند. + خدمت آگاهی + خدمت آگاهی در حال اجراست. + خدمت آگاهی در حال اجرا نیست. +\nتلاش کنید برنامه را دوباره شروع کنید. + شروع دوبارهٔ خودکار خدمت آگاهی + به کار انداختن أگاهی‌ها برای این حساب + به کار انداختن أگاهی‌ها برای این نشست + پیکربندی أگاهی‌های پرصدا + پیکربندی أگاهی‌های تماس + پیکربندی أگاهی‌های صامت + تأخیر بین همگام‌سازی‌ها + ثانیه + ثانیه + + نگارش + نگارش olm + شرایط و ضوابط + تذکّرهای سوم‌شخص + حق رونوشت + سیاست محرمانگی + نگه‌داری رسانه + پاک‌سازی انباره + پاک‌سازی انبارهٔ رسانه + + تنظیمات کاربر + أگاهی‌ها + کاربران چشم‌پوشیده + دیگر + پیش‌رفته + یک‌پارچگی‌ها + رمزنویسی + مدیریت کلیدهای رمزنویسی + هدف‌های آگاهی + مخاطبان محلّی + اجازهٔ مخاطبان + کشور دفترچه تلفن + نمای حانه + سنجاق کردن اتاق‌هایی با آگاهی‌های بی‌پاسخ + سنجاق کردن اتاق‌هایی با پیام‌های نخوانده + نشست‌ها + پیش‌نمایش نشانی نامعتبر + فرستادن آگاهی‌های نوشتن + قالب‌بندی مارک‌دون + نمایش رسیدهای خواندن + برای یک فهرست باجزیییات، روی رسیدهای خواندن کلیک کنید. + تأیید هویت + گذرواژه: + ثبت + + واردشده به عنوان + کارساز خانگی + کارساز هویت + اجازهٔ یک‌پارچگی‌ها + مدیر یکپارچگی + + رابط کاربری + زبان + گزینش زبان + + هم‌اکنون عضو هیچ اجتماعی نیستید. + + آگاهی‌ها + کاربران مسدود + + پیش‌رفته + شناسهٔ داخلی این اتاق + نشانی‌ها + آزمایشگاه‌ها + رمزنگاری سرتاسری + رمزنگاری سرتاسری فعّال است + برای به کار انداختن رمزنگاری سرتاسری باید حارج شوید. + رمزنگاری فقط به نشست‌های تأیید شده + شناسهٔ اجتماع جدید (مثلاً ‪+foo:matrix.org‬) + شناسهٔ اجتماع نامعتبر + %s یک شناسهٔ اجتماع معتبر نیست + + + زمینه + + رمزنگاری فقط به نشست‌های تأیید شده + اتاق شامل نشست‌های ناشناخته + برای فهرست شدن اتاق‌های عمومی از یک کاساز، نامش را بنویسید + تمامی اتاق‌های روی کارساز %s + تمامی اتاق‌های بومی %s + + + ۱ اتاق + %d اتاق + + + + %d آگاهی + %d آگاهی + + + زمینه‌تان + افزودن کاره‌های ماتریس + پیام رمزنگاشته + + ایجاد اجتماع + نام اجتماع + شناسهٔ اجتماع + اتاق‌ها + اتاق‌ها + پالایش اعضای گروه + پالایش اتاق‌های گروهی + + + ۱ اتاق + %d اتاق + + مدیر اجتماع توضیحی بلند برای این اجتماع فراهم نکرده است. + + بار کردن تنبلانهٔ اعضای اتاق + (پیش‌رفته) + (پیش‌رفته) برپایی با کلید بازیابی + گرفتن نگارش پشتیبان… + شکست در گرفتن آخرین نگارش کلیدهای بازیابی (%s). + نگارش + ویرایش + پاسخ + + تلاش دوباره + برای آغاز استفاده از کاره، به اتاقی بپیوندید. + برایتان دعوتی فرستاد + دعوت‌شده به دست %s + + همه‌چی سر جاشه! + هیچ پیام نخواندهٔ دیگری ندارید + به خانه خوش آمدید! + این‌جا به پیام‌های نخوانده برسید + گفت‌وگوها + گفت‌گوهای پیام مستقیمتان این‌جا نشان داده خواهند شد + اتاق‌ها + اتاق‌هایتان این‌جا نشان داده خواهند شد + + بازخودها + موافقت + پسند + افزودن بازخورد + دیدن بازخوردها + بازخوردها + + رویداد به دست کاربر حذف شد + رویداد به دست مدیر اتاق مدیریت شد + آخرین ویرایش به دست %1$s در %2$s + + + رویداد بدشکل. نمی‌توان نمایش داد + ایجاد اتاق جدید + بدون شبکه. لطفاً اتّصال اینترنتیتان را بررسی کنید. + تغییر + تغییر شبکه + لطفاً شکیبایی کنید… + تمام اجتماع‌ها + + این اتاق نمی‌تواند پیش‌نمایش یابد + پیش‌نمایش اتاق‌های قابل خواندن به صورت عمومی هنوز در ریوت‌اکس پشتیبانی نمی‌شود + + اتاق‌ها + پیام‌های مستقیم + + اتاق جدید + ایجاد + نام اتاق + عمومی + هرکسی می‌تواند به این اتاق بپیوندد + نمایهٔ اتاق‌ها + انتشار این اتاق در نمایهٔ اتاق‌ها + + نگارش SDK ماتریس + عمومی + ترجیحات + امنیت و محرمانگی + صدا و تصویر + راهنما و درباره + + + ارایهٔ پیشنهاد + لطفاً پیشنهادتان را در زیر بنویسید. + پیشنهادتان را این‌جا شرح دهید + ممنون. پیشنهاد با موفّقیت فرستاده شد + فرستادن پیشنهاد شکست خورد (%s) + + نمایش رویدادهای نهفته در خط زمانی + + ریوت‌اکس - نسل بعدی کارخواه ماتریس + پیام‌های مستقیم + + منتظر… + رمزنگاری بندانگشتی‌ها… + فرستادن بندانگشتی‌ها (%1$s / %2$s) + رمزنگاری پرونده… + فرستادن پرونده (%1$s / %2$s) + + بارگری پرونده %1$s… + پرونده %1$s بارگیری شد! + + (ویراسته) + + پالایش گفت‌وگوها… + ایجاد اتاقی جدید + فرستادن یک پیام مستقیم جدید + نام یا شناسه (‪#example:matrix.org‬) + + به کار انداختن کشیدن برای پاسخ در خط زمانی + + افزودن با شناسهٔ ماتریس + نتیجه‌ای پیدا نشد. برای جست‌وجو روی کارساز، از افزون با شناسهٔ ماتریس استفاده کنید. + پالایش با نام کاربری یا شناسه… + + ایجاد یک گفت‌وگوی مستقیم جدید + ایجاد اتاقی جدید + هرزنامه است + نامناسب است + گزارش سفارشی… + گزارش این محتوا + دلیل گزارش این محتوا + گزارش + انسداد کاربر + + محتوا گزارش شد + به عنوان هرزنامه گزارش شد + به عنوان نامناسب گزارش شد + انسداد کاربر + + تنظیمات پیش‌رفته و سفارشی + + دیدن تمام نشست‌هایم + تنظیمات پیش‌رفته + حالت توسعه‌دهنده + تکان دادن + تکان تشخیص داده شد! + دیگر نشست‌ها + + آگاهی‌ها + اتاق‌های اخیر + دیگر اتاق‌ها + + خط زمانی + + ویرایشگر پیام + + نشست‌های فعّال + نمایش تمامی نشست‌ها + مدیریت نشست‌ها + + %d نشست فعّال + %d نشست فعّال + + + شکست در گرفتن نشست‌ها + نشست‌ها + ابزارهای توسعه diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml index fef747d546..41d8ba1057 100644 --- a/vector/src/main/res/values-fi/strings.xml +++ b/vector/src/main/res/values-fi/strings.xml @@ -43,7 +43,7 @@ Puuttuvien oikeuksien takia osa ominaisuuksista ei ole käytettävissä… Sinulta puuttuu oikeus käynnistää ryhmäpuhelu tässä huoneessa Puhelua ei voitu käynnistää - Laitteen tiedot + Istunnon tiedot Ryhmäpuhelut eivät ole tuettuja salatuissa huoneissa Lähetä silti tai @@ -313,7 +313,7 @@ YLLÄPITÄJÄN TYÖKALUT PUHELUT YKSITYISKESKUSTELUT - LAITTEET + ISTUNNOT Kutsu Poistu huoneesta @@ -327,7 +327,7 @@ Näytä kaikki tämän käyttäjän viestit Käyttäjätunnus, nimi tai sähköpostiosoite Mainitse - Näytä laitelista + Näytä istuntolista Olet ylentämässä käyttäjää samalle tasolle kuin oma käyttäjätasosi. Et voi perua tätä toimintoa.\nOletko varma? Haluatko kutsua käyttäjän %s tähän keskusteluun\? @@ -350,7 +350,7 @@ Lähetä viesti (salaamaton)… Yhteys palvelimeen katkesi. Viestejä ei lähetetty. %1$s vai %2$s\? - Viestejä ei lähetetty koska huoneessa on tuntemattomia laitteita. %1$s vai %2$s\? + Viestejä ei lähetetty, koska läsnä on tuntemattomia istuntoja. %1$s vai %2$s\? Lähetä kaikki uudelleen Peruuta kaikki Lähetä lähettämättömät viestit @@ -443,7 +443,7 @@ Sovelluksen tiedot Ota ilmoitukset käyttöön tällä tilillä - Ota ilmoitukset käyttöön tällä laitteella + Ota ilmoitukset käyttöön tässä istunnossa Näyttö päälle kolmeksi sekunniksi Viestit yksityiskeskusteluissa @@ -482,8 +482,8 @@ Koti Kiinnitä huoneet, joissa on huomaamatta jääneitä ilmoituksia Kiinnitä huoneet, joissa on lukemattomia viestejä - Laitteet - Laitteen tiedot + Istunnot + Istunnon tiedot ID Julkinen nimi Päivitä julkinen nimi @@ -618,11 +618,11 @@ Sessio-ID Salauksenpurkuvirhe - Lähettävän laitteen tiedot + Lähettäjän istunnon tiedot Julkinen nimi Julkinen nimi Tunnus - Laitteen avain + Istunnon avain Vahvistus Ed25519-sormenjälki @@ -647,7 +647,7 @@ Vahvistettu Kielletty - tuntematon laite + tuntematon istunto ei mitään Vahvista @@ -665,7 +665,7 @@ \nVoit tehdä sen nyt tai myöhemmin sovelluksen asetuksissa. - Huoneessa on tuntemattomia laitteita + Huoneessa on tuntemattomia istuntoja Huoneessa on tuntemattomia laitteita joita ei ole vahvistettu.\nLaitteet eivät välttämättä kuulu väitetyille omistajilleen.\nJokainen uusi laite kannattaa vahvistaa ennen kuin jatkat, mutta voit myös lähettää viestit vahvistamattomille laitteille.\n\nTuntemattomat laitteet: @@ -752,7 +752,7 @@ Käytä järjestelmän kamerasovellusta - Lisäsit uuden laitteen \'%s\', joka pyytää salausavaimia. + Lisäsit uuden istunnon \'%s\', joka pyytää salausavaimia. Vahvistamaton laitteesi \'%s\' pyytää salausavaimia. Aloita varmennus Jaa ilman varmennusta @@ -913,12 +913,12 @@ Haluatko lisätä paketteja? jatka sovelluksella… Yhtään ulkopuolista sovellusta tämän toiminnon suorittamiseksi ei löytynyt. - Pyydä salausavaimia uudelleen muilta laitteiltasi. + Pyydä salausavaimia uudelleen muista istunnoistasi. Avainpyyntö lähetetty. Pyyntö lähetetty - Käynnistä Riot toisella laitteela, joka voi purkaa viestin, jotta se voi lähettää avaimet tähän laitteeseen. + Käynnistä Riot toisella laitteella, joka voi purkaa viestin, jotta se voi lähettää avaimet tähän istuntoon. yksi jäsenyysmuutos @@ -1018,9 +1018,9 @@ Haluatko lisätä paketteja? \nTarkista tilisi asetukset. Ota käyttöön - Laitteen asetukset. - Ilmoitukset ovat käytössä tällä laitteella. - Ilmoituksia ei sallita tällä laitteella. + Istunnon asetukset. + Ilmoitukset ovat käytössä tässä istunnossa. + Ilmoitukset eivät ole käytössä tässä istunnossa. \nTarkista Riotin asetukset. Ota käyttöön @@ -1320,8 +1320,8 @@ Jotta et menetä mitään, automaattiset päivitykset kannattaa pitää käytös Varmuuskopio palautettiin %d avaimella. - yksi uusi avain lisätty tähän laitteeseen. - %d uutta avainta lisätty tähän laitteeseen. + Yksi uusi avain lisätty tähän istuntoon. + %d uutta avainta lisätty tähän listuntoon. Uusimman palautusavaimen version hakeminen epäonnistui (%s). @@ -1332,11 +1332,11 @@ Jotta et menetä mitään, automaattiset päivitykset kannattaa pitää käytös Poista varmuuskopio Avaimien varmuuskopiointi on käytössä tällä laitteella. - Avaimien varmuuskopiointi ei ole käytössä tällä laitteella. - Avaimiasi ei varmuuskopioida tältä laitteelta. + Avaimien varmuuskopiointi ei ole käytössä tässä istunnossa. + Avaimiasi ei varmuuskopioida tästä istunnosta. Varmuuskopiossa on allekirjoitus tuntemattomasta laitteesta ID:llä %s. - Varmuuskopiossa on pätevä allekirjoitus tältä laitteelta. + Varmuuskopiossa on pätevä allekirjoitus tästä istunnosta. Varmuuskopiossa on pätevä allekirjoitus varmennetulta laitteelta %s. Varmuuskopiossa on pätevä allekirjoitus varmentamattomalta laitteelta %s Varmuuskopiossa on epäkelpo allekirjoitus varmennetulta laitteelta %s @@ -1451,21 +1451,21 @@ Jotta et menetä mitään, automaattiset päivitykset kannattaa pitää käytös Selvä Pyyntö peruttu - Laite vastaanotti odottamattoman viestin + Istunto vastaanotti odottamattoman viestin Virheellinen viesti vastaanotettu Avain ei täsmää Käyttäjä ei täsmää Tuntematon virhe Kotipalvelimellasi on jo varmuuskopio - Näyttää, että olet jo asettanut avainten varmuuskopioinnin toiselta laitteelta. Halutatko korvata sen tällä\? + Näyttää, että olet jo asettanut avainten varmuuskopioinnin toisesta istunnosta. Halutatko korvata sen tällä\? Korvaa Seis Tarkistetaan varmuuskopion tilaa Odotetaan vastapuolen varmistusta… - Laite ei ole tietoinen kyseisestä transaktiosta + Istunto ei ole tietoinen kyseisestä transaktiosta SAS ei täsmännyt Muokkaa Vastaa @@ -1624,7 +1624,7 @@ Jotta et menetä mitään, automaattiset päivitykset kannattaa pitää käytös %s \nSynkronointia saatetaan lykätä resursseista (akusta) tai laitteen tilasta (virransäästö) riippuen. Julkinen nimi (näkyy ihmisille, joihin olet yhteydessä) - Laitteen julkinen nimi näkyy ihmisille, joihin olet yhteydessä + Istunnon julkinen nimi näkyy ihmisille, joihin olet yhteydessä Jatkaaksesi sinun täytyy hyväksyä palvelun käyttöehdot. Et käytä identiteettipalvelinta @@ -2026,4 +2026,29 @@ Jotta et menetä mitään, automaattiset päivitykset kannattaa pitää käytös \n \n• palvelimen ylläpitäjä on estänyt pääsysi turvallisuussyistä. Kirjaudu sisään palauttaaksesi salausavaimesi, jotka ovat tallessa vain tällä laitteella. Tarvitset niitä lukeaksi kaikki salatut viestisi millä tahansa laitteella. + Näytä kaikki istuntoni + Lisäasetukset + Asetukset + Nykyinen istunto + Muut istunnot + + Ota salaus käyttöön + Salausta ei voi poistaa käytöstä, kun se on kerran otettu käyttöön. + + Odotetaan… + %s peruutti + Sinä peruutit + %s hyväksyi + Sinä hyväksyit + Sinä + + Lue lisää + Ilmoitukset + + Yksi henkilö + %1$d ihmistä + + Poistu huoneesta + Poistutaan huoneesta… + diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 76e2a0d0a7..9366560cb2 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -287,7 +287,7 @@ Activer les notifications pour cette session Messages dans les discussions directes Messages dans les discussions de groupe - Demandes d’appel + Appels entrants Messages envoyés par un robot Synchronisation en arrière-plan @@ -2087,8 +2087,8 @@ Si vous n’avez pas configuré de nouvelle méthode de récupération, un attaq Aller à l’accusé de lecture - RiotX ne gère pas (encore) les évènements de type « %1$s » - RiotX ne gère pas (encore) les messages de type « %1$s » + RiotX ne gère pas les évènements de type « %1$s » + RiotX ne gère pas les messages de type « %1$s » RiotX a rencontré un problème lors de l’affichage du contenu de l’évènement ayant pour identifiant « %1$s » Ne plus ignorer @@ -2152,7 +2152,7 @@ Si vous n’avez pas configuré de nouvelle méthode de récupération, un attaq Les autres utilisateurs ne lui font peut-être pas confiance Compléter la sécurité - Ouvrez une session existante et utilisez-la pour vérifier celle-ci, pour lui permettre d’accéder aux messages chiffrés. Si vous ne pouvez pas le faire, utilisez votre clé de récupération ou votre phrase secrète. + Ouvrez une session existante et utilisez-la pour vérifier celle-ci, pour lui permettre d’accéder aux messages chiffrés. Vérifier @@ -2174,8 +2174,50 @@ Si vous n’avez pas configuré de nouvelle méthode de récupération, un attaq Code QR + L’autre utilisateur a-t-il bien scanné le code QR \? Oui Non La connectivité avec le serveur a été perdue + Nom d’utilisateur + Outils de développement + Données du compte + + %d vote + %d votes + + + %d vote − Résultats finaux + %d votes − Résultats finaux + + Option sélectionnée + Crée un sondage simple + Vous n’avez pas accès à une session existante \? + Utilisez votre clé de récupération ou votre phrase de passe + + Nouvelle connexion + + Impossible de trouver les secrets dans le stockage + Saisir la phrase de passe du coffre secret + Attention : + Vous devriez accéder à votre coffre secret uniquement depuis un appareil de confiance + Accédez à l’historique de vos messages sécurisés et à votre identité de signature croisée pour vérifier d’autres sessions en saisissant votre phrase de passe + + Supprimer… + Voulez-vous envoyer cette pièce jointe à %1$s \? + + Envoyer l’image en taille originale + Envoyer les images en taille originale + + + Confirmer la suppression + Voulez-vous vraiment supprimer cet évènement \? Notez que si vous supprimez un changement de nom ou de sujet du salon, cela pourrait annuler le changement. + Fournir un motif + Motif de la suppression + + Évènement supprimé par l’utilisateur, motif : %1$s + Évènement modéré par l’administrateur du salon, motif : %1$s + + Les clés sont déjà à jour ! + diff --git a/vector/src/main/res/values-fy/strings.xml b/vector/src/main/res/values-fy/strings.xml index 13b3b21468..87e1b59509 100644 --- a/vector/src/main/res/values-fy/strings.xml +++ b/vector/src/main/res/values-fy/strings.xml @@ -78,4 +78,83 @@ Om in gearkomst yn dit groepspetear te starten hasto útnûgingsrjochten nedich Kin de oprop net starte Apparaatynformaasje + Gearkomstpetearen wurde net stipe yn fersifere petearen + Dochs belje + Dochs ferstjoere + of + Utnûgje + Offline + Akseptearje + Oerslaan + Klear + Ofbrekke + Negearje + Beoardielje + Wegerje + + Ofslute + Aksjes + Ofmelde + Binne jo wis dat jo jo ôfmelde wolle\? + Spraakoprop + Fideo-oprop + Globaal sykje + Alles as lêzen markearje + Histoarysk + Flugge reaksje + As lêzen markearje + Iepenje + Slute + Nei klamboerd kopiearre + Utskeakelje + + Befêstiging + Warskôging + Flater + + Start + Favoriten + Minsken + Petearen + Mienskippen + + Petearnammen filterje + Favoriten filterje + Persoanen filterje + Petearnammen filterje + Mienskipsnammen filterje + + Utnûgingen + Lege prioriteit + Systeemmeldingen + + Petearen + Lokale kontaktelist + Brûkerskatalogus + Allinnich Matrix-kontakten + Gjin petearen + Jo hawwe Riot gjin tagong ta jo lokale kontakten jûn + Gjin resultaten + Gjin identiteitsserver konfigurearre. + + Petearen + Petearkatalogus + Gjin petearen + Gjin publike petearen beskikber + + 1 brûker + %d brûkers + + + Utnûgje + Mienskippen + Gjin groepen + + Lochboek ferstjoere + Ungeloklochboek ferstjoere + Skermôfdruk ferstjoere + Flater melde + Beskriuw de flater. Wat hawwe jo dien\? Wat ferwachten jo dat der barre soe\? Wat is der echt bard\? + Beskriuw it probleem yn it Ingelsk, wannear mooglik. + Beskriuw hjir jo probleem diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index c4dde2e079..e04abc02b9 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -1497,7 +1497,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Minden közösség Ennek a szobának nincs előnézete - A RiotX-ben a nyilvános szoba előnézete egyenlőre nem támogatott + A RiotX-ben a nyilvános szoba előnézete egyelőre nem támogatott Szobák Közvetlen üzenetek @@ -1895,7 +1895,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Következő Telefonszám ellenőrzése - Elküldtük a kódot ide: %1$s. Add meg itt alul amivel ellenőrizhetjük, hogy te te vagy. + Elküldtük a kódot ide: %1$s. Add meg itt alul amivel ellenőrizhetjük, hogy te vagy. Kód megadása Küld újra Következő @@ -2082,8 +2082,8 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Olvasási visszaigazolásra ugrás - RiotX (egyenlőre) nem kezeli ezt az eseményt: \'%1$s\' - RiotX (egyenlőre) nem kezeli ezt az üzenet típust: \'%1$s\' + RiotX nem kezeli ezt az eseményt: \'%1$s\' + RiotX nem kezeli ezt az üzenet típust: \'%1$s\' RiotX problémába ütközött az esemény (azon: %1$s) megjelenítésekor Figyelembe vesz @@ -2147,7 +2147,7 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró Más felhasználók lehet, hogy nem bíznak benne Biztonság beállítása - A titkosított üzenetekhez való hozzáféréshez nyiss meg egy létező munkamenetet és használd ennek a hitelesítésére. Ha egyhez sem férsz hozzá használd a visszaállítási kulcsodat vagy jelmondatodat. + A titkosított üzenetekhez való hozzáféréshez nyiss meg egy létező munkamenetet és használd ennek a hitelesítésére. Ellenőriz @@ -2169,8 +2169,50 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró QR kód + A másik felhasználó sikeresen beolvasta a QR kódot\? Igen Nem Megszakadt a kapcsolat a szerverrel + Felhasználónév + Fejlesztői Eszközök + Fiók Adatok + + %d szavazat + %d szavazat + + + Végeredmény - %d szavazat + Végeredmény - %d szavazat + + Kiválasztott Beállítások + Egyszerű szavazás készítése + A létező munkamenet nem érhető el\? + Használd a visszaállítási kulcsot vagy jelmondatot + + Új Bejelentkezés + + A tárolóban nem található jelszó/kulcs + Add meg a jelmondatot a biztonsági tárolóhoz + Figyelem: + Csak biztonságos eszközről férj hozzá a biztonsági tárolóhoz + A jelmondat megadásával hozzáférhetsz a biztonságos üzeneteidhez és az eszközök közötti hitelesítéshez használt személyazonosságodhoz, hogy más munkameneteket hitelesíthess + + Töröl… + Ezt a csatolmányt el szeretnéd küldeni ide: %1$s\? + + Kép küldése eredeti méretben + Képek küldése eredeti méretben + + + Törlés megerősítése + Biztos hogy eltávolítod (törlöd) ezt az eseményt\? Figyelem, ha törlöd vagy megváltoztatod a szoba nevét vagy a témát ez a változtatás érvényét vesztheti. + Ok megadása + Ok a kitakaráshoz + + Az eseményt a felhasználó törölte, ezért: %1$s + Az eseményt a szoba adminisztrátora moderálta, ezért: %1$s + + A kulcsok már frissek! + diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 92b235c5dd..159d691c04 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -38,7 +38,7 @@ Audio Video Impossibile avviare la chiamata, riprova più tardi - Poichè i tuoi permessi non sono sufficienti, alcune funzioni potrebbero non esser disponibili… + Poiché i tuoi permessi non sono sufficienti, alcune funzioni potrebbero non esser disponibili… Non hai permessi sufficienti per avviare una conferenza in questa stanza Impossibile avviare la chiamata Informazioni sulla sessione @@ -268,7 +268,7 @@ \n \nTi sta bene comunicare i dati di tutti i tuoi contatti per questo scopo\?
- Purtroppo l\'azione non è stata eseguita poichè mancano i permessi + Purtroppo l\'azione non è stata eseguita poiché mancano i permessi Salvato @@ -2132,8 +2132,8 @@ Vai alla ricevuta di lettura - RiotX non gestisce eventi del tipo \'%1$s\' (non ancora) - RiotX non gestisce messaggi del tipo \'%1$s\' (non ancora) + RiotX non gestisce eventi del tipo \'%1$s\' + RiotX non gestisce messaggi del tipo \'%1$s\' RiotX ha riscontrato un errore con il rendering del contenuto dell\'evento con id \'%1$s\' Non ignorare @@ -2185,7 +2185,7 @@ Nessuna informazione crittografica disponibile - Questa sessione è fidata per i messaggi sicuri perchè l\'hai verificata: + Questa sessione è fidata per i messaggi sicuri perché l\'hai verificata: Verifica questa sessione per segnarla come fidata e darle l\'accesso ai messaggi cifrati. Se non hai fatto l\'accesso a questa sessione il tuo account potrebbe essere compromesso: @@ -2197,7 +2197,7 @@ Gli altri utenti potrebbero non fidarsi Completa la sicurezza - Apri una sessione esistente e usala per verificare questa, dandole l\'accesso ai messaggi cifrati. Se non riesci ad accedere a nessuna, usa la tua chiave o password di recupero. + Apri una sessione esistente e usala per verificare questa, dandole l\'accesso ai messaggi cifrati. Verifica @@ -2209,7 +2209,7 @@ Fidato Non fidato - Questa sessione è fidata per i messaggi sicuri perchè %1$s (%2$s) l\'ha verificata: + Questa sessione è fidata per i messaggi sicuri perché %1$s (%2$s) l\'ha verificata: %1$s (%2$s) ha fatto l\'accesso con una nuova sessione: Finché questo utente non si fida di questa sessione, i messaggi inviati da e verso di essa sono etichettati con avvisi. In alternativa, puoi verificarlo manualmente. @@ -2219,8 +2219,50 @@ Codice QR + L\'altro utente ha scansionato correttamente il codice QR\? No La connessione al server è stata persa + Nome utente + Strumenti Svil + Dati account + + %d voto + %d voti + + + %d voto - Risultato finale + %d voti - Risultato finale + + Opzione selezionata + Crea un semplice sondaggio + Non puoi accedere ad una sessione esistente\? + Usa la chiave di recupero o la password + + Nuovo accesso + + Impossibile trovare segreti nell\'archivio + Inserisci la password dell\'archivio segreto + Attenzione: + Dovresti accedere all\'archivio segreto solo da un dispositivo fidato + Accedi alla cronologia dei messaggi sicuri e all\'identità di firma incrociata per verificare altre sessioni inserendo la tua password + + Rimuovi… + Vuoi inviare questo allegato a %1$s\? + + Invia immagine nella dimensione originale + Invia immagini nella dimensione originale + + + Conferma rimozione + Sei sicuro di volere rimuovere (eliminare) questo evento\? Nota che se elimini il nome della stanza o cambi l\'argomento, ciò potrebbe annullare la modifica. + Includi un motivo + Motivo della revisione + + Evento eliminato da un utente, motivo: %1$s + Evento moderato da un admin della stanza, motivo: %1$s + + Le chiavi sono già aggiornate! + diff --git a/vector/src/main/res/values-ja/strings.xml b/vector/src/main/res/values-ja/strings.xml index c44c400208..6507c31620 100644 --- a/vector/src/main/res/values-ja/strings.xml +++ b/vector/src/main/res/values-ja/strings.xml @@ -1071,4 +1071,10 @@ Matrixでのメッセージの可視性は電子メールと同様です。メ アルゴリズム 署名 + 通知に関する問題の解決 + システム設定 + アカウント設定 + カスタム設定 + 起動時の実行 + バックグラウンド制限の確認 diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/vector/src/main/res/values-nb-rNO/strings.xml new file mode 100644 index 0000000000..54cac5a112 --- /dev/null +++ b/vector/src/main/res/values-nb-rNO/strings.xml @@ -0,0 +1,726 @@ + + + nb + NO + Latn + + Lyst tema + Mørkt tema + Svart tema + Klargjør tjenesten + Synkroniserer … + Meldinger + Rom + Innstillinger + Medlemsdetaljer + Historisk + Feilrapport + Send et klistremerke + Er du sikker\? + Laster… + + OK + Lukk + Lagre + Forlat + Send + Kopier + Send på nytt + Fjern + Sitering + Nedlastning + Del + Tøm + Senere + Neste + Permalenke + Vis kilden + Slett + Endre navn + Ingen + Tilbakekall + Koble fra + Stemme + Film + eller + Inviter + Frakoblet + Godta + Hopp over + Fullført + Løp vekk + Ignorer + Gjennomgang + Avslå + + Avslutt + Handlinger + Logg ut + Historisk + Lukk + Kopiert til utklippstavle + Slå av + + Advarsel + Feil + + Hjem + Folk + Rom + Samfunn + + Invitasjoner + Lavprioritet + Ingen treff + Rom + Ingen rom + Inviter + Samfunn + Ingen grupper + + Meld fra om en bug + Fremgang (%s%%) + + Les + + Bli med i rommet + Brukernavn + Opprett konto + Logg inn + Logg ut + Identitetstjener-URL + Søk + + Ta et bilde + Spill inn en video + + Logg inn + Opprett konto + Send + Hopp over + Gå tilbake til påloggingsskjermen + Passord + Nytt passord + E-postadresse + Telefonnummer + Ugyldig token + Glemt passord\? + Jeg har verifisert E-postadressen min + Vennligst skriv inn en gyldig URL + Mobil + + Original + Store + Medium + Små + + I går + I dag + + Informasjon + Lagret + JA + NEI + Fortsett + + Fjern + Bli med + Forhåndsvisning + Avvis + + Synkroniserer … + + 1sek + %dsek + + + 1m + %dm + + + 1t + %dt + + + 1d + %dd + + + Lag + + Tilkoblet + Frakoblet + Rolig + Inviter + Utesteng + Opphev utestengelse + Spark ut + %1$s %2$s + + Søk + Filen ble ikke funnet + Stol på + Logg ut + Ignorer + Folk + Filer + Instillinger + BLE MED + + Avbryt opplastning + Avbryt nedlasting + + Ingen treff + ROM + ROM + Demp + Direktesamtale + Glem + Meldinger + Instillinger + Versjon + Versjn %s + Opphavsrettighet + Profilbilde + Visningsnavn + E-post + Telefon + Åpne Innstillinger + + Slå på + + Slå på + + Vanlig + Varslingslyd + sekunder + + Versjon + Opphavsrettighet + Retningslinjer for personvern + Tøm mellomlageret + Varsler + Annet + Avansert + Kryptografi + Lokale kontakter + Sesjoner + Send varsler om at du skriver + Analyser + Send analytiske data + Ja, jeg vil hjelpe til! + + ID + Offentlig navn + Sist sett + Autentisering + Passord: + Send + + Logget inn som + Identitetstjener + Brukergrensesnitt + Språk + Passord + Endre passord + Nåværende passord + Nytt passord + Bekreft nytt passord + Passordene er ikke like + + Land + Telefonnummer + Kode + Media + Velg + Velg + Merkeskilt + 3 dager + 1 uke + 1 måned + For alltid + + Emne + Lavprioritet + Ingen + + Varsler + Alle + Bannlyste brukere + + Avansert + Adresser + Mappe + Tema + + Hendelsesinformasjon + Bruker-ID + Algoritme + Økt-ID + Det offentlige navnet til en økt er synlig for folkene du kommuniserer med + ID + Øktnøkkel + Verifisering + Eksporter + Importer + Svartelistet + + ingen + + Bekreft + Svarteliste + Hjemmetjener-URL + Skriv her … + + Rom + Meg + Skriftstørrelse + Små + Vanlig + Store + Større + Det største + Enorm + + Åpne i nettleser + Din bruker-ID + Ditt tema + Modul-ID + Rom-ID + + + Tillat + Bekreft + Del + Ignorer + + Av + Lag + Eksempel + eksempel + + Hjem + Folk + Ble med + Invitert + Bli med igjen + Glem rommet + + Profilbilde + + Deaktiver kontoen + Deaktiver kontoen + + Vennligst skriv inn et brukernavn. + utvid + skjul + + Alltid + %1$s: + %1$s: %2$s + Aldri mist krypterte beskjeder + Fullført + Del + Lagre som fil + Erstatt + Stopp + + Uventet feil + Er du sikker\? + Aldri mist krypterte beskjeder + Aldri mist krypterte beskjeder + Versjon + Algoritme + Signatur + + Verifisert! + Jeg forstår + + Verifiseringsforespørsel + Ukjent feil + + Rediger + Svar + + Prøv igjen + Invitert av %s + + Reaksjoner + Like + Reaksjoner + + Endre + Vennligst vent … + Nytt rom + Offentlig + Generelt + Brukervalg + Sikkerhet og personvern + Ekspert + Format: + + Stemme og video + Hjelp/Om + + + Vis skjulte hendelser i tidslinjen + + Venter … + (redigert) + + Vilkår for bruk + Bytt ut identitetstjener + Venter + + Opprett et nytt rom + Vis passord + Skjul passord + Fil + Kontakt oss + Kamera + Lyd + Galleri + Demp + Instillinger + Lær mer + Annet + Fortsett + Registrer deg + Logg inn + Fortsett med SSO + + Neste + E-post + Nytt passord + + Fortsett + + Jeg har verifisert E-postadressen min + + Suksess! + Passordet ditt har blitt tilbakestilt. + Advarsel + E-post + E-post (valgfritt) + Neste + + Telefonnummer + Neste + + Skriv inn kode + Neste + + Brukernavn + Passord + Neste + Advarsel + Vennligst sjekk eposten din + Sett av + + Logg på + Logg på + Passord + Instillinger + Gjeldende økt + Føyer til ¯\\_(ツ)_/¯ på en råtekstmelding + + Video. + Bilde. + Lyd + Fil + + Du avbrøt + Du aksepterte + Verifiseringsforespørsel + + + Du + + Verifiser %s + Verifiserte %s + Sikkerhet + Lær mer + Mer + Varsler + Opplastinger + Ordstyrere + Tilpasset + Invitasjoner + Brukere + + Opphev ignorering + + Tidslinje + + Vil du skru på kryptering\? + Bekreft + Advarsel + + Sesjoner + Ja + Advarsel: + Bekreft fjerning + Status.im-tema + + Lytter etter hendelser + Verifiser økten + + Aktiv samtale + Send loggbøker + Send tilbakestillings-E-post + Brukernavnet er i bruk + Hjemmetjener: + Identitetstjener: + Samtaler + + 1 medlem + %d medlemmer + + 1 medlem + + %1$s nå + %s skriver … + Søk + Filtrer rommets medlemmer + + 1 rom + %d rom + + Alle meldinger + Kun nevninger + olm-versjon + Deaktiver kontoen + Riot samler inn anonyme statistikker for å hjelpe oss med å forbedre programmet. + %1$s @ %2$s + Integreringsbehandler + + Rommets navn + Hvem kan lese historikken\? + Hvem kan gå inn i dette rommet\? + + Kun medlemmer (f.o.m. da denne innstillingen ble valgt) + Kun medlemmer (f.o.m. da de ble invitert) + Kun medlemmer (f.o.m. de ble med) + + Kun folk som har blitt invitert + Dette rommet viser ikke merkeskilter for noen samfunn + Kopier rommets ID + Eksporter E2E-romnøkler + Eksporter romnøkler + Importer E2E-romnøkler + Importer romnøkler + Verifisert + Av-verifiser + Verifiser økten + + 1 rom + %d rom + + + + %1$s: 1 melding + %1$s: %2$d meldinger + + + %d varsel + %d varsler + + + Ny hendelse + Nye meldinger + Ny invitasjon + Bitteliten + + 1 aktiv modul + %d aktive moduler + + + + Modul + Last inn modul + Ditt visningsnavn + Din avatars URL + Legg til Matrix-apper + Begynn verifisering + Advarsel! + Kommandofeil + Forlat rommet + Velg rommets tema + Stille + Bråkete + + Kryptert melding + + Opprett et samfunn + Samfunnsnavn + Samfunns-ID + Rom + Ingen brukere + + Rom + + 1 medlem + %d medlemmer + + + + 1 rom + %d rom + + Årsak: %1$s + For å gå videre, vennligst skriv inn passordet ditt: + +%d + %d+ + Begynn å bruke Nøkkelsikkerhetskopiering + Det var meg + Begynn å bruke Nøkkelsikkerhetskopiering + + Sendte deg en invitasjon + Rom + Opprett et nytt rom + Rom + Direktemeldinger + + Rommets navn + Velkommen til betaen! + URL: + Direktemeldinger + + Meldingsredigeringer + Filtrer samtaler … + Opprett et nytt rom + Blir med i rommet … + + Identitetstjener + Skriv inn en ny identitetstjener + Send vedlegg + + Klistremerke + Det er søppelpost + Alle meldinger + Kun nevninger + Forlat rommet + Uleste meldinger + + Kom i gang + + Velg en tjener + Tilbakestill passordet på %1$s + Advarsel! + Velg en E-postadresse + Velg et telefonnummer + Godkjenn vilkårene for å fortsette + + Avanserte innstillinger + Utviklermodus + Dersom dette først har blitt skrudd på, kan kryptering aldri bli skrudd av. + + De samsvarer + Ikke sikker + Venter … + Verifiser denne økten + QR-kodebilde + + Forlat rommet + Forlater rommet … + + Dersom dette først har blitt skrudd på, kan kryptering aldri bli skrudd av. + + Aktive økter + Vis alle økter + Behandle økter + Verifiser denne økten + Verifisert + Betrodd + Ikke betrodd + + QR-kode + + Nei + + Utviklerverktøy + Ny innlogging + + Fjern … + Bråkete notifikasjoner + Stille notifikasjoner + + Samfunnsdetaljer + Sikkerhetskopiering av nøkler + Bruk sikkerhetskopiering av nøkler + nøkkelbackup er ikke fulllført, vennligst vent… + du kommer til å miste dine enkrypterte meldinger hvis du logger ut nå + Sikkerhetskopiering av nøkler pågår. Hvis du logger ut nå mister du tilgang til dine enkrypterte meldinger. + Sikker sikkerhetskopiering av nøkler burde være aktivt på alle øktene dine for å unngå å miste tilgang til dine enkrypterte meldinger. + Jeg vil ikke ha mine enkrypterte meldinger + Sikkerhetskopierer nøkler… + Bruk sikkerhetskopiering av nøkler + Sikkerhetskopi + Du kommer til å miste tilgang til dine enkrypterte meldinger med mindre du sikkerhetskopierer nøklene dine før du logger av. + + Tredjepartslinsenser + + Bli + Snakk + Se dekryptert kilde + Rapporter innhold + Pågående konferansesamtale. +\nBli med som %1$s eller %2$s + Kan ikke starte samtalen, vennligst prøv igjen senere + På grunn av manglende tillatelse, kan noen funksjoner mangle… + Denne handlingen er ikke mulig på grunn av manglende tillatelser. + Du må ha tillatelse til å invitere for å starte en konferanse i dette rommet + Kan ikke starte samtale + Informasjon om Økten + Konferansesamtaler er ikke støttet i enkrypterte rom + Ring likevel. + Send likevel + Er du sikker på at vil logge ut\? + Telefonsamtale + Videosamtale + Globalt søk + Marker alle som lest + Raskt svar + Marker som lest + Åpne + Bekreftelse + Favoritter + Filtrer romnavn + Filtrer favoritter + Filtrer folk + Filtrer romnavn + Filtrer samfunnsnavn + + Systemadvarsler + + Samtaler + Lokal adressebok + Brukerkatalog + Bare matrix-kontakter + Ingen samtaler + Du ga ikke Riot tilgang til dine lokale kontakter + Ingen identitetsserver konfigurert. + + Romkatalog + Ingen offentlige rom tilgjengelig + + 1 bruker + %d brukere + + + Send kjæsjlogg + Send skjermbilde + Vennligst forklar feilen. Hva gjorde du\? Hva forventet du at skulle skje\? Hva skjedde i stedet\? + Om mulig, vennligst beskriv på engelsk. + Forklar problemet ditt her + For å diagnotisere feil, logger fra denne klienten vil bli sendt med denne feilmeldingen. Feilmeldingen, som inkluderer loggene og skjermdumper, blir ikke offentlig synlig. Hvis du foretrekker å bare sende teksten over, vennligst fjern merkene fra boksene: + Det ser ut som du rister på telefonen i frustrasjon. Har du list til å åpne feilrapporteringsskjermen\? + Applikasjonen kræsjet sist gang. Har du lyst til å åpne kræsjskjermen\? + Sinnarist for å rapportere feil + + Feilrapport har blitt sendt + feilrapporten feilet å sendes (%s) + Send inn i + Hjemmetjener URL + Start ny chat + diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml index bacc675f86..eac681e604 100755 --- a/vector/src/main/res/values-nl/strings.xml +++ b/vector/src/main/res/values-nl/strings.xml @@ -40,7 +40,7 @@ Sommige functies zijn misschien afwezig wegens ontbrekende rechten… Om een vergadering in dit groepsgesprek te starten heeft u uitnodigingsrechten nodig Kan de oproep niet starten - Apparaatinformatie + Sessie-informatie Vergadergesprekken worden niet ondersteund in versleutelde gesprekken Toch sturen of @@ -174,9 +174,9 @@ Er moet een nieuw wachtwoord ingevoerd worden. Er is een e-mail verstuurd naar %s. Klik hieronder zodra u de koppeling in de e-mail hebt bezocht. Verifiëren van het e-mailadres is mislukt: zorg dat u op de koppeling in de e-mail hebt geklikt - Uw wachtwoord is opnieuw ingesteld. -\n -\nU bent op alle apparaten afgemeld en zult niet langer pushmeldingen ontvangen. Om meldingen opnieuw in te schakelen, meldt u zich op elk apparaat opnieuw aan. + Uw wachtwoord is opnieuw ingesteld. +\n +\nU bent op alle sessies afgemeld en zult niet langer pushmeldingen ontvangen. Om meldingen opnieuw in te schakelen, meldt u zich op elk apparaat opnieuw aan. URL moet met http[s]:// beginnen @@ -196,8 +196,8 @@ Er is nog niet op de koppeling in de e-mail geklikt - U moet zich opnieuw aanmelden om de sleutels voor eind-tot-eind-versleuteling voor dit apparaat te genereren, en om de publieke sleutel naar uw thuisserver te sturen. -\nDit is eenmalig. + U moet zich opnieuw aanmelden om de sleutels voor eind-tot-eind-versleuteling voor deze sessie te genereren, en om de publieke sleutel naar uw thuisserver te sturen. +\nDit is eenmalig. \nExcuses voor het ongemak. @@ -261,12 +261,11 @@ Riot heeft toegang nodig tot uw camera en microfoon om video-oproepen te maken. \n \nVerleen toegang op de volgende pop-ups om de oproep te maken. - Riot heeft toegang nodig tot uw adresboek om andere Matrix-gebruikers te vinden aan de hand van hun e-mailadressen en telefoonnummers. -\n -\nVerleen toegang op de volgende pop-up om gebruikers op Riot te ontdekken via uw adresboek. - Riot heeft toegang nodig tot uw adresboek om andere Matrix-gebruikers te vinden aan de hand van hun e-mailadressen en telefoonnummers. -\n -\nRiot toegang verlenen tot uw contacten\? + Riot kan uw adresboek benaderen om andere Matrix-gebruikers te vinden aan de hand van hun e-mailadressen en telefoonnummers. +\nAls u het goed vindt om uw adresboek hiervoor te delen, verleen dan toegang op de volgende pop-up. + Riot kan uw adresboek gebruiken om andere Matrix-gebruikers te vinden aan de hand van hun e-mailadressen en telefoonnummers. +\n +\nWilt u uw adresboek hiervoor delen\? Sorry. De actie is niet toegepast vanwege ontbrekende rechten @@ -312,7 +311,7 @@ BEHEERDERSGEREEDSCHAPPEN BELLEN TWEEGESPREKKEN - APPARATEN + SESSIES Uitnodigen Dit gesprek verlaten @@ -326,7 +325,7 @@ Alle berichten van deze gebruiker tonen Gebruikers-ID, naam of e-mailadres Vermelden - Lijst met apparaten weergeven + Sessielijst weergeven U kunt deze veranderingen niet ongedaan maken aangezien u de gebruiker tot hetzelfde niveau als uzelf promoveert. \nWeet u het zeker\? @@ -350,7 +349,7 @@ Verstuur een versleuteld bericht… Verstuur een bericht (niet versleuteld)… Berichten zijn niet verstuurd. Nu %1$s of %2$s\? - Berichten zijn niet verstuurd omdat er onbekende apparaten aanwezig zijn. Nu %1$s of %2$s? + Berichten zijn niet verstuurd omdat er onbekende sessies aanwezig zijn. Nu %1$s of %2$s\? alles opnieuw versturen alles annuleren Onverstuurde berichten opnieuw versturen @@ -442,7 +441,7 @@ App-informatie Meldingen voor deze account inschakelen - Meldingen voor dit apparaat inschakelen + Meldingen voor deze sessie inschakelen Het scherm voor 3 seconden aanzetten Berichten in één-op-één-gesprekken @@ -454,7 +453,7 @@ Synchronisatie in de achtergrond Achtergrondssynchronisatie inschakelen Synchronisatieverzoek is verlopen - Pauze tussen elk verzoek + Pauze tussen elk synchronisatie seconde seconden @@ -481,11 +480,11 @@ Startscherm Gesprekken met gemiste meldingen vastprikken Gesprekken met ongelezen berichten vastprikken - Apparaten - Apparaatinformatie + Sessies + Sessie-informatie ID - Naam - Apparaatnaam + Publieke naam + Publieke naam bijwerken Laatst gezien %1$s @ %2$s @@ -579,8 +578,8 @@ Eind-tot-eind-versleuteling Om de versleuteling in te schakelen dient u zich eerst af te melden. Eind-tot-eind-versleuteling is actief - Alleen naar geverifieerde apparaten versleutelen - Ongeverifieerde apparaten in dit gesprek nooit berichten sturen vanaf dit apparaat. + Alleen naar geverifieerde sessies versleutelen + Ongeverifieerde sessies in dit gesprek nooit berichten sturen vanaf deze sessie. Dit gesprek heeft geen lokale adressen @@ -618,11 +617,11 @@ Sessie-ID Ontsleutelingsfout - Informatie over apparaat van afzender - Apparaatnaam - Naam - Apparaats-ID - Apparaatssleutel + Informatie over sessie van afzender + Publieke naam + Publieke naam + ID + Sessiesleutel Verificatie Ed25519-vingerafdruk @@ -640,14 +639,14 @@ Gesprekssleutels importeren Importeer de sleutels uit een lokaal bestand Importeren - Enkel naar geverifieerde apparaten versleutelen - Versleutelde berichten nooit naar ongeverifieerde apparaten sturen vanaf dit apparaat. + Enkel naar geverifieerde sessies versleutelen + Versleutelde berichten nooit naar ongeverifieerde sessies sturen vanaf deze sessie. NIET geverifieerd Geverifieerd Geblokkeerd - onbekend apparaat + onbekende sessie geen Verifiëren @@ -655,9 +654,9 @@ Blokkeringslijst Deblokkeringslijst - Apparaat verifiëren - Om te verifiëren dat dit apparaat vertrouwd kan worden, contacteert u de eigenaar via een andere methode (bv. persoonlijk of via een telefoontje) en vraagt u hem/haar of de sleutel die hij/zij ziet in zijn/haar Gebruikersinstellingen van dit apparaat overeenkomt met de sleutel hieronder: - Als het overeenkomt, drukt u op de knop ‘Verifiëren’ hieronder. Als het niet overeenkomt, dan onderschept iemand anders het apparaat en zou u het beter blokkeren. In de toekomst zal dit verificatieproces verbeterd worden. + Sessie verifiëren + Om te verifiëren dat deze sessie vertrouwd kan worden, contacteert u de eigenaar via een andere methode (bv. persoonlijk of via een telefoontje) en vraagt u hem/haar of de sleutel die hij/zij ziet in zijn/haar Gebruikersinstellingen van deze sessie overeenkomt met de sleutel hieronder: + Als het overeenkomt, drukt u op de knop ‘Verifiëren’ hieronder. Als het niet overeenkomt, dan onderschept iemand anders deze sessie en zou u het beter blokkeren. In de toekomst zal dit verificatieproces verbeterd worden. Ik verifieer dat de sleutels overeenkomen Riot ondersteunt nu eind-tot-eind-versleuteling, maar u moet zich opnieuw aanmelden om het in te schakelen. @@ -665,12 +664,12 @@ \nU kunt dit nu of later doen vanuit de app-instellingen. - Dit gesprek bevat onbekende apparaten - Dit gesprek bevat onbekende apparaten die niet geverifieerd zijn. -\nDit betekent dat er geen garantie is dat de apparaten bij de gebruikers horen waartoe ze beweren te horen. -\nWe raden u aan om bij elk apparaat door het verificatieprocces heen te gaan voordat u verdergaat, maar u kunt het bericht ook zonder te verifiëren opnieuw versturen. + Dit gesprek bevat onbekende sessies + Dit gesprek bevat onbekende sessies die niet geverifieerd zijn. +\nDit betekent dat er geen garantie is dat de sessies bij de gebruikers horen waartoe ze beweren te horen. +\nWe raden u aan om bij elke sessie door het verificatieprocces heen te gaan voordat u verdergaat, maar u kunt het bericht ook zonder te verifiëren opnieuw versturen. \n -\nOnbekende apparaten: +\nOnbekende sessies: Kies een gesprekscatalogus @@ -757,8 +756,8 @@ Systeemcamera gebruiken - U heeft een nieuw apparaat ‘%s’ toegevoegd, dat versleutelingssleutels aanvraagt. - Uw ongeverifieerde apparaat ‘%s’ vraagt versleutelingssleutels aan. + U heeft een nieuwe sessie ‘%s’ toegevoegd, die versleutelingssleutels aanvraagt. + Uw ongeverifieerde sessie ‘%s’ vraagt versleutelingssleutels aan. Verificatie starten Delen zonder te verifiëren Verzoek negeren @@ -920,7 +919,7 @@ Mijn account deactiveren Meldingsprivacy - Riot kan in de achtergrond werken om uw meldingen veilig en privé te beheren. Dit beïnvloedt mogelijk het accuverbruik. + Riot kan op de achtergrond werken om uw meldingen veilig en privé te beheren. Dit beïnvloedt mogelijk het accuverbruik. Toestemming verlenen Kies een andere optie @@ -948,12 +947,12 @@ Downloaden Inspreken - Beveiligingssleutels van uw apparaten opnieuw aanvragen. + Beveiligingssleutels van uw sessies opnieuw aanvragen. Sleutelaanvraag verstuurd. Aanvraag verstuurd - Start Riot op een ander apparaat dat het bericht kan ontsleutelen, zodat het de sleutels naar dit apparaat kan sturen. + Start Riot op een ander apparaat dat het bericht kan ontsleutelen, zodat het de sleutels naar deze sessie kan sturen. Typ hier… @@ -1091,7 +1090,7 @@ Sleutelback-up is nog niet klaar, even geduld… Indien u zich nu afmeldt, zult u uw versleutelde berichten verliezen Sleutelback-up is bezig. Indien u zich nu afmeldt, zult u de toegang tot uw versleutelde berichten verliezen. - Veilige sleutelback-up dient actief te zijn op al uw apparaten om de toegang tot uw versleutelde berichten niet te verliezen. + Veilige sleutelback-up dient actief te zijn op al uw sessies om de toegang tot uw versleutelde berichten niet te verliezen. Ik wil mijn versleutelde berichten niet Sleutels worden geback-upt… Sleutelback-up gebruiken @@ -1136,9 +1135,9 @@ \nGelieve de accountinstellingen te controleren. Inschakelen - Apparaatinstellingen. - Meldingen zijn ingeschakeld voor dit apparaat. - Meldingen zijn niet toegestaan voor dit apparaat. + Sessie-instellingen. + Meldingen zijn ingeschakeld voor deze sessie. + Meldingen zijn niet ingeschakeld voor deze sessie. \nGelieve de Riot-instellingen te controleren. Inschakelen @@ -1378,14 +1377,14 @@ De back-up kan met deze herstelsleutel niet ontsleuteld worden: controleer of u de juiste herstelsleutel heeft ingevoerd. Back-up hersteld %s! - %1$d sessiesleutels hersteld, en %2$d nieuwe sleutel(s) die dit apparaat nog niet kende toegevoegd + %1$d sessiesleutels hersteld, en %2$d nieuwe sleutel(s) die deze sessie nog niet kende toegevoegd Back-up met %d sleutel hersteld. Back-up met %d sleutels hersteld. - Er is %d nieuwe sleutel toegevoegd aan dit apparaat. - Er zijn %d nieuwe sleutels toegevoegd aan dit apparaat. + Er is %d nieuwe sleutel toegevoegd aan deze sessie. + Er zijn %d nieuwe sleutels toegevoegd aan deze sessie. Verkrijgen van laatste herstelsleutelversie (%s) mislukt. @@ -1395,19 +1394,19 @@ Herstellen uit back-up Back-up verwijderen - Sleutelback-up is correct ingesteld voor dit apparaat. - Sleutelback-up is niet actief op dit apparaat. - Uw sleutels worden niet geback-upt vanaf dit apparaat. + Sleutelback-up is correct ingesteld voor deze sessie. + Sleutelback-up is niet actief op deze sessie. + Uw sleutels worden niet geback-upt vanaf deze sessie. - De back-up heeft een ondertekening van een onbekend apparaat met ID %s. - De back-up heeft een geldige ondertekening van dit apparaat. - De back-up heeft een geldige ondertekening van het geverifieerde apparaat %s. - De back-up heeft een geldige ondertekening van het ongeverifieerde apparaat %s - De back-up heeft een ongeldige ondertekening van het geverifieerde apparaat %s - De back-up heeft een ongeldige ondertekening van het ongeverifieerde apparaat %s + De back-up heeft een ondertekening van een onbekende sessie met ID %s. + De back-up heeft een geldige ondertekening van deze sessie. + De back-up heeft een geldige ondertekening van de geverifieerde sessie %s. + De back-up heeft een geldige ondertekening van de ongeverifieerde sessie %s + De back-up heeft een ongeldige ondertekening van de geverifieerde sessie %s + De back-up heeft een ongeldige ondertekening van de ongeverifieerde sessie %s Verkrijgen van vertrouwensinformatie voor back-up mislukt (%s). - Herstel nu met uw wachtwoord of herstelsleutel om sleutelback-up op dit apparaat te gebruiken. + Herstel nu met uw wachtwoord of herstelsleutel om sleutelback-up op deze sessie te gebruiken. Back-up wordt verwijderd… Verwijderen van back-up is mislukt (%s) @@ -1448,17 +1447,17 @@ Sorry, vergadergesprekken met Jitsi worden nog niet ondersteund op oudere apparaten (met een Android-versie lager dan 5.0) - Apparaat verifiëren + Sessie verifiëren onbekend IP-adres - Een nieuw apparaat vraagt versleutelingssleutels aan. -\nApparaatnaam: %1$s -\nLaatst gezien: %2$s -\nAls u zich niet heeft aangemeld op een ander apparaat, negeer dan dit verzoek. - Een ongeverifieerd apparaat vraagt versleutelingssleutels aan. -\nApparaatnaam: %1$s -\nLaatst gezien: %2$s -\nAls u zich niet heeft aangemeld op een ander apparaat, negeer dan dit verzoek. + Een nieuwe sessie vraagt versleutelingssleutels aan. +\nSessienaam: %1$s +\nLaatst gezien: %2$s +\nAls u zich niet heeft aangemeld op een andere sessie, negeer dan dit verzoek. + Een ongeverifieerde sessie vraagt versleutelingssleutels aan. +\nSessienaam: %1$s +\nLaatst gezien: %2$s +\nAls u zich niet heeft aangemeld op een andere sessie, negeer dan dit verzoek. Verifiëren Delen @@ -1469,18 +1468,18 @@ Voor een maximale beveiliging bevelen we aan om dit onder vier ogen te doen, of via een ander vertrouwd communicatiekanaal. Verificatie beginnen Inkomend verificatieverzoek - Verifieer dit apparaat door het als vertrouwd te markeren. Door de apparaten van uw gesprekspartners te vertrouwen, hoeft u zich nog minder zorgen te maken over het gebruik van eind-tot-eind-versleutelde berichten. - Dit apparaat verifiëren zal het als vertrouwd markeren, en het ook aan uw gesprekspartner als vertrouwd markeren. + Verifieer de sessie door deze als vertrouwd te markeren. Door de sessie van uw gesprekspartners te vertrouwen, hoeft u zich nog minder zorgen te maken over het gebruik van eind-tot-eind-versleutelde berichten. + De sessie verifiëren zal deze als vertrouwd markeren, en deze ook aan uw gesprekspartner als vertrouwd markeren. - Verifieer dit apparaat door te bevestigen dat de volgende emoticons op het scherm van uw gesprekspartner verschijnen - Verifieer dit apparaat door te bevestigen dat de volgende cijfers op het scherm van uw gesprekspartner verschijnen + Verifieer deze sessie door te bevestigen dat de volgende emoticons op het scherm van uw gesprekspartner verschijnen + Verifieer deze sessie door te bevestigen dat de volgende cijfers op het scherm van uw gesprekspartner verschijnen U heeft een inkomend verificatieverzoek ontvangen. Verzoek bekijken Wachten op bevestiging van gesprekspartner… Geverifieerd! - U heeft het apparaat geverifieerd. + U heeft de sessie geverifieerd. Beveiligde berichten met deze gebruiker worden eind-tot-eind-versleuteld en kunnen niet door derde partijen gelezen worden. Ik snap het @@ -1494,17 +1493,17 @@ De verificatie is geannuleerd. \nReden: %s - Interactieve apparaatsverificatie + Interactieve sessieverificatie Verificatieverzoek - %s wil uw apparaat verifiëren + %s wil uw sessie verifiëren De gebruiker heeft de verificatie geannuleerd Het verificatieproces is verlopen - Het apparaat heeft geen weet van die transactie - Het apparaat kan geen sleutelovereenkomst-, hash-, MAC- of SAS-methode kiezen + De sessie heeft geen weet van die transactie + De sessie kan geen sleutelovereenkomst-, hash-, MAC- of SAS-methode kiezen De hashovereenkomst kwam niet overeen De SAS kwam niet overeen - Het apparaat heeft een onverwacht bericht ontvangen + De sessie heeft een onverwacht bericht ontvangen Er is een ongeldig bericht ontvangen Sleutels komen niet overeen Gebruikers komen niet overeen @@ -1519,4 +1518,91 @@ Geen identiteitsserver geconfigureerd. Oproep mislukt door verkeerd geconfigureerde server + Vraag de beheerder van uw thuisserver (%1$s) om een TURN-server te configureren om oproepen betrouwbaar te doen werken. +\n +\nAls alternatief kunt u de publieke server op %2$s gebruiken. Dit is minder betrouwbaar en zal tevens uw IP-addres delen met die server. U kunt dit ook configureren in de Instellingen. + Probeer %s te gebruiken + Vraag het niet opnieuw + + Kies een e-mailadres om te gebruiken voor accountherstel. Later kunt u ervoor kiezen om mensen u te laten vinden via uw e-mailadres. + Kies een telefoonnummer. Later kunt u ervoor kiezen om mensen u te laten vinden via dit nummer. + Kies een e-mailadres om te gebruiken voor accountherstel. Later kunt u ervoor kiezen om mensen u te laten vinden via uw e-mailadres of telefoonnummer. + Kies een e-mailadres om te gebruiken voor accountherstel. Later kunt u ervoor kiezen om mensen u te laten vinden via uw e-mailadres of telefoonnummer. + Dit is geen geldig Matrix-serveradres + Kan geen verbinding maken met een thuisserver op deze URL, controleer de URL + Zal %s gebruiken om te assisteren in het geval dat uw thuisserver er niet over beschikt (uw IP-adres zal tijdens een oproep gedeeld worden) + Voeg een identiteitsserver toe in de instellingen om dit te doen. + Bevestig uw wachtwoord + U kunt dit niet doen vanaf de mobiele Riot + Synchroniseren op de achtergrond (experimenteel) + Geoptimaliseerd voor batterij + Riot zal op een batterijzuinige manier synchroniseren op de achtergrond. +\nAfhankelijk van de staat van uw apparaat kan het besturingssysteem de synchronisatie uitstellen. + Geoptimaliseerd voor snelheid + Riot zal periodiek op de achtergrond synchroniseren (configureerbaar). +\nDit heeft een negatieve impact op uw batterij- en datagebruik. Er zal een melding getoond worden ter informatie. + Geen achtergrondssynchronisatie + U zal geen melding van berichten ontvangen als de app zich in de achtergrond bevindt. + Kon de instellingen niet bijwerken. + + + Voorkeur voor synchronisatie-interval + %s +\nDe synchronisatie is mogelijk uitgesteld als gevolg van de batterij of staat van uw apparaat (slaapmodus). + Integraties + Latn + + Terugvaloproepassistentieserver toestaan + Authenticatie vereist + + + Gebruik een integratiebeheerder om bots, bruggen, widgets en stickerpakketten te beheren. +\nIntegratiebeheerders ontvangen configuratiedata en kunnen widgets aanpassen, gespreksuitnodigingen versturen en bestuursniveaus instellen namens u. + Ontdekken + Beheer uw ontdekinstellingen. + Integraties toestaan + Integratiebeheerder + + Publieke naam (zichtbaar voor mensen met wie u communiceert) + De publieke naam van een sessie is zichtbaar voor mensen met wie u communiceert + Widget + Widget laden + Deze widget is toegevoegd door: + Dit gebruiken kan cookies toevoegen en gegevens delen met %s: + Dit gebruiken kan gegevens delen met %s: + Kon widget niet laden. +\n%s + Widget herladen + Openen in browser + Toegang intrekken voor mij + + Uw weergavenaam + Uw profielfoto-URL + Uw gebruikers-ID + Uw thema + Widget-ID + Gespreks-ID + + + Deze widget wil gebruik maken van de volgende bronnen: + Toestaan + Alles blokkeren + Camera gebruiken + Microfoon gebruiken + DRM-beschermde media lezen + + Geen integratiebeheerder ingesteld. + Om verder te gaan dient u de dienstvoorwaarden te aanvaarden. + + Er bestaat al een back-up op uw thuisserver + Het lijkt erop dat u al een back-up van uw herstelsleutel heeft uit een andere sessie. Wilt u deze vervangen door degene die u nu aanmaakt\? + Vervangen + Stoppen + + Back-upstatus wordt gecontroleerd + U bent afgemeld vanwege onjuiste of verlopen gebruikersreferenties. + + U gebruikt geen identiteitsserver + Er is geen identiteitsserver geconfigureerd. Dit is vereist om uw wachtwoord opnieuw in te stellen. + diff --git a/vector/src/main/res/values-nn/strings.xml b/vector/src/main/res/values-nn/strings.xml index 44755a1ef0..aad1451984 100644 --- a/vector/src/main/res/values-nn/strings.xml +++ b/vector/src/main/res/values-nn/strings.xml @@ -210,7 +210,7 @@ Fekk ikkje til å stadfesta e-postadressa: sjå til at du klikka på lenken i e-posten Passordet ditt vart nullstilt. Du vart logga ut av alle sesjonar og får ikkje push-varsel lenger. For å skru varsel på att, logg inn att på kvar eining. - URL-en må byrja på http[s]:// + URL-en må starta på http[s]:// Fekk ikkje til å logga inn: Nettverksfeil Fekk ikkje til å logga inn Fekk ikkje til å registrere: Nettverksfeil @@ -433,7 +433,7 @@ Søk - Filtrer rommedlemer + Filtrer rommedlemmar Ingi treff ROM MELDINGAR @@ -446,13 +446,13 @@ ROM LÅGRETT INNBJODINGAR - Byrja samtala + Start samtale Laga eit rom Vert med i romet Vert med i eit rom Skriv inn ein rom-ID eller eit romalias - Sjå gjenom utvalet + Bla gjennom katalog 1 rom %d rom @@ -511,10 +511,10 @@ Meldingar i ein-og-ein-samtalar Meldingar i gruppesamtalar Når eg blir invitert til eit rom - Røystsamtalainnbjodingar + Invitasjon til anrop Meldingar frå botar - Byrja ved uppstarten + Køyr ved oppstart Bakgrunnsamstilling Skru på bakgrunnsamstilling Samstillingsfyrespurnaden fekk tidsavbrot @@ -523,7 +523,7 @@ sekund Versjon - olm-versjon + olm versjon Vilkår for bruk Informasjon frå tredjepart Opphavsrett @@ -588,7 +588,7 @@ For å gå fram, ver venleg og skriv passordet ditt inn. Språk Vel språk - Ventar på Godkjenning + Ventar på verifikasjon Ver venleg og sjekk eposten din og klikk på lenkja han inneheld. Når det er gjort, klikk gå fram. Fekk ikkje til å stadfesta e-postadressa. Ver venleg og sjekk e-posten din og klikk på lenkja han inneheld. Når det er gjort, klikk gå fram E-postadressa er allereie i bruk @@ -620,8 +620,8 @@ For å gå fram, ver venleg og skriv passordet ditt inn.
Kode - Særpreg - Du er førebels ikkje med i nokre samfunn. + Etikett + Du er førebels ikkje med i nokre fellesskap. 3 dagar 1 veke @@ -639,7 +639,7 @@ For å gå fram, ver venleg og skriv passordet ditt inn.
Ingen Tilgang og synlegheit - Vis rommet i romutvalet + Vis dette rommet i romkatalogen Varsel Romtilgang Romhistoria si lesbarheit @@ -647,9 +647,9 @@ For å gå fram, ver venleg og skriv passordet ditt inn.
Kven kan koma inn i rommet? Kven som helst - Berre medlemer (frå då innstillinga er vald) - Berre medlemer (frå då dei vart bodne inn) - Berre medlemer (frå då dei kom inn) + Berre medlemmar (frå då innstillinga er vald) + Berre medlemmar (frå då dei vart bodne inn) + Berre medlemmar (frå då dei kom inn) For å lenkja eit rom må det ha ei adresse. Berre folk som har vorte bodne inn @@ -658,7 +658,7 @@ For å gå fram, ver venleg og skriv passordet ditt inn.
Utestengde brukarar - Omfattande + Avansert Rommet sin interne ID Adresser Eksperimentelle funksjonar @@ -672,7 +672,7 @@ For å gå fram, ver venleg og skriv passordet ditt inn.
Dette rommet har ingen lokale adresser Ny adresse (t.d. #foo:matrix.org) - Dette rommet viser ikkje særpreg for nokre samfunn + Dette rommet viser ikkje etikettar for nokre fellesskap Ny fellesskaps-ID (t.d. +foo:matrix.org) Ugyldig fellesskaps-ID \'%s\' er ikkje ein gyldig fellesskaps-ID @@ -696,7 +696,7 @@ For å gå fram, ver venleg og skriv passordet ditt inn.
Utval Preg - %s prøvde å lasta eit spesifikt punkt i rommet si tidslinje men klarte ikkje å finna det. + %s prøvde å lasta eit spesifikt punkt i rommet sin historikk men klarte ikkje å finna det. Ende-til-ende-krypteringsinfo @@ -713,7 +713,7 @@ For å gå fram, ver venleg og skriv passordet ditt inn. Offentleg namn ID Sesjonsnøkkel - Godkjenning + Verifikasjon Ed25519-fingeravtrykk Eksporter E2E-romnøkklar @@ -739,13 +739,13 @@ For å gå fram, ver venleg og skriv passordet ditt inn. ingen Godkjenn - Fjern godkjenning + Fjern verifikasjon Set på svartelista Fjern frå svartelista Verifiser sesjonen For å godkjenna at denne sesjonen er til å stola på, ver venleg og snakk med eigaren på ein anna måte (t.d. ansikt til ansikt eller på telefon) og spør han om nøkkelen han ser i Brukarinnstillingane for denne sesjonen samsvarar med nøkkelen under: - Viss det samsvarer, klikk Verifiser-knappen under. Viss det ikkje gjer det, avlyttar nokon andre denne sesjonen og du bør sannsynlegvis svarteliste den. I framtida vil denne godkjenningsprosessen bli meir forbetra. + Viss det samsvarer, klikk Verifiser-knappen under. Viss det ikkje gjer det, avlyttar nokon andre denne sesjonen og du bør sannsynlegvis svarteliste den. I framtida vil denne verifikasjonsprosessen bli meir forbetra. Eg stadfestar at nøkklane er like Riot støttar no ende-til-ende-kryptering men du må logga inn att for å skru det på. Du kan gjera det no eller seinare i App-innstillingane. @@ -755,7 +755,7 @@ For å gå fram, ver venleg og skriv passordet ditt inn. \n \nUkjende sesjonar: - Vel eit romutval + Vel ein romkatalog Tenaren er kanskje utilgjengeleg eller overlasta Skriv ein heimtenar inn for å få henta opp offentlege rom frå Heimtenar-URL @@ -800,7 +800,7 @@ For å gå fram, ver venleg og skriv passordet ditt inn. Klarte ikkje å laga widget. Fekk ikkje til å senda førespurnad. - Makthøgda må vere eit positivt heiltal. + Tilgangsnivået må vere eit positivt heiltal. Du er ikkje i dette rommet. Du har ikkje tillating til å gjera det i dette rommet. Vantande room_id i førespurnaden. @@ -814,8 +814,8 @@ For å gå fram, ver venleg og skriv passordet ditt inn. Du la til den nye sesjonen \'%s\', som etterspør krypteringsnøkklar. Den ikkje-verifiserte sesjonen din \'%s\' etterspør krypteringsnøkklar. - Byrj godkjenning - Del utan godkjenning + Start verifikasjon + Del utan verifikasjon Oversjå førespurnaden Åtvaring! @@ -826,11 +826,11 @@ For å gå fram, ver venleg og skriv passordet ditt inn. Viser handling Stengjer brukarar med den gjevne IDen ute Slepp utestengde brukarar med den gjevne IDen inn at - Set makthøgda på ein brukar + Definer tilgangsnivå for ein brukar AvOPar brukarar med den gjevne IDen Byd brukarar med den gjevne IDen inn til det noverande rommet Vert med i rommet med det gjevne aliaset - Fer frå rommet + Forlat rom Set romemnet Sparkar brukarar med gjeven ID Forandrar visingsnamnet ditt @@ -857,7 +857,7 @@ For å gå fram, ver venleg og skriv passordet ditt inn. Rom Komne inn Bodne inn - Filtrer gruppemedlemer + Filtrer gruppemedlemmar Filtrer grupperom Fellesskapssadministratoren har ikkje satt ei lang skildring for dette fellesskapet. @@ -1063,11 +1063,79 @@ Meldingssynlegheit på Matrix liknar på epost. At vi gløymer meldingane dine t Bruk ein integrasjonshandterar (Integration Manager) for å handtere botar, bruker, tillegg og klistermerkepakker. \nIntegrasjonshandterarar hentar konfigurasjonsdata, kan endre tillegg, sende rominvitasjonar og sette tilgangsnivå på vegne av deg. Handtering av kryptografiske nøkklar - Dette valet krev ein tredjepartsapplikasjon for å registrere meldingane. + Dette valet krev eit tredjepartsprogram for å registrere meldingane. Last rom-medlemmar etter behov (lazy-load) Heimetenaren din støttar ikkje såklalla lazy-loading av rommelemar endå. Prøv igjen seinare. Sikre meldingar med denne brukaren er ende-til-ende kryptert, dei kan ikkje bli lesne av tredje partar. Anna informasjon frå tredjepart Meldingar med denne brukaren er ende-til-ende kryptert, dei kan ikkje bli lesne av tredje part. + Konfiguer bråkete-varsel + Konfiguer anrops-varsel + Konfiguer stille-varsel + Konfiguer LED-farge, vibrasjon eller lyd… + + + Vis bli-med/forlat hendelsar + Invitasjonar, utkastingar og utestengingar gjeld ikkje dette. + Opna i nettlesar + Program for nettprat, under din kontroll og med full fleksibilitet. Riot lar deg kommunisere på den måten du vil. Implementert for [matrix] - protokollen for open, desentralisert kommunikasjon. +\n +\nOpprett ein gratis matrix.org-konto, sett opp eigen server på https://modular.im, eller ta i bruk ein annan Matrix-server. +\n +\nKvifor velje Riot.im\? +\n +\n• KOMPLETT KOMMUNIKASJON: Bygg rom rund teamet, venner, fellesskapet - du bestemmer! Send direktemeldingar, del filer, legg til tillegg, start taleanrop og videokonferansar! Alt saman er tilgjengeleg utan kostnad. +\n +\n• KRAFTIGE INTEGRASJONAR: Bruk Riot.im saman med verktøya du brukar frå før. Med Riot.Im kan du kommunisere med brukarar på andre plattformar. +\n +\n• PRIVAT OG SIKKER: Hald dine samtalar hemmelege. Industristandard ende-til-ende-kryptering sørgjer for at private samtalar forblir private. +\n +\n• OPEN, IKKJE LUKKA: Open kjeldekode, på toppen av Matrix-protokollen. Eig dine eigne data ved å drifte eigen server eller hjå nokon du stolar på. +\n +\n• OVERALT DER DU ER: Hald kontakten uansett kvar du er; fullstendig synkronisert meldingshistorikk på alle klientar, samt online på https://riot.im. + + Hentar sikkerheitskopiversjon… + Versjon + Matrix SDK versjon + Innstillingar + Sikkerheit og personvern + Tale & Video + Hjelp & Om + + + Vis skjulte hendelsar i historikken + + Aktiver swipe-for-å-svare i historikken + + Forlat rommet + Uleste meldingar + + Denne heime-tenaren køyrer ein gammal versjon, du kan derfor ikkje kopla til. Be administrator om å oppgradere. + + Etter aktivering, så kan ikkje kryptering bli deaktivert. + + Sikkerheit + Få meir info + Rominnstillingar + Forlat rom + Administratorar + Moderatorar + Tilpassa + Invitasjonar + Brukarar + + Administrator i %1$s + Moderator i %1$s + Tilpassa (%1$d) i %2$s + + Denne økta klarde ikkje å dele denne verifikasjonen med dine andre økter. +\nVerifikasjonsdata er lagra lokalt, og vil bli delt i ein framtidig versjon av programmet. + + Historikk + + Etter aktivering, så kan ikkje kryptering bli deaktivert. + + Fullstendig sikkerheit + diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml index 37e402911e..2deab4a7e5 100644 --- a/vector/src/main/res/values-pl/strings.xml +++ b/vector/src/main/res/values-pl/strings.xml @@ -33,7 +33,7 @@ Ze względu na brak pewnych uprawnień, niektóre funkcje mogą nie działać… Musisz być uprawniony, aby rozpocząć połączenie grupowe Nie można rozpocząć połączenia - Informacje o urządzeniu + Informacje o sesji Połączenia grupowe nie są obsługiwane w szyfrowanych pokojach Wyślij mimo wszystko i @@ -84,7 +84,6 @@ 1 użytkownik %d użytkowników %d użytkowników - Wyślij dzienniki @@ -251,7 +250,7 @@ Przyznaj dostęp w następnym oknie. NARZĘDZIA ADMINISTRACYJNE DZWOŃ WIADOMOŚCI BEZPOŚREDNIE - URZĄDZENIA + SESJE Zaproś Opuść pokój @@ -265,7 +264,7 @@ Przyznaj dostęp w następnym oknie. Pokaż wszystkie wiadomości od tego użytkownika ID, nazwa lub e-mail użytkownika Wspomnij - Pokaż listę urządzeń + Pokaż listę sesji Czy na pewno chcesz zaprosić %s do tej rozmowy? Zaproś przez ID @@ -357,7 +356,7 @@ Zauważ, że ta czynność spowoduje ponowne uruchomienie aplikacji i może to t Informacje o aplikacji Włącz powiadomienia dla tego konta - Włącz powiadomienia dla tego użytkownika + Włącz powiadomienia dla tej sesji Podświetl ekran na 3 sekundy Wiadomości bezpośrednie @@ -388,14 +387,14 @@ Zauważ, że ta czynność spowoduje ponowne uruchomienie aplikacji i może to t Uprawnienie do dostępu do kontaktów Przypnij pokoje z ominiętymi powiadomieniami Przypnij pokoje z nieprzeczytanych wiadomościami - Urządzenia + Sesje Pokaż czas wysłania dla wszystkich wiadomości Tryb oszczędzania danych - Szczegóły o urządzeniu + Szczegóły o sesji ID - Nazwa - Nazwa urządzenia + Nazwa publiczna + Zaaktualizuj nazwę publiczną Ostatnio widziany(-a) %1$s @ %2$s To działanie wymaga dodatkowego uwierzytelnienia. @@ -480,9 +479,9 @@ Zauważ, że ta czynność spowoduje ponowne uruchomienie aplikacji i może to t ID użytkownika Algorytm ID sesji - Nazwa urządzenia - Nazwa - ID urządzenia + Nazwa publiczna + Nazwa publiczna + ID Weryfikacja Odcisk palca Ed25519 @@ -493,17 +492,17 @@ Zauważ, że ta czynność spowoduje ponowne uruchomienie aplikacji i może to t Zweryfikowano Na czarnej liście - nieznane urządzenie + nieznana sesja brak Weryfikuj Dodaj na czarną listę Usuń z czarnej listy - Weryfikuj urządzenie + Weryfikuj sesję Te klucze są ze sobą zgodne - Pokój zawiera nieznane urządzenie + Pokój zawiera nieznane sesje Wybierz katalog pokojów Serwer może być wyłączony lub przeciążony Adres serwera domowego @@ -575,17 +574,13 @@ Zauważ, że ta czynność spowoduje ponowne uruchomienie aplikacji i może to t Możesz dodać adres e-mail do swojego profilu w ustawieniach. Serwer Domowy: Serwer Tożsamości: - Twoje hasło zostało zresetowane. - -Zostałeś wylogowany ze wszystkich urządzeń i nie będziesz więcej otrzymywać powiadomień push. Aby ponownie włączyć powiadomienia, zaloguj się ponownie na każdym urządzeniu. + Twoje hasło zostało zresetowane. Zostałeś wylogowany ze wszystkich sesji i nie będziesz więcej otrzymywać powiadomień push. Aby ponownie włączyć powiadomienia, zaloguj się ponownie na każdym urządzeniu. Numer telefonu Wprowadzony token dostępu nie został rozpoznany Uszkodzony JSON - Należy się ponownie zalogować w celu wygenerowania kluczy szyfrowania end-to-end dla tego urządzenia i wysłania klucza publicznego do Twojego serwera domowego. -Jest to jednorazowe działanie. -Przepraszamy za trudności. + Należy się ponownie zalogować w celu wygenerowania kluczy szyfrowania end-to-end dla tej sesji i wysłania klucza publicznego do Twojego serwera domowego. Jest to jednorazowe działanie. Przepraszamy za trudności. Lista grup @@ -638,7 +633,7 @@ Jesteś pewien? połączenie odebrane gdzie indziej Wiadomość nie została wysłana. Czy %1$s lub %2$s teraz? - Wiadomość niewysłana z powodu obecności nieznanych urządzeń. Czy %1$s lub %2$s teraz? + Wiadomość niewysłana z powodu obecności nieznanych sesji. Czy %1$s lub %2$s teraz\? Nowa wiadomość Kilka nowych wiadomości @@ -677,7 +672,7 @@ Jesteś pewien? Wiadomości zawierające moją wyświetlaną nazwę Wiadomości zawierające moją nazwę użytkownika Żądanie synchronizacji nieudane z powodu przekroczenia limitu czasu - Opóźnienie między każdym żądaniem + Opóźnienie między każdą synchronizacją Wersja olm Informacje o stronach trzecich Zatrzymaj media @@ -686,7 +681,7 @@ Jesteś pewien? Ekran domowy Podgląd zawartości URL Pokaż czas w formacie 12-godzinnym - Wibruj gdy ktoś wspomni o tobie + Wibruj gdy ktoś wspomni o Tobie Analityka @@ -711,8 +706,8 @@ Jesteś pewien? Każdy kto zna link pokoju, razem z gośćmi Musisz się wylogować aby uruchomić szyfrowanie. - Szyfruj wiadomości tylko do zaufanych urządzeń - Nigdy nie wysyłaj zaszyfrowanych wiadomości do niezweryfikowanych urządzeń w tym pokoju z tego urządzenia. + Szyfruj wiadomości tylko do zaufanych sesji + Nigdy nie wysyłaj zaszyfrowanych wiadomości do niezweryfikowanych sesjiw tym pokoju z tego urządzenia. Ten pokój nie ma adresu lokalnego Nowe ID społęczności (np. +foo:matrix.org) @@ -728,7 +723,7 @@ Jesteś pewien? NIe ustawiaj jako główny adres Błąd odszyfrowywania - Klucz urządzenia + Klucz sesji Wyeksportuj klucze pokoju Eksportuj klucze do pliku lokalnego Dołącz ponownie @@ -737,12 +732,12 @@ Jesteś pewien? Pobierz Wyczyść Wyślij naklejkę - Poproś ponownie o klucze szyfrujące z innych Twoich urządzeń. + Poproś ponownie o klucze szyfrujące z innych Twoich sesji. Prośba o klucz wysłana. Prośba wysłana - Uruchom proszę Riot na innym urządzeniu, które może odszyfrować wiadomość, aby wysłać klucze do tego urządzenia. + Uruchom proszę Riot na innym urządzeniu, które może odszyfrować wiadomość, aby wysłać klucze do tej sesji. Prywatność powiadomień Standardowa @@ -777,7 +772,7 @@ Jesteś pewien? Informacje o szyfrowaniu end-to-end Informacje zdarzenia - Informacja o urządzeniu nadawcy + Informacja o sesji nadawcy Eksportuj klucze E2E pokoju Klucze pokoju zostały zapisane w \'%s\' @@ -787,21 +782,17 @@ Uwaga: ten plik może zostać usunięty, jeśli aplikacja zostanie odinstalowana Importuj klucze pokoju Importuj klucze z lokalnego pliku Importuj - Szyfruj wiadomości tylko do zaufanych urządzeń - Nigdy nie wysyłaj zaszyfrowanych wiadomości do niezweryfikowanych urządzeń w tym pokoju z tego urządzenia. + Szyfruj wiadomości tylko do zaufanych sesji + Nigdy nie wysyłaj zaszyfrowanych wiadomości do niezweryfikowanych sesji w tym pokoju z tej sesji. Usuń weryfikację - Aby sprawdzić czy to urządzenie jest zaufane, skontaktuj się z jego właścicielem używając innych form (np. osobiście lub telefonicznie) i zapytaj ich czy klucz, który widzą w ustawieniach użytkownika dla tego urządzenia pasuje do klucza poniżej: - Jeśli klucz pasuje, potwierdź to przyciskiem poniżej. Jeśli nie, to ktoś inny najprawdopodobniej przejmuje lub podszywa się pod to urządzenie i powinieneś dodać urządzenie do czarnej listy. W przyszłości proces weryfikacji będzie bardziej skomplikowany. + Aby sprawdzić czy ta sesja jest zaufana, skontaktuj się z jej właścicielem używając innych form (np. osobiście lub telefonicznie) i zapytaj czy klucz, który widzą w ustawieniach użytkownika dla tego urządzenia pasuje do klucza poniżej: + Jeśli klucz pasuje, potwierdź to przyciskiem poniżej. Jeśli nie, to ktoś inny najprawdopodobniej przejmuje lub podszywa się pod tą sesję i powinieneś dodać tę sesję do czarnej listy. W przyszłości proces weryfikacji będzie bardziej skomplikowany. Riot obsługuje już szyfrowanie end-to-end (E2E), ale musisz zalogować się ponownie, aby je włączyć. Możesz to zrobić teraz lub później z poziomu ustawień aplikacji. - Ten pokój zawiera nieznane urządzenia, które nie zostały zweryfikowane. -Oznacza to brak gwarancji, że urządzenia należą do użytkowników do których twierdzą, że należą. -Przed kontynuowaniem, zalecamy wykonanie procesu weryfikacji każdego urządzenia, ale możesz ponownie wysłać wiadomość bez weryfikacji, jeśli wolisz. - -Nieznane urządzenia: + Ten pokój zawiera nieznane sesje, które nie zostały zweryfikowane. Oznacza to brak gwarancji, że sesje należą do użytkowników do których twierdzą, że należą. Przed kontynuowaniem, zalecamy wykonanie procesu weryfikacji każdego urządzenia, ale możesz ponownie wysłać wiadomość bez weryfikacji, jeśli wolisz. Nieznane sesje: Wyślij naklejkę @@ -862,7 +853,7 @@ Czy chcesz dodać teraz kilka? Poziom uprawnień musi być liczbą dodatnią. Nie jesteś w tym pokoju. Nie masz uprawnień, aby zrobić to w tym pokoju. - Nastrój + Wyróżnik społeczności Ten pokój nie wyświetla wyróżników dla żadnych społeczności Brakujące room_id w żądaniu. @@ -872,8 +863,8 @@ Czy chcesz dodać teraz kilka? Dodaj aplikacje Matrix Wyślij wiadomości głosowe - Dodałeś(-aś) nowe urządzenie \'%s\', które żąda kluczy szyfrujących. - Twoje niezweryfikowane urządzenie \'%s\' żąda kluczy szyfrujących. + Dodałeś(-aś) nową sesję \'%s\', która żąda kluczy szyfrujących. + Twoje niezweryfikowana sesja \'%s\' żąda kluczy szyfrujących. Rozpocznij weryfikację Współdziel bez weryfikacji Zignoruj żądanie @@ -932,8 +923,7 @@ Widoczność wiadomości w Matrix jest podobna do wiadomości e-mail. Nasze zapo 1 członek %d członków - %d członków - + @@ -955,26 +945,22 @@ Widoczność wiadomości w Matrix jest podobna do wiadomości e-mail. Nasze zapo 1 sekunda %d sek. - %d sek. - + 1 minuta %d min. - %d min. - + 1 godzina %s godz. - %s godz. - + 1 dzień %d dni - %d dni - + Pokazać wszystkie wiadomości od tego użytkownika? @@ -1052,10 +1038,9 @@ Pamiętaj ta akcja może zresetować aplikacje i potrwać jakiś czas. Połączenia Wyrzuć - Czy chcesz wyrzucić tego użytkownika z rozmowy? - Czy chcesz wyrzucić tych użytkowników z rozmowy? - Czy chcesz wyrzucić tych użytkowników z rozmowy? - + Czy chcesz wyrzucić tego użytkownika z rozmowy\? + Czy chcesz wyrzucić tych użytkowników z rozmowy\? + Formatowanie Markdown Pokaż zdarzenia dołączenia i wyjścia @@ -1086,8 +1071,8 @@ Pamiętaj ta akcja może zresetować aplikacje i potrwać jakiś czas. Powiadomienia są właczone dla twojego konta. Włącz - Ustawienia Urządzenia. - Powiadomienia są włączone dla tego urządzenia. + Ustawienia Sesji. + Powiadomienia są włączone dla tej sesji. Włącz Token Firebase @@ -1100,8 +1085,7 @@ Pamiętaj ta akcja może zresetować aplikacje i potrwać jakiś czas. Sprawdź ustawienia systemowe. Powiadomienia są wyłączone dla twojego konta. Sprawdź ustawienia konta. - Powiadomienia nie są dozwolone dla tego urządzenia. -Proszę sprawdź ustawienia Riot. + Powiadomienia nie są włączone dla tej sesji. Proszę sprawdź ustawienia Riot. "Zawsze wprowadzamy zmiany i ulepszenia do Riot.im. Pełną listę zmian można znaleźć tutaj: %1$s. Aby upewnić się, że niczego nie przegapisz, po prostu miej włączone aktualizacje." @@ -1152,27 +1136,27 @@ Spróbuj uruchomić ponownie aplikację. Usuń kopię zapasową Wersja - Zweryfikuj urządzenie + Zweryfikuj sesję Oznacz jako przeczytane Zweryfikowano! Weryfikacja klucza Żądanie weryfikacji - %s chce zweryfikować twoje urządzenie + %s chce zweryfikować twoją sesję Nieznany błąd Użyj kopii zapasowej klucza Podpis - Nowe urządzenie żąda kluczy szyfrujących. -\nNazwa urządzenia: %1$s -\nOstatnio widziane: %2$s -\nJeśli to nie Ty zalogowałeś(-aś) się na innym urządzeniu, zignoruj to żądanie. - Nowe niezweryfikowane urządzenie żąda kluczy szyfrujących. -\nNazwa urządzenia: %1$s -\nOstatnio widziane: %2$s -\nJeśli to nie Ty zalogowałeś(-aś) się na innym urządzeniu, zignoruj to żądanie. + Nowa sesja żąda kluczy szyfrujących. +\nNazwa sesji: %1$s +\nOstatnio widziana: %2$s +\nJeśli to nie Ty zalogowałeś(-aś) się na innej sesji, zignoruj to żądanie. + Nowa niezweryfikowana sesja żąda kluczy szyfrujących. +\nNazwa sesji: %1$s +\nOstatnio widziana: %2$s +\nJeśli to nie Ty zalogowałeś(-aś) się na innej sesji, zignoruj to żądanie. Weryfikuj Udostępnij @@ -1183,7 +1167,7 @@ Spróbuj uruchomić ponownie aplikację. Przywrócono kopię zapasową z %d kluczem. Przywrócono kopię zapasową z %d kluczami. - Przywrócono kopię zapasową z %d kluczami. + Użyj kopii zapasowej klucza Uruchamianie… (%1$d z %2$d) @@ -1205,7 +1189,7 @@ Spróbuj uruchomić ponownie aplikację. Utwórz nowy pokój Wszystkie społeczności - Rozmowy bezpośrednie + Wiadomości Bezpośrednie Nowy pokój STWÓRZ @@ -1219,27 +1203,1042 @@ Spróbuj uruchomić ponownie aplikację. Pokoje Opublikuj pokój do spisu pokojów - Aby nie utracić dostępu do Twoich zaszyfrowanych wiadomości, powinienieś aktywować kopię zapasową klucza na wszystkich swoich urządzeniach. + Aby nie utracić dostępu do Twoich zaszyfrowanych wiadomości, powinienieś aktywować kopię zapasową klucza na wszystkich aktywnych sesjach. Tworzenie kopii zapasowej kluczy… Utwórz kopię zapasową Twoje urządzenie używa przestarzałego protokołu bezpieczeństwa TSL, podatnego na ataki, dlatego dla Twojego bezpieczeństwa nie będziesz mógł się połączyć - Kopia zapasowa klucza została prawidłowo skonfigurowana dla tego urządzenia. + Kopia zapasowa klucza została prawidłowo skonfigurowana dla tej sesji. Inicjalizacja usługi Zaloguj się za pomocą logowania jednorazowego Ustaw ważność powiadomienia za pomocą wydarzeń, skonfiguruj dźwięk, diodę LED, wibracje Ważność powiadomień ze względu na wydarzenie - Powiadomienia diagnostyczne + Diagnostyka powiadomień Rozwiązywanie problemów Diagnostyka podstawowa nie wykazała problemów. Jeżeli wciąż nie otrzymujesz powiadomień, prosimy o przesłanie raportu o błędach, w celu ich rozwiązania. Aplikacja nie potrzebuje łączyć się z serwerem domowym w tle, powinno to zredukować użycie baterii Jeżeli nie pamiętasz swoich danych odzystkiwania, możesz %s. Zgubiłeś (-łaś) swój klucz odzyskiwania\? Możesz ustawić nowy w ustawieniach. - Kopia zapasowa posiada poprawną sygnaturę z niezweryfikowanego urządzenia %s + Kopia zapasowa posiada poprawną sygnaturę z niezweryfikowanej sesji %s Wykryto nową, bezpieczną kopię kluczy wiadomości. \n \nJeżeli nie ustawiałeś nowej metody odzyskiwania, atakujący mogą uzyskać dostęp do Twojego konta. Zmień hasło konta i ustaw nową metodę odzyskiwania jak najszybciej w Ustawieniach. Jesteś na bieżąco! Ten adres URL jest nieosiągalny, proszę sprawdź jego poprawność + Unieważnij + Rozłącz + Nie pytaj ponownie + + To nie jest prawidłowy adres serwera Matrix + Potwierdź swoje hasło + Napraw Usługi Play + + Dodaj Konto + + Zarządzanie Kluczami Kryptograficznymi + Zezwól na integracje + Menadżer Integracji + + Hasło jest nieprawidłowe + Hasła nie pasują do siebie + + Wybierz + Wybierz + Odtwarzaj dźwięk migawki + + Nazwa urządzenia (widoczna dla osób, z którymi się komunikujesz) + Publiczna nazwa urządzenia jest widoczna dla ludzi, z którymi się komunikujesz + nieznany adres ip + Pokój + Nowe Zaproszenie + Ten widget został dodany przez: + Otwórz w przeglądarce + Twój motyw + Identyfikator Pokoju + + + Zezwól + Wprowadź nazwę użytkownika. + Wprowadź hasło + (Zaawansowane) + Ustaw Hasło + Sukces ! + Twoje klucze są archiwizowane. + Zastąp + Zatrzymaj + + Klucz Odzyskiwania + Nieoczekiwany błąd + Czy na pewno\? + Pobieranie wersji kopii zapasowej… + Wprowadź Klucz Odzyskiwania + + Odzyskiwanie Wiadomości + + Błąd sieci: sprawdź połączenie i spróbuj ponownie. + + Obliczanie klucza odzyskiwania… + Pobieranie kluczy… + Importowanie kluczy… + Odblokuj Historię + Wprowadź klucz odzyskiwania + Kopia Przywrócona %s ! + + dodano jeden nowy klucz do tej sesji. + dodano %d nowe klucze do tej sesji. + dodano %d nowych kluczy do tej sesji. + + + Niepowodzenie przy pobieraniu wersji kluczy (%s). + Usuwanie kopii zapasowej… + Sprawdzanie stanu kopii zapasowej + Oczekiwanie na potwierdzenie partnera… + + Sesja została z powodzeniem zweryfikowana. + Druga strona odrzuciła weryfikację. +\n%s + Reakcje + + Ostatnio zmieniony %2$s przez %1$s + + + Brak sieci. Sprawdź swoje połączenie z Internetem. + Proszę czekać… + Tego pokoju nie można podejrzeć + Witaj w programie beta! + Bezpieczeństwo i Prywatność + Ekspert + Brak reguł push + Oczekiwanie… + Wysyłanie miniatury (%1$s / %2$s) + Szyfrowanie pliku… + Wysyłanie pliku (%1$s / %2$s) + + Pobieranie pliku %1$s… + Plik %1$s został pobrany! + + Tworzę pokój… + Warunki Usługi + Wyśli załącznik + + Utwórz nowy pokój + Pokaż hasło + Ukryj hasło + Przewiń do dołu + + Plik \'%1$s\' (%2$s) jest zbyt duży do przesłania. Limit wynosi %3$s. + + Plik + Kontakt + Galeria + Naklejka + To jest spam + Nieodpowiednia treść + Inny powód… + Zgłoś tę treść + Powód zgłoszenia treści + ZGŁOŚ + ZABLOKUJ UŻYTKOWNIKA + + Treść zgłoszona + Zgłoszone jako spam + Weyfikacja zostaje anulowana. +\nPowód: %s + + Proszę napisz swoją sugestię poniżej. + Opisz swoją sugestię tutaj + Utwórz nową rozmowę bezpośrednią + Połączenie z serwerem zostało utracone + + Proszę wykonać kopię + Preferencje + Głos i wideo + Wiadomości Bezpośrednie + + Filtruj rozmowy… + Wyślij nową wiadomość bezpośrednią + Dodaj przez matrix ID + Filtruj wg nazwy użytkownika lub ID… + + Wersja Matrix SDK + Pomoc i o aplikacji + + + Wszystkie wiadomości (hałaśliwy) + Wszystkie wiadomości + Tylko wspomnienia + Wycisz + Ustawienia + Nie ignorujesz żadnych użytkowników + + Widziany przez + + Zaawansowane ustawienia + Tryb programisty + Ustawienia + Bezpieczeństwo + Więcej + Ustawienia pokoju + Powiadomienia + Opuść pokój + Włącz szyfrowanie end-to-end + Brak + Brak skonfigurowanego serwera tożsamości. + + Poproś administratora serwera (%1$s) o skonfigurowanie usługi TURN, aby połączenia mogły działać prawidłowo. +\n +\nMożesz także użyć publicznego serwera %2$s, choć nie będzie on tak niezawodny i pozna twój adres IP. Wybór możesz zmienić w Ustawieniach. + Spróbuj użyć %s + Wprowadź adres email, aby możliwe było odzyskiwanie konta. W przyszłości pozwoli on także odnaleźć Cię Twoim znajomym. + Wprowadź numer telefonu, później pozwoli on Cię odnaleźć Twoim znajomym. + Nie udało się połączyć z serwerem o podanym adresie URL, upewnij się, że wpisano go poprawnie + Dodaj serwer tożsamości w ustawieniach, aby móc wykonać tę akcję. + Wymagane jest uwierzytelnienie + + + Niektóre rodzaje wiadomości będą ciche (wygenerują powiadomienie bez dźwięku). + Nie udało się wczytać niestandardowych reguł, spróbuj ponowić. + Weryfikacja Usług Google + Riot używa Usług Google Play do dostarczania wiadomości push. Konfiguracja usług nie wydaje się być prawidłowa: +\n%1$s + Otrzymano token FCM: +\n%1$s + Niepowodzenie przy pobieraniu tokena FCM: +\n%1$s + [%1$s] +\nRiot nie ma wpływu na wystąpienie tego problemu. Na tym urządzeniu nie ma konta Google. Otwórz menadżer kont i dodaj konto Google. + Token FCM z powodzeniem zarejestrowany na serwerze domowym. + Niepowodzenie przy rejestracji tokena FCM na serwerze domowym: +\n%1$s + + Usługa została zatrzymana i automatycznie uruchomiona ponownie. + Usługa nie uruchomiła się ponownie + + Usługa zostanie uruchomiona przy starcie urządzenia. + Usługa nie zostanie uruchomiona przy starcie urządzenia, nie otrzymasz żadnych powiadomień, dopóki Riot nie zostanie uruchomiony. + Dla zwiększenia bezpieczeństwa, zalecamy aby wykonać ten krok osobiście lub przez inne zaufane środki komunikacji. + Treść została zgłoszona. +\n +\nJeśli nie chcesz więcej otrzymywać wiadomości od tego użytkownika, możesz go zablokować by ukryć jego wiadomości + Treść została zgłoszona jako spam. +\n +\nJeśli nie chcesz więcej widzieć treści tego użytkownika,możesz go zablokować by ukryć jego wiadomości + Sprawdź ograniczenia pracy w tle + Wyłącz ograniczenia + + Konfiguruj powiadomienia połączeń + Wybierz kolor diody LED, wibrację, dźwięk… + + + Integracje + Latn + + Wprowadź adres e-mail, aby możliwe było odzyskiwanie konta. Opcjonalnie użyj adresu e-mail lub numeru telefonu aby móc zostać odkrytym przez znajomych. + Wprowadź adres e-mail, aby możliwe było odzyskiwanie konta. Opcjonalnie użyj adresu e-mail lub numeru telefonu aby móc zostać odkrytym przez znajomych. + Pozwól na awaryjny serwer wspomagania połączeń + Użyje %s aby wspomagać gdy Twój serwer domowy takiego nie ofertuje (Twój adres IP będzie udostępniony podczas połączenia) + [%1$s] +\nBłąd jest poza kontrolą Riot i nawiązując do Google sygnalizuje on, iż urządzenie posiada zbyt wiele aplikacji zarejestrowanych z FCM. Błąd występuje jedynie w przypadku posiadania skrajnie wielu aplikacji, w związku z czym nie powinno dotknąć to normalnego użytkownika. + [%1$s] +\nBłąd jest poza kontrolą Riot. Może on występować z wielu powodów. Przypuszczalnie aplikacja powróci do normalnego stanu po spróbowaniu ponownie, chociaż można sprawdzić także w ustawieniach systemu uprawnienia Usług Google Play dotyczące dostępu do sieci, sprawdzić prawidłowość zegaru urządzenia lub też, może być to błąd niestandardowego oprogramowania systemowego. + Aktywuj uruchamianie przy starcie systemu + + Restrykcje dotyczące działania aplikacji w tle są wyłączone dla Riot. Test powinen zostać uruchomiony używając danych komórkowych (bez WIFI). +\n%1$s + "Restrykcje dotyczące działania aplikacji w tle są włączone dla Riot. +\nPraca którą aplikacja próbuje wykonać będzie agresywnie ograniczona podczas działania w tle i może wpłynąć na wyświetlanie powiadomień. +\n%1$s" + Na Riot nie ma wpływu Optymalizacja Baterii. + Jeżeli użytkownik pozostawi urządzenie odłączone od zasilania oraz nieużywane przez określony okres, z wyłączonym ekranem, urządzenie przejdzie w tryb Doze. Uniemożliwia to aplikacjom dostęp do sieci i opóźnia ich zadania, synchonizację oraz standardowe alarmy. + Tryb synchronizacji w tle (eksperymentalny) + Zoptymalizowano dla baterii + Riot będzie synchronizował się w tle w sposób który oszczędza limitowane zasoby urządzenia (baterię). +\nW zależności od stanu zasobów urządzenia, synchronizacja może być opóźniania przez system operacyjny. + Zopytmalizowano dla działania w czasie rzeczywistym + Riot będzie synchornizował się okresowo o ściśle określonym czasie (konfigurowalne). +\nWpłynie to na użycie baterii i sieci, na panelu powiadomień pozostanie wyświetlone stałe powiadomiene o nasłuchiwaniu zdarzeń. + Brak synchronizacji w tle + Nie będziesz otrzymywać powiadomień o przychodzących wiadomościach gdy aplikacja będzie działać w tle. + Niepowodzenie przy aktualizacji ustawień. + + + Preferowany interwał synchronizacji + %s +\nSynchronizacja może zostać opóźniona w zależności od zasobów (bateria) lub stanu urządzenia (hibernacja). + Użyj Menedżera Integracji aby zarządzać botami, mostami, widżetami oraz pakietami naklejek. +\nMenadżerowie Integracji odbierają dane konfiguracji, mogą zmieniać widżety, wysyłać zaproszenia do pokoi oraz ustawiać poziomy uprawnień na Twoje żądanie. + Pokaż podgląd linków wewnątrz czatu jeśli twój serwer wspiera tę funkcję. + Formatuj wiadomości używając składni Markdown zanim zostaną wysłane. Pozwala to na zaawansowane formatowanie takie jak używanie asterysków do wyświetlania tekstu w kursywie. + Nie wpływa to na zaproszenia, wyrzucenia oraz bany. + Wysyłaj wiadomości za pomocą klawisza enter + Przycisk enter na klawiaturze programowej wyśle wiadomość zamiast wprowadzania łamanania linii + + Znajdź + Zarządzaj ustawieniami wyszukiwania. + Riot potrzebuje utrzymać mało wpływowe połączenie w tle, w celu otrzymywania wiarygodnych powiadomień. +\nNa następnym ekranie zostanie się poproszonym o pozwolenie działania w tle dla Riot, proszę zaakceptować. + Tryb oszczędzania danych użyje filtra szczegółowego, w związku z czym aktualizacje o obecności i powiadomienia o pisaniu zostaną przefiltrowane. + + Media + Domyślne źródło mediów + Odzyskiwanie zaszyforwanych wiadomości + + %1$s: 1 wiadomość + %1$s: %2$d wiadomości + + + + %d powiadomienie + %d powiadomień + + + + Nowe wydarzenie + Nowe wiadomości + Ja + ** Nie udało się wysłać - proszę otworzyć pokój + + Widżet + Załaduj widżet + Używając tego pliki cookies mogą zostać ustawione i dane wspóldzielone z %s: + Używając tego dane mogą być współdzielone z %s: + Uruchomienie widżetu nie powiodło się. +\n%s + Przeładuj widżet + Cofnij dostęp dla mnie + + Wyświetlana nazwa + Adres URL awatara + Twój ID użytkownika + ID Widżetu + Przepraszamy, połączenia konferencyjne za pomocą Jitsi nie są wspierane na starszych urządzeniach (urządzenia z systemem Android poniżej wersji 5.0) + Widżet chce użyć następujących zasobów: + Zablokuj wszystko + Użyj aparatu + Użyj mikrofonu + Odczytaj media zabezpieczone DRM + + Nie skonfigurowano menedżera integracji. + Uruchom systemową kamerę zamiast niestandardowego ekranu kamery w aplikacji. + Aby kontynuować, musisz zaakceptować Warunki użytkowania dla tej usługi. + + Nie znaleziono prawidłowej aplikacji Usługi Google Play. Powiadomienia mogą nie działać prawidłowo. + + Riot.im - Rozmawiaj, we własny sposób + Bezpieczna aplikacja czatu całkowicie pod Twoją kontrolą. + Aplikacja czatu, całkowicie pod twoją kontrolą i całkowicie dopasowująca się. Riot pozwala na komunikowanie się w jakikolwiek sposób chcesz. Stworzony dla [Matrix] - standardu otwartej, zdecentralizowanej komunikacji. +\n +\nStwórz darmowe konto matrix.org, zdobądź swój własny serwer na https://modular.im, lub użyj innego serwera Matrix. +\n +\nDlaczego warto wybrać Riot.im\? +\n +\n• KOMPLETNA KOMUNIKACJA: Stwórz pokoje dla twojego zespołu, przyjaciół, społeczności - tak, jak lubisz! Czatuj, udostępniaj pliki, dodawaj widżety i wykonuj połączenia głosowe lub wideo - wszystko za darmo. +\n +\n• SILNA INTEGRACJA: Używaj Riot.im z narzędziami które znasz i kochasz. Z Riot.im możesz pisać z użytkownikami i grupami na innych aplikacjach do czatu. +\n +\n• PRYWATNE I BEZPIECZNE: Utrzymuj twoje konwersacje w sekrecie. Nowoczesne szyfrowanie typu \"end-to-end\" zapewnia, że to co jest prywatne, pozostaje prywatne +\n +\n• OTWARTE, NIE ZAMKNIĘTE: Otwartoźródłowe i oparte o Matrix. Bądź w posiadaniu swoich danych posiadając swój własny serwer, lub wybierając taki, któremu ufasz. +\n +\n•GDZIEKOLWIEK JESTEŚ: Bądź w kontakcie gdziekolwiek jesteś wraz z w pełni synchronizowaną historią wiadomości na wszystkich twoich urządzeniach lub na https://riot.im. + + Hasło jest zbyt słabe + + Proszę usunąć hasło, jeżeli chcesz aby Riot wygenerował klucz odzyskiwania. + Brak dostępnych sesji Matrix + + Nie utrać zaszyfrowanych wiadomości + Wiadomości w pokojach zaszyfrowanych są bezpieczne dzięki szyfrowaniu end-to-end. Jedynie Ty i Twój odbiorca posiadają klucze dla tych wiadomości. +\n +\nBezpiecznie utwórz kopię zapasową, aby ich nie utracić. + Zacznij używać Kopii Zapasowej Kluczy + Wyeksportuj klucze ręcznie + + Zabezpiecz swoją kopię zapasową używając hasła. + Będziemy przechowywać zaszyfrowaną kopię Twoich kluczy na serwerze domowym. Chroń swoją kopię zapasową hasłem, aby pozostała bezpieczna. +\n +\nDla pełni bezpieczeństwa, powinno być ono inne niż hasło konta. + Tworzenie kopii zapasowej + Lub, zabezpiecz swoją kopię zapasową z Kluczem Odzyskiwania i zachowaj w bezpiecznym miejscu. + (Zaawansowane) Ustaw za pomocą Klucza Odzyskiwania + Twój klucz odzyskiwania jest siatką bezpieczeństwa - możesz użyć go, aby odzyskać dostęp do zaszyfrowanych wiadomości jeżeli zapomnisz hasła. +\nPrzechowuj swój klucz odzyskiwania w bardzo bezpiecznym miejscu, takim jak menedżer haseł (lub sejf) + Przechowuj swój klucz odzyskiwania w bardzo bezpiecznym miejscu, takim jak menedżer haseł (lub sejf) + Zrobiłem kopię + Zapisz Klucz Odzyskiwania + Zapisz jako plik + "Klucz odzyskiwania został zapisany do \'%s\'. +\n +\nUwaga: plik może zostać usunięty, jeżeli aplikacja jest odinstalowana." + + Kopia zapasowa już istnieje na Twoim serwerze domowym + Wygląda na to, iż kopia zapasowa kluczy została skonfigurowana za pomocą innej sesji. Czy chcesz zastąpić ją tą, którą tworzysz\? + Generowanie Klucza Odzyskiwania używając hasła, proces może zająć kilka sekund. + Kopia zapasowa uruchomiona + Twoje klucze szyfrujące będą kopiowane do kopii zapasowej w tle przez Twój serwer domowy. Wstępna kopia zapasowa może zająć kilka minut. + + + Utracisz dostęp do swoich wiadomości jeżeli wylogujesz się lub utracisz to urządzenie. + + Użyj kopii zapasowej klucza aby odblokować historię zaszyfrowanych wiadomości + użyj klucza odzyskiwania + Użyj Klucza Odzyskiwania aby odblokować historię zaszyfrowanych wiadomości + Kopia zapasowa nie może zostać zdeszyfrowana za pomocą tego hasła: proszę upewnij się, czy wprowadzone hasło jest poprawne. + Przywracanie kopii zapasowej: + Kopia zapasowa nie może zostać zdeszyfrowana za pomocą tego klucza odzyskiwania: proszę upewnij się, czy wprowadzony klucz odzyskiwania jest poprawny. + + Przywrócono %1$d kluczy sesji, i dodano %2$d nowych kluczy które nie były znane tej sesji + Szyfrowanie sesji nie jest aktywowane + + + Kopia zapasowa klucza nie jest aktywna dla tej sesji. + Twoje klucze nie są będą zapisywane w kopii zapasowej od tej sesji. + + Kopia zapasowa posiada sygnaturę od nieznanej sesji z ID %s. + Kopia zapasowa posiada prawidłową sygnaturę dla tej sesji. + Kopia zapasowa posiada prawidłową sygnaturę z zweryfikowanej sesji %s. + Kopia zapasowa posiada nieprawidłową sygnaturę ze zweryfikowej sesji %s + Kopia zapasowa posiada niezweryfikową sygnaturę z nieznanej sesji %s + Nie udało się uzyskać zaufanych informacji dla kopii zapasowej (%s). + + Aby użyć Kopii Zapasowej Kluczy dla tej sesji, przywróć ją za pomocą hasła lub klucza odzyskiwania. + Usuwanie kopii zapasowej nie powiodło się (%s) + + Nowa kopia zapasowa kluczy + To byłem(-łam) ja + Nigdy nie utrać zaszyfrowanych wiadomości + Zacznij korzystać z Kopii Zapasowej Kluczy + + Nigdy nie utrać zaszyfrowanych wiadomości + Użyj Kopii Zapasowej Kluczy + + Nowe klucze szyfrowanych wiadomości + Zarządzaj w Kopii Zapasowej Kluczy + + Tworzenei kopii zapasowej kluczy… + + + Kopiowanie %d klucza… + Kopiowanie %d kluczy… + + + + Nieprawidłowa odpowiedź funkcji autoodkrywania serwera domowego + Opcje automatycznego uzupełniania serwerów + Riot wykryło niestandardową konfigurację serwera dla Twojej domeny userID \"%1$s\": +\n%2$s + Użyj Konfiguracji + + Zostałeś(-łaś) wylogowana ze względu na nieprawidłowe lub wygasłe dane logowania. + + Zweryfikuj porównując krótki ciąg tekstowy. + Rozpocznij weryfikację + Przychodzące żądanie weryfikacji + Zweryfikuj tą sesję poprzez oznaczenie jej jako zaufanej. Zaufanie sesji innych osób przynosi spokój na umyśle gdy są to szyfrowane wiadomości end-to-end. + Werfikacja tej sesji oznaczy ją jako zaufaną, a także oznaczy Twoją sesję jako zaufaną dla rozmówcy. + + Wyświetl żądanie + Bezpieczne wiadomości od tego użytkownika są zabezpeiczone za pomocą szyfrowania end-to-end i są nie do odczytania przez osoby trzecie. + Połączenie nie powiodło się z powodu niewłaściwie skonfigurowanego serwera + Nie możesz tego zrobić z mobilnej aplikacji Riot + Niektóre powiadomienia są wyłączone w osobistej konfiguracji. + Usługi Google Play są aktualne. + Automatycznie uruchom ponownie usługę powiadomień + Potwierdź, że następujące emoji pojawiły się na ekranie partnera + Potwierdź, że następujące liczby pojawiły się na ekranie partnera + + Otrzymano przychodzące żądanie weryfikacji. + Rozumiem + + Nic się nie pojawiło\? Nie wszystkie aplikacje obsługują już interaktywną weryfikację. Spróbuj weryfikacji starszego typu. + Użyj weryfikacji starszego typu. + + Weryfikacja Anulowana + Interaktywna Weryfikacja Sesji + Użytkownik anulował weryfikację + Upłynął limit czasu weryfikacji + Sesja nie rozpoznała tej transakcji + Sesje nie mogą porozumieć się w sprawie wykorzystania metody szyfrowania (wymiana kluczy, hasz, MAC lub SAS) + Zobowiązanie bitowe nie zgadza się + SAS nie zgadza się + Sesja otrzymała niespodziewaną wiadomość + Otrzymano nieprawidłową wiadomość + Poprzednie wersje Riot posiadały błąd bezpieczeństwa, który umożliwiał Twojemu serwerowi tożsamości (%1$s) dostęp do Twojego konta. Jeżeli ufasz %2$s, możesz to zignorować; w przeciwnym wypadku wyloguj się i zaloguj ponownie. +\n +\nInformacje o szczegółach tutaj: +\nhttps://medium.com/@RiotChat/36b4792ea0d6 + + Dołącz do pokoju, aby rozpocząć korzystanie z aplikacji. + Zapoznaj się z nieprzeczytanymi wiadomościami tutaj + Twoje rozmowy bezpośrednie będą wyświetlane tutaj + Twoje pokoje będą wyświetlane tutaj + + Nieprawidłowe zdarzenie, nie można wyświetlić + Podgląd globalnego, publicznego pokoju nie jest wciąż wspierany w RiotX + + Wystąpił błąd podczas otrzymywania zaufanych informacji + Wystąpił błąd podczas uzyskiwania danych kluczy kopii zapasowej + + Jako że RiotX jest we wczesnej fazie rozwoju, niektóre funkcje mogą być niedostępne i możesz doświadczyć błędów. + Najnowsza lista funkcji jest zawsze na %1$s, i jeżeli znajdziesz błąd, proszę wyślij raport w lewym górnym menu poprzez \"Ekran domowy\", postaramy załatać się go tak szybko, jak tylko się da. + Opis w Sklepie Play + Jeżeli znajdziesz błędy, proszę wyślij raport za pomocą menu w lewym górnym rogu \"Ekranu domowego\", postaramy się je naprawić tak szybko, jak tylko się da. + + Importowanie kluczy E2E z pliku \"%1$s\". + + Informacje o stronach trzecich + Już wyświetlasz ten pokój! + + id_aplikacji: + klucz_push: + wyświetlana_nazwa_aplikacji: + nazwa_sesji: + Url: + Format: + + Zarejestruj token + + Dziękujemy, sugestia została szczęśliwie wysłana + Wysłanie sugestii nie powiodło się (%s) + + Wyświetl ukryte wydarzenia na linii czasowej + + RiotX - Klient Matrix następnej generacji + Szybszy i lżejszy klient Matrix używający najnowszych frameworków Androida + RiotX jest nowym klientem dla protokołu Matrix (Matrix.org): otwarta sieć dla bezpiecznej, zdecentralizowej komunikacji. RiotX jest całkowicie przepisanym klientem Riot, opartym na nowym SDK Matrix dla systemu Android. +\n +\nUwaga: Jest to wersja beta. RiotX jest obecnie w fazie aktywnego rozwoju i posiada ograniczenia oraz (mamy nadzieje że niewiele) błędy. Wszystkie opinie są mile widziane! +\n +\nRiotX wspiera: • Logowanie do istniejącego konta • Tworzenie pokoi oraz dołączanie do pokoi publicznych • Akceptowanie i odrzucanie zaproszeń • Wyświetlanie listy pokoi użytkowników • Wyświetlanie informacji o pokoju • Wysyłanie wiadomości tekstowych • Wysyłanie załączników • Odczytywanie i pisanie wiadomości w zaszyfrowanych pokojach • Kryptografia: Kopię zapasową kluczy E2E, zaawansowaną weryfikację urządzeń, żądanie udostępniania kluczy i odpowiedzi na nie • Powiadomienia push • Jasne, Ciemne oraz Czarne motywy +\n +\nNie wszystkie funkcje Riot są wdrożone w RiotX. Główne niedostępne (pojawią się już wkrótce!) funkcje: • Ustawienia pokoi (wyświetl listę członków pokoi) • Rozmowy • Widżety • … + + (edytowano) + + %1$s aby utworzyć konto. + Użyj starszej aplikacji + + + Edycje wiadomości + Nie znaleziono edycji + + Aktywuj gest przesunięcia, aby odpowiedzieć na osi czasu + + Link skopiowany do schowka + + Nie znaleziono, użyj Dodaj poprzez ID Matrix aby wyszukać na serwerze. + Zacznij pisać, aby uzyskać rezultaty + Dołączanie do pokoju… + + Wyświetl historię edycji + + Sprawdź Warunki + Bądź odkryty przez innych + Używaj Botów, mostów, widżetów i paczek naklejek + + Czytaj na + + + Obecnie używasz %1$s aby odkrywać i być odkrytym przez kontakty, które znasz. + Nie używasz serwera tożsamości. Aby odkrywać i być odkrywanym przez kontakty, które znasz, skonfiguruj jeden poniżej. + Rozpoznawalny adres e-mail + Opcje odkrywania pojawią się w momencie gdy dodasz adres e-mail. + Opcje odkrywania pojawią się w momencie gdy dodasz numer telefonu. + Odłączenie od serwera tożsamości oznacza, iż nie będziesz mógł(-ła) zostać odkryty(-ta) przez innych użytkowników i nie będziesz mógł(-ła) zapraszać innych za pomocą adresu e-mail oraz numeru telefonu. + Rozpoznawalne numery telefonu + Wysłaliśmy e-mail potwierdzający do %s, sprawdź swoją skrzynkę i naciśnij link potwierdzający + Oczekiwanie + + Wprowadź nowy serwer tożsamości + Nie można połączyć z serwerem tożsamości + Wprowadź adres serwera tożsamości + Serwer tożsamości nie posiada warunków usługi + Wybrany system tożsamości nie posiada jakichkolwiek warunków usługi. Kontynuuj jedynie, gdy ufasz właścicielowi usługi + Wiadomość tekstowa wysłana do %s. Proszę wprowadzić kod weryfikacyjny w niej zawarty. + + Udostępniasz adres e-mail lub numer telefonu serwerowi tożsamości %1$s. Musisz ponownie połączyć się z %2$s aby ich nie udostępniać. + Akceptuj Warunki Usługi serwera tożsamości (%s) aby pozwolić na bycie odkrytym za pomocą adresu e-mail lub numeru telefonu. + + Aktywuj szczegółowe dzienniki. + Dzienniki szczegółowe pomogą twórcom poprzez udostępnianie większej ilości danych, które zostaną wysłane, gdy potrząśniesz wściekle. Nawet jeżeli zostaną aktywowane, aplikacja nie zapisuje szczegółów wiadomości oraz innych danych prywatnych. + + + Spróbuj ponownie, gdy zaakceptujesz warunki usługi oraz warunki ogólne serwera domowego. + + Wygląda na to, iż serwer potrzebuje wiele czasu aby odpowiedzieć i może być to spowodowane zarówno słabą łącznością jak i problemem z serwerem. Spróbuj ponownie za chwilę. + + Otwórz panel nawigacji + Otwórz menu tworzenia pokoju + Zamknij menu tworzenia pokoju… + Zamknij baner kopii zapasowej kluczy + odczytano przez %1$s, %2$s oraz %3$d + odczytano przez %1$s, %2$s oraz %3$s + odczytano przez %1$s oraz %2$s + %s odczytał + + 1 użytkownik odczytał + %d użytkowników odczytało + + + + Wystąpił błąd poczas otrzymywania załącznika. + Audio + Nie można obsłużyć otrzymanych danych + + Zgłoszono jako nieodpowiedni + Zawartość została zgłoszona jako niewłaścwa. +\n +\nJeżeli nie chcesz widzieć treści od tego użytkownika, możesz go zablokować aby ukryć jego wiadomości + + Riot potrzebuje uprawnień aby zapisywać klucze E2E na dysku. +\n +\nPozwól na dostęp w następnym oknie aby móc eksportować klucze ręcznie. + + Opuść pokój + %1$s nie dokona(-ła) zmian + Wysyła wiadomość jako spoiler + Spoiler + Wprowadź słowa kluczowe aby znaleźć reakcję. + + Naciśnij długo na pokój aby wyświetlić więcej opcji + + + %1$s ustawił(-a) pokój dostępnym publicznie dla każdego, kto zna link. + %1$s ustawił(-a) pokój tylko dla zaproszonych. + Nieprztane wiadomości + + Wyzwól swoją komunikację + Czatuj z osobami bezpośrednio lub w grupach + Pozostaw konwersacje prywatnymi za pomocą szyfrowania + Rozszerz i dopasuj swoje doświadczenie + Rozpocznij + + Wybierz serwer + Tak jak adres e-mail, konta mają jeden dom, aczkolwiek możesz rozmawiać ze wszystkimi + Dołącz do milionów, za darmo, na największym publicznym serwerze + Hosting premium dla organizacji + Dowiedz się więcej + Inne + Ustawienia niestandardowe i zaawansowane + + Kontynuuj + Połącz z %1$s + Połącz z Modular + Połącz z serwerem niestandardowym + Zaloguj się do %1$s + Zarejestruj + Zaloguj się + Kontynuuj za pomocą logowania jednostopniowego + + Adres Modular + Adres + Hosting premium dla organizacji + Wprowadź adres Riot Modular lub serwera którego chcesz użyć + Wprowadź adres serwera lub Riot z którym chcesz się połączyć + + Wystąpił błąd podczas ładowania strony: %1$s (%2$d) + Aplikacja nie jest w stanie zalogować się do tego serwera domowego. Serwer domowy obsluguje następujące metody logowania: %1$s. +\n +\nCzy chcesz zalogować się używając klienta sieciowego\? + Przepraszamy, serwer nie obsługuje tworzenia nowych kont. + Aplikacja nie jest w stanie utworzyć konta na tym serwerze domowym. +\n +\nCzy chcesz zarejestrować się używając klienta sieciowego\? + + E-mail nie jest powiązany z kontem. + + Zresetuj hasło na %1$s + Wiadomość weryfikacyjna zostanie wysłana na adres e-mail aby potwierdzić ustawienie nowego hasła. + Dalej + E-mail + Nowe hasło + + Uwaga! + Zmiana hasła zresetuje wszystkie klucze szyfrowania end-to-end dla wszystkich twoich sesji, czyniąc zaszyfrowaną historię czasu nie do odczytania. Ustaw Kopię Zapasową Kluczy lub wyeksportuj klucze pokoju do innej sesji przed resetowaniem hasła. + Kontynuuj + + Adres e-mail nie został połączony z kontem + + Sprawdź swoją skrzynkę + E-mail weryfikacyjny został wysłany do %1$s. + Naciśnij na link aby potwierdzić nowe hasło. Po naciśnięciu na link, który je zawiera, naciśnij poniżej. + Zweryfikowałem(-łam) swój adres e-mail + + Sukces! + Hasło zostało zresetowane. + Zostałeś(-łaś) wylogowany(-na) ze wszystkich sesji i nie będziesz otrzymywać powiadomień push. Aby re-aktywować powiadomienia, zaloguj się ponownie na każdym z urządzeń. + Powróć do logowania + + Ostrzeżenie + Hasło wciąż nie zostało zmienione. +\n +\nZatrzymać proces zmiany hasła\? + + Ustaw adres e-mail + Ustaw e-mail aby odzyskać konto. Później, opcjonalnie, będziesz w stanie pozwolić na odkrycie Ciebie za pomocą Twojego adresu e-mail. + E-mail + E-mail (nieobowiązkowy) + Dalej + + Ustaw numer telefonu + Ustaw numer telefonu, aby pozwolić innym na odkrycie Ciebie za pomocą numeru telefonu. + Użyj formatu międzynarodowego. + Numer telefonu + Numer telefonu (nieobowiązkowy) + Dalej + + Potwierdź numer telefonu + Wysłaliśmy kod do %1$s. Wprowadź go poniżej, aby potwierdzić, że to Ty. + Wprowadź kod + Wyślij ponownie + Dalej + + Międzynarodowe numery telefonów muszą zaczynać się od \'+\' + Numer telefonu wydaje się być nieprawidłowy. Sprawdź + + Zaloguj się do %1$s + Nazwa użytkownika lub e-mail + Nazwa użytkownika + Hasło + Dalej + Nazwa użytkownika zajęta + Ostrzeżenie + Twoje konto wciąż nie zostalo utworzone. +\n +\nZatrzymać proces rejestracji\? + + Wybierz matrix.org + Wybierz modular + Wybierz niestandardowy serwer domowy + Wypełnij zadanie Captcha + Zaakceptuj warunki aby kontynuować + + Sprawdź swój e-mail + Wysłaliśmy e-mail do %1$s. +\nProszę nacisnąć na link który zawiera aby kontynuować tworzenie konta. + Wprowadzony kod jest nieprawidłowy. Sprawdź. + Nieaktualny serwer domowy + Ten serwer domowy pracuje pod kontrolą zbyt starej wersji, aby się z nim połączyć. Zapytaj administratora serwera domowego o aktualizację. + + + Zostało wysłane zbyt wiele żądań. Możesz spróbować ponownie za %1$d sekundę… + Zostało wysłane zbyt wiele żądań. Możesz spróbować ponownie za %1$d sekundy… + + + + Wylogowałeś(-łaś) się + Mogło to się stać z wielu powodów: +\n +\n• Zmieniłeś(-łaś) swoje hasło na innej sesji. +\n +\n• Usunęłaś swoją sesję z innej sesji. +\n +\n• Administrator Twojego serwera unieważnił dostęp ze względów bezpieczeństwa. + Zaloguj ponownie + + Wylogowałeś(-łaś) się + Zaloguj się + Administaror twojego serwera domowego (%1$s) wylogował cię z konta %2$s (%3$s). + Zaloguj się aby odzyskać klucze szyfrowania przechowywane wyłącznie na tym urządzeniu. Będziesz ich potrzebował aby odczytać zaszyfrowane wiadomości na każdym z urządzeń. + Zaloguj się + Hasło + Wyczyść dane osobowe + Ostrzeżenie: Twoje dane osobowe (włączając w to klucze szyfrujące) są wciąż przechowywane na tym urządzeniu. +\n +\nWyczyść je, jeżeli skończyłeś(-łaś) używać tego urządzenia, lub chcesz zalogować się na inne konto. + Wyczyść wszystkie dane + + Wyczyść dane + Wyczyścić wszystkie dane przechowywane na tym urządzeniu\? +\nZaloguj się ponownie aby uzyskać dostęp do danych konta i wiadomości. + Utracisz dostęp do zaszyfrowanych wiadomości do czasu, aż zalogujesz się aby odzyskać Twoje klucze szyfrujące. + Wyczyść dane + Aktualna sesja jest dla użytkownika %1$s, podajesz natomiast dane dla użytkownika %2$s. Nie jest to wspierane przez RiotX. +\nNa początku usuń dane, następnie zaloguj ponownie na innym koncie. + + Link matrix.to został zdeformowany + Opis zbyt krótki + + Synchronizacja wstępna… + + Zobacz wszystkie sesje + Wściekłe potrząśnięcie + Próg detekcji + Potrząśnij telefonem aby wypróbować próg detekcji + Potrząśnięcie wykryte! + Aktualna sesja + Inne sesje + + Wyświetlanie jedynie początkowych wyników, wprowadź więcej znaków… + + Bezproblemowy + RiotX może zawieszać się częściej gdy napotka na niespodziewany błąd + + Żądanie weryfikujące podany userID + Preparuje ¯\\_(ツ)_/¯ dla zwykłej wiadomości tekstowej + + Aktywuj szyfrowanie + Odkąd zostanie włączone, szyfrowanie nie może zostać wyłączone. + + Twoja domena adresu e-mail nie została dopuszczona do rejestracji na tym serwerze + + Niezaufane logowanie + Zgadzają się + Nie zgadzają się + Zweryfikuj użytkownika poprzez potwierdzenie unikalnego ciągu emoji, w tym samym porządku na jego ekranie. + Dla najlepszego bezpieczeństwa, użyj innych zaufanych form komunikacji lub zrób to osobiście. + Patrz na zieloną tarczę, aby upewnić się czy użytkownik jest zaufany. Zaufaj wszystkim użytkownikom w pokoju, aby upewnić się, że pokój jest bezpieczny. + + Nie jest bezpieczny + Jeden z poniższych mogł zostać narażony: +\n +\n- Serwer domowy +\n- Serwer domowy użytkownika, z którym jest on połączony +\n- Połączenie z Internetem Twoje lub innych użytkowników +\n- Urządzenie Twoje lub innych użytkowników + + Wideo. + Zdjęcie. + Dźwięk + Plik + + Oczekiwanie… + %s anulowana + Anulowałeś(-łaś) + %s zaakceptowana + Zaakceptowałeś(-łaś) + Żądanie weryfikacji wysłane + Żądanie weryfikacji + + + Zweryfikuj tą sesję + Zweryfikuj ręcznie + + Ty + + Zeskanuj kod z urządzenia innego użytkownika aby bezpiecznie zweryfikować siebie nawzajem + Zeskanuj ich kod + Nie można zeskanować + Jeżeli nie jesteś z tą osobą, zamiast tego porównaj emoji + + Zweryfikuj porównując emoji + + Zweryfikuj za pomocą Emoji + Jeżeli nie możesz zeskanować kodu powyżej, zweryfikuj porównując krótki, unikalny ciąg emoji. + + Obraz kodu QR + + Zweryfikuj %s + Zweryfikowano %s + Oczekiwanie na %s… + Dla wyższego poziomu bezpieczeństwa, zweryfikuj %s poprzez sprawdzenie jednorazowego kodu na obu urządzeniach. +\n +\nDla najwyższego bezpieczeństwa, zrób to osobiście. + Wiadomości w tym pokoju są zaszyfrowane end-to-end. +\n +\nTwoje wiadomości są zabezpieczone zamkami i jedynie Ty oraz Twój odbiorca posiadacie klucze, aby je odblokować. + Dowiedz się więcej + + Jedna osoba + %1$d osób + + + Opuszczanie pokoju… + + Administratorzy + Moderatorzy + Niestandardowy + Zaproszenia + Użytkownicy + + Administrator w %1$s + Moderator w %1$s + Niestandardowy (%1$d) w %2$s + + Przeskocz do znacznika odczytania + + RiotX nie obsługuje wydarzeń typu \'%1$s\' + RiotX nie obsługuje wiadomości typu \'%1$s\' + RiotX napotkał problem przy wyświetlaniu zawartości wydarzenia z ID \'%1$s\' + + Nie ignoruj + + Sesja nie jest w stanie podzielić się weryfikacją z innymi sesjami. +\nWeryfikacja zostanie zapisana lokalnie i udostępniona w przyszłych wersjach aplikacji. + + Ostatnie pokoje + Inne pokoje + + Wysyła wiadomość w odcieniach tęczy + Wysyła emoji w odcieniach tęczy + + Oś czasu + + Edycja wiadomości + + Odkąd zostanie włączone, szyfrowanie nie może zostać wyłączone. + + Aktywować szyfrowanie\? + Odkąd zostanie włączone, szyfrowanie w pokoju nie może zostać wyłączone. Wiadomości wysłane w zaszyfrowanym pokoju nie są widzane przez serwer, a jedynie przez uczestników w pokoju. +\nAktywowanie szyfrowania może uniemożliwić wielu botom i mostom prawidłowe działanie. + Aktywuj szyfrowanie + + Aby być bezpiecznym, zweryfikuj %s poprzez sprawdzenie jednorazowego kodu. + Aby być bezpiecznym, zrób to osobiście lub użyj innej metody komunikacji. + + Porównaj unikalny ciąg emoji, upewniając się, że pojawiają się w identycznym porządku. + Porównaj kod wyświetlany na ekranie innego użytkownika. + Wiadomości z użytkownikiem są szyfrowane end-to-end i nie mogą zostać odczytane przez osoby trzecie. + Nowa sesja została zweryfikowana. Posiadasz dostęp do zaszyfrowanych wiadomości, a inni użytkownicy będą ją widzieć jako zaufaną. + + + Podpis krzyżowy + Podpis krzyżowy jest aktywowany. +\nKlucze Prywatne znajdują się na urządzeniu. + Podpis krzyżowy aktywowany. +\nKlucze są zaufane. +\nKlucze prywatne nie są znane + Podpis krzyżowy jest aktywowany. +\nKlucze nie są zaufane + Podpis krzyżowy nie jest aktywowany + + + Aktywne Sesje + Pokaż wszystkie Sesje + Zarządzaj Sesjami + Wyloguj z tej sesji + + Brak dostępnej informacji o kryptografii + + Ta sesja jest zaufana dla bezpiecznej wymiany wiadomości, ponieważ ją zweryfikowałeś(-łaś): + Zweryfikuj tą sesję aby oznaczyć ją jako zaufaną i przyznać jej dostęp do zaszyfrowanych wiadomości. Jeżeli nie logowałeś(-łaś) się do tej sesji, twoje konto mogło zostać naruszone: + + + %d aktywna sesja + %d aktywnych sesji + + + + Zweryfikuj tą sesję + Inni użytkownicy mogą jemu nie ufać + Całkowite Bezpieczeństwo + + Otwórz obecną sesję i użyj jej do zweryfikowania obecnej, przyznając jej dostęp do zaszyfrowanych wiadomości. + + + Zweryfkuj + Zweryfikowano + Ostrzeżenie + + Uzyskanie sesji nie powiodło się + Sesje + Zaufany + Niezaufany + + Sesja jest zaufana dla bezpiecznej wymiany wiadomości ponieważ %1$s (%2$s) zweryfikował(-a) ją: + %1$s (%2$s) zalogował(-a) się używając nowej sesji: + Dopóki użytkownik ufa tej sesji, wiadomości wysłane do oraz od niej będą oznaczone ostrzeżeniami. Ewentualnie, możesz zweryfikować je ręcznie. + + + Inicjalizacja podpisu krzyżowego + Zresetuj Klucze + + Kod QR + + Czy inny użytkownik pomyślnie zeskanował kod QR\? + Tak + Nie + + Narzędzia programistyczne + Dane konta + + %d głos + %d głosów + + + + %d głos - wyniki końcowe + %d głosów - wyniki końcowe + + + Wybrana Opcja + Tworzy prostą ankietę + Nie masz dostępu do instniejącej sesji\? + Użyj klucza odzyskiwania lub hasła + + Nowa rejestracja + + Nie można odnaleźć tajemnej przestrzeni w pamięci + Wprowadź hasło tajemnej przestrzeni + Ostrzeżenie: + Powinieneś(-nnaś) uzyskać dostęp do tajnej przestrzeni jedynie z zaufanego urządzenia + Uzyskaj dostęp do historii bezpiecznych wiadomości i Twojego(-jej) tożsamości podpisu krzyżowego poprzez weryfikację innej sesji za pomocą hasła + + Usuń… + Czy chcesz wysłać załącznik do %1$s\? + + Wyślij obraz w oryginalnym rozmiarze + Wyślij obrazy w oryginalnym rozmiarze + + + + Potwierdź Usunięcie + Jesteś pewny(-na), że chcesz usunąć to wydarzenie\? Jeżeli usuniesz nazwę pokoju lub zmienisz temat, wciąż będzie możliwe cofnięcie zmiany. + Podaj przyczynę + Przyczyna reakcji + + Wydarzenie usunięte przez użytkownika, przyczyna: %1$s + Wydarzenie moderowane przez administratora pokoju, przyczyna: %1$s + + Klucze są już aktualne! + + Sprawdź + Zrezygnuj + + Niezgodność klucza + Niezgodność użytkowników + Nie używasz Serwera Tożsamości + Nie skonfigurowano serwera toższamości, który jest wymagany do resetowania hasła. + + Wygląda na to, że próbujesz podłączyć się do innego serwera domowego. Czy chcesz się wylogować\? + + Przysyła zaproszenie + Zaproszono przez %s + + Nie masz więcej nieprzeczytanych wiadomości + Witaj w domu! + Reakcje + Zgoda + Lubię to + Zobacz Reakcje + Zdarzenie usunięte przez użytkownika + Zdarzenie moderowane przez administratora pokoju + Katalog Pokoi + Szybkie Reakcje + + Ogólne + Zasady push + Brak zarejestrowanych bramek push + + Szyfrowanie miniatury… + Nie możesz czegoś odnaleźć\? + Utwórz nowy pokój + Otwórz katalog pokoi + + Nazwa lub ID (#przykład:matrix.org) + + Serwer toższamości + Odłącz od serwera tożsamości + Skonfiguruj serwer tożsamości + Zmień serwer tożsamości + Aparat + Obecnie nie ma połączenia z siecią + + Zablokuj użytkownika + + Złóż sugestię + Tryb programisty aktywuje ukryte funkcje i może również spowodować, że aplikacja będzie mniej stabilna. Tylko dla programistów! + Wiadomości w tym pokoju nie są szyfrowane end-to-end. + Przesłane pliki diff --git a/vector/src/main/res/values-pt/strings.xml b/vector/src/main/res/values-pt/strings.xml index 3508d1fd9b..56417b9a36 100755 --- a/vector/src/main/res/values-pt/strings.xml +++ b/vector/src/main/res/values-pt/strings.xml @@ -1065,17 +1065,17 @@ A visibilidade das mensagens no Matrix é parecida com a dos emails. O nosso esq backup de chaves Backup de chaves, imcompleto. Por favor, Aguarde. - Backup de chaves, em progresso. Caso pagina seja fechada, irá perder acesso as suas mensagens encriptadas. + Backup de chaves, em progresso. Caso página seja fechada, irá perder acesso as suas mensagens encriptadas. Backup de chaves, em progresso … Utilize Backup de chaves. Tem a certeza? Fazer Backup - Irá perder acesso as suas mensagens encriptadas, se não fizer backup de suas chaves, antes de abandonar esta pagina. + Irá perder acesso as suas mensagens encriptadas, se não fizer backup de suas chaves, antes de abandonar esta página. Ficar Utilize Backup de chaves - Se sair desta pagina, irá perder as suas mensagens encriptadas + Se sair desta página, irá perder as suas mensagens encriptadas Não quero as minhas mensagens encriptadas Backup de chaves, em progresso … @@ -1120,7 +1120,7 @@ A visibilidade das mensagens no Matrix é parecida com a dos emails. O nosso esq Razão Configurações avançadas para notificações - Importancia de notificações por evento + Importância de notificações por evento Diagnóstico de Notificações Diagnóstico de falhas diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index 9fbcc509be..dd6caf2ddc 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -1297,17 +1297,17 @@ Не удалось удалить резервную копию (%s) Удаление резервной копии… - Для Безопасного Восстановления Сообщений на этом устройстве, требуется пароль или ключ восстановления. - Резервная копия имеет невалидную подпись верифицированного устройства %s - Резервная копия имеет валидную подпись неверифицированного устройства %s - Резервная копия имеет валидную подпись верифицированного устройства %s. - Резервная копия имеет валидную подпись этого устройства. - Резервная копия подписана устройством с идентификатором: %s. - Ваши ключи не сохраняются с этого устройства. + Для Восстановления Сообщений в этой сессии, требуется пароль или ключ восстановления. + Резервная копия имеет недействительную подпись из подтвержденной сессии %s + "Резервная копия имеет действительную подпись из неподтвержденной сессии %s" + Резервная копия имеет действительно подпись из подтверждённой сессии %s. + Резервная копия имеет действительную подпись с этой сессии. + Резервная копия подписана сессией с идентификатором %s. + Резервные копии ключей этой сессии не сохраняются. - Резервное копирование ключей не активно на этом устройстве. - Резервное копирование ключей успешно настроено для этого устройства. + Резервное копирование ключей не активировано в этой сессии. + Резервное копирование ключей успешно настроено для этой сессии. Удалить резервную копию Восстановить из резервной копии @@ -1335,7 +1335,7 @@ Резервная копия восстановлена %s ! Ошибка получения информации о доверии для резервной копии (%s). - Резервная копия имеет невалиндую подпись неверифицированного устройства %s + Резервная копия имеет недействительную подпись из неподтвержденной сессии %s Не удалось получить последнюю версию ключей восстановления (%s). %d новый ключ был добавлен к этому устройству. @@ -1349,7 +1349,7 @@ Восстановлены резервные копии с %d ключами. Восстановлены резервные копии с %d ключами. - Восстановлены %1$d сеансовые ключи и добавлены %2$d новые ключи, которые не были известны этому устройству + Восстановлены %1$d сессионные ключи и добавлены %2$d новые ключи, которые не были известны этой сессии Невозможно расшифровать резервную копию с помощью этого ключа восстановления: убедитесь, что вы ввели правильный ключ. Невозможно расшифровать резервную копию с помощью этой парольной фразы: убедитесь, что вы ввели верную фразу. @@ -1484,10 +1484,10 @@ \nИмя сессии: %1$s \nПоследний раз в сети: %2$s \nЕсли вы не вошли с другой сессии, проигнорируйте этот запрос. - Непроверенное устройство запрашивает ключи шифрования. -\nИмя устройства: %1$s -\nПоследний раз в сети: %2$s -\nЕсли вы не вошли с другого устройства, проигнорируйте этот запрос. + Непроверенная сессия запрашивает ключи шифрования. +\nИмя сессии: %1$s +\nПоследний раз в сети: %2$s +\nЕсли вы не открывали новую сессию - проигнорируйте этот запрос. Подтвердить Поделиться @@ -1544,7 +1544,7 @@ Неизвестная ошибка Резервная копия существует на домашнем сервере - Похоже, что у вас уже есть резервное копирование ключа настройки с другого устройства. Вы хотите заменить его на тот, который вы создаете\? + "Похоже, у вас уже есть резервная копия ключа настройки из другой сессии. Хотите заменить его новым\?" Заменить Стоп @@ -1811,4 +1811,23 @@ ID комнаты + Латиница + + Используйте менеджер интеграций чтобы управлять ботами, мостами, виджетами и наборами стикеров. +\nМенеджеры интеграций получают данные о конфигурации, могут изменять виджеты, отправлять приглашения в комнаты и устанавливать права от вашего имени. + "Использование может оставить cookie на вашем устройстве и отправить данные в %s:" + Использование может отправить данные в %s: + Не удалось загрузить виджет. +\n%s + Отозвать доступ для меня + + Ваше отображаемое имя + URL вашего аватара + Ваш ID + Данный виджет запрашивает доступ к следующим ресурсам: + Запретить все + Использовать камеру + Использовать микрофон + Получать доступ к медиа, защищённым DRM + diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index 96e0e1201a..2820e0b279 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -2103,8 +2103,8 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani Për siguri ekstra, verifikojeni %s duke parë kontrolluar në të dy pajisjet tuaja një kod njëpërdorimsh. \n \nPër sigurinë maksimale, bëjeni këtë ju vetë. - RiotX (ende) nuk trajton akte të llojit \'%1$s\' - RiotX (ende) nuk trajton mesazhe të llojit \'%1$s\' + RiotX nuk trajton akte të llojit \'%1$s\' + RiotX nuk trajton mesazhe të llojit \'%1$s\' RiotX ndeshi një problem kur vizatohej lëndë e aktit me ID \'%1$s\' Ky sesion s’është në gjendje të ndajë këtë verifikim me sesionet tuaj të tjerë. @@ -2120,7 +2120,7 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani \nKyçet privatë nuk njihen Verifikojeni këtë sesion që t’i vihet shenjë si i besuar & dhe t’i akordohet hyrje te mesazhe të fshehtëzuar. Nëse s’keni bërë hyrjen në këtë sesion, llogaria juaj mund të jetë komprometuar: - Hapni një sesion ekzistues & përdoreni për të verifikuar këtë, duke i akorduar hyrje te mesazhe të fshehtëzuar. Nëse s’hyni dot te një, përdorni kyçin ose fjalëkalimin tuaj për rimarrje. + Hapni një sesion ekzistues & përdoreni për të verifikuar këtë, duke i akorduar hyrje te mesazhe të fshehtëzuar. Jo i Besuar @@ -2129,7 +2129,50 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani Gatit CrossSigning Zeroji Kyçet + A e skanoi me sukses përdoruesi tjetër kodin QR\? Jo Humbi lidhja me shërbyesin + Emër përdoruesi + %s i verifikuar + Mjete Zhvilluesi + Të dhëna Llogarie + + %d votë + %d vota + + + %d votë - Përfundimet finale + %d vota - Përfundimet finale + + Mundësi e Përzgjedhur + Krijoni një pyetësor të thjeshtë + S’hyni dot në një sesion ekzistues\? + Përdorni kyçin ose frazëkalimin tuaj të rimarrjes + + Hyrje e Re + + S’gjenden dot të fshehta në depozitim + Jepni frazëkalimin e fshehtë për në depozitim + Kujdes: + Duhet të hyni në depozitim të fshehtë vetëm nga një pajisje e besuar + Për verifikim sesionesh të tjerë përmes dhënies së frazëkalimit tuaj, hyni te historiku i mesazheve tuaj të sigurt dhe identiteti juaj për cross-signing + + Hiqni… + Doni të dërgohet kjo bashkëngjitje te %1$s\? + + Dërgoje figurën në madhësinë origjinale + Dërgoji figurat në madhësinë origjinale + + + Ripohoni Heqjen + Jeni i sigurt se doni të hiqet (fshihet) kjo veprimtari\? Kini parasysh se, nëse fshini emrin ose ndryshimin e temës së një dhome, ndryshimi mund të zhbëhet. + Përfshini një arsye + Arsye për redaktimin + + Veprimtari e fshirë nga përdorues, arsye: %1$s + Veprimtari e moderuar nga përgjegjësi i dhomës, arsye: %1$s + + Kyçet janë tashmë të përditësuar! + diff --git a/vector/src/main/res/values-tr/strings.xml b/vector/src/main/res/values-tr/strings.xml index 53a5264879..c4e2105154 100644 --- a/vector/src/main/res/values-tr/strings.xml +++ b/vector/src/main/res/values-tr/strings.xml @@ -7,8 +7,8 @@ Koyu Tema Siyah Tema - Eşzamanlama (senkronizasyon) - Olaylar dinleniyor + Senkronize ediliyor… + Etkinlikler dinleniyor Sesli bildirimler Sessiz bildirimler @@ -50,15 +50,15 @@ Yeniden Adlandır İçeriği bildir Şu anki görüşme - Devam eden konferans görüşmesi. -\n%1$s veya %2$s olarak katıl. - Sesli + Devam eden konferans görüşmesi. +\n%1$s veya %2$s olarak katıl + Sesli arama Görüntülü Görüşme başlatılamıyor, lütfen sonra tekrar deneyin Eksik izinler nedeni ile bazı özellikler eksik olabilir… Bu odada bir konferans başlatma davetiyesi göndermek için izniniz olması gerekmektedir Arama gerçekleştirilemiyor - Cihaz ayrıntıları + Oturum bilgileri Konferans görüşmeleri şifreli (encrypted) odalarda desteklenmiyor Ne olursa olsun gönder veya @@ -88,7 +88,7 @@ Odalar Topluluklar - Odaları ara + Oda adlarını filtrele Favorileri filtrele Kişileri filtrele Oda adlarını filtrele @@ -185,7 +185,7 @@ Anahtar yedekleme tamamlanmadı, lütfen bekleyiniz… Oturumu şimdi kapatırsanız şifrelenmiş mesajlarınızı kaybedeceksiniz Anahtar yedekleme işlemi sürüyor. Şimdi oturumu kapatırsanız tüm şifrelenmiş mesajlarınıza erişiminizi kaybedeceksiniz. - Şifrelenmiş mesajlarınızı kaybetmeyi önlemek için Güvenli Anahtar Yedekleme\'nin tüm cihazlarınızda aktif olması gerekmektedir. + Şifrelenmiş mesajlarınıza erişimi kaybetmemek için Güvenli Anahtar Yedekleme\'nin tüm oturumlarınızda etkin olması gerekir. Şifrelenmiş mesajlarımı istemiyorum Anahtarlar yedekleniyor… Anahtar Yedekleme Kullan @@ -225,7 +225,7 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Yedeği Sil Yedeklenmiş şifreleme anahtarlarınız sunucudan silinsin mi? Bundan sonra şifrelenmiş mesaj geçmişini okumak için kurtarma anahtarınızı kullanamayacaksınız. - Bu cihazda Anahtar Yedekleme özelliğini kullanmak için şimdi parolanızla veya kurtarma anahtarınızla geri yükleyin. + Bu oturumda Anahtar Yedekleme özelliğini kullanmak için şimdi parolanızla veya kurtarma anahtarınızla geri yükleyin. Hata Sistem Uyarıları @@ -266,9 +266,9 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Yeni şifre mutlaka girilmeli. Eposta %s adresine gönderildi. Epostadaki yönergeleri takip ettikten sonra, aşağıdaki tuşa tıklayın. Eposta doğrulaması başarısız: epostanızdaki bağlantıya tıkladığınızdan emin olun - Şifreniz sıfırlandı. -\n -\nTüm cihazlarınızdan çıkış yaptınız ve mesaj bildirimleri almayacaksınız. Tekrar bildirimleri alabilmek için her cihazda tekrar giriş yapmalısınız. + Şifreniz sıfırlandı. +\n +\nTüm oturumlardan çıkış yaptınız ve mesaj bildirimleri almayacaksınız. Tekrar bildirimleri alabilmek için her cihazda tekrar giriş yapmalısınız. Lütfen anamakinenin ilkelerini gözden geçirin ve kabul edin: URL http[s]:// ile başlamalıdır @@ -288,16 +288,16 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Kullanıcı adı zaten kullanımda Eposta bağlantısına hala tıklanmadı - Bu cihaza özel uçtan uca şifreleme anahtarı oluşturup, herkese açık anahtarı anamakineye yüklemek için giriş yapmanız gerekli. -\nBu bir kereye mahsus. + Bu oturuma özel uçtan uca şifreleme anahtarı oluşturup, herkese açık anahtarı anamakineye yüklemek için giriş yapmanız gerekli. +\nBu bir kereye mahsus. \nRahatsızlık için üzgünüz. - Diğer cihazlarından şifreleme anahtarlarını tekrar iste. + Diğer oturumlardan şifreleme anahtarlarını tekrar iste. Anahtar isteği gönderildi. İstek gönderildi - Riot\'u mesajları çözebilen farklı bir cihazda açarsanız ordan anahtarları bu cihaza gönderebilirsiniz. + Riot\'u mesajları çözebilen farklı bir cihazda açarsanız ordan anahtarları bu oturuma gönderebilirsiniz. Makbuz Listesini Oku @@ -309,7 +309,7 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Farklı gönder - Orjinal + Orijinal Büyük Orta Küçük @@ -364,12 +364,10 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Riot\'un görüntülü arama yapması için kameranıza ve mikrofonunuza erişmeye ihtiyacı var. \n \nLütfen çıkacak ekranda kamera ve mikrofona erişebilmesine izin verin. - Riot\'un eposta ve telefon numaralarına göre diğer Matrix kullanıcılarını bulmak için rehberinize erişmeye ihtiyacı var. -\n -\nLütfen çıkacak ekranda rehberinize erişebilmesine izin verin. - Riot\'un eposta ve telefon numaralarına göre diğer Matrix kullanıcılarını bulmak için rehberinize erişmeye ihtiyacı var. -\n -\nRiot rehberinize erişebilsin mi\? + Riot eposta ve telefon numaralarına göre diğer Matrix kullanıcılarını bulmak için rehberinizi kontrol edebilir. Eğer bu nedenle rehberinizi paylaşmak istiyorsanız, lütfen açılan ekranda erişime izin verin. + Riot\'un eposta ve telefon numaralarına göre diğer Matrix kullanıcılarını bulmak için rehberinizi kontrol edebilir. +\n +\nRehberinizi bu sebeple paylaşmayı kabul ediyor musunuz\? Üzgünüz. İsteğiniz, yetersiz izinlerden dolayı gerçekleştirilemedi @@ -437,7 +435,7 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını YÖNETİCİ ARAÇLARI ARAMA DİREKT SOHBETLER - CİHAZLAR + OTURUMLAR Davet Odadan ayrıl @@ -455,7 +453,7 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını \nBu işlem uygulamayı yeniden başlatak ve biraz zaman alacak. Kullanıcı ID, Ad ya da eposta Bahset - Cihaz Listesini Göster + Oturum Listesini Göster Bu işlemin geri dönüşü yok kullanıcıyı sen ile aynı erişim seviyesine getiriyorsun. \nEmin misin\? @@ -490,7 +488,7 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Cevap gönder (şifrelenmemiş)… Sunucuya olan bağlantı koptu. Mesaj gönderilemedi. %1$s ya da %2$s yapılsın mı\? - Mesaj gönderilemedi çünkü bilinmeyen bir cihaz belirlendi. %1$s ya da %2$s yapalım mı\? + Mesaj gönderilemedi çünkü bilinmeyen bir oturum belirlendi. %1$s ya da %2$s yapalım mı\? Hepsini tekrar gönder Hepsini iptal et Gönderilmeyen mesajları tekrar gönder @@ -619,9 +617,9 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını \nLütfen hesap ayarlarınızı gözden geçirin. Etkinleştir - Cihaz Ayarları. - Bildirimler bu cihaz için etkinleştirilmiş. - Bildirimler bu cihaz için etkin değil. + Oturum Ayarları. + Bildirimler bu oturum için etkinleştirilmiş. + Bildirimler bu oturum için etkin değil. \nLütfen Riot ayarlarını gözden geçirin. Etkinleştir @@ -680,13 +678,13 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Pil Optimizasyonu Riot Pil Optimizasyonundan etkilenmedi. - Eğer kullanıcı cihazını prize bağlanmamış sabit ve ekranı kapalı bir şekilde bir süre bırakırsa cihaz Derin uyku moduna geçer. Bu uygulamaların internete erişmesini engeller ve yapılacak işlerini, senkronizasyolarını, alarmını erteler + Eğer kullanıcı cihazını prize bağlanmamış sabit ve ekranı kapalı bir şekilde bir süre bırakırsa cihaz Derin uyku moduna geçer. Bu uygulamaların internete erişmesini engeller ve yapılacak işlerini, senkronizasyolarını, alarmını erteler. Optimizasyonu Göz ardı et Normal Azaltılmış gizlilik Uygulamanın arkaplanda çalışması için izne ihtiyacı var - • Bildirimler Google Bulut Mesajlaşma ile gönderildi + • Bildirimler Firebase Bulut Mesajlaşma ile gönderildi • Bildirimler sadece meta verileri içeriyor • Bildirimin mesaj içeriği direkt Matrix anamakinesinden güvenli bir şekilde bulundu • Bildirimler meta ve mesaj verileri içeriyor @@ -694,7 +692,7 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Bildirim sesi Bildirim seslerini bu hesap için etkinleştir - Bildirim seslerini bu cihaz için etkinleştir + Bildirim seslerini bu oturum için etkinleştir Ekranı 3 saniyeliğine açık tut Sesli Bildirimleri Ayarla Arama Bildirimlerini Ayarla @@ -714,7 +712,7 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Açılışta başlat Arkaplanda eşzamanlamayı etkinleştir Eşzamanlama talep süresi doldu - Her talep arasında gecikme + Her senkronizasyon arası gecikme saniye saniye @@ -742,7 +740,7 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Ana ekran Bildirimlerini kaçırdığın odaları öne çıkar Okunmamış mesajlı odaları öne çıkar - Cihazlar + Oturumlar Satır içi URL önizlemesi Bağlantıları anamakine destekliyorsa sohbette önizle. Yazıyor bildirimleri gönder @@ -782,10 +780,10 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Veri kaydetme modu Veri kaydetme modu belirli filtreler uygular bu sayede ilerki güncellemeler ve yazıyor bildirimleri filtrelenir. - Cihaz detayları + Oturum bilgileri ID - Ad - Cihaz Adı + Görünür Ad + Görünür Adı Güncelle Son görülme %1$s @ %2$s Bu işlem ek yetki gerektiriyor. @@ -812,9 +810,9 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Şifre Şifreni değiştir - Eski şifre + Mevcut şifre Yeni şifre - Tekrar şifre + Yeni şifreyi onayla Şifreyi güncellerken hata oluştu Şifren güncellendi %s den/dan tüm mesajlar gösterilsin mi\? @@ -885,8 +883,8 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Uçtan-uca Şifreleme Uçtan-uca Şifreleme etkin Şifrelemeyi etkinleştimek için çıkış yapman gerekiyor. - Sadece doğrulanmış cihazlara şifrele - Asla doğrulanmamış cihaz olan oda da şifrelenmiş mesaj gönderme. + Sadece doğrulanmış oturumlara şifrele + Asla doğrulanmamış oturum olan oda da şifrelenmiş mesaj gönderme. Bu oda yerel adrese sahip değil Yeni adres (örn. #foo:matrix.org) @@ -919,7 +917,7 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Uçtan-uca şifreleme bilgilendirmesi - Olay Bilgilendirmesi + Etkinlik Bilgilendirmesi Kullanıcı idsi Curve25519 kimlik anahtarı Alınmış Ed25519 parmak izi anahtarı @@ -927,11 +925,11 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Oturum ID\'si Çözme hatası - Göndericinin cihaz bilgisi - Cihaz adı - Ad - Cihaz ID\'si - Cihaz anahtarı + Göndericinin oturum bilgisi + Görünür Ad + Görünür Ad + Kimlik + Oturum anahtarı Doğrulama Ed25519 parmak izi @@ -951,15 +949,15 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Oda anahtarlarını içe aktar Oda anahtarını yerel dosyadan içe aktar İçe aktar - Sadece doğrulanmış cihazlara şifrele - Asla bu cihazdan doğrulanmamış cihazlara şifrelenmiş mesaj gönderme. + Sadece doğrulanmış oturumlara şifrele + Asla bu cihazdan doğrulanmamış oturumlara şifrelenmiş mesaj gönderme. %1$d/%2$d anahtar başarı ile içe aktarıldı. Doğrulanmamış Doğrulanmış Karalistede - bilinmeyen cihaz + bilinmeyen oturum yok Doğrula @@ -967,21 +965,21 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Karalisteye ekle Karalisteden sil - Cihazı doğrula - Bu cihazın güvenilir olduğunu doğrulamak için, lütfen sahibi ile iletişime geçin (örn. yüz yüze ya da telefonla arama) ve onlara Kullanıcı Ayarları altında hangi anahtarın aşağıdaki anahtar ile uyuştuğunu sorun: - Eğer uyuşursa aşağıdaki doğrulama tuşuna basın. Eğer uyuşmaz ise biri bu cihazı engelliyor demektir bu durumda sen o kişi karalisteye eklemelisin. Gelecekte doğrulama işlemi daha gelişmiş olacak. + Oturumu doğrula + Bu oturumun güvenilir olduğunu doğrulamak için, lütfen sahibi ile iletişime geçin (örn. yüz yüze ya da telefonla arama) ve onlara Kullanıcı Ayarları altında hangi anahtarın aşağıdaki anahtar ile uyuştuğunu sorun: + Eğer uyuşursa aşağıdaki doğrulama tuşuna basın. Eğer uyuşmaz ise biri bu oturum engelliyor demektir bu durumda sen o kişi karalisteye eklemelisin. Gelecekte doğrulama işlemi daha gelişmiş olacak. Anahtarın uyuştuğunu doğruluyorum - Riot artık uçtan-uca şifrelemeyi destekliyor ancak etkinleştirmen için tekrar giriş yapman gerekli. -\n -\nBunu şimdi yada sonra uygulama ayarlarından yapabilirsin. + Riot artık uçtan-uca şifrelemeyi destekliyor ancak etkinleştirmen için tekrar giriş yapman gerekli. +\n +\nBunu şimdi ya da sonra uygulama ayarlarından yapabilirsin. - Oda bilinmeyen cihaz barındırıyor - Bu oda doğrulanmamış bilinmeyen cihaz barındırıyor. -\nBu cihazların kullanıcıların sahip olduğu cihaz olduklarının garantisi yok. -\nDevam etmeden önce her cihaz için doğrulama işlemi yapmanızı öneririz, fakat isterseniz mesajları doğrulamadan da tekrar gönderebilirsiniz. -\n -\nBilinmeyen Cihazlar: + Oda bilinmeyen oturum barındırıyor + Bu oda doğrulanmamış bilinmeyen oturum barındırıyor. +\nBu oturumların kullanıcıların sahip olduğu cihaz olduklarının garantisi yok. +\nDevam etmeden önce her oturum için doğrulama işlemi yapmanızı öneririz, fakat isterseniz mesajları doğrulamadan da tekrar gönderebilirsiniz. +\n +\nBilinmeyen Oturumlar: Bir oda dizini seç Sunucu müsait değil ya da aşırı yüklenmiş @@ -1045,8 +1043,8 @@ Eğer yeni kurtarma yöntemini siz ayarlamadıysanız, bir saldırgan hesabını Sesli mesaj gönder Bu seçenek mesajları kaydetmek için üçüncü parti bir uygulama gerektiriyor. - %s adlı şifreleme anahatarlarını isteyebilen yeni bir cihaz eklediniz. - %s adlı doğrulanmamış cihaz şifreleme anahtarlarını istiyor. + %s adlı şifreleme anahatarlarını isteyebilen yeni bir oturum eklediniz. + %s adlı doğrulanmamış oturum şifreleme anahtarlarını istiyor. Doğrulamayı başlat Doğrulamadan paylaş İsteği göz ardı et @@ -1236,11 +1234,11 @@ Neden Riot.im’i seçmeliyim? Lütfen bir kopya oluşturun Kurtarma anahtarını bununla paylaş… - Parola kullanılarak Kurtarma anahtarı oluşturuluyor bu işlem bir kaç saniye sürecek. + Parola kullanılarak Kurtarma anahtarı oluşturuluyor bu işlem birkaç saniye sürecek. Kurtarma Anahtarı Beklenmeyen hata Yedekleme Başlatıldı - Şifreleme anahtarın arkaplanda anamakineye yedeklenecek. İlk yedekleme bir kaç dakika sürecek. + Şifreleme anahtarın arkaplanda anamakineye yedeklenecek. İlk yedekleme birkaç dakika sürecek. Emin misin\? @@ -1269,14 +1267,14 @@ Neden Riot.im’i seçmeliyim? Yedekleme bu kurtarma anahtarı ile çözülemez: lütfen doğru kurtarma anahtarını girdiğinizi doğrulayın. Yedekleme İşlendi %s ! - %1$d oturum anahtarı onarıldı ve bu cihaz tarafından bilinmeyen %2$d yeni anahtar eklendi + %1$d oturum anahtarı onarıldı ve bu oturum tarafından bilinmeyen %2$d yeni anahtar eklendi Yedekleme %d anahtarı ile işlendi. Yedekleme %d anahtarları ile işlendi. - %d tane yeni anahtar bu cihaza eklendi. - %d tane yeni anahtar bu cihaza eklendi. + %d yeni anahtar bu oturuma eklendi. + %d tane yeni anahtar bu oturuma eklendi. Son sürüm onarma anahtarı alınırken hata oluştu (%s). @@ -1286,16 +1284,16 @@ Neden Riot.im’i seçmeliyim? Yedeklemeden onar Yedeklemeyi Sil - Anahtar Yedekleme bu cihaz için doğru bir şekilde ile ayarlandı. - Anahtar Yedekleme bu cihazda etkin değil. - Anahtarlarınız bu cihazdan yenilenemiyor. + Anahtar Yedekleme bu oturum için doğru bir şekilde ile ayarlandı. + Anahtar Yedekleme bu oturumda etkin değil. + Anahtarlarınız bu oturumdan yenilenemiyor. - Yedekleme %s ID\'li bilinmeyen cihazdan gelen bir imzaya sahip. - Yedekleme bu cihazdan geçerli imzaya sahip. - Yedekleme %s adlı doğrulanmış cihazdan geçerli imzaya sahip. - Yedekleme %s adlı doğrulanmamış cihazdan geçerli imzaya sahip - Yedekleme %s adlı doğrulanmış cihazdan geçersiz imzaya sahip - Yedekleme %s adlı doğrulanmamış cihazdan geçersiz imzaya sahip + Yedekleme %s ID\'li bilinmeyen oturumdan gelen bir imzaya sahip. + Yedekleme bu oturumdan geçerli imzaya sahip. + Yedekleme %s adlı doğrulanmış oturumdan geçerli imzaya sahip. + Yedekleme %s adlı doğrulanmamış oturumdan geçerli imzaya sahip + Yedekleme %s adlı doğrulanmış oturumdan geçersiz imzaya sahip + Yedekleme %s adlı doğrulanmamış oturumdan geçersiz imzaya sahip Yedekleme için güven bilgisi alınırken hata oluştu (%s). @@ -1307,26 +1305,268 @@ Neden Riot.im’i seçmeliyim? Algoritma İmza - İşlem başlatılıyor - Cihazı doğrula + Hizmet başlatılıyor + Oturumu doğrula Hiç (Yok) Geri Al Bağlantıyı Kes - Önemseme + Yoksay Gözden Geçir Reddet Okunmuş olarak işaretle - Arama yanlış yapılandırılmış odadan dolayı başarısız oldu + Arama yanlış yapılandırılmış sunucudan dolayı başarısız oldu %s kullanmayı deneyin - Yeniden sorma + Tekrar sorma Tek oturum açma ile giriş yap - Hesap kurtarması için email ayarla, ve sonradan da isteğe bağlı olarak başklarının seni tanıyan kişilerin bulması için kullan. + Hesap kurtarma epostası kaydedin ve daha sonra isteğe bağlı olarak sizi tanıyan kişilerin sizi bulabilmesini sağlayın. Bu adrese erişilemiyor, lütfen kontrol et - Bu uygulamanın arkaplanda anasunucuya bağlanması gerekmez, bu durum batarya kullanımını azaltır. + Bu uygulamanın arkaplanda anasunucuya bağlanması gerekmez, bu durum batarya kullanımını azaltır Enter ile mesaj gönder Klavyedeki Enter tuşuna bastığında alt satıra geçmek yerine mesajı gönder + Latn + + Hiçbir kimlik sunucusu yapılandırılmamış. + + Lütfen çağrıların düzgün çalışması için anasunucuzun (%1$s) yetkilisine DÖNÜŞ sunucusunu yapılandırmasını söyleyin. +\n +\nAlternatif olarak %2$s herkese açık sunucuyu kullanmayı da deneyebilirsiniz, ancak bu o kadar da güvenilir olmayacak ve IP adresiniz sunucu ile paylaşılacaktır. Bunu ayrıca Ayarlardan da düzenleyebilirsiniz. + Telefon numarası kaydedin ve daha sonra isteğe bağlı olarak sizi tanıyan kişilerin sizi bulabilmesini sağlayın. + Hesap kurtarma epostası kaydedin. Daha sonra isteğe bağlı eposta veya telefon nuraması kullanarak sizi tanıyan kişilerin sizi bulabilmesini sağlayın. + Hesap kurtarma epostası kaydedin. Daha sonra isteğe bağlı eposta veya telefon nuraması kullanarak sizi tanıyan kişilerin sizi bulabilmesini sağlayın. + Cihazın saldırılara açık, eski bir TLS güvenlik protokolü kullanıyor; güvenliğin için bağlantın engellendi + İkincil çağrı yardımcı sunucusuna izin ver + Anasunucunuz çağrı yardımcı sunucusu vermez ise %s çağrı yardımcı sunucusu olarak kullanılacaktır (Çağrıda IP adresiniz paylaşılacaktır) + Bu eylemi gerçekleştirebilmek için ayarlarınızdan bir kimlik sunucusu ekleyin. + Şifreni doğrula + Riot, cihazın sınırlı kaynaklarını (pil) koruyacak şekilde arka planda senkronize olur. +\nCihazınızın kaynak durumuna bağlı olarak, senkronizasyon işletim sistemi tarafından ertelenebilir. + Gerçek zamanlı için optimize + Riot periyodik olarak belirli bir zamanda (ayarlanabilir) arka planda senkronize olur. +\nBu pil ve radyo kullanımını etkileyecek ve riotun olayları dinlediğini belirten kalıcı bir bildirim gösterecektir. + Arka plan senkronizasyonu yok + Tercih Edilen Senkronize Aralığı + %s +\nSenkronizasyon kaynağın (pil) yada cihazın durumuna (uyku) bağlı olarak değişebilir. + Entegrasyonlar + Botları, köprüleri, widget\'ları ve etiket paketlerini yönetmek için bir entegrasyon yöneticisi kullanın. +\nEntegrasyon yöneticileri yapılandırma verilerini alır ve widget\'ları değiştirebilir, oda davetlerini gönderebilir ve sizin adınıza yetki seviyelerini ayarlayabilir. + Keşif + Keşif ayarlarını düzenle. + Entegrasyonlara izin ver + Entegre Yöneticisi + + Şifreyi güncelle + Şifre geçersiz + Şifreler uyuşmuyor + + Medya + Varsayılan sıkıştırma + Seç + Varsayılan medya kaynağı + Seç + Deklanşör sesi çal + + Görünür ad (iletişim kurduğunuz kişilere görünen) + Bir oturumun görünür adı iletişim kurduğunuz kişilere görünür + bilinmeyen ip + + %1$s: 1 mesaj + %1$s: %2$d tane mesaj + + + %d bildirim + %d tane bildirim + + + Yeni Etkinlik + Oda + Yeni Mesajlar + Yeni Davet + Ben + ** Görderme başarısız - lütfen alan aç + + Widget + Widget Yükle + Bu widget\'ı ekleyen: + Bunu kullanmak çerezler oluşturabilir ve %s ile veri paylaşabilir: + Bunu kullanmak %s ile veri paylaşabilir: + Widget yüklenemedi. +\n%s + Widget\'ı yenile + Tarayıcıda Aç + Benim için erişimi iptal et + + Görünür adın + Avatarının URL\'si + Kullanıcı kimliğin + Teman + Widget kimliği + Oda kimliği + + + Üzgünüz, jitsi konferans aramaları eski cihazlarda desteklenmiyor (Android 5.0 altı) + Bu widget şu kaynakları kullanmak istiyor: + İzin ver + Hepsini reddet + Kamera kullan + Mikrofon kullan + DRM korumalı içerik oynat + + Hiçbir entegrasyon düzenleyicisi yapılandırılmamış. + Devam etmek için Hizmet Şartlarını kabul etmeniz gerekir. + + Yeni oturum şifreleme anahtarlarını talep ediyor. Oturum adı: %1$s +\nSon görülme: %2$s +\nEğer farklı bir oturumda giriş yapmadıysanız bu talebi reddedin. + Bir doğrulanmamış oturum şifreleme anahtarlarını talep ediyor. +\nOturum adı: %1$s +\nSon görülme: %2$s +\nEğer farklı bir oturumda giriş yapmadıysanız bu talebi reddedin. + + Doğrula + Paylaş + Anahtar Paylaşma Talebi + Reddet + + Ana sunucunuzda bir yedek zaten var + Başka bir oturumda zaten kurum anahtarı yapmışsın gibi görünüyor. Bunları şu anki oluşturulanlar ile değiştirmek ister misin\? + Değiştir + Dur + + Yedek durumu kontrol ediliyor + Geçersiz anasunucu keşif cevabı + Sunucu Ayarlarını Otomatik Doldur + Riot userld alan adı için özel sunucu yapılandırması buldu \"%1$s\": +\n%2$s + Yapılandırmayı kullan + + Geçersiz ya da süresi dolmuş girdilerden dolayı çıkış yaptınız. + + Kısa metin dizinisi karşılaştırarak doğrulayın. + Yüksek güvenlik için bunu yüz yüze ya da farklı güvenilir iletişim yolu kullanarak yapmanızı tavsiye ederiz. + Doğrulamaya Başla + Gelen Doğrulama Talebi + Güvenilir olarak işaretlemek için bu oturumu doğrulayın. Partnerlerin oturumlarına güvenmek, uçtan uca şifreli mesajlar kullanırken size ekstra huzur verir. + Bu oturumun doğrulanmak oturumu kendinizde ve ayrıca partnerinizde güvenilir olarak işaretleyecektir. + + Partnerinizin ekranında aşağıdaki emojinin göründüğünü onaylayarak bu oturumu doğrulayın + Partnerinizin ekranında aşağıdaki sayıların göründüğünü onaylayarak bu oturumu doğrulayın + + Gelen bir doğrulama talebi aldın. + Talebi görüntüle + Partnerinizin kabul etmesi bekleniyor… + + Doğrulandı! + Bu oturumu başarıyla doğruladın. + Bu kullanıcı ile güvenli mesajlar uçtan uca şifrelenir ve üçüncü şahıslar tarafından okunamaz. + Anlaşıldı + + Hiçbir şey gözükmüyor mu\? Şimdilik bütün platformlar interaktif doğrulamayı desteklemiyor. Eski doğrulamayı kullanın. + Eski doğrulama yöntemini kullan. + + Anahtar Doğrulaması + Talep İptal edildi + Diğer grup doğrulamayı iptal etti. +\n%s + Doğrulama iptal edildi. +\nSebep: %s + + Etkileşimli Oturum Doğrulama + Doğrulama Talebi + %s oturumunu doğrulamak istiyor + + Kullanıcı doğrulamayı iptal etti + Doğrulama işlemi zaman aşımına uğradı + Oturum bu işlemi bilmiyor + Oturum anahtar anlaşması, hash, MAC, yada SAS yöntemi üzerinde anlaşamaz + Hash tahaddütü eşleşmedi + SAS eşleşmedi + Oturum beklenmeyen bir mesaj aldı + Geçersiz bir mesaj alındı + Anahtar uyuşmazlığı + Kullanıcı uyuşmazlığı + Bilinmeyen Hata + + Herhangi bir kimlik sunucusu kullanmıyorsunuz + Hiçbir kimlik sunucusu yapılandırılmamış, bu şifrenizi değiştirebilmeniz için gerekli. + + Riot\'un önceki sürümlerinde, kimlik sunucunuzun (%1$s) hesabınıza erişebilmesini sağlayan bir güvenlik hatası vardı. %2$s sunucusuna güveniyorsanız, bunu göz ardı edebilirsiniz; aksi takdirde lütfen çıkış yapın ve tekrar giriş yapın. +\n +\nDaha fazla ayrıntıya buradan bakabilirsiniz: +\nhttps://medium.com/@RiotChat/36b4792ea0d6 + + Başka bir anasunucuya bağlanmaya çalışıyorsunuz gibi görünüyor. Çıkış yapmak ister misiniz\? + + Düzenle + Yanıtla + + Tekrar dene + Uygulamayı kullanmaya başlamak için bir odaya katıl. + Sana bir davet gönderdi + %s seni davet etti + + Her şey tamam! + Hiç okunmamış mesajınız bulunmuyor + Eve hoşgeldiniz! + Okunmamış mesajları burada bulursun + Tartışmalar + Doğrudan mesajların burada görünecek + Odalar + Odalar burada görünecek + + Tepkiler + Bencede + Beğendim + Bir Tepki Ekle + Tekpileri Görüntüle + Tepkiler + + Etkinlik kullanıcı tarafından silindi + Etkinlik oda yöneticisi tarafından yönetildi + %2$s tarihinde %1$s tarafından düzenlendi + + + Hatalı etkinlik, görüntülenemiyor + Yeni Oda Oluştur + Internet yok. Lütfen internet bağlantını kontrol et. + Değiştir + Ağı değiştir + Lütfen bekleyin… + Tüm Topluluklar + + Bu oda önizlenemez + RiotX henüz herkese-açık odaları önizlemeyi desteklemiyor + + Odalar + Doğrudan Mesajlar + + Yeni Oda + OLUŞTUR + Oda Adı + Herkese Açık + Herkes bu odaya katılabilir + Oda Dizini + Bu odayı oda dizininde yayınla + + Güven bilgisi alınırken hata oluştu + Anahtar yedek verileri alınırken hata oluştu + + Betaya hoşgeldiniz! + RiotX geliştirme sürecindeyken bazı özellikler eksik olabilir ve hatalar ile karşılaşabilirsiniz. + Bu geçerli bir Matrix sunucu adresi değil + Bu URL ile ev-sunucusuna erişilemiyor, lütfen kontrol edin + Bunu Riot mobil ile yapamazsınız + Kimlik doğrulama gereklidir + + + Arkaplan Senkronizasyon Modu (Deneysel) + Batarya için optimize edildi + Uygulama arka plandayken gelen mesajlar için uyarılmayacaksınız. + Ayarlar güncellenemedi. + + diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index ea1506ca99..4311c2cc24 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -1373,7 +1373,7 @@ Riot 在后台时的工作将被显著的限制,这可能会影响消息通知 抱歉,旧设备 (Android 系统版本低于 5.0)不支持使用 Jitsi 创建电话会议 - 验证设备 + 验证会话 未知 IP 一个新设备正在请求加密密钥。 diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index 111ee26b36..137d9aef77 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -2033,8 +2033,8 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意 跳至讀取回條 - RiotX 尚無法處理類型為「%1$s」的事件 - RiotX 尚無法處理類型為「%1$s」的訊息 + RiotX 無法處理類型為「%1$s」的事件 + RiotX 無法處理類型為「%1$s」的訊息 在彩現 id「%1$s」事件的內容時,RiotX 遇到問題 取消忽略 @@ -2097,7 +2097,7 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意 其他使用者可能不會信任它 全面的安全性 - 開啟既有的工作階段並使用它來驗證這個,讓它可以存取已加密的訊息。如果您無法存取,請使用您的復原金鑰或通關密語。 + 開啟既有的工作階段並使用它來驗證這個,讓它可以存取已加密的訊息。 驗證 @@ -2119,8 +2119,47 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意 QR code + 其他使用者是否掃苗 QR code 成功? 到伺服器的連線已遺失 + 使用者名稱 + 開發者工具 + 帳號資料 + + %d 投票 + + + %d 投票 - 最後結果 + + 已選取的選項 + 建立簡易投票 + 無法存取既有的工作階段? + 使用您的復原金鑰或通關密語 + + 新登入 + + 在儲存空間中找不到秘密 + 輸入秘密儲存空間通關密語 + 警告: + 您僅能從受信任的裝置存取秘密儲存空間 + 透過輸入通關密語來存取您的安全訊息歷史與您的交叉簽章身份以驗證其他工作階段 + + 移除…… + 您想要傳送此附件到 %1$s 嗎? + + 使用原始大小傳送圖片 + + + 確認移除 + 您確定您想要移除(刪除)此活動嗎?注意,如果您刪除聊天室名稱或變更主題,則可能會撤銷變更。 + 包含理由 + 修改原因 + + 被使用者刪除的活動,理由:%1$s + 由聊天室管理員管理的活動,理由:%1$s + + 金鑰已為最新! + From 9111800e7a64cbef4d0aa271b68446941552249d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 14 Apr 2020 17:23:59 +0200 Subject: [PATCH 142/156] Create specific string for RiotX --- .../main/java/im/vector/riotx/features/login/LoginFragment.kt | 2 +- vector/src/main/res/values/strings.xml | 2 +- vector/src/main/res/values/strings_riotX.xml | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) 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 01cc19fa5b..6fb746e095 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 @@ -81,7 +81,7 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() { val login = loginField.text.toString() val password = passwordField.text.toString() - loginViewModel.handle(LoginAction.LoginOrRegister(login, password, getString(R.string.login_mobile_device))) + loginViewModel.handle(LoginAction.LoginOrRegister(login, password, getString(R.string.login_mobile_device_riotx))) } private fun cleanupUi() { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index ae2cb7bbbb..3f176c4ee9 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -287,7 +287,7 @@ This is not a valid Matrix server address Cannot reach a homeserver at this URL, please check it Your device is using an outdated TLS security protocol, vulnerable to attack, for your security you will not be able to connect - RiotX Android + Mobile Invalid username/password The access token specified was not recognised diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 9ef21170a4..1752197c28 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -7,6 +7,7 @@ + RiotX Android Key Requests From e54077e020c2a31f27dc7770e47d23d37e64b311 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 14 Apr 2020 17:26:23 +0200 Subject: [PATCH 143/156] String copied to Riot project --- vector/src/main/res/values/strings.xml | 104 ++++++++++++++++++ vector/src/main/res/values/strings_riotX.xml | 106 +------------------ 2 files changed, 106 insertions(+), 104 deletions(-) diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 3f176c4ee9..6b0591595a 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2194,4 +2194,108 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming Keys are already up to date! + RiotX Android + + Key Requests + + Unlock encrypted messages history + + Refresh + + New Session + Tap to review & verify + Use this session to verify your new one, granting it access to encrypted messages. + This wasn’t me + Your account may be compromised + + If you cancel, you won’t be able to read encrypted messages on this device, and other users won’t trust it + If you cancel, you won’t be able to read encrypted messages on your new device, and other users won’t trust it + You won’t verify %1$s (%2$s) if you cancel now. Start again in their user profile. + + + One of the following may be compromised:\n\n- Your password\n- Your homeserver\n- This device, or the other device\n- The internet connection either device is using\n\nWe recommend you change your password & recovery key in Settings immediately. + + + Verify your devices from Settings. + Verification Cancelled + + Message Password + Message Key + Account Password + + + Set a %s + Generate a Message Key + + + Confirm %s + + + Enter your %s to continue. + + + Secure & unlock encrypted messages and trust with a %s. + + Enter your %s again to confirm it. + Don’t re-use your account password. + + + This might take several seconds, please be patient. + Setting up recovery. + Your recovery key + You‘re done! + Keep it safe + Finish + + + Use this %1$s as a safety net in case you forget your %2$s. + + Publishing created identity keys + Generating secure key from passphrase + Defining SSSS default Key + Synchronizing Master key + Synchronizing User key + Synchronizing Self Signing key + Setting Up Key Backup + + + + Your %2$s & %1$s are now set.\n\nKeep them safe! You’ll need them to unlock encrypted messages and secure information if you lose all of your active sessions. + + + Print it and store it somewhere safe + Save it on a USB key or backup drive + Copy it to your personal cloud storage + + You cannot do that from mobile + + Setting a Message Password lets you secure & unlock encrypted messages and trust.\n\nIf you don’t want to set a Message Password, generate a Message Key instead. + Setting a Message Password lets you secure & unlock encrypted messages and trust. + + + Encryption enabled + Messages in this room are end-to-end encrypted. Learn more & verify users in their profile. + Encryption not enabled + The encryption used by this room is not supported + + %s created and configured the room. + + Almost there! Is the other device showing the same shield? + Almost there! Waiting for confirmation… + Waiting for %s… + + Failed to import keys + + Notifications configuration + Messages containing @room + Encrypted messages in one-to-one chats + Encrypted messages in group chats + When rooms are upgraded + Troubleshoot + Set notification importance by event + + Sends a message as plain text, without interpreting it as markdown + + Incorrect username and/or password. The entered password starts or ends with spaces, please check it. + diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 1752197c28..00bf65e121 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -7,112 +7,11 @@ - RiotX Android - - Key Requests - - Unlock encrypted messages history - - Refresh - - - New Session - Tap to review & verify - Use this session to verify your new one, granting it access to encrypted messages. - This wasn’t me - Your account may be compromised - - If you cancel, you won’t be able to read encrypted messages on this device, and other users won’t trust it - If you cancel, you won’t be able to read encrypted messages on your new device, and other users won’t trust it - You won’t verify %1$s (%2$s) if you cancel now. Start again in their user profile. - - - One of the following may be compromised:\n\n- Your password\n- Your homeserver\n- This device, or the other device\n- The internet connection either device is using\n\nWe recommend you change your password & recovery key in Settings immediately. - - - Verify your devices from Settings. - Verification Cancelled - - Message Password - Message Key - Account Password - - - - Set a %s - Generate a Message Key - - - Confirm %s - - - - Enter your %s to continue. - - - Secure & unlock encrypted messages and trust with a %s. - - Enter your %s again to confirm it. - Don’t re-use your account password. - - - This might take several seconds, please be patient. - Setting up recovery. - Your recovery key - You‘re done! - Keep it safe - Finish - - - Use this %1$s as a safety net in case you forget your %2$s. - - Publishing created identity keys - Generating secure key from passphrase - Defining SSSS default Key - Synchronizing Master key - Synchronizing User key - Synchronizing Self Signing key - Setting Up Key Backup - - - - Your %2$s & %1$s are now set.\n\nKeep them safe! You’ll need them to unlock encrypted messages and secure information if you lose all of your active sessions. - - - Print it and store it somewhere safe - Save it on a USB key or backup drive - Copy it to your personal cloud storage - - You cannot do that from mobile - - Setting a Message Password lets you secure & unlock encrypted messages and trust.\n\nIf you don’t want to set a Message Password, generate a Message Key instead. - Setting a Message Password lets you secure & unlock encrypted messages and trust. - - - Encryption enabled - Messages in this room are end-to-end encrypted. Learn more & verify users in their profile. - Encryption not enabled - The encryption used by this room is not supported - - %s created and configured the room. - - Almost there! Is the other device showing the same shield? - Almost there! Waiting for confirmation… - Waiting for %s… - - Failed to import keys - - Notifications configuration - Messages containing @room - Encrypted messages in one-to-one chats - Encrypted messages in group chats - When rooms are upgraded - Troubleshoot - Set notification importance by event + @@ -127,8 +26,7 @@ - Sends a message as plain text, without interpreting it as markdown + - Incorrect username and/or password. The entered password starts or ends with spaces, please check it. From 68ca0e9d4bd6170706e7b0b46d17143d2687c798 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 14 Apr 2020 17:35:11 +0200 Subject: [PATCH 144/156] Fix / sending event not always updating --- CHANGES.md | 1 + .../internal/session/room/timeline/DefaultTimeline.kt | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 16b39cc6b1..25926b5937 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,6 +25,7 @@ Bugfix 🐛: - Cross- Signing | After signin in new session, verification paper trail in DM is off (#1191) - Failed to encrypt message in room (message stays in red), [thanks to pwr22] (#925) - Cross-Signing | web <-> riotX After QR code scan, gossiping fails (#1210) + - Local echo are not updated in timeline (for failed & encrypted states) Translations 🗣: - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 53bd620e51..f2bee734ce 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -171,10 +171,13 @@ internal class DefaultTimeline( val realm = Realm.getInstance(realmConfiguration) backgroundRealm.set(realm) - roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()?.also { - it.sendingTimelineEvents.addChangeListener { _ -> - postSnapshot() + roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() + roomEntity?.sendingTimelineEvents?.addChangeListener { events -> + // Remove in memory as soon as they are known by database + events.forEach { te -> + inMemorySendingEvents.removeAll { te.eventId == it.eventId } } + postSnapshot() } nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() From f2b684aa9ef2f6c73edbd2ad5fa16adbadabeb30 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 14 Apr 2020 18:23:20 +0200 Subject: [PATCH 145/156] Fix / user and self signing failing bad copy paste --- .../crypto/crosssigning/DefaultCrossSigningService.kt | 4 ++-- .../internal/crypto/tasks/InitializeCrossSigningTask.kt | 6 ++++-- 2 files changed, 6 insertions(+), 4 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 addf41d3ab..2166e4be3a 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 @@ -159,8 +159,8 @@ internal class DefaultCrossSigningService @Inject constructor( setUserKeysAsTrusted(userId, true) cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK) masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) } - userPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) } - selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) } + userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) } + selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) } callback?.onSuccess(Unit) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/InitializeCrossSigningTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/InitializeCrossSigningTask.kt index 19cd619d47..9a7d84e235 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/InitializeCrossSigningTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/InitializeCrossSigningTask.kt @@ -97,9 +97,11 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor( Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey") // Sign userSigningKey with master - val signedSSK = JsonCanonicalizer.getCanonicalJson(Map::class.java, CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING) + val signedSSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING) .key(sskPublicKey) - .build().signalableJSONDictionary()).let { masterPkOlm.sign(it) } + .build() + .canonicalSignable() + .let { masterPkOlm.sign(it) } // I need to upload the keys val mskCrossSigningKeyInfo = CryptoCrossSigningKey.Builder(userId, KeyUsage.MASTER) From 0c0e9521f5282ed158dac875fee6c0eabfe2368b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 15 Apr 2020 09:50:25 +0200 Subject: [PATCH 146/156] Fix lint issue --- vector/src/main/res/values-de/strings.xml | 1 - vector/src/main/res/values-eu/strings.xml | 1 - vector/src/main/res/values-fr/strings.xml | 1 - vector/src/main/res/values-hu/strings.xml | 1 - vector/src/main/res/values-it/strings.xml | 1 - vector/src/main/res/values-pl/strings.xml | 1 - vector/src/main/res/values-sq/strings.xml | 1 - vector/src/main/res/values-zh-rTW/strings.xml | 1 - 8 files changed, 8 deletions(-) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 55055048be..77d364b048 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -2224,7 +2224,6 @@ Verwahre deinen Wiederherstellungsschlüssel an einem sehr sicheren Ort wie eine QR-Code - Hat dein Gegenüber den QR-Code erfolgreich gescannt\? Ja Nein diff --git a/vector/src/main/res/values-eu/strings.xml b/vector/src/main/res/values-eu/strings.xml index af69503897..f6a4ab139f 100644 --- a/vector/src/main/res/values-eu/strings.xml +++ b/vector/src/main/res/values-eu/strings.xml @@ -2166,7 +2166,6 @@ Abisua: Fitxategi hau ezabatu daiteke aplikazioa desinstalatzen bada. QR kodea - Beste erabiltzaileak QR kodea ongi eskaneatu du\? Bai Ez diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 9366560cb2..ea432d61bc 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -2174,7 +2174,6 @@ Si vous n’avez pas configuré de nouvelle méthode de récupération, un attaq Code QR - L’autre utilisateur a-t-il bien scanné le code QR \? Oui Non diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index e04abc02b9..c40bd860b8 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -2169,7 +2169,6 @@ Ha nem te állítottad be a visszaállítási metódust, akkor egy támadó pró QR kód - A másik felhasználó sikeresen beolvasta a QR kódot\? Igen Nem diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 159d691c04..65e78c601f 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -2219,7 +2219,6 @@ Codice QR - L\'altro utente ha scansionato correttamente il codice QR\? No diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml index 2deab4a7e5..a747e92ee0 100644 --- a/vector/src/main/res/values-pl/strings.xml +++ b/vector/src/main/res/values-pl/strings.xml @@ -2146,7 +2146,6 @@ Spróbuj uruchomić ponownie aplikację. Kod QR - Czy inny użytkownik pomyślnie zeskanował kod QR\? Tak Nie diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml index 2820e0b279..15164a6e90 100644 --- a/vector/src/main/res/values-sq/strings.xml +++ b/vector/src/main/res/values-sq/strings.xml @@ -2129,7 +2129,6 @@ Që të garantoni se s’ju shpëton gjë, thjesht mbajeni të aktivizuar mekani Gatit CrossSigning Zeroji Kyçet - A e skanoi me sukses përdoruesi tjetër kodin QR\? Jo Humbi lidhja me shërbyesin diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index 137d9aef77..479265bb22 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -2119,7 +2119,6 @@ Matrix 中的消息可見度類似于電子郵件。我們忘記您的郵件意 QR code - 其他使用者是否掃苗 QR code 成功? From 6639f89a6802126ec74fd594198a727f7158f2a9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 15 Apr 2020 12:53:08 +0200 Subject: [PATCH 147/156] Simpler code --- .../im/vector/matrix/android/internal/util/Debouncer.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/Debouncer.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/Debouncer.kt index ba966b18a2..575551da1b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/Debouncer.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/Debouncer.kt @@ -23,12 +23,9 @@ internal class Debouncer(private val handler: Handler) { private val runnables = HashMap() fun debounce(identifier: String, r: Runnable, millis: Long): Boolean { - if (runnables.containsKey(identifier)) { - // debounce - runnables[identifier]?.let { - handler.removeCallbacks(it) - } - } + // debounce + runnables[identifier]?.let { runnable -> handler.removeCallbacks(runnable) } + insertRunnable(identifier, r, millis) return true } From 24d0cdef1f2973ad4e72cf535515b1a38c5ffb99 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 15 Apr 2020 12:53:28 +0200 Subject: [PATCH 148/156] Add missing changes of SDK version and build tools --- .travis.yml | 4 ++-- tools/release/sign_apk.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6b9e9f9946..85bddac7f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,10 +23,10 @@ android: - platform-tools # The BuildTools version used by your project - - build-tools-28.0.3 + - build-tools-29.0.3 # The SDK version used to compile your project - - android-28 + - android-29 before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock diff --git a/tools/release/sign_apk.sh b/tools/release/sign_apk.sh index 7f421c15f4..866510ba13 100755 --- a/tools/release/sign_apk.sh +++ b/tools/release/sign_apk.sh @@ -17,7 +17,7 @@ PARAM_KEYSTORE_PATH=$1 PARAM_APK=$2 # Other params -BUILD_TOOLS_VERSION="28.0.3" +BUILD_TOOLS_VERSION="29.0.3" MIN_SDK_VERSION=19 echo "Signing APK with build-tools version ${BUILD_TOOLS_VERSION} for min SDK version ${MIN_SDK_VERSION}..." From 6dc517584fcbd63cc612cd26d0a43ea92dd6730e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 15 Apr 2020 12:56:33 +0200 Subject: [PATCH 149/156] Fix merge issue --- CHANGES.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e8d68d0181..240203b2c9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,8 +10,6 @@ Features ✨: Improvements 🙌: - Verification DM / Handle concurrent .start after .ready (#794) - Reimplementation of multiple attachment picker - - CrossSigning / Update Shield Logic for DM (#963) - - Xsigning | Complete security new session design update (#1135) - Cross-Signing | Update Shield Logic for DM (#963) - Cross-Signing | Complete security new session design update (#1135) - Cross-Signing | Setup key backup as part of SSSS bootstrapping (#1201) From e26a0bc9ae9b6e63b45345e95405ed0b3e3527d5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 15 Apr 2020 12:57:15 +0200 Subject: [PATCH 150/156] Add Android SDK version --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 240203b2c9..a1748f3623 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -34,7 +34,7 @@ SDK API changes ⚠️: - Increase targetSdkVersion to 29 Build 🧱: - - + - Compile with Android SDK 29 (Android Q) Other changes: - Increase File Logger capacities ( + use dev log preferences) From 6751d88ade168a6739a0899e1de9dc6b7cdfa6ac Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 15 Apr 2020 13:00:03 +0200 Subject: [PATCH 151/156] Remove .idea exclusion --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1f3f9c7819..4a264a28d8 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,3 @@ /tmp ktlint -.idea From c35d854776a9fe37660615d78b6eae1ea372611a Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 10 Apr 2020 17:42:34 +0200 Subject: [PATCH 152/156] Add shield in composer --- CHANGES.md | 1 + .../home/room/detail/RoomDetailFragment.kt | 2 +- .../room/detail/composer/TextComposerView.kt | 30 ++++++++++++++----- ...constraint_set_composer_layout_compact.xml | 25 ++++++++++++---- ...onstraint_set_composer_layout_expanded.xml | 21 +++++++++++-- .../main/res/layout/merge_composer_layout.xml | 7 +++++ vector/src/main/res/values/strings.xml | 4 +-- 7 files changed, 71 insertions(+), 19 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e7f920d788..c1c0a62105 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,7 @@ Improvements 🙌: - Cross-Signing | Restore history after recover from passphrase (#1214) - Cross-Sign | QR code scan confirmation screens design update (#1187) - Emoji Verification | It's not the same butterfly! (#1220) + - Cross-Signing | Composer decoration: shields (#1077) Bugfix 🐛: - Missing avatar/displayname after verification request message (#841) 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 8c45090c91..86f4847ff7 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 @@ -700,7 +700,7 @@ class RoomDetailFragment @Inject constructor( val isRoomEncrypted = summary?.isEncrypted ?: false if (state.tombstoneEvent == null) { composerLayout.visibility = View.VISIBLE - composerLayout.setRoomEncrypted(isRoomEncrypted) + composerLayout.setRoomEncrypted(isRoomEncrypted, state.asyncRoomSummary.invoke()?.roomEncryptionTrustLevel) notificationAreaView.render(NotificationAreaView.State.Hidden) } else { composerLayout.visibility = View.GONE diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerView.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerView.kt index 593ce1a8f6..b494c901a6 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerView.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerView.kt @@ -26,12 +26,15 @@ import android.widget.ImageView import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import androidx.core.content.ContextCompat import androidx.core.text.toSpannable +import androidx.core.view.isVisible import androidx.transition.AutoTransition import androidx.transition.Transition import androidx.transition.TransitionManager import butterknife.BindView import butterknife.ButterKnife +import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel import im.vector.riotx.R import kotlinx.android.synthetic.main.merge_composer_layout.view.* @@ -64,6 +67,8 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib lateinit var composerEditText: ComposerEditText @BindView(R.id.composer_avatar_view) lateinit var composerAvatarImageView: ImageView + @BindView(R.id.composer_shield) + lateinit var composerShieldImageView: ImageView private var currentConstraintSetId: Int = -1 @@ -158,12 +163,23 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib } } - fun setRoomEncrypted(isEncrypted: Boolean) { - composerEditText.setHint( - if (isEncrypted) { - R.string.room_message_placeholder_encrypted - } else { - R.string.room_message_placeholder_not_encrypted - }) + fun setRoomEncrypted(isEncrypted: Boolean, roomEncryptionTrustLevel: RoomEncryptionTrustLevel?) { + if (isEncrypted) { + composerEditText.setHint(R.string.room_message_placeholder_encrypted) + composerShieldImageView.isVisible = true + val shield = when (roomEncryptionTrustLevel) { + RoomEncryptionTrustLevel.Trusted -> { + ContextCompat.getDrawable(context, R.drawable.ic_shield_trusted) + } + RoomEncryptionTrustLevel.Warning -> { + ContextCompat.getDrawable(context, R.drawable.ic_shield_warning) + } + else -> ContextCompat.getDrawable(context, R.drawable.ic_shield_black) + } + composerShieldImageView.setImageDrawable(shield) + } else { + composerEditText.setHint(R.string.room_message_placeholder_not_encrypted) + composerShieldImageView.isVisible = false + } } } diff --git a/vector/src/main/res/layout/constraint_set_composer_layout_compact.xml b/vector/src/main/res/layout/constraint_set_composer_layout_compact.xml index ac04dfe3ec..4607f28f34 100644 --- a/vector/src/main/res/layout/constraint_set_composer_layout_compact.xml +++ b/vector/src/main/res/layout/constraint_set_composer_layout_compact.xml @@ -99,18 +99,31 @@ android:layout_width="32dp" android:layout_height="32dp" android:layout_marginStart="8dp" - android:layout_marginLeft="8dp" android:layout_marginTop="8dp" - android:layout_marginEnd="8dp" - android:layout_marginRight="8dp" + android:layout_marginEnd="4dp" + app:layout_goneMarginEnd="8dp" android:layout_marginBottom="8dp" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@+id/composerEditText" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="1" + app:layout_constraintEnd_toStartOf="@+id/composer_shield" tools:src="@tools:sample/avatars" /> + + + + diff --git a/vector/src/main/res/layout/constraint_set_composer_layout_expanded.xml b/vector/src/main/res/layout/constraint_set_composer_layout_expanded.xml index d246c988e6..17b350542a 100644 --- a/vector/src/main/res/layout/constraint_set_composer_layout_expanded.xml +++ b/vector/src/main/res/layout/constraint_set_composer_layout_expanded.xml @@ -107,17 +107,32 @@ android:layout_marginStart="8dp" android:layout_marginLeft="8dp" android:layout_marginTop="8dp" - android:layout_marginEnd="8dp" + android:layout_marginEnd="4dp" + app:layout_goneMarginEnd="8dp" android:layout_marginRight="8dp" android:layout_marginBottom="8dp" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@+id/composerEditText" + app:layout_constraintEnd_toStartOf="@+id/composer_shield" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="1" tools:src="@tools:sample/avatars" /> + + + + + + %s is typing… %1$s & %2$s are typing… %1$s & %2$s & others are typing… - Send an encrypted message… - Send a message (unencrypted)… + Message… + Message… Send an encrypted reply… Send a reply (unencrypted)… Connectivity to the server has been lost. From 1deacfbb34932e274a3097e34e4fac4bde519a5d Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 14 Apr 2020 10:55:21 +0200 Subject: [PATCH 153/156] Code review --- .../home/room/detail/composer/TextComposerView.kt | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerView.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerView.kt index b494c901a6..e8d8c8876c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerView.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerView.kt @@ -167,16 +167,12 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib if (isEncrypted) { composerEditText.setHint(R.string.room_message_placeholder_encrypted) composerShieldImageView.isVisible = true - val shield = when (roomEncryptionTrustLevel) { - RoomEncryptionTrustLevel.Trusted -> { - ContextCompat.getDrawable(context, R.drawable.ic_shield_trusted) - } - RoomEncryptionTrustLevel.Warning -> { - ContextCompat.getDrawable(context, R.drawable.ic_shield_warning) - } - else -> ContextCompat.getDrawable(context, R.drawable.ic_shield_black) + val shieldRes = when (roomEncryptionTrustLevel) { + RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted + RoomEncryptionTrustLevel.Warning -> R.drawable.ic_shield_warning + else -> R.drawable.ic_shield_black } - composerShieldImageView.setImageDrawable(shield) + composerShieldImageView.setImageDrawable(ContextCompat.getDrawable(context, shieldRes)) } else { composerEditText.setHint(R.string.room_message_placeholder_not_encrypted) composerShieldImageView.isVisible = false From 81012746c45e77930207f3334a3308c31a5c34d1 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 15 Apr 2020 15:11:59 +0200 Subject: [PATCH 154/156] Code review / added new key for message hint --- .../features/home/room/detail/composer/TextComposerView.kt | 4 ++-- vector/src/main/res/values/strings.xml | 4 ++-- vector/src/main/res/values/strings_riotX.xml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerView.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerView.kt index e8d8c8876c..4391009b08 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerView.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/composer/TextComposerView.kt @@ -165,7 +165,7 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib fun setRoomEncrypted(isEncrypted: Boolean, roomEncryptionTrustLevel: RoomEncryptionTrustLevel?) { if (isEncrypted) { - composerEditText.setHint(R.string.room_message_placeholder_encrypted) + composerEditText.setHint(R.string.room_message_placeholder) composerShieldImageView.isVisible = true val shieldRes = when (roomEncryptionTrustLevel) { RoomEncryptionTrustLevel.Trusted -> R.drawable.ic_shield_trusted @@ -174,7 +174,7 @@ class TextComposerView @JvmOverloads constructor(context: Context, attrs: Attrib } composerShieldImageView.setImageDrawable(ContextCompat.getDrawable(context, shieldRes)) } else { - composerEditText.setHint(R.string.room_message_placeholder_not_encrypted) + composerEditText.setHint(R.string.room_message_placeholder) composerShieldImageView.isVisible = false } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 50741dfaa1..6b0591595a 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -515,8 +515,8 @@ %s is typing… %1$s & %2$s are typing… %1$s & %2$s & others are typing… - Message… - Message… + Send an encrypted message… + Send a message (unencrypted)… Send an encrypted reply… Send a reply (unencrypted)… Connectivity to the server has been lost. diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml index 00bf65e121..025491f94b 100644 --- a/vector/src/main/res/values/strings_riotX.xml +++ b/vector/src/main/res/values/strings_riotX.xml @@ -6,7 +6,7 @@ - + Message… From b4a3eb2cb33fb38388ba7e550260e336eea9b9bf Mon Sep 17 00:00:00 2001 From: onurays Date: Wed, 15 Apr 2020 16:39:41 +0300 Subject: [PATCH 155/156] Fix return the exception instead of empty one. --- .../matrix/android/internal/session/DefaultFileService.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt index 6e6beecfb1..66a1dcc9cc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/DefaultFileService.kt @@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.session import android.os.Environment import arrow.core.Try import im.vector.matrix.android.api.MatrixCallback -import im.vector.matrix.android.api.extensions.tryThis import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.file.FileService import im.vector.matrix.android.api.util.Cancelable @@ -78,7 +77,12 @@ internal class DefaultFileService @Inject constructor( .url(resolvedUrl) .build() - val response = tryThis { okHttpClient.newCall(request).execute() } ?: return@flatMap Try.Failure(IOException()) + val response = try { + okHttpClient.newCall(request).execute() + } catch (e: Throwable) { + return@flatMap Try.Failure(e) + } + var inputStream = response.body?.byteStream() Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${inputStream?.available()}") From affe2b59daca1ec87269a1b4ffba974757b361da Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 15 Apr 2020 16:40:01 +0200 Subject: [PATCH 156/156] Fix crash --- vector/src/main/res/values-ru/strings.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index dd6caf2ddc..427eeee332 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -1811,8 +1811,6 @@ ID комнаты - Латиница - Используйте менеджер интеграций чтобы управлять ботами, мостами, виджетами и наборами стикеров. \nМенеджеры интеграций получают данные о конфигурации, могут изменять виджеты, отправлять приглашения в комнаты и устанавливать права от вашего имени. "Использование может оставить cookie на вашем устройстве и отправить данные в %s:"