mirror of
https://github.com/bitwarden/android.git
synced 2024-11-24 02:15:53 +03:00
PM-11485: Add routing for accessibility autofill (#3895)
This commit is contained in:
parent
fe1f897d64
commit
6521848a8d
29 changed files with 624 additions and 35 deletions
|
@ -15,6 +15,7 @@ import androidx.core.os.LocaleListCompat
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityCompletionManager
|
||||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManager
|
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManager
|
||||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillCompletionManager
|
import com.x8bit.bitwarden.data.autofill.manager.AutofillCompletionManager
|
||||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||||
|
@ -43,6 +44,9 @@ class MainActivity : AppCompatActivity() {
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var autofillCompletionManager: AutofillCompletionManager
|
lateinit var autofillCompletionManager: AutofillCompletionManager
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var accessibilityCompletionManager: AccessibilityCompletionManager
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var settingsRepository: SettingsRepository
|
lateinit var settingsRepository: SettingsRepository
|
||||||
|
|
||||||
|
@ -74,6 +78,10 @@ class MainActivity : AppCompatActivity() {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
EventsEffect(viewModel = mainViewModel) { event ->
|
EventsEffect(viewModel = mainViewModel) { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
|
is MainEvent.CompleteAccessibilityAutofill -> {
|
||||||
|
handleCompleteAccessibilityAutofill(event)
|
||||||
|
}
|
||||||
|
|
||||||
is MainEvent.CompleteAutofill -> handleCompleteAutofill(event)
|
is MainEvent.CompleteAutofill -> handleCompleteAutofill(event)
|
||||||
MainEvent.Recreate -> handleRecreate()
|
MainEvent.Recreate -> handleRecreate()
|
||||||
MainEvent.NavigateToDebugMenu -> navController.navigateToDebugMenuScreen()
|
MainEvent.NavigateToDebugMenu -> navController.navigateToDebugMenuScreen()
|
||||||
|
@ -130,6 +138,15 @@ class MainActivity : AppCompatActivity() {
|
||||||
mainViewModel.trySendAction(MainAction.OpenDebugMenu)
|
mainViewModel.trySendAction(MainAction.OpenDebugMenu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleCompleteAccessibilityAutofill(
|
||||||
|
event: MainEvent.CompleteAccessibilityAutofill,
|
||||||
|
) {
|
||||||
|
accessibilityCompletionManager.completeAccessibilityAutofill(
|
||||||
|
activity = this,
|
||||||
|
cipherView = event.cipherView,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleCompleteAutofill(event: MainEvent.CompleteAutofill) {
|
private fun handleCompleteAutofill(event: MainEvent.CompleteAutofill) {
|
||||||
autofillCompletionManager.completeAutofill(
|
autofillCompletionManager.completeAutofill(
|
||||||
activity = this,
|
activity = this,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.EmailTokenResult
|
import com.x8bit.bitwarden.data.auth.repository.model.EmailTokenResult
|
||||||
import com.x8bit.bitwarden.data.auth.util.getCompleteRegistrationDataIntentOrNull
|
import com.x8bit.bitwarden.data.auth.util.getCompleteRegistrationDataIntentOrNull
|
||||||
import com.x8bit.bitwarden.data.auth.util.getPasswordlessRequestDataIntentOrNull
|
import com.x8bit.bitwarden.data.auth.util.getPasswordlessRequestDataIntentOrNull
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManager
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
|
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2AssertionRequestOrNull
|
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2AssertionRequestOrNull
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CredentialRequestOrNull
|
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CredentialRequestOrNull
|
||||||
|
@ -53,13 +54,14 @@ private const val SPECIAL_CIRCUMSTANCE_KEY = "special-circumstance"
|
||||||
@Suppress("LongParameterList", "TooManyFunctions")
|
@Suppress("LongParameterList", "TooManyFunctions")
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class MainViewModel @Inject constructor(
|
class MainViewModel @Inject constructor(
|
||||||
|
accessibilitySelectionManager: AccessibilitySelectionManager,
|
||||||
autofillSelectionManager: AutofillSelectionManager,
|
autofillSelectionManager: AutofillSelectionManager,
|
||||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||||
private val garbageCollectionManager: GarbageCollectionManager,
|
private val garbageCollectionManager: GarbageCollectionManager,
|
||||||
private val fido2CredentialManager: Fido2CredentialManager,
|
private val fido2CredentialManager: Fido2CredentialManager,
|
||||||
private val intentManager: IntentManager,
|
private val intentManager: IntentManager,
|
||||||
settingsRepository: SettingsRepository,
|
settingsRepository: SettingsRepository,
|
||||||
private val vaultRepository: VaultRepository,
|
vaultRepository: VaultRepository,
|
||||||
private val authRepository: AuthRepository,
|
private val authRepository: AuthRepository,
|
||||||
private val environmentRepository: EnvironmentRepository,
|
private val environmentRepository: EnvironmentRepository,
|
||||||
private val savedStateHandle: SavedStateHandle,
|
private val savedStateHandle: SavedStateHandle,
|
||||||
|
@ -85,6 +87,12 @@ class MainViewModel @Inject constructor(
|
||||||
.onEach { specialCircumstance = it }
|
.onEach { specialCircumstance = it }
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
accessibilitySelectionManager
|
||||||
|
.accessibilitySelectionFlow
|
||||||
|
.map { MainAction.Internal.AccessibilitySelectionReceive(it) }
|
||||||
|
.onEach(::sendAction)
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
autofillSelectionManager
|
autofillSelectionManager
|
||||||
.autofillSelectionFlow
|
.autofillSelectionFlow
|
||||||
.onEach { trySendAction(MainAction.Internal.AutofillSelectionReceive(it)) }
|
.onEach { trySendAction(MainAction.Internal.AutofillSelectionReceive(it)) }
|
||||||
|
@ -151,6 +159,10 @@ class MainViewModel @Inject constructor(
|
||||||
|
|
||||||
override fun handleAction(action: MainAction) {
|
override fun handleAction(action: MainAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
|
is MainAction.Internal.AccessibilitySelectionReceive -> {
|
||||||
|
handleAccessibilitySelectionReceive(action)
|
||||||
|
}
|
||||||
|
|
||||||
is MainAction.Internal.AutofillSelectionReceive -> {
|
is MainAction.Internal.AutofillSelectionReceive -> {
|
||||||
handleAutofillSelectionReceive(action)
|
handleAutofillSelectionReceive(action)
|
||||||
}
|
}
|
||||||
|
@ -169,6 +181,12 @@ class MainViewModel @Inject constructor(
|
||||||
sendEvent(MainEvent.NavigateToDebugMenu)
|
sendEvent(MainEvent.NavigateToDebugMenu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleAccessibilitySelectionReceive(
|
||||||
|
action: MainAction.Internal.AccessibilitySelectionReceive,
|
||||||
|
) {
|
||||||
|
sendEvent(MainEvent.CompleteAccessibilityAutofill(cipherView = action.cipherView))
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleAutofillSelectionReceive(
|
private fun handleAutofillSelectionReceive(
|
||||||
action: MainAction.Internal.AutofillSelectionReceive,
|
action: MainAction.Internal.AutofillSelectionReceive,
|
||||||
) {
|
) {
|
||||||
|
@ -332,11 +350,13 @@ class MainViewModel @Inject constructor(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
EmailTokenResult.Expired -> {
|
EmailTokenResult.Expired -> {
|
||||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance
|
specialCircumstanceManager.specialCircumstance = SpecialCircumstance
|
||||||
.RegistrationEvent
|
.RegistrationEvent
|
||||||
.ExpiredRegistrationLink
|
.ExpiredRegistrationLink
|
||||||
}
|
}
|
||||||
|
|
||||||
EmailTokenResult.Success -> {
|
EmailTokenResult.Success -> {
|
||||||
if (authRepository.activeUserId != null) {
|
if (authRepository.activeUserId != null) {
|
||||||
authRepository.hasPendingAccountAddition = true
|
authRepository.hasPendingAccountAddition = true
|
||||||
|
@ -384,6 +404,14 @@ sealed class MainAction {
|
||||||
* Actions for internal use by the ViewModel.
|
* Actions for internal use by the ViewModel.
|
||||||
*/
|
*/
|
||||||
sealed class Internal : MainAction() {
|
sealed class Internal : MainAction() {
|
||||||
|
/**
|
||||||
|
* Indicates the user has manually selected the given [cipherView] for accessibility
|
||||||
|
* autofill.
|
||||||
|
*/
|
||||||
|
data class AccessibilitySelectionReceive(
|
||||||
|
val cipherView: CipherView,
|
||||||
|
) : Internal()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates the user has manually selected the given [cipherView] for autofill.
|
* Indicates the user has manually selected the given [cipherView] for autofill.
|
||||||
*/
|
*/
|
||||||
|
@ -421,6 +449,12 @@ sealed class MainAction {
|
||||||
* Represents events that are emitted by the [MainViewModel].
|
* Represents events that are emitted by the [MainViewModel].
|
||||||
*/
|
*/
|
||||||
sealed class MainEvent {
|
sealed class MainEvent {
|
||||||
|
/**
|
||||||
|
* Event indicating that the user has chosen the given [cipherView] for accessibility autofill
|
||||||
|
* and that the process is ready to complete.
|
||||||
|
*/
|
||||||
|
data class CompleteAccessibilityAutofill(val cipherView: CipherView) : MainEvent()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event indicating that the user has chosen the given [cipherView] for autofill and that the
|
* Event indicating that the user has chosen the given [cipherView] for autofill and that the
|
||||||
* process is ready to complete.
|
* process is ready to complete.
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
package com.x8bit.bitwarden.data.autofill.accessibility.di
|
package com.x8bit.bitwarden.data.autofill.accessibility.di
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManager
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManager
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManagerImpl
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManagerImpl
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityCompletionManager
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityCompletionManagerImpl
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManager
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManagerImpl
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.LauncherPackageNameManager
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.LauncherPackageNameManager
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.LauncherPackageNameManagerImpl
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.LauncherPackageNameManagerImpl
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.parser.AccessibilityParser
|
import com.x8bit.bitwarden.data.autofill.accessibility.parser.AccessibilityParser
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.parser.AccessibilityParserImpl
|
import com.x8bit.bitwarden.data.autofill.accessibility.parser.AccessibilityParserImpl
|
||||||
|
import com.x8bit.bitwarden.data.autofill.manager.AutofillTotpManager
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -20,6 +28,19 @@ import javax.inject.Singleton
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
object AccessibilityModule {
|
object AccessibilityModule {
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun providesAccessibilityCompletionManager(
|
||||||
|
accessibilityAutofillManager: AccessibilityAutofillManager,
|
||||||
|
totpManager: AutofillTotpManager,
|
||||||
|
dispatcherManager: DispatcherManager,
|
||||||
|
): AccessibilityCompletionManager =
|
||||||
|
AccessibilityCompletionManagerImpl(
|
||||||
|
accessibilityAutofillManager = accessibilityAutofillManager,
|
||||||
|
totpManager = totpManager,
|
||||||
|
dispatcherManager = dispatcherManager,
|
||||||
|
)
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun providesAccessibilityInvokeManager(): AccessibilityAutofillManager =
|
fun providesAccessibilityInvokeManager(): AccessibilityAutofillManager =
|
||||||
|
@ -29,6 +50,11 @@ object AccessibilityModule {
|
||||||
@Provides
|
@Provides
|
||||||
fun providesAccessibilityParser(): AccessibilityParser = AccessibilityParserImpl()
|
fun providesAccessibilityParser(): AccessibilityParser = AccessibilityParserImpl()
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun providesAccessibilitySelectionManager(): AccessibilitySelectionManager =
|
||||||
|
AccessibilitySelectionManagerImpl()
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Provides
|
@Provides
|
||||||
fun providesLauncherPackageNameManager(
|
fun providesLauncherPackageNameManager(
|
||||||
|
@ -39,4 +65,10 @@ object AccessibilityModule {
|
||||||
clockProvider = { clock },
|
clockProvider = { clock },
|
||||||
packageManager = packageManager,
|
packageManager = packageManager,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun providesPackageManager(
|
||||||
|
@ApplicationContext context: Context,
|
||||||
|
): PackageManager = context.packageManager
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.model.AccessibilityAction
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A relay manager used to notify the accessibility service to attempt an autofill.
|
* A relay manager used to notify the accessibility service to attempt an autofill.
|
||||||
*/
|
*/
|
||||||
|
@ -8,5 +10,5 @@ interface AccessibilityAutofillManager {
|
||||||
* Indicates that the Autofill tile has been clicked and we attempt an accessibility-based
|
* Indicates that the Autofill tile has been clicked and we attempt an accessibility-based
|
||||||
* autofill.
|
* autofill.
|
||||||
*/
|
*/
|
||||||
var isAccessibilityTileClicked: Boolean
|
var accessibilityAction: AccessibilityAction?
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.model.AccessibilityAction
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default implementation for the [AccessibilityAutofillManager].
|
* The default implementation for the [AccessibilityAutofillManager].
|
||||||
*/
|
*/
|
||||||
class AccessibilityAutofillManagerImpl : AccessibilityAutofillManager {
|
class AccessibilityAutofillManagerImpl : AccessibilityAutofillManager {
|
||||||
override var isAccessibilityTileClicked: Boolean = false
|
override var accessibilityAction: AccessibilityAction? = null
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import com.bitwarden.vault.CipherView
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A manager for completing the accessibility-based autofill process after the user has made a
|
||||||
|
* selection.
|
||||||
|
*/
|
||||||
|
interface AccessibilityCompletionManager {
|
||||||
|
/**
|
||||||
|
* Completes the accessibility-based autofill flow originating with the given [activity] using
|
||||||
|
* the selected [cipherView].
|
||||||
|
*/
|
||||||
|
fun completeAccessibilityAutofill(activity: Activity, cipherView: CipherView)
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import com.bitwarden.vault.CipherView
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.model.AccessibilityAction
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.util.toUriOrNull
|
||||||
|
import com.x8bit.bitwarden.data.autofill.manager.AutofillTotpManager
|
||||||
|
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||||
|
import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation for the [AccessibilityCompletionManager].
|
||||||
|
*/
|
||||||
|
class AccessibilityCompletionManagerImpl(
|
||||||
|
private val accessibilityAutofillManager: AccessibilityAutofillManager,
|
||||||
|
private val totpManager: AutofillTotpManager,
|
||||||
|
dispatcherManager: DispatcherManager,
|
||||||
|
) : AccessibilityCompletionManager {
|
||||||
|
private val mainScope = CoroutineScope(dispatcherManager.main)
|
||||||
|
|
||||||
|
override fun completeAccessibilityAutofill(activity: Activity, cipherView: CipherView) {
|
||||||
|
val autofillSelectionData = activity
|
||||||
|
.intent
|
||||||
|
?.getAutofillSelectionDataOrNull()
|
||||||
|
?: run {
|
||||||
|
activity.finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (autofillSelectionData.framework != AutofillSelectionData.Framework.ACCESSIBILITY) {
|
||||||
|
activity.finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val uri = autofillSelectionData
|
||||||
|
.uri
|
||||||
|
?.toUriOrNull()
|
||||||
|
?: run {
|
||||||
|
activity.finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
accessibilityAutofillManager.accessibilityAction = AccessibilityAction.AttemptFill(
|
||||||
|
cipherView = cipherView,
|
||||||
|
uri = uri,
|
||||||
|
)
|
||||||
|
mainScope.launch {
|
||||||
|
totpManager.tryCopyTotpToClipboard(cipherView = cipherView)
|
||||||
|
activity.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||||
|
|
||||||
|
import com.bitwarden.vault.CipherView
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A manager class used to handle the accessibility autofill selections.
|
||||||
|
*/
|
||||||
|
interface AccessibilitySelectionManager {
|
||||||
|
/**
|
||||||
|
* Emits a [CipherView] as a result of calls to [emitAccessibilitySelection].
|
||||||
|
*/
|
||||||
|
val accessibilitySelectionFlow: Flow<CipherView>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers an emission via [accessibilitySelectionFlow].
|
||||||
|
*/
|
||||||
|
fun emitAccessibilitySelection(cipherView: CipherView)
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||||
|
|
||||||
|
import com.bitwarden.vault.CipherView
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation of the [AccessibilitySelectionManager].
|
||||||
|
*/
|
||||||
|
class AccessibilitySelectionManagerImpl : AccessibilitySelectionManager {
|
||||||
|
private val accessibilitySelectionChannel: Channel<CipherView> = Channel(
|
||||||
|
capacity = Int.MAX_VALUE,
|
||||||
|
)
|
||||||
|
|
||||||
|
override val accessibilitySelectionFlow: Flow<CipherView> =
|
||||||
|
accessibilitySelectionChannel.receiveAsFlow()
|
||||||
|
|
||||||
|
override fun emitAccessibilitySelection(cipherView: CipherView) {
|
||||||
|
accessibilitySelectionChannel.trySend(cipherView)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.x8bit.bitwarden.data.autofill.accessibility.model
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import com.bitwarden.vault.CipherView
|
||||||
|
|
||||||
|
/**
|
||||||
|
*Represents an action to be taken by the accessibility service.
|
||||||
|
*/
|
||||||
|
sealed class AccessibilityAction {
|
||||||
|
/**
|
||||||
|
* Indicates that the accessibility service should attempt to scan the currently foregrounded
|
||||||
|
* application for a [Uri].
|
||||||
|
*/
|
||||||
|
data object AttemptParseUri : AccessibilityAction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the accessibility service should attempt to scan the currently foregrounded
|
||||||
|
* application for a fields to fill.
|
||||||
|
*/
|
||||||
|
data class AttemptFill(
|
||||||
|
val cipherView: CipherView,
|
||||||
|
val uri: Uri,
|
||||||
|
) : AccessibilityAction()
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.x8bit.bitwarden.data.autofill.accessibility.util
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||||
|
import java.net.URISyntaxException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to parse a [Uri] from a string and returns null if an error occurs.
|
||||||
|
*/
|
||||||
|
@OmitFromCoverage
|
||||||
|
fun String.toUriOrNull(): Uri? =
|
||||||
|
try {
|
||||||
|
Uri.parse(this)
|
||||||
|
} catch (e: URISyntaxException) {
|
||||||
|
null
|
||||||
|
}
|
|
@ -6,11 +6,13 @@ import kotlinx.parcelize.Parcelize
|
||||||
/**
|
/**
|
||||||
* Represents data for a manual autofill selection.
|
* Represents data for a manual autofill selection.
|
||||||
*
|
*
|
||||||
|
* @property framework The framework being used to fulfill this autofill request.
|
||||||
* @property type The type of autofill selection that must be made.
|
* @property type The type of autofill selection that must be made.
|
||||||
* @property uri A URI representing the location where data should be filled (if available).
|
* @property uri A URI representing the location where data should be filled (if available).
|
||||||
*/
|
*/
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class AutofillSelectionData(
|
data class AutofillSelectionData(
|
||||||
|
val framework: Framework,
|
||||||
val type: Type,
|
val type: Type,
|
||||||
val uri: String?,
|
val uri: String?,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
@ -22,4 +24,12 @@ data class AutofillSelectionData(
|
||||||
CARD,
|
CARD,
|
||||||
LOGIN,
|
LOGIN,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of framework to use with this autofill.
|
||||||
|
*/
|
||||||
|
enum class Framework {
|
||||||
|
ACCESSIBILITY,
|
||||||
|
AUTOFILL,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,18 +30,21 @@ private const val AUTOFILL_BUNDLE_KEY = "autofill-bundle-key"
|
||||||
*/
|
*/
|
||||||
fun createAutofillSelectionIntent(
|
fun createAutofillSelectionIntent(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
framework: AutofillSelectionData.Framework,
|
||||||
type: AutofillSelectionData.Type,
|
type: AutofillSelectionData.Type,
|
||||||
uri: String?,
|
uri: String?,
|
||||||
): Intent =
|
): Intent =
|
||||||
Intent(
|
Intent(context, MainActivity::class.java)
|
||||||
context,
|
|
||||||
MainActivity::class.java,
|
|
||||||
)
|
|
||||||
.apply {
|
.apply {
|
||||||
|
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
putExtra(
|
putExtra(
|
||||||
AUTOFILL_BUNDLE_KEY,
|
AUTOFILL_BUNDLE_KEY,
|
||||||
bundleOf(
|
bundleOf(
|
||||||
AUTOFILL_SELECTION_DATA_KEY to AutofillSelectionData(type = type, uri = uri),
|
AUTOFILL_SELECTION_DATA_KEY to AutofillSelectionData(
|
||||||
|
framework = framework,
|
||||||
|
type = type,
|
||||||
|
uri = uri,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ fun FilledData.buildVaultItemDataset(
|
||||||
): Dataset {
|
): Dataset {
|
||||||
val intent = createAutofillSelectionIntent(
|
val intent = createAutofillSelectionIntent(
|
||||||
context = autofillAppInfo.context,
|
context = autofillAppInfo.context,
|
||||||
|
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||||
type = when (this.originalPartition) {
|
type = when (this.originalPartition) {
|
||||||
is AutofillPartition.Card -> AutofillSelectionData.Type.CARD
|
is AutofillPartition.Card -> AutofillSelectionData.Type.CARD
|
||||||
is AutofillPartition.Login -> AutofillSelectionData.Type.LOGIN
|
is AutofillPartition.Login -> AutofillSelectionData.Type.LOGIN
|
||||||
|
|
|
@ -8,6 +8,7 @@ import android.service.quicksettings.TileService
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import com.x8bit.bitwarden.AccessibilityActivity
|
import com.x8bit.bitwarden.AccessibilityActivity
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManager
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManager
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.model.AccessibilityAction
|
||||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||||
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
import com.x8bit.bitwarden.data.platform.util.isBuildVersionBelow
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
@ -33,7 +34,7 @@ class BitwardenAutofillTileService : TileService() {
|
||||||
|
|
||||||
@SuppressLint("StartActivityAndCollapseDeprecated")
|
@SuppressLint("StartActivityAndCollapseDeprecated")
|
||||||
private fun launchAutofill() {
|
private fun launchAutofill() {
|
||||||
accessibilityAutofillManager.isAccessibilityTileClicked = true
|
accessibilityAutofillManager.accessibilityAction = AccessibilityAction.AttemptParseUri
|
||||||
val intent = Intent(applicationContext, AccessibilityActivity::class.java)
|
val intent = Intent(applicationContext, AccessibilityActivity::class.java)
|
||||||
if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) {
|
if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
|
|
|
@ -10,6 +10,7 @@ import com.x8bit.bitwarden.R
|
||||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManager
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
|
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
|
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
||||||
|
@ -29,6 +30,7 @@ import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardMan
|
||||||
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
|
import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
|
import com.x8bit.bitwarden.data.platform.manager.model.OrganizationEvent
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.util.toAutofillSelectionDataOrNull
|
||||||
import com.x8bit.bitwarden.data.platform.manager.util.toFido2AssertionRequestOrNull
|
import com.x8bit.bitwarden.data.platform.manager.util.toFido2AssertionRequestOrNull
|
||||||
import com.x8bit.bitwarden.data.platform.manager.util.toFido2RequestOrNull
|
import com.x8bit.bitwarden.data.platform.manager.util.toFido2RequestOrNull
|
||||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||||
|
@ -92,6 +94,7 @@ class VaultItemListingViewModel @Inject constructor(
|
||||||
private val vaultRepository: VaultRepository,
|
private val vaultRepository: VaultRepository,
|
||||||
private val environmentRepository: EnvironmentRepository,
|
private val environmentRepository: EnvironmentRepository,
|
||||||
private val settingsRepository: SettingsRepository,
|
private val settingsRepository: SettingsRepository,
|
||||||
|
private val accessibilitySelectionManager: AccessibilitySelectionManager,
|
||||||
private val autofillSelectionManager: AutofillSelectionManager,
|
private val autofillSelectionManager: AutofillSelectionManager,
|
||||||
private val cipherMatchingManager: CipherMatchingManager,
|
private val cipherMatchingManager: CipherMatchingManager,
|
||||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||||
|
@ -104,7 +107,6 @@ class VaultItemListingViewModel @Inject constructor(
|
||||||
val activeAccountSummary = userState.toActiveAccountSummary()
|
val activeAccountSummary = userState.toActiveAccountSummary()
|
||||||
val accountSummaries = userState.toAccountSummaries()
|
val accountSummaries = userState.toAccountSummaries()
|
||||||
val specialCircumstance = specialCircumstanceManager.specialCircumstance
|
val specialCircumstance = specialCircumstanceManager.specialCircumstance
|
||||||
val autofillSelectionData = specialCircumstance as? SpecialCircumstance.AutofillSelection
|
|
||||||
val fido2CreationData = specialCircumstance as? SpecialCircumstance.Fido2Save
|
val fido2CreationData = specialCircumstance as? SpecialCircumstance.Fido2Save
|
||||||
val fido2AssertionData = specialCircumstance as? SpecialCircumstance.Fido2Assertion
|
val fido2AssertionData = specialCircumstance as? SpecialCircumstance.Fido2Assertion
|
||||||
val fido2GetCredentialsData =
|
val fido2GetCredentialsData =
|
||||||
|
@ -127,7 +129,7 @@ class VaultItemListingViewModel @Inject constructor(
|
||||||
policyDisablesSend = policyManager
|
policyDisablesSend = policyManager
|
||||||
.getActivePolicies(type = PolicyTypeJson.DISABLE_SEND)
|
.getActivePolicies(type = PolicyTypeJson.DISABLE_SEND)
|
||||||
.any(),
|
.any(),
|
||||||
autofillSelectionData = autofillSelectionData?.autofillSelectionData,
|
autofillSelectionData = specialCircumstance?.toAutofillSelectionDataOrNull(),
|
||||||
hasMasterPassword = userState.activeAccount.hasMasterPassword,
|
hasMasterPassword = userState.activeAccount.hasMasterPassword,
|
||||||
fido2CredentialRequest = fido2CreationData?.fido2CredentialRequest,
|
fido2CredentialRequest = fido2CreationData?.fido2CredentialRequest,
|
||||||
fido2CredentialAssertionRequest = fido2AssertionData?.fido2AssertionRequest,
|
fido2CredentialAssertionRequest = fido2AssertionData?.fido2AssertionRequest,
|
||||||
|
@ -547,9 +549,19 @@ class VaultItemListingViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleItemClick(action: VaultItemListingsAction.ItemClick) {
|
private fun handleItemClick(action: VaultItemListingsAction.ItemClick) {
|
||||||
if (state.isAutofill) {
|
state.autofillSelectionData?.let { autofillSelectionData ->
|
||||||
val cipherView = getCipherViewOrNull(action.id) ?: return
|
val cipherView = getCipherViewOrNull(cipherId = action.id) ?: return
|
||||||
autofillSelectionManager.emitAutofillSelection(cipherView = cipherView)
|
when (autofillSelectionData.framework) {
|
||||||
|
AutofillSelectionData.Framework.ACCESSIBILITY -> {
|
||||||
|
accessibilitySelectionManager.emitAccessibilitySelection(
|
||||||
|
cipherView = cipherView,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AutofillSelectionData.Framework.AUTOFILL -> {
|
||||||
|
autofillSelectionManager.emitAutofillSelection(cipherView = cipherView)
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@ import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||||
import com.x8bit.bitwarden.data.auth.util.getCompleteRegistrationDataIntentOrNull
|
import com.x8bit.bitwarden.data.auth.util.getCompleteRegistrationDataIntentOrNull
|
||||||
import com.x8bit.bitwarden.data.auth.util.getPasswordlessRequestDataIntentOrNull
|
import com.x8bit.bitwarden.data.auth.util.getPasswordlessRequestDataIntentOrNull
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManager
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManagerImpl
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
|
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
|
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionRequest
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
||||||
|
@ -70,6 +72,8 @@ import java.time.ZoneOffset
|
||||||
class MainViewModelTest : BaseViewModelTest() {
|
class MainViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
private val autofillSelectionManager: AutofillSelectionManager = AutofillSelectionManagerImpl()
|
private val autofillSelectionManager: AutofillSelectionManager = AutofillSelectionManagerImpl()
|
||||||
|
private val accessibilitySelectionManager: AccessibilitySelectionManager =
|
||||||
|
AccessibilitySelectionManagerImpl()
|
||||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
|
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
|
||||||
private val mutableAppThemeFlow = MutableStateFlow(AppTheme.DEFAULT)
|
private val mutableAppThemeFlow = MutableStateFlow(AppTheme.DEFAULT)
|
||||||
private val mutableScreenCaptureAllowedFlow = MutableStateFlow(true)
|
private val mutableScreenCaptureAllowedFlow = MutableStateFlow(true)
|
||||||
|
@ -221,6 +225,20 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `accessibility selection updates should emit CompleteAccessibilityAutofill events`() =
|
||||||
|
runTest {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
val cipherView = mockk<CipherView>()
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
accessibilitySelectionManager.emitAccessibilitySelection(cipherView = cipherView)
|
||||||
|
assertEquals(
|
||||||
|
MainEvent.CompleteAccessibilityAutofill(cipherView = cipherView),
|
||||||
|
awaitItem(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `autofill selection updates should emit CompleteAutofill events`() = runTest {
|
fun `autofill selection updates should emit CompleteAutofill events`() = runTest {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
|
@ -964,6 +982,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
private fun createViewModel(
|
private fun createViewModel(
|
||||||
initialSpecialCircumstance: SpecialCircumstance? = null,
|
initialSpecialCircumstance: SpecialCircumstance? = null,
|
||||||
) = MainViewModel(
|
) = MainViewModel(
|
||||||
|
accessibilitySelectionManager = accessibilitySelectionManager,
|
||||||
autofillSelectionManager = autofillSelectionManager,
|
autofillSelectionManager = autofillSelectionManager,
|
||||||
specialCircumstanceManager = specialCircumstanceManager,
|
specialCircumstanceManager = specialCircumstanceManager,
|
||||||
garbageCollectionManager = garbageCollectionManager,
|
garbageCollectionManager = garbageCollectionManager,
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.model.AccessibilityAction
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertNull
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class AccessibilityAutofillManagerTest {
|
||||||
|
private val accessibilityAutofillManager: AccessibilityAutofillManager =
|
||||||
|
AccessibilityAutofillManagerImpl()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `isAccessibilityTileClicked should simply hold the state it is provided`() {
|
||||||
|
val attemptParseUri = AccessibilityAction.AttemptParseUri
|
||||||
|
val attemptFill = AccessibilityAction.AttemptFill(cipherView = mockk(), uri = mockk())
|
||||||
|
assertNull(accessibilityAutofillManager.accessibilityAction)
|
||||||
|
accessibilityAutofillManager.accessibilityAction = attemptParseUri
|
||||||
|
assertEquals(attemptParseUri, accessibilityAutofillManager.accessibilityAction)
|
||||||
|
accessibilityAutofillManager.accessibilityAction = attemptFill
|
||||||
|
assertEquals(attemptFill, accessibilityAutofillManager.accessibilityAction)
|
||||||
|
accessibilityAutofillManager.accessibilityAction = null
|
||||||
|
assertNull(accessibilityAutofillManager.accessibilityAction)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,210 @@
|
||||||
|
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import com.bitwarden.vault.CipherView
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.model.AccessibilityAction
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.util.toUriOrNull
|
||||||
|
import com.x8bit.bitwarden.data.autofill.manager.AutofillTotpManager
|
||||||
|
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||||
|
import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull
|
||||||
|
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.just
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.runs
|
||||||
|
import io.mockk.unmockkStatic
|
||||||
|
import io.mockk.verify
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class AccessibilityCompletionManagerTest {
|
||||||
|
|
||||||
|
private val activity: Activity = mockk {
|
||||||
|
every { finish() } just runs
|
||||||
|
}
|
||||||
|
private val accessibilityAutofillManager: AccessibilityAutofillManager = mockk()
|
||||||
|
private val totpManager: AutofillTotpManager = mockk()
|
||||||
|
private val fakeDispatcherManager: FakeDispatcherManager = FakeDispatcherManager()
|
||||||
|
|
||||||
|
private val accessibilityCompletionManager: AccessibilityCompletionManager =
|
||||||
|
AccessibilityCompletionManagerImpl(
|
||||||
|
accessibilityAutofillManager = accessibilityAutofillManager,
|
||||||
|
totpManager = totpManager,
|
||||||
|
dispatcherManager = fakeDispatcherManager,
|
||||||
|
)
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setup() {
|
||||||
|
fakeDispatcherManager.setMain(fakeDispatcherManager.unconfined)
|
||||||
|
mockkStatic(
|
||||||
|
Intent::getAutofillSelectionDataOrNull,
|
||||||
|
String::toUriOrNull,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun tearDown() {
|
||||||
|
fakeDispatcherManager.resetMain()
|
||||||
|
unmockkStatic(
|
||||||
|
Intent::getAutofillSelectionDataOrNull,
|
||||||
|
String::toUriOrNull,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `completeAccessibilityAutofill when there is no Intent should finish the Activity`() {
|
||||||
|
every { activity.intent } returns null
|
||||||
|
|
||||||
|
accessibilityCompletionManager.completeAccessibilityAutofill(
|
||||||
|
activity = activity,
|
||||||
|
cipherView = mockk(),
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(exactly = 1) {
|
||||||
|
activity.intent
|
||||||
|
activity.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `completeAccessibilityAutofill when there is no AutofillSelectionData should finish the Activity`() {
|
||||||
|
val mockIntent: Intent = mockk()
|
||||||
|
every { activity.intent } returns mockIntent
|
||||||
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||||
|
|
||||||
|
accessibilityCompletionManager.completeAccessibilityAutofill(
|
||||||
|
activity = activity,
|
||||||
|
cipherView = mockk(),
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(exactly = 1) {
|
||||||
|
activity.intent
|
||||||
|
mockIntent.getAutofillSelectionDataOrNull()
|
||||||
|
activity.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `completeAccessibilityAutofill when the AutofillSelectionData is for the incorrect framework should finish the Activity`() {
|
||||||
|
val mockIntent: Intent = mockk()
|
||||||
|
every { activity.intent } returns mockIntent
|
||||||
|
val selectionData = AutofillSelectionData(
|
||||||
|
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||||
|
type = AutofillSelectionData.Type.LOGIN,
|
||||||
|
uri = "",
|
||||||
|
)
|
||||||
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns selectionData
|
||||||
|
|
||||||
|
accessibilityCompletionManager.completeAccessibilityAutofill(
|
||||||
|
activity = activity,
|
||||||
|
cipherView = mockk(),
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(exactly = 1) {
|
||||||
|
activity.intent
|
||||||
|
mockIntent.getAutofillSelectionDataOrNull()
|
||||||
|
activity.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `completeAccessibilityAutofill when the AutofillSelectionData is missing uri should finish the Activity`() {
|
||||||
|
val mockIntent: Intent = mockk()
|
||||||
|
every { activity.intent } returns mockIntent
|
||||||
|
val selectionData = AutofillSelectionData(
|
||||||
|
framework = AutofillSelectionData.Framework.ACCESSIBILITY,
|
||||||
|
type = AutofillSelectionData.Type.LOGIN,
|
||||||
|
uri = null,
|
||||||
|
)
|
||||||
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns selectionData
|
||||||
|
|
||||||
|
accessibilityCompletionManager.completeAccessibilityAutofill(
|
||||||
|
activity = activity,
|
||||||
|
cipherView = mockk(),
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(exactly = 1) {
|
||||||
|
activity.intent
|
||||||
|
mockIntent.getAutofillSelectionDataOrNull()
|
||||||
|
activity.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `completeAccessibilityAutofill when the AutofillSelectionData contains an invalid uri should finish the Activity`() {
|
||||||
|
val mockIntent: Intent = mockk()
|
||||||
|
every { activity.intent } returns mockIntent
|
||||||
|
val stringUri = "invalid uri"
|
||||||
|
every { stringUri.toUriOrNull() } returns null
|
||||||
|
|
||||||
|
val selectionData = AutofillSelectionData(
|
||||||
|
framework = AutofillSelectionData.Framework.ACCESSIBILITY,
|
||||||
|
type = AutofillSelectionData.Type.LOGIN,
|
||||||
|
uri = stringUri,
|
||||||
|
)
|
||||||
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns selectionData
|
||||||
|
|
||||||
|
accessibilityCompletionManager.completeAccessibilityAutofill(
|
||||||
|
activity = activity,
|
||||||
|
cipherView = mockk(),
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(exactly = 1) {
|
||||||
|
activity.intent
|
||||||
|
mockIntent.getAutofillSelectionDataOrNull()
|
||||||
|
activity.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `completeAccessibilityAutofill when the AutofillSelectionData is correct should set the accessibility action, copy the totp and finish the activity`() {
|
||||||
|
val cipherView: CipherView = mockk()
|
||||||
|
val mockIntent: Intent = mockk()
|
||||||
|
every { activity.intent } returns mockIntent
|
||||||
|
val stringUri = "androidapp://com.x8bit.bitwarden"
|
||||||
|
val uri: Uri = mockk()
|
||||||
|
every { stringUri.toUriOrNull() } returns uri
|
||||||
|
val selectionData = AutofillSelectionData(
|
||||||
|
framework = AutofillSelectionData.Framework.ACCESSIBILITY,
|
||||||
|
type = AutofillSelectionData.Type.LOGIN,
|
||||||
|
uri = stringUri,
|
||||||
|
)
|
||||||
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns selectionData
|
||||||
|
every {
|
||||||
|
accessibilityAutofillManager.accessibilityAction = AccessibilityAction.AttemptFill(
|
||||||
|
cipherView = cipherView,
|
||||||
|
uri = uri,
|
||||||
|
)
|
||||||
|
} just runs
|
||||||
|
coEvery { totpManager.tryCopyTotpToClipboard(cipherView = cipherView) } just runs
|
||||||
|
|
||||||
|
accessibilityCompletionManager.completeAccessibilityAutofill(
|
||||||
|
activity = activity,
|
||||||
|
cipherView = cipherView,
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(exactly = 1) {
|
||||||
|
activity.intent
|
||||||
|
mockIntent.getAutofillSelectionDataOrNull()
|
||||||
|
accessibilityAutofillManager.accessibilityAction = AccessibilityAction.AttemptFill(
|
||||||
|
cipherView = cipherView,
|
||||||
|
uri = uri,
|
||||||
|
)
|
||||||
|
activity.finish()
|
||||||
|
}
|
||||||
|
coVerify(exactly = 1) {
|
||||||
|
totpManager.tryCopyTotpToClipboard(cipherView = cipherView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||||
|
|
||||||
|
import app.cash.turbine.test
|
||||||
|
import com.bitwarden.vault.CipherView
|
||||||
|
import io.mockk.mockk
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class AccessibilitySelectionManagerTest {
|
||||||
|
|
||||||
|
private val accessibilitySelectionManager: AccessibilitySelectionManager =
|
||||||
|
AccessibilitySelectionManagerImpl()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `autofillSelectionFlow should emit whenever emitAutofillSelection is called`() =
|
||||||
|
runTest {
|
||||||
|
accessibilitySelectionManager.accessibilitySelectionFlow.test {
|
||||||
|
expectNoEvents()
|
||||||
|
|
||||||
|
val cipherView1 = mockk<CipherView>()
|
||||||
|
accessibilitySelectionManager.emitAccessibilitySelection(cipherView = cipherView1)
|
||||||
|
|
||||||
|
assertEquals(cipherView1, awaitItem())
|
||||||
|
|
||||||
|
val cipherView2 = mockk<CipherView>()
|
||||||
|
accessibilitySelectionManager.emitAccessibilitySelection(cipherView = cipherView2)
|
||||||
|
|
||||||
|
assertEquals(cipherView2, awaitItem())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +0,0 @@
|
||||||
package com.x8bit.bitwarden.data.autofill.manager
|
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManager
|
|
||||||
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilityAutofillManagerImpl
|
|
||||||
import org.junit.jupiter.api.Assertions.assertFalse
|
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
|
|
||||||
class AccessibilityAutofillManagerTest {
|
|
||||||
private val accessibilityAutofillManager: AccessibilityAutofillManager =
|
|
||||||
AccessibilityAutofillManagerImpl()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `isAccessibilityTileClicked should simply hold the state it is provided`() {
|
|
||||||
assertFalse(accessibilityAutofillManager.isAccessibilityTileClicked)
|
|
||||||
accessibilityAutofillManager.isAccessibilityTileClicked = true
|
|
||||||
assertTrue(accessibilityAutofillManager.isAccessibilityTileClicked)
|
|
||||||
accessibilityAutofillManager.isAccessibilityTileClicked = false
|
|
||||||
assertFalse(accessibilityAutofillManager.isAccessibilityTileClicked)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -63,6 +63,7 @@ class SpecialCircumstanceExtensionsTest {
|
||||||
fun `toAutofillSelectionDataOrNull should return a non-null value for AutofillSelection`() {
|
fun `toAutofillSelectionDataOrNull should return a non-null value for AutofillSelection`() {
|
||||||
val autofillSelectionData = AutofillSelectionData(
|
val autofillSelectionData = AutofillSelectionData(
|
||||||
type = AutofillSelectionData.Type.LOGIN,
|
type = AutofillSelectionData.Type.LOGIN,
|
||||||
|
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||||
uri = "uri",
|
uri = "uri",
|
||||||
)
|
)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
|
|
@ -489,6 +489,7 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
||||||
fun `when the active user has an unlocked vault but there is an AutofillSelection special circumstance the nav state should be VaultUnlockedForAutofillSelection`() {
|
fun `when the active user has an unlocked vault but there is an AutofillSelection special circumstance the nav state should be VaultUnlockedForAutofillSelection`() {
|
||||||
val autofillSelectionData = AutofillSelectionData(
|
val autofillSelectionData = AutofillSelectionData(
|
||||||
type = AutofillSelectionData.Type.LOGIN,
|
type = AutofillSelectionData.Type.LOGIN,
|
||||||
|
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||||
uri = "uri",
|
uri = "uri",
|
||||||
)
|
)
|
||||||
specialCircumstanceManager.specialCircumstance =
|
specialCircumstanceManager.specialCircumstance =
|
||||||
|
|
|
@ -1435,6 +1435,7 @@ private const val CIPHER_ID = "mockId-1"
|
||||||
private val AUTOFILL_SELECTION_DATA =
|
private val AUTOFILL_SELECTION_DATA =
|
||||||
AutofillSelectionData(
|
AutofillSelectionData(
|
||||||
type = AutofillSelectionData.Type.LOGIN,
|
type = AutofillSelectionData.Type.LOGIN,
|
||||||
|
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||||
uri = AUTOFILL_URI,
|
uri = AUTOFILL_URI,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -263,6 +263,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
||||||
fun `initial add state should be correct when autofill selection`() = runTest {
|
fun `initial add state should be correct when autofill selection`() = runTest {
|
||||||
val autofillSelectionData = AutofillSelectionData(
|
val autofillSelectionData = AutofillSelectionData(
|
||||||
type = AutofillSelectionData.Type.LOGIN,
|
type = AutofillSelectionData.Type.LOGIN,
|
||||||
|
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||||
uri = "https://www.test.com",
|
uri = "https://www.test.com",
|
||||||
)
|
)
|
||||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.AutofillSelection(
|
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.AutofillSelection(
|
||||||
|
|
|
@ -32,6 +32,7 @@ class AutofillSelectionDataExtensionsTest {
|
||||||
),
|
),
|
||||||
AutofillSelectionData(
|
AutofillSelectionData(
|
||||||
type = AutofillSelectionData.Type.CARD,
|
type = AutofillSelectionData.Type.CARD,
|
||||||
|
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||||
uri = null,
|
uri = null,
|
||||||
)
|
)
|
||||||
.toDefaultAddTypeContent(isIndividualVaultDisabled = false),
|
.toDefaultAddTypeContent(isIndividualVaultDisabled = false),
|
||||||
|
@ -60,6 +61,7 @@ class AutofillSelectionDataExtensionsTest {
|
||||||
),
|
),
|
||||||
AutofillSelectionData(
|
AutofillSelectionData(
|
||||||
type = AutofillSelectionData.Type.LOGIN,
|
type = AutofillSelectionData.Type.LOGIN,
|
||||||
|
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||||
uri = "https://www.test.com",
|
uri = "https://www.test.com",
|
||||||
)
|
)
|
||||||
.toDefaultAddTypeContent(isIndividualVaultDisabled = true),
|
.toDefaultAddTypeContent(isIndividualVaultDisabled = true),
|
||||||
|
|
|
@ -2041,6 +2041,7 @@ private val ACCOUNT_SUMMARIES = listOf(
|
||||||
private val AUTOFILL_SELECTION_DATA =
|
private val AUTOFILL_SELECTION_DATA =
|
||||||
AutofillSelectionData(
|
AutofillSelectionData(
|
||||||
type = AutofillSelectionData.Type.LOGIN,
|
type = AutofillSelectionData.Type.LOGIN,
|
||||||
|
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||||
uri = "https:://www.test.com",
|
uri = "https:://www.test.com",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
|
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManager
|
||||||
|
import com.x8bit.bitwarden.data.autofill.accessibility.manager.AccessibilitySelectionManagerImpl
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
|
import com.x8bit.bitwarden.data.autofill.fido2.manager.Fido2CredentialManager
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialAssertionResult
|
||||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
||||||
|
@ -93,6 +95,8 @@ import java.time.ZoneOffset
|
||||||
@Suppress("LargeClass")
|
@Suppress("LargeClass")
|
||||||
class VaultItemListingViewModelTest : BaseViewModelTest() {
|
class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
|
private val accessibilitySelectionManager: AccessibilitySelectionManager =
|
||||||
|
AccessibilitySelectionManagerImpl()
|
||||||
private val autofillSelectionManager: AutofillSelectionManager = AutofillSelectionManagerImpl()
|
private val autofillSelectionManager: AutofillSelectionManager = AutofillSelectionManagerImpl()
|
||||||
|
|
||||||
private var mockFilteredCiphers: List<CipherView>? = null
|
private var mockFilteredCiphers: List<CipherView>? = null
|
||||||
|
@ -309,6 +313,49 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `ItemClick for vault item when accessibility autofill should post to the accessibilitySelectionManager`() =
|
||||||
|
runTest {
|
||||||
|
setupMockUri()
|
||||||
|
val cipherView = createMockCipherView(
|
||||||
|
number = 1,
|
||||||
|
fido2Credentials = createMockSdkFido2CredentialList(number = 1),
|
||||||
|
)
|
||||||
|
coEvery {
|
||||||
|
vaultRepository.getDecryptedFido2CredentialAutofillViews(
|
||||||
|
cipherViewList = listOf(cipherView),
|
||||||
|
)
|
||||||
|
} returns DecryptFido2CredentialAutofillViewResult.Error
|
||||||
|
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.AutofillSelection(
|
||||||
|
autofillSelectionData = AutofillSelectionData(
|
||||||
|
type = AutofillSelectionData.Type.LOGIN,
|
||||||
|
framework = AutofillSelectionData.Framework.ACCESSIBILITY,
|
||||||
|
uri = "https://www.test.com",
|
||||||
|
),
|
||||||
|
shouldFinishWhenComplete = true,
|
||||||
|
)
|
||||||
|
mutableVaultDataStateFlow.value = DataState.Loaded(
|
||||||
|
data = VaultData(
|
||||||
|
cipherViewList = listOf(cipherView),
|
||||||
|
folderViewList = emptyList(),
|
||||||
|
collectionViewList = emptyList(),
|
||||||
|
sendViewList = emptyList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val viewModel = createVaultItemListingViewModel()
|
||||||
|
|
||||||
|
accessibilitySelectionManager.accessibilitySelectionFlow.test {
|
||||||
|
viewModel.trySendAction(VaultItemListingsAction.ItemClick(id = "mockId-1"))
|
||||||
|
assertEquals(cipherView, awaitItem())
|
||||||
|
}
|
||||||
|
coVerify(exactly = 1) {
|
||||||
|
vaultRepository.getDecryptedFido2CredentialAutofillViews(
|
||||||
|
cipherViewList = listOf(cipherView),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ItemClick for vault item when autofill should post to the AutofillSelectionManager`() =
|
fun `ItemClick for vault item when autofill should post to the AutofillSelectionManager`() =
|
||||||
runTest {
|
runTest {
|
||||||
|
@ -326,6 +373,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
SpecialCircumstance.AutofillSelection(
|
SpecialCircumstance.AutofillSelection(
|
||||||
autofillSelectionData = AutofillSelectionData(
|
autofillSelectionData = AutofillSelectionData(
|
||||||
type = AutofillSelectionData.Type.LOGIN,
|
type = AutofillSelectionData.Type.LOGIN,
|
||||||
|
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||||
uri = "https://www.test.com",
|
uri = "https://www.test.com",
|
||||||
),
|
),
|
||||||
shouldFinishWhenComplete = true,
|
shouldFinishWhenComplete = true,
|
||||||
|
@ -1248,6 +1296,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
val autofillSelectionData = AutofillSelectionData(
|
val autofillSelectionData = AutofillSelectionData(
|
||||||
type = AutofillSelectionData.Type.LOGIN,
|
type = AutofillSelectionData.Type.LOGIN,
|
||||||
|
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||||
uri = "https://www.test.com",
|
uri = "https://www.test.com",
|
||||||
)
|
)
|
||||||
specialCircumstanceManager.specialCircumstance =
|
specialCircumstanceManager.specialCircumstance =
|
||||||
|
@ -3707,6 +3756,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||||
vaultRepository = vaultRepository,
|
vaultRepository = vaultRepository,
|
||||||
environmentRepository = environmentRepository,
|
environmentRepository = environmentRepository,
|
||||||
settingsRepository = settingsRepository,
|
settingsRepository = settingsRepository,
|
||||||
|
accessibilitySelectionManager = accessibilitySelectionManager,
|
||||||
autofillSelectionManager = autofillSelectionManager,
|
autofillSelectionManager = autofillSelectionManager,
|
||||||
cipherMatchingManager = cipherMatchingManager,
|
cipherMatchingManager = cipherMatchingManager,
|
||||||
specialCircumstanceManager = specialCircumstanceManager,
|
specialCircumstanceManager = specialCircumstanceManager,
|
||||||
|
|
|
@ -488,6 +488,7 @@ class VaultItemListingDataExtensionsTest {
|
||||||
isIconLoadingDisabled = false,
|
isIconLoadingDisabled = false,
|
||||||
autofillSelectionData = AutofillSelectionData(
|
autofillSelectionData = AutofillSelectionData(
|
||||||
type = AutofillSelectionData.Type.LOGIN,
|
type = AutofillSelectionData.Type.LOGIN,
|
||||||
|
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||||
uri = null,
|
uri = null,
|
||||||
),
|
),
|
||||||
fido2CreationData = null,
|
fido2CreationData = null,
|
||||||
|
@ -572,6 +573,7 @@ class VaultItemListingDataExtensionsTest {
|
||||||
isIconLoadingDisabled = false,
|
isIconLoadingDisabled = false,
|
||||||
autofillSelectionData = AutofillSelectionData(
|
autofillSelectionData = AutofillSelectionData(
|
||||||
type = AutofillSelectionData.Type.LOGIN,
|
type = AutofillSelectionData.Type.LOGIN,
|
||||||
|
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||||
uri = null,
|
uri = null,
|
||||||
),
|
),
|
||||||
fido2CreationData = null,
|
fido2CreationData = null,
|
||||||
|
@ -694,6 +696,7 @@ class VaultItemListingDataExtensionsTest {
|
||||||
isIconLoadingDisabled = false,
|
isIconLoadingDisabled = false,
|
||||||
autofillSelectionData = AutofillSelectionData(
|
autofillSelectionData = AutofillSelectionData(
|
||||||
type = AutofillSelectionData.Type.LOGIN,
|
type = AutofillSelectionData.Type.LOGIN,
|
||||||
|
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||||
uri = "https://www.test.com",
|
uri = "https://www.test.com",
|
||||||
),
|
),
|
||||||
fido2CreationData = null,
|
fido2CreationData = null,
|
||||||
|
|
Loading…
Reference in a new issue