mirror of
https://github.com/bitwarden/android.git
synced 2026-03-22 12:32:53 -05:00
PM-11485: Add routing logic to handle searching during accessibility autofill (#3924)
This commit is contained in:
@@ -3,10 +3,12 @@ package com.x8bit.bitwarden.ui.platform.feature.search
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitwarden.vault.CipherView
|
||||
import com.bitwarden.vault.LoginUriView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
@@ -66,6 +68,7 @@ class SearchViewModel @Inject constructor(
|
||||
private val clock: Clock,
|
||||
private val clipboardManager: BitwardenClipboardManager,
|
||||
private val policyManager: PolicyManager,
|
||||
private val accessibilitySelectionManager: AccessibilitySelectionManager,
|
||||
private val autofillSelectionManager: AutofillSelectionManager,
|
||||
private val organizationEventManager: OrganizationEventManager,
|
||||
private val vaultRepo: VaultRepository,
|
||||
@@ -160,7 +163,7 @@ class SearchViewModel @Inject constructor(
|
||||
|
||||
private fun handleAutofillItemClick(action: SearchAction.AutofillItemClick) {
|
||||
val cipherView = getCipherViewOrNull(cipherId = action.itemId) ?: return
|
||||
autofillSelectionManager.emitAutofillSelection(cipherView = cipherView)
|
||||
useCipherForAutofill(cipherView = cipherView)
|
||||
}
|
||||
|
||||
private fun handleAutofillAndSaveItemClick(action: SearchAction.AutofillAndSaveItemClick) {
|
||||
@@ -497,7 +500,7 @@ class SearchViewModel @Inject constructor(
|
||||
UpdateCipherResult.Success -> {
|
||||
// Complete the autofill selection flow
|
||||
val cipherView = getCipherViewOrNull(cipherId = action.cipherId) ?: return
|
||||
autofillSelectionManager.emitAutofillSelection(cipherView = cipherView)
|
||||
useCipherForAutofill(cipherView = cipherView)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -622,6 +625,20 @@ class SearchViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun useCipherForAutofill(cipherView: CipherView) {
|
||||
when (state.autofillSelectionData?.framework) {
|
||||
AutofillSelectionData.Framework.ACCESSIBILITY -> {
|
||||
accessibilitySelectionManager.emitAccessibilitySelection(cipherView = cipherView)
|
||||
}
|
||||
|
||||
AutofillSelectionData.Framework.AUTOFILL -> {
|
||||
autofillSelectionManager.emitAutofillSelection(cipherView = cipherView)
|
||||
}
|
||||
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private fun vaultPendingReceive(vaultData: DataState.Pending<VaultData>) {
|
||||
updateStateWithVaultData(vaultData = vaultData.data, clearDialogState = false)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManagerImpl
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
@@ -75,6 +77,8 @@ import java.time.ZoneOffset
|
||||
@Suppress("LargeClass")
|
||||
class SearchViewModelTest : BaseViewModelTest() {
|
||||
|
||||
private val accessibilitySelectionManager: AccessibilitySelectionManager =
|
||||
AccessibilitySelectionManagerImpl()
|
||||
private val autofillSelectionManager: AutofillSelectionManager =
|
||||
AutofillSelectionManagerImpl()
|
||||
|
||||
@@ -206,7 +210,23 @@ class SearchViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AutofillItemClick should emit NavigateToViewCipher`() = runTest {
|
||||
fun `AutofillItemClick should call emitAccessibilitySelection`() = runTest {
|
||||
val cipherView = setupForAutofill(
|
||||
autofillSelectionData = AUTOFILL_SELECTION_DATA.copy(
|
||||
framework = AutofillSelectionData.Framework.ACCESSIBILITY,
|
||||
),
|
||||
)
|
||||
val cipherId = CIPHER_ID
|
||||
val viewModel = createViewModel()
|
||||
|
||||
accessibilitySelectionManager.accessibilitySelectionFlow.test {
|
||||
viewModel.trySendAction(SearchAction.AutofillItemClick(itemId = cipherId))
|
||||
assertEquals(cipherView, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AutofillItemClick should call emitAutofillSelection`() = runTest {
|
||||
val cipherView = setupForAutofill()
|
||||
val cipherId = CIPHER_ID
|
||||
val viewModel = createViewModel()
|
||||
@@ -268,6 +288,64 @@ class SearchViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `AutofillAndSaveItemClick with request success should post to the AccessibilitySelectionManager`() =
|
||||
runTest {
|
||||
val autofillSelectionData = AUTOFILL_SELECTION_DATA.copy(
|
||||
framework = AutofillSelectionData.Framework.ACCESSIBILITY,
|
||||
)
|
||||
val cipherView = setupForAutofill(autofillSelectionData = autofillSelectionData)
|
||||
val cipherId = CIPHER_ID
|
||||
val updatedCipherView = cipherView.copy(
|
||||
login = createMockLoginView(number = 1, clock = clock).copy(
|
||||
uris = listOf(createMockUriView(number = 1)) +
|
||||
LoginUriView(
|
||||
uri = AUTOFILL_URI,
|
||||
match = null,
|
||||
uriChecksum = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
val initialState = INITIAL_STATE_FOR_AUTOFILL.copy(
|
||||
autofillSelectionData = autofillSelectionData,
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
coEvery {
|
||||
vaultRepository.updateCipher(
|
||||
cipherId = cipherId,
|
||||
cipherView = updatedCipherView,
|
||||
)
|
||||
} returns UpdateCipherResult.Success
|
||||
|
||||
turbineScope {
|
||||
val stateTurbine = viewModel
|
||||
.stateFlow
|
||||
.testIn(backgroundScope)
|
||||
val selectionTurbine = accessibilitySelectionManager
|
||||
.accessibilitySelectionFlow
|
||||
.testIn(backgroundScope)
|
||||
|
||||
assertEquals(initialState, stateTurbine.awaitItem())
|
||||
|
||||
viewModel.trySendAction(SearchAction.AutofillAndSaveItemClick(itemId = cipherId))
|
||||
|
||||
assertEquals(
|
||||
initialState.copy(
|
||||
dialogState = SearchState.DialogState.Loading(
|
||||
message = R.string.loading.asText(),
|
||||
),
|
||||
),
|
||||
stateTurbine.awaitItem(),
|
||||
)
|
||||
|
||||
assertEquals(initialState, stateTurbine.awaitItem())
|
||||
|
||||
// Autofill flow is completed
|
||||
assertEquals(cipherView, selectionTurbine.awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `AutofillAndSaveItemClick with request success should post to the AutofillSelectionManager`() =
|
||||
@@ -397,6 +475,36 @@ class SearchViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `MasterPasswordRepromptSubmit for a request Success with a valid password for autofill should post to the AccessibilitySelectionManager`() =
|
||||
runTest {
|
||||
setupMockUri()
|
||||
val cipherView = setupForAutofill(
|
||||
autofillSelectionData = AUTOFILL_SELECTION_DATA.copy(
|
||||
framework = AutofillSelectionData.Framework.ACCESSIBILITY,
|
||||
),
|
||||
)
|
||||
val cipherId = CIPHER_ID
|
||||
val password = "password"
|
||||
coEvery {
|
||||
authRepository.validatePassword(password = password)
|
||||
} returns ValidatePasswordResult.Success(isValid = true)
|
||||
val viewModel = createViewModel()
|
||||
|
||||
accessibilitySelectionManager.accessibilitySelectionFlow.test {
|
||||
viewModel.trySendAction(
|
||||
SearchAction.MasterPasswordRepromptSubmit(
|
||||
password = password,
|
||||
masterPasswordRepromptData = MasterPasswordRepromptData.Autofill(
|
||||
cipherId = cipherId,
|
||||
),
|
||||
),
|
||||
)
|
||||
assertEquals(cipherView, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `MasterPasswordRepromptSubmit for a request Success with a valid password for autofill should post to the AutofillSelectionManager`() =
|
||||
@@ -1333,6 +1441,7 @@ class SearchViewModelTest : BaseViewModelTest() {
|
||||
clipboardManager = clipboardManager,
|
||||
policyManager = policyManager,
|
||||
specialCircumstanceManager = specialCircumstanceManager,
|
||||
accessibilitySelectionManager = accessibilitySelectionManager,
|
||||
autofillSelectionManager = autofillSelectionManager,
|
||||
organizationEventManager = organizationEventManager,
|
||||
)
|
||||
@@ -1341,9 +1450,11 @@ class SearchViewModelTest : BaseViewModelTest() {
|
||||
* Generates and returns [CipherView] to be populated for autofill testing and sets up the
|
||||
* state to return that item.
|
||||
*/
|
||||
private fun setupForAutofill(): CipherView {
|
||||
private fun setupForAutofill(
|
||||
autofillSelectionData: AutofillSelectionData = AUTOFILL_SELECTION_DATA,
|
||||
): CipherView {
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.AutofillSelection(
|
||||
autofillSelectionData = AUTOFILL_SELECTION_DATA,
|
||||
autofillSelectionData = autofillSelectionData,
|
||||
shouldFinishWhenComplete = true,
|
||||
)
|
||||
val cipherView = createMockCipherView(
|
||||
|
||||
Reference in New Issue
Block a user