PM-12397: Clear accessibility action when on launcher or Bitwarden App (#3947)

This commit is contained in:
David Perez 2024-09-20 09:44:30 -05:00 committed by GitHub
parent 5667a1cfd0
commit d0c2bb5b7e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 101 additions and 20 deletions

View file

@ -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.model.AccessibilityAction
import com.x8bit.bitwarden.data.autofill.accessibility.parser.AccessibilityParser 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.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.accessibility.util.shouldSkipPackage
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
import com.x8bit.bitwarden.data.autofill.util.createAutofillSelectionIntent import com.x8bit.bitwarden.data.autofill.util.createAutofillSelectionIntent
@ -28,10 +29,16 @@ class BitwardenAccessibilityProcessorImpl(
val rootNode = rootAccessibilityNodeInfo ?: return val rootNode = rootAccessibilityNodeInfo ?: return
// Ignore the event when the phone is inactive // Ignore the event when the phone is inactive
if (!powerManager.isInteractive) return if (!powerManager.isInteractive) return
// We skip if the package is not supported // We skip if the system package
if (rootNode.shouldSkipPackage) return if (rootNode.isSystemPackage) return
// We skip any package that is a launcher // We skip any package that is a launcher or unsupported
if (launcherPackageNameManager.launcherPackages.any { it == rootNode.packageName }) return 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 // Only process the event if the tile was clicked
val accessibilityAction = accessibilityAutofillManager.accessibilityAction ?: return val accessibilityAction = accessibilityAutofillManager.accessibilityAction ?: return

View file

@ -33,15 +33,19 @@ private val PACKAGE_NAME_BLOCK_LIST: List<String> = listOf(
val AccessibilityNodeInfo.shouldSkipPackage: Boolean val AccessibilityNodeInfo.shouldSkipPackage: Boolean
get() { get() {
val packageName = this.packageName.takeUnless { it.isNullOrBlank() } ?: return true 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.startsWith(prefix = PACKAGE_NAME_BITWARDEN_PREFIX)) return true
if (packageName.contains(other = PACKAGE_NAME_LAUNCHER_PARTIAL, ignoreCase = true)) { if (packageName.contains(other = PACKAGE_NAME_LAUNCHER_PARTIAL, ignoreCase = true)) {
return true return true
} }
if (PACKAGE_NAME_BLOCK_LIST.contains(packageName)) return true return PACKAGE_NAME_BLOCK_LIST.contains(packageName)
return false
} }
/**
* 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. * Fills the [AccessibilityNodeInfo] text field with the [value] provided.
*/ */

View file

@ -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.model.FillableFields
import com.x8bit.bitwarden.data.autofill.accessibility.parser.AccessibilityParser 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.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.accessibility.util.shouldSkipPackage
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
import com.x8bit.bitwarden.data.autofill.util.createAutofillSelectionIntent import com.x8bit.bitwarden.data.autofill.util.createAutofillSelectionIntent
@ -49,7 +50,10 @@ class BitwardenAccessibilityProcessorTest {
@BeforeEach @BeforeEach
fun setup() { fun setup() {
mockkStatic(AccessibilityNodeInfo::shouldSkipPackage) mockkStatic(
AccessibilityNodeInfo::isSystemPackage,
AccessibilityNodeInfo::shouldSkipPackage,
)
mockkStatic(::createAutofillSelectionIntent) mockkStatic(::createAutofillSelectionIntent)
mockkStatic(Toast::class) mockkStatic(Toast::class)
every { every {
@ -61,7 +65,10 @@ class BitwardenAccessibilityProcessorTest {
@AfterEach @AfterEach
fun tearDown() { fun tearDown() {
unmockkStatic(AccessibilityNodeInfo::shouldSkipPackage) unmockkStatic(
AccessibilityNodeInfo::isSystemPackage,
AccessibilityNodeInfo::shouldSkipPackage,
)
unmockkStatic(::createAutofillSelectionIntent) unmockkStatic(::createAutofillSelectionIntent)
unmockkStatic(Toast::class) unmockkStatic(Toast::class)
} }
@ -92,9 +99,9 @@ class BitwardenAccessibilityProcessorTest {
} }
@Test @Test
fun `processAccessibilityEvent with skippable package should return`() { fun `processAccessibilityEvent with system package should return`() {
val rootNode = mockk<AccessibilityNodeInfo> { val rootNode = mockk<AccessibilityNodeInfo> {
every { shouldSkipPackage } returns true every { isSystemPackage } returns true
} }
every { powerManager.isInteractive } returns true every { powerManager.isInteractive } returns true
@ -104,7 +111,28 @@ class BitwardenAccessibilityProcessorTest {
verify(exactly = 1) { verify(exactly = 1) {
powerManager.isInteractive powerManager.isInteractive
rootNode.isSystemPackage
}
}
@Test
fun `processAccessibilityEvent with skippable package should return`() {
val rootNode = mockk<AccessibilityNodeInfo> {
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 rootNode.shouldSkipPackage
accessibilityAutofillManager.accessibilityAction = null
} }
} }
@ -112,11 +140,13 @@ class BitwardenAccessibilityProcessorTest {
fun `processAccessibilityEvent with launcher package should return`() { fun `processAccessibilityEvent with launcher package should return`() {
val testPackageName = "com.google.android.launcher" val testPackageName = "com.google.android.launcher"
val rootNode = mockk<AccessibilityNodeInfo> { val rootNode = mockk<AccessibilityNodeInfo> {
every { isSystemPackage } returns false
every { shouldSkipPackage } returns false every { shouldSkipPackage } returns false
every { packageName } returns testPackageName every { packageName } returns testPackageName
} }
every { launcherPackageNameManager.launcherPackages } returns listOf(testPackageName) every { launcherPackageNameManager.launcherPackages } returns listOf(testPackageName)
every { powerManager.isInteractive } returns true every { powerManager.isInteractive } returns true
every { accessibilityAutofillManager.accessibilityAction = null } just runs
bitwardenAccessibilityProcessor.processAccessibilityEvent( bitwardenAccessibilityProcessor.processAccessibilityEvent(
rootAccessibilityNodeInfo = rootNode, rootAccessibilityNodeInfo = rootNode,
@ -124,8 +154,10 @@ class BitwardenAccessibilityProcessorTest {
verify(exactly = 1) { verify(exactly = 1) {
powerManager.isInteractive powerManager.isInteractive
rootNode.isSystemPackage
rootNode.shouldSkipPackage rootNode.shouldSkipPackage
launcherPackageNameManager.launcherPackages launcherPackageNameManager.launcherPackages
accessibilityAutofillManager.accessibilityAction = null
} }
} }
@ -133,6 +165,7 @@ class BitwardenAccessibilityProcessorTest {
fun `processAccessibilityEvent without accessibility action should return`() { fun `processAccessibilityEvent without accessibility action should return`() {
val testPackageName = "com.android.chrome" val testPackageName = "com.android.chrome"
val rootNode = mockk<AccessibilityNodeInfo> { val rootNode = mockk<AccessibilityNodeInfo> {
every { isSystemPackage } returns false
every { shouldSkipPackage } returns false every { shouldSkipPackage } returns false
every { packageName } returns testPackageName every { packageName } returns testPackageName
} }
@ -147,6 +180,7 @@ class BitwardenAccessibilityProcessorTest {
verify(exactly = 1) { verify(exactly = 1) {
powerManager.isInteractive powerManager.isInteractive
rootNode.shouldSkipPackage rootNode.shouldSkipPackage
rootNode.isSystemPackage
launcherPackageNameManager.launcherPackages launcherPackageNameManager.launcherPackages
accessibilityAutofillManager.accessibilityAction accessibilityAutofillManager.accessibilityAction
} }
@ -156,6 +190,7 @@ class BitwardenAccessibilityProcessorTest {
fun `processAccessibilityEvent with AttemptParseUri and a invalid uri should show a toast`() { fun `processAccessibilityEvent with AttemptParseUri and a invalid uri should show a toast`() {
val testPackageName = "com.android.chrome" val testPackageName = "com.android.chrome"
val rootNode = mockk<AccessibilityNodeInfo> { val rootNode = mockk<AccessibilityNodeInfo> {
every { isSystemPackage } returns false
every { shouldSkipPackage } returns false every { shouldSkipPackage } returns false
every { packageName } returns testPackageName every { packageName } returns testPackageName
} }
@ -173,6 +208,7 @@ class BitwardenAccessibilityProcessorTest {
verify(exactly = 1) { verify(exactly = 1) {
powerManager.isInteractive powerManager.isInteractive
rootNode.isSystemPackage
rootNode.shouldSkipPackage rootNode.shouldSkipPackage
launcherPackageNameManager.launcherPackages launcherPackageNameManager.launcherPackages
accessibilityAutofillManager.accessibilityAction accessibilityAutofillManager.accessibilityAction
@ -189,6 +225,7 @@ class BitwardenAccessibilityProcessorTest {
fun `processAccessibilityEvent with AttemptParseUri and a valid uri should start the main activity`() { fun `processAccessibilityEvent with AttemptParseUri and a valid uri should start the main activity`() {
val testPackageName = "com.android.chrome" val testPackageName = "com.android.chrome"
val rootNode = mockk<AccessibilityNodeInfo> { val rootNode = mockk<AccessibilityNodeInfo> {
every { isSystemPackage } returns false
every { shouldSkipPackage } returns false every { shouldSkipPackage } returns false
every { packageName } returns testPackageName every { packageName } returns testPackageName
} }
@ -214,6 +251,7 @@ class BitwardenAccessibilityProcessorTest {
verify(exactly = 1) { verify(exactly = 1) {
powerManager.isInteractive powerManager.isInteractive
rootNode.isSystemPackage
rootNode.shouldSkipPackage rootNode.shouldSkipPackage
launcherPackageNameManager.launcherPackages launcherPackageNameManager.launcherPackages
accessibilityAutofillManager.accessibilityAction accessibilityAutofillManager.accessibilityAction
@ -238,6 +276,7 @@ class BitwardenAccessibilityProcessorTest {
val uri = mockk<Uri>() val uri = mockk<Uri>()
val attemptFill = AccessibilityAction.AttemptFill(cipherView = cipherView, uri = uri) val attemptFill = AccessibilityAction.AttemptFill(cipherView = cipherView, uri = uri)
val rootNode = mockk<AccessibilityNodeInfo> { val rootNode = mockk<AccessibilityNodeInfo> {
every { isSystemPackage } returns false
every { shouldSkipPackage } returns false every { shouldSkipPackage } returns false
every { packageName } returns testPackageName every { packageName } returns testPackageName
} }
@ -252,6 +291,7 @@ class BitwardenAccessibilityProcessorTest {
verify(exactly = 1) { verify(exactly = 1) {
powerManager.isInteractive powerManager.isInteractive
rootNode.isSystemPackage
rootNode.shouldSkipPackage rootNode.shouldSkipPackage
launcherPackageNameManager.launcherPackages launcherPackageNameManager.launcherPackages
accessibilityAutofillManager.accessibilityAction accessibilityAutofillManager.accessibilityAction
@ -285,6 +325,7 @@ class BitwardenAccessibilityProcessorTest {
val uri = mockk<Uri>() val uri = mockk<Uri>()
val attemptFill = AccessibilityAction.AttemptFill(cipherView = cipherView, uri = uri) val attemptFill = AccessibilityAction.AttemptFill(cipherView = cipherView, uri = uri)
val rootNode = mockk<AccessibilityNodeInfo> { val rootNode = mockk<AccessibilityNodeInfo> {
every { isSystemPackage } returns false
every { shouldSkipPackage } returns false every { shouldSkipPackage } returns false
every { packageName } returns testPackageName every { packageName } returns testPackageName
} }
@ -302,6 +343,7 @@ class BitwardenAccessibilityProcessorTest {
verify(exactly = 1) { verify(exactly = 1) {
powerManager.isInteractive powerManager.isInteractive
rootNode.isSystemPackage
rootNode.shouldSkipPackage rootNode.shouldSkipPackage
launcherPackageNameManager.launcherPackages launcherPackageNameManager.launcherPackages
accessibilityAutofillManager.accessibilityAction accessibilityAutofillManager.accessibilityAction

View file

@ -105,6 +105,43 @@ class AccessibilityNodeInfoExtensionsTest {
assertTrue(accessibilityNodeInfo.isEditText) assertTrue(accessibilityNodeInfo.isEditText)
} }
@Test
fun `isSystemPackage when packageName is null should return false`() {
val accessibilityNodeInfo = mockk<AccessibilityNodeInfo> {
every { packageName } returns null
}
assertFalse(accessibilityNodeInfo.isSystemPackage)
}
@Test
fun `isSystemPackage when packageName is blank should return false`() {
val accessibilityNodeInfo = mockk<AccessibilityNodeInfo> {
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<AccessibilityNodeInfo> {
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<AccessibilityNodeInfo> {
every { packageName } returns "com.android.systemui"
}
assertTrue(accessibilityNodeInfo.isSystemPackage)
}
@Test @Test
fun `shouldSkipPackage when packageName is null should return true`() { fun `shouldSkipPackage when packageName is null should return true`() {
val accessibilityNodeInfo = mockk<AccessibilityNodeInfo> { val accessibilityNodeInfo = mockk<AccessibilityNodeInfo> {
@ -123,15 +160,6 @@ class AccessibilityNodeInfoExtensionsTest {
assertTrue(accessibilityNodeInfo.shouldSkipPackage) assertTrue(accessibilityNodeInfo.shouldSkipPackage)
} }
@Test
fun `shouldSkipPackage when packageName is system UI package should return true`() {
val accessibilityNodeInfo = mockk<AccessibilityNodeInfo> {
every { packageName } returns "com.android.systemui"
}
assertTrue(accessibilityNodeInfo.shouldSkipPackage)
}
@Suppress("MaxLineLength") @Suppress("MaxLineLength")
@Test @Test
fun `shouldSkipPackage when packageName is prefixed with bitwarden package should return true`() { fun `shouldSkipPackage when packageName is prefixed with bitwarden package should return true`() {