aboutsummaryrefslogtreecommitdiffstats
path: root/app/src
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-11-02 23:28:09 +0100
committerDanilo M. <danix@danix.xyz>2026-11-02 23:28:09 +0100
commit82ab9a21b1c3ebafc82b7a8db63b76e2b6af20f9 (patch)
tree69995801c7e1c0947d693f7b661d8ceca42f2946 /app/src
parent2329d0236ea39fd119b1199ae40d2b9f5a316506 (diff)
downloadHardenedPass-82ab9a21b1c3ebafc82b7a8db63b76e2b6af20f9.tar.gz
HardenedPass-82ab9a21b1c3ebafc82b7a8db63b76e2b6af20f9.zip
refactor(app): make the PGP clipboard and .gpg-id helpers extensions
The Compose entry view lives in MainActivity, which is not a BasePGPActivity. The helpers move unchanged to FragmentActivity extensions in the same file, so the existing subclasses keep calling them as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'app/src')
-rw-r--r--app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt1
-rw-r--r--app/src/main/java/app/passwordstore/ui/crypto/BasePGPActivity.kt191
2 files changed, 96 insertions, 96 deletions
diff --git a/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt b/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt
index 70525083..6539aa14 100644
--- a/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/autofill/AutofillDecryptActivity.kt
@@ -22,6 +22,7 @@ import app.passwordstore.data.passfile.PasswordEntry
import app.passwordstore.data.repo.PasswordRepository
import app.passwordstore.ui.crypto.BasePGPActivity
import app.passwordstore.ui.crypto.PasswordDialog
+import app.passwordstore.ui.crypto.getPGPIdentifiers
import app.passwordstore.util.auth.BiometricAuthenticator
import app.passwordstore.util.auth.BiometricAuthenticator.Result as BiometricResult
import app.passwordstore.util.autofill.AutofillPreferences
diff --git a/app/src/main/java/app/passwordstore/ui/crypto/BasePGPActivity.kt b/app/src/main/java/app/passwordstore/ui/crypto/BasePGPActivity.kt
index 2b3f4dab..95dc67ad 100644
--- a/app/src/main/java/app/passwordstore/ui/crypto/BasePGPActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/crypto/BasePGPActivity.kt
@@ -16,6 +16,7 @@ import androidx.activity.result.contract.ActivityResultContracts.StartActivityFo
import androidx.annotation.CallSuper
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import app.passwordstore.R
import app.passwordstore.crypto.PGPIdentifier
@@ -26,13 +27,13 @@ import app.passwordstore.ui.pgp.PGPKeyImportActivity
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.extensions.clipboard
import app.passwordstore.util.extensions.getString
+import app.passwordstore.util.extensions.sharedPrefs
import app.passwordstore.util.extensions.snackbar
import app.passwordstore.util.extensions.unsafeLazy
import app.passwordstore.util.services.ClipboardService
import app.passwordstore.util.settings.Constants
import app.passwordstore.util.settings.PreferenceKeys
import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import java.io.File
import javax.inject.Inject
@@ -84,25 +85,6 @@ open class BasePGPActivity : AppCompatActivity() {
}
/**
- * Copies provided [text] to the clipboard. Shows a [Snackbar] which can be disabled by passing
- * [showSnackbar] as false.
- */
- fun copyTextToClipboard(
- text: String?,
- showSnackbar: Boolean = true,
- @StringRes snackbarTextRes: Int = R.string.clipboard_copied_text,
- ) {
- val clipboard = clipboard ?: return
- val clip = ClipData.newPlainText("pgp_handler_result_pm", text)
- clip.description.extras =
- PersistableBundle().apply { putBoolean("android.content.extra.IS_SENSITIVE", true) }
- clipboard.setPrimaryClip(clip)
- if (showSnackbar && Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
- snackbar(message = resources.getString(snackbarTextRes))
- }
- }
-
- /**
* Function to execute [onKeysExist] only if there are PGP keys imported in the app's key manager.
*/
fun requireKeysExist(onKeysExist: () -> Unit) {
@@ -125,82 +107,6 @@ open class BasePGPActivity : AppCompatActivity() {
}
}
- /**
- * Copies a provided [password] string to the clipboard. This wraps [copyTextToClipboard] to hide
- * the default [Snackbar] and starts off an instance of [ClipboardService] to provide a way of
- * clearing the clipboard.
- */
- fun copyPasswordToClipboard(password: String?) {
- copyTextToClipboard(password)
- val clearAfter =
- settings.getString(PreferenceKeys.GENERAL_SHOW_TIME)?.toIntOrNull()
- ?: Constants.DEFAULT_DECRYPTION_TIMEOUT
-
- if (clearAfter != 0) {
- val service =
- Intent(this, ClipboardService::class.java).apply {
- action = ClipboardService.ACTION_START
- putExtra(ClipboardService.EXTRA_NOTIFICATION_TIME, clearAfter)
- }
- startForegroundService(service)
- }
- }
-
- /**
- * Get a list of available [PGPIdentifier]s for the current password repository. This method
- * throws when no identifiers were able to be parsed. If this method returns null, it means that
- * an invalid identifier was encountered and further execution must stop to let the user correct
- * the problem.
- */
- fun getPGPIdentifiers(subDir: String): List<PGPIdentifier>? {
- val repoRoot = PasswordRepository.getRepositoryDirectory()
- val gpgIdentifierFile =
- File(repoRoot, subDir).findTillRoot(".gpg-id", repoRoot)
- ?: File(repoRoot, ".gpg-id").apply { createNewFile() }
- val gpgIdentifiers =
- gpgIdentifierFile
- .readLines()
- .filter { it.isNotBlank() }
- .map { line ->
- PGPIdentifier.fromString(line)
- ?: run {
- // The line being empty means this is most likely an empty `.gpg-id`
- // file we created. Skip the validation so we can make the user add a
- // real ID.
- if (line.isEmpty()) return@run
- // Apparently `gpg-id` being the first line is also acceptable?
- if (line == "gpg-id") return@run
- if (line.removePrefix("0x").matches("[a-fA-F0-9]{8}".toRegex())) {
- snackbar(message = resources.getString(R.string.short_gpg_id))
- } else {
- snackbar(message = resources.getString(R.string.invalid_gpg_id))
- }
- return null
- }
- }
- .filterIsInstance<PGPIdentifier>()
- if (gpgIdentifiers.isEmpty()) {
- error("Failed to parse identifiers from .gpg-id: ${gpgIdentifierFile.readText()}")
- }
- return gpgIdentifiers
- }
-
- @Suppress("ReturnCount")
- private fun File.findTillRoot(fileName: String, rootPath: File): File? {
- val gpgFile = File(this, fileName)
- if (gpgFile.exists()) return gpgFile
-
- if (this.absolutePath == rootPath.absolutePath) {
- return null
- }
- val parent = parentFile
- return if (parent != null && parent.exists()) {
- parent.findTillRoot(fileName, rootPath)
- } else {
- null
- }
- }
-
companion object {
const val EXTRA_FILE_PATH = "FILE_PATH"
@@ -236,3 +142,96 @@ open class BasePGPActivity : AppCompatActivity() {
}
}
}
+
+/**
+ * Copies provided [text] to the clipboard, flagged sensitive. Below Android 11 it also shows a
+ * snackbar, which can be disabled by passing [showSnackbar] as false.
+ */
+fun FragmentActivity.copyTextToClipboard(
+ text: String?,
+ showSnackbar: Boolean = true,
+ @StringRes snackbarTextRes: Int = R.string.clipboard_copied_text,
+) {
+ val clipboard = clipboard ?: return
+ val clip = ClipData.newPlainText("pgp_handler_result_pm", text)
+ clip.description.extras =
+ PersistableBundle().apply { putBoolean("android.content.extra.IS_SENSITIVE", true) }
+ clipboard.setPrimaryClip(clip)
+ if (showSnackbar && Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ snackbar(message = resources.getString(snackbarTextRes))
+ }
+}
+
+/**
+ * Copies a provided [password] string to the clipboard. This wraps [copyTextToClipboard] and starts
+ * off an instance of [ClipboardService] to provide a way of clearing the clipboard.
+ */
+fun FragmentActivity.copyPasswordToClipboard(password: String?) {
+ copyTextToClipboard(password)
+ val clearAfter =
+ sharedPrefs.getString(PreferenceKeys.GENERAL_SHOW_TIME)?.toIntOrNull()
+ ?: Constants.DEFAULT_DECRYPTION_TIMEOUT
+
+ if (clearAfter != 0) {
+ val service =
+ Intent(this, ClipboardService::class.java).apply {
+ action = ClipboardService.ACTION_START
+ putExtra(ClipboardService.EXTRA_NOTIFICATION_TIME, clearAfter)
+ }
+ startForegroundService(service)
+ }
+}
+
+/**
+ * Get a list of available [PGPIdentifier]s for the current password repository. This method throws
+ * when no identifiers were able to be parsed. If this method returns null, it means that an invalid
+ * identifier was encountered and further execution must stop to let the user correct the problem.
+ */
+fun FragmentActivity.getPGPIdentifiers(subDir: String): List<PGPIdentifier>? {
+ val repoRoot = PasswordRepository.getRepositoryDirectory()
+ val gpgIdentifierFile =
+ File(repoRoot, subDir).findTillRoot(".gpg-id", repoRoot)
+ ?: File(repoRoot, ".gpg-id").apply { createNewFile() }
+ val gpgIdentifiers =
+ gpgIdentifierFile
+ .readLines()
+ .filter { it.isNotBlank() }
+ .map { line ->
+ PGPIdentifier.fromString(line)
+ ?: run {
+ // The line being empty means this is most likely an empty `.gpg-id`
+ // file we created. Skip the validation so we can make the user add a
+ // real ID.
+ if (line.isEmpty()) return@run
+ // Apparently `gpg-id` being the first line is also acceptable?
+ if (line == "gpg-id") return@run
+ if (line.removePrefix("0x").matches("[a-fA-F0-9]{8}".toRegex())) {
+ snackbar(message = resources.getString(R.string.short_gpg_id))
+ } else {
+ snackbar(message = resources.getString(R.string.invalid_gpg_id))
+ }
+ return null
+ }
+ }
+ .filterIsInstance<PGPIdentifier>()
+ if (gpgIdentifiers.isEmpty()) {
+ error("Failed to parse identifiers from .gpg-id: ${gpgIdentifierFile.readText()}")
+ }
+ return gpgIdentifiers
+}
+
+@Suppress("ReturnCount")
+private fun File.findTillRoot(fileName: String, rootPath: File): File? {
+ val gpgFile = File(this, fileName)
+ if (gpgFile.exists()) return gpgFile
+
+ if (this.absolutePath == rootPath.absolutePath) {
+ return null
+ }
+ val parent = parentFile
+ return if (parent != null && parent.exists()) {
+ parent.findTillRoot(fileName, rootPath)
+ } else {
+ null
+ }
+}