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) {
|
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() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 -> {
|
||||||
|
|
|
@ -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 -> {
|
||||||
|
|
|
@ -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") }
|
||||||
|
|
|
@ -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") }
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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() }
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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() } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)}"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"]
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue