Skip to content

Commit 496f714

Browse files
PM-16705: Improve the node validation logic
1 parent d5c0412 commit 496f714

File tree

2 files changed

+67
-59
lines changed

2 files changed

+67
-59
lines changed

app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorImpl.kt

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,13 @@ class BitwardenAccessibilityProcessorImpl(
3333
) {
3434
// Only process the event if the tile was clicked
3535
val accessibilityAction = accessibilityAutofillManager.accessibilityAction ?: return
36-
val eventNode = event.source ?: return
3736

3837
// Prevent clearing the action until we receive a processable event in case unprocessable
3938
// events are still being received from the device. This can happen on slower devices or if
4039
// screen transitions are still being performed.
41-
if (!eventNode.shouldProcessEvent(rootAccessibilityNodeInfoProvider)) {
42-
return
43-
}
40+
val eventNode = event
41+
.getValidNode(rootAccessibilityNodeInfoProvider = rootAccessibilityNodeInfoProvider)
42+
?: return
4443

4544
// Clear the action since we are now acting on a supported node.
4645
accessibilityAutofillManager.accessibilityAction = null
@@ -53,35 +52,43 @@ class BitwardenAccessibilityProcessorImpl(
5352
}
5453
}
5554

56-
private fun AccessibilityNodeInfo.shouldProcessEvent(
55+
private fun AccessibilityEvent.getValidNode(
5756
rootAccessibilityNodeInfoProvider: () -> AccessibilityNodeInfo?,
58-
): Boolean {
57+
): AccessibilityNodeInfo? {
58+
val eventNode = this
59+
.source
60+
?: run {
61+
Timber.w("Accessibility event source is null, attempting root node")
62+
// We only call for the root node once, after verifying that the there is an action
63+
// to be filled. This is because it is a significant performance hit.
64+
// Additionally, we do not use the root node if it does not have the same package
65+
// as the triggering event.
66+
rootAccessibilityNodeInfoProvider()?.takeIf { it.packageName == this.packageName }
67+
}
68+
?: run {
69+
Timber.w("Root node was also null, skipping this event")
70+
return null
71+
}
72+
5973
// Ignore the event when the phone is inactive.
60-
if (!powerManager.isInteractive) return false
74+
if (!powerManager.isInteractive) return null
6175
// We skip if the system package.
62-
if (this.isSystemPackage) {
63-
Timber.d("Skipping autofill for system package $packageName.")
64-
return false
76+
if (eventNode.isSystemPackage) {
77+
Timber.d("Skipping autofill for system package ${eventNode.packageName}.")
78+
return null
6579
}
6680
// We skip any package that is explicitly blocked.
67-
if (this.shouldSkipPackage) {
68-
Timber.d("Skipping autofill on block-listed package $packageName.")
69-
return false
81+
if (eventNode.shouldSkipPackage) {
82+
Timber.d("Skipping autofill on block-listed package ${eventNode.packageName}.")
83+
return null
7084
}
7185
// We skip any package that is a launcher.
72-
if (launcherPackageNameManager.launcherPackages.any { it == this.packageName }) {
73-
Timber.d("Skipping autofill on launcher package $packageName.")
74-
return false
75-
}
76-
77-
// We only call for the root node once, after all other checks have passed, because it is a
78-
// significant performance hit.
79-
if (rootAccessibilityNodeInfoProvider()?.packageName != this.packageName) {
80-
Timber.d("Skipping autofill due to package name mismatch.")
81-
return false
86+
if (launcherPackageNameManager.launcherPackages.any { it == eventNode.packageName }) {
87+
Timber.d("Skipping autofill on launcher package ${eventNode.packageName}.")
88+
return null
8289
}
8390

84-
return true
91+
return eventNode
8592
}
8693

8794
private fun handleAttemptParseUri(rootNode: AccessibilityNodeInfo) {

app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorTest.kt

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class BitwardenAccessibilityProcessorTest {
7777
}
7878

7979
@Test
80-
fun `processAccessibilityEvent with null event source should return`() {
80+
fun `processAccessibilityEvent with null event source and root node should return`() {
8181
val event = mockk<AccessibilityEvent> {
8282
every { source } returns null
8383
}
@@ -89,6 +89,41 @@ class BitwardenAccessibilityProcessorTest {
8989
}
9090
}
9191

92+
@Test
93+
fun `processAccessibilityEvent with null event source and invalid root node should return`() {
94+
val event = mockk<AccessibilityEvent> {
95+
every { source } returns null
96+
every { packageName } returns "event_package"
97+
}
98+
val rootNode = mockk<AccessibilityNodeInfo> {
99+
every { packageName } returns "other_package"
100+
}
101+
102+
bitwardenAccessibilityProcessor.processAccessibilityEvent(event = event) { rootNode }
103+
104+
verify(exactly = 0) {
105+
powerManager.isInteractive
106+
}
107+
}
108+
109+
@Test
110+
fun `processAccessibilityEvent with null event source and valid root node should continue`() {
111+
val event = mockk<AccessibilityEvent> {
112+
every { source } returns null
113+
every { packageName } returns "package"
114+
}
115+
val rootNode = mockk<AccessibilityNodeInfo> {
116+
every { packageName } returns "package"
117+
}
118+
every { powerManager.isInteractive } returns false
119+
120+
bitwardenAccessibilityProcessor.processAccessibilityEvent(event = event) { rootNode }
121+
122+
verify(exactly = 1) {
123+
powerManager.isInteractive
124+
}
125+
}
126+
92127
@Test
93128
fun `processAccessibilityEvent with powerManager not interactive should return`() {
94129
val event = mockk<AccessibilityEvent> {
@@ -184,40 +219,6 @@ class BitwardenAccessibilityProcessorTest {
184219
}
185220
}
186221

187-
@Test
188-
fun `processAccessibilityEvent with mismatched package name should return`() {
189-
val testPackageName = "com.android.chrome"
190-
val rootNode = mockk<AccessibilityNodeInfo> {
191-
every { packageName } returns "other.package.name"
192-
}
193-
val node = mockk<AccessibilityNodeInfo> {
194-
every { isSystemPackage } returns false
195-
every { shouldSkipPackage } returns false
196-
every { packageName } returns testPackageName
197-
}
198-
val event = mockk<AccessibilityEvent> {
199-
every { source } returns node
200-
every { packageName } returns testPackageName
201-
}
202-
every { powerManager.isInteractive } returns true
203-
every { launcherPackageNameManager.launcherPackages } returns emptyList()
204-
every {
205-
accessibilityAutofillManager.accessibilityAction
206-
} returns AccessibilityAction.AttemptParseUri
207-
every { accessibilityAutofillManager.accessibilityAction = null } just runs
208-
every { accessibilityParser.parseForUriOrPackageName(rootNode = node) } returns null
209-
210-
bitwardenAccessibilityProcessor.processAccessibilityEvent(event = event) { rootNode }
211-
212-
verify(exactly = 1) {
213-
powerManager.isInteractive
214-
node.isSystemPackage
215-
node.shouldSkipPackage
216-
launcherPackageNameManager.launcherPackages
217-
accessibilityAutofillManager.accessibilityAction
218-
}
219-
}
220-
221222
@Test
222223
fun `processAccessibilityEvent with AttemptParseUri and a invalid uri should show a toast`() {
223224
val testPackageName = "com.android.chrome"

0 commit comments

Comments
 (0)