diff --git a/CHANGES.md b/CHANGES.md index 60d66014ef..81a0e11525 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,7 +25,7 @@ Build 🧱: - Revert to build-tools 3.5.3 Other changes: - - + - Use Intent.ACTION_CREATE_DOCUMENT to save megolm key or recovery key in a txt file Changes in Riot.imX 0.91.4 (2020-07-06) =================================================== diff --git a/vector/src/main/java/im/vector/riotx/core/extensions/Fragment.kt b/vector/src/main/java/im/vector/riotx/core/extensions/Fragment.kt index 7c1cae3644..7c66cb61dd 100644 --- a/vector/src/main/java/im/vector/riotx/core/extensions/Fragment.kt +++ b/vector/src/main/java/im/vector/riotx/core/extensions/Fragment.kt @@ -16,13 +16,12 @@ package im.vector.riotx.core.extensions -import android.content.ActivityNotFoundException -import android.content.Intent +import android.app.Activity import android.os.Parcelable import androidx.fragment.app.Fragment import im.vector.riotx.R import im.vector.riotx.core.platform.VectorBaseFragment -import im.vector.riotx.core.utils.toast +import im.vector.riotx.core.utils.selectTxtFileToWrite import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -98,27 +97,25 @@ fun Fragment.getAllChildFragments(): List { const val POP_BACK_STACK_EXCLUSIVE = 0 fun Fragment.queryExportKeys(userId: String, requestCode: Int) { - // We need WRITE_EXTERNAL permission -// if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, -// this, -// PERMISSION_REQUEST_CODE_EXPORT_KEYS, -// R.string.permissions_rationale_msg_keys_backup_export)) { - // WRITE permissions are not needed - val timestamp = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).let { - it.format(Date()) - } - val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) - intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.type = "text/plain" - intent.putExtra( - Intent.EXTRA_TITLE, - "riot-megolm-export-$userId-$timestamp.txt" - ) + val timestamp = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date()) - try { - startActivityForResult(Intent.createChooser(intent, getString(R.string.keys_backup_setup_step1_manual_export)), requestCode) - } catch (activityNotFoundException: ActivityNotFoundException) { - activity?.toast(R.string.error_no_external_application_found) - } -// } + selectTxtFileToWrite( + activity = requireActivity(), + fragment = this, + defaultFileName = "riot-megolm-export-$userId-$timestamp.txt", + chooserHint = getString(R.string.keys_backup_setup_step1_manual_export), + requestCode = requestCode + ) +} + +fun Activity.queryExportKeys(userId: String, requestCode: Int) { + val timestamp = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date()) + + selectTxtFileToWrite( + activity = this, + fragment = null, + defaultFileName = "riot-megolm-export-$userId-$timestamp.txt", + chooserHint = getString(R.string.keys_backup_setup_step1_manual_export), + requestCode = requestCode + ) } diff --git a/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt index 2520f44f50..9c2d12514a 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/ExternalApplicationsUtil.kt @@ -424,6 +424,33 @@ fun openPlayStore(activity: Activity, appId: String = BuildConfig.APPLICATION_ID } } +/** + * Ask the user to select a location and a file name to write in + */ +fun selectTxtFileToWrite( + activity: Activity, + fragment: Fragment?, + defaultFileName: String, + chooserHint: String, + requestCode: Int +) { + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.type = "text/plain" + intent.putExtra(Intent.EXTRA_TITLE, defaultFileName) + + try { + val chooserIntent = Intent.createChooser(intent, chooserHint) + if (fragment != null) { + fragment.startActivityForResult(chooserIntent, requestCode) + } else { + activity.startActivityForResult(chooserIntent, requestCode) + } + } catch (activityNotFoundException: ActivityNotFoundException) { + activity.toast(R.string.error_no_external_application_found) + } +} + // ============================================================================================================== // Media utils // ============================================================================================================== diff --git a/vector/src/main/java/im/vector/riotx/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/riotx/core/utils/PermissionsTools.kt index 4790b26ad0..360a5efccc 100644 --- a/vector/src/main/java/im/vector/riotx/core/utils/PermissionsTools.kt +++ b/vector/src/main/java/im/vector/riotx/core/utils/PermissionsTools.kt @@ -63,7 +63,6 @@ const val PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA = 569 const val PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA = 570 const val PERMISSION_REQUEST_CODE_AUDIO_CALL = 571 const val PERMISSION_REQUEST_CODE_VIDEO_CALL = 572 -const val PERMISSION_REQUEST_CODE_EXPORT_KEYS = 573 const val PERMISSION_REQUEST_CODE_CHANGE_AVATAR = 574 const val PERMISSION_REQUEST_CODE_DOWNLOAD_FILE = 575 const val PERMISSION_REQUEST_CODE_PICK_ATTACHMENT = 576 diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt index b99c0e4330..f42fee0030 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupActivity.kt @@ -16,7 +16,6 @@ package im.vector.riotx.features.crypto.keysbackup.setup import android.app.Activity -import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import androidx.appcompat.app.AlertDialog @@ -27,12 +26,9 @@ import im.vector.matrix.android.api.MatrixCallback import im.vector.riotx.R import im.vector.riotx.core.dialogs.ExportKeysDialog import im.vector.riotx.core.extensions.observeEvent +import im.vector.riotx.core.extensions.queryExportKeys import im.vector.riotx.core.extensions.replaceFragment import im.vector.riotx.core.platform.SimpleFragmentActivity -import im.vector.riotx.core.utils.PERMISSIONS_FOR_WRITING_FILES -import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_EXPORT_KEYS -import im.vector.riotx.core.utils.allGranted -import im.vector.riotx.core.utils.checkPermissions import im.vector.riotx.core.utils.toast import im.vector.riotx.features.crypto.keys.KeysExporter @@ -97,7 +93,7 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() { .show() } KeysBackupSetupSharedViewModel.NAVIGATE_MANUAL_EXPORT -> { - exportKeysManually() + queryExportKeys(session.myUserId, REQUEST_CODE_SAVE_MEGOLM_EXPORT) } } } @@ -129,38 +125,6 @@ class KeysBackupSetupActivity : SimpleFragmentActivity() { }) } - private fun exportKeysManually() { - if (checkPermissions(PERMISSIONS_FOR_WRITING_FILES, - this, - PERMISSION_REQUEST_CODE_EXPORT_KEYS, - R.string.permissions_rationale_msg_keys_backup_export)) { - try { - val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) - intent.addCategory(Intent.CATEGORY_OPENABLE) - intent.type = "text/plain" - intent.putExtra(Intent.EXTRA_TITLE, "riot-megolm-export-${session.myUserId}-${System.currentTimeMillis()}.txt") - - startActivityForResult( - Intent.createChooser( - intent, - getString(R.string.keys_backup_setup_step1_manual_export) - ), - REQUEST_CODE_SAVE_MEGOLM_EXPORT - ) - } catch (activityNotFoundException: ActivityNotFoundException) { - toast(R.string.error_no_external_application_found) - } - } - } - - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - if (allGranted(grantResults)) { - if (requestCode == PERMISSION_REQUEST_CODE_EXPORT_KEYS) { - exportKeysManually() - } - } - } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == REQUEST_CODE_SAVE_MEGOLM_EXPORT) { val uri = data?.data diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt index d9a90eb457..6381786e57 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupSharedViewModel.kt @@ -48,6 +48,9 @@ class KeysBackupSetupSharedViewModel @Inject constructor() : ViewModel() { lateinit var session: Session + val userId: String + get() = session.myUserId + var showManualExport: MutableLiveData = MutableLiveData() var navigateEvent: MutableLiveData> = MutableLiveData() diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt index 21a25f1684..de9c0add30 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/keysbackup/setup/KeysBackupSetupStep3Fragment.kt @@ -15,8 +15,10 @@ */ package im.vector.riotx.features.crypto.keysbackup.setup +import android.app.Activity +import android.content.Intent +import android.net.Uri import android.os.Bundle -import android.os.Environment import android.view.View import android.widget.Button import android.widget.TextView @@ -29,25 +31,27 @@ import butterknife.BindView import butterknife.OnClick import com.google.android.material.bottomsheet.BottomSheetDialog import im.vector.riotx.R -import im.vector.riotx.core.files.addEntryToDownloadManager -import im.vector.riotx.core.files.writeToFile import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.utils.LiveEvent -import im.vector.riotx.core.utils.PERMISSIONS_FOR_WRITING_FILES -import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_EXPORT_KEYS -import im.vector.riotx.core.utils.allGranted -import im.vector.riotx.core.utils.checkPermissions import im.vector.riotx.core.utils.copyToClipboard +import im.vector.riotx.core.utils.selectTxtFileToWrite import im.vector.riotx.core.utils.startSharePlainTextIntent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.io.File +import java.io.IOException +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale import javax.inject.Inject class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment() { + companion object { + private const val SAVE_RECOVERY_KEY_REQUEST_CODE = 2754 + } + override fun getLayoutResId() = R.layout.fragment_keys_backup_setup_step3 @BindView(R.id.keys_backup_setup_step3_button) @@ -130,15 +134,15 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment() } dialog.findViewById(R.id.keys_backup_setup_save)?.setOnClickListener { - val permissionsChecked = checkPermissions( - PERMISSIONS_FOR_WRITING_FILES, - this, - PERMISSION_REQUEST_CODE_EXPORT_KEYS, - R.string.permissions_rationale_msg_keys_backup_export + val userId = viewModel.userId + val timestamp = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date()) + selectTxtFileToWrite( + activity = requireActivity(), + fragment = this, + defaultFileName = "recovery-key-$userId-$timestamp.txt", + chooserHint = getString(R.string.save_recovery_key_chooser_hint), + requestCode = SAVE_RECOVERY_KEY_REQUEST_CODE ) - if (permissionsChecked) { - exportRecoveryKeyToFile(recoveryKey) - } dialog.dismiss() } @@ -163,19 +167,19 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment() } } - private fun exportRecoveryKeyToFile(data: String) { + private fun exportRecoveryKeyToFile(uri: Uri, data: String) { GlobalScope.launch(Dispatchers.Main) { Try { withContext(Dispatchers.IO) { - val parentDir = context?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) - val file = File(parentDir, "recovery-key-" + System.currentTimeMillis() + ".txt") - - writeToFile(data, file) - - addEntryToDownloadManager(requireContext(), file, "text/plain") - - file.absolutePath + requireContext().contentResolver.openOutputStream(uri) + ?.use { os -> + os.write(data.toByteArray()) + os.flush() + } + }?.let { + uri.toString() } + ?: throw IOException() } .fold( { throwable -> @@ -200,11 +204,14 @@ class KeysBackupSetupStep3Fragment @Inject constructor() : VectorBaseFragment() } } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - if (allGranted(grantResults)) { - if (requestCode == PERMISSION_REQUEST_CODE_EXPORT_KEYS) { - viewModel.recoveryKey.value?.let { - exportRecoveryKeyToFile(it) + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + when (requestCode) { + SAVE_RECOVERY_KEY_REQUEST_CODE -> { + val uri = data?.data + if (resultCode == Activity.RESULT_OK && uri != null) { + viewModel.recoveryKey.value?.let { + exportRecoveryKeyToFile(uri, it) + } } } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt index 3c2acb1693..73c167fa74 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -42,8 +42,6 @@ import im.vector.riotx.core.intent.analyseIntent import im.vector.riotx.core.intent.getFilenameFromUri import im.vector.riotx.core.platform.SimpleTextWatcher import im.vector.riotx.core.preference.VectorPreference -import im.vector.riotx.core.utils.PERMISSION_REQUEST_CODE_EXPORT_KEYS -import im.vector.riotx.core.utils.allGranted import im.vector.riotx.core.utils.openFileSelection import im.vector.riotx.core.utils.toast import im.vector.riotx.features.crypto.keys.KeysExporter @@ -142,14 +140,6 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( mCrossSigningStatePreference.isVisible = true } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { - if (allGranted(grantResults)) { - if (requestCode == PERMISSION_REQUEST_CODE_EXPORT_KEYS) { - queryExportKeys(activeSessionHolder.getSafeActiveSession()?.myUserId ?: "", REQUEST_CODE_SAVE_MEGOLM_EXPORT) - } - } - } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_SAVE_MEGOLM_EXPORT) { diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 7cb839eba6..a6868a1214 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2526,4 +2526,6 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming You cannot access this message because your session is not trusted by the sender You cannot access this message because the sender purposely did not send the keys Waiting for encryption history + + Save recovery key in