diff options
Diffstat (limited to 'app/src/main')
28 files changed, 573 insertions, 553 deletions
diff --git a/app/src/main/java/dev/msfjarvis/aps/data/repo/PasswordRepository.kt b/app/src/main/java/dev/msfjarvis/aps/data/repo/PasswordRepository.kt index 9bf1bce6..f675e80a 100644 --- a/app/src/main/java/dev/msfjarvis/aps/data/repo/PasswordRepository.kt +++ b/app/src/main/java/dev/msfjarvis/aps/data/repo/PasswordRepository.kt @@ -42,10 +42,11 @@ object PasswordRepository { private fun initializeRepository(repositoryDir: File) { val builder = FileRepositoryBuilder() repository = - runCatching { builder.setGitDir(repositoryDir).build() }.getOrElse { e -> - e.printStackTrace() - null - } + runCatching { builder.setGitDir(repositoryDir).build() } + .getOrElse { e -> + e.printStackTrace() + null + } } fun createRepository(repositoryDir: File) { @@ -61,40 +62,40 @@ object PasswordRepository { if (!remotes.contains(name)) { runCatching { - val uri = URIish(url) - val refSpec = RefSpec("+refs/head/*:refs/remotes/$name/*") + val uri = URIish(url) + val refSpec = RefSpec("+refs/head/*:refs/remotes/$name/*") - val remoteConfig = RemoteConfig(storedConfig, name) - remoteConfig.addFetchRefSpec(refSpec) - remoteConfig.addPushRefSpec(refSpec) - remoteConfig.addURI(uri) - remoteConfig.addPushURI(uri) + val remoteConfig = RemoteConfig(storedConfig, name) + remoteConfig.addFetchRefSpec(refSpec) + remoteConfig.addPushRefSpec(refSpec) + remoteConfig.addURI(uri) + remoteConfig.addPushURI(uri) - remoteConfig.update(storedConfig) + remoteConfig.update(storedConfig) - storedConfig.save() - } + storedConfig.save() + } .onFailure { e -> e.printStackTrace() } } else if (replace) { runCatching { - val uri = URIish(url) + val uri = URIish(url) - val remoteConfig = RemoteConfig(storedConfig, name) - // remove the first and eventually the only uri - if (remoteConfig.urIs.size > 0) { - remoteConfig.removeURI(remoteConfig.urIs[0]) - } - if (remoteConfig.pushURIs.size > 0) { - remoteConfig.removePushURI(remoteConfig.pushURIs[0]) - } + val remoteConfig = RemoteConfig(storedConfig, name) + // remove the first and eventually the only uri + if (remoteConfig.urIs.size > 0) { + remoteConfig.removeURI(remoteConfig.urIs[0]) + } + if (remoteConfig.pushURIs.size > 0) { + remoteConfig.removePushURI(remoteConfig.pushURIs[0]) + } - remoteConfig.addURI(uri) - remoteConfig.addPushURI(uri) + remoteConfig.addURI(uri) + remoteConfig.addPushURI(uri) - remoteConfig.update(storedConfig) + remoteConfig.update(storedConfig) - storedConfig.save() - } + storedConfig.save() + } .onFailure { e -> e.printStackTrace() } } } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivity.kt index 291c654f..bd3141a8 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivity.kt @@ -200,18 +200,18 @@ class AutofillDecryptActivity : AppCompatActivity() { ) { OpenPgpApi.RESULT_CODE_SUCCESS -> { runCatching { - val entry = - withContext(Dispatchers.IO) { - @Suppress("BlockingMethodInNonBlockingContext") - passwordEntryFactory.create(decryptedOutput.toByteArray()) - } - AutofillPreferences.credentialsFromStoreEntry( - this, - file, - entry, - directoryStructure - ) - } + val entry = + withContext(Dispatchers.IO) { + @Suppress("BlockingMethodInNonBlockingContext") + passwordEntryFactory.create(decryptedOutput.toByteArray()) + } + AutofillPreferences.credentialsFromStoreEntry( + this, + file, + entry, + directoryStructure + ) + } .getOrElse { e -> logcat(ERROR) { e.asLog("Failed to parse password entry") } return null @@ -221,17 +221,17 @@ class AutofillDecryptActivity : AppCompatActivity() { val pendingIntent: PendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT)!! runCatching { - val intentToResume = - withContext(Dispatchers.Main) { - suspendCoroutine<Intent> { cont -> - continueAfterUserInteraction = cont - decryptInteractionRequiredAction.launch( - IntentSenderRequest.Builder(pendingIntent.intentSender).build() - ) + val intentToResume = + withContext(Dispatchers.Main) { + suspendCoroutine<Intent> { cont -> + continueAfterUserInteraction = cont + decryptInteractionRequiredAction.launch( + IntentSenderRequest.Builder(pendingIntent.intentSender).build() + ) + } } - } - decryptCredential(file, intentToResume) - } + decryptCredential(file, intentToResume) + } .getOrElse { e -> logcat(ERROR) { e.asLog("OpenPgpApi ACTION_DECRYPT_VERIFY failed with user interaction") diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivityV2.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivityV2.kt index d7287605..af875e00 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivityV2.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivityV2.kt @@ -150,25 +150,25 @@ class AutofillDecryptActivityV2 : AppCompatActivity() { } .onSuccess { encryptedInput -> runCatching { - withContext(Dispatchers.IO) { - val outputStream = ByteArrayOutputStream() - repository.decrypt( - password, - encryptedInput, - outputStream, - ) - outputStream + withContext(Dispatchers.IO) { + val outputStream = ByteArrayOutputStream() + repository.decrypt( + password, + encryptedInput, + outputStream, + ) + outputStream + } } - } .onFailure { e -> logcat(ERROR) { e.asLog("Decryption failed") } return null } .onSuccess { result -> return runCatching { - val entry = passwordEntryFactory.create(result.toByteArray()) - AutofillPreferences.credentialsFromStoreEntry(this, file, entry, directoryStructure) - } + val entry = passwordEntryFactory.create(result.toByteArray()) + AutofillPreferences.credentialsFromStoreEntry(this, file, entry, directoryStructure) + } .getOrElse { e -> logcat(ERROR) { e.asLog("Failed to parse password entry") } return null diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterView.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterView.kt index 573e5337..6bf238a1 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterView.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterView.kt @@ -149,31 +149,31 @@ class AutofillFilterView : AppCompatActivity() { ::PasswordViewHolder, lifecycleScope, ) { item -> - val file = item.file.relativeTo(item.rootDir) - val pathToIdentifier = directoryStructure.getPathToIdentifierFor(file) - val identifier = directoryStructure.getIdentifierFor(file) - val accountPart = directoryStructure.getAccountPartFor(file) - check(identifier != null || accountPart != null) { - "At least one of identifier and accountPart should always be non-null" - } - title.text = - if (identifier != null) { - buildSpannedString { - if (pathToIdentifier != null) append("$pathToIdentifier/") - bold { underline { append(identifier) } } - } - } else { - accountPart + val file = item.file.relativeTo(item.rootDir) + val pathToIdentifier = directoryStructure.getPathToIdentifierFor(file) + val identifier = directoryStructure.getIdentifierFor(file) + val accountPart = directoryStructure.getAccountPartFor(file) + check(identifier != null || accountPart != null) { + "At least one of identifier and accountPart should always be non-null" } - subtitle.apply { - if (identifier != null && accountPart != null) { - text = accountPart - visibility = View.VISIBLE - } else { - visibility = View.GONE + title.text = + if (identifier != null) { + buildSpannedString { + if (pathToIdentifier != null) append("$pathToIdentifier/") + bold { underline { append(identifier) } } + } + } else { + accountPart + } + subtitle.apply { + if (identifier != null && accountPart != null) { + text = accountPart + visibility = View.VISIBLE + } else { + visibility = View.GONE + } } } - } .onItemClicked { _, item -> decryptAndFill(item) } layoutManager = LinearLayoutManager(context) } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillPublisherChangedActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillPublisherChangedActivity.kt index 794fad8c..b0b5ea9c 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillPublisherChangedActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillPublisherChangedActivity.kt @@ -105,27 +105,28 @@ class AutofillPublisherChangedActivity : AppCompatActivity() { private fun showPackageInfo() { runCatching { - with(binding) { - val packageInfo = packageManager.getPackageInfo(appPackage, PackageManager.GET_META_DATA) - val installTime = DateUtils.getRelativeTimeSpanString(packageInfo.firstInstallTime) - warningAppInstallDate.text = - getString(R.string.oreo_autofill_warning_publisher_install_time, installTime) - val appInfo = packageManager.getApplicationInfo(appPackage, PackageManager.GET_META_DATA) - warningAppName.text = - getString( - R.string.oreo_autofill_warning_publisher_app_name, - packageManager.getApplicationLabel(appInfo) - ) + with(binding) { + val packageInfo = packageManager.getPackageInfo(appPackage, PackageManager.GET_META_DATA) + val installTime = DateUtils.getRelativeTimeSpanString(packageInfo.firstInstallTime) + warningAppInstallDate.text = + getString(R.string.oreo_autofill_warning_publisher_install_time, installTime) + val appInfo = packageManager.getApplicationInfo(appPackage, PackageManager.GET_META_DATA) + warningAppName.text = + getString( + R.string.oreo_autofill_warning_publisher_app_name, + packageManager.getApplicationLabel(appInfo) + ) - val currentHash = computeCertificatesHash(this@AutofillPublisherChangedActivity, appPackage) - warningAppAdvancedInfo.text = - getString( - R.string.oreo_autofill_warning_publisher_advanced_info_template, - appPackage, - currentHash - ) + val currentHash = + computeCertificatesHash(this@AutofillPublisherChangedActivity, appPackage) + warningAppAdvancedInfo.text = + getString( + R.string.oreo_autofill_warning_publisher_advanced_info_template, + appPackage, + currentHash + ) + } } - } .onFailure { e -> logcat(ERROR) { e.asLog("Failed to retrieve package info for $appPackage") } finish() diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/DecryptActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/DecryptActivity.kt index 77abde2a..af9a4ddd 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/DecryptActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/DecryptActivity.kt @@ -178,42 +178,42 @@ class DecryptActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound { OpenPgpApi.RESULT_CODE_SUCCESS -> { startAutoDismissTimer() runCatching { - val showPassword = settings.getBoolean(PreferenceKeys.SHOW_PASSWORD, true) - val entry = passwordEntryFactory.create(outputStream.toByteArray()) + val showPassword = settings.getBoolean(PreferenceKeys.SHOW_PASSWORD, true) + val entry = passwordEntryFactory.create(outputStream.toByteArray()) - if (settings.getBoolean(PreferenceKeys.COPY_ON_DECRYPT, false)) { - copyPasswordToClipboard(entry.password) - } + if (settings.getBoolean(PreferenceKeys.COPY_ON_DECRYPT, false)) { + copyPasswordToClipboard(entry.password) + } - passwordEntry = entry - invalidateOptionsMenu() + passwordEntry = entry + invalidateOptionsMenu() - val items = arrayListOf<FieldItem>() - if (!entry.password.isNullOrBlank()) { - items.add(FieldItem.createPasswordField(entry.password!!)) - } + val items = arrayListOf<FieldItem>() + if (!entry.password.isNullOrBlank()) { + items.add(FieldItem.createPasswordField(entry.password!!)) + } - if (entry.hasTotp()) { - items.add(FieldItem.createOtpField(entry.totp.first())) - } + if (entry.hasTotp()) { + items.add(FieldItem.createOtpField(entry.totp.first())) + } - if (!entry.username.isNullOrBlank()) { - items.add(FieldItem.createUsernameField(entry.username!!)) - } + if (!entry.username.isNullOrBlank()) { + items.add(FieldItem.createUsernameField(entry.username!!)) + } - entry.extraContent.forEach { (key, value) -> - items.add(FieldItem(key, value, FieldItem.ActionType.COPY)) - } + entry.extraContent.forEach { (key, value) -> + items.add(FieldItem(key, value, FieldItem.ActionType.COPY)) + } - val adapter = - FieldItemAdapter(items, showPassword) { text -> copyTextToClipboard(text) } - binding.recyclerView.adapter = adapter - binding.recyclerView.itemAnimator = null + val adapter = + FieldItemAdapter(items, showPassword) { text -> copyTextToClipboard(text) } + binding.recyclerView.adapter = adapter + binding.recyclerView.itemAnimator = null - if (entry.hasTotp()) { - entry.totp.onEach(adapter::updateOTPCode).launchIn(lifecycleScope) + if (entry.hasTotp()) { + entry.totp.onEach(adapter::updateOTPCode).launchIn(lifecycleScope) + } } - } .onFailure { e -> logcat(ERROR) { e.asLog() } } } OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> { diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/GetKeyIdsActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/GetKeyIdsActivity.kt index b24868c9..afd30270 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/GetKeyIdsActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/GetKeyIdsActivity.kt @@ -56,15 +56,15 @@ class GetKeyIdsActivity : BasePgpActivity() { when (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { OpenPgpApi.RESULT_CODE_SUCCESS -> { runCatching { - val ids = - result.getLongArrayExtra(OpenPgpApi.RESULT_KEY_IDS)?.map { - OpenPgpUtils.convertKeyIdToHex(it) - } - ?: emptyList() - val keyResult = Intent().putExtra(OpenPgpApi.EXTRA_KEY_IDS, ids.toTypedArray()) - setResult(RESULT_OK, keyResult) - finish() - } + val ids = + result.getLongArrayExtra(OpenPgpApi.RESULT_KEY_IDS)?.map { + OpenPgpUtils.convertKeyIdToHex(it) + } + ?: emptyList() + val keyResult = Intent().putExtra(OpenPgpApi.EXTRA_KEY_IDS, ids.toTypedArray()) + setResult(RESULT_OK, keyResult) + finish() + } .onFailure { e -> logcat(ERROR) { e.asLog() } } } OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> { diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivity.kt index 930da5cd..7a9ba54e 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivity.kt @@ -143,15 +143,15 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB val reader = QRCodeReader() runCatching { - val result = reader.decode(binaryBitmap) - val text = result.text - val currentExtras = binding.extraContent.text.toString() - if (currentExtras.isNotEmpty() && currentExtras.last() != '\n') - binding.extraContent.append("\n$text") - else binding.extraContent.append(text) - snackbar(message = getString(R.string.otp_import_success)) - binding.otpImportButton.isVisible = false - } + val result = reader.decode(binaryBitmap) + val text = result.text + val currentExtras = binding.extraContent.text.toString() + if (currentExtras.isNotEmpty() && currentExtras.last() != '\n') + binding.extraContent.append("\n$text") + else binding.extraContent.append(text) + snackbar(message = getString(R.string.otp_import_success)) + binding.otpImportButton.isVisible = false + } .onFailure { snackbar(message = getString(R.string.otp_import_failure)) } } @@ -411,22 +411,25 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB File(repoRoot, directory.text.toString()).findTillRoot(".gpg-id", repoRoot) ?: File(repoRoot, ".gpg-id").apply { createNewFile() } val gpgIdentifiers = - gpgIdentifierFile.readLines().filter { it.isNotBlank() }.map { line -> - GpgIdentifier.fromString(line) - ?: run { - // The line being empty means this is most likely an empty `.gpg-id` - // file - // we created. Skip the validation so we can make the user add a real - // ID. - if (line.isEmpty()) return@run - if (line.removePrefix("0x").matches("[a-fA-F0-9]{8}".toRegex())) { - snackbar(message = resources.getString(R.string.short_key_ids_unsupported)) - } else { - snackbar(message = resources.getString(R.string.invalid_gpg_id)) + gpgIdentifierFile + .readLines() + .filter { it.isNotBlank() } + .map { line -> + GpgIdentifier.fromString(line) + ?: run { + // The line being empty means this is most likely an empty `.gpg-id` + // file + // we created. Skip the validation so we can make the user add a real + // ID. + if (line.isEmpty()) return@run + if (line.removePrefix("0x").matches("[a-fA-F0-9]{8}".toRegex())) { + snackbar(message = resources.getString(R.string.short_key_ids_unsupported)) + } else { + snackbar(message = resources.getString(R.string.invalid_gpg_id)) + } + return@with } - return@with - } - } + } if (gpgIdentifiers.isEmpty()) { gpgKeySelectAction.launch( Intent(this@PasswordCreationActivity, GetKeyIdsActivity::class.java) @@ -480,86 +483,92 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB when (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) { OpenPgpApi.RESULT_CODE_SUCCESS -> { runCatching { - val file = File(path) - // If we're not editing, this file should not already exist! - // Additionally, if we were editing and the incoming and outgoing - // filenames differ, it means we renamed. Ensure that the target - // doesn't already exist to prevent an accidental overwrite. - if ((!editing || (editing && suggestedName != file.nameWithoutExtension)) && - file.exists() - ) { - snackbar(message = getString(R.string.password_creation_duplicate_error)) - return@runCatching - } - - if (!file.isInsideRepository()) { - snackbar(message = getString(R.string.message_error_destination_outside_repo)) - return@runCatching - } + val file = File(path) + // If we're not editing, this file should not already exist! + // Additionally, if we were editing and the incoming and outgoing + // filenames differ, it means we renamed. Ensure that the target + // doesn't already exist to prevent an accidental overwrite. + if ((!editing || (editing && suggestedName != file.nameWithoutExtension)) && + file.exists() + ) { + snackbar(message = getString(R.string.password_creation_duplicate_error)) + return@runCatching + } - withContext(Dispatchers.IO) { - file.outputStream().use { it.write(outputStream.toByteArray()) } - } + if (!file.isInsideRepository()) { + snackbar(message = getString(R.string.message_error_destination_outside_repo)) + return@runCatching + } - // associate the new password name with the last name's timestamp in - // history - val preference = getSharedPreferences("recent_password_history", Context.MODE_PRIVATE) - val oldFilePathHash = "$repoPath/${oldCategory?.trim('/')}/$oldFileName.gpg".base64() - val timestamp = preference.getString(oldFilePathHash) - if (timestamp != null) { - preference.edit { - remove(oldFilePathHash) - putString(file.absolutePath.base64(), timestamp) + withContext(Dispatchers.IO) { + file.outputStream().use { it.write(outputStream.toByteArray()) } } - } - val returnIntent = Intent() - returnIntent.putExtra(RETURN_EXTRA_CREATED_FILE, path) - returnIntent.putExtra(RETURN_EXTRA_NAME, editName) - returnIntent.putExtra( - RETURN_EXTRA_LONG_NAME, - getLongName(fullPath, repoPath, editName) - ) + // associate the new password name with the last name's timestamp in + // history + val preference = + getSharedPreferences("recent_password_history", Context.MODE_PRIVATE) + val oldFilePathHash = + "$repoPath/${oldCategory?.trim('/')}/$oldFileName.gpg".base64() + val timestamp = preference.getString(oldFilePathHash) + if (timestamp != null) { + preference.edit { + remove(oldFilePathHash) + putString(file.absolutePath.base64(), timestamp) + } + } - if (shouldGeneratePassword) { - val directoryStructure = AutofillPreferences.directoryStructure(applicationContext) - val entry = passwordEntryFactory.create(content.encodeToByteArray()) - returnIntent.putExtra(RETURN_EXTRA_PASSWORD, entry.password) - val username = entry.username ?: directoryStructure.getUsernameFor(file) - returnIntent.putExtra(RETURN_EXTRA_USERNAME, username) - } + val returnIntent = Intent() + returnIntent.putExtra(RETURN_EXTRA_CREATED_FILE, path) + returnIntent.putExtra(RETURN_EXTRA_NAME, editName) + returnIntent.putExtra( + RETURN_EXTRA_LONG_NAME, + getLongName(fullPath, repoPath, editName) + ) - if (directoryInputLayout.isVisible && - directoryInputLayout.isEnabled && - oldFileName != null - ) { - val oldFile = File("$repoPath/${oldCategory?.trim('/')}/$oldFileName.gpg") - if (oldFile.path != file.path && !oldFile.delete()) { - setResult(RESULT_CANCELED) - MaterialAlertDialogBuilder(this@PasswordCreationActivity) - .setTitle(R.string.password_creation_file_fail_title) - .setMessage( - getString(R.string.password_creation_file_delete_fail_message, oldFileName) - ) - .setCancelable(false) - .setPositiveButton(android.R.string.ok) { _, _ -> finish() } - .show() - return@runCatching + if (shouldGeneratePassword) { + val directoryStructure = + AutofillPreferences.directoryStructure(applicationContext) + val entry = passwordEntryFactory.create(content.encodeToByteArray()) + returnIntent.putExtra(RETURN_EXTRA_PASSWORD, entry.password) + val username = entry.username ?: directoryStructure.getUsernameFor(file) + returnIntent.putExtra(RETURN_EXTRA_USERNAME, username) } - } - val commitMessageRes = - if (editing) R.string.git_commit_edit_text else R.string.git_commit_add_text - lifecycleScope.launch { - commitChange( - resources.getString(commitMessageRes, getLongName(fullPath, repoPath, editName)) - ) - .onSuccess { - setResult(RESULT_OK, returnIntent) - finish() + if (directoryInputLayout.isVisible && + directoryInputLayout.isEnabled && + oldFileName != null + ) { + val oldFile = File("$repoPath/${oldCategory?.trim('/')}/$oldFileName.gpg") + if (oldFile.path != file.path && !oldFile.delete()) { + setResult(RESULT_CANCELED) + MaterialAlertDialogBuilder(this@PasswordCreationActivity) + .setTitle(R.string.password_creation_file_fail_title) + .setMessage( + getString(R.string.password_creation_file_delete_fail_message, oldFileName) + ) + .setCancelable(false) + .setPositiveButton(android.R.string.ok) { _, _ -> finish() } + .show() + return@runCatching } + } + + val commitMessageRes = + if (editing) R.string.git_commit_edit_text else R.string.git_commit_add_text + lifecycleScope.launch { + commitChange( + resources.getString( + commitMessageRes, + getLongName(fullPath, repoPath, editName) + ) + ) + .onSuccess { + setResult(RESULT_OK, returnIntent) + finish() + } + } } - } .onFailure { e -> if (e is IOException) { logcat(ERROR) { e.asLog("Failed to write password file") } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivityV2.kt b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivityV2.kt index a24fbc9e..a35891a5 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivityV2.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivityV2.kt @@ -119,15 +119,15 @@ class PasswordCreationActivityV2 : BasePgpActivity() { val reader = QRCodeReader() runCatching { - val result = reader.decode(binaryBitmap) - val text = result.text - val currentExtras = binding.extraContent.text.toString() - if (currentExtras.isNotEmpty() && currentExtras.last() != '\n') - binding.extraContent.append("\n$text") - else binding.extraContent.append(text) - snackbar(message = getString(R.string.otp_import_success)) - binding.otpImportButton.isVisible = false - } + val result = reader.decode(binaryBitmap) + val text = result.text + val currentExtras = binding.extraContent.text.toString() + if (currentExtras.isNotEmpty() && currentExtras.last() != '\n') + binding.extraContent.append("\n$text") + else binding.extraContent.append(text) + snackbar(message = getString(R.string.otp_import_success)) + binding.otpImportButton.isVisible = false + } .onFailure { snackbar(message = getString(R.string.otp_import_failure)) } } @@ -357,86 +357,87 @@ class PasswordCreationActivityV2 : BasePgpActivity() { lifecycleScope.launch(Dispatchers.Main) { runCatching { - val result = - withContext(Dispatchers.IO) { - val outputStream = ByteArrayOutputStream() - repository.encrypt(content.byteInputStream(), outputStream) - outputStream + val result = + withContext(Dispatchers.IO) { + val outputStream = ByteArrayOutputStream() + repository.encrypt(content.byteInputStream(), outputStream) + outputStream + } + val file = File(path) + // If we're not editing, this file should not already exist! + // Additionally, if we were editing and the incoming and outgoing + // filenames differ, it means we renamed. Ensure that the target + // doesn't already exist to prevent an accidental overwrite. + if ((!editing || (editing && suggestedName != file.nameWithoutExtension)) && + file.exists() + ) { + snackbar(message = getString(R.string.password_creation_duplicate_error)) + return@runCatching } - val file = File(path) - // If we're not editing, this file should not already exist! - // Additionally, if we were editing and the incoming and outgoing - // filenames differ, it means we renamed. Ensure that the target - // doesn't already exist to prevent an accidental overwrite. - if ((!editing || (editing && suggestedName != file.nameWithoutExtension)) && file.exists() - ) { - snackbar(message = getString(R.string.password_creation_duplicate_error)) - return@runCatching - } - - if (!file.isInsideRepository()) { - snackbar(message = getString(R.string.message_error_destination_outside_repo)) - return@runCatching - } - withContext(Dispatchers.IO) { file.writeBytes(result.toByteArray()) } - - // associate the new password name with the last name's timestamp in - // history - val preference = getSharedPreferences("recent_password_history", Context.MODE_PRIVATE) - val oldFilePathHash = "$repoPath/${oldCategory?.trim('/')}/$oldFileName.gpg".base64() - val timestamp = preference.getString(oldFilePathHash) - if (timestamp != null) { - preference.edit { - remove(oldFilePathHash) - putString(file.absolutePath.base64(), timestamp) + if (!file.isInsideRepository()) { + snackbar(message = getString(R.string.message_error_destination_outside_repo)) + return@runCatching } - } - val returnIntent = Intent() - returnIntent.putExtra(RETURN_EXTRA_CREATED_FILE, path) - returnIntent.putExtra(RETURN_EXTRA_NAME, editName) - returnIntent.putExtra(RETURN_EXTRA_LONG_NAME, getLongName(fullPath, repoPath, editName)) - - if (shouldGeneratePassword) { - val directoryStructure = AutofillPreferences.directoryStructure(applicationContext) - val entry = passwordEntryFactory.create(content.encodeToByteArray()) - returnIntent.putExtra(RETURN_EXTRA_PASSWORD, entry.password) - val username = entry.username ?: directoryStructure.getUsernameFor(file) - returnIntent.putExtra(RETURN_EXTRA_USERNAME, username) - } + withContext(Dispatchers.IO) { file.writeBytes(result.toByteArray()) } + + // associate the new password name with the last name's timestamp in + // history + val preference = getSharedPreferences("recent_password_history", Context.MODE_PRIVATE) + val oldFilePathHash = "$repoPath/${oldCategory?.trim('/')}/$oldFileName.gpg".base64() + val timestamp = preference.getString(oldFilePathHash) + if (timestamp != null) { + preference.edit { + remove(oldFilePathHash) + putString(file.absolutePath.base64(), timestamp) + } + } - if (directoryInputLayout.isVisible && - directoryInputLayout.isEnabled && - oldFileName != null - ) { - val oldFile = File("$repoPath/${oldCategory?.trim('/')}/$oldFileName.gpg") - if (oldFile.path != file.path && !oldFile.delete()) { - setResult(RESULT_CANCELED) - MaterialAlertDialogBuilder(this@PasswordCreationActivityV2) - .setTitle(R.string.password_creation_file_fail_title) - .setMessage( - getString(R.string.password_creation_file_delete_fail_message, oldFileName) - ) - .setCancelable(false) - .setPositiveButton(android.R.string.ok) { _, _ -> finish() } - .show() - return@runCatching + val returnIntent = Intent() + returnIntent.putExtra(RETURN_EXTRA_CREATED_FILE, path) + returnIntent.putExtra(RETURN_EXTRA_NAME, editName) + returnIntent.putExtra(RETURN_EXTRA_LONG_NAME, getLongName(fullPath, repoPath, editName)) + + if (shouldGeneratePassword) { + val directoryStructure = AutofillPreferences.directoryStructure(applicationContext) + val entry = passwordEntryFactory.create(content.encodeToByteArray()) + returnIntent.putExtra(RETURN_EXTRA_PASSWORD, entry.password) + val username = entry.username ?: directoryStructure.getUsernameFor(file) + returnIntent.putExtra(RETURN_EXTRA_USERNAME, username) } - } - val commitMessageRes = - if (editing) R.string.git_commit_edit_text else R.string.git_commit_add_text - lifecycleScope.launch { - commitChange( - resources.getString(commitMessageRes, getLongName(fullPath, repoPath, editName)) - ) - .onSuccess { - setResult(RESULT_OK, returnIntent) - finish() + if (directoryInputLayout.isVisible && + directoryInputLayout.isEnabled && + oldFileName != null + ) { + val oldFile = File("$repoPath/${oldCategory?.trim('/')}/$oldFileName.gpg") + if (oldFile.path != file.path && !oldFile.delete()) { + setResult(RESULT_CANCELED) + MaterialAlertDialogBuilder(this@PasswordCreationActivityV2) + .setTitle(R.string.password_creation_file_fail_title) + .setMessage( + getString(R.string.password_creation_file_delete_fail_message, oldFileName) + ) + .setCancelable(false) + .setPositiveButton(android.R.string.ok) { _, _ -> finish() } + .show() + return@runCatching } + } + + val commitMessageRes = + if (editing) R.string.git_commit_edit_text else R.string.git_commit_add_text + lifecycleScope.launch { + commitChange( + resources.getString(commitMessageRes, getLongName(fullPath, repoPath, editName)) + ) + .onSuccess { + setResult(RESULT_OK, returnIntent) + finish() + } + } } - } .onFailure { e -> if (e is IOException) { logcat(ERROR) { e.asLog("Failed to write password file") } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt index a21e9a6a..b0ddc8a7 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/dialogs/PasswordGeneratorDialogFragment.kt @@ -48,8 +48,7 @@ class PasswordGeneratorDialogFragment : DialogFragment() { val monoTypeface = Typeface.createFromAsset(callingActivity.assets, "fonts/sourcecodepro.ttf") val prefs = requireActivity() - .applicationContext - .getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE) + .applicationContext.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE) builder.setView(binding.root) @@ -102,21 +101,21 @@ class PasswordGeneratorDialogFragment : DialogFragment() { val passwordLength = getLength() setPrefs(requireContext(), passwordOptions, passwordLength) passwordField.text = - runCatching { PasswordGenerator.generate(passwordOptions, passwordLength) }.getOrElse { - exception -> - val errorText = - when (exception) { - is MaxIterationsExceededException -> - requireContext().getString(R.string.pwgen_max_iterations_exceeded) - is NoCharactersIncludedException -> - requireContext().getString(R.string.pwgen_no_chars_error) - is PasswordLengthTooShortException -> - requireContext().getString(R.string.pwgen_length_too_short_error) - else -> requireContext().getString(R.string.pwgen_some_error_occurred) - } - Toast.makeText(requireActivity(), errorText, Toast.LENGTH_SHORT).show() - "" - } + runCatching { PasswordGenerator.generate(passwordOptions, passwordLength) } + .getOrElse { exception -> + val errorText = + when (exception) { + is MaxIterationsExceededException -> + requireContext().getString(R.string.pwgen_max_iterations_exceeded) + is NoCharactersIncludedException -> + requireContext().getString(R.string.pwgen_no_chars_error) + is PasswordLengthTooShortException -> + requireContext().getString(R.string.pwgen_length_too_short_error) + else -> requireContext().getString(R.string.pwgen_some_error_occurred) + } + Toast.makeText(requireActivity(), errorText, Toast.LENGTH_SHORT).show() + "" + } } private fun isChecked(@IdRes id: Int): Boolean { diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/folderselect/SelectFolderFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/folderselect/SelectFolderFragment.kt index ae430485..54fd2892 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/folderselect/SelectFolderFragment.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/folderselect/SelectFolderFragment.kt @@ -59,18 +59,17 @@ class SelectFolderFragment : Fragment(R.layout.password_recycler_view) { override fun onAttach(context: Context) { super.onAttach(context) runCatching { - listener = - object : OnFragmentInteractionListener { - override fun onFragmentInteraction(item: PasswordItem) { - if (item.type == PasswordItem.TYPE_CATEGORY) { - model.navigateTo(item.file, listMode = ListMode.DirectoriesOnly) - (requireActivity() as AppCompatActivity).supportActionBar?.setDisplayHomeAsUpEnabled( - true - ) + listener = + object : OnFragmentInteractionListener { + override fun onFragmentInteraction(item: PasswordItem) { + if (item.type == PasswordItem.TYPE_CATEGORY) { + model.navigateTo(item.file, listMode = ListMode.DirectoriesOnly) + (requireActivity() as AppCompatActivity) + .supportActionBar?.setDisplayHomeAsUpEnabled(true) + } } } - } - } + } .onFailure { throw ClassCastException("$context must implement OnFragmentInteractionListener") } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/git/config/GitConfigActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/git/config/GitConfigActivity.kt index 959d9325..03c92313 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/git/config/GitConfigActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/git/config/GitConfigActivity.kt @@ -88,9 +88,8 @@ class GitConfigActivity : BaseGitActivity() { binding.gitAbortRebase.alpha = if (needsAbort) 1.0f else 0.5f } binding.gitLog.setOnClickListener { - runCatching { startActivity(Intent(this, GitLogActivity::class.java)) }.onFailure { ex -> - logcat(ERROR) { "Failed to start GitLogActivity\n${ex}" } - } + runCatching { startActivity(Intent(this, GitLogActivity::class.java)) } + .onFailure { ex -> logcat(ERROR) { "Failed to start GitLogActivity\n${ex}" } } } binding.gitAbortRebase.setOnClickListener { lifecycleScope.launch { @@ -142,16 +141,16 @@ class GitConfigActivity : BaseGitActivity() { */ private fun headStatusMsg(repo: Repository): String { return runCatching { - val headRef = repo.getRef(Constants.HEAD) - if (headRef.isSymbolic) { - val branchName = headRef.target.name - val shortBranchName = Repository.shortenRefName(branchName) - getString(R.string.git_head_on_branch, shortBranchName) - } else { - val commitHash = headRef.objectId.abbreviate(8).name() - getString(R.string.git_head_detached, commitHash) + val headRef = repo.getRef(Constants.HEAD) + if (headRef.isSymbolic) { + val branchName = headRef.target.name + val shortBranchName = Repository.shortenRefName(branchName) + getString(R.string.git_head_on_branch, shortBranchName) + } else { + val commitHash = headRef.objectId.abbreviate(8).name() + getString(R.string.git_head_detached, commitHash) + } } - } .getOrElse { ex -> logcat(ERROR) { "Error getting HEAD reference\n${ex}" } getString(R.string.git_head_missing) diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/git/config/GitServerConfigActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/git/config/GitServerConfigActivity.kt index 6b1f30ea..e713a54e 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/git/config/GitServerConfigActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/git/config/GitServerConfigActivity.kt @@ -240,24 +240,24 @@ class GitServerConfigActivity : BaseGitActivity() { .setCancelable(false) .setPositiveButton(R.string.dialog_delete) { dialog, _ -> runCatching { - lifecycleScope.launch { - val snackbar = - snackbar( - message = getString(R.string.delete_directory_progress_text), - length = Snackbar.LENGTH_INDEFINITE - ) - withContext(Dispatchers.IO) { localDir.deleteRecursively() } - snackbar.dismiss() - launchGitOperation(GitOp.CLONE) - .fold( - success = { - setResult(RESULT_OK) - finish() - }, - failure = { err -> promptOnErrorHandler(err) { finish() } } - ) + lifecycleScope.launch { + val snackbar = + snackbar( + message = getString(R.string.delete_directory_progress_text), + length = Snackbar.LENGTH_INDEFINITE + ) + withContext(Dispatchers.IO) { localDir.deleteRecursively() } + snackbar.dismiss() + launchGitOperation(GitOp.CLONE) + .fold( + success = { + setResult(RESULT_OK) + finish() + }, + failure = { err -> promptOnErrorHandler(err) { finish() } } + ) + } } - } .onFailure { e -> e.printStackTrace() MaterialAlertDialogBuilder(this).setMessage(e.message).show() @@ -268,11 +268,11 @@ class GitServerConfigActivity : BaseGitActivity() { .show() } else { runCatching { - // Silently delete & replace the lone .git folder if it exists - if (localDir.exists() && localDirFiles.size == 1 && localDirFiles[0].name == ".git") { - localDir.deleteRecursively() + // Silently delete & replace the lone .git folder if it exists + if (localDir.exists() && localDirFiles.size == 1 && localDirFiles[0].name == ".git") { + localDir.deleteRecursively() + } } - } .onFailure { e -> logcat(ERROR) { e.asLog() } MaterialAlertDialogBuilder(this).setMessage(e.message).show() diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/CloneFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/CloneFragment.kt index 16c1a784..e3d1df85 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/CloneFragment.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/onboarding/fragments/CloneFragment.kt @@ -55,13 +55,13 @@ class CloneFragment : Fragment(R.layout.fragment_clone) { private fun createRepository() { val localDir = PasswordRepository.getRepositoryDirectory() runCatching { - check(localDir.exists() || localDir.mkdir()) { "Failed to create directory!" } - PasswordRepository.createRepository(localDir) - if (!PasswordRepository.isInitialized) { - PasswordRepository.initialize() + check(localDir.exists() || localDir.mkdir()) { "Failed to create directory!" } + PasswordRepository.createRepository(localDir) + if (!PasswordRepository.isInitialized) { + PasswordRepository.initialize() + } + parentFragmentManager.performTransactionWithBackStack(KeySelectionFragment.newInstance()) } - parentFragmentManager.performTransactionWithBackStack(KeySelectionFragment.newInstance()) - } .onFailure { e -> logcat(ERROR) { e.asLog() } if (!localDir.delete()) { diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordFragment.kt b/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordFragment.kt index 063f472c..0d0f9cfc 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordFragment.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordFragment.kt @@ -293,32 +293,32 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) { override fun onAttach(context: Context) { super.onAttach(context) runCatching { - listener = - object : OnFragmentInteractionListener { - override fun onFragmentInteraction(item: PasswordItem) { - if (settings.getString(PreferenceKeys.SORT_ORDER) == - PasswordSortOrder.RECENTLY_USED.name - ) { - // save the time when password was used - val preferences = - context.getSharedPreferences("recent_password_history", Context.MODE_PRIVATE) - preferences.edit { - putString(item.file.absolutePath.base64(), System.currentTimeMillis().toString()) + listener = + object : OnFragmentInteractionListener { + override fun onFragmentInteraction(item: PasswordItem) { + if (settings.getString(PreferenceKeys.SORT_ORDER) == + PasswordSortOrder.RECENTLY_USED.name + ) { + // save the time when password was used + val preferences = + context.getSharedPreferences("recent_password_history", Context.MODE_PRIVATE) + preferences.edit { + putString(item.file.absolutePath.base64(), System.currentTimeMillis().toString()) + } } - } - if (item.type == PasswordItem.TYPE_CATEGORY) { - navigateTo(item.file) - } else { - if (requireArguments().getBoolean("matchWith", false)) { - requireStore().matchPasswordWithApp(item) + if (item.type == PasswordItem.TYPE_CATEGORY) { + navigateTo(item.file) } else { - requireStore().decryptPassword(item) + if (requireArguments().getBoolean("matchWith", false)) { + requireStore().matchPasswordWithApp(item) + } else { + requireStore().decryptPassword(item) + } } } } - } - } + } .onFailure { throw ClassCastException("$context must implement OnFragmentInteractionListener") } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt b/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt index e4ec2359..84bef7bb 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/passwords/PasswordStore.kt @@ -292,9 +292,8 @@ class PasswordStore : BaseGitActivity() { .setPositiveButton(resources.getString(R.string.dialog_ok), null) when (id) { R.id.user_pref -> { - runCatching { startActivity(Intent(this, SettingsActivity::class.java)) }.onFailure { e -> - e.printStackTrace() - } + runCatching { startActivity(Intent(this, SettingsActivity::class.java)) } + .onFailure { e -> e.printStackTrace() } } R.id.git_push -> { if (!PasswordRepository.isInitialized) { @@ -618,9 +617,7 @@ class PasswordStore : BaseGitActivity() { fun matchPasswordWithApp(item: PasswordItem) { val path = - item - .file - .absolutePath + item.file.absolutePath .replace(PasswordRepository.getRepositoryDirectory().toString() + "/", "") .replace(".gpg", "") val data = Intent() diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/proxy/ProxySelectorActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/proxy/ProxySelectorActivity.kt index 6fc63673..41cab040 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/proxy/ProxySelectorActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/proxy/ProxySelectorActivity.kt @@ -42,9 +42,10 @@ class ProxySelectorActivity : AppCompatActivity() { with(binding) { proxyHost.setText(proxyPrefs.getString(PreferenceKeys.PROXY_HOST)) proxyUser.setText(proxyPrefs.getString(PreferenceKeys.PROXY_USERNAME)) - proxyPrefs.getInt(PreferenceKeys.PROXY_PORT, -1).takeIf { it != -1 }?.let { - proxyPort.setText("$it") - } + proxyPrefs + .getInt(PreferenceKeys.PROXY_PORT, -1) + .takeIf { it != -1 } + ?.let { proxyPort.setText("$it") } proxyPassword.setText(proxyPrefs.getString(PreferenceKeys.PROXY_PASSWORD)) save.setOnClickListener { saveSettings() } proxyHost.doOnTextChanged { text, _, _, _ -> @@ -70,18 +71,22 @@ class ProxySelectorActivity : AppCompatActivity() { private fun saveSettings() { proxyPrefs.edit { - binding.proxyHost.text?.toString()?.takeIf { it.isNotEmpty() }.let { - gitSettings.proxyHost = it - } - binding.proxyUser.text?.toString()?.takeIf { it.isNotEmpty() }.let { - gitSettings.proxyUsername = it - } - binding.proxyPort.text?.toString()?.takeIf { it.isNotEmpty() }?.let { - gitSettings.proxyPort = it.toInt() - } - binding.proxyPassword.text?.toString()?.takeIf { it.isNotEmpty() }.let { - gitSettings.proxyPassword = it - } + binding.proxyHost.text + ?.toString() + ?.takeIf { it.isNotEmpty() } + .let { gitSettings.proxyHost = it } + binding.proxyUser.text + ?.toString() + ?.takeIf { it.isNotEmpty() } + .let { gitSettings.proxyUsername = it } + binding.proxyPort.text + ?.toString() + ?.takeIf { it.isNotEmpty() } + ?.let { gitSettings.proxyPort = it.toInt() } + binding.proxyPassword.text + ?.toString() + ?.takeIf { it.isNotEmpty() } + .let { gitSettings.proxyPassword = it } } proxyUtils.setDefaultProxy() Handler(Looper.getMainLooper()).postDelayed(500) { finish() } diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt b/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt index 1c43127a..ffd0f851 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/settings/RepositorySettings.kt @@ -161,9 +161,9 @@ class RepositorySettings(private val activity: FragmentActivity) : SettingsProvi .setCancelable(false) .setPositiveButton(R.string.dialog_delete) { dialogInterface, _ -> runCatching { - PasswordRepository.getRepositoryDirectory().deleteRecursively() - PasswordRepository.closeRepository() - } + PasswordRepository.getRepositoryDirectory().deleteRecursively() + PasswordRepository.closeRepository() + } .onFailure { it.message?.let { message -> activity.snackbar(message = message) } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyImportActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyImportActivity.kt index af00e3df..446e0c32 100644 --- a/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyImportActivity.kt +++ b/app/src/main/java/dev/msfjarvis/aps/ui/sshkeygen/SshKeyImportActivity.kt @@ -25,16 +25,16 @@ class SshKeyImportActivity : AppCompatActivity() { 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() - } + 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)) diff --git a/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillMatcher.kt b/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillMatcher.kt index 206074fa..42c1d693 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillMatcher.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/autofill/AutofillMatcher.kt @@ -107,11 +107,13 @@ class AutofillMatcher { val matchedFiles = matchPreferences.getStringSet(matchesKey(formOrigin), emptySet())!!.map { File(it) } return Ok( - matchedFiles.filter { it.exists() }.also { validFiles -> - matchPreferences.edit { - putStringSet(matchesKey(formOrigin), validFiles.map { it.absolutePath }.toSet()) + matchedFiles + .filter { it.exists() } + .also { validFiles -> + matchPreferences.edit { + putStringSet(matchesKey(formOrigin), validFiles.map { it.absolutePath }.toSet()) + } } - } ) } diff --git a/app/src/main/java/dev/msfjarvis/aps/util/extensions/Extensions.kt b/app/src/main/java/dev/msfjarvis/aps/util/extensions/Extensions.kt index 6e14a29d..8530a216 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/extensions/Extensions.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/extensions/Extensions.kt @@ -34,9 +34,10 @@ fun File.contains(other: File): Boolean { if (!isDirectory) return false if (!other.exists()) return false val relativePath = - runCatching { other.relativeTo(this) }.getOrElse { - return false - } + runCatching { other.relativeTo(this) } + .getOrElse { + return false + } // Direct containment is equivalent to the relative path being equal to the filename. return relativePath.path == other.name } diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/GitCommandExecutor.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/GitCommandExecutor.kt index 115c2f9f..e6eb77f5 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/GitCommandExecutor.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/GitCommandExecutor.kt @@ -51,76 +51,76 @@ class GitCommandExecutor( // Count the number of uncommitted files var nbChanges = 0 return runCatching { - for (command in operation.commands) { - when (command) { - is StatusCommand -> { - val res = withContext(Dispatchers.IO) { command.call() } - nbChanges = res.uncommittedChanges.size - } - is CommitCommand -> { - // the previous status will eventually be used to avoid a commit - if (nbChanges > 0) { - withContext(Dispatchers.IO) { - val name = gitSettings.authorName.ifEmpty { "root" } - val email = gitSettings.authorEmail.ifEmpty { "localhost" } - val identity = PersonIdent(name, email) - command.setAuthor(identity).setCommitter(identity).call() - } + for (command in operation.commands) { + when (command) { + is StatusCommand -> { + val res = withContext(Dispatchers.IO) { command.call() } + nbChanges = res.uncommittedChanges.size } - } - is PullCommand -> { - val result = withContext(Dispatchers.IO) { command.call() } - if (result.rebaseResult != null) { - if (!result.rebaseResult.status.isSuccessful) { - throw PullException.PullRebaseFailed + is CommitCommand -> { + // the previous status will eventually be used to avoid a commit + if (nbChanges > 0) { + withContext(Dispatchers.IO) { + val name = gitSettings.authorName.ifEmpty { "root" } + val email = gitSettings.authorEmail.ifEmpty { "localhost" } + val identity = PersonIdent(name, email) + command.setAuthor(identity).setCommitter(identity).call() + } } - } else if (result.mergeResult != null) { - if (!result.mergeResult.mergeStatus.isSuccessful) { - throw PullException.PullMergeFailed + } + is PullCommand -> { + val result = withContext(Dispatchers.IO) { command.call() } + if (result.rebaseResult != null) { + if (!result.rebaseResult.status.isSuccessful) { + throw PullException.PullRebaseFailed + } + } else if (result.mergeResult != null) { + if (!result.mergeResult.mergeStatus.isSuccessful) { + throw PullException.PullMergeFailed + } } } - } - is PushCommand -> { - val results = withContext(Dispatchers.IO) { command.call() } - for (result in results) { - // Code imported (modified) from Gerrit PushOp, license Apache v2 - for (rru in result.remoteUpdates) { - when (rru.status) { - RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD -> - throw PushException.NonFastForward - RemoteRefUpdate.Status.REJECTED_NODELETE, - RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED, - RemoteRefUpdate.Status.NON_EXISTING, - RemoteRefUpdate.Status.NOT_ATTEMPTED, -> - throw PushException.Generic(rru.status.name) - RemoteRefUpdate.Status.REJECTED_OTHER_REASON -> { - throw if ("non-fast-forward" == rru.message) { - PushException.RemoteRejected - } else { - PushException.Generic(rru.message) + is PushCommand -> { + val results = withContext(Dispatchers.IO) { command.call() } + for (result in results) { + // Code imported (modified) from Gerrit PushOp, license Apache v2 + for (rru in result.remoteUpdates) { + when (rru.status) { + RemoteRefUpdate.Status.REJECTED_NONFASTFORWARD -> + throw PushException.NonFastForward + RemoteRefUpdate.Status.REJECTED_NODELETE, + RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED, + RemoteRefUpdate.Status.NON_EXISTING, + RemoteRefUpdate.Status.NOT_ATTEMPTED, -> + throw PushException.Generic(rru.status.name) + RemoteRefUpdate.Status.REJECTED_OTHER_REASON -> { + throw if ("non-fast-forward" == rru.message) { + PushException.RemoteRejected + } else { + PushException.Generic(rru.message) + } } - } - RemoteRefUpdate.Status.UP_TO_DATE -> { - withContext(Dispatchers.Main) { - Toast.makeText( - activity.applicationContext, - activity.applicationContext.getString(R.string.git_push_up_to_date), - Toast.LENGTH_SHORT - ) - .show() + RemoteRefUpdate.Status.UP_TO_DATE -> { + withContext(Dispatchers.Main) { + Toast.makeText( + activity.applicationContext, + activity.applicationContext.getString(R.string.git_push_up_to_date), + Toast.LENGTH_SHORT + ) + .show() + } } + else -> {} } - else -> {} } } } - } - else -> { - withContext(Dispatchers.IO) { command.call() } + else -> { + withContext(Dispatchers.IO) { command.call() } + } } } } - } .also { snackbar.dismiss() } } diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/GitLogModel.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/GitLogModel.kt index 444a9fee..ed210a65 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/GitLogModel.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/GitLogModel.kt @@ -25,10 +25,11 @@ private fun commits(): Iterable<RevCommit> { logcat(TAG, ERROR) { "Could not access git repository" } return listOf() } - return runCatching { Git(repo).log().call() }.getOrElse { e -> - logcat(TAG, ERROR) { e.asLog("Failed to obtain git commits") } - listOf() - } + return runCatching { Git(repo).log().call() } + .getOrElse { e -> + logcat(TAG, ERROR) { e.asLog("Failed to obtain git commits") } + listOf() + } } /** diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt index f487b195..576a802d 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/operation/GitOperation.kt @@ -109,14 +109,14 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) { private fun getSshKey(make: Boolean) { runCatching { - val intent = - if (make) { - Intent(callingActivity.applicationContext, SshKeyGenActivity::class.java) - } else { - Intent(callingActivity.applicationContext, SshKeyImportActivity::class.java) - } - callingActivity.startActivity(intent) - } + val intent = + if (make) { + Intent(callingActivity.applicationContext, SshKeyGenActivity::class.java) + } else { + Intent(callingActivity.applicationContext, SshKeyImportActivity::class.java) + } + callingActivity.startActivity(intent) + } .onFailure { e -> logcat(ERROR) { e.asLog() } } } diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshKey.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshKey.kt index f2db6067..d5aaff33 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshKey.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshKey.kt @@ -84,19 +84,19 @@ object SshKey { val mustAuthenticate: Boolean get() { return runCatching { - if (type !in listOf(Type.KeystoreNative, Type.KeystoreWrappedEd25519)) return false - when (val key = androidKeystore.getKey(KEYSTORE_ALIAS, null)) { - is PrivateKey -> { - val factory = KeyFactory.getInstance(key.algorithm, PROVIDER_ANDROID_KEY_STORE) - return factory.getKeySpec(key, KeyInfo::class.java).isUserAuthenticationRequired + if (type !in listOf(Type.KeystoreNative, Type.KeystoreWrappedEd25519)) return false + when (val key = androidKeystore.getKey(KEYSTORE_ALIAS, null)) { + is PrivateKey -> { + val factory = KeyFactory.getInstance(key.algorithm, PROVIDER_ANDROID_KEY_STORE) + return factory.getKeySpec(key, KeyInfo::class.java).isUserAuthenticationRequired + } + is SecretKey -> { + val factory = SecretKeyFactory.getInstance(key.algorithm, PROVIDER_ANDROID_KEY_STORE) + (factory.getKeySpec(key, KeyInfo::class.java) as KeyInfo).isUserAuthenticationRequired + } + else -> throw IllegalStateException("SSH key does not exist in Keystore") } - is SecretKey -> { - val factory = SecretKeyFactory.getInstance(key.algorithm, PROVIDER_ANDROID_KEY_STORE) - (factory.getKeySpec(key, KeyInfo::class.java) as KeyInfo).isUserAuthenticationRequired - } - else -> throw IllegalStateException("SSH key does not exist in Keystore") } - } .getOrElse { error -> // It is fine to swallow the exception here since it will reappear when the key // is @@ -316,19 +316,24 @@ object SshKey { private object KeystoreNativeKeyProvider : KeyProvider { override fun getPublic(): PublicKey = - runCatching { androidKeystore.sshPublicKey!! }.getOrElse { error -> - logcat { error.asLog() } - throw IOException("Failed to get public key '$KEYSTORE_ALIAS' from Android Keystore", error) - } + runCatching { androidKeystore.sshPublicKey!! } + .getOrElse { error -> + logcat { error.asLog() } + throw IOException( + "Failed to get public key '$KEYSTORE_ALIAS' from Android Keystore", + error + ) + } override fun getPrivate(): PrivateKey = - runCatching { androidKeystore.sshPrivateKey!! }.getOrElse { error -> - logcat { error.asLog() } - throw IOException( - "Failed to access private key '$KEYSTORE_ALIAS' from Android Keystore", - error - ) - } + runCatching { androidKeystore.sshPrivateKey!! } + .getOrElse { error -> + logcat { error.asLog() } + throw IOException( + "Failed to access private key '$KEYSTORE_ALIAS' from Android Keystore", + error + ) + } override fun getType(): KeyType = KeyType.fromKey(public) } @@ -336,22 +341,23 @@ object SshKey { private object KeystoreWrappedEd25519KeyProvider : KeyProvider { override fun getPublic(): PublicKey = - runCatching { parseSshPublicKey(sshPublicKey!!)!! }.getOrElse { error -> - logcat { error.asLog() } - throw IOException("Failed to get the public key for wrapped ed25519 key", error) - } + runCatching { parseSshPublicKey(sshPublicKey!!)!! } + .getOrElse { error -> + logcat { error.asLog() } + throw IOException("Failed to get the public key for wrapped ed25519 key", error) + } override fun getPrivate(): PrivateKey = runCatching { - // The current MasterKey API does not allow getting a reference to an existing one - // without specifying the KeySpec for a new one. However, the value for passed here - // for `requireAuthentication` is not used as the key already exists at this point. - val encryptedPrivateKeyFile = runBlocking { getOrCreateWrappedPrivateKeyFile(false) } - val rawPrivateKey = encryptedPrivateKeyFile.openFileInput().use { it.readBytes() } - EdDSAPrivateKey( - EdDSAPrivateKeySpec(rawPrivateKey, EdDSANamedCurveTable.ED_25519_CURVE_SPEC) - ) - } + // The current MasterKey API does not allow getting a reference to an existing one + // without specifying the KeySpec for a new one. However, the value for passed here + // for `requireAuthentication` is not used as the key already exists at this point. + val encryptedPrivateKeyFile = runBlocking { getOrCreateWrappedPrivateKeyFile(false) } + val rawPrivateKey = encryptedPrivateKeyFile.openFileInput().use { it.readBytes() } + EdDSAPrivateKey( + EdDSAPrivateKeySpec(rawPrivateKey, EdDSANamedCurveTable.ED_25519_CURVE_SPEC) + ) + } .getOrElse { error -> logcat { error.asLog() } throw IOException("Failed to unwrap wrapped ed25519 key", error) diff --git a/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshjSessionFactory.kt b/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshjSessionFactory.kt index 50c146cc..a9f84fa9 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshjSessionFactory.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/git/sshj/SshjSessionFactory.kt @@ -93,9 +93,8 @@ private fun makeTofuHostKeyVerifier(hostKeyFile: File): HostKeyVerifier { return object : HostKeyVerifier { override fun verify(hostname: String?, port: Int, key: PublicKey?): Boolean { val digest = - runCatching { SecurityUtils.getMessageDigest("SHA-256") }.getOrElse { e -> - throw SSHRuntimeException(e) - } + runCatching { SecurityUtils.getMessageDigest("SHA-256") } + .getOrElse { e -> throw SSHRuntimeException(e) } digest.update(PlainBuffer().putPublicKey(key).compactData) val digestData = digest.digest() val hostKeyEntry = "SHA256:${Base64.encodeToString(digestData, Base64.NO_WRAP)}" diff --git a/app/src/main/java/dev/msfjarvis/aps/util/settings/GitSettings.kt b/app/src/main/java/dev/msfjarvis/aps/util/settings/GitSettings.kt index e6915853..8cbf84d9 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/settings/GitSettings.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/settings/GitSettings.kt @@ -140,9 +140,10 @@ constructor( newBranch: String ): UpdateConnectionSettingsResult { val parsedUrl = - runCatching { URIish(newUrl) }.getOrElse { - return UpdateConnectionSettingsResult.FailedToParseUrl - } + runCatching { URIish(newUrl) } + .getOrElse { + return UpdateConnectionSettingsResult.FailedToParseUrl + } val newProtocol = when (parsedUrl.scheme) { in listOf("http", "https") -> Protocol.Https diff --git a/app/src/main/java/dev/msfjarvis/aps/util/viewmodel/SearchableRepositoryViewModel.kt b/app/src/main/java/dev/msfjarvis/aps/util/viewmodel/SearchableRepositoryViewModel.kt index 87cf33c6..5f3d04e3 100644 --- a/app/src/main/java/dev/msfjarvis/aps/util/viewmodel/SearchableRepositoryViewModel.kt +++ b/app/src/main/java/dev/msfjarvis/aps/util/viewmodel/SearchableRepositoryViewModel.kt @@ -229,9 +229,8 @@ class SearchableRepositoryViewModel(application: Application) : AndroidViewModel .filter { it.first > 0 } .toList() .sortedWith( - compareByDescending<Pair<Int, PasswordItem>> { it.first }.thenBy(itemComparator) { - it.second - } + compareByDescending<Pair<Int, PasswordItem>> { it.first } + .thenBy(itemComparator) { it.second } ) .map { it.second } } |