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 0575c711e1..f80675c657 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 @@ -10,6 +10,7 @@ import com.x8bit.bitwarden.data.autofill.accessibility.manager.LauncherPackageNa import com.x8bit.bitwarden.data.autofill.accessibility.model.AccessibilityAction import com.x8bit.bitwarden.data.autofill.accessibility.parser.AccessibilityParser import com.x8bit.bitwarden.data.autofill.accessibility.util.fillTextField +import com.x8bit.bitwarden.data.autofill.accessibility.util.isSystemPackage import com.x8bit.bitwarden.data.autofill.accessibility.util.shouldSkipPackage import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData import com.x8bit.bitwarden.data.autofill.util.createAutofillSelectionIntent @@ -28,10 +29,16 @@ class BitwardenAccessibilityProcessorImpl( val rootNode = rootAccessibilityNodeInfo ?: return // Ignore the event when the phone is inactive if (!powerManager.isInteractive) return - // We skip if the package is not supported - if (rootNode.shouldSkipPackage) return - // We skip any package that is a launcher - if (launcherPackageNameManager.launcherPackages.any { it == rootNode.packageName }) return + // We skip if the system package + if (rootNode.isSystemPackage) return + // We skip any package that is a launcher or unsupported + if (rootNode.shouldSkipPackage || + launcherPackageNameManager.launcherPackages.any { it == rootNode.packageName } + ) { + // Clear the action since this event needs to be ignored completely + accessibilityAutofillManager.accessibilityAction = null + return + } // Only process the event if the tile was clicked val accessibilityAction = accessibilityAutofillManager.accessibilityAction ?: return diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/util/AccessibilityNodeInfoExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/util/AccessibilityNodeInfoExtensions.kt index 9cb8c39189..eb1eb1bfdc 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/util/AccessibilityNodeInfoExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/accessibility/util/AccessibilityNodeInfoExtensions.kt @@ -33,15 +33,19 @@ private val PACKAGE_NAME_BLOCK_LIST: List = listOf( val AccessibilityNodeInfo.shouldSkipPackage: Boolean get() { val packageName = this.packageName.takeUnless { it.isNullOrBlank() } ?: return true - if (packageName == PACKAGE_NAME_SYSTEM_UI) return true if (packageName.startsWith(prefix = PACKAGE_NAME_BITWARDEN_PREFIX)) return true if (packageName.contains(other = PACKAGE_NAME_LAUNCHER_PARTIAL, ignoreCase = true)) { return true } - if (PACKAGE_NAME_BLOCK_LIST.contains(packageName)) return true - return false + return PACKAGE_NAME_BLOCK_LIST.contains(packageName) } +/** + * Returns true if the event is from the system UI package. + */ +val AccessibilityNodeInfo.isSystemPackage: Boolean + get() = this.packageName == PACKAGE_NAME_SYSTEM_UI + /** * Fills the [AccessibilityNodeInfo] text field with the [value] provided. */ 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 86b462b302..19c682ecc6 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 @@ -14,6 +14,7 @@ import com.x8bit.bitwarden.data.autofill.accessibility.model.AccessibilityAction import com.x8bit.bitwarden.data.autofill.accessibility.model.FillableFields import com.x8bit.bitwarden.data.autofill.accessibility.parser.AccessibilityParser import com.x8bit.bitwarden.data.autofill.accessibility.util.fillTextField +import com.x8bit.bitwarden.data.autofill.accessibility.util.isSystemPackage import com.x8bit.bitwarden.data.autofill.accessibility.util.shouldSkipPackage import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData import com.x8bit.bitwarden.data.autofill.util.createAutofillSelectionIntent @@ -49,7 +50,10 @@ class BitwardenAccessibilityProcessorTest { @BeforeEach fun setup() { - mockkStatic(AccessibilityNodeInfo::shouldSkipPackage) + mockkStatic( + AccessibilityNodeInfo::isSystemPackage, + AccessibilityNodeInfo::shouldSkipPackage, + ) mockkStatic(::createAutofillSelectionIntent) mockkStatic(Toast::class) every { @@ -61,7 +65,10 @@ class BitwardenAccessibilityProcessorTest { @AfterEach fun tearDown() { - unmockkStatic(AccessibilityNodeInfo::shouldSkipPackage) + unmockkStatic( + AccessibilityNodeInfo::isSystemPackage, + AccessibilityNodeInfo::shouldSkipPackage, + ) unmockkStatic(::createAutofillSelectionIntent) unmockkStatic(Toast::class) } @@ -92,9 +99,9 @@ class BitwardenAccessibilityProcessorTest { } @Test - fun `processAccessibilityEvent with skippable package should return`() { + fun `processAccessibilityEvent with system package should return`() { val rootNode = mockk { - every { shouldSkipPackage } returns true + every { isSystemPackage } returns true } every { powerManager.isInteractive } returns true @@ -104,7 +111,28 @@ class BitwardenAccessibilityProcessorTest { verify(exactly = 1) { powerManager.isInteractive + rootNode.isSystemPackage + } + } + + @Test + fun `processAccessibilityEvent with skippable package should return`() { + val rootNode = mockk { + every { isSystemPackage } returns false + every { shouldSkipPackage } returns true + } + every { powerManager.isInteractive } returns true + every { accessibilityAutofillManager.accessibilityAction = null } just runs + + bitwardenAccessibilityProcessor.processAccessibilityEvent( + rootAccessibilityNodeInfo = rootNode, + ) + + verify(exactly = 1) { + powerManager.isInteractive + rootNode.isSystemPackage rootNode.shouldSkipPackage + accessibilityAutofillManager.accessibilityAction = null } } @@ -112,11 +140,13 @@ class BitwardenAccessibilityProcessorTest { fun `processAccessibilityEvent with launcher package should return`() { val testPackageName = "com.google.android.launcher" val rootNode = mockk { + every { isSystemPackage } returns false every { shouldSkipPackage } returns false every { packageName } returns testPackageName } every { launcherPackageNameManager.launcherPackages } returns listOf(testPackageName) every { powerManager.isInteractive } returns true + every { accessibilityAutofillManager.accessibilityAction = null } just runs bitwardenAccessibilityProcessor.processAccessibilityEvent( rootAccessibilityNodeInfo = rootNode, @@ -124,8 +154,10 @@ class BitwardenAccessibilityProcessorTest { verify(exactly = 1) { powerManager.isInteractive + rootNode.isSystemPackage rootNode.shouldSkipPackage launcherPackageNameManager.launcherPackages + accessibilityAutofillManager.accessibilityAction = null } } @@ -133,6 +165,7 @@ class BitwardenAccessibilityProcessorTest { fun `processAccessibilityEvent without accessibility action should return`() { val testPackageName = "com.android.chrome" val rootNode = mockk { + every { isSystemPackage } returns false every { shouldSkipPackage } returns false every { packageName } returns testPackageName } @@ -147,6 +180,7 @@ class BitwardenAccessibilityProcessorTest { verify(exactly = 1) { powerManager.isInteractive rootNode.shouldSkipPackage + rootNode.isSystemPackage launcherPackageNameManager.launcherPackages accessibilityAutofillManager.accessibilityAction } @@ -156,6 +190,7 @@ class BitwardenAccessibilityProcessorTest { fun `processAccessibilityEvent with AttemptParseUri and a invalid uri should show a toast`() { val testPackageName = "com.android.chrome" val rootNode = mockk { + every { isSystemPackage } returns false every { shouldSkipPackage } returns false every { packageName } returns testPackageName } @@ -173,6 +208,7 @@ class BitwardenAccessibilityProcessorTest { verify(exactly = 1) { powerManager.isInteractive + rootNode.isSystemPackage rootNode.shouldSkipPackage launcherPackageNameManager.launcherPackages accessibilityAutofillManager.accessibilityAction @@ -189,6 +225,7 @@ class BitwardenAccessibilityProcessorTest { fun `processAccessibilityEvent with AttemptParseUri and a valid uri should start the main activity`() { val testPackageName = "com.android.chrome" val rootNode = mockk { + every { isSystemPackage } returns false every { shouldSkipPackage } returns false every { packageName } returns testPackageName } @@ -214,6 +251,7 @@ class BitwardenAccessibilityProcessorTest { verify(exactly = 1) { powerManager.isInteractive + rootNode.isSystemPackage rootNode.shouldSkipPackage launcherPackageNameManager.launcherPackages accessibilityAutofillManager.accessibilityAction @@ -238,6 +276,7 @@ class BitwardenAccessibilityProcessorTest { val uri = mockk() val attemptFill = AccessibilityAction.AttemptFill(cipherView = cipherView, uri = uri) val rootNode = mockk { + every { isSystemPackage } returns false every { shouldSkipPackage } returns false every { packageName } returns testPackageName } @@ -252,6 +291,7 @@ class BitwardenAccessibilityProcessorTest { verify(exactly = 1) { powerManager.isInteractive + rootNode.isSystemPackage rootNode.shouldSkipPackage launcherPackageNameManager.launcherPackages accessibilityAutofillManager.accessibilityAction @@ -285,6 +325,7 @@ class BitwardenAccessibilityProcessorTest { val uri = mockk() val attemptFill = AccessibilityAction.AttemptFill(cipherView = cipherView, uri = uri) val rootNode = mockk { + every { isSystemPackage } returns false every { shouldSkipPackage } returns false every { packageName } returns testPackageName } @@ -302,6 +343,7 @@ class BitwardenAccessibilityProcessorTest { verify(exactly = 1) { powerManager.isInteractive + rootNode.isSystemPackage rootNode.shouldSkipPackage launcherPackageNameManager.launcherPackages accessibilityAutofillManager.accessibilityAction diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/util/AccessibilityNodeInfoExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/util/AccessibilityNodeInfoExtensionsTest.kt index 8f691e0972..b7fe959e58 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/util/AccessibilityNodeInfoExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/accessibility/util/AccessibilityNodeInfoExtensionsTest.kt @@ -105,6 +105,43 @@ class AccessibilityNodeInfoExtensionsTest { assertTrue(accessibilityNodeInfo.isEditText) } + @Test + fun `isSystemPackage when packageName is null should return false`() { + val accessibilityNodeInfo = mockk { + every { packageName } returns null + } + + assertFalse(accessibilityNodeInfo.isSystemPackage) + } + + @Test + fun `isSystemPackage when packageName is blank should return false`() { + val accessibilityNodeInfo = mockk { + every { packageName } returns "" + } + + assertFalse(accessibilityNodeInfo.isSystemPackage) + } + + @Suppress("MaxLineLength") + @Test + fun `isSystemPackage when packageName is populated with non system UI package should return false`() { + val accessibilityNodeInfo = mockk { + every { packageName } returns "com.x8bit.bitwarden.beta" + } + + assertFalse(accessibilityNodeInfo.isSystemPackage) + } + + @Test + fun `isSystemPackage when packageName is system UI package should return true`() { + val accessibilityNodeInfo = mockk { + every { packageName } returns "com.android.systemui" + } + + assertTrue(accessibilityNodeInfo.isSystemPackage) + } + @Test fun `shouldSkipPackage when packageName is null should return true`() { val accessibilityNodeInfo = mockk { @@ -123,15 +160,6 @@ class AccessibilityNodeInfoExtensionsTest { assertTrue(accessibilityNodeInfo.shouldSkipPackage) } - @Test - fun `shouldSkipPackage when packageName is system UI package should return true`() { - val accessibilityNodeInfo = mockk { - every { packageName } returns "com.android.systemui" - } - - assertTrue(accessibilityNodeInfo.shouldSkipPackage) - } - @Suppress("MaxLineLength") @Test fun `shouldSkipPackage when packageName is prefixed with bitwarden package should return true`() {