-
Notifications
You must be signed in to change notification settings - Fork 821
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PM-11487: Initial accessibility service and processor for handling au…
…tofill (#3906)
- Loading branch information
1 parent
f544ccc
commit 4c1d55e
Showing
11 changed files
with
643 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
...ain/java/com/x8bit/bitwarden/data/autofill/accessibility/BitwardenAccessibilityService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package com.x8bit.bitwarden.data.autofill.accessibility | ||
|
||
import android.accessibilityservice.AccessibilityService | ||
import android.view.accessibility.AccessibilityEvent | ||
import androidx.annotation.Keep | ||
import com.x8bit.bitwarden.data.autofill.accessibility.processor.BitwardenAccessibilityProcessor | ||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage | ||
import com.x8bit.bitwarden.data.tiles.BitwardenAutofillTileService | ||
import dagger.hilt.android.AndroidEntryPoint | ||
import javax.inject.Inject | ||
|
||
/** | ||
* The [AccessibilityService] implementation for the app. This is not used in the traditional | ||
* way, we use the [BitwardenAutofillTileService] to invoke this service in order to provide an | ||
* autofill fallback mechanism. | ||
*/ | ||
@Keep | ||
@OmitFromCoverage | ||
@AndroidEntryPoint | ||
class BitwardenAccessibilityService : AccessibilityService() { | ||
@Inject | ||
lateinit var processor: BitwardenAccessibilityProcessor | ||
|
||
override fun onAccessibilityEvent(event: AccessibilityEvent) { | ||
if (rootInActiveWindow?.packageName != event.packageName) return | ||
processor.processAccessibilityEvent(rootAccessibilityNodeInfo = rootInActiveWindow) | ||
} | ||
|
||
override fun onInterrupt() = Unit | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
.../x8bit/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessor.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.x8bit.bitwarden.data.autofill.accessibility.processor | ||
|
||
import android.view.accessibility.AccessibilityNodeInfo | ||
|
||
/** | ||
* A class to handle accessibility event processing. This only includes fill requests. | ||
*/ | ||
interface BitwardenAccessibilityProcessor { | ||
/** | ||
* Processes the [AccessibilityNodeInfo] for autofill options. | ||
*/ | ||
fun processAccessibilityEvent(rootAccessibilityNodeInfo: AccessibilityNodeInfo?) | ||
} |
86 changes: 86 additions & 0 deletions
86
...it/bitwarden/data/autofill/accessibility/processor/BitwardenAccessibilityProcessorImpl.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package com.x8bit.bitwarden.data.autofill.accessibility.processor | ||
|
||
import android.content.Context | ||
import android.os.PowerManager | ||
import android.view.accessibility.AccessibilityNodeInfo | ||
import android.widget.Toast | ||
import com.x8bit.bitwarden.R | ||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManager | ||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.LauncherPackageNameManager | ||
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.shouldSkipPackage | ||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData | ||
import com.x8bit.bitwarden.data.autofill.util.createAutofillSelectionIntent | ||
|
||
/** | ||
* The default implementation of the [BitwardenAccessibilityProcessor]. | ||
*/ | ||
class BitwardenAccessibilityProcessorImpl( | ||
private val context: Context, | ||
private val accessibilityParser: AccessibilityParser, | ||
private val accessibilityAutofillManager: AccessibilityAutofillManager, | ||
private val launcherPackageNameManager: LauncherPackageNameManager, | ||
private val powerManager: PowerManager, | ||
) : BitwardenAccessibilityProcessor { | ||
override fun processAccessibilityEvent(rootAccessibilityNodeInfo: AccessibilityNodeInfo?) { | ||
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 | ||
|
||
// Only process the event if the tile was clicked | ||
val accessibilityAction = accessibilityAutofillManager.accessibilityAction ?: return | ||
accessibilityAutofillManager.accessibilityAction = null | ||
|
||
when (accessibilityAction) { | ||
is AccessibilityAction.AttemptFill -> { | ||
handleAttemptFill(rootNode = rootNode, attemptFill = accessibilityAction) | ||
} | ||
|
||
AccessibilityAction.AttemptParseUri -> handleAttemptParseUri(rootNode = rootNode) | ||
} | ||
} | ||
|
||
private fun handleAttemptParseUri(rootNode: AccessibilityNodeInfo) { | ||
accessibilityParser | ||
.parseForUriOrPackageName(rootNode = rootNode) | ||
?.let { uri -> | ||
context.startActivity( | ||
createAutofillSelectionIntent( | ||
context = context, | ||
framework = AutofillSelectionData.Framework.ACCESSIBILITY, | ||
type = AutofillSelectionData.Type.LOGIN, | ||
uri = uri.toString(), | ||
), | ||
) | ||
} | ||
?: run { | ||
Toast | ||
.makeText( | ||
context, | ||
R.string.autofill_tile_uri_not_found, | ||
Toast.LENGTH_LONG, | ||
) | ||
.show() | ||
} | ||
} | ||
|
||
private fun handleAttemptFill( | ||
rootNode: AccessibilityNodeInfo, | ||
attemptFill: AccessibilityAction.AttemptFill, | ||
) { | ||
val loginView = attemptFill.cipherView.login ?: return | ||
val fields = accessibilityParser.parseForFillableFields(rootNode = rootNode) | ||
fields.usernameFields.forEach { usernameField -> | ||
usernameField.fillTextField(value = loginView.username) | ||
} | ||
fields.passwordFields.forEach { passwordField -> | ||
passwordField.fillTextField(value = loginView.password) | ||
} | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
...a/com/x8bit/bitwarden/data/autofill/accessibility/util/AccessibilityNodeInfoExtensions.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package com.x8bit.bitwarden.data.autofill.accessibility.util | ||
|
||
import android.view.accessibility.AccessibilityNodeInfo | ||
import androidx.core.os.bundleOf | ||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage | ||
|
||
private const val PACKAGE_NAME_BITWARDEN_PREFIX: String = "com.x8bit.bitwarden" | ||
private const val PACKAGE_NAME_SYSTEM_UI: String = "com.android.systemui" | ||
private const val PACKAGE_NAME_LAUNCHER_PARTIAL: String = "launcher" | ||
private val PACKAGE_NAME_BLOCK_LIST: List<String> = listOf( | ||
"com.google.android.googlequicksearchbox", | ||
"com.google.android.apps.nexuslauncher", | ||
"com.google.android.launcher", | ||
"com.computer.desktop.ui.launcher", | ||
"com.launcher.notelauncher", | ||
"com.anddoes.launcher", | ||
"com.actionlauncher.playstore", | ||
"ch.deletescape.lawnchair.plah", | ||
"com.microsoft.launcher", | ||
"com.teslacoilsw.launcher", | ||
"com.teslacoilsw.launcher.prime", | ||
"is.shortcut", | ||
"me.craftsapp.nlauncher", | ||
"com.ss.squarehome2", | ||
"com.treydev.pns", | ||
) | ||
|
||
/** | ||
* Returns true if the event is for an unsupported package. | ||
*/ | ||
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 | ||
} | ||
|
||
/** | ||
* Fills the [AccessibilityNodeInfo] text field with the [value] provided. | ||
*/ | ||
@OmitFromCoverage | ||
fun AccessibilityNodeInfo.fillTextField(value: String?) { | ||
performAction( | ||
AccessibilityNodeInfo.ACTION_SET_TEXT, | ||
bundleOf(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE to value), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" | ||
xmlns:tools="http://schemas.android.com/tools" | ||
android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged" | ||
android:accessibilityFeedbackType="feedbackGeneric" | ||
android:accessibilityFlags="flagReportViewIds|flagRetrieveInteractiveWindows" | ||
android:canRetrieveWindowContent="true" | ||
android:description="@string/autofill_service_description" | ||
android:isAccessibilityTool="false" | ||
android:notificationTimeout="100" | ||
android:summary="@string/autofill_accessibility_summary" | ||
tools:ignore="UnusedAttribute" /> |
Oops, something went wrong.