mirror of
https://github.com/bitwarden/android.git
synced 2024-11-23 18:06:08 +03:00
Add URI generation algorithm to autofill parsing (#582)
This commit is contained in:
parent
e9e538db59
commit
197feea56a
10 changed files with 549 additions and 12 deletions
|
@ -13,6 +13,7 @@ sealed class AutofillRequest {
|
||||||
data class Fillable(
|
data class Fillable(
|
||||||
val ignoreAutofillIds: List<AutofillId>,
|
val ignoreAutofillIds: List<AutofillId>,
|
||||||
val partition: AutofillPartition,
|
val partition: AutofillPartition,
|
||||||
|
val uri: String?,
|
||||||
) : AutofillRequest()
|
) : AutofillRequest()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -11,11 +11,26 @@ sealed class AutofillView {
|
||||||
*/
|
*/
|
||||||
abstract val autofillId: AutofillId
|
abstract val autofillId: AutofillId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The package id for this view, if there is one. (ex: "com.x8bit.bitwarden")
|
||||||
|
*/
|
||||||
|
abstract val idPackage: String?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the view is currently focused.
|
* Whether the view is currently focused.
|
||||||
*/
|
*/
|
||||||
abstract val isFocused: Boolean
|
abstract val isFocused: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The web domain for this view, if there is one. (ex: "m.facebook.com")
|
||||||
|
*/
|
||||||
|
abstract val webDomain: String?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The web scheme for this view, if there is one. (ex: "https")
|
||||||
|
*/
|
||||||
|
abstract val webScheme: String?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A view that corresponds to the card data partition for autofill fields.
|
* A view that corresponds to the card data partition for autofill fields.
|
||||||
*/
|
*/
|
||||||
|
@ -26,7 +41,10 @@ sealed class AutofillView {
|
||||||
*/
|
*/
|
||||||
data class ExpirationMonth(
|
data class ExpirationMonth(
|
||||||
override val autofillId: AutofillId,
|
override val autofillId: AutofillId,
|
||||||
|
override val idPackage: String?,
|
||||||
override val isFocused: Boolean,
|
override val isFocused: Boolean,
|
||||||
|
override val webDomain: String?,
|
||||||
|
override val webScheme: String?,
|
||||||
) : Card()
|
) : Card()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,7 +52,10 @@ sealed class AutofillView {
|
||||||
*/
|
*/
|
||||||
data class ExpirationYear(
|
data class ExpirationYear(
|
||||||
override val autofillId: AutofillId,
|
override val autofillId: AutofillId,
|
||||||
|
override val idPackage: String?,
|
||||||
override val isFocused: Boolean,
|
override val isFocused: Boolean,
|
||||||
|
override val webDomain: String?,
|
||||||
|
override val webScheme: String?,
|
||||||
) : Card()
|
) : Card()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,7 +63,10 @@ sealed class AutofillView {
|
||||||
*/
|
*/
|
||||||
data class Number(
|
data class Number(
|
||||||
override val autofillId: AutofillId,
|
override val autofillId: AutofillId,
|
||||||
|
override val idPackage: String?,
|
||||||
override val isFocused: Boolean,
|
override val isFocused: Boolean,
|
||||||
|
override val webDomain: String?,
|
||||||
|
override val webScheme: String?,
|
||||||
) : Card()
|
) : Card()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,7 +74,10 @@ sealed class AutofillView {
|
||||||
*/
|
*/
|
||||||
data class SecurityCode(
|
data class SecurityCode(
|
||||||
override val autofillId: AutofillId,
|
override val autofillId: AutofillId,
|
||||||
|
override val idPackage: String?,
|
||||||
override val isFocused: Boolean,
|
override val isFocused: Boolean,
|
||||||
|
override val webDomain: String?,
|
||||||
|
override val webScheme: String?,
|
||||||
) : Card()
|
) : Card()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +91,10 @@ sealed class AutofillView {
|
||||||
*/
|
*/
|
||||||
data class EmailAddress(
|
data class EmailAddress(
|
||||||
override val autofillId: AutofillId,
|
override val autofillId: AutofillId,
|
||||||
|
override val idPackage: String?,
|
||||||
override val isFocused: Boolean,
|
override val isFocused: Boolean,
|
||||||
|
override val webDomain: String?,
|
||||||
|
override val webScheme: String?,
|
||||||
) : Login()
|
) : Login()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,7 +102,10 @@ sealed class AutofillView {
|
||||||
*/
|
*/
|
||||||
data class Password(
|
data class Password(
|
||||||
override val autofillId: AutofillId,
|
override val autofillId: AutofillId,
|
||||||
|
override val idPackage: String?,
|
||||||
override val isFocused: Boolean,
|
override val isFocused: Boolean,
|
||||||
|
override val webDomain: String?,
|
||||||
|
override val webScheme: String?,
|
||||||
) : Login()
|
) : Login()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -80,7 +113,10 @@ sealed class AutofillView {
|
||||||
*/
|
*/
|
||||||
data class Username(
|
data class Username(
|
||||||
override val autofillId: AutofillId,
|
override val autofillId: AutofillId,
|
||||||
|
override val idPackage: String?,
|
||||||
override val isFocused: Boolean,
|
override val isFocused: Boolean,
|
||||||
|
override val webDomain: String?,
|
||||||
|
override val webScheme: String?,
|
||||||
) : Login()
|
) : Login()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.x8bit.bitwarden.data.autofill.model
|
||||||
|
|
||||||
|
import android.view.autofill.AutofillId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A convenience data structure for view node traversal.
|
||||||
|
*/
|
||||||
|
data class ViewNodeTraversalData(
|
||||||
|
val autofillViews: List<AutofillView>,
|
||||||
|
val ignoreAutofillIds: List<AutofillId>,
|
||||||
|
)
|
|
@ -5,6 +5,8 @@ import android.view.autofill.AutofillId
|
||||||
import com.x8bit.bitwarden.data.autofill.model.AutofillPartition
|
import com.x8bit.bitwarden.data.autofill.model.AutofillPartition
|
||||||
import com.x8bit.bitwarden.data.autofill.model.AutofillRequest
|
import com.x8bit.bitwarden.data.autofill.model.AutofillRequest
|
||||||
import com.x8bit.bitwarden.data.autofill.model.AutofillView
|
import com.x8bit.bitwarden.data.autofill.model.AutofillView
|
||||||
|
import com.x8bit.bitwarden.data.autofill.model.ViewNodeTraversalData
|
||||||
|
import com.x8bit.bitwarden.data.autofill.util.buildUriOrNull
|
||||||
import com.x8bit.bitwarden.data.autofill.util.toAutofillView
|
import com.x8bit.bitwarden.data.autofill.util.toAutofillView
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,9 +16,9 @@ import com.x8bit.bitwarden.data.autofill.util.toAutofillView
|
||||||
class AutofillParserImpl : AutofillParser {
|
class AutofillParserImpl : AutofillParser {
|
||||||
override fun parse(assistStructure: AssistStructure): AutofillRequest {
|
override fun parse(assistStructure: AssistStructure): AutofillRequest {
|
||||||
// Parse the `assistStructure` into internal models.
|
// Parse the `assistStructure` into internal models.
|
||||||
val traversalData = assistStructure.traverse()
|
val traversalDataList = assistStructure.traverse()
|
||||||
// Flatten the autofill views for processing.
|
// Flatten the autofill views for processing.
|
||||||
val autofillViews = traversalData
|
val autofillViews = traversalDataList
|
||||||
.map { it.autofillViews }
|
.map { it.autofillViews }
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|
||||||
|
@ -25,6 +27,10 @@ class AutofillParserImpl : AutofillParser {
|
||||||
.firstOrNull { it.isFocused }
|
.firstOrNull { it.isFocused }
|
||||||
?: return AutofillRequest.Unfillable
|
?: return AutofillRequest.Unfillable
|
||||||
|
|
||||||
|
val uri = traversalDataList.buildUriOrNull(
|
||||||
|
assistStructure = assistStructure,
|
||||||
|
)
|
||||||
|
|
||||||
// Choose the first focused partition of data for fulfillment.
|
// Choose the first focused partition of data for fulfillment.
|
||||||
val partition = when (focusedView) {
|
val partition = when (focusedView) {
|
||||||
is AutofillView.Card -> {
|
is AutofillView.Card -> {
|
||||||
|
@ -40,13 +46,14 @@ class AutofillParserImpl : AutofillParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Flatten the ignorable autofill ids.
|
// Flatten the ignorable autofill ids.
|
||||||
val ignoreAutofillIds = traversalData
|
val ignoreAutofillIds = traversalDataList
|
||||||
.map { it.ignoreAutofillIds }
|
.map { it.ignoreAutofillIds }
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|
||||||
return AutofillRequest.Fillable(
|
return AutofillRequest.Fillable(
|
||||||
ignoreAutofillIds = ignoreAutofillIds,
|
ignoreAutofillIds = ignoreAutofillIds,
|
||||||
partition = partition,
|
partition = partition,
|
||||||
|
uri = uri,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,11 +99,3 @@ private fun AssistStructure.ViewNode.traverse(): ViewNodeTraversalData {
|
||||||
ignoreAutofillIds = mutableIgnoreAutofillIdList,
|
ignoreAutofillIds = mutableIgnoreAutofillIdList,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A convenience data structure for view node traversal.
|
|
||||||
*/
|
|
||||||
private data class ViewNodeTraversalData(
|
|
||||||
val autofillViews: List<AutofillView>,
|
|
||||||
val ignoreAutofillIds: List<AutofillId>,
|
|
||||||
)
|
|
||||||
|
|
|
@ -18,8 +18,11 @@ fun AssistStructure.ViewNode.toAutofillView(): AutofillView? = autofillId
|
||||||
?.let { supportedHint ->
|
?.let { supportedHint ->
|
||||||
buildAutofillView(
|
buildAutofillView(
|
||||||
autofillId = nonNullAutofillId,
|
autofillId = nonNullAutofillId,
|
||||||
|
idPackage = idPackage,
|
||||||
isFocused = isFocused,
|
isFocused = isFocused,
|
||||||
hint = supportedHint,
|
hint = supportedHint,
|
||||||
|
webDomain = webDomain,
|
||||||
|
webScheme = webScheme,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,58 +30,82 @@ fun AssistStructure.ViewNode.toAutofillView(): AutofillView? = autofillId
|
||||||
/**
|
/**
|
||||||
* Convert the data into an [AutofillView] if the [hint] is supported.
|
* Convert the data into an [AutofillView] if the [hint] is supported.
|
||||||
*/
|
*/
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod", "LongParameterList")
|
||||||
private fun buildAutofillView(
|
private fun buildAutofillView(
|
||||||
autofillId: AutofillId,
|
autofillId: AutofillId,
|
||||||
|
idPackage: String?,
|
||||||
isFocused: Boolean,
|
isFocused: Boolean,
|
||||||
hint: String,
|
hint: String,
|
||||||
|
webDomain: String?,
|
||||||
|
webScheme: String?,
|
||||||
): AutofillView? = when (hint) {
|
): AutofillView? = when (hint) {
|
||||||
View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH -> {
|
View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH -> {
|
||||||
AutofillView.Card.ExpirationMonth(
|
AutofillView.Card.ExpirationMonth(
|
||||||
autofillId = autofillId,
|
autofillId = autofillId,
|
||||||
|
idPackage = idPackage,
|
||||||
isFocused = isFocused,
|
isFocused = isFocused,
|
||||||
|
webDomain = webDomain,
|
||||||
|
webScheme = webScheme,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR -> {
|
View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR -> {
|
||||||
AutofillView.Card.ExpirationYear(
|
AutofillView.Card.ExpirationYear(
|
||||||
autofillId = autofillId,
|
autofillId = autofillId,
|
||||||
|
idPackage = idPackage,
|
||||||
isFocused = isFocused,
|
isFocused = isFocused,
|
||||||
|
webDomain = webDomain,
|
||||||
|
webScheme = webScheme,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
View.AUTOFILL_HINT_CREDIT_CARD_NUMBER -> {
|
View.AUTOFILL_HINT_CREDIT_CARD_NUMBER -> {
|
||||||
AutofillView.Card.Number(
|
AutofillView.Card.Number(
|
||||||
autofillId = autofillId,
|
autofillId = autofillId,
|
||||||
|
idPackage = idPackage,
|
||||||
isFocused = isFocused,
|
isFocused = isFocused,
|
||||||
|
webDomain = webDomain,
|
||||||
|
webScheme = webScheme,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE -> {
|
View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE -> {
|
||||||
AutofillView.Card.SecurityCode(
|
AutofillView.Card.SecurityCode(
|
||||||
autofillId = autofillId,
|
autofillId = autofillId,
|
||||||
|
idPackage = idPackage,
|
||||||
isFocused = isFocused,
|
isFocused = isFocused,
|
||||||
|
webDomain = webDomain,
|
||||||
|
webScheme = webScheme,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
View.AUTOFILL_HINT_EMAIL_ADDRESS -> {
|
View.AUTOFILL_HINT_EMAIL_ADDRESS -> {
|
||||||
AutofillView.Login.EmailAddress(
|
AutofillView.Login.EmailAddress(
|
||||||
autofillId = autofillId,
|
autofillId = autofillId,
|
||||||
|
idPackage = idPackage,
|
||||||
isFocused = isFocused,
|
isFocused = isFocused,
|
||||||
|
webDomain = webDomain,
|
||||||
|
webScheme = webScheme,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
View.AUTOFILL_HINT_PASSWORD -> {
|
View.AUTOFILL_HINT_PASSWORD -> {
|
||||||
AutofillView.Login.Password(
|
AutofillView.Login.Password(
|
||||||
autofillId = autofillId,
|
autofillId = autofillId,
|
||||||
|
idPackage = idPackage,
|
||||||
isFocused = isFocused,
|
isFocused = isFocused,
|
||||||
|
webDomain = webDomain,
|
||||||
|
webScheme = webScheme,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
View.AUTOFILL_HINT_USERNAME -> {
|
View.AUTOFILL_HINT_USERNAME -> {
|
||||||
AutofillView.Login.Username(
|
AutofillView.Login.Username(
|
||||||
autofillId = autofillId,
|
autofillId = autofillId,
|
||||||
|
idPackage = idPackage,
|
||||||
isFocused = isFocused,
|
isFocused = isFocused,
|
||||||
|
webDomain = webDomain,
|
||||||
|
webScheme = webScheme,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
package com.x8bit.bitwarden.data.autofill.util
|
||||||
|
|
||||||
|
import android.app.assist.AssistStructure
|
||||||
|
import com.x8bit.bitwarden.data.autofill.model.ViewNodeTraversalData
|
||||||
|
import com.x8bit.bitwarden.ui.platform.base.util.orNullIfBlank
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The android app URI scheme. Example: androidapp://com.x8bit.bitwarden
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
* 2. Try searching traversal data for package names, if one is found, convert it into a URI.
|
||||||
|
* 3. Try extracting a package name from [assistStructure], if one is found, convert it into a URI.
|
||||||
|
*/
|
||||||
|
@Suppress("ReturnCount")
|
||||||
|
fun List<ViewNodeTraversalData>.buildUriOrNull(
|
||||||
|
assistStructure: AssistStructure,
|
||||||
|
): String? {
|
||||||
|
// Search list of [ViewNodeTraversalData] for a website URI.
|
||||||
|
buildWebsiteUriOrNull()
|
||||||
|
?.let { websiteUri ->
|
||||||
|
return websiteUri
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search list of [ViewNodeTraversalData] for a valid package name.
|
||||||
|
buildPackageNameOrNull()
|
||||||
|
?.let { packageName ->
|
||||||
|
return buildUri(
|
||||||
|
domain = packageName,
|
||||||
|
scheme = ANDROID_APP_SCHEME,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try getting the package name from the [AssistStructure] as a last ditch effort.
|
||||||
|
return assistStructure
|
||||||
|
.buildPackageNameOrNull()
|
||||||
|
?.let { packageName ->
|
||||||
|
buildUri(
|
||||||
|
domain = packageName,
|
||||||
|
scheme = ANDROID_APP_SCHEME,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine [domain] and [scheme] into a URI.
|
||||||
|
*/
|
||||||
|
private fun buildUri(
|
||||||
|
domain: String,
|
||||||
|
scheme: String,
|
||||||
|
): String = "$scheme://$domain"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to extract the package name from the title of the [AssistStructure].
|
||||||
|
*
|
||||||
|
* As an example, the title might look like this: com.facebook.katana/com.facebook.bloks.facebook...
|
||||||
|
* Then this function would return: com.facebook.katana
|
||||||
|
*/
|
||||||
|
private fun AssistStructure.buildPackageNameOrNull(): String? = if (windowNodeCount > 0) {
|
||||||
|
getWindowNodeAt(0)
|
||||||
|
.title
|
||||||
|
?.toString()
|
||||||
|
?.orNullIfBlank()
|
||||||
|
?.split('/')
|
||||||
|
?.firstOrNull()
|
||||||
|
} 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.idPackage.isNullOrEmpty() }
|
||||||
|
?.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.webDomain.isNullOrEmpty() }
|
||||||
|
?.let { autofillView ->
|
||||||
|
val webDomain = requireNotNull(autofillView.webDomain)
|
||||||
|
val webScheme = autofillView.webScheme.orNullIfBlank() ?: DEFAULT_SCHEME
|
||||||
|
buildUri(
|
||||||
|
domain = webDomain,
|
||||||
|
scheme = webScheme,
|
||||||
|
)
|
||||||
|
}
|
|
@ -27,7 +27,10 @@ class FilledDataBuilderTest {
|
||||||
val autofillId: AutofillId = mockk()
|
val autofillId: AutofillId = mockk()
|
||||||
val autofillView = AutofillView.Login.Username(
|
val autofillView = AutofillView.Login.Username(
|
||||||
autofillId = autofillId,
|
autofillId = autofillId,
|
||||||
|
idPackage = null,
|
||||||
isFocused = false,
|
isFocused = false,
|
||||||
|
webDomain = null,
|
||||||
|
webScheme = null,
|
||||||
)
|
)
|
||||||
val autofillPartition = AutofillPartition.Login(
|
val autofillPartition = AutofillPartition.Login(
|
||||||
views = listOf(autofillView),
|
views = listOf(autofillView),
|
||||||
|
@ -36,6 +39,7 @@ class FilledDataBuilderTest {
|
||||||
val autofillRequest = AutofillRequest.Fillable(
|
val autofillRequest = AutofillRequest.Fillable(
|
||||||
ignoreAutofillIds = ignoreAutofillIds,
|
ignoreAutofillIds = ignoreAutofillIds,
|
||||||
partition = autofillPartition,
|
partition = autofillPartition,
|
||||||
|
uri = URI,
|
||||||
)
|
)
|
||||||
val filledItem = FilledItem(
|
val filledItem = FilledItem(
|
||||||
autofillId = autofillId,
|
autofillId = autofillId,
|
||||||
|
@ -67,7 +71,10 @@ class FilledDataBuilderTest {
|
||||||
val autofillId: AutofillId = mockk()
|
val autofillId: AutofillId = mockk()
|
||||||
val autofillView = AutofillView.Card.Number(
|
val autofillView = AutofillView.Card.Number(
|
||||||
autofillId = autofillId,
|
autofillId = autofillId,
|
||||||
|
idPackage = null,
|
||||||
isFocused = false,
|
isFocused = false,
|
||||||
|
webDomain = null,
|
||||||
|
webScheme = null,
|
||||||
)
|
)
|
||||||
val autofillPartition = AutofillPartition.Card(
|
val autofillPartition = AutofillPartition.Card(
|
||||||
views = listOf(autofillView),
|
views = listOf(autofillView),
|
||||||
|
@ -76,6 +83,7 @@ class FilledDataBuilderTest {
|
||||||
val autofillRequest = AutofillRequest.Fillable(
|
val autofillRequest = AutofillRequest.Fillable(
|
||||||
ignoreAutofillIds = ignoreAutofillIds,
|
ignoreAutofillIds = ignoreAutofillIds,
|
||||||
partition = autofillPartition,
|
partition = autofillPartition,
|
||||||
|
uri = URI,
|
||||||
)
|
)
|
||||||
val filledItem = FilledItem(
|
val filledItem = FilledItem(
|
||||||
autofillId = autofillId,
|
autofillId = autofillId,
|
||||||
|
@ -100,4 +108,8 @@ class FilledDataBuilderTest {
|
||||||
// Verify
|
// Verify
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val URI: String = "androidapp://com.x8bit.bitwarden"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,14 @@ import android.view.autofill.AutofillId
|
||||||
import com.x8bit.bitwarden.data.autofill.model.AutofillPartition
|
import com.x8bit.bitwarden.data.autofill.model.AutofillPartition
|
||||||
import com.x8bit.bitwarden.data.autofill.model.AutofillRequest
|
import com.x8bit.bitwarden.data.autofill.model.AutofillRequest
|
||||||
import com.x8bit.bitwarden.data.autofill.model.AutofillView
|
import com.x8bit.bitwarden.data.autofill.model.AutofillView
|
||||||
|
import com.x8bit.bitwarden.data.autofill.model.ViewNodeTraversalData
|
||||||
|
import com.x8bit.bitwarden.data.autofill.util.buildUriOrNull
|
||||||
import com.x8bit.bitwarden.data.autofill.util.toAutofillView
|
import com.x8bit.bitwarden.data.autofill.util.toAutofillView
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
import io.mockk.unmockkStatic
|
import io.mockk.unmockkStatic
|
||||||
|
import io.mockk.verify
|
||||||
import org.junit.jupiter.api.AfterEach
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
@ -44,12 +47,15 @@ class AutofillParserTests {
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup() {
|
fun setup() {
|
||||||
mockkStatic(AssistStructure.ViewNode::toAutofillView)
|
mockkStatic(AssistStructure.ViewNode::toAutofillView)
|
||||||
|
mockkStatic(List<ViewNodeTraversalData>::buildUriOrNull)
|
||||||
|
every { any<List<ViewNodeTraversalData>>().buildUriOrNull(assistStructure) } returns URI
|
||||||
parser = AutofillParserImpl()
|
parser = AutofillParserImpl()
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
fun teardown() {
|
fun teardown() {
|
||||||
unmockkStatic(AssistStructure.ViewNode::toAutofillView)
|
unmockkStatic(AssistStructure.ViewNode::toAutofillView)
|
||||||
|
unmockkStatic(List<ViewNodeTraversalData>::buildUriOrNull)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -82,6 +88,9 @@ class AutofillParserTests {
|
||||||
val parentAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
|
val parentAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
|
||||||
autofillId = parentAutofillId,
|
autofillId = parentAutofillId,
|
||||||
isFocused = true,
|
isFocused = true,
|
||||||
|
idPackage = null,
|
||||||
|
webDomain = null,
|
||||||
|
webScheme = null,
|
||||||
)
|
)
|
||||||
val parentViewNode: AssistStructure.ViewNode = mockk {
|
val parentViewNode: AssistStructure.ViewNode = mockk {
|
||||||
every { this@mockk.autofillHints } returns arrayOf(parentAutofillHint)
|
every { this@mockk.autofillHints } returns arrayOf(parentAutofillHint)
|
||||||
|
@ -99,6 +108,7 @@ class AutofillParserTests {
|
||||||
val expected = AutofillRequest.Fillable(
|
val expected = AutofillRequest.Fillable(
|
||||||
ignoreAutofillIds = listOf(childAutofillId),
|
ignoreAutofillIds = listOf(childAutofillId),
|
||||||
partition = autofillPartition,
|
partition = autofillPartition,
|
||||||
|
uri = URI,
|
||||||
)
|
)
|
||||||
every { assistStructure.windowNodeCount } returns 1
|
every { assistStructure.windowNodeCount } returns 1
|
||||||
every { assistStructure.getWindowNodeAt(0) } returns windowNode
|
every { assistStructure.getWindowNodeAt(0) } returns windowNode
|
||||||
|
@ -108,6 +118,9 @@ class AutofillParserTests {
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
|
verify(exactly = 1) {
|
||||||
|
any<List<ViewNodeTraversalData>>().buildUriOrNull(assistStructure)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -117,10 +130,16 @@ class AutofillParserTests {
|
||||||
val cardAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
|
val cardAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
|
||||||
autofillId = cardAutofillId,
|
autofillId = cardAutofillId,
|
||||||
isFocused = true,
|
isFocused = true,
|
||||||
|
idPackage = null,
|
||||||
|
webDomain = null,
|
||||||
|
webScheme = null,
|
||||||
)
|
)
|
||||||
val loginAutofillView: AutofillView.Login = AutofillView.Login.Username(
|
val loginAutofillView: AutofillView.Login = AutofillView.Login.Username(
|
||||||
autofillId = loginAutofillId,
|
autofillId = loginAutofillId,
|
||||||
isFocused = false,
|
isFocused = false,
|
||||||
|
idPackage = null,
|
||||||
|
webDomain = null,
|
||||||
|
webScheme = null,
|
||||||
)
|
)
|
||||||
val autofillPartition = AutofillPartition.Card(
|
val autofillPartition = AutofillPartition.Card(
|
||||||
views = listOf(cardAutofillView),
|
views = listOf(cardAutofillView),
|
||||||
|
@ -128,6 +147,7 @@ class AutofillParserTests {
|
||||||
val expected = AutofillRequest.Fillable(
|
val expected = AutofillRequest.Fillable(
|
||||||
ignoreAutofillIds = emptyList(),
|
ignoreAutofillIds = emptyList(),
|
||||||
partition = autofillPartition,
|
partition = autofillPartition,
|
||||||
|
uri = URI,
|
||||||
)
|
)
|
||||||
every { cardViewNode.toAutofillView() } returns cardAutofillView
|
every { cardViewNode.toAutofillView() } returns cardAutofillView
|
||||||
every { loginViewNode.toAutofillView() } returns loginAutofillView
|
every { loginViewNode.toAutofillView() } returns loginAutofillView
|
||||||
|
@ -137,6 +157,9 @@ class AutofillParserTests {
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
|
verify(exactly = 1) {
|
||||||
|
any<List<ViewNodeTraversalData>>().buildUriOrNull(assistStructure)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -146,10 +169,16 @@ class AutofillParserTests {
|
||||||
val cardAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
|
val cardAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
|
||||||
autofillId = cardAutofillId,
|
autofillId = cardAutofillId,
|
||||||
isFocused = false,
|
isFocused = false,
|
||||||
|
idPackage = null,
|
||||||
|
webDomain = null,
|
||||||
|
webScheme = null,
|
||||||
)
|
)
|
||||||
val loginAutofillView: AutofillView.Login = AutofillView.Login.Username(
|
val loginAutofillView: AutofillView.Login = AutofillView.Login.Username(
|
||||||
autofillId = loginAutofillId,
|
autofillId = loginAutofillId,
|
||||||
isFocused = true,
|
isFocused = true,
|
||||||
|
idPackage = null,
|
||||||
|
webDomain = null,
|
||||||
|
webScheme = null,
|
||||||
)
|
)
|
||||||
val autofillPartition = AutofillPartition.Login(
|
val autofillPartition = AutofillPartition.Login(
|
||||||
views = listOf(loginAutofillView),
|
views = listOf(loginAutofillView),
|
||||||
|
@ -157,6 +186,7 @@ class AutofillParserTests {
|
||||||
val expected = AutofillRequest.Fillable(
|
val expected = AutofillRequest.Fillable(
|
||||||
ignoreAutofillIds = emptyList(),
|
ignoreAutofillIds = emptyList(),
|
||||||
partition = autofillPartition,
|
partition = autofillPartition,
|
||||||
|
uri = URI,
|
||||||
)
|
)
|
||||||
every { cardViewNode.toAutofillView() } returns cardAutofillView
|
every { cardViewNode.toAutofillView() } returns cardAutofillView
|
||||||
every { loginViewNode.toAutofillView() } returns loginAutofillView
|
every { loginViewNode.toAutofillView() } returns loginAutofillView
|
||||||
|
@ -166,6 +196,9 @@ class AutofillParserTests {
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
|
verify(exactly = 1) {
|
||||||
|
any<List<ViewNodeTraversalData>>().buildUriOrNull(assistStructure)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -175,10 +208,16 @@ class AutofillParserTests {
|
||||||
val cardAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
|
val cardAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
|
||||||
autofillId = cardAutofillId,
|
autofillId = cardAutofillId,
|
||||||
isFocused = true,
|
isFocused = true,
|
||||||
|
idPackage = null,
|
||||||
|
webDomain = null,
|
||||||
|
webScheme = null,
|
||||||
)
|
)
|
||||||
val loginAutofillView: AutofillView.Login = AutofillView.Login.Username(
|
val loginAutofillView: AutofillView.Login = AutofillView.Login.Username(
|
||||||
autofillId = loginAutofillId,
|
autofillId = loginAutofillId,
|
||||||
isFocused = true,
|
isFocused = true,
|
||||||
|
idPackage = null,
|
||||||
|
webDomain = null,
|
||||||
|
webScheme = null,
|
||||||
)
|
)
|
||||||
val autofillPartition = AutofillPartition.Card(
|
val autofillPartition = AutofillPartition.Card(
|
||||||
views = listOf(cardAutofillView),
|
views = listOf(cardAutofillView),
|
||||||
|
@ -186,6 +225,7 @@ class AutofillParserTests {
|
||||||
val expected = AutofillRequest.Fillable(
|
val expected = AutofillRequest.Fillable(
|
||||||
ignoreAutofillIds = emptyList(),
|
ignoreAutofillIds = emptyList(),
|
||||||
partition = autofillPartition,
|
partition = autofillPartition,
|
||||||
|
uri = URI,
|
||||||
)
|
)
|
||||||
every { cardViewNode.toAutofillView() } returns cardAutofillView
|
every { cardViewNode.toAutofillView() } returns cardAutofillView
|
||||||
every { loginViewNode.toAutofillView() } returns loginAutofillView
|
every { loginViewNode.toAutofillView() } returns loginAutofillView
|
||||||
|
@ -195,6 +235,9 @@ class AutofillParserTests {
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
|
verify(exactly = 1) {
|
||||||
|
any<List<ViewNodeTraversalData>>().buildUriOrNull(assistStructure)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -206,4 +249,8 @@ class AutofillParserTests {
|
||||||
every { assistStructure.getWindowNodeAt(0) } returns cardWindowNode
|
every { assistStructure.getWindowNodeAt(0) } returns cardWindowNode
|
||||||
every { assistStructure.getWindowNodeAt(1) } returns loginWindowNode
|
every { assistStructure.getWindowNodeAt(1) } returns loginWindowNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val URI: String = "androidapp://com.x8bit.bitwarden"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,10 @@ class ViewNodeExtensionsTest {
|
||||||
private val viewNode: AssistStructure.ViewNode = mockk {
|
private val viewNode: AssistStructure.ViewNode = mockk {
|
||||||
every { this@mockk.autofillId } returns expectedAutofillId
|
every { this@mockk.autofillId } returns expectedAutofillId
|
||||||
every { this@mockk.childCount } returns 0
|
every { this@mockk.childCount } returns 0
|
||||||
|
every { this@mockk.idPackage } returns ID_PACKAGE
|
||||||
every { this@mockk.isFocused } returns expectedIsFocused
|
every { this@mockk.isFocused } returns expectedIsFocused
|
||||||
|
every { this@mockk.webDomain } returns WEB_DOMAIN
|
||||||
|
every { this@mockk.webScheme } returns WEB_SCHEME
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -25,7 +28,10 @@ class ViewNodeExtensionsTest {
|
||||||
val autofillHint = View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH
|
val autofillHint = View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH
|
||||||
val expected = AutofillView.Card.ExpirationMonth(
|
val expected = AutofillView.Card.ExpirationMonth(
|
||||||
autofillId = expectedAutofillId,
|
autofillId = expectedAutofillId,
|
||||||
|
idPackage = ID_PACKAGE,
|
||||||
isFocused = expectedIsFocused,
|
isFocused = expectedIsFocused,
|
||||||
|
webDomain = WEB_DOMAIN,
|
||||||
|
webScheme = WEB_SCHEME,
|
||||||
)
|
)
|
||||||
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
||||||
|
|
||||||
|
@ -42,7 +48,10 @@ class ViewNodeExtensionsTest {
|
||||||
val autofillHint = View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR
|
val autofillHint = View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR
|
||||||
val expected = AutofillView.Card.ExpirationYear(
|
val expected = AutofillView.Card.ExpirationYear(
|
||||||
autofillId = expectedAutofillId,
|
autofillId = expectedAutofillId,
|
||||||
|
idPackage = ID_PACKAGE,
|
||||||
isFocused = expectedIsFocused,
|
isFocused = expectedIsFocused,
|
||||||
|
webDomain = WEB_DOMAIN,
|
||||||
|
webScheme = WEB_SCHEME,
|
||||||
)
|
)
|
||||||
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
||||||
|
|
||||||
|
@ -59,7 +68,10 @@ class ViewNodeExtensionsTest {
|
||||||
val autofillHint = View.AUTOFILL_HINT_CREDIT_CARD_NUMBER
|
val autofillHint = View.AUTOFILL_HINT_CREDIT_CARD_NUMBER
|
||||||
val expected = AutofillView.Card.Number(
|
val expected = AutofillView.Card.Number(
|
||||||
autofillId = expectedAutofillId,
|
autofillId = expectedAutofillId,
|
||||||
|
idPackage = ID_PACKAGE,
|
||||||
isFocused = expectedIsFocused,
|
isFocused = expectedIsFocused,
|
||||||
|
webDomain = WEB_DOMAIN,
|
||||||
|
webScheme = WEB_SCHEME,
|
||||||
)
|
)
|
||||||
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
||||||
|
|
||||||
|
@ -76,7 +88,10 @@ class ViewNodeExtensionsTest {
|
||||||
val autofillHint = View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE
|
val autofillHint = View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE
|
||||||
val expected = AutofillView.Card.SecurityCode(
|
val expected = AutofillView.Card.SecurityCode(
|
||||||
autofillId = expectedAutofillId,
|
autofillId = expectedAutofillId,
|
||||||
|
idPackage = ID_PACKAGE,
|
||||||
isFocused = expectedIsFocused,
|
isFocused = expectedIsFocused,
|
||||||
|
webDomain = WEB_DOMAIN,
|
||||||
|
webScheme = WEB_SCHEME,
|
||||||
)
|
)
|
||||||
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
||||||
|
|
||||||
|
@ -93,7 +108,10 @@ class ViewNodeExtensionsTest {
|
||||||
val autofillHint = View.AUTOFILL_HINT_EMAIL_ADDRESS
|
val autofillHint = View.AUTOFILL_HINT_EMAIL_ADDRESS
|
||||||
val expected = AutofillView.Login.EmailAddress(
|
val expected = AutofillView.Login.EmailAddress(
|
||||||
autofillId = expectedAutofillId,
|
autofillId = expectedAutofillId,
|
||||||
|
idPackage = ID_PACKAGE,
|
||||||
isFocused = expectedIsFocused,
|
isFocused = expectedIsFocused,
|
||||||
|
webDomain = WEB_DOMAIN,
|
||||||
|
webScheme = WEB_SCHEME,
|
||||||
)
|
)
|
||||||
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
||||||
|
|
||||||
|
@ -110,7 +128,10 @@ class ViewNodeExtensionsTest {
|
||||||
val autofillHint = View.AUTOFILL_HINT_PASSWORD
|
val autofillHint = View.AUTOFILL_HINT_PASSWORD
|
||||||
val expected = AutofillView.Login.Password(
|
val expected = AutofillView.Login.Password(
|
||||||
autofillId = expectedAutofillId,
|
autofillId = expectedAutofillId,
|
||||||
|
idPackage = ID_PACKAGE,
|
||||||
isFocused = expectedIsFocused,
|
isFocused = expectedIsFocused,
|
||||||
|
webDomain = WEB_DOMAIN,
|
||||||
|
webScheme = WEB_SCHEME,
|
||||||
)
|
)
|
||||||
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
||||||
|
|
||||||
|
@ -127,7 +148,10 @@ class ViewNodeExtensionsTest {
|
||||||
val autofillHint = View.AUTOFILL_HINT_USERNAME
|
val autofillHint = View.AUTOFILL_HINT_USERNAME
|
||||||
val expected = AutofillView.Login.Username(
|
val expected = AutofillView.Login.Username(
|
||||||
autofillId = expectedAutofillId,
|
autofillId = expectedAutofillId,
|
||||||
|
idPackage = ID_PACKAGE,
|
||||||
isFocused = expectedIsFocused,
|
isFocused = expectedIsFocused,
|
||||||
|
webDomain = WEB_DOMAIN,
|
||||||
|
webScheme = WEB_SCHEME,
|
||||||
)
|
)
|
||||||
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
||||||
|
|
||||||
|
@ -158,7 +182,10 @@ class ViewNodeExtensionsTest {
|
||||||
val autofillHintTwo = View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR
|
val autofillHintTwo = View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR
|
||||||
val expected = AutofillView.Card.ExpirationYear(
|
val expected = AutofillView.Card.ExpirationYear(
|
||||||
autofillId = expectedAutofillId,
|
autofillId = expectedAutofillId,
|
||||||
|
idPackage = ID_PACKAGE,
|
||||||
isFocused = expectedIsFocused,
|
isFocused = expectedIsFocused,
|
||||||
|
webDomain = WEB_DOMAIN,
|
||||||
|
webScheme = WEB_SCHEME,
|
||||||
)
|
)
|
||||||
every { viewNode.autofillHints } returns arrayOf(autofillHintOne, autofillHintTwo)
|
every { viewNode.autofillHints } returns arrayOf(autofillHintOne, autofillHintTwo)
|
||||||
|
|
||||||
|
@ -168,4 +195,10 @@ class ViewNodeExtensionsTest {
|
||||||
// Verify
|
// Verify
|
||||||
assertEquals(expected, actual)
|
assertEquals(expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ID_PACKAGE: String = "ID_PACKAGE"
|
||||||
|
private const val WEB_DOMAIN: String = "WEB_DOMAIN"
|
||||||
|
private const val WEB_SCHEME: String = "WEB_SCHEME"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,270 @@
|
||||||
|
package com.x8bit.bitwarden.data.autofill.util
|
||||||
|
|
||||||
|
import android.app.assist.AssistStructure
|
||||||
|
import com.x8bit.bitwarden.data.autofill.model.AutofillView
|
||||||
|
import com.x8bit.bitwarden.data.autofill.model.ViewNodeTraversalData
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertNull
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class ViewNodeTraversalDataExtensionsTest {
|
||||||
|
private val windowNode: AssistStructure.WindowNode = mockk()
|
||||||
|
private val assistStructure: AssistStructure = mockk {
|
||||||
|
every { this@mockk.windowNodeCount } returns 1
|
||||||
|
every { this@mockk.getWindowNodeAt(0) } returns windowNode
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `buildUriOrNull should return URI when contains valid domain and scheme`() {
|
||||||
|
// Setup
|
||||||
|
val autofillView = AutofillView.Card.Number(
|
||||||
|
autofillId = mockk(),
|
||||||
|
idPackage = null,
|
||||||
|
isFocused = false,
|
||||||
|
webDomain = WEB_DOMAIN,
|
||||||
|
webScheme = WEB_SCHEME,
|
||||||
|
)
|
||||||
|
val viewNodeTraversalData = ViewNodeTraversalData(
|
||||||
|
autofillViews = listOf(
|
||||||
|
autofillView,
|
||||||
|
),
|
||||||
|
ignoreAutofillIds = emptyList(),
|
||||||
|
)
|
||||||
|
val expected = "$WEB_SCHEME://$WEB_DOMAIN"
|
||||||
|
|
||||||
|
// Test
|
||||||
|
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
|
||||||
|
assistStructure = assistStructure,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `buildUriOrNull should return URI with default scheme when domain valid and scheme null`() {
|
||||||
|
// Setup
|
||||||
|
val autofillView = AutofillView.Card.Number(
|
||||||
|
autofillId = mockk(),
|
||||||
|
idPackage = null,
|
||||||
|
isFocused = false,
|
||||||
|
webDomain = WEB_DOMAIN,
|
||||||
|
webScheme = null,
|
||||||
|
)
|
||||||
|
val viewNodeTraversalData = ViewNodeTraversalData(
|
||||||
|
autofillViews = listOf(
|
||||||
|
autofillView,
|
||||||
|
),
|
||||||
|
ignoreAutofillIds = emptyList(),
|
||||||
|
)
|
||||||
|
val expected = "https://$WEB_DOMAIN"
|
||||||
|
|
||||||
|
// Test
|
||||||
|
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
|
||||||
|
assistStructure = assistStructure,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `buildUriOrNull should return URI with default scheme when domain valid and scheme empty`() {
|
||||||
|
// Setup
|
||||||
|
val autofillView = AutofillView.Card.Number(
|
||||||
|
autofillId = mockk(),
|
||||||
|
idPackage = null,
|
||||||
|
isFocused = false,
|
||||||
|
webDomain = WEB_DOMAIN,
|
||||||
|
webScheme = "",
|
||||||
|
)
|
||||||
|
val viewNodeTraversalData = ViewNodeTraversalData(
|
||||||
|
autofillViews = listOf(
|
||||||
|
autofillView,
|
||||||
|
),
|
||||||
|
ignoreAutofillIds = emptyList(),
|
||||||
|
)
|
||||||
|
val expected = "https://$WEB_DOMAIN"
|
||||||
|
|
||||||
|
// Test
|
||||||
|
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
|
||||||
|
assistStructure = assistStructure,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `buildUriOrNull should return idPackage URI when domain is null`() {
|
||||||
|
// Setup
|
||||||
|
val autofillView = AutofillView.Card.Number(
|
||||||
|
autofillId = mockk(),
|
||||||
|
idPackage = ID_PACKAGE,
|
||||||
|
isFocused = false,
|
||||||
|
webDomain = null,
|
||||||
|
webScheme = null,
|
||||||
|
)
|
||||||
|
val viewNodeTraversalData = ViewNodeTraversalData(
|
||||||
|
autofillViews = listOf(
|
||||||
|
autofillView,
|
||||||
|
),
|
||||||
|
ignoreAutofillIds = emptyList(),
|
||||||
|
)
|
||||||
|
val expected = "androidapp://$ID_PACKAGE"
|
||||||
|
|
||||||
|
// Test
|
||||||
|
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
|
||||||
|
assistStructure = assistStructure,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `buildUriOrNull should return idPackage URI when domain is empty`() {
|
||||||
|
// Setup
|
||||||
|
val autofillView = AutofillView.Card.Number(
|
||||||
|
autofillId = mockk(),
|
||||||
|
idPackage = ID_PACKAGE,
|
||||||
|
isFocused = false,
|
||||||
|
webDomain = null,
|
||||||
|
webScheme = null,
|
||||||
|
)
|
||||||
|
val viewNodeTraversalData = ViewNodeTraversalData(
|
||||||
|
autofillViews = listOf(
|
||||||
|
autofillView,
|
||||||
|
),
|
||||||
|
ignoreAutofillIds = emptyList(),
|
||||||
|
)
|
||||||
|
val expected = "androidapp://$ID_PACKAGE"
|
||||||
|
|
||||||
|
// Test
|
||||||
|
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
|
||||||
|
assistStructure = assistStructure,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `buildUriOrNull should return title URI when domain and idPackage are null`() {
|
||||||
|
// Setup
|
||||||
|
val autofillView = AutofillView.Card.Number(
|
||||||
|
autofillId = mockk(),
|
||||||
|
idPackage = null,
|
||||||
|
isFocused = false,
|
||||||
|
webDomain = null,
|
||||||
|
webScheme = null,
|
||||||
|
)
|
||||||
|
val viewNodeTraversalData = ViewNodeTraversalData(
|
||||||
|
autofillViews = listOf(
|
||||||
|
autofillView,
|
||||||
|
),
|
||||||
|
ignoreAutofillIds = emptyList(),
|
||||||
|
)
|
||||||
|
val expected = "androidapp://com.x8bit.bitwarden"
|
||||||
|
every { windowNode.title } returns "com.x8bit.bitwarden/path.deeper.into.app"
|
||||||
|
|
||||||
|
// Test
|
||||||
|
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
|
||||||
|
assistStructure = assistStructure,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `buildUriOrNull should return title URI when domain and idPackage are empty`() {
|
||||||
|
// Setup
|
||||||
|
val autofillView = AutofillView.Card.Number(
|
||||||
|
autofillId = mockk(),
|
||||||
|
idPackage = "",
|
||||||
|
isFocused = false,
|
||||||
|
webDomain = "",
|
||||||
|
webScheme = null,
|
||||||
|
)
|
||||||
|
val viewNodeTraversalData = ViewNodeTraversalData(
|
||||||
|
autofillViews = listOf(
|
||||||
|
autofillView,
|
||||||
|
),
|
||||||
|
ignoreAutofillIds = emptyList(),
|
||||||
|
)
|
||||||
|
val expected = "androidapp://com.x8bit.bitwarden"
|
||||||
|
every { windowNode.title } returns "com.x8bit.bitwarden/path.deeper.into.app"
|
||||||
|
|
||||||
|
// Test
|
||||||
|
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
|
||||||
|
assistStructure = assistStructure,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assertEquals(expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `buildUriOrNull should return null when title, domain, and idPackage are null`() {
|
||||||
|
// Setup
|
||||||
|
val autofillView = AutofillView.Card.Number(
|
||||||
|
autofillId = mockk(),
|
||||||
|
idPackage = null,
|
||||||
|
isFocused = false,
|
||||||
|
webDomain = null,
|
||||||
|
webScheme = null,
|
||||||
|
)
|
||||||
|
val viewNodeTraversalData = ViewNodeTraversalData(
|
||||||
|
autofillViews = listOf(
|
||||||
|
autofillView,
|
||||||
|
),
|
||||||
|
ignoreAutofillIds = emptyList(),
|
||||||
|
)
|
||||||
|
every { windowNode.title } returns null
|
||||||
|
|
||||||
|
// Test
|
||||||
|
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
|
||||||
|
assistStructure = assistStructure,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assertNull(actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `buildUriOrNull should return null when title, domain, and idPackage are empty`() {
|
||||||
|
// Setup
|
||||||
|
val autofillView = AutofillView.Card.Number(
|
||||||
|
autofillId = mockk(),
|
||||||
|
idPackage = "",
|
||||||
|
isFocused = false,
|
||||||
|
webDomain = "",
|
||||||
|
webScheme = null,
|
||||||
|
)
|
||||||
|
val viewNodeTraversalData = ViewNodeTraversalData(
|
||||||
|
autofillViews = listOf(
|
||||||
|
autofillView,
|
||||||
|
),
|
||||||
|
ignoreAutofillIds = emptyList(),
|
||||||
|
)
|
||||||
|
every { windowNode.title } returns ""
|
||||||
|
|
||||||
|
// Test
|
||||||
|
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
|
||||||
|
assistStructure = assistStructure,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
assertNull(actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ID_PACKAGE: String = "com.x8bit.bitwarden"
|
||||||
|
private const val WEB_DOMAIN: String = "www.google.com"
|
||||||
|
private const val WEB_SCHEME: String = "https"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue