mirror of
https://github.com/android-password-store/Android-Password-Store
synced 2025-09-01 06:45:19 +00:00
Wire in fallback key selection flow (#958)
Co-authored-by: Fabian Henneke <fabian@henneke.me>
This commit is contained in:
@@ -81,6 +81,11 @@
|
|||||||
android:parentActivityName=".PasswordStore"
|
android:parentActivityName=".PasswordStore"
|
||||||
android:windowSoftInputMode="adjustResize" />
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".crypto.GetKeyIdsActivity"
|
||||||
|
android:parentActivityName=".PasswordStore"
|
||||||
|
android:theme="@style/NoBackgroundTheme" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".autofill.AutofillService"
|
android:name=".autofill.AutofillService"
|
||||||
android:enabled="@bool/enable_accessibility_autofill"
|
android:enabled="@bool/enable_accessibility_autofill"
|
||||||
|
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.zeapo.pwdstore.crypto
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.result.IntentSenderRequest
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.github.ajalt.timberkt.e
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import me.msfjarvis.openpgpktx.util.OpenPgpApi
|
||||||
|
import me.msfjarvis.openpgpktx.util.OpenPgpUtils
|
||||||
|
import org.openintents.openpgp.IOpenPgpService2
|
||||||
|
|
||||||
|
class GetKeyIdsActivity : BasePgpActivity() {
|
||||||
|
|
||||||
|
private val userInteractionRequiredResult = registerForActivityResult(StartIntentSenderForResult()) { result ->
|
||||||
|
if (result.data == null || result.resultCode == RESULT_CANCELED) {
|
||||||
|
setResult(RESULT_CANCELED, result.data)
|
||||||
|
finish()
|
||||||
|
return@registerForActivityResult
|
||||||
|
}
|
||||||
|
getKeyIds(result.data!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
bindToOpenKeychain(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBound(service: IOpenPgpService2) {
|
||||||
|
super.onBound(service)
|
||||||
|
getKeyIds()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(e: Exception) {
|
||||||
|
e(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Key ids from OpenKeychain
|
||||||
|
*/
|
||||||
|
private fun getKeyIds(data: Intent = Intent()) {
|
||||||
|
data.action = OpenPgpApi.ACTION_GET_KEY_IDS
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
api?.executeApiAsync(data, null, null) { result ->
|
||||||
|
when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||||
|
OpenPgpApi.RESULT_CODE_SUCCESS -> {
|
||||||
|
try {
|
||||||
|
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()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {
|
||||||
|
val sender = getUserInteractionRequestIntent(result)
|
||||||
|
userInteractionRequiredResult.launch(IntentSenderRequest.Builder(sender).build())
|
||||||
|
}
|
||||||
|
OpenPgpApi.RESULT_CODE_ERROR -> handleError(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -250,7 +250,7 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
|
|||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
private fun parseGpgIdentifier(identifier: String) : GpgIdentifier? {
|
private fun parseGpgIdentifier(identifier: String): GpgIdentifier? {
|
||||||
// Match long key IDs:
|
// Match long key IDs:
|
||||||
// FF22334455667788 or 0xFF22334455667788
|
// FF22334455667788 or 0xFF22334455667788
|
||||||
val maybeLongKeyId = identifier.removePrefix("0x").takeIf {
|
val maybeLongKeyId = identifier.removePrefix("0x").takeIf {
|
||||||
@@ -279,166 +279,196 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
|
|||||||
/**
|
/**
|
||||||
* Encrypts the password and the extra content
|
* Encrypts the password and the extra content
|
||||||
*/
|
*/
|
||||||
private fun encrypt(receivedIntent: Intent? = null) = with(binding) {
|
private fun encrypt(receivedIntent: Intent? = null) {
|
||||||
val editName = filename.text.toString().trim()
|
with(binding) {
|
||||||
val editPass = password.text.toString()
|
val editName = filename.text.toString().trim()
|
||||||
val editExtra = extraContent.text.toString()
|
val editPass = password.text.toString()
|
||||||
|
val editExtra = extraContent.text.toString()
|
||||||
|
|
||||||
if (editName.isEmpty()) {
|
if (editName.isEmpty()) {
|
||||||
snackbar(message = resources.getString(R.string.file_toast_text))
|
snackbar(message = resources.getString(R.string.file_toast_text))
|
||||||
return@with
|
return@with
|
||||||
} else if (editName.contains('/')) {
|
} else if (editName.contains('/')) {
|
||||||
snackbar(message = resources.getString(R.string.invalid_filename_text))
|
snackbar(message = resources.getString(R.string.invalid_filename_text))
|
||||||
return@with
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editPass.isEmpty() && editExtra.isEmpty()) {
|
|
||||||
snackbar(message = resources.getString(R.string.empty_toast_text))
|
|
||||||
return@with
|
|
||||||
}
|
|
||||||
|
|
||||||
if (copy) {
|
|
||||||
copyPasswordToClipboard(editPass)
|
|
||||||
}
|
|
||||||
|
|
||||||
val data = receivedIntent ?: Intent()
|
|
||||||
data.action = OpenPgpApi.ACTION_ENCRYPT
|
|
||||||
|
|
||||||
// pass enters the key ID into `.gpg-id`.
|
|
||||||
val repoRoot = PasswordRepository.getRepositoryDirectory(applicationContext)
|
|
||||||
val gpgIdentifierFile = File(repoRoot, directory.text.toString()).findTillRoot(".gpg-id", repoRoot)
|
|
||||||
if (gpgIdentifierFile == null) {
|
|
||||||
snackbar(message = resources.getString(R.string.failed_to_find_key_id))
|
|
||||||
return@with
|
|
||||||
}
|
|
||||||
val gpgIdentifierFileContent = gpgIdentifierFile.useLines { it.firstOrNull() } ?: ""
|
|
||||||
when (val identifier = parseGpgIdentifier(gpgIdentifierFileContent)) {
|
|
||||||
is GpgIdentifier.KeyId -> data.putExtra(OpenPgpApi.EXTRA_KEY_IDS, arrayOf(identifier.id).toLongArray())
|
|
||||||
is GpgIdentifier.UserId -> data.putExtra(OpenPgpApi.EXTRA_USER_IDS, arrayOf(identifier.email))
|
|
||||||
null -> {
|
|
||||||
snackbar(message = resources.getString(R.string.invalid_gpg_id))
|
|
||||||
return@with
|
return@with
|
||||||
}
|
}
|
||||||
}
|
|
||||||
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true)
|
|
||||||
|
|
||||||
val content = "$editPass\n$editExtra"
|
if (editPass.isEmpty() && editExtra.isEmpty()) {
|
||||||
val inputStream = ByteArrayInputStream(content.toByteArray())
|
snackbar(message = resources.getString(R.string.empty_toast_text))
|
||||||
val outputStream = ByteArrayOutputStream()
|
return@with
|
||||||
|
|
||||||
val path = when {
|
|
||||||
// If we allowed the user to edit the relative path, we have to consider it here instead
|
|
||||||
// of fullPath.
|
|
||||||
directoryInputLayout.isEnabled -> {
|
|
||||||
val editRelativePath = directory.text.toString().trim()
|
|
||||||
if (editRelativePath.isEmpty()) {
|
|
||||||
snackbar(message = resources.getString(R.string.path_toast_text))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val passwordDirectory = File("$repoPath/${editRelativePath.trim('/')}")
|
|
||||||
if (!passwordDirectory.exists() && !passwordDirectory.mkdir()) {
|
|
||||||
snackbar(message = "Failed to create directory ${editRelativePath.trim('/')}")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
"${passwordDirectory.path}/$editName.gpg"
|
|
||||||
}
|
}
|
||||||
else -> "$fullPath/$editName.gpg"
|
|
||||||
}
|
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
if (copy) {
|
||||||
api?.executeApiAsync(data, inputStream, outputStream) { result ->
|
copyPasswordToClipboard(editPass)
|
||||||
when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
|
}
|
||||||
OpenPgpApi.RESULT_CODE_SUCCESS -> {
|
|
||||||
try {
|
|
||||||
val file = File(path)
|
|
||||||
// If we're not editing, this file should not already exist!
|
|
||||||
if (!editing && file.exists()) {
|
|
||||||
snackbar(message = getString(R.string.password_creation_duplicate_error))
|
|
||||||
return@executeApiAsync
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isInsideRepository(file)) {
|
val data = receivedIntent ?: Intent()
|
||||||
snackbar(message = getString(R.string.message_error_destination_outside_repo))
|
data.action = OpenPgpApi.ACTION_ENCRYPT
|
||||||
return@executeApiAsync
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
file.outputStream().use {
|
|
||||||
it.write(outputStream.toByteArray())
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
e(e) { "Failed to write password file" }
|
|
||||||
setResult(RESULT_CANCELED)
|
|
||||||
MaterialAlertDialogBuilder(this@PasswordCreationActivity)
|
|
||||||
.setTitle(getString(R.string.password_creation_file_fail_title))
|
|
||||||
.setMessage(getString(R.string.password_creation_file_write_fail_message))
|
|
||||||
.setCancelable(false)
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
return@executeApiAsync
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = PasswordEntry(content)
|
|
||||||
returnIntent.putExtra(RETURN_EXTRA_PASSWORD, entry.password)
|
|
||||||
val username = PasswordEntry(content).username
|
|
||||||
?: directoryStructure.getUsernameFor(file)
|
|
||||||
returnIntent.putExtra(RETURN_EXTRA_USERNAME, username)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// pass enters the key ID into `.gpg-id`.
|
||||||
|
val repoRoot = PasswordRepository.getRepositoryDirectory(applicationContext)
|
||||||
|
val gpgIdentifierFile = File(repoRoot, directory.text.toString()).findTillRoot(".gpg-id", repoRoot)
|
||||||
|
if (gpgIdentifierFile == null) {
|
||||||
|
snackbar(message = resources.getString(R.string.failed_to_find_key_id))
|
||||||
|
return@with
|
||||||
|
}
|
||||||
|
val gpgIdentifiers = gpgIdentifierFile.readLines()
|
||||||
|
.filter { it.isNotBlank() }
|
||||||
|
.map { line ->
|
||||||
|
parseGpgIdentifier(line) ?: run {
|
||||||
|
snackbar(message = resources.getString(R.string.invalid_gpg_id))
|
||||||
|
return@with
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (gpgIdentifiers.isEmpty()) {
|
||||||
|
registerForActivityResult(StartActivityForResult()) { result ->
|
||||||
|
if (result.resultCode == RESULT_OK) {
|
||||||
|
result.data?.getStringArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)?.let { keyIds ->
|
||||||
|
gpgIdentifierFile.writeText(keyIds.joinToString("\n"))
|
||||||
val repo = PasswordRepository.getRepository(null)
|
val repo = PasswordRepository.getRepository(null)
|
||||||
if (repo != null) {
|
if (repo != null) {
|
||||||
val status = Git(repo).status().call()
|
commitChange(
|
||||||
if (status.modified.isNotEmpty()) {
|
getString(
|
||||||
commitChange(
|
R.string.git_commit_gpg_id,
|
||||||
getString(
|
getLongName(gpgIdentifierFile.parentFile!!.absolutePath, repoPath, gpgIdentifierFile.name)
|
||||||
R.string.git_commit_edit_text,
|
|
||||||
getLongName(fullPath, repoPath, editName)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
encrypt(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.launch(Intent(this@PasswordCreationActivity, GetKeyIdsActivity::class.java))
|
||||||
|
return@with
|
||||||
|
}
|
||||||
|
val keyIds = gpgIdentifiers.filterIsInstance<GpgIdentifier.KeyId>().map { it.id }.toLongArray()
|
||||||
|
if (keyIds.isNotEmpty()) {
|
||||||
|
data.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keyIds)
|
||||||
|
}
|
||||||
|
val userIds = gpgIdentifiers.filterIsInstance<GpgIdentifier.UserId>().map { it.email }.toTypedArray()
|
||||||
|
if (userIds.isNotEmpty()) {
|
||||||
|
data.putExtra(OpenPgpApi.EXTRA_USER_IDS, userIds)
|
||||||
|
}
|
||||||
|
|
||||||
if (directoryInputLayout.isVisible && directoryInputLayout.isEnabled && oldFileName != null) {
|
data.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true)
|
||||||
val oldFile = File("$repoPath/${oldCategory?.trim('/')}/$oldFileName.gpg")
|
|
||||||
if (oldFile.path != file.path && !oldFile.delete()) {
|
val content = "$editPass\n$editExtra"
|
||||||
|
val inputStream = ByteArrayInputStream(content.toByteArray())
|
||||||
|
val outputStream = ByteArrayOutputStream()
|
||||||
|
|
||||||
|
val path = when {
|
||||||
|
// If we allowed the user to edit the relative path, we have to consider it here instead
|
||||||
|
// of fullPath.
|
||||||
|
directoryInputLayout.isEnabled -> {
|
||||||
|
val editRelativePath = directory.text.toString().trim()
|
||||||
|
if (editRelativePath.isEmpty()) {
|
||||||
|
snackbar(message = resources.getString(R.string.path_toast_text))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val passwordDirectory = File("$repoPath/${editRelativePath.trim('/')}")
|
||||||
|
if (!passwordDirectory.exists() && !passwordDirectory.mkdir()) {
|
||||||
|
snackbar(message = "Failed to create directory ${editRelativePath.trim('/')}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
"${passwordDirectory.path}/$editName.gpg"
|
||||||
|
}
|
||||||
|
else -> "$fullPath/$editName.gpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
api?.executeApiAsync(data, inputStream, outputStream) { result ->
|
||||||
|
when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||||
|
OpenPgpApi.RESULT_CODE_SUCCESS -> {
|
||||||
|
try {
|
||||||
|
val file = File(path)
|
||||||
|
// If we're not editing, this file should not already exist!
|
||||||
|
if (!editing && file.exists()) {
|
||||||
|
snackbar(message = getString(R.string.password_creation_duplicate_error))
|
||||||
|
return@executeApiAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isInsideRepository(file)) {
|
||||||
|
snackbar(message = getString(R.string.message_error_destination_outside_repo))
|
||||||
|
return@executeApiAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
file.outputStream().use {
|
||||||
|
it.write(outputStream.toByteArray())
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
e(e) { "Failed to write password file" }
|
||||||
setResult(RESULT_CANCELED)
|
setResult(RESULT_CANCELED)
|
||||||
MaterialAlertDialogBuilder(this@PasswordCreationActivity)
|
MaterialAlertDialogBuilder(this@PasswordCreationActivity)
|
||||||
.setTitle(R.string.password_creation_file_fail_title)
|
.setTitle(getString(R.string.password_creation_file_fail_title))
|
||||||
.setMessage(getString(R.string.password_creation_file_delete_fail_message, oldFileName))
|
.setMessage(getString(R.string.password_creation_file_write_fail_message))
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
|
return@executeApiAsync
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = PasswordEntry(content)
|
||||||
|
returnIntent.putExtra(RETURN_EXTRA_PASSWORD, entry.password)
|
||||||
|
val username = PasswordEntry(content).username
|
||||||
|
?: directoryStructure.getUsernameFor(file)
|
||||||
|
returnIntent.putExtra(RETURN_EXTRA_USERNAME, username)
|
||||||
|
}
|
||||||
|
|
||||||
|
val repo = PasswordRepository.getRepository(null)
|
||||||
|
if (repo != null) {
|
||||||
|
val status = Git(repo).status().call()
|
||||||
|
if (status.modified.isNotEmpty()) {
|
||||||
|
commitChange(
|
||||||
|
getString(
|
||||||
|
R.string.git_commit_edit_text,
|
||||||
|
getLongName(fullPath, repoPath, editName)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directoryInputLayout.isVisible && directoryInputLayout.isEnabled && oldFileName != null) {
|
||||||
|
val oldFile = File("$repoPath/${oldCategory?.trim('/')}/$oldFileName.gpg")
|
||||||
|
if (oldFile.path != file.path && !oldFile.delete()) {
|
||||||
|
setResult(RESULT_CANCELED)
|
||||||
|
MaterialAlertDialogBuilder(this@PasswordCreationActivity)
|
||||||
|
.setTitle(R.string.password_creation_file_fail_title)
|
||||||
|
.setMessage(getString(R.string.password_creation_file_delete_fail_message, oldFileName))
|
||||||
|
.setCancelable(false)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
} else {
|
||||||
|
setResult(RESULT_OK, returnIntent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setResult(RESULT_OK, returnIntent)
|
setResult(RESULT_OK, returnIntent)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
setResult(RESULT_OK, returnIntent)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e(e) { "An Exception occurred" }
|
e(e) { "An Exception occurred" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {
|
||||||
|
val sender = getUserInteractionRequestIntent(result)
|
||||||
|
userInteractionRequiredResult.launch(IntentSenderRequest.Builder(sender).build())
|
||||||
|
}
|
||||||
|
OpenPgpApi.RESULT_CODE_ERROR -> handleError(result)
|
||||||
}
|
}
|
||||||
OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {
|
|
||||||
val sender = getUserInteractionRequestIntent(result)
|
|
||||||
userInteractionRequiredResult.launch(IntentSenderRequest.Builder(sender).build())
|
|
||||||
}
|
|
||||||
OpenPgpApi.RESULT_CODE_ERROR -> handleError(result)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -37,9 +37,6 @@ object PreferenceKeys {
|
|||||||
const val GIT_SERVER_INFO = "git_server_info"
|
const val GIT_SERVER_INFO = "git_server_info"
|
||||||
const val HTTPS_PASSWORD = "https_password"
|
const val HTTPS_PASSWORD = "https_password"
|
||||||
const val LENGTH = "length"
|
const val LENGTH = "length"
|
||||||
const val OPENPGP_KEY_IDS_SET = "openpgp_key_ids_set"
|
|
||||||
const val OPENPGP_KEY_ID_PREF = "openpgp_key_id_pref"
|
|
||||||
const val OPENPGP_PROVIDER_LIST = "openpgp_provider_list"
|
|
||||||
const val OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES = "oreo_autofill_custom_public_suffixes"
|
const val OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES = "oreo_autofill_custom_public_suffixes"
|
||||||
const val OREO_AUTOFILL_DEFAULT_USERNAME = "oreo_autofill_default_username"
|
const val OREO_AUTOFILL_DEFAULT_USERNAME = "oreo_autofill_default_username"
|
||||||
const val OREO_AUTOFILL_DIRECTORY_STRUCTURE = "oreo_autofill_directory_structure"
|
const val OREO_AUTOFILL_DIRECTORY_STRUCTURE = "oreo_autofill_directory_structure"
|
||||||
|
@@ -45,6 +45,7 @@
|
|||||||
<string name="git_commit_remove_text">Remove %1$s from store.</string>
|
<string name="git_commit_remove_text">Remove %1$s from store.</string>
|
||||||
<string name="git_commit_move_text">Rename %1$s to %2$s.</string>
|
<string name="git_commit_move_text">Rename %1$s to %2$s.</string>
|
||||||
<string name="git_commit_move_multiple_text">Move multiple passwords to %1$s.</string>
|
<string name="git_commit_move_multiple_text">Move multiple passwords to %1$s.</string>
|
||||||
|
<string name="git_commit_gpg_id">Initialize GPG IDs in %1$s.</string>
|
||||||
|
|
||||||
<!-- PGPHandler -->
|
<!-- PGPHandler -->
|
||||||
<string name="clipboard_password_toast_text">Password copied to clipboard, you have %d seconds to paste it somewhere.</string>
|
<string name="clipboard_password_toast_text">Password copied to clipboard, you have %d seconds to paste it somewhere.</string>
|
||||||
@@ -366,7 +367,7 @@
|
|||||||
<string name="otp_import_failure">Failed to import TOTP configuration</string>
|
<string name="otp_import_failure">Failed to import TOTP configuration</string>
|
||||||
<string name="exporting_passwords">Exporting passwords…</string>
|
<string name="exporting_passwords">Exporting passwords…</string>
|
||||||
<string name="failed_to_find_key_id">Failed to locate .gpg-id, is your store set up correctly?</string>
|
<string name="failed_to_find_key_id">Failed to locate .gpg-id, is your store set up correctly?</string>
|
||||||
<string name="invalid_gpg_id">Found .gpg-id, but it did not contain a key ID, fingerprint or user ID</string>
|
<string name="invalid_gpg_id">Found .gpg-id, but it contains an invalid key ID, fingerprint or user ID</string>
|
||||||
<string name="invalid_filename_text">File name must not contain \'/\', set directory above</string>
|
<string name="invalid_filename_text">File name must not contain \'/\', set directory above</string>
|
||||||
<string name="directory_hint">Directory</string>
|
<string name="directory_hint">Directory</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Reference in New Issue
Block a user