From 0967234ad8edf9dd40767e28ea89cb1b52cbb2cf Mon Sep 17 00:00:00 2001 From: David Perez Date: Wed, 13 Nov 2024 15:38:08 -0600 Subject: [PATCH] PM-14411: Autofill logic to work better with QuickTile (#4300) --- .../accessibility/model/FillableFields.kt | 4 +- .../BitwardenAccessibilityProcessorImpl.kt | 15 +++-- .../platform/manager/exit/ExitManagerImpl.kt | 4 +- .../itemlisting/VaultItemListingViewModel.kt | 2 +- .../BitwardenAccessibilityProcessorTest.kt | 64 +++++++++++++++++-- .../VaultItemListingViewModelTest.kt | 17 +++++ 6 files changed, 91 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/model/FillableFields.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/model/FillableFields.kt index 24aeebcc7..70d4827a7 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/model/FillableFields.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/model/FillableFields.kt @@ -8,4 +8,6 @@ import android.view.accessibility.AccessibilityNodeInfo data class FillableFields( val usernameField: AccessibilityNodeInfo?, val passwordFields: List, -) +) { + val hasFields: Boolean = usernameField != null && passwordFields.isNotEmpty() +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorImpl.kt index 40cb33825..fafcbcea5 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorImpl.kt @@ -35,12 +35,10 @@ class BitwardenAccessibilityProcessorImpl( if (!powerManager.isInteractive) return // We skip if the system package if (eventNode.isSystemPackage) return - // We skip any package that is a launcher or unsupported - if (eventNode.shouldSkipPackage || - launcherPackageNameManager.launcherPackages.any { it == eventNode.packageName } - ) { - // Clear the action since this event needs to be ignored completely - accessibilityAutofillManager.accessibilityAction = null + // We skip any package that is unsupported + if (eventNode.shouldSkipPackage) return + // We skip any package that is a launcher + if (launcherPackageNameManager.launcherPackages.any { it == eventNode.packageName }) { return } @@ -65,6 +63,11 @@ class BitwardenAccessibilityProcessorImpl( private fun handleAttemptParseUri(rootNode: AccessibilityNodeInfo) { accessibilityParser .parseForUriOrPackageName(rootNode = rootNode) + ?.takeIf { + accessibilityParser + .parseForFillableFields(rootNode = rootNode, uri = it) + .hasFields + } ?.let { uri -> context.startActivity( createAutofillSelectionIntent( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/exit/ExitManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/exit/ExitManagerImpl.kt index 25e3afeab..f8ea1be8b 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/exit/ExitManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/manager/exit/ExitManagerImpl.kt @@ -8,9 +8,9 @@ import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage */ @OmitFromCoverage class ExitManagerImpl( - val activity: Activity, + private val activity: Activity, ) : ExitManager { override fun exitApplication() { - activity.finish() + activity.finishAndRemoveTask() } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt index 4af2169e7..d07c744e4 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt @@ -850,7 +850,7 @@ class VaultItemListingViewModel @Inject constructor( private fun handleBackClick() { sendEvent( - event = if (state.isTotp) { + event = if (state.isTotp || state.isAutofill) { VaultItemListingEvent.ExitApp } else { VaultItemListingEvent.NavigateBack diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorTest.kt index 40217beb8..c7ed53e8c 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorTest.kt @@ -129,7 +129,6 @@ class BitwardenAccessibilityProcessorTest { every { source } returns node } every { powerManager.isInteractive } returns true - every { accessibilityAutofillManager.accessibilityAction = null } just runs bitwardenAccessibilityProcessor.processAccessibilityEvent(event = event) { null } @@ -137,7 +136,6 @@ class BitwardenAccessibilityProcessorTest { powerManager.isInteractive node.isSystemPackage node.shouldSkipPackage - accessibilityAutofillManager.accessibilityAction = null } } @@ -154,7 +152,6 @@ class BitwardenAccessibilityProcessorTest { } every { launcherPackageNameManager.launcherPackages } returns listOf(testPackageName) every { powerManager.isInteractive } returns true - every { accessibilityAutofillManager.accessibilityAction = null } just runs bitwardenAccessibilityProcessor.processAccessibilityEvent(event = event) { null } @@ -163,7 +160,6 @@ class BitwardenAccessibilityProcessorTest { node.isSystemPackage node.shouldSkipPackage launcherPackageNameManager.launcherPackages - accessibilityAutofillManager.accessibilityAction = null } } @@ -266,6 +262,59 @@ class BitwardenAccessibilityProcessorTest { } } + @Suppress("MaxLineLength") + @Test + fun `processAccessibilityEvent with AttemptParseUri and a valid uri but no fields to fill display toast`() { + val testPackageName = "com.android.chrome" + val rootNode = mockk { + every { packageName } returns testPackageName + } + val node = mockk { + every { isSystemPackage } returns false + every { shouldSkipPackage } returns false + every { packageName } returns testPackageName + } + val event = mockk { + every { source } returns node + every { packageName } returns testPackageName + } + every { powerManager.isInteractive } returns true + every { launcherPackageNameManager.launcherPackages } returns emptyList() + every { + accessibilityAutofillManager.accessibilityAction + } returns AccessibilityAction.AttemptParseUri + every { accessibilityAutofillManager.accessibilityAction = null } just runs + every { + createAutofillSelectionIntent( + context = context, + framework = AutofillSelectionData.Framework.ACCESSIBILITY, + type = AutofillSelectionData.Type.LOGIN, + uri = any(), + ) + } returns mockk() + val uri = mockk() + every { accessibilityParser.parseForUriOrPackageName(rootNode = node) } returns uri + every { + accessibilityParser.parseForFillableFields(rootNode = node, uri = uri) + } returns mockk { every { hasFields } returns false } + + bitwardenAccessibilityProcessor.processAccessibilityEvent(event = event) { rootNode } + + verify(exactly = 1) { + powerManager.isInteractive + node.isSystemPackage + node.shouldSkipPackage + launcherPackageNameManager.launcherPackages + accessibilityAutofillManager.accessibilityAction + accessibilityAutofillManager.accessibilityAction = null + accessibilityParser.parseForUriOrPackageName(rootNode = node) + accessibilityParser.parseForFillableFields(rootNode = node, uri = uri) + Toast + .makeText(context, R.string.autofill_tile_uri_not_found, Toast.LENGTH_LONG) + .show() + } + } + @Suppress("MaxLineLength") @Test fun `processAccessibilityEvent with AttemptParseUri and a valid uri should start the main activity`() { @@ -296,7 +345,11 @@ class BitwardenAccessibilityProcessorTest { uri = any(), ) } returns mockk() - every { accessibilityParser.parseForUriOrPackageName(rootNode = node) } returns mockk() + val uri = mockk() + every { accessibilityParser.parseForUriOrPackageName(rootNode = node) } returns uri + every { + accessibilityParser.parseForFillableFields(rootNode = node, uri = uri) + } returns mockk { every { hasFields } returns true } bitwardenAccessibilityProcessor.processAccessibilityEvent(event = event) { rootNode } @@ -308,6 +361,7 @@ class BitwardenAccessibilityProcessorTest { accessibilityAutofillManager.accessibilityAction accessibilityAutofillManager.accessibilityAction = null accessibilityParser.parseForUriOrPackageName(rootNode = node) + accessibilityParser.parseForFillableFields(rootNode = node, uri = uri) createAutofillSelectionIntent( context = context, framework = AutofillSelectionData.Framework.ACCESSIBILITY, diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt index 6035771f0..a7769df7a 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt @@ -281,6 +281,23 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { } } + @Test + fun `BackClick with AutofillSelectionData should emit ExitApp`() = runTest { + specialCircumstanceManager.specialCircumstance = SpecialCircumstance.AutofillSelection( + autofillSelectionData = AutofillSelectionData( + framework = AutofillSelectionData.Framework.ACCESSIBILITY, + type = AutofillSelectionData.Type.LOGIN, + uri = null, + ), + shouldFinishWhenComplete = false, + ) + val viewModel = createVaultItemListingViewModel() + viewModel.eventFlow.test { + viewModel.trySendAction(VaultItemListingsAction.BackClick) + assertEquals(VaultItemListingEvent.ExitApp, awaitItem()) + } + } + @Test fun `BackClick should emit NavigateBack`() = runTest { val viewModel = createVaultItemListingViewModel()