aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-11-03 08:54:55 +0100
committerDanilo M. <danix@danix.xyz>2026-11-03 08:54:55 +0100
commit32511cf9a3970727df982ac2509696e534a027e7 (patch)
treed3035b03ce5a08a856916e09ed3038006b97c00b /app/src/main/java
parent5e35c7a69e2a25aa5b793b5135cdf051d1cac101 (diff)
downloadHardenedPass-32511cf9a3970727df982ac2509696e534a027e7.tar.gz
HardenedPass-32511cf9a3970727df982ac2509696e534a027e7.zip
feat(app): host the Compose editor in the autofill save flow
AutofillSaveActivity shows the editor itself, prefilled from the form and FLAG_SECURE, instead of starting the legacy editor for a result. The autofill match and the generated-password fill work as before: the username and password are read back from the saved content, as the old editor did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/app/passwordstore/ui/autofill/AutofillSaveActivity.kt124
1 files changed, 71 insertions, 53 deletions
diff --git a/app/src/main/java/app/passwordstore/ui/autofill/AutofillSaveActivity.kt b/app/src/main/java/app/passwordstore/ui/autofill/AutofillSaveActivity.kt
index 9476b344..eb533411 100644
--- a/app/src/main/java/app/passwordstore/ui/autofill/AutofillSaveActivity.kt
+++ b/app/src/main/java/app/passwordstore/ui/autofill/AutofillSaveActivity.kt
@@ -9,12 +9,17 @@ import android.content.Context
import android.content.Intent
import android.content.IntentSender
import android.os.Bundle
+import android.view.WindowManager
import android.view.autofill.AutofillManager
-import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
+import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
-import app.passwordstore.data.repo.PasswordRepository
-import app.passwordstore.ui.crypto.PasswordCreationActivity
+import app.passwordstore.data.passfile.PasswordEntry
+import app.passwordstore.ui.compose.theme.APSTheme
+import app.passwordstore.ui.editor.EditorDestination
+import app.passwordstore.ui.editor.EditorForm
+import app.passwordstore.ui.editor.SaveResult
+import app.passwordstore.ui.editor.passContent
import app.passwordstore.util.autofill.AutofillMatcher
import app.passwordstore.util.autofill.AutofillPreferences
import app.passwordstore.util.autofill.AutofillResponseBuilder
@@ -23,10 +28,15 @@ import com.github.androidpasswordstore.autofillparser.AutofillAction
import com.github.androidpasswordstore.autofillparser.Credentials
import com.github.androidpasswordstore.autofillparser.FormOrigin
import dagger.hilt.android.AndroidEntryPoint
-import java.io.File
+import javax.inject.Inject
import logcat.LogPriority.ERROR
import logcat.logcat
+/**
+ * Saves credentials the autofill service caught (or generates a password for a form) in the Compose
+ * editor, prefilled from the form's origin, then records the autofill match and, for a generated
+ * password, fills it in.
+ */
@AndroidEntryPoint
class AutofillSaveActivity : AppCompatActivity() {
@@ -102,60 +112,68 @@ class AutofillSaveActivity : AppCompatActivity() {
}
}
+ private val generate by unsafeLazy { intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false) }
+
+ @Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val repo = PasswordRepository.getRepositoryDirectory()
- val saveIntent =
- Intent(this, PasswordCreationActivity::class.java).apply {
- putExtras(
- bundleOf(
- "REPO_PATH" to repo.absolutePath,
- "FILE_PATH" to repo.resolve(intent.getStringExtra(EXTRA_FOLDER_NAME)!!).absolutePath,
- PasswordCreationActivity.EXTRA_FILE_NAME to intent.getStringExtra(EXTRA_NAME),
- PasswordCreationActivity.EXTRA_PASSWORD to intent.getStringExtra(EXTRA_PASSWORD),
- PasswordCreationActivity.EXTRA_GENERATE_PASSWORD to
- intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false),
- )
+ // The whole window is the editor: keep it out of screenshots and recents.
+ window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+ val initial =
+ EditorForm(
+ name = intent.getStringExtra(EXTRA_NAME).orEmpty(),
+ dir = intent.getStringExtra(EXTRA_FOLDER_NAME).orEmpty().trim('/'),
+ password = intent.getStringExtra(EXTRA_PASSWORD).orEmpty(),
+ )
+ setContent {
+ APSTheme {
+ EditorDestination(
+ activity = this,
+ initial = initial,
+ original = null,
+ onClose = {
+ setResult(RESULT_CANCELED)
+ finish()
+ },
+ onSaved = ::finishSaved,
+ generate = generate,
)
}
- registerForActivityResult(StartActivityForResult()) { result ->
- val data = result.data
- if (result.resultCode == RESULT_OK && data != null) {
- val createdPath = data.getStringExtra("CREATED_FILE")!!
- formOrigin?.let { AutofillMatcher.addMatchFor(this, it, File(createdPath)) }
- val password = data.getStringExtra("PASSWORD")
- val resultIntent =
- if (password != null) {
- // Password was generated and should be filled into a form.
- val username = data.getStringExtra("USERNAME")
- val clientState =
- intent?.getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE)
- ?: run {
- logcat(ERROR) { "AutofillDecryptActivity started without EXTRA_CLIENT_STATE" }
- finish()
- return@registerForActivityResult
- }
- val credentials = Credentials(username, password, null)
- val fillInDataset =
- AutofillResponseBuilder.makeFillInDataset(
- this,
- credentials,
- clientState,
- AutofillAction.Generate,
- )
- Intent().apply {
- putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset)
- }
- } else {
- // Password was extracted from a form, there is nothing to fill.
- Intent()
+ }
+ }
+
+ /** Records the autofill match and, for a generated password, returns it to fill the form. */
+ private fun finishSaved(saved: SaveResult.Saved) {
+ formOrigin?.let { AutofillMatcher.addMatchFor(this, it, saved.file) }
+ val resultIntent =
+ if (generate) {
+ val clientState =
+ intent?.getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE)
+ ?: run {
+ logcat(ERROR) { "AutofillSaveActivity started without EXTRA_CLIENT_STATE" }
+ finish()
+ return
}
- setResult(RESULT_OK, resultIntent)
- } else {
- setResult(RESULT_CANCELED)
- }
- finish()
+ // Read back from the saved content, as the old editor did, so a username in the extras
+ // counts too.
+ val entry = passwordEntryFactory.create(passContent(saved.form).encodeToByteArray())
+ val username =
+ entry.username ?: AutofillPreferences.directoryStructure(this).getUsernameFor(saved.file)
+ val credentials = Credentials(username, entry.password, null)
+ val fillInDataset =
+ AutofillResponseBuilder.makeFillInDataset(
+ this,
+ credentials,
+ clientState,
+ AutofillAction.Generate,
+ )
+ Intent().putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset)
+ } else {
+ // The password came from the form: there is nothing to fill.
+ Intent()
}
- .launch(saveIntent)
+ setResult(RESULT_OK, resultIntent)
+ finish()
}
}