aboutsummaryrefslogtreecommitdiff
path: root/app/src/main
diff options
context:
space:
mode:
authorFabian Henneke <FabianHenneke@users.noreply.github.com>2020-03-25 18:13:04 +0100
committerGitHub <noreply@github.com>2020-03-25 18:13:04 +0100
commitfde16c60f4ce5d57a0c7d5a0186dcd532a23f0f0 (patch)
tree8a72bcc077590707c47017548cc8adc3eae6e0d1 /app/src/main
parent973e023dda3caf17fe024b9ba7f1872c5527e654 (diff)
Make preferred directory structure for Autofill configurable (#660)
Some users keep their password files in a directory structure such as: /example.org/john@doe.org.gpg while others prefer the style: /example.org/john@doe.org/password.gpg This commit adds a setting that allows to switch between the two. All Autofill operations, such as search, match, generate and save, respect this setting. Note: The first style seems to be the most widely used and is therefore kept as the default. The second style is mentioned on the official Pass website at: https://www.passwordstore.org/#organization
Diffstat (limited to 'app/src/main')
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/UserPreference.kt37
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillHelper.kt13
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillPreferences.kt59
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillDecryptActivity.kt6
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt74
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt23
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt46
-rw-r--r--app/src/main/res/layout/encrypt_layout.xml4
-rw-r--r--app/src/main/res/values/arrays.xml8
-rw-r--r--app/src/main/res/values/strings.xml2
-rw-r--r--app/src/main/res/xml/preference.xml7
11 files changed, 212 insertions, 67 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
index 895338e9..06ee24e5 100644
--- a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
@@ -62,8 +62,9 @@ class UserPreference : AppCompatActivity() {
private lateinit var prefsFragment: PrefsFragment
class PrefsFragment : PreferenceFragmentCompat() {
- private var autofillDependencies = listOf<Preference?>()
private var autoFillEnablePreference: SwitchPreferenceCompat? = null
+ private lateinit var autofillDependencies: List<Preference>
+ private lateinit var oreoAutofillDependencies: List<Preference>
private lateinit var callingActivity: UserPreference
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@@ -96,16 +97,21 @@ class UserPreference : AppCompatActivity() {
// Autofill preferences
autoFillEnablePreference = findPreference("autofill_enable")
- val autoFillAppsPreference = findPreference<Preference>("autofill_apps")
- val autoFillDefaultPreference = findPreference<CheckBoxPreference>("autofill_default")
- val autoFillAlwaysShowDialogPreference = findPreference<CheckBoxPreference>("autofill_always")
- val autoFillShowFullNamePreference = findPreference<CheckBoxPreference>("autofill_full_path")
+ val autoFillAppsPreference = findPreference<Preference>("autofill_apps")!!
+ val autoFillDefaultPreference = findPreference<CheckBoxPreference>("autofill_default")!!
+ val autoFillAlwaysShowDialogPreference =
+ findPreference<CheckBoxPreference>("autofill_always")!!
+ val autoFillShowFullNamePreference =
+ findPreference<CheckBoxPreference>("autofill_full_path")!!
autofillDependencies = listOf(
autoFillAppsPreference,
autoFillDefaultPreference,
autoFillAlwaysShowDialogPreference,
autoFillShowFullNamePreference
)
+ val oreoAutofillDirectoryStructurePreference =
+ findPreference<ListPreference>("oreo_autofill_directory_structure")!!
+ oreoAutofillDependencies = listOf(oreoAutofillDirectoryStructurePreference)
// Misc preferences
val appVersionPreference = findPreference<Preference>("app_version")
@@ -236,7 +242,7 @@ class UserPreference : AppCompatActivity() {
selectExternalGitRepositoryPreference?.onPreferenceChangeListener = resetRepo
externalGitRepositoryPreference?.onPreferenceChangeListener = resetRepo
- autoFillAppsPreference?.onPreferenceClickListener = ClickListener {
+ autoFillAppsPreference.onPreferenceClickListener = ClickListener {
val intent = Intent(callingActivity, AutofillPreferenceActivity::class.java)
startActivity(intent)
true
@@ -356,10 +362,14 @@ class UserPreference : AppCompatActivity() {
private fun updateAutofillSettings() {
val isAccessibilityServiceEnabled = callingActivity.isAccessibilityServiceEnabled
+ val isAutofillServiceEnabled = callingActivity.isAutofillServiceEnabled
autoFillEnablePreference?.isChecked =
- isAccessibilityServiceEnabled || callingActivity.isAutofillServiceEnabled
+ isAccessibilityServiceEnabled || isAutofillServiceEnabled
autofillDependencies.forEach {
- it?.isVisible = isAccessibilityServiceEnabled
+ it.isVisible = isAccessibilityServiceEnabled
+ }
+ oreoAutofillDependencies.forEach {
+ it.isVisible = isAutofillServiceEnabled
}
}
@@ -409,16 +419,7 @@ class UserPreference : AppCompatActivity() {
startActivity(intent)
}
setNegativeButton(R.string.dialog_cancel, null)
- setOnDismissListener {
- val isEnabled =
- if (enableOreoAutofill && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- callingActivity.isAutofillServiceEnabled
- } else {
- callingActivity.isAccessibilityServiceEnabled
- }
- autoFillEnablePreference?.isChecked = isEnabled
- autofillDependencies.forEach { it?.isVisible = isEnabled }
- }
+ setOnDismissListener { updateAutofillSettings() }
show()
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillHelper.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillHelper.kt
index 4b7b47e2..e3d48302 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillHelper.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillHelper.kt
@@ -73,9 +73,14 @@ val AssistStructure.ViewNode.webOrigin: String?
data class Credentials(val username: String?, val password: String) {
companion object {
- fun fromStoreEntry(file: File, entry: PasswordEntry): Credentials {
- return if (entry.hasUsername()) Credentials(entry.username, entry.password)
- else Credentials(file.nameWithoutExtension, entry.password)
+ fun fromStoreEntry(
+ file: File,
+ entry: PasswordEntry,
+ directoryStructure: DirectoryStructure
+ ): Credentials {
+ // Always give priority to a username stored in the encrypted extras
+ val username = entry.username ?: directoryStructure.getUsernameFor(file)
+ return Credentials(username, entry.password)
}
}
}
@@ -95,7 +100,7 @@ private fun makeRemoteView(
fun makeFillMatchRemoteView(context: Context, file: File, formOrigin: FormOrigin): RemoteViews {
val title = formOrigin.getPrettyIdentifier(context, untrusted = false)
- val summary = file.nameWithoutExtension
+ val summary = AutofillPreferences.directoryStructure(context).getUsernameFor(file)
val iconRes = R.drawable.ic_person_black_24dp
return makeRemoteView(context, title, summary, iconRes)
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillPreferences.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillPreferences.kt
new file mode 100644
index 00000000..c1ce51e9
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillPreferences.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+package com.zeapo.pwdstore.autofill.oreo
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.preference.PreferenceManager
+import java.io.File
+import java.nio.file.Paths
+
+private val Context.defaultSharedPreferences: SharedPreferences
+ get() = PreferenceManager.getDefaultSharedPreferences(this)
+
+enum class DirectoryStructure(val value: String) {
+ FileBased("file"),
+ DirectoryBased("directory");
+
+ fun getUsernameFor(file: File) = when (this) {
+ FileBased -> file.nameWithoutExtension
+ DirectoryBased -> file.parentFile?.name ?: file.nameWithoutExtension
+ }
+
+ fun getIdentifierFor(file: File) = when (this) {
+ FileBased -> file.parentFile?.name
+ DirectoryBased -> file.parentFile?.parentFile?.name
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ fun getSaveFolderName(sanitizedIdentifier: String, username: String?) = when (this) {
+ FileBased -> sanitizedIdentifier
+ DirectoryBased -> Paths.get(sanitizedIdentifier, username ?: "username").toString()
+ }
+
+ fun getSaveFileName(username: String?) = when (this) {
+ FileBased -> username
+ DirectoryBased -> "password"
+ }
+
+ companion object {
+ const val PREFERENCE = "oreo_autofill_directory_structure"
+ private val DEFAULT = FileBased
+
+ private val reverseMap = values().associateBy { it.value }
+ fun fromValue(value: String?) = if (value != null) reverseMap[value] ?: DEFAULT else DEFAULT
+ }
+}
+
+object AutofillPreferences {
+
+ fun directoryStructure(context: Context): DirectoryStructure {
+ val value =
+ context.defaultSharedPreferences.getString(DirectoryStructure.PREFERENCE, null)
+ return DirectoryStructure.fromValue(value)
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillDecryptActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillDecryptActivity.kt
index 2f3824f9..2c44ee42 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillDecryptActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillDecryptActivity.kt
@@ -18,7 +18,9 @@ import com.github.ajalt.timberkt.d
import com.github.ajalt.timberkt.e
import com.zeapo.pwdstore.PasswordEntry
import com.zeapo.pwdstore.autofill.oreo.AutofillAction
+import com.zeapo.pwdstore.autofill.oreo.AutofillPreferences
import com.zeapo.pwdstore.autofill.oreo.Credentials
+import com.zeapo.pwdstore.autofill.oreo.DirectoryStructure
import com.zeapo.pwdstore.autofill.oreo.FillableForm
import java.io.ByteArrayOutputStream
import java.io.File
@@ -76,6 +78,7 @@ class AutofillDecryptActivity : Activity(), CoroutineScope {
}
private var continueAfterUserInteraction: Continuation<Intent>? = null
+ private lateinit var directoryStructure: DirectoryStructure
override val coroutineContext
get() = Dispatchers.IO + SupervisorJob()
@@ -94,6 +97,7 @@ class AutofillDecryptActivity : Activity(), CoroutineScope {
}
val isSearchAction = intent?.getBooleanExtra(EXTRA_SEARCH_ACTION, true)!!
val action = if (isSearchAction) AutofillAction.Search else AutofillAction.Match
+ directoryStructure = AutofillPreferences.directoryStructure(this)
d { action.toString() }
launch {
val credentials = decryptUsernameAndPassword(File(filePath))
@@ -176,7 +180,7 @@ class AutofillDecryptActivity : Activity(), CoroutineScope {
val entry = withContext(Dispatchers.IO) {
PasswordEntry(decryptedOutput)
}
- Credentials.fromStoreEntry(file, entry)
+ Credentials.fromStoreEntry(file, entry, directoryStructure)
} catch (e: UnsupportedEncodingException) {
e(e) { "Failed to parse password entry" }
null
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt
index 8c77fff8..e4517197 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt
@@ -23,10 +23,13 @@ import com.afollestad.recyclical.withItem
import com.github.ajalt.timberkt.e
import com.zeapo.pwdstore.R
import com.zeapo.pwdstore.autofill.oreo.AutofillMatcher
+import com.zeapo.pwdstore.autofill.oreo.AutofillPreferences
+import com.zeapo.pwdstore.autofill.oreo.DirectoryStructure
import com.zeapo.pwdstore.autofill.oreo.FormOrigin
import com.zeapo.pwdstore.utils.PasswordItem
import com.zeapo.pwdstore.utils.PasswordRepository
import java.io.File
+import java.nio.file.Paths
import java.util.Locale
import kotlinx.android.synthetic.main.activity_oreo_autofill_filter.*
@@ -69,6 +72,8 @@ class AutofillFilterView : AppCompatActivity() {
get() = PasswordRepository.PasswordSortOrder.getSortOrder(preferences)
private lateinit var formOrigin: FormOrigin
+ private lateinit var repositoryRoot: File
+ private lateinit var directoryStructure: DirectoryStructure
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -98,6 +103,8 @@ class AutofillFilterView : AppCompatActivity() {
return
}
}
+ repositoryRoot = PasswordRepository.getRepositoryDirectory(this)
+ directoryStructure = AutofillPreferences.directoryStructure(this)
supportActionBar?.hide()
bindUI()
@@ -110,9 +117,19 @@ class AutofillFilterView : AppCompatActivity() {
withDataSource(dataSource)
withItem<PasswordItem, PasswordViewHolder>(R.layout.oreo_autofill_filter_row) {
onBind(::PasswordViewHolder) { _, item ->
- title.text = item.fullPathToParent
- // drop the .gpg extension
- subtitle.text = item.name.dropLast(4)
+ when (directoryStructure) {
+ DirectoryStructure.FileBased -> {
+ title.text = item.file.relativeTo(item.rootDir).parent
+ subtitle.text = item.file.nameWithoutExtension
+ }
+ DirectoryStructure.DirectoryBased -> {
+ title.text =
+ item.file.relativeTo(item.rootDir).parentFile?.parent ?: "/INVALID"
+ subtitle.text =
+ Paths.get(item.file.parentFile.name, item.file.nameWithoutExtension)
+ .toString()
+ }
+ }
}
onClick { decryptAndFill(item) }
}
@@ -156,40 +173,41 @@ class AutofillFilterView : AppCompatActivity() {
}
}
+ private fun File.matches(filter: String, strict: Boolean): Boolean {
+ return if (strict) {
+ val toMatch = directoryStructure.getIdentifierFor(this) ?: return false
+ // In strict mode, we match
+ // * the search term exactly,
+ // * subdomains of the search term,
+ // * or the search term plus an arbitrary protocol.
+ toMatch == filter || toMatch.endsWith(".$filter") || toMatch.endsWith("://$filter")
+ } else {
+ val toMatch =
+ "${relativeTo(repositoryRoot).path}/$nameWithoutExtension".toLowerCase(Locale.getDefault())
+ toMatch.contains(filter.toLowerCase(Locale.getDefault()))
+ }
+ }
+
private fun recursiveFilter(filter: String, dir: File? = null, strict: Boolean = true) {
- val root = PasswordRepository.getRepositoryDirectory(this)
// on the root the pathStack is empty
val passwordItems = if (dir == null) {
- PasswordRepository.getPasswords(
- PasswordRepository.getRepositoryDirectory(this),
- sortOrder
- )
+ PasswordRepository.getPasswords(repositoryRoot, sortOrder)
} else {
- PasswordRepository.getPasswords(
- dir,
- PasswordRepository.getRepositoryDirectory(this),
- sortOrder
- )
+ PasswordRepository.getPasswords(dir, repositoryRoot, sortOrder)
}
for (item in passwordItems) {
if (item.type == PasswordItem.TYPE_CATEGORY) {
recursiveFilter(filter, item.file, strict = strict)
- }
-
- // TODO: Implement fuzzy search if strict == false?
- val matches = if (strict) item.file.parentFile.name.let {
- it == filter || it.endsWith(".$filter") || it.endsWith("://$filter")
- }
- else "${item.file.relativeTo(root).path}/${item.file.nameWithoutExtension}".toLowerCase(
- Locale.getDefault()
- ).contains(filter.toLowerCase(Locale.getDefault()))
-
- val inAdapter = dataSource.contains(item)
- if (item.type == PasswordItem.TYPE_PASSWORD && matches && !inAdapter) {
- dataSource.add(item)
- } else if (!matches && inAdapter) {
- dataSource.remove(item)
+ } else {
+ // TODO: Implement fuzzy search if strict == false?
+ val matches = item.file.matches(filter, strict = strict)
+ val inAdapter = dataSource.contains(item)
+ if (matches && !inAdapter) {
+ dataSource.add(item)
+ } else if (!matches && inAdapter) {
+ dataSource.remove(item)
+ }
}
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt
index e5368b73..e5def0fb 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt
@@ -18,6 +18,7 @@ import com.github.ajalt.timberkt.e
import com.zeapo.pwdstore.PasswordStore
import com.zeapo.pwdstore.autofill.oreo.AutofillAction
import com.zeapo.pwdstore.autofill.oreo.AutofillMatcher
+import com.zeapo.pwdstore.autofill.oreo.AutofillPreferences
import com.zeapo.pwdstore.autofill.oreo.Credentials
import com.zeapo.pwdstore.autofill.oreo.FillableForm
import com.zeapo.pwdstore.autofill.oreo.FormOrigin
@@ -32,7 +33,7 @@ class AutofillSaveActivity : Activity() {
private const val EXTRA_FOLDER_NAME =
"com.zeapo.pwdstore.autofill.oreo.ui.EXTRA_FOLDER_NAME"
private const val EXTRA_PASSWORD = "com.zeapo.pwdstore.autofill.oreo.ui.EXTRA_PASSWORD"
- private const val EXTRA_USERNAME = "com.zeapo.pwdstore.autofill.oreo.ui.EXTRA_USERNAME"
+ private const val EXTRA_NAME = "com.zeapo.pwdstore.autofill.oreo.ui.EXTRA_NAME"
private const val EXTRA_SHOULD_MATCH_APP =
"com.zeapo.pwdstore.autofill.oreo.ui.EXTRA_SHOULD_MATCH_APP"
private const val EXTRA_SHOULD_MATCH_WEB =
@@ -48,15 +49,23 @@ class AutofillSaveActivity : Activity() {
formOrigin: FormOrigin
): IntentSender {
val identifier = formOrigin.getPrettyIdentifier(context, untrusted = false)
- val sanitizedIdentifier = identifier.replace("""[\\\/]""", "")
- val folderName =
- sanitizedIdentifier.takeUnless { it.isBlank() } ?: formOrigin.identifier
+ // Prevent directory traversals
+ val sanitizedIdentifier = identifier.replace('\\', '_')
+ .replace('/', '_')
+ .trimStart('.')
+ .takeUnless { it.isBlank() } ?: formOrigin.identifier
+ val directoryStructure = AutofillPreferences.directoryStructure(context)
+ val folderName = directoryStructure.getSaveFolderName(
+ sanitizedIdentifier = sanitizedIdentifier,
+ username = credentials?.username
+ )
+ val fileName = directoryStructure.getSaveFileName(username = credentials?.username)
val intent = Intent(context, AutofillSaveActivity::class.java).apply {
putExtras(
bundleOf(
EXTRA_FOLDER_NAME to folderName,
+ EXTRA_NAME to fileName,
EXTRA_PASSWORD to credentials?.password,
- EXTRA_USERNAME to credentials?.username,
EXTRA_SHOULD_MATCH_APP to formOrigin.identifier.takeIf { formOrigin is FormOrigin.App },
EXTRA_SHOULD_MATCH_WEB to formOrigin.identifier.takeIf { formOrigin is FormOrigin.Web },
EXTRA_GENERATE_PASSWORD to (credentials == null)
@@ -87,15 +96,13 @@ class AutofillSaveActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val repo = PasswordRepository.getRepositoryDirectory(applicationContext)
- val username = intent.getStringExtra(EXTRA_USERNAME)
-
val saveIntent = Intent(this, PgpActivity::class.java).apply {
putExtras(
bundleOf(
"REPO_PATH" to repo.absolutePath,
"FILE_PATH" to repo.resolve(intent.getStringExtra(EXTRA_FOLDER_NAME)).absolutePath,
"OPERATION" to "ENCRYPT",
- "SUGGESTED_NAME" to username,
+ "SUGGESTED_NAME" to intent.getStringExtra(EXTRA_NAME),
"SUGGESTED_PASS" to intent.getStringExtra(EXTRA_PASSWORD),
"GENERATE_PASSWORD" to intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false)
)
diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt
index 90efd583..2fb85118 100644
--- a/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt
@@ -41,6 +41,8 @@ import com.zeapo.pwdstore.ClipboardService
import com.zeapo.pwdstore.PasswordEntry
import com.zeapo.pwdstore.R
import com.zeapo.pwdstore.UserPreference
+import com.zeapo.pwdstore.autofill.oreo.AutofillPreferences
+import com.zeapo.pwdstore.autofill.oreo.DirectoryStructure
import com.zeapo.pwdstore.ui.dialogs.PasswordGeneratorDialogFragment
import com.zeapo.pwdstore.ui.dialogs.XkPasswordGeneratorDialogFragment
import com.zeapo.pwdstore.utils.Otp
@@ -157,9 +159,23 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
}
title = getString(R.string.new_password_title)
- crypto_password_category.text = getRelativePath(fullPath, repoPath)
- suggestedName?.let {
- crypto_password_file_edit.setText(it)
+ crypto_password_category.apply {
+ setText(getRelativePath(fullPath, repoPath))
+ // If the activity has been provided with suggested info, we allow the user to
+ // edit the path, otherwise we style the EditText like a TextView.
+ if (suggestedName != null) {
+ isEnabled = true
+ } else {
+ setBackgroundColor(getColor(android.R.color.transparent))
+ }
+ }
+ suggestedName?.let { crypto_password_file_edit.setText(it) }
+ // Allow the user to quickly switch between storing the username as the filename or
+ // in the encrypted extras. This only makes sense if the directory structure is
+ // FileBased.
+ if (suggestedName != null &&
+ AutofillPreferences.directoryStructure(this) == DirectoryStructure.FileBased
+ ) {
encrypt_username.apply {
visibility = View.VISIBLE
setOnClickListener {
@@ -549,7 +565,20 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
val iStream = ByteArrayInputStream(content.toByteArray(Charset.forName("UTF-8")))
val oStream = ByteArrayOutputStream()
- val path = if (intent.getBooleanExtra("fromDecrypt", false)) fullPath else "$fullPath/$editName.gpg"
+ val path = when {
+ intent.getBooleanExtra("fromDecrypt", false) -> fullPath
+ // If we allowed the user to edit the relative path, we have to consider it here instead
+ // of fullPath.
+ crypto_password_category.isEnabled -> {
+ val editRelativePath = crypto_password_category.text!!.toString().trim()
+ if (editRelativePath.isEmpty()) {
+ showSnackbar(resources.getString(R.string.path_toast_text))
+ return
+ }
+ "$repoPath/${editRelativePath.trim('/')}/$editName.gpg"
+ }
+ else -> "$fullPath/$editName.gpg"
+ }
lifecycleScope.launch(IO) {
api?.executeApiAsync(data, iStream, oStream, object : OpenPgpApi.IOpenPgpCallback {
@@ -575,9 +604,14 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
}
if (shouldGeneratePassword) {
+ val directoryStructure =
+ AutofillPreferences.directoryStructure(applicationContext)
val entry = PasswordEntry(content)
returnIntent.putExtra("PASSWORD", entry.password)
- returnIntent.putExtra("USERNAME", entry.username ?: file.nameWithoutExtension)
+ returnIntent.putExtra(
+ "USERNAME",
+ directoryStructure.getUsernameFor(file)
+ )
}
setResult(RESULT_OK, returnIntent)
@@ -615,7 +649,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
crypto_extra_edit.setText(passwordEntry?.extraContent)
crypto_extra_edit.typeface = monoTypeface
- crypto_password_category.text = relativeParentPath
+ crypto_password_category.setText(relativeParentPath)
crypto_password_file_edit.setText(name)
crypto_password_file_edit.isEnabled = false
diff --git a/app/src/main/res/layout/encrypt_layout.xml b/app/src/main/res/layout/encrypt_layout.xml
index 40ae757f..e4d9464a 100644
--- a/app/src/main/res/layout/encrypt_layout.xml
+++ b/app/src/main/res/layout/encrypt_layout.xml
@@ -9,13 +9,13 @@
android:padding="@dimen/activity_horizontal_margin"
tools:context="com.zeapo.pwdstore.crypto.PgpActivity">
- <androidx.appcompat.widget.AppCompatTextView
+ <androidx.appcompat.widget.AppCompatEditText
android:id="@+id/crypto_password_category"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:textColor="?android:attr/textColor"
- android:textIsSelectable="false"
+ android:enabled="false"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 522fe6cc..37b55432 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -51,5 +51,13 @@
<item>classic</item>
<item>xkpasswd</item>
</string-array>
+ <string-array name="oreo_autofill_directory_structure_entries">
+ <item>/example.org/john@doe.org(.gpg)</item>
+ <item>/example.org/john@doe.org/password(.gpg)</item>
+ </string-array>
+ <string-array name="oreo_autofill_directory_structure_values">
+ <item>file</item>
+ <item>directory</item>
+ </string-array>
</resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5df51af5..ba491c87 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -42,6 +42,7 @@
<string name="clipboard_username_toast_text">Username copied to clipboard</string>
<string name="clipboard_otp_toast_text">OTP code copied to clipboard</string>
<string name="file_toast_text">Please provide a file name</string>
+ <string name="path_toast_text">Please provide a file path</string>
<string name="empty_toast_text">You cannot use an empty password or empty extra content</string>
<!-- Git Async Task -->
@@ -268,6 +269,7 @@
<string name="oreo_autofill_fill_support">Fill credentials</string>
<string name="oreo_autofill_flaky_fill_support">Fill credentials (may require restarting the browser from time to time)</string>
<string name="oreo_autofill_no_support">No support</string>
+ <string name="oreo_autofill_preference_directory_structure">Password file organization</string>
<!-- Autofill -->
<string name="autofill_description">Autofills password fields in apps. Only works for Android versions 4.3 and up. Does not rely on the clipboard for Android versions 5.0 and up.</string>
diff --git a/app/src/main/res/xml/preference.xml b/app/src/main/res/xml/preference.xml
index 08f23787..b1c5e4d0 100644
--- a/app/src/main/res/xml/preference.xml
+++ b/app/src/main/res/xml/preference.xml
@@ -6,6 +6,13 @@
app:defaultValue="true"
app:key="autofill_enable"
app:title="@string/pref_autofill_enable_title"/>
+ <androidx.preference.ListPreference
+ app:defaultValue="file"
+ app:entries="@array/oreo_autofill_directory_structure_entries"
+ app:entryValues="@array/oreo_autofill_directory_structure_values"
+ app:key="oreo_autofill_directory_structure"
+ app:title="@string/oreo_autofill_preference_directory_structure"
+ app:useSimpleSummaryProvider="true" />
<androidx.preference.Preference
app:key="autofill_apps"
app:title="@string/pref_autofill_apps_title"/>