mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
Refactor autofill flow to partition fill data by cipher (#571)
This commit is contained in:
parent
faca927c0f
commit
ff9dd81c55
14 changed files with 442 additions and 193 deletions
|
@ -1,11 +1,9 @@
|
|||
package com.x8bit.bitwarden.data.autofill.builder
|
||||
|
||||
import android.service.autofill.Dataset
|
||||
import android.service.autofill.FillResponse
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillAppInfo
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledData
|
||||
import com.x8bit.bitwarden.data.autofill.util.applyOverlayToDataset
|
||||
import com.x8bit.bitwarden.ui.autofill.buildAutofillRemoteViews
|
||||
import com.x8bit.bitwarden.data.autofill.util.buildDataset
|
||||
|
||||
/**
|
||||
* The default implementation for [FillResponseBuilder]. This is a component for compiling fulfilled
|
||||
|
@ -16,32 +14,35 @@ class FillResponseBuilderImpl : FillResponseBuilder {
|
|||
autofillAppInfo: AutofillAppInfo,
|
||||
filledData: FilledData,
|
||||
): FillResponse? =
|
||||
if (filledData.filledItems.isNotEmpty()) {
|
||||
val remoteViewsPlaceholder = buildAutofillRemoteViews(
|
||||
packageName = autofillAppInfo.packageName,
|
||||
context = autofillAppInfo.context,
|
||||
)
|
||||
val datasetBuilder = Dataset.Builder()
|
||||
if (filledData.filledPartitions.any { it.filledItems.isNotEmpty() }) {
|
||||
val fillResponseBuilder = FillResponse.Builder()
|
||||
|
||||
// Set UI for each valid autofill view.
|
||||
filledData.filledItems.forEach { filledItem ->
|
||||
filledItem.applyOverlayToDataset(
|
||||
appInfo = autofillAppInfo,
|
||||
datasetBuilder = datasetBuilder,
|
||||
remoteViews = remoteViewsPlaceholder,
|
||||
filledData
|
||||
.filledPartitions
|
||||
.forEach { filledPartition ->
|
||||
// It won't be empty but we really don't want to make an empty dataset,
|
||||
// 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
|
||||
// from one of the ciphers that can fulfill this partition type.
|
||||
val dataset = filledPartition.buildDataset(
|
||||
autofillAppInfo = autofillAppInfo,
|
||||
)
|
||||
|
||||
// Load the dataset into the fill request.
|
||||
fillResponseBuilder.addDataset(dataset)
|
||||
}
|
||||
val dataset = datasetBuilder.build()
|
||||
FillResponse.Builder()
|
||||
.addDataset(dataset)
|
||||
}
|
||||
fillResponseBuilder
|
||||
.setIgnoredIds(*filledData.ignoreAutofillIds.toTypedArray())
|
||||
.build()
|
||||
} else {
|
||||
// It is impossible for an `AutofillPartition` to be empty due to the way it is
|
||||
// constructed. However, the [FillRequest] requires at least one dataset or an
|
||||
// authentication intent with a presentation view. Neither of these make sense in the
|
||||
// case where we have no views to fill. What we are supposed to do when we cannot
|
||||
// fulfill a request is replace [FillResponse] with null in order to avoid this crash.
|
||||
// It is impossible for [filledData] to be empty due to the way it is constructed.
|
||||
// However, the [FillRequest] requires at least one dataset or an authentication intent
|
||||
// with a presentation view. Neither of these make sense in the case where we have no
|
||||
// views to fill. What we are supposed to do when we cannot fulfill a request is
|
||||
// replace [FillResponse] with null in order to avoid this crash.
|
||||
null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package com.x8bit.bitwarden.data.autofill.builder
|
||||
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillPartition
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillRequest
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillView
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledData
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledPartition
|
||||
|
||||
/**
|
||||
* The default [FilledDataBuilder]. This converts parsed autofill data into filled data that is
|
||||
|
@ -13,24 +15,65 @@ class FilledDataBuilderImpl : FilledDataBuilder {
|
|||
override suspend fun build(autofillRequest: AutofillRequest.Fillable): FilledData {
|
||||
// TODO: determine whether or not the vault is locked (BIT-1296)
|
||||
|
||||
val filledItems = autofillRequest
|
||||
.partition
|
||||
.views
|
||||
.map(AutofillView::toFilledItem)
|
||||
|
||||
val filledPartitions = when (autofillRequest.partition) {
|
||||
is AutofillPartition.Card -> {
|
||||
// TODO: perform fulfillment with dummy data (BIT-1315)
|
||||
listOf(
|
||||
fillCardPartition(
|
||||
autofillViews = autofillRequest.partition.views,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
is AutofillPartition.Login -> {
|
||||
// TODO: perform fulfillment with dummy data (BIT-1315)
|
||||
listOf(
|
||||
fillLoginPartition(
|
||||
autofillViews = autofillRequest.partition.views,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return FilledData(
|
||||
filledItems = filledItems,
|
||||
filledPartitions = filledPartitions,
|
||||
ignoreAutofillIds = autofillRequest.ignoreAutofillIds,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map this [AutofillView] to a [FilledItem].
|
||||
/**
|
||||
* Construct a [FilledPartition] by fulfilling the card [autofillViews] with data.
|
||||
*/
|
||||
private fun AutofillView.toFilledItem(): FilledItem =
|
||||
private fun fillCardPartition(
|
||||
autofillViews: List<AutofillView.Card>,
|
||||
): FilledPartition {
|
||||
val filledItems = autofillViews
|
||||
.map { autofillView ->
|
||||
FilledItem(
|
||||
autofillId = autofillId,
|
||||
autofillId = autofillView.autofillId,
|
||||
)
|
||||
}
|
||||
|
||||
return FilledPartition(
|
||||
filledItems = filledItems,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a [FilledPartition] by fulfilling the login [autofillViews] with data.
|
||||
*/
|
||||
private fun fillLoginPartition(
|
||||
autofillViews: List<AutofillView.Login>,
|
||||
): FilledPartition {
|
||||
val filledItems = autofillViews
|
||||
.map { autofillView ->
|
||||
FilledItem(
|
||||
autofillId = autofillView.autofillId,
|
||||
)
|
||||
}
|
||||
|
||||
return FilledPartition(
|
||||
filledItems = filledItems,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package com.x8bit.bitwarden.data.autofill.di
|
||||
|
||||
import com.x8bit.bitwarden.data.autofill.builder.FilledDataBuilder
|
||||
import com.x8bit.bitwarden.data.autofill.builder.FilledDataBuilderImpl
|
||||
import com.x8bit.bitwarden.data.autofill.builder.FillResponseBuilder
|
||||
import com.x8bit.bitwarden.data.autofill.builder.FillResponseBuilderImpl
|
||||
import com.x8bit.bitwarden.data.autofill.builder.FilledDataBuilder
|
||||
import com.x8bit.bitwarden.data.autofill.builder.FilledDataBuilderImpl
|
||||
import com.x8bit.bitwarden.data.autofill.parser.AutofillParser
|
||||
import com.x8bit.bitwarden.data.autofill.parser.AutofillParserImpl
|
||||
import com.x8bit.bitwarden.data.autofill.processor.AutofillProcessor
|
||||
|
|
|
@ -3,9 +3,10 @@ package com.x8bit.bitwarden.data.autofill.model
|
|||
import android.view.autofill.AutofillId
|
||||
|
||||
/**
|
||||
* The fulfilled autofill data to be loaded into the a fill response.
|
||||
* A fulfilled autofill dataset. This is all of the data to fulfill each view of the autofill
|
||||
* request for a given cipher.
|
||||
*/
|
||||
data class FilledData(
|
||||
val filledItems: List<FilledItem>,
|
||||
val filledPartitions: List<FilledPartition>,
|
||||
val ignoreAutofillIds: List<AutofillId>,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package com.x8bit.bitwarden.data.autofill.model
|
||||
|
||||
/**
|
||||
* All of the data required to build a `Dataset` for fulfilling a partition of data based on a
|
||||
* cipher.
|
||||
*
|
||||
* @param filledItems A filled copy of each view from this partition.
|
||||
*/
|
||||
data class FilledPartition(
|
||||
val filledItems: List<FilledItem>,
|
||||
)
|
|
@ -1,73 +1,40 @@
|
|||
package com.x8bit.bitwarden.data.autofill.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.service.autofill.Dataset
|
||||
import android.service.autofill.Field
|
||||
import android.service.autofill.Presentations
|
||||
import android.view.autofill.AutofillId
|
||||
import android.widget.RemoteViews
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillAppInfo
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledItem
|
||||
|
||||
/**
|
||||
* Apply this [FilledItem] to the dataset being built by [datasetBuilder] in the form of an
|
||||
* overlay presentation.
|
||||
*/
|
||||
fun FilledItem.applyOverlayToDataset(
|
||||
appInfo: AutofillAppInfo,
|
||||
datasetBuilder: Dataset.Builder,
|
||||
remoteViews: RemoteViews,
|
||||
) {
|
||||
if (appInfo.sdkInt >= Build.VERSION_CODES.TIRAMISU) {
|
||||
setOverlay(
|
||||
autoFillId = autofillId,
|
||||
datasetBuilder = datasetBuilder,
|
||||
remoteViews = remoteViews,
|
||||
)
|
||||
} else {
|
||||
setOverlayPreTiramisu(
|
||||
autoFillId = autofillId,
|
||||
datasetBuilder = datasetBuilder,
|
||||
remoteViews = remoteViews,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up an overlay presentation in the [datasetBuilder] for Android devices running on API
|
||||
* Tiramisu or greater.
|
||||
* Set up an overlay presentation for this [FilledItem] in the [datasetBuilder] for Android devices
|
||||
* running on API Tiramisu or greater.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
private fun setOverlay(
|
||||
autoFillId: AutofillId,
|
||||
fun FilledItem.applyToDatasetPostTiramisu(
|
||||
datasetBuilder: Dataset.Builder,
|
||||
remoteViews: RemoteViews,
|
||||
presentations: Presentations,
|
||||
) {
|
||||
val presentation = Presentations.Builder()
|
||||
.setMenuPresentation(remoteViews)
|
||||
.build()
|
||||
|
||||
datasetBuilder.setField(
|
||||
autoFillId,
|
||||
autofillId,
|
||||
Field.Builder()
|
||||
.setPresentations(presentation)
|
||||
.setPresentations(presentations)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up an overlay presentation in the [datasetBuilder] for Android devices running on APIs that
|
||||
* predate Tiramisu.
|
||||
* Set up an overlay presentation for this [FilledItem] in the [datasetBuilder] for Android devices
|
||||
* running on APIs that predate Tiramisu.
|
||||
*/
|
||||
@Suppress("Deprecation")
|
||||
private fun setOverlayPreTiramisu(
|
||||
autoFillId: AutofillId,
|
||||
fun FilledItem.applyToDatasetPreTiramisu(
|
||||
datasetBuilder: Dataset.Builder,
|
||||
remoteViews: RemoteViews,
|
||||
) {
|
||||
datasetBuilder.setValue(
|
||||
autoFillId,
|
||||
autofillId,
|
||||
null,
|
||||
remoteViews,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
package com.x8bit.bitwarden.data.autofill.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.service.autofill.Dataset
|
||||
import android.service.autofill.Presentations
|
||||
import android.widget.RemoteViews
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillAppInfo
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledPartition
|
||||
import com.x8bit.bitwarden.ui.autofill.buildAutofillRemoteViews
|
||||
|
||||
/**
|
||||
* Build a [Dataset] to represent the [FilledPartition]. This dataset includes an overlay UI
|
||||
* presentation for each filled item.
|
||||
*/
|
||||
fun FilledPartition.buildDataset(
|
||||
autofillAppInfo: AutofillAppInfo,
|
||||
): Dataset {
|
||||
val remoteViewsPlaceholder = buildAutofillRemoteViews(
|
||||
packageName = autofillAppInfo.packageName,
|
||||
title = autofillAppInfo.context.resources.getString(R.string.app_name),
|
||||
)
|
||||
val datasetBuilder = Dataset.Builder()
|
||||
|
||||
if (autofillAppInfo.sdkInt >= Build.VERSION_CODES.TIRAMISU) {
|
||||
applyToDatasetPostTiramisu(
|
||||
datasetBuilder = datasetBuilder,
|
||||
remoteViews = remoteViewsPlaceholder,
|
||||
)
|
||||
} else {
|
||||
buildDatasetPreTiramisu(
|
||||
datasetBuilder = datasetBuilder,
|
||||
remoteViews = remoteViewsPlaceholder,
|
||||
)
|
||||
}
|
||||
|
||||
return datasetBuilder.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply this [FilledPartition] to the [datasetBuilder] on devices running OS version Tiramisu or
|
||||
* greater.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
private fun FilledPartition.applyToDatasetPostTiramisu(
|
||||
datasetBuilder: Dataset.Builder,
|
||||
remoteViews: RemoteViews,
|
||||
) {
|
||||
val presentation = Presentations.Builder()
|
||||
.setMenuPresentation(remoteViews)
|
||||
.build()
|
||||
|
||||
filledItems.forEach { filledItem ->
|
||||
filledItem.applyToDatasetPostTiramisu(
|
||||
datasetBuilder = datasetBuilder,
|
||||
presentations = presentation,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply this [FilledPartition] to the [datasetBuilder] on devices running OS versions that predate
|
||||
* Tiramisu.
|
||||
*/
|
||||
private fun FilledPartition.buildDatasetPreTiramisu(
|
||||
datasetBuilder: Dataset.Builder,
|
||||
remoteViews: RemoteViews,
|
||||
) {
|
||||
filledItems.forEach { filledItem ->
|
||||
filledItem.applyToDatasetPreTiramisu(
|
||||
datasetBuilder = datasetBuilder,
|
||||
remoteViews = remoteViews,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package com.x8bit.bitwarden.ui.autofill
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.RemoteViews
|
||||
import com.x8bit.bitwarden.R
|
||||
|
||||
|
@ -8,8 +7,8 @@ import com.x8bit.bitwarden.R
|
|||
* Build [RemoteViews] for representing an autofill suggestion.
|
||||
*/
|
||||
fun buildAutofillRemoteViews(
|
||||
context: Context,
|
||||
packageName: String,
|
||||
title: String,
|
||||
): RemoteViews =
|
||||
RemoteViews(
|
||||
packageName,
|
||||
|
@ -18,6 +17,6 @@ fun buildAutofillRemoteViews(
|
|||
.apply {
|
||||
setTextViewText(
|
||||
R.id.text,
|
||||
context.resources.getText(R.string.app_name),
|
||||
title,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,21 +4,16 @@ import android.content.Context
|
|||
import android.service.autofill.Dataset
|
||||
import android.service.autofill.FillResponse
|
||||
import android.view.autofill.AutofillId
|
||||
import android.widget.RemoteViews
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillAppInfo
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledData
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledItem
|
||||
import com.x8bit.bitwarden.data.autofill.util.applyOverlayToDataset
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledPartition
|
||||
import com.x8bit.bitwarden.data.autofill.util.buildDataset
|
||||
import com.x8bit.bitwarden.data.util.mockBuilder
|
||||
import com.x8bit.bitwarden.ui.autofill.buildAutofillRemoteViews
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkConstructor
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkConstructor
|
||||
import io.mockk.unmockkStatic
|
||||
import io.mockk.verify
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
@ -29,31 +24,25 @@ import org.junit.jupiter.api.Test
|
|||
class FillResponseBuilderTest {
|
||||
private lateinit var fillResponseBuilder: FillResponseBuilder
|
||||
|
||||
private val dataset: Dataset = mockk()
|
||||
private val context: Context = mockk()
|
||||
private val dataset: Dataset = mockk()
|
||||
private val fillResponse: FillResponse = mockk()
|
||||
private val remoteViews: RemoteViews = mockk()
|
||||
private val appInfo: AutofillAppInfo = AutofillAppInfo(
|
||||
context = context,
|
||||
packageName = PACKAGE_NAME,
|
||||
sdkInt = 17,
|
||||
)
|
||||
private val autofillIdOne: AutofillId = mockk()
|
||||
private val autofillIdTwo: AutofillId = mockk()
|
||||
private val filledItemOne: FilledItem = mockk {
|
||||
every { this@mockk.autofillId } returns autofillIdOne
|
||||
private val filledPartitionOne: FilledPartition = mockk {
|
||||
every { this@mockk.filledItems } returns listOf(mockk())
|
||||
}
|
||||
private val filledItemTwo: FilledItem = mockk {
|
||||
every { this@mockk.autofillId } returns autofillIdTwo
|
||||
private val filledPartitionTwo: FilledPartition = mockk {
|
||||
every { this@mockk.filledItems } returns emptyList()
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkConstructor(Dataset.Builder::class)
|
||||
mockkConstructor(FillResponse.Builder::class)
|
||||
mockkStatic(::buildAutofillRemoteViews)
|
||||
mockkStatic(FilledItem::applyOverlayToDataset)
|
||||
every { anyConstructed<Dataset.Builder>().build() } returns dataset
|
||||
mockkStatic(FilledPartition::buildDataset)
|
||||
every { anyConstructed<FillResponse.Builder>().build() } returns fillResponse
|
||||
|
||||
fillResponseBuilder = FillResponseBuilderImpl()
|
||||
|
@ -61,17 +50,15 @@ class FillResponseBuilderTest {
|
|||
|
||||
@AfterEach
|
||||
fun teardown() {
|
||||
unmockkConstructor(Dataset.Builder::class)
|
||||
unmockkConstructor(FillResponse.Builder::class)
|
||||
unmockkStatic(::buildAutofillRemoteViews)
|
||||
unmockkStatic(FilledItem::applyOverlayToDataset)
|
||||
mockkStatic(FilledPartition::buildDataset)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `build should return null when filledItems empty`() {
|
||||
fun `build should return null when filledPartitions is empty`() {
|
||||
// Test
|
||||
val filledData = FilledData(
|
||||
filledItems = emptyList(),
|
||||
filledPartitions = emptyList(),
|
||||
ignoreAutofillIds = emptyList(),
|
||||
)
|
||||
val actual = fillResponseBuilder.build(
|
||||
|
@ -84,7 +71,28 @@ class FillResponseBuilderTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `build should apply filledItems and ignore ignoreAutofillIds`() {
|
||||
fun `build should return null when filledPartitions contains no views`() {
|
||||
// Test
|
||||
val filledPartitions = FilledPartition(
|
||||
filledItems = emptyList(),
|
||||
)
|
||||
val filledData = FilledData(
|
||||
filledPartitions = listOf(
|
||||
filledPartitions,
|
||||
),
|
||||
ignoreAutofillIds = emptyList(),
|
||||
)
|
||||
val actual = fillResponseBuilder.build(
|
||||
autofillAppInfo = appInfo,
|
||||
filledData = filledData,
|
||||
)
|
||||
|
||||
// Verify
|
||||
assertNull(actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `build should apply FilledPartitions with filledItems and ignore ignoreAutofillIds`() {
|
||||
// Setup
|
||||
val ignoredAutofillIdOne: AutofillId = mockk()
|
||||
val ignoredAutofillIdTwo: AutofillId = mockk()
|
||||
|
@ -92,34 +100,19 @@ class FillResponseBuilderTest {
|
|||
ignoredAutofillIdOne,
|
||||
ignoredAutofillIdTwo,
|
||||
)
|
||||
val filledItems = listOf(
|
||||
filledItemOne,
|
||||
filledItemTwo,
|
||||
val filledPartitions = listOf(
|
||||
filledPartitionOne,
|
||||
filledPartitionTwo,
|
||||
)
|
||||
val filledData = FilledData(
|
||||
filledItems = filledItems,
|
||||
filledPartitions = filledPartitions,
|
||||
ignoreAutofillIds = ignoreAutofillIds,
|
||||
)
|
||||
every {
|
||||
buildAutofillRemoteViews(
|
||||
context = context,
|
||||
packageName = PACKAGE_NAME,
|
||||
filledPartitionOne.buildDataset(
|
||||
autofillAppInfo = appInfo,
|
||||
)
|
||||
} returns remoteViews
|
||||
every {
|
||||
filledItemOne.applyOverlayToDataset(
|
||||
appInfo = appInfo,
|
||||
datasetBuilder = anyConstructed(),
|
||||
remoteViews = remoteViews,
|
||||
)
|
||||
} just runs
|
||||
every {
|
||||
filledItemTwo.applyOverlayToDataset(
|
||||
appInfo = appInfo,
|
||||
datasetBuilder = anyConstructed(),
|
||||
remoteViews = remoteViews,
|
||||
)
|
||||
} just runs
|
||||
} returns dataset
|
||||
mockBuilder<FillResponse.Builder> { it.addDataset(dataset) }
|
||||
mockBuilder<FillResponse.Builder> {
|
||||
it.setIgnoredIds(
|
||||
|
@ -138,15 +131,8 @@ class FillResponseBuilderTest {
|
|||
assertEquals(fillResponse, actual)
|
||||
|
||||
verify(exactly = 1) {
|
||||
filledItemOne.applyOverlayToDataset(
|
||||
appInfo = appInfo,
|
||||
datasetBuilder = any(),
|
||||
remoteViews = remoteViews,
|
||||
)
|
||||
filledItemTwo.applyOverlayToDataset(
|
||||
appInfo = appInfo,
|
||||
datasetBuilder = any(),
|
||||
remoteViews = remoteViews,
|
||||
filledPartitionOne.buildDataset(
|
||||
autofillAppInfo = appInfo,
|
||||
)
|
||||
anyConstructed<FillResponse.Builder>().addDataset(dataset)
|
||||
anyConstructed<FillResponse.Builder>().setIgnoredIds(
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.x8bit.bitwarden.data.autofill.model.AutofillRequest
|
|||
import com.x8bit.bitwarden.data.autofill.model.AutofillView
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledData
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledPartition
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
@ -21,7 +22,7 @@ class FilledDataBuilderTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `build should return FilledData with FilledItems and ignored AutofillIds`() = runTest {
|
||||
fun `build should return filled data and ignored AutofillIds when Login`() = runTest {
|
||||
// Setup
|
||||
val autofillId: AutofillId = mockk()
|
||||
val autofillView = AutofillView.Login.Username(
|
||||
|
@ -39,10 +40,55 @@ class FilledDataBuilderTest {
|
|||
val filledItem = FilledItem(
|
||||
autofillId = autofillId,
|
||||
)
|
||||
val expected = FilledData(
|
||||
val filledPartition = FilledPartition(
|
||||
filledItems = listOf(
|
||||
filledItem,
|
||||
),
|
||||
)
|
||||
val expected = FilledData(
|
||||
filledPartitions = listOf(
|
||||
filledPartition,
|
||||
),
|
||||
ignoreAutofillIds = ignoreAutofillIds,
|
||||
)
|
||||
|
||||
// Test
|
||||
val actual = filledDataBuilder.build(
|
||||
autofillRequest = autofillRequest,
|
||||
)
|
||||
|
||||
// Verify
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `build should return filled data and ignored AutofillIds when Card`() = runTest {
|
||||
// Setup
|
||||
val autofillId: AutofillId = mockk()
|
||||
val autofillView = AutofillView.Card.Number(
|
||||
autofillId = autofillId,
|
||||
isFocused = false,
|
||||
)
|
||||
val autofillPartition = AutofillPartition.Card(
|
||||
views = listOf(autofillView),
|
||||
)
|
||||
val ignoreAutofillIds: List<AutofillId> = mockk()
|
||||
val autofillRequest = AutofillRequest.Fillable(
|
||||
ignoreAutofillIds = ignoreAutofillIds,
|
||||
partition = autofillPartition,
|
||||
)
|
||||
val filledItem = FilledItem(
|
||||
autofillId = autofillId,
|
||||
)
|
||||
val filledPartition = FilledPartition(
|
||||
filledItems = listOf(
|
||||
filledItem,
|
||||
),
|
||||
)
|
||||
val expected = FilledData(
|
||||
filledPartitions = listOf(
|
||||
filledPartition,
|
||||
),
|
||||
ignoreAutofillIds = ignoreAutofillIds,
|
||||
)
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import com.x8bit.bitwarden.data.autofill.builder.FilledDataBuilder
|
|||
import com.x8bit.bitwarden.data.autofill.model.AutofillAppInfo
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillRequest
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledData
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledItem
|
||||
import com.x8bit.bitwarden.data.autofill.parser.AutofillParser
|
||||
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||
import io.mockk.coEvery
|
||||
|
@ -138,9 +137,8 @@ class AutofillProcessorTest {
|
|||
val fillRequest: FillRequest = mockk {
|
||||
every { this@mockk.fillContexts } returns fillContextList
|
||||
}
|
||||
val filledItems: List<FilledItem> = listOf(mockk())
|
||||
val filledData = FilledData(
|
||||
filledItems = filledItems,
|
||||
filledPartitions = listOf(mockk()),
|
||||
ignoreAutofillIds = emptyList(),
|
||||
)
|
||||
val fillResponse: FillResponse = mockk()
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
package com.x8bit.bitwarden.data.autofill.util
|
||||
|
||||
import android.content.Context
|
||||
import android.service.autofill.Dataset
|
||||
import android.service.autofill.Field
|
||||
import android.service.autofill.Presentations
|
||||
import android.view.autofill.AutofillId
|
||||
import android.widget.RemoteViews
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillAppInfo
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledItem
|
||||
import com.x8bit.bitwarden.data.util.mockBuilder
|
||||
import io.mockk.every
|
||||
|
@ -20,7 +18,6 @@ import org.junit.jupiter.api.Test
|
|||
|
||||
class FilledItemExtensionsTest {
|
||||
private val autofillId: AutofillId = mockk()
|
||||
private val context: Context = mockk()
|
||||
private val datasetBuilder: Dataset.Builder = mockk()
|
||||
private val field: Field = mockk()
|
||||
private val filledItem = FilledItem(
|
||||
|
@ -31,29 +28,19 @@ class FilledItemExtensionsTest {
|
|||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkConstructor(Dataset.Builder::class)
|
||||
mockkConstructor(Presentations.Builder::class)
|
||||
mockkConstructor(Field.Builder::class)
|
||||
every { anyConstructed<Presentations.Builder>().build() } returns presentations
|
||||
every { anyConstructed<Field.Builder>().build() } returns field
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun teardown() {
|
||||
unmockkConstructor(Dataset.Builder::class)
|
||||
unmockkConstructor(Presentations.Builder::class)
|
||||
unmockkConstructor(Field.Builder::class)
|
||||
}
|
||||
|
||||
@Suppress("Deprecation")
|
||||
@Test
|
||||
fun `applyOverlayToDataset should use setValue to set RemoteViews when before tiramisu`() {
|
||||
fun `applyToDatasetPreTiramisu should use setValue to set RemoteViews`() {
|
||||
// Setup
|
||||
val appInfo = AutofillAppInfo(
|
||||
context = context,
|
||||
packageName = PACKAGE_NAME,
|
||||
sdkInt = 1,
|
||||
)
|
||||
every {
|
||||
datasetBuilder.setValue(
|
||||
autofillId,
|
||||
|
@ -63,8 +50,7 @@ class FilledItemExtensionsTest {
|
|||
} returns datasetBuilder
|
||||
|
||||
// Test
|
||||
filledItem.applyOverlayToDataset(
|
||||
appInfo = appInfo,
|
||||
filledItem.applyToDatasetPreTiramisu(
|
||||
datasetBuilder = datasetBuilder,
|
||||
remoteViews = remoteViews,
|
||||
)
|
||||
|
@ -80,14 +66,8 @@ class FilledItemExtensionsTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `applyOverlayToDataset should use setField to set Presentation on or after Tiramisu`() {
|
||||
fun `applyToDatasetPostTiramisu should use setField to set presentations`() {
|
||||
// Setup
|
||||
val appInfo = AutofillAppInfo(
|
||||
context = context,
|
||||
packageName = PACKAGE_NAME,
|
||||
sdkInt = 34,
|
||||
)
|
||||
mockBuilder<Presentations.Builder> { it.setMenuPresentation(remoteViews) }
|
||||
mockBuilder<Field.Builder> { it.setPresentations(presentations) }
|
||||
every {
|
||||
datasetBuilder.setField(
|
||||
|
@ -97,15 +77,13 @@ class FilledItemExtensionsTest {
|
|||
} returns datasetBuilder
|
||||
|
||||
// Test
|
||||
filledItem.applyOverlayToDataset(
|
||||
appInfo = appInfo,
|
||||
filledItem.applyToDatasetPostTiramisu(
|
||||
datasetBuilder = datasetBuilder,
|
||||
remoteViews = remoteViews,
|
||||
presentations = presentations,
|
||||
)
|
||||
|
||||
// Verify
|
||||
verify(exactly = 1) {
|
||||
anyConstructed<Presentations.Builder>().setMenuPresentation(remoteViews)
|
||||
anyConstructed<Field.Builder>().setPresentations(presentations)
|
||||
datasetBuilder.setField(
|
||||
autofillId,
|
||||
|
@ -113,8 +91,4 @@ class FilledItemExtensionsTest {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PACKAGE_NAME: String = "com.x8bit.bitwarden"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
package com.x8bit.bitwarden.data.autofill.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.service.autofill.Dataset
|
||||
import android.service.autofill.Presentations
|
||||
import android.widget.RemoteViews
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillAppInfo
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledPartition
|
||||
import com.x8bit.bitwarden.data.util.mockBuilder
|
||||
import com.x8bit.bitwarden.ui.autofill.buildAutofillRemoteViews
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkConstructor
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkConstructor
|
||||
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.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class FilledPartitionExtensionsTest {
|
||||
private val res: Resources = mockk()
|
||||
private val context: Context = mockk {
|
||||
every { this@mockk.resources } returns res
|
||||
}
|
||||
private val dataset: Dataset = mockk()
|
||||
private val filledItem: FilledItem = mockk()
|
||||
private val filledPartition = FilledPartition(
|
||||
filledItems = listOf(
|
||||
filledItem,
|
||||
),
|
||||
)
|
||||
private val presentations: Presentations = mockk()
|
||||
private val remoteViews: RemoteViews = mockk()
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkConstructor(Dataset.Builder::class)
|
||||
mockkConstructor(Presentations.Builder::class)
|
||||
mockkStatic(::buildAutofillRemoteViews)
|
||||
mockkStatic(FilledItem::applyToDatasetPostTiramisu)
|
||||
mockkStatic(FilledItem::applyToDatasetPreTiramisu)
|
||||
every { anyConstructed<Dataset.Builder>().build() } returns dataset
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun teardown() {
|
||||
unmockkConstructor(Dataset.Builder::class)
|
||||
unmockkConstructor(Presentations.Builder::class)
|
||||
unmockkStatic(::buildAutofillRemoteViews)
|
||||
unmockkStatic(FilledItem::applyToDatasetPostTiramisu)
|
||||
unmockkStatic(FilledItem::applyToDatasetPreTiramisu)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildDataset should applyToDatasetPostTiramisu when sdkInt is at least 33`() {
|
||||
// Setup
|
||||
val autofillAppInfo = AutofillAppInfo(
|
||||
context = context,
|
||||
packageName = PACKAGE_NAME,
|
||||
sdkInt = 34,
|
||||
)
|
||||
val title = "Bitwarden"
|
||||
every { res.getString(R.string.app_name) } returns title
|
||||
every {
|
||||
buildAutofillRemoteViews(
|
||||
packageName = PACKAGE_NAME,
|
||||
title = title,
|
||||
)
|
||||
} returns remoteViews
|
||||
mockBuilder<Presentations.Builder> { it.setMenuPresentation(remoteViews) }
|
||||
every {
|
||||
filledItem.applyToDatasetPostTiramisu(
|
||||
datasetBuilder = any(),
|
||||
presentations = presentations,
|
||||
)
|
||||
} just runs
|
||||
every { anyConstructed<Presentations.Builder>().build() } returns presentations
|
||||
|
||||
// Test
|
||||
val actual = filledPartition.buildDataset(
|
||||
autofillAppInfo = autofillAppInfo,
|
||||
)
|
||||
|
||||
// Verify
|
||||
assertEquals(dataset, actual)
|
||||
verify(exactly = 1) {
|
||||
buildAutofillRemoteViews(
|
||||
packageName = PACKAGE_NAME,
|
||||
title = title,
|
||||
)
|
||||
anyConstructed<Presentations.Builder>().setMenuPresentation(remoteViews)
|
||||
anyConstructed<Presentations.Builder>().build()
|
||||
filledItem.applyToDatasetPostTiramisu(
|
||||
datasetBuilder = any(),
|
||||
presentations = presentations,
|
||||
)
|
||||
anyConstructed<Dataset.Builder>().build()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildDataset should applyToDatasetPreTiramisu when sdkInt is less than 33`() {
|
||||
// Setup
|
||||
val autofillAppInfo = AutofillAppInfo(
|
||||
context = context,
|
||||
packageName = PACKAGE_NAME,
|
||||
sdkInt = 18,
|
||||
)
|
||||
val title = "Bitwarden"
|
||||
every { res.getString(R.string.app_name) } returns title
|
||||
every {
|
||||
buildAutofillRemoteViews(
|
||||
packageName = PACKAGE_NAME,
|
||||
title = title,
|
||||
)
|
||||
} returns remoteViews
|
||||
every {
|
||||
filledItem.applyToDatasetPreTiramisu(
|
||||
datasetBuilder = any(),
|
||||
remoteViews = remoteViews,
|
||||
)
|
||||
} just runs
|
||||
|
||||
// Test
|
||||
val actual = filledPartition.buildDataset(
|
||||
autofillAppInfo = autofillAppInfo,
|
||||
)
|
||||
|
||||
// Verify
|
||||
assertEquals(dataset, actual)
|
||||
verify(exactly = 1) {
|
||||
buildAutofillRemoteViews(
|
||||
packageName = PACKAGE_NAME,
|
||||
title = title,
|
||||
)
|
||||
filledItem.applyToDatasetPreTiramisu(
|
||||
datasetBuilder = any(),
|
||||
remoteViews = remoteViews,
|
||||
)
|
||||
anyConstructed<Dataset.Builder>().build()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PACKAGE_NAME: String = "com.x8bit.bitwarden"
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package com.x8bit.bitwarden.ui.autofill
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.widget.RemoteViews
|
||||
import com.x8bit.bitwarden.R
|
||||
|
@ -18,9 +17,6 @@ import org.junit.jupiter.api.Test
|
|||
|
||||
class BitwardenRemoteViewsTest {
|
||||
private val testResources: Resources = mockk()
|
||||
private val context: Context = mockk {
|
||||
every { this@mockk.resources } returns testResources
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
|
@ -31,7 +27,6 @@ class BitwardenRemoteViewsTest {
|
|||
fun teardown() {
|
||||
unmockkConstructor(RemoteViews::class)
|
||||
confirmVerified(
|
||||
context,
|
||||
testResources,
|
||||
)
|
||||
}
|
||||
|
@ -39,19 +34,18 @@ class BitwardenRemoteViewsTest {
|
|||
@Test
|
||||
fun `buildAutofillRemoteViews should set text`() {
|
||||
// Setup
|
||||
val appName = "Bitwarden"
|
||||
every { testResources.getText(R.string.app_name) } returns appName
|
||||
val title = "Bitwarden"
|
||||
every {
|
||||
anyConstructed<RemoteViews>()
|
||||
.setTextViewText(
|
||||
R.id.text,
|
||||
appName,
|
||||
title,
|
||||
)
|
||||
} just runs
|
||||
|
||||
// Test
|
||||
buildAutofillRemoteViews(
|
||||
context = context,
|
||||
title = title,
|
||||
packageName = PACKAGE_NAME,
|
||||
)
|
||||
|
||||
|
@ -61,12 +55,10 @@ class BitwardenRemoteViewsTest {
|
|||
|
||||
// Verify
|
||||
verify(exactly = 1) {
|
||||
context.resources
|
||||
testResources.getText(R.string.app_name)
|
||||
anyConstructed<RemoteViews>()
|
||||
.setTextViewText(
|
||||
R.id.text,
|
||||
"Bitwarden",
|
||||
title,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue