all: reformat with ktfmt 0.36
This commit is contained in:
parent
11720e9542
commit
aaf6ceb8ec
34 changed files with 637 additions and 614 deletions
|
@ -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])
|
||||
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.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() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) } }
|
||||
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
|
||||
}
|
||||
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) }
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
||||
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() } }
|
||||
}
|
||||
OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
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()
|
||||
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 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 (!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
|
||||
}
|
||||
}
|
||||
|
||||
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") }
|
||||
|
|
|
@ -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 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)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
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 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 (!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
|
||||
}
|
||||
}
|
||||
|
||||
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") }
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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() }
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
} 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 -> {}
|
||||
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 -> {
|
||||
withContext(Dispatchers.IO) { command.call() }
|
||||
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)
|
||||
}
|
||||
}
|
||||
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() }
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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() } }
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)}"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
@ -130,8 +130,8 @@ class AutofillSmsActivity : AppCompatActivity() {
|
|||
private suspend fun waitForSms() {
|
||||
val smsClient = SmsCodeRetriever.getAutofillClient(this@AutofillSmsActivity)
|
||||
runCatching {
|
||||
withContext(Dispatchers.IO) { smsClient.startSmsCodeRetriever().suspendableAwait() }
|
||||
}
|
||||
withContext(Dispatchers.IO) { smsClient.startSmsCodeRetriever().suspendableAwait() }
|
||||
}
|
||||
.onFailure { e ->
|
||||
if (e is ResolvableApiException) {
|
||||
e.startResolutionForResult(this@AutofillSmsActivity, 1)
|
||||
|
|
|
@ -105,31 +105,34 @@ internal class SingleFieldMatcher(
|
|||
}
|
||||
|
||||
override fun match(fields: List<FormField>, alreadyMatched: List<FormField>): List<FormField>? {
|
||||
return fields.minus(alreadyMatched).filter { take(it, alreadyMatched) }.let { contestants ->
|
||||
when (contestants.size) {
|
||||
1 -> return@let listOf(contestants.single())
|
||||
0 -> return@let null
|
||||
}
|
||||
var current = contestants
|
||||
for ((i, tieBreaker) in tieBreakers.withIndex()) {
|
||||
// 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
|
||||
return fields
|
||||
.minus(alreadyMatched)
|
||||
.filter { take(it, alreadyMatched) }
|
||||
.let { contestants ->
|
||||
when (contestants.size) {
|
||||
1 -> return@let listOf(contestants.single())
|
||||
0 -> return@let null
|
||||
}
|
||||
// and return if the available options have been narrowed to a single field.
|
||||
if (new.size == 1) {
|
||||
logcat { "Tie breaker #${i + 1}: Success" }
|
||||
var current = contestants
|
||||
for ((i, tieBreaker) in tieBreakers.withIndex()) {
|
||||
// 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
|
||||
break
|
||||
}
|
||||
logcat { "Tie breaker #${i + 1}: Matched ${new.size} fields; continuing" }
|
||||
current = new
|
||||
listOf(current.singleOrNull() ?: return null)
|
||||
}
|
||||
listOf(current.singleOrNull() ?: return null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -251,8 +251,7 @@ public fun getInstalledBrowsersWithAutofillSupportLevel(
|
|||
.map { it to getBrowserAutofillSupportLevel(context, it.activityInfo.packageName) }
|
||||
.filter { it.first.isDefault || it.second != BrowserAutofillSupportLevel.None }
|
||||
.map {
|
||||
context
|
||||
.packageManager
|
||||
context.packageManager
|
||||
.getApplicationLabel(it.first.activityInfo.applicationInfo)
|
||||
.toString() to it.second
|
||||
}
|
||||
|
|
|
@ -182,9 +182,10 @@ internal class FormField(
|
|||
// Basic type detection for HTML fields
|
||||
private val htmlTag = node.htmlInfo?.tag
|
||||
private val htmlAttributes: Map<String, String> =
|
||||
node.htmlInfo?.attributes?.filter { it.first != null && it.second != null }?.associate {
|
||||
Pair(it.first.lowercase(Locale.US), it.second.lowercase(Locale.US))
|
||||
}
|
||||
node.htmlInfo
|
||||
?.attributes
|
||||
?.filter { it.first != null && it.second != null }
|
||||
?.associate { Pair(it.first.lowercase(Locale.US), it.second.lowercase(Locale.US)) }
|
||||
?: emptyMap()
|
||||
private val htmlAttributesDebug = htmlAttributes.entries.joinToString { "${it.key}=${it.value}" }
|
||||
private val htmlInputType = htmlAttributes["type"]
|
||||
|
|
|
@ -32,9 +32,9 @@ afterEvaluate {
|
|||
}
|
||||
}
|
||||
// disable kapt tasks for unit tests
|
||||
tasks.matching { it.name.startsWith("kapt") && it.name.endsWith("UnitTestKotlin") }.configureEach {
|
||||
enabled = false
|
||||
}
|
||||
tasks
|
||||
.matching { it.name.startsWith("kapt") && it.name.endsWith("UnitTestKotlin") }
|
||||
.configureEach { enabled = false }
|
||||
|
||||
fun Project.hasDaggerCompilerDependency(): Boolean {
|
||||
return configurations.any {
|
||||
|
|
|
@ -79,17 +79,17 @@ constructor(
|
|||
when (id) {
|
||||
is GpgIdentifier.KeyId -> {
|
||||
val keyIdMatch =
|
||||
keys.map { key -> key to tryGetId(key) }.firstOrNull { (_, keyId) ->
|
||||
keyId?.id == id.id
|
||||
}
|
||||
keys
|
||||
.map { key -> key to tryGetId(key) }
|
||||
.firstOrNull { (_, keyId) -> keyId?.id == id.id }
|
||||
keyIdMatch?.first
|
||||
}
|
||||
is GpgIdentifier.UserId -> {
|
||||
val selector = SelectUserId.byEmail(id.email)
|
||||
val userIdMatch =
|
||||
keys.map { key -> key to tryParseKeyring(key) }.firstOrNull { (_, keyRing) ->
|
||||
selector.firstMatch(keyRing) != null
|
||||
}
|
||||
keys
|
||||
.map { key -> key to tryParseKeyring(key) }
|
||||
.firstOrNull { (_, keyRing) -> selector.firstMatch(keyRing) != null }
|
||||
userIdMatch?.first
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue