mirror of
https://github.com/bitwarden/android.git
synced 2024-11-24 10:25:57 +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.lifecycle.compose.collectAsStateWithLifecycle
|
||||
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.AutofillCompletionManager
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
|
@ -43,6 +44,9 @@ class MainActivity : AppCompatActivity() {
|
|||
@Inject
|
||||
lateinit var autofillCompletionManager: AutofillCompletionManager
|
||||
|
||||
@Inject
|
||||
lateinit var accessibilityCompletionManager: AccessibilityCompletionManager
|
||||
|
||||
@Inject
|
||||
lateinit var settingsRepository: SettingsRepository
|
||||
|
||||
|
@ -74,6 +78,10 @@ class MainActivity : AppCompatActivity() {
|
|||
val navController = rememberNavController()
|
||||
EventsEffect(viewModel = mainViewModel) { event ->
|
||||
when (event) {
|
||||
is MainEvent.CompleteAccessibilityAutofill -> {
|
||||
handleCompleteAccessibilityAutofill(event)
|
||||
}
|
||||
|
||||
is MainEvent.CompleteAutofill -> handleCompleteAutofill(event)
|
||||
MainEvent.Recreate -> handleRecreate()
|
||||
MainEvent.NavigateToDebugMenu -> navController.navigateToDebugMenuScreen()
|
||||
|
@ -130,6 +138,15 @@ class MainActivity : AppCompatActivity() {
|
|||
mainViewModel.trySendAction(MainAction.OpenDebugMenu)
|
||||
}
|
||||
|
||||
private fun handleCompleteAccessibilityAutofill(
|
||||
event: MainEvent.CompleteAccessibilityAutofill,
|
||||
) {
|
||||
accessibilityCompletionManager.completeAccessibilityAutofill(
|
||||
activity = this,
|
||||
cipherView = event.cipherView,
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleCompleteAutofill(event: MainEvent.CompleteAutofill) {
|
||||
autofillCompletionManager.completeAutofill(
|
||||
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.util.getCompleteRegistrationDataIntentOrNull
|
||||
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.util.getFido2AssertionRequestOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.util.getFido2CredentialRequestOrNull
|
||||
|
@ -53,13 +54,14 @@ private const val SPECIAL_CIRCUMSTANCE_KEY = "special-circumstance"
|
|||
@Suppress("LongParameterList", "TooManyFunctions")
|
||||
@HiltViewModel
|
||||
class MainViewModel @Inject constructor(
|
||||
accessibilitySelectionManager: AccessibilitySelectionManager,
|
||||
autofillSelectionManager: AutofillSelectionManager,
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||
private val garbageCollectionManager: GarbageCollectionManager,
|
||||
private val fido2CredentialManager: Fido2CredentialManager,
|
||||
private val intentManager: IntentManager,
|
||||
settingsRepository: SettingsRepository,
|
||||
private val vaultRepository: VaultRepository,
|
||||
vaultRepository: VaultRepository,
|
||||
private val authRepository: AuthRepository,
|
||||
private val environmentRepository: EnvironmentRepository,
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
|
@ -85,6 +87,12 @@ class MainViewModel @Inject constructor(
|
|||
.onEach { specialCircumstance = it }
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
accessibilitySelectionManager
|
||||
.accessibilitySelectionFlow
|
||||
.map { MainAction.Internal.AccessibilitySelectionReceive(it) }
|
||||
.onEach(::sendAction)
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
autofillSelectionManager
|
||||
.autofillSelectionFlow
|
||||
.onEach { trySendAction(MainAction.Internal.AutofillSelectionReceive(it)) }
|
||||
|
@ -151,6 +159,10 @@ class MainViewModel @Inject constructor(
|
|||
|
||||
override fun handleAction(action: MainAction) {
|
||||
when (action) {
|
||||
is MainAction.Internal.AccessibilitySelectionReceive -> {
|
||||
handleAccessibilitySelectionReceive(action)
|
||||
}
|
||||
|
||||
is MainAction.Internal.AutofillSelectionReceive -> {
|
||||
handleAutofillSelectionReceive(action)
|
||||
}
|
||||
|
@ -169,6 +181,12 @@ class MainViewModel @Inject constructor(
|
|||
sendEvent(MainEvent.NavigateToDebugMenu)
|
||||
}
|
||||
|
||||
private fun handleAccessibilitySelectionReceive(
|
||||
action: MainAction.Internal.AccessibilitySelectionReceive,
|
||||
) {
|
||||
sendEvent(MainEvent.CompleteAccessibilityAutofill(cipherView = action.cipherView))
|
||||
}
|
||||
|
||||
private fun handleAutofillSelectionReceive(
|
||||
action: MainAction.Internal.AutofillSelectionReceive,
|
||||
) {
|
||||
|
@ -332,11 +350,13 @@ class MainViewModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
EmailTokenResult.Expired -> {
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance
|
||||
.RegistrationEvent
|
||||
.ExpiredRegistrationLink
|
||||
}
|
||||
|
||||
EmailTokenResult.Success -> {
|
||||
if (authRepository.activeUserId != null) {
|
||||
authRepository.hasPendingAccountAddition = true
|
||||
|
@ -384,6 +404,14 @@ sealed class MainAction {
|
|||
* Actions for internal use by the ViewModel.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -421,6 +449,12 @@ sealed class MainAction {
|
|||
* Represents events that are emitted by the [MainViewModel].
|
||||
*/
|
||||
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
|
||||
* process is ready to complete.
|
||||
|
|
|
@ -1,15 +1,23 @@
|
|||
package com.x8bit.bitwarden.data.autofill.accessibility.di
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
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.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.LauncherPackageNameManagerImpl
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.parser.AccessibilityParser
|
||||
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.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import java.time.Clock
|
||||
import javax.inject.Singleton
|
||||
|
@ -20,6 +28,19 @@ import javax.inject.Singleton
|
|||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object AccessibilityModule {
|
||||
@Singleton
|
||||
@Provides
|
||||
fun providesAccessibilityCompletionManager(
|
||||
accessibilityAutofillManager: AccessibilityAutofillManager,
|
||||
totpManager: AutofillTotpManager,
|
||||
dispatcherManager: DispatcherManager,
|
||||
): AccessibilityCompletionManager =
|
||||
AccessibilityCompletionManagerImpl(
|
||||
accessibilityAutofillManager = accessibilityAutofillManager,
|
||||
totpManager = totpManager,
|
||||
dispatcherManager = dispatcherManager,
|
||||
)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun providesAccessibilityInvokeManager(): AccessibilityAutofillManager =
|
||||
|
@ -29,6 +50,11 @@ object AccessibilityModule {
|
|||
@Provides
|
||||
fun providesAccessibilityParser(): AccessibilityParser = AccessibilityParserImpl()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun providesAccessibilitySelectionManager(): AccessibilitySelectionManager =
|
||||
AccessibilitySelectionManagerImpl()
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun providesLauncherPackageNameManager(
|
||||
|
@ -39,4 +65,10 @@ object AccessibilityModule {
|
|||
clockProvider = { clock },
|
||||
packageManager = packageManager,
|
||||
)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun providesPackageManager(
|
||||
@ApplicationContext context: Context,
|
||||
): PackageManager = context.packageManager
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
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.
|
||||
*/
|
||||
|
@ -8,5 +10,5 @@ interface AccessibilityAutofillManager {
|
|||
* Indicates that the Autofill tile has been clicked and we attempt an accessibility-based
|
||||
* autofill.
|
||||
*/
|
||||
var isAccessibilityTileClicked: Boolean
|
||||
var accessibilityAction: AccessibilityAction?
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package com.x8bit.bitwarden.data.autofill.accessibility.manager
|
||||
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.model.AccessibilityAction
|
||||
|
||||
/**
|
||||
* The default implementation for the [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.
|
||||
*
|
||||
* @property framework The framework being used to fulfill this autofill request.
|
||||
* @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).
|
||||
*/
|
||||
@Parcelize
|
||||
data class AutofillSelectionData(
|
||||
val framework: Framework,
|
||||
val type: Type,
|
||||
val uri: String?,
|
||||
) : Parcelable {
|
||||
|
@ -22,4 +24,12 @@ data class AutofillSelectionData(
|
|||
CARD,
|
||||
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(
|
||||
context: Context,
|
||||
framework: AutofillSelectionData.Framework,
|
||||
type: AutofillSelectionData.Type,
|
||||
uri: String?,
|
||||
): Intent =
|
||||
Intent(
|
||||
context,
|
||||
MainActivity::class.java,
|
||||
)
|
||||
Intent(context, MainActivity::class.java)
|
||||
.apply {
|
||||
setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
putExtra(
|
||||
AUTOFILL_BUNDLE_KEY,
|
||||
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 {
|
||||
val intent = createAutofillSelectionIntent(
|
||||
context = autofillAppInfo.context,
|
||||
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||
type = when (this.originalPartition) {
|
||||
is AutofillPartition.Card -> AutofillSelectionData.Type.CARD
|
||||
is AutofillPartition.Login -> AutofillSelectionData.Type.LOGIN
|
||||
|
|
|
@ -8,6 +8,7 @@ import android.service.quicksettings.TileService
|
|||
import androidx.annotation.Keep
|
||||
import com.x8bit.bitwarden.AccessibilityActivity
|
||||
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.util.isBuildVersionBelow
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
@ -33,7 +34,7 @@ class BitwardenAutofillTileService : TileService() {
|
|||
|
||||
@SuppressLint("StartActivityAndCollapseDeprecated")
|
||||
private fun launchAutofill() {
|
||||
accessibilityAutofillManager.isAccessibilityTileClicked = true
|
||||
accessibilityAutofillManager.accessibilityAction = AccessibilityAction.AttemptParseUri
|
||||
val intent = Intent(applicationContext, AccessibilityActivity::class.java)
|
||||
if (isBuildVersionBelow(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) {
|
||||
@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.model.ValidatePasswordResult
|
||||
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.model.Fido2CredentialAssertionRequest
|
||||
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.model.OrganizationEvent
|
||||
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.toFido2RequestOrNull
|
||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||
|
@ -92,6 +94,7 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
private val vaultRepository: VaultRepository,
|
||||
private val environmentRepository: EnvironmentRepository,
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val accessibilitySelectionManager: AccessibilitySelectionManager,
|
||||
private val autofillSelectionManager: AutofillSelectionManager,
|
||||
private val cipherMatchingManager: CipherMatchingManager,
|
||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||
|
@ -104,7 +107,6 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
val activeAccountSummary = userState.toActiveAccountSummary()
|
||||
val accountSummaries = userState.toAccountSummaries()
|
||||
val specialCircumstance = specialCircumstanceManager.specialCircumstance
|
||||
val autofillSelectionData = specialCircumstance as? SpecialCircumstance.AutofillSelection
|
||||
val fido2CreationData = specialCircumstance as? SpecialCircumstance.Fido2Save
|
||||
val fido2AssertionData = specialCircumstance as? SpecialCircumstance.Fido2Assertion
|
||||
val fido2GetCredentialsData =
|
||||
|
@ -127,7 +129,7 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
policyDisablesSend = policyManager
|
||||
.getActivePolicies(type = PolicyTypeJson.DISABLE_SEND)
|
||||
.any(),
|
||||
autofillSelectionData = autofillSelectionData?.autofillSelectionData,
|
||||
autofillSelectionData = specialCircumstance?.toAutofillSelectionDataOrNull(),
|
||||
hasMasterPassword = userState.activeAccount.hasMasterPassword,
|
||||
fido2CredentialRequest = fido2CreationData?.fido2CredentialRequest,
|
||||
fido2CredentialAssertionRequest = fido2AssertionData?.fido2AssertionRequest,
|
||||
|
@ -547,9 +549,19 @@ class VaultItemListingViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleItemClick(action: VaultItemListingsAction.ItemClick) {
|
||||
if (state.isAutofill) {
|
||||
val cipherView = getCipherViewOrNull(action.id) ?: return
|
||||
autofillSelectionManager.emitAutofillSelection(cipherView = cipherView)
|
||||
state.autofillSelectionData?.let { autofillSelectionData ->
|
||||
val cipherView = getCipherViewOrNull(cipherId = action.id) ?: return
|
||||
when (autofillSelectionData.framework) {
|
||||
AutofillSelectionData.Framework.ACCESSIBILITY -> {
|
||||
accessibilitySelectionManager.emitAccessibilitySelection(
|
||||
cipherView = cipherView,
|
||||
)
|
||||
}
|
||||
|
||||
AutofillSelectionData.Framework.AUTOFILL -> {
|
||||
autofillSelectionManager.emitAutofillSelection(cipherView = cipherView)
|
||||
}
|
||||
}
|
||||
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.util.getCompleteRegistrationDataIntentOrNull
|
||||
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.model.Fido2CredentialAssertionRequest
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
||||
|
@ -70,6 +72,8 @@ import java.time.ZoneOffset
|
|||
class MainViewModelTest : BaseViewModelTest() {
|
||||
|
||||
private val autofillSelectionManager: AutofillSelectionManager = AutofillSelectionManagerImpl()
|
||||
private val accessibilitySelectionManager: AccessibilitySelectionManager =
|
||||
AccessibilitySelectionManagerImpl()
|
||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
|
||||
private val mutableAppThemeFlow = MutableStateFlow(AppTheme.DEFAULT)
|
||||
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
|
||||
fun `autofill selection updates should emit CompleteAutofill events`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
@ -964,6 +982,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
|||
private fun createViewModel(
|
||||
initialSpecialCircumstance: SpecialCircumstance? = null,
|
||||
) = MainViewModel(
|
||||
accessibilitySelectionManager = accessibilitySelectionManager,
|
||||
autofillSelectionManager = autofillSelectionManager,
|
||||
specialCircumstanceManager = specialCircumstanceManager,
|
||||
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`() {
|
||||
val autofillSelectionData = AutofillSelectionData(
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||
uri = "uri",
|
||||
)
|
||||
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`() {
|
||||
val autofillSelectionData = AutofillSelectionData(
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||
uri = "uri",
|
||||
)
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
|
|
|
@ -1435,6 +1435,7 @@ private const val CIPHER_ID = "mockId-1"
|
|||
private val AUTOFILL_SELECTION_DATA =
|
||||
AutofillSelectionData(
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||
uri = AUTOFILL_URI,
|
||||
)
|
||||
|
||||
|
|
|
@ -263,6 +263,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
fun `initial add state should be correct when autofill selection`() = runTest {
|
||||
val autofillSelectionData = AutofillSelectionData(
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||
uri = "https://www.test.com",
|
||||
)
|
||||
specialCircumstanceManager.specialCircumstance = SpecialCircumstance.AutofillSelection(
|
||||
|
|
|
@ -32,6 +32,7 @@ class AutofillSelectionDataExtensionsTest {
|
|||
),
|
||||
AutofillSelectionData(
|
||||
type = AutofillSelectionData.Type.CARD,
|
||||
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||
uri = null,
|
||||
)
|
||||
.toDefaultAddTypeContent(isIndividualVaultDisabled = false),
|
||||
|
@ -60,6 +61,7 @@ class AutofillSelectionDataExtensionsTest {
|
|||
),
|
||||
AutofillSelectionData(
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||
uri = "https://www.test.com",
|
||||
)
|
||||
.toDefaultAddTypeContent(isIndividualVaultDisabled = true),
|
||||
|
|
|
@ -2041,6 +2041,7 @@ private val ACCOUNT_SUMMARIES = listOf(
|
|||
private val AUTOFILL_SELECTION_DATA =
|
||||
AutofillSelectionData(
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||
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.ValidatePinResult
|
||||
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.model.Fido2CredentialAssertionResult
|
||||
import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2CredentialRequest
|
||||
|
@ -93,6 +95,8 @@ import java.time.ZoneOffset
|
|||
@Suppress("LargeClass")
|
||||
class VaultItemListingViewModelTest : BaseViewModelTest() {
|
||||
|
||||
private val accessibilitySelectionManager: AccessibilitySelectionManager =
|
||||
AccessibilitySelectionManagerImpl()
|
||||
private val autofillSelectionManager: AutofillSelectionManager = AutofillSelectionManagerImpl()
|
||||
|
||||
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
|
||||
fun `ItemClick for vault item when autofill should post to the AutofillSelectionManager`() =
|
||||
runTest {
|
||||
|
@ -326,6 +373,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
SpecialCircumstance.AutofillSelection(
|
||||
autofillSelectionData = AutofillSelectionData(
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||
uri = "https://www.test.com",
|
||||
),
|
||||
shouldFinishWhenComplete = true,
|
||||
|
@ -1248,6 +1296,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
val autofillSelectionData = AutofillSelectionData(
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||
uri = "https://www.test.com",
|
||||
)
|
||||
specialCircumstanceManager.specialCircumstance =
|
||||
|
@ -3707,6 +3756,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() {
|
|||
vaultRepository = vaultRepository,
|
||||
environmentRepository = environmentRepository,
|
||||
settingsRepository = settingsRepository,
|
||||
accessibilitySelectionManager = accessibilitySelectionManager,
|
||||
autofillSelectionManager = autofillSelectionManager,
|
||||
cipherMatchingManager = cipherMatchingManager,
|
||||
specialCircumstanceManager = specialCircumstanceManager,
|
||||
|
|
|
@ -488,6 +488,7 @@ class VaultItemListingDataExtensionsTest {
|
|||
isIconLoadingDisabled = false,
|
||||
autofillSelectionData = AutofillSelectionData(
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||
uri = null,
|
||||
),
|
||||
fido2CreationData = null,
|
||||
|
@ -572,6 +573,7 @@ class VaultItemListingDataExtensionsTest {
|
|||
isIconLoadingDisabled = false,
|
||||
autofillSelectionData = AutofillSelectionData(
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||
uri = null,
|
||||
),
|
||||
fido2CreationData = null,
|
||||
|
@ -694,6 +696,7 @@ class VaultItemListingDataExtensionsTest {
|
|||
isIconLoadingDisabled = false,
|
||||
autofillSelectionData = AutofillSelectionData(
|
||||
type = AutofillSelectionData.Type.LOGIN,
|
||||
framework = AutofillSelectionData.Framework.AUTOFILL,
|
||||
uri = "https://www.test.com",
|
||||
),
|
||||
fido2CreationData = null,
|
||||
|
|
Loading…
Reference in a new issue