all: reformat with ktfmt 0.36

This commit is contained in:
Harsh Shandilya 2022-04-25 10:47:02 +05:30
parent 11720e9542
commit aaf6ceb8ec
No known key found for this signature in database
GPG key ID: 366D7BBAD1031E80
34 changed files with 637 additions and 614 deletions

View file

@ -42,10 +42,11 @@ object PasswordRepository {
private fun initializeRepository(repositoryDir: File) { private fun initializeRepository(repositoryDir: File) {
val builder = FileRepositoryBuilder() val builder = FileRepositoryBuilder()
repository = repository =
runCatching { builder.setGitDir(repositoryDir).build() }.getOrElse { e -> runCatching { builder.setGitDir(repositoryDir).build() }
e.printStackTrace() .getOrElse { e ->
null e.printStackTrace()
} null
}
} }
fun createRepository(repositoryDir: File) { fun createRepository(repositoryDir: File) {
@ -61,40 +62,40 @@ object PasswordRepository {
if (!remotes.contains(name)) { if (!remotes.contains(name)) {
runCatching { runCatching {
val uri = URIish(url) val uri = URIish(url)
val refSpec = RefSpec("+refs/head/*:refs/remotes/$name/*") val refSpec = RefSpec("+refs/head/*:refs/remotes/$name/*")
val remoteConfig = RemoteConfig(storedConfig, name) val remoteConfig = RemoteConfig(storedConfig, name)
remoteConfig.addFetchRefSpec(refSpec) remoteConfig.addFetchRefSpec(refSpec)
remoteConfig.addPushRefSpec(refSpec) remoteConfig.addPushRefSpec(refSpec)
remoteConfig.addURI(uri) remoteConfig.addURI(uri)
remoteConfig.addPushURI(uri) remoteConfig.addPushURI(uri)
remoteConfig.update(storedConfig) remoteConfig.update(storedConfig)
storedConfig.save() storedConfig.save()
} }
.onFailure { e -> e.printStackTrace() } .onFailure { e -> e.printStackTrace() }
} else if (replace) { } else if (replace) {
runCatching { runCatching {
val uri = URIish(url) val uri = URIish(url)
val remoteConfig = RemoteConfig(storedConfig, name) val remoteConfig = RemoteConfig(storedConfig, name)
// remove the first and eventually the only uri // remove the first and eventually the only uri
if (remoteConfig.urIs.size > 0) { if (remoteConfig.urIs.size > 0) {
remoteConfig.removeURI(remoteConfig.urIs[0]) remoteConfig.removeURI(remoteConfig.urIs[0])
}
if (remoteConfig.pushURIs.size > 0) {
remoteConfig.removePushURI(remoteConfig.pushURIs[0])
}
remoteConfig.addURI(uri)
remoteConfig.addPushURI(uri)
remoteConfig.update(storedConfig)
storedConfig.save()
} }
if (remoteConfig.pushURIs.size > 0) {
remoteConfig.removePushURI(remoteConfig.pushURIs[0])
}
remoteConfig.addURI(uri)
remoteConfig.addPushURI(uri)
remoteConfig.update(storedConfig)
storedConfig.save()
}
.onFailure { e -> e.printStackTrace() } .onFailure { e -> e.printStackTrace() }
} }
} }

View file

@ -200,18 +200,18 @@ class AutofillDecryptActivity : AppCompatActivity() {
) { ) {
OpenPgpApi.RESULT_CODE_SUCCESS -> { OpenPgpApi.RESULT_CODE_SUCCESS -> {
runCatching { runCatching {
val entry = val entry =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
passwordEntryFactory.create(decryptedOutput.toByteArray()) passwordEntryFactory.create(decryptedOutput.toByteArray())
} }
AutofillPreferences.credentialsFromStoreEntry( AutofillPreferences.credentialsFromStoreEntry(
this, this,
file, file,
entry, entry,
directoryStructure directoryStructure
) )
} }
.getOrElse { e -> .getOrElse { e ->
logcat(ERROR) { e.asLog("Failed to parse password entry") } logcat(ERROR) { e.asLog("Failed to parse password entry") }
return null return null
@ -221,17 +221,17 @@ class AutofillDecryptActivity : AppCompatActivity() {
val pendingIntent: PendingIntent = val pendingIntent: PendingIntent =
result.getParcelableExtra(OpenPgpApi.RESULT_INTENT)!! result.getParcelableExtra(OpenPgpApi.RESULT_INTENT)!!
runCatching { runCatching {
val intentToResume = val intentToResume =
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
suspendCoroutine<Intent> { cont -> suspendCoroutine<Intent> { cont ->
continueAfterUserInteraction = cont continueAfterUserInteraction = cont
decryptInteractionRequiredAction.launch( decryptInteractionRequiredAction.launch(
IntentSenderRequest.Builder(pendingIntent.intentSender).build() IntentSenderRequest.Builder(pendingIntent.intentSender).build()
) )
}
} }
} decryptCredential(file, intentToResume)
decryptCredential(file, intentToResume) }
}
.getOrElse { e -> .getOrElse { e ->
logcat(ERROR) { logcat(ERROR) {
e.asLog("OpenPgpApi ACTION_DECRYPT_VERIFY failed with user interaction") e.asLog("OpenPgpApi ACTION_DECRYPT_VERIFY failed with user interaction")

View file

@ -150,25 +150,25 @@ class AutofillDecryptActivityV2 : AppCompatActivity() {
} }
.onSuccess { encryptedInput -> .onSuccess { encryptedInput ->
runCatching { runCatching {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val outputStream = ByteArrayOutputStream() val outputStream = ByteArrayOutputStream()
repository.decrypt( repository.decrypt(
password, password,
encryptedInput, encryptedInput,
outputStream, outputStream,
) )
outputStream outputStream
}
} }
}
.onFailure { e -> .onFailure { e ->
logcat(ERROR) { e.asLog("Decryption failed") } logcat(ERROR) { e.asLog("Decryption failed") }
return null return null
} }
.onSuccess { result -> .onSuccess { result ->
return runCatching { return runCatching {
val entry = passwordEntryFactory.create(result.toByteArray()) val entry = passwordEntryFactory.create(result.toByteArray())
AutofillPreferences.credentialsFromStoreEntry(this, file, entry, directoryStructure) AutofillPreferences.credentialsFromStoreEntry(this, file, entry, directoryStructure)
} }
.getOrElse { e -> .getOrElse { e ->
logcat(ERROR) { e.asLog("Failed to parse password entry") } logcat(ERROR) { e.asLog("Failed to parse password entry") }
return null return null

View file

@ -149,31 +149,31 @@ class AutofillFilterView : AppCompatActivity() {
::PasswordViewHolder, ::PasswordViewHolder,
lifecycleScope, lifecycleScope,
) { item -> ) { item ->
val file = item.file.relativeTo(item.rootDir) val file = item.file.relativeTo(item.rootDir)
val pathToIdentifier = directoryStructure.getPathToIdentifierFor(file) val pathToIdentifier = directoryStructure.getPathToIdentifierFor(file)
val identifier = directoryStructure.getIdentifierFor(file) val identifier = directoryStructure.getIdentifierFor(file)
val accountPart = directoryStructure.getAccountPartFor(file) val accountPart = directoryStructure.getAccountPartFor(file)
check(identifier != null || accountPart != null) { check(identifier != null || accountPart != null) {
"At least one of identifier and accountPart should always be non-null" "At least one of identifier and accountPart should always be non-null"
} }
title.text = title.text =
if (identifier != null) { if (identifier != null) {
buildSpannedString { buildSpannedString {
if (pathToIdentifier != null) append("$pathToIdentifier/") if (pathToIdentifier != null) append("$pathToIdentifier/")
bold { underline { append(identifier) } } bold { underline { append(identifier) } }
}
} else {
accountPart
}
subtitle.apply {
if (identifier != null && accountPart != null) {
text = accountPart
visibility = View.VISIBLE
} else {
visibility = View.GONE
} }
} else {
accountPart
}
subtitle.apply {
if (identifier != null && accountPart != null) {
text = accountPart
visibility = View.VISIBLE
} else {
visibility = View.GONE
} }
} }
}
.onItemClicked { _, item -> decryptAndFill(item) } .onItemClicked { _, item -> decryptAndFill(item) }
layoutManager = LinearLayoutManager(context) layoutManager = LinearLayoutManager(context)
} }

View file

@ -105,27 +105,28 @@ class AutofillPublisherChangedActivity : AppCompatActivity() {
private fun showPackageInfo() { private fun showPackageInfo() {
runCatching { runCatching {
with(binding) { with(binding) {
val packageInfo = packageManager.getPackageInfo(appPackage, PackageManager.GET_META_DATA) val packageInfo = packageManager.getPackageInfo(appPackage, PackageManager.GET_META_DATA)
val installTime = DateUtils.getRelativeTimeSpanString(packageInfo.firstInstallTime) val installTime = DateUtils.getRelativeTimeSpanString(packageInfo.firstInstallTime)
warningAppInstallDate.text = warningAppInstallDate.text =
getString(R.string.oreo_autofill_warning_publisher_install_time, installTime) getString(R.string.oreo_autofill_warning_publisher_install_time, installTime)
val appInfo = packageManager.getApplicationInfo(appPackage, PackageManager.GET_META_DATA) val appInfo = packageManager.getApplicationInfo(appPackage, PackageManager.GET_META_DATA)
warningAppName.text = warningAppName.text =
getString( getString(
R.string.oreo_autofill_warning_publisher_app_name, R.string.oreo_autofill_warning_publisher_app_name,
packageManager.getApplicationLabel(appInfo) packageManager.getApplicationLabel(appInfo)
) )
val currentHash = computeCertificatesHash(this@AutofillPublisherChangedActivity, appPackage) val currentHash =
warningAppAdvancedInfo.text = computeCertificatesHash(this@AutofillPublisherChangedActivity, appPackage)
getString( warningAppAdvancedInfo.text =
R.string.oreo_autofill_warning_publisher_advanced_info_template, getString(
appPackage, R.string.oreo_autofill_warning_publisher_advanced_info_template,
currentHash appPackage,
) currentHash
)
}
} }
}
.onFailure { e -> .onFailure { e ->
logcat(ERROR) { e.asLog("Failed to retrieve package info for $appPackage") } logcat(ERROR) { e.asLog("Failed to retrieve package info for $appPackage") }
finish() finish()

View file

@ -178,42 +178,42 @@ class DecryptActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound {
OpenPgpApi.RESULT_CODE_SUCCESS -> { OpenPgpApi.RESULT_CODE_SUCCESS -> {
startAutoDismissTimer() startAutoDismissTimer()
runCatching { runCatching {
val showPassword = settings.getBoolean(PreferenceKeys.SHOW_PASSWORD, true) val showPassword = settings.getBoolean(PreferenceKeys.SHOW_PASSWORD, true)
val entry = passwordEntryFactory.create(outputStream.toByteArray()) val entry = passwordEntryFactory.create(outputStream.toByteArray())
if (settings.getBoolean(PreferenceKeys.COPY_ON_DECRYPT, false)) { if (settings.getBoolean(PreferenceKeys.COPY_ON_DECRYPT, false)) {
copyPasswordToClipboard(entry.password) copyPasswordToClipboard(entry.password)
}
passwordEntry = entry
invalidateOptionsMenu()
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.username.isNullOrBlank()) {
items.add(FieldItem.createUsernameField(entry.username!!))
}
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
if (entry.hasTotp()) {
entry.totp.onEach(adapter::updateOTPCode).launchIn(lifecycleScope)
}
} }
passwordEntry = entry
invalidateOptionsMenu()
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.username.isNullOrBlank()) {
items.add(FieldItem.createUsernameField(entry.username!!))
}
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
if (entry.hasTotp()) {
entry.totp.onEach(adapter::updateOTPCode).launchIn(lifecycleScope)
}
}
.onFailure { e -> logcat(ERROR) { e.asLog() } } .onFailure { e -> logcat(ERROR) { e.asLog() } }
} }
OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> { OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {

View file

@ -56,15 +56,15 @@ class GetKeyIdsActivity : BasePgpActivity() {
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 {
val ids = val ids =
result.getLongArrayExtra(OpenPgpApi.RESULT_KEY_IDS)?.map { result.getLongArrayExtra(OpenPgpApi.RESULT_KEY_IDS)?.map {
OpenPgpUtils.convertKeyIdToHex(it) OpenPgpUtils.convertKeyIdToHex(it)
} }
?: emptyList() ?: emptyList()
val keyResult = Intent().putExtra(OpenPgpApi.EXTRA_KEY_IDS, ids.toTypedArray()) val keyResult = Intent().putExtra(OpenPgpApi.EXTRA_KEY_IDS, ids.toTypedArray())
setResult(RESULT_OK, keyResult) setResult(RESULT_OK, keyResult)
finish() finish()
} }
.onFailure { e -> logcat(ERROR) { e.asLog() } } .onFailure { e -> logcat(ERROR) { e.asLog() } }
} }
OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> { OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {

View file

@ -143,15 +143,15 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
val reader = QRCodeReader() val reader = QRCodeReader()
runCatching { runCatching {
val result = reader.decode(binaryBitmap) val result = reader.decode(binaryBitmap)
val text = result.text val text = result.text
val currentExtras = binding.extraContent.text.toString() val currentExtras = binding.extraContent.text.toString()
if (currentExtras.isNotEmpty() && currentExtras.last() != '\n') if (currentExtras.isNotEmpty() && currentExtras.last() != '\n')
binding.extraContent.append("\n$text") binding.extraContent.append("\n$text")
else binding.extraContent.append(text) else binding.extraContent.append(text)
snackbar(message = getString(R.string.otp_import_success)) snackbar(message = getString(R.string.otp_import_success))
binding.otpImportButton.isVisible = false binding.otpImportButton.isVisible = false
} }
.onFailure { snackbar(message = getString(R.string.otp_import_failure)) } .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, directory.text.toString()).findTillRoot(".gpg-id", repoRoot)
?: File(repoRoot, ".gpg-id").apply { createNewFile() } ?: File(repoRoot, ".gpg-id").apply { createNewFile() }
val gpgIdentifiers = val gpgIdentifiers =
gpgIdentifierFile.readLines().filter { it.isNotBlank() }.map { line -> gpgIdentifierFile
GpgIdentifier.fromString(line) .readLines()
?: run { .filter { it.isNotBlank() }
// The line being empty means this is most likely an empty `.gpg-id` .map { line ->
// file GpgIdentifier.fromString(line)
// we created. Skip the validation so we can make the user add a real ?: run {
// ID. // The line being empty means this is most likely an empty `.gpg-id`
if (line.isEmpty()) return@run // file
if (line.removePrefix("0x").matches("[a-fA-F0-9]{8}".toRegex())) { // we created. Skip the validation so we can make the user add a real
snackbar(message = resources.getString(R.string.short_key_ids_unsupported)) // ID.
} else { if (line.isEmpty()) return@run
snackbar(message = resources.getString(R.string.invalid_gpg_id)) 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()) { if (gpgIdentifiers.isEmpty()) {
gpgKeySelectAction.launch( gpgKeySelectAction.launch(
Intent(this@PasswordCreationActivity, GetKeyIdsActivity::class.java) 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)) { when (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
OpenPgpApi.RESULT_CODE_SUCCESS -> { OpenPgpApi.RESULT_CODE_SUCCESS -> {
runCatching { runCatching {
val file = File(path) val file = File(path)
// If we're not editing, this file should not already exist! // If we're not editing, this file should not already exist!
// Additionally, if we were editing and the incoming and outgoing // Additionally, if we were editing and the incoming and outgoing
// filenames differ, it means we renamed. Ensure that the target // filenames differ, it means we renamed. Ensure that the target
// doesn't already exist to prevent an accidental overwrite. // doesn't already exist to prevent an accidental overwrite.
if ((!editing || (editing && suggestedName != file.nameWithoutExtension)) && if ((!editing || (editing && suggestedName != file.nameWithoutExtension)) &&
file.exists() file.exists()
) { ) {
snackbar(message = getString(R.string.password_creation_duplicate_error)) 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.outputStream().use { it.write(outputStream.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)
}
}
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)
}
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 return@runCatching
} }
}
val commitMessageRes = if (!file.isInsideRepository()) {
if (editing) R.string.git_commit_edit_text else R.string.git_commit_add_text snackbar(message = getString(R.string.message_error_destination_outside_repo))
lifecycleScope.launch { return@runCatching
commitChange( }
resources.getString(commitMessageRes, getLongName(fullPath, repoPath, editName))
) withContext(Dispatchers.IO) {
.onSuccess { file.outputStream().use { it.write(outputStream.toByteArray()) }
setResult(RESULT_OK, returnIntent) }
finish()
// 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)
} }
}
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)
}
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 -> .onFailure { e ->
if (e is IOException) { if (e is IOException) {
logcat(ERROR) { e.asLog("Failed to write password file") } logcat(ERROR) { e.asLog("Failed to write password file") }

View file

@ -119,15 +119,15 @@ class PasswordCreationActivityV2 : BasePgpActivity() {
val reader = QRCodeReader() val reader = QRCodeReader()
runCatching { runCatching {
val result = reader.decode(binaryBitmap) val result = reader.decode(binaryBitmap)
val text = result.text val text = result.text
val currentExtras = binding.extraContent.text.toString() val currentExtras = binding.extraContent.text.toString()
if (currentExtras.isNotEmpty() && currentExtras.last() != '\n') if (currentExtras.isNotEmpty() && currentExtras.last() != '\n')
binding.extraContent.append("\n$text") binding.extraContent.append("\n$text")
else binding.extraContent.append(text) else binding.extraContent.append(text)
snackbar(message = getString(R.string.otp_import_success)) snackbar(message = getString(R.string.otp_import_success))
binding.otpImportButton.isVisible = false binding.otpImportButton.isVisible = false
} }
.onFailure { snackbar(message = getString(R.string.otp_import_failure)) } .onFailure { snackbar(message = getString(R.string.otp_import_failure)) }
} }
@ -357,86 +357,87 @@ class PasswordCreationActivityV2 : BasePgpActivity() {
lifecycleScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
runCatching { runCatching {
val result = val result =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val outputStream = ByteArrayOutputStream() val outputStream = ByteArrayOutputStream()
repository.encrypt(content.byteInputStream(), outputStream) repository.encrypt(content.byteInputStream(), outputStream)
outputStream outputStream
} }
val file = File(path) val file = File(path)
// If we're not editing, this file should not already exist! // If we're not editing, this file should not already exist!
// Additionally, if we were editing and the incoming and outgoing // Additionally, if we were editing and the incoming and outgoing
// filenames differ, it means we renamed. Ensure that the target // filenames differ, it means we renamed. Ensure that the target
// doesn't already exist to prevent an accidental overwrite. // doesn't already exist to prevent an accidental overwrite.
if ((!editing || (editing && suggestedName != file.nameWithoutExtension)) && file.exists() if ((!editing || (editing && suggestedName != file.nameWithoutExtension)) &&
) { file.exists()
snackbar(message = getString(R.string.password_creation_duplicate_error)) ) {
return@runCatching snackbar(message = getString(R.string.password_creation_duplicate_error))
}
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)
}
}
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)
}
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 return@runCatching
} }
}
val commitMessageRes = if (!file.isInsideRepository()) {
if (editing) R.string.git_commit_edit_text else R.string.git_commit_add_text snackbar(message = getString(R.string.message_error_destination_outside_repo))
lifecycleScope.launch { return@runCatching
commitChange( }
resources.getString(commitMessageRes, getLongName(fullPath, repoPath, editName))
) withContext(Dispatchers.IO) { file.writeBytes(result.toByteArray()) }
.onSuccess {
setResult(RESULT_OK, returnIntent) // associate the new password name with the last name's timestamp in
finish() // 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)
} }
}
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)
}
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 -> .onFailure { e ->
if (e is IOException) { if (e is IOException) {
logcat(ERROR) { e.asLog("Failed to write password file") } logcat(ERROR) { e.asLog("Failed to write password file") }

View file

@ -48,8 +48,7 @@ class PasswordGeneratorDialogFragment : DialogFragment() {
val monoTypeface = Typeface.createFromAsset(callingActivity.assets, "fonts/sourcecodepro.ttf") val monoTypeface = Typeface.createFromAsset(callingActivity.assets, "fonts/sourcecodepro.ttf")
val prefs = val prefs =
requireActivity() requireActivity()
.applicationContext .applicationContext.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE)
.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE)
builder.setView(binding.root) builder.setView(binding.root)
@ -102,21 +101,21 @@ class PasswordGeneratorDialogFragment : DialogFragment() {
val passwordLength = getLength() val passwordLength = getLength()
setPrefs(requireContext(), passwordOptions, passwordLength) setPrefs(requireContext(), passwordOptions, passwordLength)
passwordField.text = passwordField.text =
runCatching { PasswordGenerator.generate(passwordOptions, passwordLength) }.getOrElse { runCatching { PasswordGenerator.generate(passwordOptions, passwordLength) }
exception -> .getOrElse { exception ->
val errorText = val errorText =
when (exception) { when (exception) {
is MaxIterationsExceededException -> is MaxIterationsExceededException ->
requireContext().getString(R.string.pwgen_max_iterations_exceeded) requireContext().getString(R.string.pwgen_max_iterations_exceeded)
is NoCharactersIncludedException -> is NoCharactersIncludedException ->
requireContext().getString(R.string.pwgen_no_chars_error) requireContext().getString(R.string.pwgen_no_chars_error)
is PasswordLengthTooShortException -> is PasswordLengthTooShortException ->
requireContext().getString(R.string.pwgen_length_too_short_error) requireContext().getString(R.string.pwgen_length_too_short_error)
else -> requireContext().getString(R.string.pwgen_some_error_occurred) else -> requireContext().getString(R.string.pwgen_some_error_occurred)
} }
Toast.makeText(requireActivity(), errorText, Toast.LENGTH_SHORT).show() Toast.makeText(requireActivity(), errorText, Toast.LENGTH_SHORT).show()
"" ""
} }
} }
private fun isChecked(@IdRes id: Int): Boolean { private fun isChecked(@IdRes id: Int): Boolean {

View file

@ -59,18 +59,17 @@ class SelectFolderFragment : Fragment(R.layout.password_recycler_view) {
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
super.onAttach(context) super.onAttach(context)
runCatching { runCatching {
listener = listener =
object : OnFragmentInteractionListener { object : OnFragmentInteractionListener {
override fun onFragmentInteraction(item: PasswordItem) { override fun onFragmentInteraction(item: PasswordItem) {
if (item.type == PasswordItem.TYPE_CATEGORY) { if (item.type == PasswordItem.TYPE_CATEGORY) {
model.navigateTo(item.file, listMode = ListMode.DirectoriesOnly) model.navigateTo(item.file, listMode = ListMode.DirectoriesOnly)
(requireActivity() as AppCompatActivity).supportActionBar?.setDisplayHomeAsUpEnabled( (requireActivity() as AppCompatActivity)
true .supportActionBar?.setDisplayHomeAsUpEnabled(true)
) }
} }
} }
} }
}
.onFailure { .onFailure {
throw ClassCastException("$context must implement OnFragmentInteractionListener") throw ClassCastException("$context must implement OnFragmentInteractionListener")
} }

View file

@ -88,9 +88,8 @@ class GitConfigActivity : BaseGitActivity() {
binding.gitAbortRebase.alpha = if (needsAbort) 1.0f else 0.5f binding.gitAbortRebase.alpha = if (needsAbort) 1.0f else 0.5f
} }
binding.gitLog.setOnClickListener { binding.gitLog.setOnClickListener {
runCatching { startActivity(Intent(this, GitLogActivity::class.java)) }.onFailure { ex -> runCatching { startActivity(Intent(this, GitLogActivity::class.java)) }
logcat(ERROR) { "Failed to start GitLogActivity\n${ex}" } .onFailure { ex -> logcat(ERROR) { "Failed to start GitLogActivity\n${ex}" } }
}
} }
binding.gitAbortRebase.setOnClickListener { binding.gitAbortRebase.setOnClickListener {
lifecycleScope.launch { lifecycleScope.launch {
@ -142,16 +141,16 @@ class GitConfigActivity : BaseGitActivity() {
*/ */
private fun headStatusMsg(repo: Repository): String { private fun headStatusMsg(repo: Repository): String {
return runCatching { return runCatching {
val headRef = repo.getRef(Constants.HEAD) val headRef = repo.getRef(Constants.HEAD)
if (headRef.isSymbolic) { if (headRef.isSymbolic) {
val branchName = headRef.target.name val branchName = headRef.target.name
val shortBranchName = Repository.shortenRefName(branchName) val shortBranchName = Repository.shortenRefName(branchName)
getString(R.string.git_head_on_branch, shortBranchName) getString(R.string.git_head_on_branch, shortBranchName)
} else { } else {
val commitHash = headRef.objectId.abbreviate(8).name() val commitHash = headRef.objectId.abbreviate(8).name()
getString(R.string.git_head_detached, commitHash) getString(R.string.git_head_detached, commitHash)
}
} }
}
.getOrElse { ex -> .getOrElse { ex ->
logcat(ERROR) { "Error getting HEAD reference\n${ex}" } logcat(ERROR) { "Error getting HEAD reference\n${ex}" }
getString(R.string.git_head_missing) getString(R.string.git_head_missing)

View file

@ -240,24 +240,24 @@ class GitServerConfigActivity : BaseGitActivity() {
.setCancelable(false) .setCancelable(false)
.setPositiveButton(R.string.dialog_delete) { dialog, _ -> .setPositiveButton(R.string.dialog_delete) { dialog, _ ->
runCatching { runCatching {
lifecycleScope.launch { lifecycleScope.launch {
val snackbar = val snackbar =
snackbar( snackbar(
message = getString(R.string.delete_directory_progress_text), message = getString(R.string.delete_directory_progress_text),
length = Snackbar.LENGTH_INDEFINITE length = Snackbar.LENGTH_INDEFINITE
) )
withContext(Dispatchers.IO) { localDir.deleteRecursively() } withContext(Dispatchers.IO) { localDir.deleteRecursively() }
snackbar.dismiss() snackbar.dismiss()
launchGitOperation(GitOp.CLONE) launchGitOperation(GitOp.CLONE)
.fold( .fold(
success = { success = {
setResult(RESULT_OK) setResult(RESULT_OK)
finish() finish()
}, },
failure = { err -> promptOnErrorHandler(err) { finish() } } failure = { err -> promptOnErrorHandler(err) { finish() } }
) )
}
} }
}
.onFailure { e -> .onFailure { e ->
e.printStackTrace() e.printStackTrace()
MaterialAlertDialogBuilder(this).setMessage(e.message).show() MaterialAlertDialogBuilder(this).setMessage(e.message).show()
@ -268,11 +268,11 @@ class GitServerConfigActivity : BaseGitActivity() {
.show() .show()
} else { } else {
runCatching { runCatching {
// Silently delete & replace the lone .git folder if it exists // Silently delete & replace the lone .git folder if it exists
if (localDir.exists() && localDirFiles.size == 1 && localDirFiles[0].name == ".git") { if (localDir.exists() && localDirFiles.size == 1 && localDirFiles[0].name == ".git") {
localDir.deleteRecursively() localDir.deleteRecursively()
}
} }
}
.onFailure { e -> .onFailure { e ->
logcat(ERROR) { e.asLog() } logcat(ERROR) { e.asLog() }
MaterialAlertDialogBuilder(this).setMessage(e.message).show() MaterialAlertDialogBuilder(this).setMessage(e.message).show()

View file

@ -55,13 +55,13 @@ class CloneFragment : Fragment(R.layout.fragment_clone) {
private fun createRepository() { private fun createRepository() {
val localDir = PasswordRepository.getRepositoryDirectory() val localDir = PasswordRepository.getRepositoryDirectory()
runCatching { runCatching {
check(localDir.exists() || localDir.mkdir()) { "Failed to create directory!" } check(localDir.exists() || localDir.mkdir()) { "Failed to create directory!" }
PasswordRepository.createRepository(localDir) PasswordRepository.createRepository(localDir)
if (!PasswordRepository.isInitialized) { if (!PasswordRepository.isInitialized) {
PasswordRepository.initialize() PasswordRepository.initialize()
}
parentFragmentManager.performTransactionWithBackStack(KeySelectionFragment.newInstance())
} }
parentFragmentManager.performTransactionWithBackStack(KeySelectionFragment.newInstance())
}
.onFailure { e -> .onFailure { e ->
logcat(ERROR) { e.asLog() } logcat(ERROR) { e.asLog() }
if (!localDir.delete()) { if (!localDir.delete()) {

View file

@ -293,32 +293,32 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
super.onAttach(context) super.onAttach(context)
runCatching { runCatching {
listener = listener =
object : OnFragmentInteractionListener { object : OnFragmentInteractionListener {
override fun onFragmentInteraction(item: PasswordItem) { override fun onFragmentInteraction(item: PasswordItem) {
if (settings.getString(PreferenceKeys.SORT_ORDER) == if (settings.getString(PreferenceKeys.SORT_ORDER) ==
PasswordSortOrder.RECENTLY_USED.name PasswordSortOrder.RECENTLY_USED.name
) { ) {
// save the time when password was used // save the time when password was used
val preferences = val preferences =
context.getSharedPreferences("recent_password_history", Context.MODE_PRIVATE) context.getSharedPreferences("recent_password_history", Context.MODE_PRIVATE)
preferences.edit { preferences.edit {
putString(item.file.absolutePath.base64(), System.currentTimeMillis().toString()) putString(item.file.absolutePath.base64(), System.currentTimeMillis().toString())
}
} }
}
if (item.type == PasswordItem.TYPE_CATEGORY) { if (item.type == PasswordItem.TYPE_CATEGORY) {
navigateTo(item.file) navigateTo(item.file)
} else {
if (requireArguments().getBoolean("matchWith", false)) {
requireStore().matchPasswordWithApp(item)
} else { } else {
requireStore().decryptPassword(item) if (requireArguments().getBoolean("matchWith", false)) {
requireStore().matchPasswordWithApp(item)
} else {
requireStore().decryptPassword(item)
}
} }
} }
} }
} }
}
.onFailure { .onFailure {
throw ClassCastException("$context must implement OnFragmentInteractionListener") throw ClassCastException("$context must implement OnFragmentInteractionListener")
} }

View file

@ -292,9 +292,8 @@ class PasswordStore : BaseGitActivity() {
.setPositiveButton(resources.getString(R.string.dialog_ok), null) .setPositiveButton(resources.getString(R.string.dialog_ok), null)
when (id) { when (id) {
R.id.user_pref -> { R.id.user_pref -> {
runCatching { startActivity(Intent(this, SettingsActivity::class.java)) }.onFailure { e -> runCatching { startActivity(Intent(this, SettingsActivity::class.java)) }
e.printStackTrace() .onFailure { e -> e.printStackTrace() }
}
} }
R.id.git_push -> { R.id.git_push -> {
if (!PasswordRepository.isInitialized) { if (!PasswordRepository.isInitialized) {
@ -618,9 +617,7 @@ class PasswordStore : BaseGitActivity() {
fun matchPasswordWithApp(item: PasswordItem) { fun matchPasswordWithApp(item: PasswordItem) {
val path = val path =
item item.file.absolutePath
.file
.absolutePath
.replace(PasswordRepository.getRepositoryDirectory().toString() + "/", "") .replace(PasswordRepository.getRepositoryDirectory().toString() + "/", "")
.replace(".gpg", "") .replace(".gpg", "")
val data = Intent() val data = Intent()

View file

@ -42,9 +42,10 @@ class ProxySelectorActivity : AppCompatActivity() {
with(binding) { with(binding) {
proxyHost.setText(proxyPrefs.getString(PreferenceKeys.PROXY_HOST)) proxyHost.setText(proxyPrefs.getString(PreferenceKeys.PROXY_HOST))
proxyUser.setText(proxyPrefs.getString(PreferenceKeys.PROXY_USERNAME)) proxyUser.setText(proxyPrefs.getString(PreferenceKeys.PROXY_USERNAME))
proxyPrefs.getInt(PreferenceKeys.PROXY_PORT, -1).takeIf { it != -1 }?.let { proxyPrefs
proxyPort.setText("$it") .getInt(PreferenceKeys.PROXY_PORT, -1)
} .takeIf { it != -1 }
?.let { proxyPort.setText("$it") }
proxyPassword.setText(proxyPrefs.getString(PreferenceKeys.PROXY_PASSWORD)) proxyPassword.setText(proxyPrefs.getString(PreferenceKeys.PROXY_PASSWORD))
save.setOnClickListener { saveSettings() } save.setOnClickListener { saveSettings() }
proxyHost.doOnTextChanged { text, _, _, _ -> proxyHost.doOnTextChanged { text, _, _, _ ->
@ -70,18 +71,22 @@ class ProxySelectorActivity : AppCompatActivity() {
private fun saveSettings() { private fun saveSettings() {
proxyPrefs.edit { proxyPrefs.edit {
binding.proxyHost.text?.toString()?.takeIf { it.isNotEmpty() }.let { binding.proxyHost.text
gitSettings.proxyHost = it ?.toString()
} ?.takeIf { it.isNotEmpty() }
binding.proxyUser.text?.toString()?.takeIf { it.isNotEmpty() }.let { .let { gitSettings.proxyHost = it }
gitSettings.proxyUsername = it binding.proxyUser.text
} ?.toString()
binding.proxyPort.text?.toString()?.takeIf { it.isNotEmpty() }?.let { ?.takeIf { it.isNotEmpty() }
gitSettings.proxyPort = it.toInt() .let { gitSettings.proxyUsername = it }
} binding.proxyPort.text
binding.proxyPassword.text?.toString()?.takeIf { it.isNotEmpty() }.let { ?.toString()
gitSettings.proxyPassword = it ?.takeIf { it.isNotEmpty() }
} ?.let { gitSettings.proxyPort = it.toInt() }
binding.proxyPassword.text
?.toString()
?.takeIf { it.isNotEmpty() }
.let { gitSettings.proxyPassword = it }
} }
proxyUtils.setDefaultProxy() proxyUtils.setDefaultProxy()
Handler(Looper.getMainLooper()).postDelayed(500) { finish() } Handler(Looper.getMainLooper()).postDelayed(500) { finish() }

View file

@ -161,9 +161,9 @@ class RepositorySettings(private val activity: FragmentActivity) : SettingsProvi
.setCancelable(false) .setCancelable(false)
.setPositiveButton(R.string.dialog_delete) { dialogInterface, _ -> .setPositiveButton(R.string.dialog_delete) { dialogInterface, _ ->
runCatching { runCatching {
PasswordRepository.getRepositoryDirectory().deleteRecursively() PasswordRepository.getRepositoryDirectory().deleteRecursively()
PasswordRepository.closeRepository() PasswordRepository.closeRepository()
} }
.onFailure { it.message?.let { message -> activity.snackbar(message = message) } } .onFailure { it.message?.let { message -> activity.snackbar(message = message) } }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {

View file

@ -25,16 +25,16 @@ class SshKeyImportActivity : AppCompatActivity() {
return@registerForActivityResult return@registerForActivityResult
} }
runCatching { runCatching {
SshKey.import(uri) SshKey.import(uri)
Toast.makeText( Toast.makeText(
this, this,
resources.getString(R.string.ssh_key_success_dialog_title), resources.getString(R.string.ssh_key_success_dialog_title),
Toast.LENGTH_LONG Toast.LENGTH_LONG
) )
.show() .show()
setResult(RESULT_OK) setResult(RESULT_OK)
finish() finish()
} }
.onFailure { e -> .onFailure { e ->
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(this)
.setTitle(resources.getString(R.string.ssh_key_error_dialog_title)) .setTitle(resources.getString(R.string.ssh_key_error_dialog_title))

View file

@ -107,11 +107,13 @@ class AutofillMatcher {
val matchedFiles = val matchedFiles =
matchPreferences.getStringSet(matchesKey(formOrigin), emptySet())!!.map { File(it) } matchPreferences.getStringSet(matchesKey(formOrigin), emptySet())!!.map { File(it) }
return Ok( return Ok(
matchedFiles.filter { it.exists() }.also { validFiles -> matchedFiles
matchPreferences.edit { .filter { it.exists() }
putStringSet(matchesKey(formOrigin), validFiles.map { it.absolutePath }.toSet()) .also { validFiles ->
matchPreferences.edit {
putStringSet(matchesKey(formOrigin), validFiles.map { it.absolutePath }.toSet())
}
} }
}
) )
} }

View file

@ -34,9 +34,10 @@ fun File.contains(other: File): Boolean {
if (!isDirectory) return false if (!isDirectory) return false
if (!other.exists()) return false if (!other.exists()) return false
val relativePath = val relativePath =
runCatching { other.relativeTo(this) }.getOrElse { runCatching { other.relativeTo(this) }
return false .getOrElse {
} return false
}
// Direct containment is equivalent to the relative path being equal to the filename. // Direct containment is equivalent to the relative path being equal to the filename.
return relativePath.path == other.name return relativePath.path == other.name
} }

View file

@ -51,76 +51,76 @@ class GitCommandExecutor(
// Count the number of uncommitted files // Count the number of uncommitted files
var nbChanges = 0 var nbChanges = 0
return runCatching { return runCatching {
for (command in operation.commands) { for (command in operation.commands) {
when (command) { when (command) {
is StatusCommand -> { is StatusCommand -> {
val res = withContext(Dispatchers.IO) { command.call() } val res = withContext(Dispatchers.IO) { command.call() }
nbChanges = res.uncommittedChanges.size 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()
}
} }
} is CommitCommand -> {
is PullCommand -> { // the previous status will eventually be used to avoid a commit
val result = withContext(Dispatchers.IO) { command.call() } if (nbChanges > 0) {
if (result.rebaseResult != null) { withContext(Dispatchers.IO) {
if (!result.rebaseResult.status.isSuccessful) { val name = gitSettings.authorName.ifEmpty { "root" }
throw PullException.PullRebaseFailed val email = gitSettings.authorEmail.ifEmpty { "localhost" }
} val identity = PersonIdent(name, email)
} else if (result.mergeResult != null) { command.setAuthor(identity).setCommitter(identity).call()
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)
}
}
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 -> {}
} }
} }
} }
} is PullCommand -> {
else -> { val result = withContext(Dispatchers.IO) { command.call() }
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)
}
}
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 -> {
withContext(Dispatchers.IO) { command.call() }
}
} }
} }
} }
}
.also { snackbar.dismiss() } .also { snackbar.dismiss() }
} }

View file

@ -25,10 +25,11 @@ private fun commits(): Iterable<RevCommit> {
logcat(TAG, ERROR) { "Could not access git repository" } logcat(TAG, ERROR) { "Could not access git repository" }
return listOf() return listOf()
} }
return runCatching { Git(repo).log().call() }.getOrElse { e -> return runCatching { Git(repo).log().call() }
logcat(TAG, ERROR) { e.asLog("Failed to obtain git commits") } .getOrElse { e ->
listOf() logcat(TAG, ERROR) { e.asLog("Failed to obtain git commits") }
} listOf()
}
} }
/** /**

View file

@ -109,14 +109,14 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
private fun getSshKey(make: Boolean) { private fun getSshKey(make: Boolean) {
runCatching { runCatching {
val intent = val intent =
if (make) { if (make) {
Intent(callingActivity.applicationContext, SshKeyGenActivity::class.java) Intent(callingActivity.applicationContext, SshKeyGenActivity::class.java)
} else { } else {
Intent(callingActivity.applicationContext, SshKeyImportActivity::class.java) Intent(callingActivity.applicationContext, SshKeyImportActivity::class.java)
} }
callingActivity.startActivity(intent) callingActivity.startActivity(intent)
} }
.onFailure { e -> logcat(ERROR) { e.asLog() } } .onFailure { e -> logcat(ERROR) { e.asLog() } }
} }

View file

@ -84,19 +84,19 @@ object SshKey {
val mustAuthenticate: Boolean val mustAuthenticate: Boolean
get() { get() {
return runCatching { return runCatching {
if (type !in listOf(Type.KeystoreNative, Type.KeystoreWrappedEd25519)) return false if (type !in listOf(Type.KeystoreNative, Type.KeystoreWrappedEd25519)) return false
when (val key = androidKeystore.getKey(KEYSTORE_ALIAS, null)) { when (val key = androidKeystore.getKey(KEYSTORE_ALIAS, null)) {
is PrivateKey -> { is PrivateKey -> {
val factory = KeyFactory.getInstance(key.algorithm, PROVIDER_ANDROID_KEY_STORE) val factory = KeyFactory.getInstance(key.algorithm, PROVIDER_ANDROID_KEY_STORE)
return factory.getKeySpec(key, KeyInfo::class.java).isUserAuthenticationRequired 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 -> .getOrElse { error ->
// It is fine to swallow the exception here since it will reappear when the key // It is fine to swallow the exception here since it will reappear when the key
// is // is
@ -316,19 +316,24 @@ object SshKey {
private object KeystoreNativeKeyProvider : KeyProvider { private object KeystoreNativeKeyProvider : KeyProvider {
override fun getPublic(): PublicKey = override fun getPublic(): PublicKey =
runCatching { androidKeystore.sshPublicKey!! }.getOrElse { error -> runCatching { androidKeystore.sshPublicKey!! }
logcat { error.asLog() } .getOrElse { error ->
throw IOException("Failed to get public key '$KEYSTORE_ALIAS' from Android Keystore", error) logcat { error.asLog() }
} throw IOException(
"Failed to get public key '$KEYSTORE_ALIAS' from Android Keystore",
error
)
}
override fun getPrivate(): PrivateKey = override fun getPrivate(): PrivateKey =
runCatching { androidKeystore.sshPrivateKey!! }.getOrElse { error -> runCatching { androidKeystore.sshPrivateKey!! }
logcat { error.asLog() } .getOrElse { error ->
throw IOException( logcat { error.asLog() }
"Failed to access private key '$KEYSTORE_ALIAS' from Android Keystore", throw IOException(
error "Failed to access private key '$KEYSTORE_ALIAS' from Android Keystore",
) error
} )
}
override fun getType(): KeyType = KeyType.fromKey(public) override fun getType(): KeyType = KeyType.fromKey(public)
} }
@ -336,22 +341,23 @@ object SshKey {
private object KeystoreWrappedEd25519KeyProvider : KeyProvider { private object KeystoreWrappedEd25519KeyProvider : KeyProvider {
override fun getPublic(): PublicKey = override fun getPublic(): PublicKey =
runCatching { parseSshPublicKey(sshPublicKey!!)!! }.getOrElse { error -> runCatching { parseSshPublicKey(sshPublicKey!!)!! }
logcat { error.asLog() } .getOrElse { error ->
throw IOException("Failed to get the public key for wrapped ed25519 key", error) logcat { error.asLog() }
} throw IOException("Failed to get the public key for wrapped ed25519 key", error)
}
override fun getPrivate(): PrivateKey = override fun getPrivate(): PrivateKey =
runCatching { runCatching {
// The current MasterKey API does not allow getting a reference to an existing one // 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 // 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. // for `requireAuthentication` is not used as the key already exists at this point.
val encryptedPrivateKeyFile = runBlocking { getOrCreateWrappedPrivateKeyFile(false) } val encryptedPrivateKeyFile = runBlocking { getOrCreateWrappedPrivateKeyFile(false) }
val rawPrivateKey = encryptedPrivateKeyFile.openFileInput().use { it.readBytes() } val rawPrivateKey = encryptedPrivateKeyFile.openFileInput().use { it.readBytes() }
EdDSAPrivateKey( EdDSAPrivateKey(
EdDSAPrivateKeySpec(rawPrivateKey, EdDSANamedCurveTable.ED_25519_CURVE_SPEC) EdDSAPrivateKeySpec(rawPrivateKey, EdDSANamedCurveTable.ED_25519_CURVE_SPEC)
) )
} }
.getOrElse { error -> .getOrElse { error ->
logcat { error.asLog() } logcat { error.asLog() }
throw IOException("Failed to unwrap wrapped ed25519 key", error) throw IOException("Failed to unwrap wrapped ed25519 key", error)

View file

@ -93,9 +93,8 @@ private fun makeTofuHostKeyVerifier(hostKeyFile: File): HostKeyVerifier {
return object : HostKeyVerifier { return object : HostKeyVerifier {
override fun verify(hostname: String?, port: Int, key: PublicKey?): Boolean { override fun verify(hostname: String?, port: Int, key: PublicKey?): Boolean {
val digest = val digest =
runCatching { SecurityUtils.getMessageDigest("SHA-256") }.getOrElse { e -> runCatching { SecurityUtils.getMessageDigest("SHA-256") }
throw SSHRuntimeException(e) .getOrElse { e -> throw SSHRuntimeException(e) }
}
digest.update(PlainBuffer().putPublicKey(key).compactData) digest.update(PlainBuffer().putPublicKey(key).compactData)
val digestData = digest.digest() val digestData = digest.digest()
val hostKeyEntry = "SHA256:${Base64.encodeToString(digestData, Base64.NO_WRAP)}" val hostKeyEntry = "SHA256:${Base64.encodeToString(digestData, Base64.NO_WRAP)}"

View file

@ -140,9 +140,10 @@ constructor(
newBranch: String newBranch: String
): UpdateConnectionSettingsResult { ): UpdateConnectionSettingsResult {
val parsedUrl = val parsedUrl =
runCatching { URIish(newUrl) }.getOrElse { runCatching { URIish(newUrl) }
return UpdateConnectionSettingsResult.FailedToParseUrl .getOrElse {
} return UpdateConnectionSettingsResult.FailedToParseUrl
}
val newProtocol = val newProtocol =
when (parsedUrl.scheme) { when (parsedUrl.scheme) {
in listOf("http", "https") -> Protocol.Https in listOf("http", "https") -> Protocol.Https

View file

@ -229,9 +229,8 @@ class SearchableRepositoryViewModel(application: Application) : AndroidViewModel
.filter { it.first > 0 } .filter { it.first > 0 }
.toList() .toList()
.sortedWith( .sortedWith(
compareByDescending<Pair<Int, PasswordItem>> { it.first }.thenBy(itemComparator) { compareByDescending<Pair<Int, PasswordItem>> { it.first }
it.second .thenBy(itemComparator) { it.second }
}
) )
.map { it.second } .map { it.second }
} }

View file

@ -130,8 +130,8 @@ class AutofillSmsActivity : AppCompatActivity() {
private suspend fun waitForSms() { private suspend fun waitForSms() {
val smsClient = SmsCodeRetriever.getAutofillClient(this@AutofillSmsActivity) val smsClient = SmsCodeRetriever.getAutofillClient(this@AutofillSmsActivity)
runCatching { runCatching {
withContext(Dispatchers.IO) { smsClient.startSmsCodeRetriever().suspendableAwait() } withContext(Dispatchers.IO) { smsClient.startSmsCodeRetriever().suspendableAwait() }
} }
.onFailure { e -> .onFailure { e ->
if (e is ResolvableApiException) { if (e is ResolvableApiException) {
e.startResolutionForResult(this@AutofillSmsActivity, 1) e.startResolutionForResult(this@AutofillSmsActivity, 1)

View file

@ -105,31 +105,34 @@ internal class SingleFieldMatcher(
} }
override fun match(fields: List<FormField>, alreadyMatched: List<FormField>): List<FormField>? { override fun match(fields: List<FormField>, alreadyMatched: List<FormField>): List<FormField>? {
return fields.minus(alreadyMatched).filter { take(it, alreadyMatched) }.let { contestants -> return fields
when (contestants.size) { .minus(alreadyMatched)
1 -> return@let listOf(contestants.single()) .filter { take(it, alreadyMatched) }
0 -> return@let null .let { contestants ->
} when (contestants.size) {
var current = contestants 1 -> return@let listOf(contestants.single())
for ((i, tieBreaker) in tieBreakers.withIndex()) { 0 -> return@let null
// Successively filter matched fields via tie breakers...
val new = current.filter { tieBreaker(it, alreadyMatched) }
// skipping those tie breakers that are not satisfied for any remaining field...
if (new.isEmpty()) {
logcat { "Tie breaker #${i + 1}: Didn't match any field; skipping" }
continue
} }
// and return if the available options have been narrowed to a single field. var current = contestants
if (new.size == 1) { for ((i, tieBreaker) in tieBreakers.withIndex()) {
logcat { "Tie breaker #${i + 1}: Success" } // Successively filter matched fields via tie breakers...
val new = current.filter { tieBreaker(it, alreadyMatched) }
// skipping those tie breakers that are not satisfied for any remaining field...
if (new.isEmpty()) {
logcat { "Tie breaker #${i + 1}: Didn't match any field; skipping" }
continue
}
// and return if the available options have been narrowed to a single field.
if (new.size == 1) {
logcat { "Tie breaker #${i + 1}: Success" }
current = new
break
}
logcat { "Tie breaker #${i + 1}: Matched ${new.size} fields; continuing" }
current = new current = new
break
} }
logcat { "Tie breaker #${i + 1}: Matched ${new.size} fields; continuing" } listOf(current.singleOrNull() ?: return null)
current = new
} }
listOf(current.singleOrNull() ?: return null)
}
} }
} }

View file

@ -251,8 +251,7 @@ public fun getInstalledBrowsersWithAutofillSupportLevel(
.map { it to getBrowserAutofillSupportLevel(context, it.activityInfo.packageName) } .map { it to getBrowserAutofillSupportLevel(context, it.activityInfo.packageName) }
.filter { it.first.isDefault || it.second != BrowserAutofillSupportLevel.None } .filter { it.first.isDefault || it.second != BrowserAutofillSupportLevel.None }
.map { .map {
context context.packageManager
.packageManager
.getApplicationLabel(it.first.activityInfo.applicationInfo) .getApplicationLabel(it.first.activityInfo.applicationInfo)
.toString() to it.second .toString() to it.second
} }

View file

@ -182,9 +182,10 @@ internal class FormField(
// Basic type detection for HTML fields // Basic type detection for HTML fields
private val htmlTag = node.htmlInfo?.tag private val htmlTag = node.htmlInfo?.tag
private val htmlAttributes: Map<String, String> = private val htmlAttributes: Map<String, String> =
node.htmlInfo?.attributes?.filter { it.first != null && it.second != null }?.associate { node.htmlInfo
Pair(it.first.lowercase(Locale.US), it.second.lowercase(Locale.US)) ?.attributes
} ?.filter { it.first != null && it.second != null }
?.associate { Pair(it.first.lowercase(Locale.US), it.second.lowercase(Locale.US)) }
?: emptyMap() ?: emptyMap()
private val htmlAttributesDebug = htmlAttributes.entries.joinToString { "${it.key}=${it.value}" } private val htmlAttributesDebug = htmlAttributes.entries.joinToString { "${it.key}=${it.value}" }
private val htmlInputType = htmlAttributes["type"] private val htmlInputType = htmlAttributes["type"]

View file

@ -32,9 +32,9 @@ afterEvaluate {
} }
} }
// disable kapt tasks for unit tests // disable kapt tasks for unit tests
tasks.matching { it.name.startsWith("kapt") && it.name.endsWith("UnitTestKotlin") }.configureEach { tasks
enabled = false .matching { it.name.startsWith("kapt") && it.name.endsWith("UnitTestKotlin") }
} .configureEach { enabled = false }
fun Project.hasDaggerCompilerDependency(): Boolean { fun Project.hasDaggerCompilerDependency(): Boolean {
return configurations.any { return configurations.any {

View file

@ -79,17 +79,17 @@ constructor(
when (id) { when (id) {
is GpgIdentifier.KeyId -> { is GpgIdentifier.KeyId -> {
val keyIdMatch = val keyIdMatch =
keys.map { key -> key to tryGetId(key) }.firstOrNull { (_, keyId) -> keys
keyId?.id == id.id .map { key -> key to tryGetId(key) }
} .firstOrNull { (_, keyId) -> keyId?.id == id.id }
keyIdMatch?.first keyIdMatch?.first
} }
is GpgIdentifier.UserId -> { is GpgIdentifier.UserId -> {
val selector = SelectUserId.byEmail(id.email) val selector = SelectUserId.byEmail(id.email)
val userIdMatch = val userIdMatch =
keys.map { key -> key to tryParseKeyring(key) }.firstOrNull { (_, keyRing) -> keys
selector.firstMatch(keyRing) != null .map { key -> key to tryParseKeyring(key) }
} .firstOrNull { (_, keyRing) -> selector.firstMatch(keyRing) != null }
userIdMatch?.first userIdMatch?.first
} }
} }