mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
Support other autofill types (#948)
This commit is contained in:
parent
3e1674b9e3
commit
da47e3fbbb
15 changed files with 451 additions and 41 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?,
|
||||
)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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`() {
|
||||
|
|
Loading…
Add table
Reference in a new issue