mirror of
https://github.com/bitwarden/android.git
synced 2024-12-18 07:11:51 +03:00
PM-12397: Clear accessibility action when on launcher or Bitwarden App (#3947)
This commit is contained in:
parent
5667a1cfd0
commit
d0c2bb5b7e
4 changed files with 101 additions and 20 deletions
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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`() {
|
||||||
|
|
Loading…
Reference in a new issue