From d792fa5135897a57383fbd147c369edc646fa817 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Fri, 2 Oct 2020 15:45:30 +0530 Subject: [PATCH 1/3] build: uprev dependencies Signed-off-by: Harsh Shandilya --- .../com/zeapo/pwdstore/utils/BiometricAuthenticator.kt | 9 ++++----- buildSrc/src/main/java/Dependencies.kt | 8 ++++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt b/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt index a1b4a27919..12ba84f1d3 100644 --- a/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt +++ b/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt @@ -6,7 +6,6 @@ package com.zeapo.pwdstore.utils import android.app.KeyguardManager import androidx.annotation.StringRes -import androidx.biometric.BiometricConstants import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager.Authenticators import androidx.biometric.BiometricPrompt @@ -43,12 +42,12 @@ object BiometricAuthenticator { super.onAuthenticationError(errorCode, errString) tag(TAG).d { "BiometricAuthentication error: errorCode=$errorCode, msg=$errString" } callback(when (errorCode) { - BiometricConstants.ERROR_CANCELED, BiometricConstants.ERROR_USER_CANCELED, - BiometricConstants.ERROR_NEGATIVE_BUTTON -> { + BiometricPrompt.ERROR_CANCELED, BiometricPrompt.ERROR_USER_CANCELED, + BiometricPrompt.ERROR_NEGATIVE_BUTTON -> { Result.Cancelled } - BiometricConstants.ERROR_HW_NOT_PRESENT, BiometricConstants.ERROR_HW_UNAVAILABLE, - BiometricConstants.ERROR_NO_BIOMETRICS, BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL -> { + BiometricPrompt.ERROR_HW_NOT_PRESENT, BiometricPrompt.ERROR_HW_UNAVAILABLE, + BiometricPrompt.ERROR_NO_BIOMETRICS, BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> { Result.HardwareUnavailableOrDisabled } else -> Result.Failure(errorCode, activity.getString(R.string.biometric_auth_error_reason, errString)) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index bc41a7e2fe..8bb77f7780 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -26,15 +26,15 @@ object Dependencies { private const val lifecycleVersion = "2.3.0-alpha07" - const val activity_ktx = "androidx.activity:activity-ktx:1.2.0-alpha08" + const val activity_ktx = "androidx.activity:activity-ktx:1.2.0-beta01" const val annotation = "androidx.annotation:annotation:1.1.0" const val autofill = "androidx.autofill:autofill:1.1.0-alpha02" const val appcompat = "androidx.appcompat:appcompat:1.3.0-alpha02" - const val biometric = "androidx.biometric:biometric:1.1.0-alpha02" + const val biometric = "androidx.biometric:biometric:1.1.0-beta01" const val constraint_layout = "androidx.constraintlayout:constraintlayout:2.0.1" - const val core_ktx = "androidx.core:core-ktx:1.5.0-alpha02" + const val core_ktx = "androidx.core:core-ktx:1.5.0-alpha03" const val documentfile = "androidx.documentfile:documentfile:1.0.1" - const val fragment_ktx = "androidx.fragment:fragment-ktx:1.3.0-alpha08" + const val fragment_ktx = "androidx.fragment:fragment-ktx:1.3.0-beta01" const val lifecycle_common = "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion" const val lifecycle_livedata_ktx = "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" const val lifecycle_viewmodel_ktx = "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" From cf03c554785ee822be3408edcfb81fa4cf03d0e5 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Fri, 2 Oct 2020 15:48:26 +0530 Subject: [PATCH 2/3] all: refactor ActivityResultContracts usages to adhere to API requirements The newest versions of AndroidX Activity and Fragments correctly enforce the requirement for all contracts to be registered at class init or before the lifecycle has reached `Lifecycle.State.STARTED`. To comply with these requirements, move all instances of `registerForActivityResult` being called at arbitrary points in the code to be done at class init. Signed-off-by: Harsh Shandilya --- .../java/com/zeapo/pwdstore/PasswordStore.kt | 182 +++++++-------- .../java/com/zeapo/pwdstore/UserPreference.kt | 209 +++++++++--------- .../oreo/ui/AutofillDecryptActivity.kt | 24 +- .../oreo/ui/AutofillFilterActivity.kt | 14 +- .../crypto/PasswordCreationActivity.kt | 90 ++++---- .../dialogs/FolderCreationDialogFragment.kt | 48 ++-- 6 files changed, 300 insertions(+), 267 deletions(-) diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt index fc14d8057f..b7ada2870b 100644 --- a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt +++ b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.kt @@ -85,6 +85,97 @@ class PasswordStore : BaseGitActivity() { ViewModelProvider.AndroidViewModelFactory(application) } + private val storagePermissionRequest = registerForActivityResult(RequestPermission()) { granted -> + if (granted) checkLocalRepository() + } + + private val directorySelectAction = registerForActivityResult(StartActivityForResult()) { result -> + if (result.resultCode == RESULT_OK) { + checkLocalRepository() + } + } + + private val listRefreshAction = registerForActivityResult(StartActivityForResult()) { result -> + if (result.resultCode == RESULT_OK) { + refreshPasswordList() + } + } + + private val passwordMoveAction = registerForActivityResult(StartActivityForResult()) { result -> + val intentData = result.data ?: return@registerForActivityResult + val filesToMove = requireNotNull(intentData.getStringArrayExtra("Files")) + val target = File(requireNotNull(intentData.getStringExtra("SELECTED_FOLDER_PATH"))) + val repositoryPath = getRepositoryDirectory().absolutePath + if (!target.isDirectory) { + e { "Tried moving passwords to a non-existing folder." } + return@registerForActivityResult + } + + d { "Moving passwords to ${intentData.getStringExtra("SELECTED_FOLDER_PATH")}" } + d { filesToMove.joinToString(", ") } + + lifecycleScope.launch(Dispatchers.IO) { + for (file in filesToMove) { + val source = File(file) + if (!source.exists()) { + e { "Tried moving something that appears non-existent." } + continue + } + val destinationFile = File(target.absolutePath + "/" + source.name) + val basename = source.nameWithoutExtension + val sourceLongName = getLongName(requireNotNull(source.parent), repositoryPath, basename) + val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename) + if (destinationFile.exists()) { + e { "Trying to move a file that already exists." } + withContext(Dispatchers.Main) { + MaterialAlertDialogBuilder(this@PasswordStore) + .setTitle(resources.getString(R.string.password_exists_title)) + .setMessage(resources.getString( + R.string.password_exists_message, + destinationLongName, + sourceLongName) + ) + .setPositiveButton(R.string.dialog_ok) { _, _ -> + launch(Dispatchers.IO) { + moveFile(source, destinationFile) + } + } + .setNegativeButton(R.string.dialog_cancel, null) + .show() + } + } else { + launch(Dispatchers.IO) { + moveFile(source, destinationFile) + } + } + } + when (filesToMove.size) { + 1 -> { + val source = File(filesToMove[0]) + val basename = source.nameWithoutExtension + val sourceLongName = getLongName(requireNotNull(source.parent), repositoryPath, basename) + val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename) + withContext(Dispatchers.Main) { + commitChange( + resources.getString(R.string.git_commit_move_text, sourceLongName, destinationLongName), + ) + } + } + else -> { + val repoDir = getRepositoryDirectory().absolutePath + val relativePath = getRelativePath("${target.absolutePath}/", repoDir) + withContext(Dispatchers.Main) { + commitChange( + resources.getString(R.string.git_commit_move_multiple_text, relativePath), + ) + } + } + } + } + refreshPasswordList() + plist?.dismissActionMode() + } + override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { // open search view on search key, or Ctr+F if ((keyCode == KeyEvent.KEYCODE_SEARCH || keyCode == KeyEvent.KEYCODE_F && event.isCtrlPressed) && @@ -288,9 +379,7 @@ class PasswordStore : BaseGitActivity() { Snackbar.LENGTH_INDEFINITE ).run { setAction(getString(R.string.snackbar_action_grant)) { - registerForActivityResult(RequestPermission()) { granted -> - if (granted) checkLocalRepository() - }.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) + storagePermissionRequest.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) dismiss() } show() @@ -305,11 +394,7 @@ class PasswordStore : BaseGitActivity() { private fun checkLocalRepository() { val repo = initialize() if (repo == null) { - registerForActivityResult(StartActivityForResult()) { result -> - if (result.resultCode == RESULT_OK) { - checkLocalRepository() - } - }.launch(UserPreference.createDirectorySelectionIntent(this)) + directorySelectAction.launch(UserPreference.createDirectorySelectionIntent(this)) } else { checkLocalRepository(getRepositoryDirectory()) } @@ -422,11 +507,7 @@ class PasswordStore : BaseGitActivity() { val intent = Intent(this, PasswordCreationActivity::class.java) intent.putExtra("FILE_PATH", currentDir.absolutePath) intent.putExtra("REPO_PATH", getRepositoryDirectory().absolutePath) - registerForActivityResult(StartActivityForResult()) { result -> - if (result.resultCode == RESULT_OK) { - refreshPasswordList() - } - }.launch(intent) + listRefreshAction.launch(intent) } fun createFolder() { @@ -477,80 +558,7 @@ class PasswordStore : BaseGitActivity() { val intent = Intent(this, SelectFolderActivity::class.java) val fileLocations = values.map { it.file.absolutePath }.toTypedArray() intent.putExtra("Files", fileLocations) - registerForActivityResult(StartActivityForResult()) { result -> - val intentData = result.data ?: return@registerForActivityResult - val filesToMove = requireNotNull(intentData.getStringArrayExtra("Files")) - val target = File(requireNotNull(intentData.getStringExtra("SELECTED_FOLDER_PATH"))) - val repositoryPath = getRepositoryDirectory().absolutePath - if (!target.isDirectory) { - e { "Tried moving passwords to a non-existing folder." } - return@registerForActivityResult - } - - d { "Moving passwords to ${intentData.getStringExtra("SELECTED_FOLDER_PATH")}" } - d { filesToMove.joinToString(", ") } - - lifecycleScope.launch(Dispatchers.IO) { - for (file in filesToMove) { - val source = File(file) - if (!source.exists()) { - e { "Tried moving something that appears non-existent." } - continue - } - val destinationFile = File(target.absolutePath + "/" + source.name) - val basename = source.nameWithoutExtension - val sourceLongName = getLongName(requireNotNull(source.parent), repositoryPath, basename) - val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename) - if (destinationFile.exists()) { - e { "Trying to move a file that already exists." } - withContext(Dispatchers.Main) { - MaterialAlertDialogBuilder(this@PasswordStore) - .setTitle(resources.getString(R.string.password_exists_title)) - .setMessage(resources.getString( - R.string.password_exists_message, - destinationLongName, - sourceLongName) - ) - .setPositiveButton(R.string.dialog_ok) { _, _ -> - launch(Dispatchers.IO) { - moveFile(source, destinationFile) - } - } - .setNegativeButton(R.string.dialog_cancel, null) - .show() - } - } else { - launch(Dispatchers.IO) { - moveFile(source, destinationFile) - } - } - } - when (filesToMove.size) { - 1 -> { - val source = File(filesToMove[0]) - val basename = source.nameWithoutExtension - val sourceLongName = getLongName(requireNotNull(source.parent), repositoryPath, basename) - val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename) - withContext(Dispatchers.Main) { - commitChange( - resources.getString(R.string.git_commit_move_text, sourceLongName, destinationLongName), - ) - } - } - else -> { - val repoDir = getRepositoryDirectory().absolutePath - val relativePath = getRelativePath("${target.absolutePath}/", repoDir) - withContext(Dispatchers.Main) { - commitChange( - resources.getString(R.string.git_commit_move_multiple_text, relativePath), - ) - } - } - } - } - refreshPasswordList() - plist?.dismissActionMode() - }.launch(intent) + passwordMoveAction.launch(intent) } enum class CategoryRenameError(val resource: Int) { diff --git a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt index 62ce8b46d3..4591521356 100644 --- a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt +++ b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt @@ -65,6 +65,106 @@ typealias ChangeListener = Preference.OnPreferenceChangeListener class UserPreference : AppCompatActivity() { private lateinit var prefsFragment: PrefsFragment + private var fromIntent = false + + @Suppress("DEPRECATION") + private val directorySelectAction = registerForActivityResult(OpenDocumentTree()) { uri: Uri? -> + if (uri == null) return@registerForActivityResult + + tag(TAG).d { "Selected repository URI is $uri" } + // TODO: This is fragile. Workaround until PasswordItem is backed by DocumentFile + val docId = DocumentsContract.getTreeDocumentId(uri) + val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + val path = if (split.size > 1) split[1] else split[0] + val repoPath = "${Environment.getExternalStorageDirectory()}/$path" + val prefs = sharedPrefs + + tag(TAG).d { "Selected repository path is $repoPath" } + + if (Environment.getExternalStorageDirectory().path == repoPath) { + MaterialAlertDialogBuilder(this) + .setTitle(getString(R.string.sdcard_root_warning_title)) + .setMessage(getString(R.string.sdcard_root_warning_message)) + .setPositiveButton("Remove everything") { _, _ -> + prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, uri.path) } + } + .setNegativeButton(R.string.dialog_cancel, null) + .show() + } + prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, repoPath) } + if (fromIntent) { + setResult(RESULT_OK) + finish() + } + + } + + private val sshKeyImportAction = registerForActivityResult(OpenDocument()) { uri: Uri? -> + if (uri == null) return@registerForActivityResult + runCatching { + SshKey.import(uri) + + Toast.makeText(this, resources.getString(R.string.ssh_key_success_dialog_title), Toast.LENGTH_LONG).show() + setResult(RESULT_OK) + finish() + }.onFailure { e -> + MaterialAlertDialogBuilder(this) + .setTitle(resources.getString(R.string.ssh_key_error_dialog_title)) + .setMessage(e.message) + .setPositiveButton(resources.getString(R.string.dialog_ok), null) + .show() + } + } + + private val storeExportAction = registerForActivityResult(object : OpenDocumentTree() { + override fun createIntent(context: Context, input: Uri?): Intent { + return super.createIntent(context, input).apply { + flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION or + Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or + Intent.FLAG_GRANT_PREFIX_URI_PERMISSION + } + } + }) { uri: Uri? -> + if (uri == null) return@registerForActivityResult + val targetDirectory = DocumentFile.fromTreeUri(applicationContext, uri) + + if (targetDirectory != null) { + val service = Intent(applicationContext, PasswordExportService::class.java).apply { + action = PasswordExportService.ACTION_EXPORT_PASSWORD + putExtra("uri", uri) + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + startForegroundService(service) + } else { + startService(service) + } + } + } + + private val storeCustomXkpwdDictionaryAction = registerForActivityResult(OpenDocument()) { uri -> + if (uri == null) return@registerForActivityResult + + Toast.makeText( + this, + this.resources.getString(R.string.xkpwgen_custom_dict_imported, uri.path), + Toast.LENGTH_SHORT + ).show() + + sharedPrefs.edit { putString(PreferenceKeys.PREF_KEY_CUSTOM_DICT, uri.toString()) } + + val customDictPref = prefsFragment.findPreference(PreferenceKeys.PREF_KEY_CUSTOM_DICT) + setCustomDictSummary(customDictPref, uri) + // copy user selected file to internal storage + val inputStream = contentResolver.openInputStream(uri) + val customDictFile = File(filesDir.toString(), XkpwdDictionary.XKPWD_CUSTOM_DICT_FILE).outputStream() + inputStream?.copyTo(customDictFile, 1024) + inputStream?.close() + customDictFile.close() + + setResult(RESULT_OK) + } class PrefsFragment : PreferenceFragmentCompat() { @@ -471,7 +571,10 @@ class UserPreference : AppCompatActivity() { when (intent?.getStringExtra("operation")) { "get_ssh_key" -> getSshKey() "make_ssh_key" -> makeSshKey(false) - "git_external" -> selectExternalGitRepository(fromIntent = true) + "git_external" -> { + fromIntent = true + selectExternalGitRepository() + } } prefsFragment = PrefsFragment() @@ -484,41 +587,12 @@ class UserPreference : AppCompatActivity() { } @Suppress("Deprecation") // for Environment.getExternalStorageDirectory() - fun selectExternalGitRepository(fromIntent: Boolean = false) { + fun selectExternalGitRepository() { MaterialAlertDialogBuilder(this) .setTitle(this.resources.getString(R.string.external_repository_dialog_title)) .setMessage(this.resources.getString(R.string.external_repository_dialog_text)) .setPositiveButton(R.string.dialog_ok) { _, _ -> - registerForActivityResult(OpenDocumentTree()) { uri: Uri? -> - if (uri == null) return@registerForActivityResult - - tag(TAG).d { "Selected repository URI is $uri" } - // TODO: This is fragile. Workaround until PasswordItem is backed by DocumentFile - val docId = DocumentsContract.getTreeDocumentId(uri) - val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - val path = if (split.size > 1) split[1] else split[0] - val repoPath = "${Environment.getExternalStorageDirectory()}/$path" - val prefs = sharedPrefs - - tag(TAG).d { "Selected repository path is $repoPath" } - - if (Environment.getExternalStorageDirectory().path == repoPath) { - MaterialAlertDialogBuilder(this) - .setTitle(getString(R.string.sdcard_root_warning_title)) - .setMessage(getString(R.string.sdcard_root_warning_message)) - .setPositiveButton("Remove everything") { _, _ -> - prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, uri.path) } - } - .setNegativeButton(R.string.dialog_cancel, null) - .show() - } - prefs.edit { putString(PreferenceKeys.GIT_EXTERNAL_REPO, repoPath) } - if (fromIntent) { - setResult(RESULT_OK) - finish() - } - - }.launch(null) + directorySelectAction.launch(null) } .setNegativeButton(R.string.dialog_cancel, null) .show() @@ -539,26 +613,7 @@ class UserPreference : AppCompatActivity() { } private fun importSshKey() { - registerForActivityResult(OpenDocument()) { uri: Uri? -> - if (uri == null) return@registerForActivityResult - runCatching { - SshKey.import(uri) - - Toast.makeText( - this, - this.resources.getString(R.string.ssh_key_success_dialog_title), - Toast.LENGTH_LONG - ).show() - setResult(RESULT_OK) - finish() - }.onFailure { e -> - MaterialAlertDialogBuilder(this) - .setTitle(resources.getString(R.string.ssh_key_error_dialog_title)) - .setMessage(e.message) - .setPositiveButton(resources.getString(R.string.dialog_ok), null) - .show() - } - }.launch(arrayOf("*/*")) + sshKeyImportAction.launch(arrayOf("*/*")) } /** @@ -584,32 +639,7 @@ class UserPreference : AppCompatActivity() { * Exports the passwords */ private fun exportPasswords() { - registerForActivityResult(object : OpenDocumentTree() { - override fun createIntent(context: Context, input: Uri?): Intent { - return super.createIntent(context, input).apply { - flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or - Intent.FLAG_GRANT_WRITE_URI_PERMISSION or - Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or - Intent.FLAG_GRANT_PREFIX_URI_PERMISSION - } - } - }) { uri: Uri? -> - if (uri == null) return@registerForActivityResult - val targetDirectory = DocumentFile.fromTreeUri(applicationContext, uri) - - if (targetDirectory != null) { - val service = Intent(applicationContext, PasswordExportService::class.java).apply { - action = PasswordExportService.ACTION_EXPORT_PASSWORD - putExtra("uri", uri) - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - startForegroundService(service) - } else { - startService(service) - } - } - }.launch(null) + storeExportAction.launch(null) } /** @@ -628,28 +658,7 @@ class UserPreference : AppCompatActivity() { * Pick custom xkpwd dictionary from sdcard */ private fun storeCustomDictionaryPath() { - registerForActivityResult(OpenDocument()) { uri -> - if (uri == null) return@registerForActivityResult - - Toast.makeText( - this, - this.resources.getString(R.string.xkpwgen_custom_dict_imported, uri.path), - Toast.LENGTH_SHORT - ).show() - - sharedPrefs.edit { putString(PreferenceKeys.PREF_KEY_CUSTOM_DICT, uri.toString()) } - - val customDictPref = prefsFragment.findPreference(PreferenceKeys.PREF_KEY_CUSTOM_DICT) - setCustomDictSummary(customDictPref, uri) - // copy user selected file to internal storage - val inputStream = contentResolver.openInputStream(uri) - val customDictFile = File(filesDir.toString(), XkpwdDictionary.XKPWD_CUSTOM_DICT_FILE).outputStream() - inputStream?.copyTo(customDictFile, 1024) - inputStream?.close() - customDictFile.close() - - setResult(RESULT_OK) - }.launch(arrayOf("*/*")) + storeCustomXkpwdDictionaryAction.launch(arrayOf("*/*")) } private val isAccessibilityServiceEnabled: Boolean diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillDecryptActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillDecryptActivity.kt index 9bab9e6f79..a5bdcfe034 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillDecryptActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillDecryptActivity.kt @@ -81,6 +81,18 @@ class AutofillDecryptActivity : AppCompatActivity(), CoroutineScope { } } + private val decryptInteractionRequiredAction = registerForActivityResult(StartIntentSenderForResult()) { result -> + if (continueAfterUserInteraction != null) { + val data = result.data + if (result.resultCode == RESULT_OK && data != null) { + continueAfterUserInteraction?.resume(data) + } else { + continueAfterUserInteraction?.resumeWithException(Exception("OpenPgpApi ACTION_DECRYPT_VERIFY failed to continue after user interaction")) + } + continueAfterUserInteraction = null + } + } + private var continueAfterUserInteraction: Continuation? = null private lateinit var directoryStructure: DirectoryStructure @@ -198,17 +210,7 @@ class AutofillDecryptActivity : AppCompatActivity(), CoroutineScope { val intentToResume = withContext(Dispatchers.Main) { suspendCoroutine { cont -> continueAfterUserInteraction = cont - registerForActivityResult(StartIntentSenderForResult()) { result -> - if (continueAfterUserInteraction != null) { - val data = result.data - if (result.resultCode == RESULT_OK && data != null) { - continueAfterUserInteraction?.resume(data) - } else { - continueAfterUserInteraction?.resumeWithException(Exception("OpenPgpApi ACTION_DECRYPT_VERIFY failed to continue after user interaction")) - } - continueAfterUserInteraction = null - } - }.launch(IntentSenderRequest.Builder(pendingIntent.intentSender).build()) + decryptInteractionRequiredAction.launch(IntentSenderRequest.Builder(pendingIntent.intentSender).build()) } } decryptCredential(file, intentToResume) diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt index 95e49fdd5b..f22c659661 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt @@ -79,6 +79,13 @@ class AutofillFilterView : AppCompatActivity() { ViewModelProvider.AndroidViewModelFactory(application) } + private val decryptAction = registerForActivityResult(StartActivityForResult()) { result -> + if (result.resultCode == RESULT_OK) { + setResult(RESULT_OK, result.data) + } + finish() + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) @@ -197,12 +204,7 @@ class AutofillFilterView : AppCompatActivity() { item.file ) // intent?.extras? is checked to be non-null in onCreate - registerForActivityResult(StartActivityForResult()) { result -> - if (result.resultCode == RESULT_OK) { - setResult(RESULT_OK, result.data) - } - finish() - }.launch(AutofillDecryptActivity.makeDecryptFileIntent( + decryptAction.launch(AutofillDecryptActivity.makeDecryptFileIntent( item.file, intent!!.extras!!, this diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt index eb1303fc41..d0998d6df3 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt @@ -47,6 +47,7 @@ import java.io.File import java.io.IOException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import me.msfjarvis.openpgpktx.util.OpenPgpApi import me.msfjarvis.openpgpktx.util.OpenPgpServiceConnection import me.msfjarvis.openpgpktx.util.OpenPgpUtils @@ -63,6 +64,7 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB private val oldFileName by lazy { intent.getStringExtra(EXTRA_FILE_NAME) } private var oldCategory: String? = null private var copy: Boolean = false + private var encryptionIntent: Intent = Intent() private val userInteractionRequiredResult = registerForActivityResult(StartIntentSenderForResult()) { result -> if (result.data == null) { @@ -80,6 +82,41 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB } } + private val otpImportAction = registerForActivityResult(StartActivityForResult()) { result -> + if (result.resultCode == RESULT_OK) { + binding.otpImportButton.isVisible = false + val intentResult = IntentIntegrator.parseActivityResult(RESULT_OK, result.data) + val contents = "${intentResult.contents}\n" + val currentExtras = binding.extraContent.text.toString() + if (currentExtras.isNotEmpty() && currentExtras.last() != '\n') + binding.extraContent.append("\n$contents") + else + binding.extraContent.append(contents) + snackbar(message = getString(R.string.otp_import_success)) + } else { + snackbar(message = getString(R.string.otp_import_failure)) + } + } + + private val gpgKeySelectAction = registerForActivityResult(StartActivityForResult()) { result -> + if (result.resultCode == RESULT_OK) { + result.data?.getStringArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)?.let { keyIds -> + lifecycleScope.launch { + val gpgIdentifierFile = File(PasswordRepository.getRepositoryDirectory(), ".gpg-id") + withContext(Dispatchers.IO) { + gpgIdentifierFile.writeText(keyIds.joinToString("\n")) + } + commitChange(getString( + R.string.git_commit_gpg_id, + getLongName(gpgIdentifierFile.parentFile!!.absolutePath, repoPath, gpgIdentifierFile.name) + )).onSuccess { + encrypt(encryptionIntent) + } + } + } + } + } + private fun File.findTillRoot(fileName: String, rootPath: File): File? { val gpgFile = File(this, fileName) if (gpgFile.exists()) return gpgFile @@ -107,26 +144,11 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB setContentView(root) generatePassword.setOnClickListener { generatePassword() } otpImportButton.setOnClickListener { - registerForActivityResult(StartActivityForResult()) { result -> - if (result.resultCode == RESULT_OK) { - otpImportButton.isVisible = false - val intentResult = IntentIntegrator.parseActivityResult(RESULT_OK, result.data) - val contents = "${intentResult.contents}\n" - val currentExtras = extraContent.text.toString() - if (currentExtras.isNotEmpty() && currentExtras.last() != '\n') - extraContent.append("\n$contents") - else - extraContent.append(contents) - snackbar(message = getString(R.string.otp_import_success)) - } else { - snackbar(message = getString(R.string.otp_import_failure)) - } - }.launch( - IntentIntegrator(this@PasswordCreationActivity) - .setOrientationLocked(false) - .setBeepEnabled(false) - .setDesiredBarcodeFormats(QR_CODE) - .createScanIntent() + otpImportAction.launch(IntentIntegrator(this@PasswordCreationActivity) + .setOrientationLocked(false) + .setBeepEnabled(false) + .setDesiredBarcodeFormats(QR_CODE) + .createScanIntent() ) } @@ -307,8 +329,8 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB copyPasswordToClipboard(editPass) } - val data = receivedIntent ?: Intent() - data.action = OpenPgpApi.ACTION_ENCRYPT + encryptionIntent = receivedIntent ?: Intent() + encryptionIntent.action = OpenPgpApi.ACTION_ENCRYPT // pass enters the key ID into `.gpg-id`. val repoRoot = PasswordRepository.getRepositoryDirectory() @@ -333,33 +355,19 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB } } if (gpgIdentifiers.isEmpty()) { - registerForActivityResult(StartActivityForResult()) { result -> - if (result.resultCode == RESULT_OK) { - result.data?.getStringArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)?.let { keyIds -> - gpgIdentifierFile.writeText(keyIds.joinToString("\n")) - lifecycleScope.launch { - commitChange(getString( - R.string.git_commit_gpg_id, - getLongName(gpgIdentifierFile.parentFile!!.absolutePath, repoPath, gpgIdentifierFile.name) - )).onSuccess { - encrypt(data) - } - } - } - } - }.launch(Intent(this@PasswordCreationActivity, GetKeyIdsActivity::class.java)) + gpgKeySelectAction.launch(Intent(this@PasswordCreationActivity, GetKeyIdsActivity::class.java)) return@with } val keyIds = gpgIdentifiers.filterIsInstance().map { it.id }.toLongArray() if (keyIds.isNotEmpty()) { - data.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keyIds) + encryptionIntent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keyIds) } val userIds = gpgIdentifiers.filterIsInstance().map { it.email }.toTypedArray() if (userIds.isNotEmpty()) { - data.putExtra(OpenPgpApi.EXTRA_USER_IDS, userIds) + encryptionIntent.putExtra(OpenPgpApi.EXTRA_USER_IDS, userIds) } - data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true) + encryptionIntent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true) val content = "$editPass\n$editExtra" val inputStream = ByteArrayInputStream(content.toByteArray()) @@ -386,7 +394,7 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB } lifecycleScope.launch(Dispatchers.IO) { - api?.executeApiAsync(data, inputStream, outputStream) { result -> + api?.executeApiAsync(encryptionIntent, inputStream, outputStream) { result -> when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { OpenPgpApi.RESULT_CODE_SUCCESS -> { runCatching { diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/FolderCreationDialogFragment.kt b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/FolderCreationDialogFragment.kt index 1eb1c95d64..c06a6d639c 100644 --- a/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/FolderCreationDialogFragment.kt +++ b/app/src/main/java/com/zeapo/pwdstore/ui/dialogs/FolderCreationDialogFragment.kt @@ -31,6 +31,30 @@ import me.msfjarvis.openpgpktx.util.OpenPgpApi class FolderCreationDialogFragment : DialogFragment() { + private lateinit var newFolder: File + + private val keySelectAction = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == AppCompatActivity.RESULT_OK) { + result.data?.getStringArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)?.let { keyIds -> + val gpgIdentifierFile = File(newFolder, ".gpg-id") + gpgIdentifierFile.writeText(keyIds.joinToString("\n")) + val repo = PasswordRepository.getRepository(null) + if (repo != null) { + lifecycleScope.launch { + val repoPath = getRepositoryDirectory().absolutePath + requireActivity().commitChange( + getString( + R.string.git_commit_gpg_id, + BasePgpActivity.getLongName(gpgIdentifierFile.parentFile!!.absolutePath, repoPath, gpgIdentifierFile.name) + ), + ) + dismiss() + } + } + } + } + } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val alertDialogBuilder = MaterialAlertDialogBuilder(requireContext()) alertDialogBuilder.setTitle(R.string.title_create_folder) @@ -53,7 +77,7 @@ class FolderCreationDialogFragment : DialogFragment() { val dialog = requireDialog() val folderNameView = dialog.findViewById(R.id.folder_name_text) val folderNameViewContainer = dialog.findViewById(R.id.folder_name_container) - val newFolder = File("$currentDir/${folderNameView.text}") + newFolder = File("$currentDir/${folderNameView.text}") folderNameViewContainer.error = when { newFolder.isFile -> getString(R.string.folder_creation_err_file_exists) newFolder.isDirectory -> getString(R.string.folder_creation_err_folder_exists) @@ -63,27 +87,7 @@ class FolderCreationDialogFragment : DialogFragment() { newFolder.mkdirs() (requireActivity() as PasswordStore).refreshPasswordList(newFolder) if (dialog.findViewById(R.id.set_gpg_key).isChecked) { - val gpgIdentifierFile = File(newFolder, ".gpg-id") - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == AppCompatActivity.RESULT_OK) { - result.data?.getStringArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)?.let { keyIds -> - gpgIdentifierFile.writeText(keyIds.joinToString("\n")) - val repo = PasswordRepository.getRepository(null) - if (repo != null) { - lifecycleScope.launch { - val repoPath = getRepositoryDirectory().absolutePath - requireActivity().commitChange( - getString( - R.string.git_commit_gpg_id, - BasePgpActivity.getLongName(gpgIdentifierFile.parentFile!!.absolutePath, repoPath, gpgIdentifierFile.name) - ), - ) - dismiss() - } - } - } - } - }.launch(Intent(requireContext(), GetKeyIdsActivity::class.java)) + keySelectAction.launch(Intent(requireContext(), GetKeyIdsActivity::class.java)) return } else { dismiss() From 9dc56214b9e24b3cba341988bd547e8ba9f7c624 Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Fri, 2 Oct 2020 13:12:14 +0000 Subject: [PATCH 3/3] github: switch PR workflow from an allowlist to a blocklist This is going to be less work to maintain going forward since we won't need to change it every time we include a new subproject. Signed-off-by: Harsh Shandilya --- .github/workflows/pull_request.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 1ef3b01313..ec128f1867 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -21,9 +21,9 @@ jobs: pull_number: context.payload.number, per_page: 100 }) - const serviceChanged = result.data.filter(f => f.filename.startsWith("app/") || f.filename.startsWith("buildSrc/") || f.filename.endsWith("gradle") || f.filename.startsWith(".github/workflows/pull_request.yml") || f.filename.startsWith("gradle") || f.filename.endsWith("properties")).length > 0 - console.log(serviceChanged) - return serviceChanged + const shouldRun = result.data.filter(f => !f.filename.endsWith(".md") || !f.filename.endsWith(".txt") || !f.filename.startsWith("contrib/") || !f.filename.endsWith(".yml")).length > 0 + console.log(shouldRun) + return shouldRun - name: Checkout repository if: ${{ steps.service-changed.outputs.result == 'true' }}