Handle navigation for auth requests from notification (#934)

This commit is contained in:
David Perez 2024-02-01 00:33:10 -06:00 committed by Álison Fernandes
parent 89dd552908
commit b15dc065be
11 changed files with 148 additions and 17 deletions

View file

@ -44,10 +44,6 @@
<data android:mimeType="video/*" /> <data android:mimeType="video/*" />
<data android:mimeType="text/*" /> <data android:mimeType="text/*" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="com.x8bit.bitwarden.data.auth.manager.AUTH_REQUEST" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity> </activity>
<activity <activity

View file

@ -5,6 +5,7 @@ import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.bitwarden.core.CipherView import com.bitwarden.core.CipherView
import com.x8bit.bitwarden.data.auth.util.getPasswordlessRequestDataIntentOrNull
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
import com.x8bit.bitwarden.data.autofill.util.getAutofillSaveItemOrNull import com.x8bit.bitwarden.data.autofill.util.getAutofillSaveItemOrNull
import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull
@ -111,10 +112,21 @@ class MainViewModel @Inject constructor(
intent: Intent, intent: Intent,
isFirstIntent: Boolean, isFirstIntent: Boolean,
) { ) {
val passwordlessRequestData = intent.getPasswordlessRequestDataIntentOrNull()
val autofillSaveItem = intent.getAutofillSaveItemOrNull() val autofillSaveItem = intent.getAutofillSaveItemOrNull()
val autofillSelectionData = intent.getAutofillSelectionDataOrNull() val autofillSelectionData = intent.getAutofillSelectionDataOrNull()
val shareData = intentManager.getShareDataFromIntent(intent) val shareData = intentManager.getShareDataFromIntent(intent)
when { when {
passwordlessRequestData != null -> {
specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.PasswordlessRequest(
passwordlessRequestData = passwordlessRequestData,
// Allow users back into the already-running app when completing the
// autofill task when this is not the first intent.
shouldFinishWhenComplete = isFirstIntent,
)
}
autofillSaveItem != null -> { autofillSaveItem != null -> {
specialCircumstanceManager.specialCircumstance = specialCircumstanceManager.specialCircumstance =
SpecialCircumstance.AutofillSave( SpecialCircumstance.AutofillSave(

View file

@ -3,14 +3,13 @@ package com.x8bit.bitwarden.data.auth.manager
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.core.app.NotificationChannelCompat import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import com.x8bit.bitwarden.MainActivity
import com.x8bit.bitwarden.R import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.util.createPasswordlessRequestDataIntent
import com.x8bit.bitwarden.data.autofill.util.toPendingIntentMutabilityFlag import com.x8bit.bitwarden.data.autofill.util.toPendingIntentMutabilityFlag
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
import com.x8bit.bitwarden.data.platform.manager.PushManager import com.x8bit.bitwarden.data.platform.manager.PushManager
@ -79,9 +78,7 @@ class AuthRequestNotificationManagerImpl(
PendingIntent.getActivity( PendingIntent.getActivity(
context, context,
NOTIFICATION_REQUEST_CODE, NOTIFICATION_REQUEST_CODE,
Intent(context, MainActivity::class.java) createPasswordlessRequestDataIntent(context, data),
.setAction(NOTIFICATION_ACTION)
.putExtra(NOTIFICATION_DATA, data),
PendingIntent.FLAG_UPDATE_CURRENT.toPendingIntentMutabilityFlag(), PendingIntent.FLAG_UPDATE_CURRENT.toPendingIntentMutabilityFlag(),
) )
@ -101,9 +98,7 @@ class AuthRequestNotificationManagerImpl(
?: NotificationManagerCompat.IMPORTANCE_DEFAULT ?: NotificationManagerCompat.IMPORTANCE_DEFAULT
} }
const val NOTIFICATION_ACTION: String = "com.x8bit.bitwarden.data.auth.manager.AUTH_REQUEST"
private const val NOTIFICATION_CHANNEL_ID: String = "general_notification_channel" private const val NOTIFICATION_CHANNEL_ID: String = "general_notification_channel"
private const val NOTIFICATION_ID: Int = 2_6072_022 private const val NOTIFICATION_ID: Int = 2_6072_022
private const val NOTIFICATION_DATA: String = "notificationData"
private const val NOTIFICATION_REQUEST_CODE: Int = 20220801 private const val NOTIFICATION_REQUEST_CODE: Int = 20220801
private const val NOTIFICATION_DEFAULT_TIMEOUT_MILLIS: Long = 15L * 60L * 1_000L private const val NOTIFICATION_DEFAULT_TIMEOUT_MILLIS: Long = 15L * 60L * 1_000L

View file

@ -0,0 +1,26 @@
package com.x8bit.bitwarden.data.auth.util
import android.content.Context
import android.content.Intent
import com.x8bit.bitwarden.MainActivity
import com.x8bit.bitwarden.data.platform.manager.model.PasswordlessRequestData
import com.x8bit.bitwarden.data.platform.util.getSafeParcelableExtra
private const val NOTIFICATION_DATA: String = "notificationData"
/**
* Creates an [Intent] that can be used to navigate the pending auth approval screen.
*/
fun createPasswordlessRequestDataIntent(
context: Context,
data: PasswordlessRequestData,
): Intent =
Intent(context, MainActivity::class.java)
.putExtra(NOTIFICATION_DATA, data)
/**
* Checks if the given [Intent] contains data for passwordless authorization.
* The [PasswordlessRequestData] will be returned when present.
*/
fun Intent.getPasswordlessRequestDataIntentOrNull(): PasswordlessRequestData? =
this.getSafeParcelableExtra(NOTIFICATION_DATA)

View file

@ -37,4 +37,13 @@ sealed class SpecialCircumstance : Parcelable {
val autofillSelectionData: AutofillSelectionData, val autofillSelectionData: AutofillSelectionData,
val shouldFinishWhenComplete: Boolean, val shouldFinishWhenComplete: Boolean,
) : SpecialCircumstance() ) : SpecialCircumstance()
/**
* The app was launched in order to allow the user to authorize a passwordless login.
*/
@Parcelize
data class PasswordlessRequest(
val passwordlessRequestData: PasswordlessRequestData,
val shouldFinishWhenComplete: Boolean,
) : SpecialCircumstance()
} }

View file

@ -11,6 +11,7 @@ fun SpecialCircumstance.toAutofillSaveItemOrNull(): AutofillSaveItem? =
when (this) { when (this) {
is SpecialCircumstance.AutofillSave -> this.autofillSaveItem is SpecialCircumstance.AutofillSave -> this.autofillSaveItem
is SpecialCircumstance.AutofillSelection -> null is SpecialCircumstance.AutofillSelection -> null
is SpecialCircumstance.PasswordlessRequest -> null
is SpecialCircumstance.ShareNewSend -> null is SpecialCircumstance.ShareNewSend -> null
} }
@ -21,5 +22,6 @@ fun SpecialCircumstance.toAutofillSelectionDataOrNull(): AutofillSelectionData?
when (this) { when (this) {
is SpecialCircumstance.AutofillSave -> null is SpecialCircumstance.AutofillSave -> null
is SpecialCircumstance.AutofillSelection -> this.autofillSelectionData is SpecialCircumstance.AutofillSelection -> this.autofillSelectionData
is SpecialCircumstance.PasswordlessRequest -> null
is SpecialCircumstance.ShareNewSend -> null is SpecialCircumstance.ShareNewSend -> null
} }

View file

@ -21,6 +21,7 @@ import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.VAULT_UNLOCK_ROUTE
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.navigateToVaultUnlock import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.navigateToVaultUnlock
import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.vaultUnlockDestination import com.x8bit.bitwarden.ui.auth.feature.vaultunlock.vaultUnlockDestination
import com.x8bit.bitwarden.ui.platform.feature.rootnav.util.toVaultItemListingType import com.x8bit.bitwarden.ui.platform.feature.rootnav.util.toVaultItemListingType
import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.loginapproval.navigateToLoginApproval
import com.x8bit.bitwarden.ui.platform.feature.splash.SPLASH_ROUTE import com.x8bit.bitwarden.ui.platform.feature.splash.SPLASH_ROUTE
import com.x8bit.bitwarden.ui.platform.feature.splash.navigateToSplash import com.x8bit.bitwarden.ui.platform.feature.splash.navigateToSplash
import com.x8bit.bitwarden.ui.platform.feature.splash.splashDestination import com.x8bit.bitwarden.ui.platform.feature.splash.splashDestination
@ -90,6 +91,7 @@ fun RootNavScreen(
is RootNavState.VaultUnlockedForAutofillSave, is RootNavState.VaultUnlockedForAutofillSave,
is RootNavState.VaultUnlockedForAutofillSelection, is RootNavState.VaultUnlockedForAutofillSelection,
is RootNavState.VaultUnlockedForNewSend, is RootNavState.VaultUnlockedForNewSend,
is RootNavState.VaultUnlockedForAuthRequest,
-> VAULT_UNLOCKED_GRAPH_ROUTE -> VAULT_UNLOCKED_GRAPH_ROUTE
} }
val currentRoute = navController.currentDestination?.rootLevelRoute() val currentRoute = navController.currentDestination?.rootLevelRoute()
@ -144,6 +146,14 @@ fun RootNavScreen(
navOptions = rootNavOptions, navOptions = rootNavOptions,
) )
} }
RootNavState.VaultUnlockedForAuthRequest -> {
navController.navigateToVaultUnlockedGraph(rootNavOptions)
navController.navigateToLoginApproval(
fingerprint = null,
navOptions = rootNavOptions,
)
}
} }
} }

View file

@ -83,6 +83,10 @@ class RootNavViewModel @Inject constructor(
is SpecialCircumstance.ShareNewSend -> RootNavState.VaultUnlockedForNewSend is SpecialCircumstance.ShareNewSend -> RootNavState.VaultUnlockedForNewSend
is SpecialCircumstance.PasswordlessRequest -> {
RootNavState.VaultUnlockedForAuthRequest
}
null -> { null -> {
RootNavState.VaultUnlocked( RootNavState.VaultUnlocked(
activeUserId = userState.activeAccount.userId, activeUserId = userState.activeAccount.userId,
@ -156,6 +160,12 @@ sealed class RootNavState : Parcelable {
*/ */
@Parcelize @Parcelize
data object VaultUnlockedForNewSend : RootNavState() data object VaultUnlockedForNewSend : RootNavState()
/**
* App should show the auth confirmation screen for an unlocked user.
*/
@Parcelize
data object VaultUnlockedForAuthRequest : RootNavState()
} }
/** /**

View file

@ -4,20 +4,22 @@ import androidx.lifecycle.SavedStateHandle
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions import androidx.navigation.NavOptions
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
private const val FINGERPRINT: String = "fingerprint" private const val FINGERPRINT: String = "fingerprint"
private const val LOGIN_APPROVAL_PREFIX = "login_approval" private const val LOGIN_APPROVAL_PREFIX = "login_approval"
private const val LOGIN_APPROVAL_ROUTE = "$LOGIN_APPROVAL_PREFIX/{$FINGERPRINT}" private const val LOGIN_APPROVAL_ROUTE = "$LOGIN_APPROVAL_PREFIX?$FINGERPRINT={$FINGERPRINT}"
/** /**
* Class to retrieve login approval arguments from the [SavedStateHandle]. * Class to retrieve login approval arguments from the [SavedStateHandle].
*/ */
@OmitFromCoverage @OmitFromCoverage
data class LoginApprovalArgs(val fingerprint: String) { data class LoginApprovalArgs(val fingerprint: String?) {
constructor(savedStateHandle: SavedStateHandle) : this( constructor(savedStateHandle: SavedStateHandle) : this(
checkNotNull(savedStateHandle[FINGERPRINT]) as String, fingerprint = savedStateHandle.get<String>(FINGERPRINT),
) )
} }
@ -29,6 +31,13 @@ fun NavGraphBuilder.loginApprovalDestination(
) { ) {
composableWithSlideTransitions( composableWithSlideTransitions(
route = LOGIN_APPROVAL_ROUTE, route = LOGIN_APPROVAL_ROUTE,
arguments = listOf(
navArgument(FINGERPRINT) {
type = NavType.StringType
nullable = true
defaultValue = null
},
),
) { ) {
LoginApprovalScreen( LoginApprovalScreen(
onNavigateBack = onNavigateBack, onNavigateBack = onNavigateBack,
@ -40,8 +49,8 @@ fun NavGraphBuilder.loginApprovalDestination(
* Navigate to the Login Approval screen. * Navigate to the Login Approval screen.
*/ */
fun NavController.navigateToLoginApproval( fun NavController.navigateToLoginApproval(
fingerprint: String, fingerprint: String?,
navOptions: NavOptions? = null, navOptions: NavOptions? = null,
) { ) {
navigate("$LOGIN_APPROVAL_PREFIX/$fingerprint", navOptions) navigate("$LOGIN_APPROVAL_PREFIX?$FINGERPRINT=$fingerprint", navOptions)
} }

View file

@ -29,7 +29,7 @@ class LoginApprovalViewModel @Inject constructor(
) : BaseViewModel<LoginApprovalState, LoginApprovalEvent, LoginApprovalAction>( ) : BaseViewModel<LoginApprovalState, LoginApprovalEvent, LoginApprovalAction>(
initialState = savedStateHandle[KEY_STATE] initialState = savedStateHandle[KEY_STATE]
?: LoginApprovalState( ?: LoginApprovalState(
fingerprint = LoginApprovalArgs(savedStateHandle).fingerprint, fingerprint = requireNotNull(LoginApprovalArgs(savedStateHandle).fingerprint),
masterPasswordHash = null, masterPasswordHash = null,
publicKey = "", publicKey = "",
requestId = "", requestId = "",

View file

@ -6,6 +6,7 @@ import app.cash.turbine.test
import com.bitwarden.core.CipherView import com.bitwarden.core.CipherView
import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.auth.util.getPasswordlessRequestDataIntentOrNull
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManager
import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl import com.x8bit.bitwarden.data.autofill.manager.AutofillSelectionManagerImpl
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
@ -13,6 +14,7 @@ import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
import com.x8bit.bitwarden.data.autofill.util.getAutofillSaveItemOrNull import com.x8bit.bitwarden.data.autofill.util.getAutofillSaveItemOrNull
import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull import com.x8bit.bitwarden.data.autofill.util.getAutofillSelectionDataOrNull
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManagerImpl
import com.x8bit.bitwarden.data.platform.manager.model.PasswordlessRequestData
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.repository.model.Environment import com.x8bit.bitwarden.data.platform.repository.model.Environment
@ -131,6 +133,7 @@ class MainViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel() val viewModel = createViewModel()
val mockIntent = mockk<Intent>() val mockIntent = mockk<Intent>()
val shareData = mockk<IntentManager.ShareData>() val shareData = mockk<IntentManager.ShareData>()
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
every { mockIntent.getAutofillSaveItemOrNull() } returns null every { mockIntent.getAutofillSaveItemOrNull() } returns null
every { mockIntent.getAutofillSelectionDataOrNull() } returns null every { mockIntent.getAutofillSelectionDataOrNull() } returns null
every { intentManager.getShareDataFromIntent(mockIntent) } returns shareData every { intentManager.getShareDataFromIntent(mockIntent) } returns shareData
@ -155,6 +158,7 @@ class MainViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel() val viewModel = createViewModel()
val mockIntent = mockk<Intent>() val mockIntent = mockk<Intent>()
val autofillSelectionData = mockk<AutofillSelectionData>() val autofillSelectionData = mockk<AutofillSelectionData>()
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
every { mockIntent.getAutofillSaveItemOrNull() } returns null every { mockIntent.getAutofillSaveItemOrNull() } returns null
every { mockIntent.getAutofillSelectionDataOrNull() } returns autofillSelectionData every { mockIntent.getAutofillSelectionDataOrNull() } returns autofillSelectionData
every { intentManager.getShareDataFromIntent(mockIntent) } returns null every { intentManager.getShareDataFromIntent(mockIntent) } returns null
@ -179,6 +183,7 @@ class MainViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel() val viewModel = createViewModel()
val mockIntent = mockk<Intent>() val mockIntent = mockk<Intent>()
val autofillSaveItem = mockk<AutofillSaveItem>() val autofillSaveItem = mockk<AutofillSaveItem>()
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
every { mockIntent.getAutofillSaveItemOrNull() } returns autofillSaveItem every { mockIntent.getAutofillSaveItemOrNull() } returns autofillSaveItem
every { mockIntent.getAutofillSelectionDataOrNull() } returns null every { mockIntent.getAutofillSelectionDataOrNull() } returns null
every { intentManager.getShareDataFromIntent(mockIntent) } returns null every { intentManager.getShareDataFromIntent(mockIntent) } returns null
@ -196,12 +201,40 @@ class MainViewModelTest : BaseViewModelTest() {
) )
} }
@Suppress("MaxLineLength")
@Test
fun `on ReceiveFirstIntent with a passwordless request data should set the special circumstance to PasswordlessRequest`() {
val viewModel = createViewModel()
val mockIntent = mockk<Intent>()
val passwordlessRequestData = mockk<PasswordlessRequestData>()
every {
mockIntent.getPasswordlessRequestDataIntentOrNull()
} returns passwordlessRequestData
every { mockIntent.getAutofillSaveItemOrNull() } returns null
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
viewModel.trySendAction(
MainAction.ReceiveFirstIntent(
intent = mockIntent,
),
)
assertEquals(
SpecialCircumstance.PasswordlessRequest(
passwordlessRequestData = passwordlessRequestData,
shouldFinishWhenComplete = true,
),
specialCircumstanceManager.specialCircumstance,
)
}
@Suppress("MaxLineLength") @Suppress("MaxLineLength")
@Test @Test
fun `on ReceiveNewIntent with share data should set the special circumstance to ShareNewSend`() { fun `on ReceiveNewIntent with share data should set the special circumstance to ShareNewSend`() {
val viewModel = createViewModel() val viewModel = createViewModel()
val mockIntent = mockk<Intent>() val mockIntent = mockk<Intent>()
val shareData = mockk<IntentManager.ShareData>() val shareData = mockk<IntentManager.ShareData>()
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
every { mockIntent.getAutofillSaveItemOrNull() } returns null every { mockIntent.getAutofillSaveItemOrNull() } returns null
every { mockIntent.getAutofillSelectionDataOrNull() } returns null every { mockIntent.getAutofillSelectionDataOrNull() } returns null
every { intentManager.getShareDataFromIntent(mockIntent) } returns shareData every { intentManager.getShareDataFromIntent(mockIntent) } returns shareData
@ -226,6 +259,7 @@ class MainViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel() val viewModel = createViewModel()
val mockIntent = mockk<Intent>() val mockIntent = mockk<Intent>()
val autofillSelectionData = mockk<AutofillSelectionData>() val autofillSelectionData = mockk<AutofillSelectionData>()
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
every { mockIntent.getAutofillSaveItemOrNull() } returns null every { mockIntent.getAutofillSaveItemOrNull() } returns null
every { mockIntent.getAutofillSelectionDataOrNull() } returns autofillSelectionData every { mockIntent.getAutofillSelectionDataOrNull() } returns autofillSelectionData
every { intentManager.getShareDataFromIntent(mockIntent) } returns null every { intentManager.getShareDataFromIntent(mockIntent) } returns null
@ -250,6 +284,7 @@ class MainViewModelTest : BaseViewModelTest() {
val viewModel = createViewModel() val viewModel = createViewModel()
val mockIntent = mockk<Intent>() val mockIntent = mockk<Intent>()
val autofillSaveItem = mockk<AutofillSaveItem>() val autofillSaveItem = mockk<AutofillSaveItem>()
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
every { mockIntent.getAutofillSaveItemOrNull() } returns autofillSaveItem every { mockIntent.getAutofillSaveItemOrNull() } returns autofillSaveItem
every { mockIntent.getAutofillSelectionDataOrNull() } returns null every { mockIntent.getAutofillSelectionDataOrNull() } returns null
every { intentManager.getShareDataFromIntent(mockIntent) } returns null every { intentManager.getShareDataFromIntent(mockIntent) } returns null
@ -267,6 +302,33 @@ class MainViewModelTest : BaseViewModelTest() {
) )
} }
@Suppress("MaxLineLength")
@Test
fun `on ReceiveNewIntent with a passwordless auth request data should set the special circumstance to PasswordlessRequest`() {
val viewModel = createViewModel()
val mockIntent = mockk<Intent>()
val passwordlessRequestData = mockk<PasswordlessRequestData>()
every {
mockIntent.getPasswordlessRequestDataIntentOrNull()
} returns passwordlessRequestData
every { mockIntent.getAutofillSaveItemOrNull() } returns null
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
viewModel.trySendAction(
MainAction.ReceiveNewIntent(
intent = mockIntent,
),
)
assertEquals(
SpecialCircumstance.PasswordlessRequest(
passwordlessRequestData = passwordlessRequestData,
shouldFinishWhenComplete = false,
),
specialCircumstanceManager.specialCircumstance,
)
}
@Suppress("MaxLineLength") @Suppress("MaxLineLength")
@Test @Test
fun `changes in the allowed screen capture value should result in emissions of ScreenCaptureSettingChange `() = fun `changes in the allowed screen capture value should result in emissions of ScreenCaptureSettingChange `() =