Support other autofill types (#948)

This commit is contained in:
Lucas Kivi 2024-02-05 11:42:42 -06:00 committed by Álison Fernandes
parent 3e1674b9e3
commit da47e3fbbb
15 changed files with 451 additions and 41 deletions

View file

@ -32,8 +32,7 @@ class FillResponseBuilderImpl : FillResponseBuilder {
filledData
.filledPartitions
.forEach { filledPartition ->
// It won't be empty but we really don't want to make an empty dataset,
// it causes a crash.
// We really don't want to make an empty dataset as it causes a crash.
if (filledPartition.filledItems.isNotEmpty()) {
// We build a dataset for each filled partition. A filled partition is a
// copy of all the views that we are going to fill, loaded with the data

View file

@ -95,7 +95,7 @@ class FilledDataBuilderImpl(
inlinePresentationSpec: InlinePresentationSpec?,
): FilledPartition {
val filledItems = autofillViews
.map { autofillView ->
.mapNotNull { autofillView ->
val value = when (autofillView) {
is AutofillView.Card.ExpirationMonth -> autofillCipher.expirationMonth
is AutofillView.Card.ExpirationYear -> autofillCipher.expirationYear
@ -124,7 +124,7 @@ class FilledDataBuilderImpl(
inlinePresentationSpec: InlinePresentationSpec?,
): FilledPartition {
val filledItems = autofillViews
.map { autofillView ->
.mapNotNull { autofillView ->
val value = when (autofillView) {
is AutofillView.Login.Username -> autofillCipher.username
is AutofillView.Login.Password -> autofillCipher.password

View file

@ -11,11 +11,15 @@ sealed class AutofillView {
* The data important to a given [AutofillView].
*
* @param autofillId The [AutofillId] associated with this view.
* @param autofillOptions A list of autofill options that can be used to fill this view.
* @param autofillType The autofill field type. (ex: View.AUTOFILL_TYPE_TEXT)
* @param isFocused Whether the view is currently focused.
* @param textValue A text value that represents the input present in the field.
*/
data class Data(
val autofillId: AutofillId,
val autofillOptions: List<String>,
val autofillType: Int,
val isFocused: Boolean,
val textValue: String?,
)

View file

@ -7,14 +7,14 @@ import android.view.autofill.AutofillValue
*/
@Suppress("MagicNumber")
fun AutofillValue.extractMonthValue(
autofillOptions: List<String>?,
autofillOptions: List<String>,
): String? =
when {
this.isList && autofillOptions?.size == 13 -> {
this.isList && autofillOptions.size == 13 -> {
this.listValue.toString()
}
this.isList && autofillOptions?.size == 12 -> {
this.isList && autofillOptions.size == 12 -> {
(this.listValue + 1).toString()
}

View file

@ -1,17 +1,63 @@
package com.x8bit.bitwarden.data.autofill.util
import android.view.View
import android.view.autofill.AutofillValue
import com.x8bit.bitwarden.data.autofill.model.AutofillView
import com.x8bit.bitwarden.data.autofill.model.FilledItem
/**
* Convert this [AutofillView] into a [FilledItem].
* Convert this [AutofillView] into a [FilledItem]. Return null if not possible.
*/
fun AutofillView.buildFilledItemOrNull(
value: String,
): FilledItem =
// TODO: handle other autofill types (BIT-1457)
FilledItem(
autofillId = data.autofillId,
value = AutofillValue.forText(value),
)
): FilledItem? =
when (this.data.autofillType) {
View.AUTOFILL_TYPE_DATE -> {
value
.toLongOrNull()
?.let { AutofillValue.forDate(it) }
}
View.AUTOFILL_TYPE_LIST -> this.buildListAutofillValueOrNull(value = value)
View.AUTOFILL_TYPE_TEXT -> AutofillValue.forText(value)
View.AUTOFILL_TYPE_TOGGLE -> {
value
.toBooleanStrictOrNull()
?.let { AutofillValue.forToggle(it) }
}
else -> null
}
?.let { autofillValue ->
FilledItem(
autofillId = this.data.autofillId,
value = autofillValue,
)
}
/**
* Build a list [AutofillValue] out of [value] or return null if not possible.
*/
@Suppress("MagicNumber")
private fun AutofillView.buildListAutofillValueOrNull(
value: String,
): AutofillValue? =
if (this is AutofillView.Card.ExpirationMonth) {
val autofillOptionsSize = this.data.autofillOptions.size
// The idea here is that `value` is a numerical representation of a month.
val monthIndex = value.toIntOrNull()
when {
monthIndex == null -> null
// We expect there is some placeholder or empty space at the beginning of the list.
autofillOptionsSize == 13 -> AutofillValue.forList(monthIndex)
autofillOptionsSize >= monthIndex -> AutofillValue.forList(monthIndex - 1)
else -> null
}
} else {
this
.data
.autofillOptions
.indexOfFirst { it == value }
.takeIf { it != -1 }
?.let { AutofillValue.forList(it) }
}

View file

@ -76,12 +76,20 @@ fun AssistStructure.ViewNode.toAutofillView(): AutofillView? =
?.firstOrNull { SUPPORTED_VIEW_HINTS.contains(it) }
if (supportedHint != null || this.isInputField) {
val autofillOptions = this
.autofillOptions
.orEmpty()
.map { it.toString() }
val autofillViewData = AutofillView.Data(
autofillId = nonNullAutofillId,
isFocused = isFocused,
autofillOptions = autofillOptions,
autofillType = this.autofillType,
isFocused = this.isFocused,
textValue = this.autofillValue?.extractTextValue(),
)
buildAutofillView(
autofillOptions = autofillOptions,
autofillViewData = autofillViewData,
supportedHint = supportedHint,
)
@ -94,13 +102,11 @@ fun AssistStructure.ViewNode.toAutofillView(): AutofillView? =
* Attempt to convert this [AssistStructure.ViewNode] and [autofillViewData] into an [AutofillView].
*/
private fun AssistStructure.ViewNode.buildAutofillView(
autofillOptions: List<String>,
autofillViewData: AutofillView.Data,
supportedHint: String?,
): AutofillView? = when {
supportedHint == View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH -> {
val autofillOptions = this
.autofillOptions
?.map { it.toString() }
val monthValue = this
.autofillValue
?.extractMonthValue(

View file

@ -5,6 +5,7 @@ import android.content.IntentSender
import android.service.autofill.Dataset
import android.service.autofill.FillResponse
import android.service.autofill.SaveInfo
import android.view.View
import android.view.autofill.AutofillId
import com.x8bit.bitwarden.data.autofill.model.AutofillAppInfo
import com.x8bit.bitwarden.data.autofill.model.AutofillCipher
@ -150,6 +151,8 @@ class FillResponseBuilderTest {
AutofillView.Login.Username(
data = AutofillView.Data(
autofillId = mockk(),
autofillOptions = emptyList(),
autofillType = AUTOFILL_TYPE,
isFocused = true,
textValue = null,
),
@ -259,6 +262,8 @@ class FillResponseBuilderTest {
AutofillView.Login.Username(
data = AutofillView.Data(
autofillId = mockk(),
autofillOptions = emptyList(),
autofillType = AUTOFILL_TYPE,
isFocused = true,
textValue = null,
),
@ -341,6 +346,7 @@ class FillResponseBuilderTest {
}
companion object {
private const val AUTOFILL_TYPE: Int = View.AUTOFILL_TYPE_TEXT
private const val CIPHER_ID: String = "1234567890"
private const val PACKAGE_NAME: String = "com.x8bit.bitwarden"
}

View file

@ -47,8 +47,9 @@ class FilledDataBuilderTest {
unmockkStatic(AutofillView::buildFilledItemOrNull)
}
@Suppress("MaxLineLength")
@Test
fun `build should return filled data and ignored AutofillIds when Login`() = runTest {
fun `build should skip null AutofillValues and return filled data and ignored AutofillIds when Login`() = runTest {
// Setup
val password = "Password"
val username = "johnDoe"
@ -65,13 +66,17 @@ class FilledDataBuilderTest {
val autofillViewPassword: AutofillView.Login.Password = mockk {
every { buildFilledItemOrNull(password) } returns filledItemPassword
}
val autofillViewUsername: AutofillView.Login.Username = mockk {
val autofillViewUsernameOne: AutofillView.Login.Username = mockk {
every { buildFilledItemOrNull(username) } returns filledItemUsername
}
val autofillViewUsernameTwo: AutofillView.Login.Username = mockk {
every { buildFilledItemOrNull(username) } returns null
}
val autofillPartition = AutofillPartition.Login(
views = listOf(
autofillViewPassword,
autofillViewUsername,
autofillViewUsernameOne,
autofillViewUsernameTwo,
),
)
val ignoreAutofillIds: List<AutofillId> = mockk()
@ -121,7 +126,8 @@ class FilledDataBuilderTest {
}
verify(exactly = 1) {
autofillViewPassword.buildFilledItemOrNull(password)
autofillViewUsername.buildFilledItemOrNull(username)
autofillViewUsernameOne.buildFilledItemOrNull(username)
autofillViewUsernameTwo.buildFilledItemOrNull(username)
}
}
@ -164,8 +170,9 @@ class FilledDataBuilderTest {
assertEquals(expected, actual)
}
@Suppress("MaxLineLength")
@Test
fun `build should return filled data and ignored AutofillIds when Card`() = runTest {
fun `build should skip null AutofillValues and return filled data and ignored AutofillIds when Card`() = runTest {
// Setup
val code = "123"
val expirationMonth = "January"
@ -194,15 +201,19 @@ class FilledDataBuilderTest {
val autofillViewExpirationYear: AutofillView.Card.ExpirationYear = mockk {
every { buildFilledItemOrNull(expirationYear) } returns filledItemExpirationYear
}
val autofillViewNumber: AutofillView.Card.Number = mockk {
val autofillViewNumberOne: AutofillView.Card.Number = mockk {
every { buildFilledItemOrNull(number) } returns filledItemNumber
}
val autofillViewNumberTwo: AutofillView.Card.Number = mockk {
every { buildFilledItemOrNull(number) } returns null
}
val autofillPartition = AutofillPartition.Card(
views = listOf(
autofillViewCode,
autofillViewExpirationMonth,
autofillViewExpirationYear,
autofillViewNumber,
autofillViewNumberOne,
autofillViewNumberTwo,
),
)
val ignoreAutofillIds: List<AutofillId> = mockk()
@ -248,7 +259,8 @@ class FilledDataBuilderTest {
autofillViewCode.buildFilledItemOrNull(code)
autofillViewExpirationMonth.buildFilledItemOrNull(expirationMonth)
autofillViewExpirationYear.buildFilledItemOrNull(expirationYear)
autofillViewNumber.buildFilledItemOrNull(number)
autofillViewNumberOne.buildFilledItemOrNull(number)
autofillViewNumberTwo.buildFilledItemOrNull(number)
}
}

View file

@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.autofill.builder
import android.os.Build
import android.service.autofill.FillRequest
import android.service.autofill.SaveInfo
import android.view.View
import android.view.autofill.AutofillId
import com.x8bit.bitwarden.data.autofill.model.AutofillAppInfo
import com.x8bit.bitwarden.data.autofill.model.AutofillPartition
@ -30,12 +31,16 @@ class SaveInfoBuilderTest {
private val autofillIdOptional: AutofillId = mockk()
private val autofillViewDataOptional = AutofillView.Data(
autofillId = autofillIdOptional,
autofillOptions = emptyList(),
autofillType = View.AUTOFILL_TYPE_TEXT,
isFocused = true,
textValue = null,
)
private val autofillIdValid: AutofillId = mockk()
private val autofillViewDataValid = AutofillView.Data(
autofillId = autofillIdValid,
autofillOptions = emptyList(),
autofillType = View.AUTOFILL_TYPE_TEXT,
isFocused = true,
textValue = null,
)

View file

@ -187,6 +187,8 @@ class AutofillParserTests {
val parentAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
data = AutofillView.Data(
autofillId = parentAutofillId,
autofillOptions = emptyList(),
autofillType = AUTOFILL_TYPE,
isFocused = true,
textValue = null,
),
@ -247,6 +249,8 @@ class AutofillParserTests {
val cardAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
data = AutofillView.Data(
autofillId = cardAutofillId,
autofillOptions = emptyList(),
autofillType = AUTOFILL_TYPE,
isFocused = true,
textValue = null,
),
@ -255,6 +259,8 @@ class AutofillParserTests {
val loginAutofillView: AutofillView.Login = AutofillView.Login.Username(
data = AutofillView.Data(
autofillId = loginAutofillId,
autofillOptions = emptyList(),
autofillType = AUTOFILL_TYPE,
isFocused = false,
textValue = null,
),
@ -302,6 +308,8 @@ class AutofillParserTests {
val cardAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
data = AutofillView.Data(
autofillId = cardAutofillId,
autofillOptions = emptyList(),
autofillType = AUTOFILL_TYPE,
isFocused = false,
textValue = null,
),
@ -310,6 +318,8 @@ class AutofillParserTests {
val loginAutofillView: AutofillView.Login = AutofillView.Login.Username(
data = AutofillView.Data(
autofillId = loginAutofillId,
autofillOptions = emptyList(),
autofillType = AUTOFILL_TYPE,
isFocused = true,
textValue = null,
),
@ -357,6 +367,8 @@ class AutofillParserTests {
val cardAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
data = AutofillView.Data(
autofillId = cardAutofillId,
autofillOptions = emptyList(),
autofillType = AUTOFILL_TYPE,
isFocused = true,
textValue = null,
),
@ -365,6 +377,8 @@ class AutofillParserTests {
val loginAutofillView: AutofillView.Login = AutofillView.Login.Username(
data = AutofillView.Data(
autofillId = loginAutofillId,
autofillOptions = emptyList(),
autofillType = AUTOFILL_TYPE,
isFocused = true,
textValue = null,
),
@ -413,6 +427,8 @@ class AutofillParserTests {
val cardAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
data = AutofillView.Data(
autofillId = cardAutofillId,
autofillOptions = emptyList(),
autofillType = AUTOFILL_TYPE,
isFocused = true,
textValue = null,
),
@ -421,6 +437,8 @@ class AutofillParserTests {
val loginAutofillView: AutofillView.Login = AutofillView.Login.Username(
data = AutofillView.Data(
autofillId = loginAutofillId,
autofillOptions = emptyList(),
autofillType = AUTOFILL_TYPE,
isFocused = true,
textValue = null,
),
@ -469,6 +487,8 @@ class AutofillParserTests {
val cardAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
data = AutofillView.Data(
autofillId = cardAutofillId,
autofillOptions = emptyList(),
autofillType = AUTOFILL_TYPE,
isFocused = true,
textValue = null,
),
@ -477,6 +497,8 @@ class AutofillParserTests {
val loginAutofillView: AutofillView.Login = AutofillView.Login.Username(
data = AutofillView.Data(
autofillId = loginAutofillId,
autofillOptions = emptyList(),
autofillType = AUTOFILL_TYPE,
isFocused = true,
textValue = null,
),
@ -524,6 +546,8 @@ class AutofillParserTests {
val cardAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
data = AutofillView.Data(
autofillId = cardAutofillId,
autofillOptions = emptyList(),
autofillType = AUTOFILL_TYPE,
isFocused = true,
textValue = null,
),
@ -532,6 +556,8 @@ class AutofillParserTests {
val loginAutofillView: AutofillView.Login = AutofillView.Login.Username(
data = AutofillView.Data(
autofillId = loginAutofillId,
autofillOptions = emptyList(),
autofillType = AUTOFILL_TYPE,
isFocused = true,
textValue = null,
),
@ -589,6 +615,7 @@ private val BLOCK_LISTED_URIS: List<String> = listOf(
"androidapp://com.x8bit.bitwarden",
"androidapp://com.oneplus.applocker",
)
private const val AUTOFILL_TYPE: Int = View.AUTOFILL_TYPE_TEXT
private const val ID_PACKAGE: String = "com.x8bit.bitwarden"
private const val MAX_INLINE_SUGGESTION_COUNT: Int = 42
private const val PACKAGE_NAME: String = "com.google"

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.autofill.util
import android.view.View
import com.x8bit.bitwarden.data.autofill.model.AutofillPartition
import com.x8bit.bitwarden.data.autofill.model.AutofillView
import io.mockk.mockk
@ -10,11 +11,15 @@ import org.junit.jupiter.api.Test
class AutofillPartitionExtensionsTest {
private val autofillDataEmptyText: AutofillView.Data = AutofillView.Data(
autofillId = mockk(),
autofillOptions = emptyList(),
autofillType = View.AUTOFILL_TYPE_TEXT,
isFocused = false,
textValue = null,
)
private val autofillDataValidText: AutofillView.Data = AutofillView.Data(
autofillId = mockk(),
autofillOptions = emptyList(),
autofillType = View.AUTOFILL_TYPE_TEXT,
isFocused = false,
textValue = TEXT_VALUE,
)

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.autofill.util
import android.view.View
import android.view.autofill.AutofillId
import android.view.autofill.AutofillValue
import com.x8bit.bitwarden.data.autofill.model.AutofillView
@ -11,6 +12,7 @@ import io.mockk.unmockkStatic
import io.mockk.verify
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@ -19,6 +21,8 @@ class AutofillViewExtensionsTest {
private val autofillValue: AutofillValue = mockk()
private val autofillViewData = AutofillView.Data(
autofillId = autofillId,
autofillOptions = emptyList(),
autofillType = View.AUTOFILL_TYPE_TEXT,
isFocused = false,
textValue = null,
)
@ -34,10 +38,202 @@ class AutofillViewExtensionsTest {
}
@Test
fun `buildFilledItem returns AutofillValue`() {
fun `buildFilledItemOrNull should return date value when date type`() {
// Setup
val value = "2002421451023587L"
val autofillView = AutofillView.Card.Number(
val rawValue = 2002421451023587L
val value = rawValue.toString()
val autofillViewData = autofillViewData.copy(
autofillType = View.AUTOFILL_TYPE_DATE,
)
val autofillView = AutofillView.Card.ExpirationYear(
data = autofillViewData,
)
val expected = FilledItem(
autofillId = autofillId,
value = autofillValue,
)
every { AutofillValue.forDate(rawValue) } returns autofillValue
// Test
val actual = autofillView.buildFilledItemOrNull(
value = value,
)
// Verify
assertEquals(expected, actual)
verify(exactly = 1) {
AutofillValue.forDate(rawValue)
}
}
@Test
fun `buildFilledItemOrNull should return null when date type but non-numerical value`() {
// Setup
val value = "January 1, 2024"
val autofillViewData = autofillViewData.copy(
autofillType = View.AUTOFILL_TYPE_DATE,
)
val autofillView = AutofillView.Card.ExpirationYear(
data = autofillViewData,
)
// Test
val actual = autofillView.buildFilledItemOrNull(
value = value,
)
// Verify
assertNull(actual)
}
@Suppress("MaxLineLength")
@Test
fun `buildFilledItemOrNull should return null when is expiration month value, list type, and non-numerical value`() {
// Setup
val value = "January"
val autofillViewData = autofillViewData.copy(
autofillType = View.AUTOFILL_TYPE_LIST,
)
val autofillView = AutofillView.Card.ExpirationMonth(
data = autofillViewData,
monthValue = null,
)
// Test
val actual = autofillView.buildFilledItemOrNull(
value = value,
)
// Verify
assertNull(actual)
}
@Suppress("MaxLineLength")
@Test
fun `buildFilledItemOrNull should return list value when is expiration list value, list type, and 13 options`() {
// Setup
val rawValue = 1
val value = rawValue.toString()
val autofillViewData = autofillViewData.copy(
autofillType = View.AUTOFILL_TYPE_LIST,
autofillOptions = List(13) { it.toString() },
)
val autofillView = AutofillView.Card.ExpirationMonth(
data = autofillViewData,
monthValue = null,
)
val expected = FilledItem(
autofillId = autofillId,
value = autofillValue,
)
every { AutofillValue.forList(rawValue) } returns autofillValue
// Test
val actual = autofillView.buildFilledItemOrNull(
value = value,
)
// Verify
assertEquals(expected, actual)
verify(exactly = 1) {
AutofillValue.forList(rawValue)
}
}
@Suppress("MaxLineLength")
@Test
fun `buildFilledItemOrNull should return list value minus one when is expiration list value, list type, and options size is greater than month value`() {
// Setup
val rawValue = 1
val value = (rawValue).toString()
val expectedListValue = rawValue - 1
val autofillViewData = autofillViewData.copy(
autofillType = View.AUTOFILL_TYPE_LIST,
autofillOptions = List(12) { it.toString() },
)
val autofillView = AutofillView.Card.ExpirationMonth(
data = autofillViewData,
monthValue = null,
)
val expected = FilledItem(
autofillId = autofillId,
value = autofillValue,
)
every { AutofillValue.forList(expectedListValue) } returns autofillValue
// Test
val actual = autofillView.buildFilledItemOrNull(
value = value,
)
// Verify
assertEquals(expected, actual)
verify(exactly = 1) {
AutofillValue.forList(expectedListValue)
}
}
@Suppress("MaxLineLength")
@Test
fun `buildFilledItemOrNull should return index list value when list type, and value is found in options`() {
// Setup
val expectedValue = 1
val value = "2025"
val autofillViewData = autofillViewData.copy(
autofillType = View.AUTOFILL_TYPE_LIST,
autofillOptions = listOf("2024", value, "2026"),
)
val autofillView = AutofillView.Card.ExpirationYear(
data = autofillViewData,
)
val expected = FilledItem(
autofillId = autofillId,
value = autofillValue,
)
every { AutofillValue.forList(expectedValue) } returns autofillValue
// Test
val actual = autofillView.buildFilledItemOrNull(
value = value,
)
// Verify
assertEquals(expected, actual)
verify(exactly = 1) {
AutofillValue.forList(expectedValue)
}
}
@Suppress("MaxLineLength")
@Test
fun `buildFilledItemOrNull should return null when list type, and value is not found in options`() {
// Setup
val value = "2027"
val autofillViewData = autofillViewData.copy(
autofillType = View.AUTOFILL_TYPE_LIST,
autofillOptions = listOf("2024", "2025", "2026"),
)
val autofillView = AutofillView.Card.ExpirationYear(
data = autofillViewData,
)
// Test
val actual = autofillView.buildFilledItemOrNull(
value = value,
)
// Verify
assertNull(actual)
}
@Test
fun `buildFilledItemOrNull should return text value when text type`() {
// Setup
val value = "Jimmy"
val autofillViewData = autofillViewData.copy(
autofillType = View.AUTOFILL_TYPE_TEXT,
)
val autofillView = AutofillView.Login.Username(
data = autofillViewData,
)
val expected = FilledItem(
@ -57,4 +253,73 @@ class AutofillViewExtensionsTest {
AutofillValue.forText(value)
}
}
@Test
fun `buildFilledItemOrNull should return toggle value when toggle type and value is boolean`() {
// Setup
val value = "false"
val expectedValue = value.toBooleanStrict()
val autofillViewData = autofillViewData.copy(
autofillType = View.AUTOFILL_TYPE_TOGGLE,
)
val autofillView = AutofillView.Login.Username(
data = autofillViewData,
)
val expected = FilledItem(
autofillId = autofillId,
value = autofillValue,
)
every { AutofillValue.forToggle(expectedValue) } returns autofillValue
// Test
val actual = autofillView.buildFilledItemOrNull(
value = value,
)
// Verify
assertEquals(expected, actual)
verify(exactly = 1) {
AutofillValue.forToggle(expectedValue)
}
}
@Test
fun `buildFilledItemOrNull should return null when toggle type and value not boolean`() {
// Setup
val value = "Jimmy"
val autofillViewData = autofillViewData.copy(
autofillType = View.AUTOFILL_TYPE_TOGGLE,
)
val autofillView = AutofillView.Login.Username(
data = autofillViewData,
)
// Test
val actual = autofillView.buildFilledItemOrNull(
value = value,
)
// Verify
assertNull(actual)
}
@Test
fun `buildFilledItemOrNull should return null when not recognized type`() {
// Setup
val value = "Jimmy"
val autofillViewData = autofillViewData.copy(
autofillType = 100,
)
val autofillView = AutofillView.Login.Username(
data = autofillViewData,
)
// Test
val actual = autofillView.buildFilledItemOrNull(
value = value,
)
// Verify
assertNull(actual)
}
}

View file

@ -7,6 +7,7 @@ import android.content.res.Resources
import android.service.autofill.Dataset
import android.service.autofill.InlinePresentation
import android.service.autofill.Presentations
import android.view.View
import android.view.autofill.AutofillId
import android.view.autofill.AutofillValue
import android.widget.RemoteViews
@ -69,6 +70,8 @@ class FilledDataExtensionsTest {
AutofillView.Login.Username(
data = AutofillView.Data(
autofillId = autofillId,
autofillOptions = emptyList(),
autofillType = View.AUTOFILL_TYPE_TEXT,
isFocused = true,
textValue = null,
),

View file

@ -23,18 +23,17 @@ class ViewNodeExtensionsTest {
private val expectedIsFocused = true
private val autofillViewData = AutofillView.Data(
autofillId = expectedAutofillId,
autofillOptions = AUTOFILL_OPTIONS_LIST,
autofillType = AUTOFILL_TYPE,
isFocused = expectedIsFocused,
textValue = TEXT_VALUE,
)
private val testAutofillOptions = arrayOf(
"option one",
"option two",
)
private val testAutofillValue: AutofillValue = mockk()
private val viewNode: AssistStructure.ViewNode = mockk {
every { this@mockk.autofillId } returns expectedAutofillId
every { this@mockk.autofillOptions } returns testAutofillOptions
every { this@mockk.autofillOptions } returns AUTOFILL_OPTIONS_ARRAY
every { this@mockk.autofillType } returns AUTOFILL_TYPE
every { this@mockk.autofillValue } returns testAutofillValue
every { this@mockk.childCount } returns 0
every { this@mockk.inputType } returns 1
@ -51,7 +50,7 @@ class ViewNodeExtensionsTest {
mockkStatic(AutofillValue::extractTextValue)
every {
testAutofillValue.extractMonthValue(
autofillOptions = testAutofillOptions.toList(),
autofillOptions = AUTOFILL_OPTIONS_LIST,
)
} returns MONTH_VALUE
every { testAutofillValue.extractTextValue() } returns TEXT_VALUE
@ -84,6 +83,34 @@ class ViewNodeExtensionsTest {
assertEquals(expected, actual)
}
@Suppress("MaxLineLength")
@Test
fun `toAutofillView should return AutofillView Card ExpirationMonth with empty options when hint matches and options are null`() {
// Setup
val autofillViewData = autofillViewData.copy(
autofillOptions = emptyList(),
)
val autofillHint = View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH
val monthValue = "11"
val expected = AutofillView.Card.ExpirationMonth(
data = autofillViewData,
monthValue = monthValue,
)
every { viewNode.autofillHints } returns arrayOf(autofillHint)
every { viewNode.autofillOptions } returns null
every {
testAutofillValue.extractMonthValue(
autofillOptions = emptyList(),
)
} returns monthValue
// Test
val actual = viewNode.toAutofillView()
// Verify
assertEquals(expected, actual)
}
@Test
fun `toAutofillView should return AutofillView Card ExpirationYear when hint matches`() {
// Setup
@ -478,6 +505,17 @@ class ViewNodeExtensionsTest {
}
}
private const val AUTOFILL_OPTION_ONE: String = "AUTOFILL_OPTION_ONE"
private const val AUTOFILL_OPTION_TWO: String = "AUTOFILL_OPTION_TWO"
private val AUTOFILL_OPTIONS_ARRAY: Array<CharSequence> = arrayOf(
AUTOFILL_OPTION_ONE,
AUTOFILL_OPTION_TWO,
)
private val AUTOFILL_OPTIONS_LIST: List<String> = listOf(
AUTOFILL_OPTION_ONE,
AUTOFILL_OPTION_TWO,
)
private const val AUTOFILL_TYPE: Int = View.AUTOFILL_TYPE_LIST
private const val ANDROID_EDIT_TEXT_CLASS_NAME: String = "android.widget.EditText"
private val IGNORED_RAW_HINTS: List<String> = listOf(
"search",

View file

@ -1,7 +1,6 @@
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
@ -15,11 +14,6 @@ class ViewNodeTraversalDataExtensionsTest {
every { this@mockk.windowNodeCount } returns 1
every { this@mockk.getWindowNodeAt(0) } returns windowNode
}
private val autofillViewData = AutofillView.Data(
autofillId = mockk(),
isFocused = false,
textValue = null,
)
@Test
fun `buildUriOrNull should return website URI when present`() {