aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/com/zeapo/pwdstore/autofill
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2020-12-05 06:07:18 +0530
committerGitHub <noreply@github.com>2020-12-05 06:07:18 +0530
commit5e66d99c852ea67a88b650c03b0e8d55e83eccde (patch)
treeaee323a08253fe7b863976eff92a1bd412bd1430 /app/src/main/java/com/zeapo/pwdstore/autofill
parent8eb55f18a11d6b2f155c7c9d48ca833313ee13db (diff)
downloadHardenedPass-5e66d99c852ea67a88b650c03b0e8d55e83eccde.tar.gz
HardenedPass-5e66d99c852ea67a88b650c03b0e8d55e83eccde.zip
Refactor package structure (#1233)
* idea: default test runner to Gradle * Kick off package structure revamp * Reparent all classes under dev.msfjarvis.aps Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Diffstat (limited to 'app/src/main/java/com/zeapo/pwdstore/autofill')
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/Api30AutofillResponseBuilder.kt189
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillMatcher.kt193
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillPreferences.kt139
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt203
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillViewUtils.kt113
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/OreoAutofillService.kt142
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillDecryptActivity.kt245
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt220
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt114
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt149
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/PasswordViewHolder.kt16
11 files changed, 0 insertions, 1723 deletions
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/Api30AutofillResponseBuilder.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/Api30AutofillResponseBuilder.kt
deleted file mode 100644
index a69e51a7..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/Api30AutofillResponseBuilder.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * 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.IntentSender
-import android.os.Build
-import android.service.autofill.Dataset
-import android.service.autofill.FillCallback
-import android.service.autofill.FillResponse
-import android.service.autofill.SaveInfo
-import android.view.inputmethod.InlineSuggestionsRequest
-import android.widget.inline.InlinePresentationSpec
-import androidx.annotation.RequiresApi
-import com.github.ajalt.timberkt.e
-import com.github.androidpasswordstore.autofillparser.AutofillAction
-import com.github.androidpasswordstore.autofillparser.FillableForm
-import com.github.androidpasswordstore.autofillparser.fillWith
-import com.github.michaelbull.result.fold
-import com.zeapo.pwdstore.autofill.oreo.ui.AutofillDecryptActivity
-import com.zeapo.pwdstore.autofill.oreo.ui.AutofillFilterView
-import com.zeapo.pwdstore.autofill.oreo.ui.AutofillPublisherChangedActivity
-import com.zeapo.pwdstore.autofill.oreo.ui.AutofillSaveActivity
-import com.zeapo.pwdstore.autofill.oreo.ui.AutofillSmsActivity
-import java.io.File
-
-/**
- * Implements [AutofillResponseBuilder]'s methods for API 30 and above
- */
-@RequiresApi(Build.VERSION_CODES.R)
-class Api30AutofillResponseBuilder(form: FillableForm) {
-
- private val formOrigin = form.formOrigin
- private val scenario = form.scenario
- private val ignoredIds = form.ignoredIds
- private val saveFlags = form.saveFlags
- private val clientState = form.toClientState()
-
- // We do not offer save when the only relevant field is a username field or there is no field.
- private val scenarioSupportsSave = scenario.hasPasswordFieldsToSave
- private val canBeSaved = saveFlags != null && scenarioSupportsSave
-
- private fun makeIntentDataset(
- context: Context,
- action: AutofillAction,
- intentSender: IntentSender,
- metadata: DatasetMetadata,
- imeSpec: InlinePresentationSpec?,
- ): Dataset {
- return Dataset.Builder(makeRemoteView(context, metadata)).run {
- fillWith(scenario, action, credentials = null)
- setAuthentication(intentSender)
- if (imeSpec != null) {
- val inlinePresentation = makeInlinePresentation(context, imeSpec, metadata)
- if (inlinePresentation != null) {
- setInlinePresentation(inlinePresentation)
- }
- }
- build()
- }
- }
-
- private fun makeMatchDataset(context: Context, file: File, imeSpec: InlinePresentationSpec?): Dataset? {
- if (!scenario.hasFieldsToFillOn(AutofillAction.Match)) return null
- val metadata = makeFillMatchMetadata(context, file)
- val intentSender = AutofillDecryptActivity.makeDecryptFileIntentSender(file, context)
- return makeIntentDataset(context, AutofillAction.Match, intentSender, metadata, imeSpec)
- }
-
- private fun makeSearchDataset(context: Context, imeSpec: InlinePresentationSpec?): Dataset? {
- if (!scenario.hasFieldsToFillOn(AutofillAction.Search)) return null
- val metadata = makeSearchAndFillMetadata(context)
- val intentSender =
- AutofillFilterView.makeMatchAndDecryptFileIntentSender(context, formOrigin)
- return makeIntentDataset(context, AutofillAction.Search, intentSender, metadata, imeSpec)
- }
-
- private fun makeGenerateDataset(context: Context, imeSpec: InlinePresentationSpec?): Dataset? {
- if (!scenario.hasFieldsToFillOn(AutofillAction.Generate)) return null
- val metadata = makeGenerateAndFillMetadata(context)
- val intentSender = AutofillSaveActivity.makeSaveIntentSender(context, null, formOrigin)
- return makeIntentDataset(context, AutofillAction.Generate, intentSender, metadata, imeSpec)
- }
-
-
- private fun makeFillOtpFromSmsDataset(context: Context, imeSpec: InlinePresentationSpec?): Dataset? {
- if (!scenario.hasFieldsToFillOn(AutofillAction.FillOtpFromSms)) return null
- if (!AutofillSmsActivity.shouldOfferFillFromSms(context)) return null
- val metadata = makeFillOtpFromSmsMetadata(context)
- val intentSender = AutofillSmsActivity.makeFillOtpFromSmsIntentSender(context)
- return makeIntentDataset(context, AutofillAction.FillOtpFromSms, intentSender, metadata, imeSpec)
- }
-
- private fun makePublisherChangedDataset(
- context: Context,
- publisherChangedException: AutofillPublisherChangedException,
- imeSpec: InlinePresentationSpec?
- ): Dataset {
- val metadata = makeWarningMetadata(context)
- // If the user decides to trust the new publisher, they can choose reset the list of
- // matches. In this case we need to immediately show a new `FillResponse` as if the app were
- // autofilled for the first time. This `FillResponse` needs to be returned as a result from
- // `AutofillPublisherChangedActivity`, which is why we create and pass it on here.
- val fillResponseAfterReset = makeFillResponse(context, null, emptyList())
- val intentSender = AutofillPublisherChangedActivity.makePublisherChangedIntentSender(
- context, publisherChangedException, fillResponseAfterReset
- )
- return makeIntentDataset(context, AutofillAction.Match, intentSender, metadata, imeSpec)
- }
-
- private fun makePublisherChangedResponse(
- context: Context,
- inlineSuggestionsRequest: InlineSuggestionsRequest?,
- publisherChangedException: AutofillPublisherChangedException
- ): FillResponse {
- val imeSpec = inlineSuggestionsRequest?.inlinePresentationSpecs?.firstOrNull()
- return FillResponse.Builder().run {
- addDataset(makePublisherChangedDataset(context, publisherChangedException, imeSpec))
- setIgnoredIds(*ignoredIds.toTypedArray())
- build()
- }
- }
-
- private fun makeFillResponse(context: Context, inlineSuggestionsRequest: InlineSuggestionsRequest?, matchedFiles: List<File>): FillResponse? {
- var datasetCount = 0
- val imeSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs ?: emptyList()
- return FillResponse.Builder().run {
- for (file in matchedFiles) {
- makeMatchDataset(context, file, imeSpecs.getOrNull(datasetCount))?.let {
- datasetCount++
- addDataset(it)
- }
- }
- makeSearchDataset(context, imeSpecs.getOrNull(datasetCount))?.let {
- datasetCount++
- addDataset(it)
- }
- makeGenerateDataset(context, imeSpecs.getOrNull(datasetCount))?.let {
- datasetCount++
- addDataset(it)
- }
- makeFillOtpFromSmsDataset(context, imeSpecs.getOrNull(datasetCount))?.let {
- datasetCount++
- addDataset(it)
- }
- if (datasetCount == 0) return null
- setHeader(makeRemoteView(context, makeHeaderMetadata(formOrigin.getPrettyIdentifier(context, untrusted = true))))
- makeSaveInfo()?.let { setSaveInfo(it) }
- setClientState(clientState)
- setIgnoredIds(*ignoredIds.toTypedArray())
- build()
- }
- }
-
- // TODO: Support multi-step authentication flows in apps via FLAG_DELAY_SAVE
- // See: https://developer.android.com/reference/android/service/autofill/SaveInfo#FLAG_DELAY_SAVE
- private fun makeSaveInfo(): SaveInfo? {
- if (!canBeSaved) return null
- check(saveFlags != null)
- val idsToSave = scenario.fieldsToSave.toTypedArray()
- if (idsToSave.isEmpty()) return null
- var saveDataTypes = SaveInfo.SAVE_DATA_TYPE_PASSWORD
- if (scenario.hasUsername) {
- saveDataTypes = saveDataTypes or SaveInfo.SAVE_DATA_TYPE_USERNAME
- }
- return SaveInfo.Builder(saveDataTypes, idsToSave).run {
- setFlags(saveFlags)
- build()
- }
- }
-
- /**
- * Creates and returns a suitable [FillResponse] to the Autofill framework.
- */
- fun fillCredentials(context: Context, inlineSuggestionsRequest: InlineSuggestionsRequest?, callback: FillCallback) {
- AutofillMatcher.getMatchesFor(context, formOrigin).fold(
- success = { matchedFiles ->
- callback.onSuccess(makeFillResponse(context, inlineSuggestionsRequest, matchedFiles))
- },
- failure = { e ->
- e(e)
- callback.onSuccess(makePublisherChangedResponse(context, inlineSuggestionsRequest, e))
- }
- )
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillMatcher.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillMatcher.kt
deleted file mode 100644
index 601be34a..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillMatcher.kt
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * 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.widget.Toast
-import androidx.core.content.edit
-import com.github.ajalt.timberkt.Timber.e
-import com.github.ajalt.timberkt.d
-import com.github.ajalt.timberkt.w
-import com.github.michaelbull.result.Err
-import com.github.michaelbull.result.Ok
-import com.github.michaelbull.result.Result
-import com.github.androidpasswordstore.autofillparser.FormOrigin
-import com.github.androidpasswordstore.autofillparser.computeCertificatesHash
-import com.zeapo.pwdstore.R
-import java.io.File
-
-private const val PREFERENCES_AUTOFILL_APP_MATCHES = "oreo_autofill_app_matches"
-private val Context.autofillAppMatches
- get() = getSharedPreferences(PREFERENCES_AUTOFILL_APP_MATCHES, Context.MODE_PRIVATE)
-
-private const val PREFERENCES_AUTOFILL_WEB_MATCHES = "oreo_autofill_web_matches"
-private val Context.autofillWebMatches
- get() = getSharedPreferences(PREFERENCES_AUTOFILL_WEB_MATCHES, Context.MODE_PRIVATE)
-
-private fun Context.matchPreferences(formOrigin: FormOrigin): SharedPreferences {
- return when (formOrigin) {
- is FormOrigin.App -> autofillAppMatches
- is FormOrigin.Web -> autofillWebMatches
- }
-}
-
-class AutofillPublisherChangedException(val formOrigin: FormOrigin) :
- Exception("The publisher of '${formOrigin.identifier}' changed since an entry was first matched with this app") {
-
- init {
- require(formOrigin is FormOrigin.App)
- }
-}
-
-/**
- * Manages "matches", i.e., associations between apps or websites and Password Store entries.
- */
-class AutofillMatcher {
-
- companion object {
-
- private const val MAX_NUM_MATCHES = 10
-
- private const val PREFERENCE_PREFIX_TOKEN = "token;"
- private fun tokenKey(formOrigin: FormOrigin.App) =
- "$PREFERENCE_PREFIX_TOKEN${formOrigin.identifier}"
-
- private const val PREFERENCE_PREFIX_MATCHES = "matches;"
- private fun matchesKey(formOrigin: FormOrigin) =
- "$PREFERENCE_PREFIX_MATCHES${formOrigin.identifier}"
-
- private fun hasFormOriginHashChanged(context: Context, formOrigin: FormOrigin): Boolean {
- return when (formOrigin) {
- is FormOrigin.Web -> false
- is FormOrigin.App -> {
- val packageName = formOrigin.identifier
- val certificatesHash = computeCertificatesHash(context, packageName)
- val storedCertificatesHash =
- context.autofillAppMatches.getString(tokenKey(formOrigin), null)
- ?: return false
- val hashHasChanged = certificatesHash != storedCertificatesHash
- if (hashHasChanged) {
- e { "$packageName: stored=$storedCertificatesHash, new=$certificatesHash" }
- true
- } else {
- false
- }
- }
- }
- }
-
- private fun storeFormOriginHash(context: Context, formOrigin: FormOrigin) {
- if (formOrigin is FormOrigin.App) {
- val packageName = formOrigin.identifier
- val certificatesHash = computeCertificatesHash(context, packageName)
- context.autofillAppMatches.edit {
- putString(tokenKey(formOrigin), certificatesHash)
- }
- }
- // We don't need to store a hash for FormOrigin.Web since it can only originate from
- // browsers we trust to verify the origin.
- }
-
- /**
- * Get all Password Store entries that have already been associated with [formOrigin] by the
- * user.
- *
- * If [formOrigin] represents an app and that app's certificates have changed since the
- * first time the user associated an entry with it, an [AutofillPublisherChangedException]
- * will be thrown.
- */
- fun getMatchesFor(context: Context, formOrigin: FormOrigin): Result<List<File>, AutofillPublisherChangedException> {
- if (hasFormOriginHashChanged(context, formOrigin)) {
- return Err(AutofillPublisherChangedException(formOrigin))
- }
- val matchPreferences = context.matchPreferences(formOrigin)
- val matchedFiles =
- matchPreferences.getStringSet(matchesKey(formOrigin), emptySet())!!.map { File(it) }
- return Ok(matchedFiles.filter { it.exists() }.also { validFiles ->
- matchPreferences.edit {
- putStringSet(matchesKey(formOrigin), validFiles.map { it.absolutePath }.toSet())
- }
- })
- }
-
- fun clearMatchesFor(context: Context, formOrigin: FormOrigin) {
- context.matchPreferences(formOrigin).edit {
- remove(matchesKey(formOrigin))
- if (formOrigin is FormOrigin.App) remove(tokenKey(formOrigin))
- }
- }
-
- /**
- * Associates the store entry [file] with [formOrigin], such that future Autofill responses
- * to requests from this app or website offer this entry as a dataset.
- *
- * The maximum number of matches is limited by [MAX_NUM_MATCHES] since older versions of
- * Android may crash when too many datasets are offered.
- */
- fun addMatchFor(context: Context, formOrigin: FormOrigin, file: File) {
- if (!file.exists()) return
- if (hasFormOriginHashChanged(context, formOrigin)) {
- // This should never happen since we already verified the publisher in
- // getMatchesFor.
- e { "App publisher changed between getMatchesFor and addMatchFor" }
- throw AutofillPublisherChangedException(formOrigin)
- }
- val matchPreferences = context.matchPreferences(formOrigin)
- val matchedFiles =
- matchPreferences.getStringSet(matchesKey(formOrigin), emptySet())!!.map { File(it) }
- val newFiles = setOf(file.absoluteFile).union(matchedFiles)
- if (newFiles.size > MAX_NUM_MATCHES) {
- Toast.makeText(
- context,
- context.getString(R.string.oreo_autofill_max_matches_reached, MAX_NUM_MATCHES),
- Toast.LENGTH_LONG
- ).show()
- return
- }
- matchPreferences.edit {
- putStringSet(matchesKey(formOrigin), newFiles.map { it.absolutePath }.toSet())
- }
- storeFormOriginHash(context, formOrigin)
- d { "Stored match for $formOrigin" }
- }
-
- /**
- * Goes through all existing matches and updates their associated entries by using
- * [moveFromTo] as a lookup table and deleting the matches for files in [delete].
- */
- fun updateMatches(context: Context, moveFromTo: Map<File, File> = emptyMap(), delete: Collection<File> = emptyList()) {
- val deletePathList = delete.map { it.absolutePath }
- val oldNewPathMap = moveFromTo.mapValues { it.value.absolutePath }
- .mapKeys { it.key.absolutePath }
- for (prefs in listOf(context.autofillAppMatches, context.autofillWebMatches)) {
- for ((key, value) in prefs.all) {
- if (!key.startsWith(PREFERENCE_PREFIX_MATCHES)) continue
- // We know that preferences starting with `PREFERENCE_PREFIX_MATCHES` were
- // created with `putStringSet`.
- @Suppress("UNCHECKED_CAST")
- val oldMatches = value as? Set<String>
- if (oldMatches == null) {
- w { "Failed to read matches for $key" }
- continue
- }
- // Delete all matches for file locations that are going to be overwritten, then
- // transfer matches over to the files at their new locations.
- val newMatches =
- oldMatches.asSequence()
- .minus(deletePathList)
- .minus(oldNewPathMap.values)
- .map { match ->
- val newPath = oldNewPathMap[match] ?: return@map match
- d { "Updating match for $key: $match --> $newPath" }
- newPath
- }.toSet()
- if (newMatches != oldMatches)
- prefs.edit { putStringSet(key, newMatches) }
- }
- }
- }
- }
-}
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
deleted file mode 100644
index cc0875f3..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillPreferences.kt
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * 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.os.Build
-import androidx.annotation.RequiresApi
-import com.github.androidpasswordstore.autofillparser.Credentials
-import com.zeapo.pwdstore.model.PasswordEntry
-import com.zeapo.pwdstore.utils.sharedPrefs
-import java.io.File
-import java.nio.file.Paths
-
-enum class DirectoryStructure(val value: String) {
- EncryptedUsername("encrypted_username"),
- FileBased("file"),
- DirectoryBased("directory");
-
- /**
- * Returns the username associated to [file], following the convention of the current
- * [DirectoryStructure].
- *
- * Examples:
- * - * --> null (EncryptedUsername)
- * - work/example.org/john@doe.org.gpg --> john@doe.org (FileBased)
- * - work/example.org/john@doe.org/password.gpg --> john@doe.org (DirectoryBased)
- * - Temporary PIN.gpg --> Temporary PIN (DirectoryBased, fallback)
- */
- fun getUsernameFor(file: File): String? = when (this) {
- EncryptedUsername -> null
- FileBased -> file.nameWithoutExtension
- DirectoryBased -> file.parentFile?.name ?: file.nameWithoutExtension
- }
-
- /**
- * Returns the origin identifier associated to [file], following the convention of the current
- * [DirectoryStructure].
- *
- * At least one of [DirectoryStructure.getIdentifierFor] and
- * [DirectoryStructure.getAccountPartFor] will always return a non-null result.
- *
- * Examples:
- * - work/example.org.gpg --> example.org (EncryptedUsername)
- * - work/example.org/john@doe.org.gpg --> example.org (FileBased)
- * - example.org.gpg --> example.org (FileBased, fallback)
- * - work/example.org/john@doe.org/password.gpg --> example.org (DirectoryBased)
- * - Temporary PIN.gpg --> null (DirectoryBased)
- */
- fun getIdentifierFor(file: File): String? = when (this) {
- EncryptedUsername -> file.nameWithoutExtension
- FileBased -> file.parentFile?.name ?: file.nameWithoutExtension
- DirectoryBased -> file.parentFile?.parent
- }
-
- /**
- * Returns the path components of [file] until right before the component that contains the
- * origin identifier according to the current [DirectoryStructure].
- *
- * Examples:
- * - work/example.org.gpg --> work (EncryptedUsername)
- * - work/example.org/john@doe.org.gpg --> work (FileBased)
- * - example.org/john@doe.org.gpg --> null (FileBased)
- * - john@doe.org.gpg --> null (FileBased)
- * - work/example.org/john@doe.org/password.gpg --> work (DirectoryBased)
- * - example.org/john@doe.org/password.gpg --> null (DirectoryBased)
- */
- fun getPathToIdentifierFor(file: File): String? = when (this) {
- EncryptedUsername -> file.parent
- FileBased -> file.parentFile?.parent
- DirectoryBased -> file.parentFile?.parentFile?.parent
- }
-
- /**
- * Returns the path component of [file] following the origin identifier according to the current
- * [DirectoryStructure] (without file extension).
- *
- * At least one of [DirectoryStructure.getIdentifierFor] and
- * [DirectoryStructure.getAccountPartFor] will always return a non-null result.
- *
- * Examples:
- * - * --> null (EncryptedUsername)
- * - work/example.org/john@doe.org.gpg --> john@doe.org (FileBased)
- * - example.org.gpg --> null (FileBased, fallback)
- * - work/example.org/john@doe.org/password.gpg --> john@doe.org/password (DirectoryBased)
- * - Temporary PIN.gpg --> Temporary PIN (DirectoryBased, fallback)
- */
- fun getAccountPartFor(file: File): String? = when (this) {
- EncryptedUsername -> null
- FileBased -> file.nameWithoutExtension.takeIf { file.parentFile != null }
- DirectoryBased -> file.parentFile?.let { parentFile ->
- "${parentFile.name}/${file.nameWithoutExtension}"
- } ?: file.nameWithoutExtension
- }
-
- @RequiresApi(Build.VERSION_CODES.O)
- fun getSaveFolderName(sanitizedIdentifier: String, username: String?) = when (this) {
- EncryptedUsername -> "/"
- FileBased -> sanitizedIdentifier
- DirectoryBased -> Paths.get(sanitizedIdentifier, username ?: "username").toString()
- }
-
- fun getSaveFileName(username: String?, identifier: String) = when (this) {
- EncryptedUsername -> identifier
- 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.sharedPrefs.getString(DirectoryStructure.PREFERENCE, null)
- return DirectoryStructure.fromValue(value)
- }
-
- fun credentialsFromStoreEntry(
- context: Context,
- 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)
- ?: context.getDefaultUsername()
- return Credentials(username, entry.password, entry.calculateTotpCode())
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt
deleted file mode 100644
index d73373e3..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * 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.IntentSender
-import android.os.Build
-import android.os.Bundle
-import android.service.autofill.Dataset
-import android.service.autofill.FillCallback
-import android.service.autofill.FillResponse
-import android.service.autofill.SaveInfo
-import androidx.annotation.RequiresApi
-import com.github.ajalt.timberkt.e
-import com.github.androidpasswordstore.autofillparser.AutofillAction
-import com.github.androidpasswordstore.autofillparser.AutofillScenario
-import com.github.androidpasswordstore.autofillparser.Credentials
-import com.github.androidpasswordstore.autofillparser.FillableForm
-import com.github.androidpasswordstore.autofillparser.fillWith
-import com.github.michaelbull.result.fold
-import com.zeapo.pwdstore.autofill.oreo.ui.AutofillDecryptActivity
-import com.zeapo.pwdstore.autofill.oreo.ui.AutofillFilterView
-import com.zeapo.pwdstore.autofill.oreo.ui.AutofillPublisherChangedActivity
-import com.zeapo.pwdstore.autofill.oreo.ui.AutofillSaveActivity
-import com.zeapo.pwdstore.autofill.oreo.ui.AutofillSmsActivity
-import java.io.File
-
-@RequiresApi(Build.VERSION_CODES.O)
-class AutofillResponseBuilder(form: FillableForm) {
- private val formOrigin = form.formOrigin
- private val scenario = form.scenario
- private val ignoredIds = form.ignoredIds
- private val saveFlags = form.saveFlags
- private val clientState = form.toClientState()
-
- // We do not offer save when the only relevant field is a username field or there is no field.
- private val scenarioSupportsSave = scenario.hasPasswordFieldsToSave
- private val canBeSaved = saveFlags != null && scenarioSupportsSave
-
- private fun makeIntentDataset(
- context: Context,
- action: AutofillAction,
- intentSender: IntentSender,
- metadata: DatasetMetadata,
- ): Dataset {
- return Dataset.Builder(makeRemoteView(context, metadata)).run {
- fillWith(scenario, action, credentials = null)
- setAuthentication(intentSender)
- build()
- }
- }
-
- private fun makeMatchDataset(context: Context, file: File): Dataset? {
- if (!scenario.hasFieldsToFillOn(AutofillAction.Match)) return null
- val metadata = makeFillMatchMetadata(context, file)
- val intentSender = AutofillDecryptActivity.makeDecryptFileIntentSender(file, context)
- return makeIntentDataset(context, AutofillAction.Match, intentSender, metadata)
- }
-
-
- private fun makeSearchDataset(context: Context): Dataset? {
- if (!scenario.hasFieldsToFillOn(AutofillAction.Search)) return null
- val metadata = makeSearchAndFillMetadata(context)
- val intentSender =
- AutofillFilterView.makeMatchAndDecryptFileIntentSender(context, formOrigin)
- return makeIntentDataset(context, AutofillAction.Search, intentSender, metadata)
- }
-
- private fun makeGenerateDataset(context: Context): Dataset? {
- if (!scenario.hasFieldsToFillOn(AutofillAction.Generate)) return null
- val metadata = makeGenerateAndFillMetadata(context)
- val intentSender = AutofillSaveActivity.makeSaveIntentSender(context, null, formOrigin)
- return makeIntentDataset(context, AutofillAction.Generate, intentSender, metadata)
- }
-
- private fun makeFillOtpFromSmsDataset(context: Context): Dataset? {
- if (!scenario.hasFieldsToFillOn(AutofillAction.FillOtpFromSms)) return null
- if (!AutofillSmsActivity.shouldOfferFillFromSms(context)) return null
- val metadata = makeFillOtpFromSmsMetadata(context)
- val intentSender = AutofillSmsActivity.makeFillOtpFromSmsIntentSender(context)
- return makeIntentDataset(context, AutofillAction.FillOtpFromSms, intentSender, metadata)
- }
-
- private fun makePublisherChangedDataset(
- context: Context,
- publisherChangedException: AutofillPublisherChangedException,
- ): Dataset {
- val metadata = makeWarningMetadata(context)
- // If the user decides to trust the new publisher, they can choose reset the list of
- // matches. In this case we need to immediately show a new `FillResponse` as if the app were
- // autofilled for the first time. This `FillResponse` needs to be returned as a result from
- // `AutofillPublisherChangedActivity`, which is why we create and pass it on here.
- val fillResponseAfterReset = makeFillResponse(context, emptyList())
- val intentSender = AutofillPublisherChangedActivity.makePublisherChangedIntentSender(
- context, publisherChangedException, fillResponseAfterReset
- )
- return makeIntentDataset(context, AutofillAction.Match, intentSender, metadata)
- }
-
- private fun makePublisherChangedResponse(
- context: Context,
- publisherChangedException: AutofillPublisherChangedException
- ): FillResponse {
- return FillResponse.Builder().run {
- addDataset(makePublisherChangedDataset(context, publisherChangedException))
- setIgnoredIds(*ignoredIds.toTypedArray())
- build()
- }
- }
-
- // TODO: Support multi-step authentication flows in apps via FLAG_DELAY_SAVE
- // See: https://developer.android.com/reference/android/service/autofill/SaveInfo#FLAG_DELAY_SAVE
- private fun makeSaveInfo(): SaveInfo? {
- if (!canBeSaved) return null
- check(saveFlags != null)
- val idsToSave = scenario.fieldsToSave.toTypedArray()
- if (idsToSave.isEmpty()) return null
- var saveDataTypes = SaveInfo.SAVE_DATA_TYPE_PASSWORD
- if (scenario.hasUsername) {
- saveDataTypes = saveDataTypes or SaveInfo.SAVE_DATA_TYPE_USERNAME
- }
- return SaveInfo.Builder(saveDataTypes, idsToSave).run {
- setFlags(saveFlags)
- build()
- }
- }
-
- private fun makeFillResponse(context: Context, matchedFiles: List<File>): FillResponse? {
- var datasetCount = 0
- return FillResponse.Builder().run {
- for (file in matchedFiles) {
- makeMatchDataset(context, file)?.let {
- datasetCount++
- addDataset(it)
- }
- }
- makeSearchDataset(context)?.let {
- datasetCount++
- addDataset(it)
- }
- makeGenerateDataset(context)?.let {
- datasetCount++
- addDataset(it)
- }
- makeFillOtpFromSmsDataset(context)?.let {
- datasetCount++
- addDataset(it)
- }
- if (datasetCount == 0) return null
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- setHeader(makeRemoteView(context, makeHeaderMetadata(formOrigin.getPrettyIdentifier(context, untrusted = true))))
- }
- makeSaveInfo()?.let { setSaveInfo(it) }
- setClientState(clientState)
- setIgnoredIds(*ignoredIds.toTypedArray())
- build()
- }
- }
-
- /**
- * Creates and returns a suitable [FillResponse] to the Autofill framework.
- */
- fun fillCredentials(context: Context, callback: FillCallback) {
- AutofillMatcher.getMatchesFor(context, formOrigin).fold(
- success = { matchedFiles ->
- callback.onSuccess(makeFillResponse(context, matchedFiles))
- },
- failure = { e ->
- e(e)
- callback.onSuccess(makePublisherChangedResponse(context, e))
- }
- )
- }
-
- companion object {
- fun makeFillInDataset(
- context: Context,
- credentials: Credentials,
- clientState: Bundle,
- action: AutofillAction
- ): Dataset {
- val scenario = AutofillScenario.fromClientState(clientState)
- // Before Android P, Datasets used for fill-in had to come with a RemoteViews, even
- // though they are rarely shown.
- // FIXME: We should clone the original dataset here and add the credentials to be filled
- // in. Otherwise, the entry in the cached list of datasets will be overwritten by the
- // fill-in dataset without any visual representation. This causes it to be missing from
- // the Autofill suggestions shown after the user clears the filled out form fields.
- val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- Dataset.Builder()
- } else {
- Dataset.Builder(makeRemoteView(context, makeEmptyMetadata()))
- }
- return builder.run {
- if (scenario != null) fillWith(scenario, action, credentials)
- else e { "Failed to recover scenario from client state" }
- build()
- }
- }
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillViewUtils.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillViewUtils.kt
deleted file mode 100644
index 49e0d3e3..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillViewUtils.kt
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * 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.annotation.SuppressLint
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.content.res.Resources
-import android.graphics.drawable.Icon
-import android.os.Build
-import android.service.autofill.InlinePresentation
-import android.view.View
-import android.widget.RemoteViews
-import android.widget.inline.InlinePresentationSpec
-import androidx.annotation.DrawableRes
-import androidx.autofill.inline.UiVersions
-import androidx.autofill.inline.v1.InlineSuggestionUi
-import com.zeapo.pwdstore.PasswordStore
-import com.zeapo.pwdstore.R
-import com.zeapo.pwdstore.utils.PasswordRepository
-import java.io.File
-
-data class DatasetMetadata(val title: String, val subtitle: String?, @DrawableRes val iconRes: Int)
-
-fun makeRemoteView(context: Context, metadata: DatasetMetadata): RemoteViews {
- return RemoteViews(context.packageName, R.layout.oreo_autofill_dataset).apply {
- setTextViewText(R.id.title, metadata.title)
- if (metadata.subtitle != null) {
- setTextViewText(R.id.summary, metadata.subtitle)
- } else {
- setViewVisibility(R.id.summary, View.GONE)
- }
- if (metadata.iconRes != Resources.ID_NULL) {
- setImageViewResource(R.id.icon, metadata.iconRes)
- } else {
- setViewVisibility(R.id.icon, View.GONE)
- }
- }
-}
-
-@SuppressLint("RestrictedApi")
-fun makeInlinePresentation(context: Context, imeSpec: InlinePresentationSpec, metadata: DatasetMetadata): InlinePresentation? {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
- return null
-
- if (UiVersions.INLINE_UI_VERSION_1 !in UiVersions.getVersions(imeSpec.style))
- return null
-
- val launchIntent = PendingIntent.getActivity(context, 0, Intent(context, PasswordStore::class.java), 0)
- val slice = InlineSuggestionUi.newContentBuilder(launchIntent).run {
- setTitle(metadata.title)
- if (metadata.subtitle != null)
- setSubtitle(metadata.subtitle)
- setContentDescription(if (metadata.subtitle != null) "${metadata.title} - ${metadata.subtitle}" else metadata.title)
- setStartIcon(Icon.createWithResource(context, metadata.iconRes))
- build().slice
- }
-
- return InlinePresentation(slice, imeSpec, false)
-}
-
-
-fun makeFillMatchMetadata(context: Context, file: File): DatasetMetadata {
- val directoryStructure = AutofillPreferences.directoryStructure(context)
- val relativeFile = file.relativeTo(PasswordRepository.getRepositoryDirectory())
- val title = directoryStructure.getIdentifierFor(relativeFile)
- ?: directoryStructure.getAccountPartFor(relativeFile)!!
- val subtitle = directoryStructure.getAccountPartFor(relativeFile)
- return DatasetMetadata(
- title,
- subtitle,
- R.drawable.ic_person_black_24dp
- )
-}
-
-fun makeSearchAndFillMetadata(context: Context) = DatasetMetadata(
- context.getString(R.string.oreo_autofill_search_in_store),
- null,
- R.drawable.ic_search_black_24dp
-)
-
-fun makeGenerateAndFillMetadata(context: Context) = DatasetMetadata(
- context.getString(R.string.oreo_autofill_generate_password),
- null,
- R.drawable.ic_autofill_new_password
-)
-
-fun makeFillOtpFromSmsMetadata(context: Context) = DatasetMetadata(
- context.getString(R.string.oreo_autofill_fill_otp_from_sms),
- null,
- R.drawable.ic_autofill_sms
-)
-
-fun makeEmptyMetadata() = DatasetMetadata(
- "PLACEHOLDER",
- "PLACEHOLDER",
- R.mipmap.ic_launcher
-)
-
-fun makeWarningMetadata(context: Context) = DatasetMetadata(
- context.getString(R.string.oreo_autofill_warning_publisher_dataset_title),
- context.getString(R.string.oreo_autofill_warning_publisher_dataset_summary),
- R.drawable.ic_warning_red_24dp
-)
-
-fun makeHeaderMetadata(title: String) = DatasetMetadata(
- title,
- null,
- 0
-)
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/OreoAutofillService.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/OreoAutofillService.kt
deleted file mode 100644
index cc7e2c0c..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/OreoAutofillService.kt
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * 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.os.Build
-import android.os.CancellationSignal
-import android.service.autofill.AutofillService
-import android.service.autofill.FillCallback
-import android.service.autofill.FillRequest
-import android.service.autofill.FillResponse
-import android.service.autofill.SaveCallback
-import android.service.autofill.SaveRequest
-import androidx.annotation.RequiresApi
-import com.github.ajalt.timberkt.d
-import com.github.ajalt.timberkt.e
-import com.github.androidpasswordstore.autofillparser.AutofillScenario
-import com.github.androidpasswordstore.autofillparser.Credentials
-import com.github.androidpasswordstore.autofillparser.FillableForm
-import com.github.androidpasswordstore.autofillparser.FixedSaveCallback
-import com.github.androidpasswordstore.autofillparser.FormOrigin
-import com.github.androidpasswordstore.autofillparser.cachePublicSuffixList
-import com.github.androidpasswordstore.autofillparser.passwordValue
-import com.github.androidpasswordstore.autofillparser.recoverNodes
-import com.github.androidpasswordstore.autofillparser.usernameValue
-import com.zeapo.pwdstore.BuildConfig
-import com.zeapo.pwdstore.R
-import com.zeapo.pwdstore.autofill.oreo.ui.AutofillSaveActivity
-import com.zeapo.pwdstore.utils.PreferenceKeys
-import com.zeapo.pwdstore.utils.getString
-import com.zeapo.pwdstore.utils.hasFlag
-import com.zeapo.pwdstore.utils.sharedPrefs
-
-@RequiresApi(Build.VERSION_CODES.O)
-class OreoAutofillService : AutofillService() {
-
- companion object {
-
- // TODO: Provide a user-configurable denylist
- private val DENYLISTED_PACKAGES = listOf(
- BuildConfig.APPLICATION_ID,
- "android",
- "com.android.settings",
- "com.android.settings.intelligence",
- "com.android.systemui",
- "com.oneplus.applocker",
- "org.sufficientlysecure.keychain",
- )
-
- private const val DISABLE_AUTOFILL_DURATION_MS = 1000 * 60 * 60 * 24L
- }
-
- override fun onCreate() {
- super.onCreate()
- cachePublicSuffixList(applicationContext)
- }
-
- override fun onFillRequest(
- request: FillRequest,
- cancellationSignal: CancellationSignal,
- callback: FillCallback
- ) {
- val structure = request.fillContexts.lastOrNull()?.structure ?: run {
- callback.onSuccess(null)
- return
- }
- if (structure.activityComponent.packageName in DENYLISTED_PACKAGES) {
- if (Build.VERSION.SDK_INT >= 28) {
- callback.onSuccess(FillResponse.Builder().run {
- disableAutofill(DISABLE_AUTOFILL_DURATION_MS)
- build()
- })
- } else {
- callback.onSuccess(null)
- }
- return
- }
- val formToFill = FillableForm.parseAssistStructure(
- this, structure,
- isManualRequest = request.flags hasFlag FillRequest.FLAG_MANUAL_REQUEST,
- getCustomSuffixes(),
- ) ?: run {
- d { "Form cannot be filled" }
- callback.onSuccess(null)
- return
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- Api30AutofillResponseBuilder(formToFill).fillCredentials(this, request.inlineSuggestionsRequest, callback)
- } else {
- AutofillResponseBuilder(formToFill).fillCredentials(this, callback)
- }
- }
-
- override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
- // SaveCallback's behavior and feature set differs based on both target and device SDK, so
- // we replace it with a wrapper that works the same in all situations.
- @Suppress("NAME_SHADOWING") val callback = FixedSaveCallback(this, callback)
- val structure = request.fillContexts.lastOrNull()?.structure ?: run {
- callback.onFailure(getString(R.string.oreo_autofill_save_app_not_supported))
- return
- }
- val clientState = request.clientState ?: run {
- e { "Received save request without client state" }
- callback.onFailure(getString(R.string.oreo_autofill_save_internal_error))
- return
- }
- val scenario = AutofillScenario.fromClientState(clientState)?.recoverNodes(structure) ?: run {
- e { "Failed to recover client state or nodes from client state" }
- callback.onFailure(getString(R.string.oreo_autofill_save_internal_error))
- return
- }
- val formOrigin = FormOrigin.fromBundle(clientState) ?: run {
- e { "Failed to recover form origin from client state" }
- callback.onFailure(getString(R.string.oreo_autofill_save_internal_error))
- return
- }
-
- val username = scenario.usernameValue
- val password = scenario.passwordValue ?: run {
- callback.onFailure(getString(R.string.oreo_autofill_save_passwords_dont_match))
- return
- }
- callback.onSuccess(
- AutofillSaveActivity.makeSaveIntentSender(
- this,
- credentials = Credentials(username, password, null),
- formOrigin = formOrigin
- )
- )
- }
-}
-
-fun Context.getDefaultUsername() = sharedPrefs.getString(PreferenceKeys.OREO_AUTOFILL_DEFAULT_USERNAME)
-
-fun Context.getCustomSuffixes(): Sequence<String> {
- return sharedPrefs.getString(PreferenceKeys.OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES)
- ?.splitToSequence('\n')
- ?.filter { it.isNotBlank() && it.first() != '.' && it.last() != '.' }
- ?: emptySequence()
-}
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
deleted file mode 100644
index a5bdcfe0..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillDecryptActivity.kt
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package com.zeapo.pwdstore.autofill.oreo.ui
-
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.content.IntentSender
-import android.os.Build
-import android.os.Bundle
-import android.view.autofill.AutofillManager
-import android.widget.Toast
-import androidx.activity.result.IntentSenderRequest
-import androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult
-import androidx.annotation.RequiresApi
-import androidx.appcompat.app.AppCompatActivity
-import com.github.ajalt.timberkt.d
-import com.github.ajalt.timberkt.e
-import com.github.michaelbull.result.getOrElse
-import com.github.michaelbull.result.onFailure
-import com.github.michaelbull.result.onSuccess
-import com.github.michaelbull.result.runCatching
-import com.github.androidpasswordstore.autofillparser.AutofillAction
-import com.github.androidpasswordstore.autofillparser.Credentials
-import com.zeapo.pwdstore.autofill.oreo.AutofillPreferences
-import com.zeapo.pwdstore.autofill.oreo.AutofillResponseBuilder
-import com.zeapo.pwdstore.autofill.oreo.DirectoryStructure
-import com.zeapo.pwdstore.model.PasswordEntry
-import com.zeapo.pwdstore.utils.OPENPGP_PROVIDER
-import java.io.ByteArrayOutputStream
-import java.io.File
-import java.io.InputStream
-import java.io.OutputStream
-import kotlin.coroutines.Continuation
-import kotlin.coroutines.resume
-import kotlin.coroutines.resumeWithException
-import kotlin.coroutines.suspendCoroutine
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancelChildren
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import me.msfjarvis.openpgpktx.util.OpenPgpApi
-import me.msfjarvis.openpgpktx.util.OpenPgpServiceConnection
-import org.openintents.openpgp.IOpenPgpService2
-import org.openintents.openpgp.OpenPgpError
-
-@RequiresApi(Build.VERSION_CODES.O)
-class AutofillDecryptActivity : AppCompatActivity(), CoroutineScope {
-
- companion object {
-
- private const val EXTRA_FILE_PATH = "com.zeapo.pwdstore.autofill.oreo.EXTRA_FILE_PATH"
- private const val EXTRA_SEARCH_ACTION =
- "com.zeapo.pwdstore.autofill.oreo.EXTRA_SEARCH_ACTION"
-
- private var decryptFileRequestCode = 1
-
- fun makeDecryptFileIntent(file: File, forwardedExtras: Bundle, context: Context): Intent {
- return Intent(context, AutofillDecryptActivity::class.java).apply {
- putExtras(forwardedExtras)
- putExtra(EXTRA_SEARCH_ACTION, true)
- putExtra(EXTRA_FILE_PATH, file.absolutePath)
- }
- }
-
- fun makeDecryptFileIntentSender(file: File, context: Context): IntentSender {
- val intent = Intent(context, AutofillDecryptActivity::class.java).apply {
- putExtra(EXTRA_SEARCH_ACTION, false)
- putExtra(EXTRA_FILE_PATH, file.absolutePath)
- }
- return PendingIntent.getActivity(
- context,
- decryptFileRequestCode++,
- intent,
- PendingIntent.FLAG_CANCEL_CURRENT
- ).intentSender
- }
- }
-
- private val decryptInteractionRequiredAction = registerForActivityResult(StartIntentSenderForResult()) { result ->
- if (continueAfterUserInteraction != null) {
- val data = result.data
- if (result.resultCode == RESULT_OK && data != null) {
- continueAfterUserInteraction?.resume(data)
- } else {
- continueAfterUserInteraction?.resumeWithException(Exception("OpenPgpApi ACTION_DECRYPT_VERIFY failed to continue after user interaction"))
- }
- continueAfterUserInteraction = null
- }
- }
-
- private var continueAfterUserInteraction: Continuation<Intent>? = null
- private lateinit var directoryStructure: DirectoryStructure
-
- override val coroutineContext
- get() = Dispatchers.IO + SupervisorJob()
-
- override fun onStart() {
- super.onStart()
- val filePath = intent?.getStringExtra(EXTRA_FILE_PATH) ?: run {
- e { "AutofillDecryptActivity started without EXTRA_FILE_PATH" }
- finish()
- return
- }
- val clientState = intent?.getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE) ?: run {
- e { "AutofillDecryptActivity started without EXTRA_CLIENT_STATE" }
- finish()
- return
- }
- 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 = decryptCredential(File(filePath))
- if (credentials == null) {
- setResult(RESULT_CANCELED)
- } else {
- val fillInDataset =
- AutofillResponseBuilder.makeFillInDataset(
- this@AutofillDecryptActivity,
- credentials,
- clientState,
- action
- )
- withContext(Dispatchers.Main) {
- setResult(RESULT_OK, Intent().apply {
- putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset)
- })
- }
- }
- withContext(Dispatchers.Main) {
- finish()
- }
- }
- }
-
- override fun onDestroy() {
- super.onDestroy()
- coroutineContext.cancelChildren()
- }
-
- private suspend fun executeOpenPgpApi(
- data: Intent,
- input: InputStream,
- output: OutputStream
- ): Intent? {
- var openPgpServiceConnection: OpenPgpServiceConnection? = null
- val openPgpService = suspendCoroutine<IOpenPgpService2> { cont ->
- openPgpServiceConnection = OpenPgpServiceConnection(
- this,
- OPENPGP_PROVIDER,
- object : OpenPgpServiceConnection.OnBound {
- override fun onBound(service: IOpenPgpService2) {
- cont.resume(service)
- }
-
- override fun onError(e: Exception) {
- cont.resumeWithException(e)
- }
- }).also { it.bindToService() }
- }
- return OpenPgpApi(this, openPgpService).executeApi(data, input, output).also {
- openPgpServiceConnection?.unbindFromService()
- }
- }
-
- private suspend fun decryptCredential(
- file: File,
- resumeIntent: Intent? = null
- ): Credentials? {
- val command = resumeIntent ?: Intent().apply {
- action = OpenPgpApi.ACTION_DECRYPT_VERIFY
- }
- runCatching {
- file.inputStream()
- }.onFailure { e ->
- e(e) { "File to decrypt not found" }
- return null
- }.onSuccess { encryptedInput ->
- val decryptedOutput = ByteArrayOutputStream()
- runCatching {
- executeOpenPgpApi(command, encryptedInput, decryptedOutput)
- }.onFailure { e ->
- e(e) { "OpenPgpApi ACTION_DECRYPT_VERIFY failed" }
- return null
- }.onSuccess { result ->
- return when (val resultCode =
- result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
- OpenPgpApi.RESULT_CODE_SUCCESS -> {
- runCatching {
- val entry = withContext(Dispatchers.IO) {
- @Suppress("BlockingMethodInNonBlockingContext")
- PasswordEntry(decryptedOutput)
- }
- AutofillPreferences.credentialsFromStoreEntry(this, file, entry, directoryStructure)
- }.getOrElse { e ->
- e(e) { "Failed to parse password entry" }
- return null
- }
- }
- OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {
- val pendingIntent: PendingIntent =
- result.getParcelableExtra(OpenPgpApi.RESULT_INTENT)!!
- runCatching {
- val intentToResume = withContext(Dispatchers.Main) {
- suspendCoroutine<Intent> { cont ->
- continueAfterUserInteraction = cont
- decryptInteractionRequiredAction.launch(IntentSenderRequest.Builder(pendingIntent.intentSender).build())
- }
- }
- decryptCredential(file, intentToResume)
- }.getOrElse { e ->
- e(e) { "OpenPgpApi ACTION_DECRYPT_VERIFY failed with user interaction" }
- return null
- }
- }
- OpenPgpApi.RESULT_CODE_ERROR -> {
- val error = result.getParcelableExtra<OpenPgpError>(OpenPgpApi.RESULT_ERROR)
- if (error != null) {
- withContext(Dispatchers.Main) {
- Toast.makeText(
- applicationContext,
- "Error from OpenKeyChain: ${error.message}",
- Toast.LENGTH_LONG
- ).show()
- }
- e { "OpenPgpApi ACTION_DECRYPT_VERIFY failed (${error.errorId}): ${error.message}" }
- }
- null
- }
- else -> {
- e { "Unrecognized OpenPgpApi result: $resultCode" }
- null
- }
- }
- }
- }
- return 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
deleted file mode 100644
index 25919ad5..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillFilterActivity.kt
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package com.zeapo.pwdstore.autofill.oreo.ui
-
-import android.annotation.TargetApi
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.content.IntentSender
-import android.os.Build
-import android.os.Bundle
-import android.view.View
-import android.view.autofill.AutofillManager
-import android.widget.TextView
-import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
-import androidx.activity.viewModels
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.text.bold
-import androidx.core.text.buildSpannedString
-import androidx.core.text.underline
-import androidx.core.widget.addTextChangedListener
-import androidx.lifecycle.ViewModelProvider
-import androidx.recyclerview.widget.LinearLayoutManager
-import com.github.ajalt.timberkt.e
-import com.github.androidpasswordstore.autofillparser.FormOrigin
-import com.zeapo.pwdstore.FilterMode
-import com.zeapo.pwdstore.ListMode
-import com.zeapo.pwdstore.R
-import com.zeapo.pwdstore.SearchMode
-import com.zeapo.pwdstore.SearchableRepositoryAdapter
-import com.zeapo.pwdstore.SearchableRepositoryViewModel
-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.databinding.ActivityOreoAutofillFilterBinding
-import com.zeapo.pwdstore.utils.PasswordItem
-import com.zeapo.pwdstore.utils.viewBinding
-
-@TargetApi(Build.VERSION_CODES.O)
-class AutofillFilterView : AppCompatActivity() {
-
- companion object {
-
- private const val HEIGHT_PERCENTAGE = 0.9
- private const val WIDTH_PERCENTAGE = 0.75
-
- private const val EXTRA_FORM_ORIGIN_WEB =
- "com.zeapo.pwdstore.autofill.oreo.ui.EXTRA_FORM_ORIGIN_WEB"
- private const val EXTRA_FORM_ORIGIN_APP =
- "com.zeapo.pwdstore.autofill.oreo.ui.EXTRA_FORM_ORIGIN_APP"
- private var matchAndDecryptFileRequestCode = 1
-
- fun makeMatchAndDecryptFileIntentSender(
- context: Context,
- formOrigin: FormOrigin
- ): IntentSender {
- val intent = Intent(context, AutofillFilterView::class.java).apply {
- when (formOrigin) {
- is FormOrigin.Web -> putExtra(EXTRA_FORM_ORIGIN_WEB, formOrigin.identifier)
- is FormOrigin.App -> putExtra(EXTRA_FORM_ORIGIN_APP, formOrigin.identifier)
- }
- }
- return PendingIntent.getActivity(
- context,
- matchAndDecryptFileRequestCode++,
- intent,
- PendingIntent.FLAG_CANCEL_CURRENT
- ).intentSender
- }
- }
-
- private lateinit var formOrigin: FormOrigin
- private lateinit var directoryStructure: DirectoryStructure
- private val binding by viewBinding(ActivityOreoAutofillFilterBinding::inflate)
-
- private val model: SearchableRepositoryViewModel by viewModels {
- ViewModelProvider.AndroidViewModelFactory(application)
- }
-
- private val decryptAction = registerForActivityResult(StartActivityForResult()) { result ->
- if (result.resultCode == RESULT_OK) {
- setResult(RESULT_OK, result.data)
- }
- finish()
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(binding.root)
- setFinishOnTouchOutside(true)
-
- val params = window.attributes
- params.height = (HEIGHT_PERCENTAGE * resources.displayMetrics.heightPixels).toInt()
- params.width = (WIDTH_PERCENTAGE * resources.displayMetrics.widthPixels).toInt()
- window.attributes = params
-
- if (intent?.hasExtra(AutofillManager.EXTRA_CLIENT_STATE) != true) {
- e { "AutofillFilterActivity started without EXTRA_CLIENT_STATE" }
- finish()
- return
- }
- formOrigin = when {
- intent?.hasExtra(EXTRA_FORM_ORIGIN_WEB) == true -> {
- FormOrigin.Web(intent!!.getStringExtra(EXTRA_FORM_ORIGIN_WEB)!!)
- }
- intent?.hasExtra(EXTRA_FORM_ORIGIN_APP) == true -> {
- FormOrigin.App(intent!!.getStringExtra(EXTRA_FORM_ORIGIN_APP)!!)
- }
- else -> {
- e { "AutofillFilterActivity started without EXTRA_FORM_ORIGIN_WEB or EXTRA_FORM_ORIGIN_APP" }
- finish()
- return
- }
- }
- directoryStructure = AutofillPreferences.directoryStructure(this)
-
- supportActionBar?.hide()
- bindUI()
- updateSearch()
- setResult(RESULT_CANCELED)
- }
-
- private fun bindUI() {
- with(binding) {
- rvPassword.apply {
- adapter = SearchableRepositoryAdapter(
- R.layout.oreo_autofill_filter_row,
- ::PasswordViewHolder
- ) { item ->
- val file = item.file.relativeTo(item.rootDir)
- val pathToIdentifier = directoryStructure.getPathToIdentifierFor(file)
- val identifier = directoryStructure.getIdentifierFor(file)
- val accountPart = directoryStructure.getAccountPartFor(file)
- check(identifier != null || accountPart != null) { "At least one of identifier and accountPart should always be non-null" }
- title.text = if (identifier != null) {
- buildSpannedString {
- if (pathToIdentifier != null)
- append("$pathToIdentifier/")
- bold { underline { append(identifier) } }
- }
- } else {
- accountPart
- }
- subtitle.apply {
- if (identifier != null && accountPart != null) {
- text = accountPart
- visibility = View.VISIBLE
- } else {
- visibility = View.GONE
- }
- }
- }.onItemClicked { _, item ->
- decryptAndFill(item)
- }
- layoutManager = LinearLayoutManager(context)
- }
- search.apply {
- val initialSearch =
- formOrigin.getPrettyIdentifier(applicationContext, untrusted = false)
- setText(initialSearch, TextView.BufferType.EDITABLE)
- addTextChangedListener { updateSearch() }
- }
- origin.text = buildSpannedString {
- append(getString(R.string.oreo_autofill_select_and_fill_into))
- append("\n")
- bold {
- append(formOrigin.getPrettyIdentifier(applicationContext, untrusted = true))
- }
- }
- strictDomainSearch.apply {
- visibility = if (formOrigin is FormOrigin.Web) View.VISIBLE else View.GONE
- isChecked = formOrigin is FormOrigin.Web
- setOnCheckedChangeListener { _, _ -> updateSearch() }
- }
- shouldMatch.text = getString(
- R.string.oreo_autofill_match_with,
- formOrigin.getPrettyIdentifier(applicationContext)
- )
- model.searchResult.observe(this@AutofillFilterView) { result ->
- val list = result.passwordItems
- (rvPassword.adapter as SearchableRepositoryAdapter).submitList(list) {
- rvPassword.scrollToPosition(0)
- }
- // Switch RecyclerView out for a "no results" message if the new list is empty and
- // the message is not yet shown (and vice versa).
- if ((list.isEmpty() && rvPasswordSwitcher.nextView.id == rvPasswordEmpty.id) ||
- (list.isNotEmpty() && rvPasswordSwitcher.nextView.id == rvPassword.id)
- ) {
- rvPasswordSwitcher.showNext()
- }
- }
- }
- }
-
- private fun updateSearch() {
- model.search(
- binding.search.text.toString().trim(),
- filterMode = if (binding.strictDomainSearch.isChecked) FilterMode.StrictDomain else FilterMode.Fuzzy,
- searchMode = SearchMode.RecursivelyInSubdirectories,
- listMode = ListMode.FilesOnly
- )
- }
-
- private fun decryptAndFill(item: PasswordItem) {
- if (binding.shouldClear.isChecked) AutofillMatcher.clearMatchesFor(applicationContext, formOrigin)
- if (binding.shouldMatch.isChecked) AutofillMatcher.addMatchFor(
- applicationContext,
- formOrigin,
- item.file
- )
- // intent?.extras? is checked to be non-null in onCreate
- decryptAction.launch(AutofillDecryptActivity.makeDecryptFileIntent(
- item.file,
- intent!!.extras!!,
- this
- ))
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt
deleted file mode 100644
index 34edf8f5..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package com.zeapo.pwdstore.autofill.oreo.ui
-
-import android.annotation.TargetApi
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.content.IntentSender
-import android.content.pm.PackageManager
-import android.os.Build
-import android.os.Bundle
-import android.service.autofill.FillResponse
-import android.text.format.DateUtils
-import android.view.View
-import android.view.autofill.AutofillManager
-import androidx.appcompat.app.AppCompatActivity
-import com.github.ajalt.timberkt.e
-import com.github.androidpasswordstore.autofillparser.FormOrigin
-import com.github.androidpasswordstore.autofillparser.computeCertificatesHash
-import com.github.michaelbull.result.onFailure
-import com.github.michaelbull.result.runCatching
-import com.zeapo.pwdstore.R
-import com.zeapo.pwdstore.autofill.oreo.AutofillMatcher
-import com.zeapo.pwdstore.autofill.oreo.AutofillPublisherChangedException
-import com.zeapo.pwdstore.databinding.ActivityOreoAutofillPublisherChangedBinding
-import com.zeapo.pwdstore.utils.viewBinding
-
-@TargetApi(Build.VERSION_CODES.O)
-class AutofillPublisherChangedActivity : AppCompatActivity() {
-
- companion object {
-
- private const val EXTRA_APP_PACKAGE =
- "com.zeapo.pwdstore.autofill.oreo.ui.EXTRA_APP_PACKAGE"
- private const val EXTRA_FILL_RESPONSE_AFTER_RESET =
- "com.zeapo.pwdstore.autofill.oreo.ui.EXTRA_FILL_RESPONSE_AFTER_RESET"
- private var publisherChangedRequestCode = 1
-
- fun makePublisherChangedIntentSender(
- context: Context,
- publisherChangedException: AutofillPublisherChangedException,
- fillResponseAfterReset: FillResponse?,
- ): IntentSender {
- val intent = Intent(context, AutofillPublisherChangedActivity::class.java).apply {
- putExtra(EXTRA_APP_PACKAGE, publisherChangedException.formOrigin.identifier)
- putExtra(EXTRA_FILL_RESPONSE_AFTER_RESET, fillResponseAfterReset)
- }
- return PendingIntent.getActivity(
- context, publisherChangedRequestCode++, intent, PendingIntent.FLAG_CANCEL_CURRENT
- ).intentSender
- }
- }
-
- private lateinit var appPackage: String
- private val binding by viewBinding(ActivityOreoAutofillPublisherChangedBinding::inflate)
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(binding.root)
- setFinishOnTouchOutside(true)
-
- appPackage = intent.getStringExtra(EXTRA_APP_PACKAGE) ?: run {
- e { "AutofillPublisherChangedActivity started without EXTRA_PACKAGE_NAME" }
- finish()
- return
- }
- supportActionBar?.hide()
- showPackageInfo()
- with(binding) {
- okButton.setOnClickListener { finish() }
- advancedButton.setOnClickListener {
- advancedButton.visibility = View.GONE
- warningAppAdvancedInfo.visibility = View.VISIBLE
- resetButton.visibility = View.VISIBLE
- }
- resetButton.setOnClickListener {
- AutofillMatcher.clearMatchesFor(this@AutofillPublisherChangedActivity, FormOrigin.App(appPackage))
- val fillResponse = intent.getParcelableExtra<FillResponse>(EXTRA_FILL_RESPONSE_AFTER_RESET)
- setResult(RESULT_OK, Intent().apply {
- putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillResponse)
- })
- finish()
- }
- }
- }
-
- private fun showPackageInfo() {
- runCatching {
- with(binding) {
- val packageInfo =
- packageManager.getPackageInfo(appPackage, PackageManager.GET_META_DATA)
- val installTime = DateUtils.getRelativeTimeSpanString(packageInfo.firstInstallTime)
- warningAppInstallDate.text =
- getString(R.string.oreo_autofill_warning_publisher_install_time, installTime)
- val appInfo =
- packageManager.getApplicationInfo(appPackage, PackageManager.GET_META_DATA)
- warningAppName.text = "“${packageManager.getApplicationLabel(appInfo)}”"
-
- val currentHash = computeCertificatesHash(this@AutofillPublisherChangedActivity, appPackage)
- warningAppAdvancedInfo.text = getString(
- R.string.oreo_autofill_warning_publisher_advanced_info_template,
- appPackage,
- currentHash
- )
- }
- }.onFailure { e ->
- e(e) { "Failed to retrieve package info for $appPackage" }
- finish()
- }
- }
-}
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
deleted file mode 100644
index bc012d3d..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package com.zeapo.pwdstore.autofill.oreo.ui
-
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.content.IntentSender
-import android.os.Build
-import android.os.Bundle
-import android.view.autofill.AutofillManager
-import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
-import androidx.annotation.RequiresApi
-import androidx.appcompat.app.AppCompatActivity
-import androidx.core.os.bundleOf
-import com.github.ajalt.timberkt.e
-import com.github.androidpasswordstore.autofillparser.AutofillAction
-import com.github.androidpasswordstore.autofillparser.Credentials
-import com.github.androidpasswordstore.autofillparser.FormOrigin
-import com.zeapo.pwdstore.autofill.oreo.AutofillMatcher
-import com.zeapo.pwdstore.autofill.oreo.AutofillPreferences
-import com.zeapo.pwdstore.autofill.oreo.AutofillResponseBuilder
-import com.zeapo.pwdstore.crypto.PasswordCreationActivity
-import com.zeapo.pwdstore.utils.PasswordRepository
-import java.io.File
-
-@RequiresApi(Build.VERSION_CODES.O)
-class AutofillSaveActivity : AppCompatActivity() {
-
- companion object {
-
- 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_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 =
- "com.zeapo.pwdstore.autofill.oreo.ui.EXTRA_SHOULD_MATCH_WEB"
- private const val EXTRA_GENERATE_PASSWORD =
- "com.zeapo.pwdstore.autofill.oreo.ui.EXTRA_GENERATE_PASSWORD"
-
- private var saveRequestCode = 1
-
- fun makeSaveIntentSender(
- context: Context,
- credentials: Credentials?,
- formOrigin: FormOrigin
- ): IntentSender {
- val identifier = formOrigin.getPrettyIdentifier(context, untrusted = false)
- // 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, identifier = identifier)
- 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_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)
- )
- )
- }
- return PendingIntent.getActivity(
- context,
- saveRequestCode++,
- intent,
- PendingIntent.FLAG_CANCEL_CURRENT
- ).intentSender
- }
- }
-
- private val formOrigin by lazy(LazyThreadSafetyMode.NONE) {
- val shouldMatchApp: String? = intent.getStringExtra(EXTRA_SHOULD_MATCH_APP)
- val shouldMatchWeb: String? = intent.getStringExtra(EXTRA_SHOULD_MATCH_WEB)
- if (shouldMatchApp != null && shouldMatchWeb == null) {
- FormOrigin.App(shouldMatchApp)
- } else if (shouldMatchApp == null && shouldMatchWeb != null) {
- FormOrigin.Web(shouldMatchWeb)
- } else {
- null
- }
- }
-
- 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)
- )
- )
- }
- 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 {
- e { "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()
- }
- setResult(RESULT_OK, resultIntent)
- } else {
- setResult(RESULT_CANCELED)
- }
- finish()
- }.launch(saveIntent)
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/PasswordViewHolder.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/PasswordViewHolder.kt
deleted file mode 100644
index 162362f6..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/PasswordViewHolder.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
- * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package com.zeapo.pwdstore.autofill.oreo.ui
-
-import android.view.View
-import android.widget.TextView
-import androidx.recyclerview.widget.RecyclerView
-import com.zeapo.pwdstore.R
-
-class PasswordViewHolder(view: View) : RecyclerView.ViewHolder(view) {
-
- val title: TextView = itemView.findViewById(R.id.title)
- val subtitle: TextView = itemView.findViewById(R.id.subtitle)
-}