BIT-1316: Add compatibility for browser apps (#796)

This commit is contained in:
Lucas Kivi
2024-01-25 23:46:42 -06:00
committed by Álison Fernandes
parent 0818638273
commit f2d90dda55
14 changed files with 715 additions and 284 deletions

View File

@@ -11,17 +11,11 @@ sealed class AutofillView {
* The data important to a given [AutofillView].
*
* @param autofillId The [AutofillId] associated with this view.
* @param idPackage The package id for this view, if there is one.
* @param isFocused Whether the view is currently focused.
* @param webDomain The web domain for this view, if there is one. (example: m.facebook.com)
* @param webScheme The web scheme for this view, if there is one. (example: https)
*/
data class Data(
val autofillId: AutofillId,
val idPackage: String?,
val isFocused: Boolean,
val webDomain: String?,
val webScheme: String?,
)
/**

View File

@@ -4,8 +4,15 @@ import android.view.autofill.AutofillId
/**
* A convenience data structure for view node traversal.
*
* @param autofillViews The list of views we care about for autofilling.
* @param idPackage The package id for this view, if there is one.
* @param ignoreAutofillIds The list of [AutofillId]s that should be ignored in the fill response.
* @param website The website that is being displayed in the app, given there is one.
*/
data class ViewNodeTraversalData(
val autofillViews: List<AutofillView>,
val idPackage: String?,
val ignoreAutofillIds: List<AutofillId>,
val website: String?,
)

View File

@@ -12,6 +12,7 @@ import com.x8bit.bitwarden.data.autofill.util.buildUriOrNull
import com.x8bit.bitwarden.data.autofill.util.getInlinePresentationSpecs
import com.x8bit.bitwarden.data.autofill.util.getMaxInlineSuggestionsCount
import com.x8bit.bitwarden.data.autofill.util.toAutofillView
import com.x8bit.bitwarden.data.autofill.util.website
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
/**
@@ -119,6 +120,8 @@ private fun AssistStructure.ViewNode.traverse(): ViewNodeTraversalData {
// Set up mutable lists for collecting valid AutofillViews and ignorable view ids.
val mutableAutofillViewList: MutableList<AutofillView> = mutableListOf()
val mutableIgnoreAutofillIdList: MutableList<AutofillId> = mutableListOf()
var idPackage: String? = this.idPackage
var website: String? = this.website
// Try converting this `ViewNode` into an `AutofillView`. If a valid instance is returned, add
// it to the list. Otherwise, ignore the `AutofillId` associated with this `ViewNode`.
@@ -134,6 +137,15 @@ private fun AssistStructure.ViewNode.traverse(): ViewNodeTraversalData {
.let { viewNodeTraversalData ->
viewNodeTraversalData.autofillViews.forEach(mutableAutofillViewList::add)
viewNodeTraversalData.ignoreAutofillIds.forEach(mutableIgnoreAutofillIdList::add)
// Get the first non-null idPackage.
if (idPackage.isNullOrBlank()) {
idPackage = viewNodeTraversalData.idPackage
}
// Get the first non-null website.
if (website == null) {
website = viewNodeTraversalData.website
}
}
}
@@ -141,6 +153,8 @@ private fun AssistStructure.ViewNode.traverse(): ViewNodeTraversalData {
// descendant's.
return ViewNodeTraversalData(
autofillViews = mutableAutofillViewList,
idPackage = idPackage,
ignoreAutofillIds = mutableIgnoreAutofillIdList,
website = website,
)
}

View File

@@ -3,12 +3,18 @@ package com.x8bit.bitwarden.data.autofill.util
import android.app.assist.AssistStructure
import android.view.View
import com.x8bit.bitwarden.data.autofill.model.AutofillView
import com.x8bit.bitwarden.ui.platform.base.util.orNullIfBlank
/**
* The class name of the android edit text field.
*/
private const val ANDROID_EDIT_TEXT_CLASS_NAME: String = "android.widget.EditText"
/**
* The default web URI scheme.
*/
private const val DEFAULT_SCHEME: String = "https"
/**
* The set of raw autofill hints that should be ignored.
*/
@@ -72,10 +78,7 @@ fun AssistStructure.ViewNode.toAutofillView(): AutofillView? =
if (supportedHint != null || this.isInputField) {
val autofillViewData = AutofillView.Data(
autofillId = nonNullAutofillId,
idPackage = idPackage,
isFocused = isFocused,
webDomain = webDomain,
webScheme = webScheme,
)
buildAutofillView(
autofillViewData = autofillViewData,
@@ -164,3 +167,22 @@ fun AssistStructure.ViewNode.isUsernameField(
inputType.isUsernameInputType ||
idEntry?.containsAnyTerms(SUPPORTED_RAW_USERNAME_HINTS) == true ||
hint?.containsAnyTerms(SUPPORTED_RAW_USERNAME_HINTS) == true
/**
* The website that this [AssistStructure.ViewNode] is a part of representing.
*/
val AssistStructure.ViewNode.website: String?
get() = this
.webDomain
.takeUnless { it?.isBlank() == true }
?.let { webDomain ->
val webScheme = this
.webScheme
.orNullIfBlank()
?: DEFAULT_SCHEME
buildUri(
domain = webDomain,
scheme = webScheme,
)
}

View File

@@ -9,11 +9,6 @@ import com.x8bit.bitwarden.ui.platform.base.util.orNullIfBlank
*/
private const val ANDROID_APP_SCHEME: String = "androidapp"
/**
* The default web URI scheme.
*/
private const val DEFAULT_SCHEME: String = "https"
/**
* Try and build a URI. The try progression looks like this:
* 1. Try searching traversal data for website URIs.
@@ -25,13 +20,17 @@ fun List<ViewNodeTraversalData>.buildUriOrNull(
assistStructure: AssistStructure,
): String? {
// Search list of [ViewNodeTraversalData] for a website URI.
buildWebsiteUriOrNull()
this
.firstOrNull { it.website != null }
?.website
?.let { websiteUri ->
return websiteUri
}
// Search list of [ViewNodeTraversalData] for a valid package name.
buildPackageNameOrNull()
this
.firstOrNull { it.idPackage != null }
?.idPackage
?.let { packageName ->
return buildUri(
domain = packageName,
@@ -53,7 +52,7 @@ fun List<ViewNodeTraversalData>.buildUriOrNull(
/**
* Combine [domain] and [scheme] into a URI.
*/
private fun buildUri(
fun buildUri(
domain: String,
scheme: String,
): String = "$scheme://$domain"
@@ -74,29 +73,3 @@ private fun AssistStructure.buildPackageNameOrNull(): String? = if (windowNodeCo
} else {
null
}
/**
* Search each [ViewNodeTraversalData.autofillViews] list for a valid package id. If one is found
* return it and terminate the search.
*/
private fun List<ViewNodeTraversalData>.buildPackageNameOrNull(): String? =
flatMap { it.autofillViews }
.firstOrNull { !it.data.idPackage.isNullOrEmpty() }
?.data
?.idPackage
/**
* Search each [ViewNodeTraversalData.autofillViews] list for a valid web domain. If one is found,
* combine it with its scheme and return it.
*/
private fun List<ViewNodeTraversalData>.buildWebsiteUriOrNull(): String? =
flatMap { it.autofillViews }
.firstOrNull { !it.data.webDomain.isNullOrEmpty() }
?.let { autofillView ->
val webDomain = requireNotNull(autofillView.data.webDomain)
val webScheme = autofillView.data.webScheme.orNullIfBlank() ?: DEFAULT_SCHEME
buildUri(
domain = webDomain,
scheme = webScheme,
)
}