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 <me@msfjarvis.dev>
This commit is contained in:
Harsh Shandilya 2020-10-02 15:48:26 +05:30
parent d792fa5135
commit cf03c55478
No known key found for this signature in database
GPG key ID: 366D7BBAD1031E80
6 changed files with 300 additions and 267 deletions

View file

@ -85,6 +85,97 @@ class PasswordStore : BaseGitActivity() {
ViewModelProvider.AndroidViewModelFactory(application) 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 { override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
// open search view on search key, or Ctr+F // open search view on search key, or Ctr+F
if ((keyCode == KeyEvent.KEYCODE_SEARCH || keyCode == KeyEvent.KEYCODE_F && event.isCtrlPressed) && if ((keyCode == KeyEvent.KEYCODE_SEARCH || keyCode == KeyEvent.KEYCODE_F && event.isCtrlPressed) &&
@ -288,9 +379,7 @@ class PasswordStore : BaseGitActivity() {
Snackbar.LENGTH_INDEFINITE Snackbar.LENGTH_INDEFINITE
).run { ).run {
setAction(getString(R.string.snackbar_action_grant)) { setAction(getString(R.string.snackbar_action_grant)) {
registerForActivityResult(RequestPermission()) { granted -> storagePermissionRequest.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (granted) checkLocalRepository()
}.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
dismiss() dismiss()
} }
show() show()
@ -305,11 +394,7 @@ class PasswordStore : BaseGitActivity() {
private fun checkLocalRepository() { private fun checkLocalRepository() {
val repo = initialize() val repo = initialize()
if (repo == null) { if (repo == null) {
registerForActivityResult(StartActivityForResult()) { result -> directorySelectAction.launch(UserPreference.createDirectorySelectionIntent(this))
if (result.resultCode == RESULT_OK) {
checkLocalRepository()
}
}.launch(UserPreference.createDirectorySelectionIntent(this))
} else { } else {
checkLocalRepository(getRepositoryDirectory()) checkLocalRepository(getRepositoryDirectory())
} }
@ -422,11 +507,7 @@ class PasswordStore : BaseGitActivity() {
val intent = Intent(this, PasswordCreationActivity::class.java) val intent = Intent(this, PasswordCreationActivity::class.java)
intent.putExtra("FILE_PATH", currentDir.absolutePath) intent.putExtra("FILE_PATH", currentDir.absolutePath)
intent.putExtra("REPO_PATH", getRepositoryDirectory().absolutePath) intent.putExtra("REPO_PATH", getRepositoryDirectory().absolutePath)
registerForActivityResult(StartActivityForResult()) { result -> listRefreshAction.launch(intent)
if (result.resultCode == RESULT_OK) {
refreshPasswordList()
}
}.launch(intent)
} }
fun createFolder() { fun createFolder() {
@ -477,80 +558,7 @@ class PasswordStore : BaseGitActivity() {
val intent = Intent(this, SelectFolderActivity::class.java) val intent = Intent(this, SelectFolderActivity::class.java)
val fileLocations = values.map { it.file.absolutePath }.toTypedArray() val fileLocations = values.map { it.file.absolutePath }.toTypedArray()
intent.putExtra("Files", fileLocations) intent.putExtra("Files", fileLocations)
registerForActivityResult(StartActivityForResult()) { result -> passwordMoveAction.launch(intent)
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)
} }
enum class CategoryRenameError(val resource: Int) { enum class CategoryRenameError(val resource: Int) {

View file

@ -65,6 +65,106 @@ typealias ChangeListener = Preference.OnPreferenceChangeListener
class UserPreference : AppCompatActivity() { class UserPreference : AppCompatActivity() {
private lateinit var prefsFragment: PrefsFragment 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<Preference>(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() { class PrefsFragment : PreferenceFragmentCompat() {
@ -471,7 +571,10 @@ class UserPreference : AppCompatActivity() {
when (intent?.getStringExtra("operation")) { when (intent?.getStringExtra("operation")) {
"get_ssh_key" -> getSshKey() "get_ssh_key" -> getSshKey()
"make_ssh_key" -> makeSshKey(false) "make_ssh_key" -> makeSshKey(false)
"git_external" -> selectExternalGitRepository(fromIntent = true) "git_external" -> {
fromIntent = true
selectExternalGitRepository()
}
} }
prefsFragment = PrefsFragment() prefsFragment = PrefsFragment()
@ -484,41 +587,12 @@ class UserPreference : AppCompatActivity() {
} }
@Suppress("Deprecation") // for Environment.getExternalStorageDirectory() @Suppress("Deprecation") // for Environment.getExternalStorageDirectory()
fun selectExternalGitRepository(fromIntent: Boolean = false) { fun selectExternalGitRepository() {
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(this)
.setTitle(this.resources.getString(R.string.external_repository_dialog_title)) .setTitle(this.resources.getString(R.string.external_repository_dialog_title))
.setMessage(this.resources.getString(R.string.external_repository_dialog_text)) .setMessage(this.resources.getString(R.string.external_repository_dialog_text))
.setPositiveButton(R.string.dialog_ok) { _, _ -> .setPositiveButton(R.string.dialog_ok) { _, _ ->
registerForActivityResult(OpenDocumentTree()) { uri: Uri? -> directorySelectAction.launch(null)
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)
} }
.setNegativeButton(R.string.dialog_cancel, null) .setNegativeButton(R.string.dialog_cancel, null)
.show() .show()
@ -539,26 +613,7 @@ class UserPreference : AppCompatActivity() {
} }
private fun importSshKey() { private fun importSshKey() {
registerForActivityResult(OpenDocument()) { uri: Uri? -> sshKeyImportAction.launch(arrayOf("*/*"))
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("*/*"))
} }
/** /**
@ -584,32 +639,7 @@ class UserPreference : AppCompatActivity() {
* Exports the passwords * Exports the passwords
*/ */
private fun exportPasswords() { private fun exportPasswords() {
registerForActivityResult(object : OpenDocumentTree() { storeExportAction.launch(null)
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)
} }
/** /**
@ -628,28 +658,7 @@ class UserPreference : AppCompatActivity() {
* Pick custom xkpwd dictionary from sdcard * Pick custom xkpwd dictionary from sdcard
*/ */
private fun storeCustomDictionaryPath() { private fun storeCustomDictionaryPath() {
registerForActivityResult(OpenDocument()) { uri -> storeCustomXkpwdDictionaryAction.launch(arrayOf("*/*"))
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<Preference>(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("*/*"))
} }
private val isAccessibilityServiceEnabled: Boolean private val isAccessibilityServiceEnabled: Boolean

View file

@ -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<Intent>? = null private var continueAfterUserInteraction: Continuation<Intent>? = null
private lateinit var directoryStructure: DirectoryStructure private lateinit var directoryStructure: DirectoryStructure
@ -198,17 +210,7 @@ class AutofillDecryptActivity : AppCompatActivity(), CoroutineScope {
val intentToResume = withContext(Dispatchers.Main) { val intentToResume = withContext(Dispatchers.Main) {
suspendCoroutine<Intent> { cont -> suspendCoroutine<Intent> { cont ->
continueAfterUserInteraction = cont continueAfterUserInteraction = cont
registerForActivityResult(StartIntentSenderForResult()) { result -> decryptInteractionRequiredAction.launch(IntentSenderRequest.Builder(pendingIntent.intentSender).build())
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())
} }
} }
decryptCredential(file, intentToResume) decryptCredential(file, intentToResume)

View file

@ -79,6 +79,13 @@ class AutofillFilterView : AppCompatActivity() {
ViewModelProvider.AndroidViewModelFactory(application) 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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(binding.root) setContentView(binding.root)
@ -197,12 +204,7 @@ class AutofillFilterView : AppCompatActivity() {
item.file item.file
) )
// intent?.extras? is checked to be non-null in onCreate // intent?.extras? is checked to be non-null in onCreate
registerForActivityResult(StartActivityForResult()) { result -> decryptAction.launch(AutofillDecryptActivity.makeDecryptFileIntent(
if (result.resultCode == RESULT_OK) {
setResult(RESULT_OK, result.data)
}
finish()
}.launch(AutofillDecryptActivity.makeDecryptFileIntent(
item.file, item.file,
intent!!.extras!!, intent!!.extras!!,
this this

View file

@ -47,6 +47,7 @@ import java.io.File
import java.io.IOException import java.io.IOException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.msfjarvis.openpgpktx.util.OpenPgpApi import me.msfjarvis.openpgpktx.util.OpenPgpApi
import me.msfjarvis.openpgpktx.util.OpenPgpServiceConnection import me.msfjarvis.openpgpktx.util.OpenPgpServiceConnection
import me.msfjarvis.openpgpktx.util.OpenPgpUtils 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 val oldFileName by lazy { intent.getStringExtra(EXTRA_FILE_NAME) }
private var oldCategory: String? = null private var oldCategory: String? = null
private var copy: Boolean = false private var copy: Boolean = false
private var encryptionIntent: Intent = Intent()
private val userInteractionRequiredResult = registerForActivityResult(StartIntentSenderForResult()) { result -> private val userInteractionRequiredResult = registerForActivityResult(StartIntentSenderForResult()) { result ->
if (result.data == null) { 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? { private fun File.findTillRoot(fileName: String, rootPath: File): File? {
val gpgFile = File(this, fileName) val gpgFile = File(this, fileName)
if (gpgFile.exists()) return gpgFile if (gpgFile.exists()) return gpgFile
@ -107,22 +144,7 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
setContentView(root) setContentView(root)
generatePassword.setOnClickListener { generatePassword() } generatePassword.setOnClickListener { generatePassword() }
otpImportButton.setOnClickListener { otpImportButton.setOnClickListener {
registerForActivityResult(StartActivityForResult()) { result -> otpImportAction.launch(IntentIntegrator(this@PasswordCreationActivity)
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) .setOrientationLocked(false)
.setBeepEnabled(false) .setBeepEnabled(false)
.setDesiredBarcodeFormats(QR_CODE) .setDesiredBarcodeFormats(QR_CODE)
@ -307,8 +329,8 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
copyPasswordToClipboard(editPass) copyPasswordToClipboard(editPass)
} }
val data = receivedIntent ?: Intent() encryptionIntent = receivedIntent ?: Intent()
data.action = OpenPgpApi.ACTION_ENCRYPT encryptionIntent.action = OpenPgpApi.ACTION_ENCRYPT
// pass enters the key ID into `.gpg-id`. // pass enters the key ID into `.gpg-id`.
val repoRoot = PasswordRepository.getRepositoryDirectory() val repoRoot = PasswordRepository.getRepositoryDirectory()
@ -333,33 +355,19 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
} }
} }
if (gpgIdentifiers.isEmpty()) { if (gpgIdentifiers.isEmpty()) {
registerForActivityResult(StartActivityForResult()) { result -> gpgKeySelectAction.launch(Intent(this@PasswordCreationActivity, GetKeyIdsActivity::class.java))
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))
return@with return@with
} }
val keyIds = gpgIdentifiers.filterIsInstance<GpgIdentifier.KeyId>().map { it.id }.toLongArray() val keyIds = gpgIdentifiers.filterIsInstance<GpgIdentifier.KeyId>().map { it.id }.toLongArray()
if (keyIds.isNotEmpty()) { if (keyIds.isNotEmpty()) {
data.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keyIds) encryptionIntent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keyIds)
} }
val userIds = gpgIdentifiers.filterIsInstance<GpgIdentifier.UserId>().map { it.email }.toTypedArray() val userIds = gpgIdentifiers.filterIsInstance<GpgIdentifier.UserId>().map { it.email }.toTypedArray()
if (userIds.isNotEmpty()) { 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 content = "$editPass\n$editExtra"
val inputStream = ByteArrayInputStream(content.toByteArray()) val inputStream = ByteArrayInputStream(content.toByteArray())
@ -386,7 +394,7 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
} }
lifecycleScope.launch(Dispatchers.IO) { 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)) { when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
OpenPgpApi.RESULT_CODE_SUCCESS -> { OpenPgpApi.RESULT_CODE_SUCCESS -> {
runCatching { runCatching {

View file

@ -31,6 +31,30 @@ import me.msfjarvis.openpgpktx.util.OpenPgpApi
class FolderCreationDialogFragment : DialogFragment() { 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 { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val alertDialogBuilder = MaterialAlertDialogBuilder(requireContext()) val alertDialogBuilder = MaterialAlertDialogBuilder(requireContext())
alertDialogBuilder.setTitle(R.string.title_create_folder) alertDialogBuilder.setTitle(R.string.title_create_folder)
@ -53,7 +77,7 @@ class FolderCreationDialogFragment : DialogFragment() {
val dialog = requireDialog() val dialog = requireDialog()
val folderNameView = dialog.findViewById<TextInputEditText>(R.id.folder_name_text) val folderNameView = dialog.findViewById<TextInputEditText>(R.id.folder_name_text)
val folderNameViewContainer = dialog.findViewById<TextInputLayout>(R.id.folder_name_container) val folderNameViewContainer = dialog.findViewById<TextInputLayout>(R.id.folder_name_container)
val newFolder = File("$currentDir/${folderNameView.text}") newFolder = File("$currentDir/${folderNameView.text}")
folderNameViewContainer.error = when { folderNameViewContainer.error = when {
newFolder.isFile -> getString(R.string.folder_creation_err_file_exists) newFolder.isFile -> getString(R.string.folder_creation_err_file_exists)
newFolder.isDirectory -> getString(R.string.folder_creation_err_folder_exists) newFolder.isDirectory -> getString(R.string.folder_creation_err_folder_exists)
@ -63,27 +87,7 @@ class FolderCreationDialogFragment : DialogFragment() {
newFolder.mkdirs() newFolder.mkdirs()
(requireActivity() as PasswordStore).refreshPasswordList(newFolder) (requireActivity() as PasswordStore).refreshPasswordList(newFolder)
if (dialog.findViewById<MaterialCheckBox>(R.id.set_gpg_key).isChecked) { if (dialog.findViewById<MaterialCheckBox>(R.id.set_gpg_key).isChecked) {
val gpgIdentifierFile = File(newFolder, ".gpg-id") keySelectAction.launch(Intent(requireContext(), GetKeyIdsActivity::class.java))
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))
return return
} else { } else {
dismiss() dismiss()