aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/dev/msfjarvis/aps/ui/autofill
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2022-07-15 00:53:48 +0530
committerHarsh Shandilya <me@msfjarvis.dev>2022-07-15 01:13:47 +0530
commit549ee790d3e52bc62565ddf92e6a556e98b5195e (patch)
treed5758e5eb80093704e683c8da926838e18182588 /app/src/main/java/dev/msfjarvis/aps/ui/autofill
parent010c6e227c9cc27f4d01bc912311f977b2aeb3a7 (diff)
downloadHardenedPass-549ee790d3e52bc62565ddf92e6a556e98b5195e.tar.gz
HardenedPass-549ee790d3e52bc62565ddf92e6a556e98b5195e.zip
all: re-do package structure yet again
Diffstat (limited to 'app/src/main/java/dev/msfjarvis/aps/ui/autofill')
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivity.kt269
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivityV2.kt180
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterView.kt241
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillPublisherChangedActivity.kt135
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillSaveActivity.kt173
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/autofill/PasswordViewHolder.kt16
6 files changed, 0 insertions, 1014 deletions
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivity.kt
deleted file mode 100644
index b5052049..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivity.kt
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package dev.msfjarvis.aps.ui.autofill
-
-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 androidx.lifecycle.lifecycleScope
-import com.github.androidpasswordstore.autofillparser.AutofillAction
-import com.github.androidpasswordstore.autofillparser.Credentials
-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 dagger.hilt.android.AndroidEntryPoint
-import dev.msfjarvis.aps.data.passfile.PasswordEntry
-import dev.msfjarvis.aps.util.autofill.AutofillPreferences
-import dev.msfjarvis.aps.util.autofill.AutofillResponseBuilder
-import dev.msfjarvis.aps.util.autofill.DirectoryStructure
-import dev.msfjarvis.aps.util.extensions.OPENPGP_PROVIDER
-import dev.msfjarvis.aps.util.extensions.asLog
-import java.io.ByteArrayOutputStream
-import java.io.File
-import java.io.InputStream
-import java.io.OutputStream
-import javax.inject.Inject
-import kotlin.coroutines.Continuation
-import kotlin.coroutines.resume
-import kotlin.coroutines.resumeWithException
-import kotlin.coroutines.suspendCoroutine
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import logcat.LogPriority.ERROR
-import logcat.logcat
-import me.msfjarvis.openpgpktx.util.OpenPgpApi
-import me.msfjarvis.openpgpktx.util.OpenPgpServiceConnection
-import org.openintents.openpgp.IOpenPgpService2
-import org.openintents.openpgp.OpenPgpError
-
-@RequiresApi(26)
-@AndroidEntryPoint
-class AutofillDecryptActivity : AppCompatActivity() {
-
- companion object {
-
- private const val EXTRA_FILE_PATH = "dev.msfjarvis.aps.autofill.oreo.EXTRA_FILE_PATH"
- private const val EXTRA_SEARCH_ACTION = "dev.msfjarvis.aps.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,
- if (Build.VERSION.SDK_INT >= 31) {
- PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
- } else {
- PendingIntent.FLAG_CANCEL_CURRENT
- },
- )
- .intentSender
- }
- }
-
- @Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
-
- 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 fun onStart() {
- super.onStart()
- val filePath =
- intent?.getStringExtra(EXTRA_FILE_PATH)
- ?: run {
- logcat(ERROR) { "AutofillDecryptActivity started without EXTRA_FILE_PATH" }
- finish()
- return
- }
- val clientState =
- intent?.getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE)
- ?: run {
- logcat(ERROR) { "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)
- logcat { action.toString() }
- lifecycleScope.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() }
- }
- }
-
- 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 ->
- logcat(ERROR) { e.asLog("File to decrypt not found") }
- return null
- }
- .onSuccess { encryptedInput ->
- val decryptedOutput = ByteArrayOutputStream()
- runCatching { executeOpenPgpApi(command, encryptedInput, decryptedOutput) }
- .onFailure { e ->
- logcat(ERROR) { e.asLog("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")
- passwordEntryFactory.create(decryptedOutput.toByteArray())
- }
- AutofillPreferences.credentialsFromStoreEntry(
- this,
- file,
- entry,
- directoryStructure
- )
- }
- .getOrElse { e ->
- logcat(ERROR) { e.asLog("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 ->
- logcat(ERROR) {
- e.asLog("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()
- }
- logcat(ERROR) {
- "OpenPgpApi ACTION_DECRYPT_VERIFY failed (${error.errorId}): ${error.message}"
- }
- }
- null
- }
- else -> {
- logcat(ERROR) { "Unrecognized OpenPgpApi result: $resultCode" }
- null
- }
- }
- }
- }
- return null
- }
-}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivityV2.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivityV2.kt
deleted file mode 100644
index 4fd8f026..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivityV2.kt
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package dev.msfjarvis.aps.ui.autofill
-
-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.annotation.RequiresApi
-import androidx.appcompat.app.AppCompatActivity
-import androidx.lifecycle.lifecycleScope
-import com.github.androidpasswordstore.autofillparser.AutofillAction
-import com.github.androidpasswordstore.autofillparser.Credentials
-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 dagger.hilt.android.AndroidEntryPoint
-import dev.msfjarvis.aps.data.crypto.CryptoRepository
-import dev.msfjarvis.aps.data.passfile.PasswordEntry
-import dev.msfjarvis.aps.ui.crypto.PasswordDialog
-import dev.msfjarvis.aps.util.autofill.AutofillPreferences
-import dev.msfjarvis.aps.util.autofill.AutofillResponseBuilder
-import dev.msfjarvis.aps.util.autofill.DirectoryStructure
-import dev.msfjarvis.aps.util.extensions.asLog
-import java.io.ByteArrayOutputStream
-import java.io.File
-import javax.inject.Inject
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import logcat.LogPriority.ERROR
-import logcat.logcat
-
-@RequiresApi(26)
-@AndroidEntryPoint
-class AutofillDecryptActivityV2 : AppCompatActivity() {
-
- companion object {
-
- private const val EXTRA_FILE_PATH = "dev.msfjarvis.aps.autofill.oreo.EXTRA_FILE_PATH"
- private const val EXTRA_SEARCH_ACTION = "dev.msfjarvis.aps.autofill.oreo.EXTRA_SEARCH_ACTION"
-
- private var decryptFileRequestCode = 1
-
- fun makeDecryptFileIntent(file: File, forwardedExtras: Bundle, context: Context): Intent {
- return Intent(context, AutofillDecryptActivityV2::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, AutofillDecryptActivityV2::class.java).apply {
- putExtra(EXTRA_SEARCH_ACTION, false)
- putExtra(EXTRA_FILE_PATH, file.absolutePath)
- }
- return PendingIntent.getActivity(
- context,
- decryptFileRequestCode++,
- intent,
- if (Build.VERSION.SDK_INT >= 31) {
- PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
- } else {
- PendingIntent.FLAG_CANCEL_CURRENT
- },
- )
- .intentSender
- }
- }
-
- @Inject lateinit var passwordEntryFactory: PasswordEntry.Factory
- @Inject lateinit var repository: CryptoRepository
-
- private lateinit var directoryStructure: DirectoryStructure
-
- override fun onStart() {
- super.onStart()
- val filePath =
- intent?.getStringExtra(EXTRA_FILE_PATH)
- ?: run {
- logcat(ERROR) { "AutofillDecryptActivityV2 started without EXTRA_FILE_PATH" }
- finish()
- return
- }
- val clientState =
- intent?.getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE)
- ?: run {
- logcat(ERROR) { "AutofillDecryptActivityV2 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)
- logcat { action.toString() }
- val dialog = PasswordDialog()
- lifecycleScope.launch {
- withContext(Dispatchers.Main) {
- dialog.password.collectLatest { value ->
- if (value != null) {
- decrypt(File(filePath), clientState, action, value)
- }
- }
- }
- }
- dialog.show(supportFragmentManager, "PASSWORD_DIALOG")
- }
-
- private suspend fun decrypt(
- filePath: File,
- clientState: Bundle,
- action: AutofillAction,
- password: String,
- ) {
- val credentials = decryptCredential(filePath, password)
- if (credentials == null) {
- setResult(RESULT_CANCELED)
- } else {
- val fillInDataset =
- AutofillResponseBuilder.makeFillInDataset(
- this@AutofillDecryptActivityV2,
- credentials,
- clientState,
- action
- )
- withContext(Dispatchers.Main) {
- setResult(
- RESULT_OK,
- Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) }
- )
- }
- }
- withContext(Dispatchers.Main) { finish() }
- }
-
- private suspend fun decryptCredential(file: File, password: String): Credentials? {
- runCatching { file.readBytes().inputStream() }
- .onFailure { e ->
- logcat(ERROR) { e.asLog("File to decrypt not found") }
- return null
- }
- .onSuccess { encryptedInput ->
- runCatching {
- withContext(Dispatchers.IO) {
- val outputStream = ByteArrayOutputStream()
- repository.decrypt(
- password,
- encryptedInput,
- outputStream,
- )
- outputStream
- }
- }
- .onFailure { e ->
- logcat(ERROR) { e.asLog("Decryption failed") }
- return null
- }
- .onSuccess { result ->
- return runCatching {
- val entry = passwordEntryFactory.create(result.toByteArray())
- AutofillPreferences.credentialsFromStoreEntry(this, file, entry, directoryStructure)
- }
- .getOrElse { e ->
- logcat(ERROR) { e.asLog("Failed to parse password entry") }
- return null
- }
- }
- }
- return null
- }
-}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterView.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterView.kt
deleted file mode 100644
index 89f1a733..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterView.kt
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package dev.msfjarvis.aps.ui.autofill
-
-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.lifecycle.lifecycleScope
-import androidx.recyclerview.widget.LinearLayoutManager
-import com.github.androidpasswordstore.autofillparser.FormOrigin
-import dagger.hilt.android.AndroidEntryPoint
-import dev.msfjarvis.aps.R
-import dev.msfjarvis.aps.data.password.PasswordItem
-import dev.msfjarvis.aps.databinding.ActivityOreoAutofillFilterBinding
-import dev.msfjarvis.aps.util.autofill.AutofillMatcher
-import dev.msfjarvis.aps.util.autofill.AutofillPreferences
-import dev.msfjarvis.aps.util.autofill.DirectoryStructure
-import dev.msfjarvis.aps.util.extensions.viewBinding
-import dev.msfjarvis.aps.util.features.Feature
-import dev.msfjarvis.aps.util.features.Features
-import dev.msfjarvis.aps.util.viewmodel.FilterMode
-import dev.msfjarvis.aps.util.viewmodel.ListMode
-import dev.msfjarvis.aps.util.viewmodel.SearchMode
-import dev.msfjarvis.aps.util.viewmodel.SearchableRepositoryAdapter
-import dev.msfjarvis.aps.util.viewmodel.SearchableRepositoryViewModel
-import javax.inject.Inject
-import logcat.LogPriority.ERROR
-import logcat.logcat
-
-@TargetApi(26)
-@AndroidEntryPoint
-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 =
- "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_FORM_ORIGIN_WEB"
- private const val EXTRA_FORM_ORIGIN_APP =
- "dev.msfjarvis.aps.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,
- if (Build.VERSION.SDK_INT >= 31) {
- PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
- } else {
- PendingIntent.FLAG_CANCEL_CURRENT
- },
- )
- .intentSender
- }
- }
-
- @Inject lateinit var features: Features
- 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) {
- logcat(ERROR) { "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 -> {
- logcat(ERROR) {
- "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,
- lifecycleScope,
- ) { 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(
- if (features.isEnabled(Feature.EnablePGPainlessBackend)) {
- AutofillDecryptActivityV2.makeDecryptFileIntent(item.file, intent!!.extras!!, this)
- } else {
- AutofillDecryptActivity.makeDecryptFileIntent(item.file, intent!!.extras!!, this)
- }
- )
- }
-}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillPublisherChangedActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillPublisherChangedActivity.kt
deleted file mode 100644
index 85911815..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillPublisherChangedActivity.kt
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package dev.msfjarvis.aps.ui.autofill
-
-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.androidpasswordstore.autofillparser.FormOrigin
-import com.github.androidpasswordstore.autofillparser.computeCertificatesHash
-import com.github.michaelbull.result.onFailure
-import com.github.michaelbull.result.runCatching
-import dev.msfjarvis.aps.R
-import dev.msfjarvis.aps.databinding.ActivityOreoAutofillPublisherChangedBinding
-import dev.msfjarvis.aps.util.autofill.AutofillMatcher
-import dev.msfjarvis.aps.util.autofill.AutofillPublisherChangedException
-import dev.msfjarvis.aps.util.extensions.asLog
-import dev.msfjarvis.aps.util.extensions.viewBinding
-import logcat.LogPriority.ERROR
-import logcat.logcat
-
-@TargetApi(26)
-class AutofillPublisherChangedActivity : AppCompatActivity() {
-
- companion object {
-
- private const val EXTRA_APP_PACKAGE = "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_APP_PACKAGE"
- private const val EXTRA_FILL_RESPONSE_AFTER_RESET =
- "dev.msfjarvis.aps.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,
- if (Build.VERSION.SDK_INT >= 31) {
- PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
- } else {
- 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 {
- logcat(ERROR) { "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 =
- getString(
- R.string.oreo_autofill_warning_publisher_app_name,
- 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 ->
- logcat(ERROR) { e.asLog("Failed to retrieve package info for $appPackage") }
- finish()
- }
- }
-}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillSaveActivity.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillSaveActivity.kt
deleted file mode 100644
index a963836e..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillSaveActivity.kt
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package dev.msfjarvis.aps.ui.autofill
-
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.content.IntentSender
-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.androidpasswordstore.autofillparser.AutofillAction
-import com.github.androidpasswordstore.autofillparser.Credentials
-import com.github.androidpasswordstore.autofillparser.FormOrigin
-import dagger.hilt.android.AndroidEntryPoint
-import dev.msfjarvis.aps.data.repo.PasswordRepository
-import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivity
-import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivityV2
-import dev.msfjarvis.aps.util.autofill.AutofillMatcher
-import dev.msfjarvis.aps.util.autofill.AutofillPreferences
-import dev.msfjarvis.aps.util.autofill.AutofillResponseBuilder
-import dev.msfjarvis.aps.util.extensions.unsafeLazy
-import dev.msfjarvis.aps.util.features.Feature
-import dev.msfjarvis.aps.util.features.Features
-import java.io.File
-import javax.inject.Inject
-import logcat.LogPriority.ERROR
-import logcat.logcat
-
-@RequiresApi(26)
-@AndroidEntryPoint
-class AutofillSaveActivity : AppCompatActivity() {
-
- companion object {
-
- private const val EXTRA_FOLDER_NAME = "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_FOLDER_NAME"
- private const val EXTRA_PASSWORD = "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_PASSWORD"
- private const val EXTRA_NAME = "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_NAME"
- private const val EXTRA_SHOULD_MATCH_APP =
- "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_SHOULD_MATCH_APP"
- private const val EXTRA_SHOULD_MATCH_WEB =
- "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_SHOULD_MATCH_WEB"
- private const val EXTRA_GENERATE_PASSWORD =
- "dev.msfjarvis.aps.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 or PendingIntent.FLAG_IMMUTABLE
- )
- .intentSender
- }
- }
-
- private val formOrigin by unsafeLazy {
- 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
- }
- }
-
- @Inject lateinit var features: Features
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val repo = PasswordRepository.getRepositoryDirectory()
- val creationActivity =
- if (features.isEnabled(Feature.EnablePGPainlessBackend))
- PasswordCreationActivityV2::class.java
- else PasswordCreationActivity::class.java
- val saveIntent =
- Intent(this, creationActivity).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 {
- 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()
- }
- setResult(RESULT_OK, resultIntent)
- } else {
- setResult(RESULT_CANCELED)
- }
- finish()
- }
- .launch(saveIntent)
- }
-}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/PasswordViewHolder.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/PasswordViewHolder.kt
deleted file mode 100644
index eacd49c3..00000000
--- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/PasswordViewHolder.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-package dev.msfjarvis.aps.ui.autofill
-
-import android.view.View
-import android.widget.TextView
-import androidx.recyclerview.widget.RecyclerView
-import dev.msfjarvis.aps.R
-
-class PasswordViewHolder(view: View) : RecyclerView.ViewHolder(view) {
-
- val title: TextView = itemView.findViewById(R.id.title)
- val subtitle: TextView = itemView.findViewById(R.id.subtitle)
-}