Merge "Propogate face unlock always require confirmation setting to biometric prompt." into udc-d1-dev am: 27443a9c09

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23668394

Change-Id: Ia2263435e31c9a87ac204c9e9c6cd3c423ef9441
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Joe Bolinger
2023-06-16 15:04:15 +00:00
committed by Automerger Merge Worker
15 changed files with 480 additions and 66 deletions

View File

@@ -374,7 +374,6 @@ public class AuthContainerView extends LinearLayout
if (Utils.isBiometricAllowed(config.mPromptInfo)) {
mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication(
config.mPromptInfo,
config.mRequireConfirmation,
config.mUserId,
config.mOperationId,
new BiometricModalities(fpProps, faceProps));

View File

@@ -1216,8 +1216,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
final PromptInfo promptInfo = (PromptInfo) args.arg1;
final int[] sensorIds = (int[]) args.arg3;
// TODO(b/251476085): remove these unused parameters (replaced with SSOT elsewhere)
final boolean credentialAllowed = (boolean) args.arg4;
final boolean requireConfirmation = (boolean) args.arg5;
final int userId = args.argi1;
final String opPackageName = (String) args.arg6;
final long operationId = args.argl1;

View File

@@ -17,6 +17,8 @@
package com.android.systemui.biometrics.dagger
import com.android.settingslib.udfps.UdfpsUtils
import com.android.systemui.biometrics.data.repository.FaceSettingsRepository
import com.android.systemui.biometrics.data.repository.FaceSettingsRepositoryImpl
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
import com.android.systemui.biometrics.data.repository.PromptRepository
@@ -45,6 +47,10 @@ import javax.inject.Qualifier
@Module
interface BiometricsModule {
@Binds
@SysUISingleton
fun faceSettings(impl: FaceSettingsRepositoryImpl): FaceSettingsRepository
@Binds
@SysUISingleton
fun biometricPromptRepository(impl: PromptRepositoryImpl): PromptRepository

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics.data.repository
import android.os.Handler
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.util.settings.SecureSettings
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
/**
* Repository for the global state of users Face Unlock preferences.
*
* Largely a wrapper around [SecureSettings]'s proxy to Settings.Secure.
*/
interface FaceSettingsRepository {
/** Get Settings for the given user [id]. */
fun forUser(id: Int?): FaceUserSettingsRepository
}
@SysUISingleton
class FaceSettingsRepositoryImpl
@Inject
constructor(
@Main private val mainHandler: Handler,
private val secureSettings: SecureSettings,
) : FaceSettingsRepository {
private val userSettings = ConcurrentHashMap<Int, FaceUserSettingsRepository>()
override fun forUser(id: Int?): FaceUserSettingsRepository =
if (id != null) {
userSettings.computeIfAbsent(id) { _ ->
FaceUserSettingsRepositoryImpl(id, mainHandler, secureSettings).also { repo ->
repo.start()
}
}
} else {
FaceUserSettingsRepositoryImpl.Empty
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics.data.repository
import android.database.ContentObserver
import android.os.Handler
import android.provider.Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.util.settings.SecureSettings
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flowOf
/** Settings for a user. */
interface FaceUserSettingsRepository {
/** The user's id. */
val userId: Int
/** If BiometricPrompt should always require confirmation (overrides app's preference). */
val alwaysRequireConfirmationInApps: Flow<Boolean>
}
class FaceUserSettingsRepositoryImpl(
override val userId: Int,
@Main private val mainHandler: Handler,
private val secureSettings: SecureSettings,
) : FaceUserSettingsRepository {
/** Indefinitely subscribe to user preference changes. */
fun start() {
watch(
FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
_alwaysRequireConfirmationInApps,
)
}
private var _alwaysRequireConfirmationInApps = MutableStateFlow(false)
override val alwaysRequireConfirmationInApps: Flow<Boolean> =
_alwaysRequireConfirmationInApps.asStateFlow()
/** Defaults to use when no user is specified. */
object Empty : FaceUserSettingsRepository {
override val userId = -1
override val alwaysRequireConfirmationInApps = flowOf(false)
}
private fun watch(
key: String,
toUpdate: MutableStateFlow<Boolean>,
defaultValue: Boolean = false,
) = secureSettings.watch(userId, mainHandler, key, defaultValue) { v -> toUpdate.value = v }
}
private fun SecureSettings.watch(
userId: Int,
handler: Handler,
key: String,
defaultValue: Boolean = false,
onChange: (Boolean) -> Unit,
) {
fun fetch(): Boolean = getIntForUser(key, if (defaultValue) 1 else 0, userId) > 0
registerContentObserverForUser(
key,
false /* notifyForDescendants */,
object : ContentObserver(handler) {
override fun onChange(selfChange: Boolean) = onChange(fetch())
},
userId
)
onChange(fetch())
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics.data.repository
import android.hardware.biometrics.PromptInfo
@@ -12,6 +28,10 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
/**
* A repository for the global state of BiometricPrompt.
@@ -40,7 +60,7 @@ interface PromptRepository {
*
* Note: overlaps/conflicts with [PromptInfo.isConfirmationRequested], which needs clean up.
*/
val isConfirmationRequired: StateFlow<Boolean>
val isConfirmationRequired: Flow<Boolean>
/** Update the prompt configuration, which should be set before [isShowing]. */
fun setPrompt(
@@ -48,7 +68,6 @@ interface PromptRepository {
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
requireConfirmation: Boolean = false,
)
/** Unset the prompt info. */
@@ -56,8 +75,12 @@ interface PromptRepository {
}
@SysUISingleton
class PromptRepositoryImpl @Inject constructor(private val authController: AuthController) :
PromptRepository {
class PromptRepositoryImpl
@Inject
constructor(
private val faceSettings: FaceSettingsRepository,
private val authController: AuthController,
) : PromptRepository {
override val isShowing: Flow<Boolean> = conflatedCallbackFlow {
val callback =
@@ -85,21 +108,30 @@ class PromptRepositoryImpl @Inject constructor(private val authController: AuthC
private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric())
override val kind = _kind.asStateFlow()
private val _isConfirmationRequired: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val isConfirmationRequired = _isConfirmationRequired.asStateFlow()
private val _faceSettings =
_userId.map { id -> faceSettings.forUser(id) }.distinctUntilChanged()
private val _faceSettingAlwaysRequireConfirmation =
_faceSettings.flatMapLatest { it.alwaysRequireConfirmationInApps }.distinctUntilChanged()
private val _isConfirmationRequired = _promptInfo.map { it?.isConfirmationRequested ?: false }
override val isConfirmationRequired =
combine(_isConfirmationRequired, _faceSettingAlwaysRequireConfirmation) {
appRequiresConfirmation,
forceRequireConfirmation ->
forceRequireConfirmation || appRequiresConfirmation
}
.distinctUntilChanged()
override fun setPrompt(
promptInfo: PromptInfo,
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
requireConfirmation: Boolean,
) {
_kind.value = kind
_userId.value = userId
_challenge.value = gatekeeperChallenge
_promptInfo.value = promptInfo
_isConfirmationRequired.value = requireConfirmation
}
override fun unsetPrompt() {
@@ -107,7 +139,6 @@ class PromptRepositoryImpl @Inject constructor(private val authController: AuthC
_userId.value = null
_challenge.value = null
_kind.value = PromptKind.Biometric()
_isConfirmationRequired.value = false
}
companion object {

View File

@@ -59,13 +59,15 @@ interface PromptSelectorInteractor {
*/
val credentialKind: Flow<PromptKind>
/** If the API caller requested explicit confirmation after successful authentication. */
val isConfirmationRequested: Flow<Boolean>
/**
* If the API caller or the user's personal preferences require explicit confirmation after
* successful authentication.
*/
val isConfirmationRequired: Flow<Boolean>
/** Use biometrics for authentication. */
fun useBiometricsForAuthentication(
promptInfo: PromptInfo,
requireConfirmation: Boolean,
userId: Int,
challenge: Long,
modalities: BiometricModalities,
@@ -114,10 +116,8 @@ constructor(
}
}
override val isConfirmationRequested: Flow<Boolean> =
promptRepository.promptInfo
.map { info -> info?.isConfirmationRequested ?: false }
.distinctUntilChanged()
override val isConfirmationRequired: Flow<Boolean> =
promptRepository.isConfirmationRequired.distinctUntilChanged()
override val isCredentialAllowed: Flow<Boolean> =
promptRepository.promptInfo
@@ -142,7 +142,6 @@ constructor(
override fun useBiometricsForAuthentication(
promptInfo: PromptInfo,
requireConfirmation: Boolean,
userId: Int,
challenge: Long,
modalities: BiometricModalities
@@ -152,7 +151,6 @@ constructor(
userId = userId,
gatekeeperChallenge = challenge,
kind = PromptKind.Biometric(modalities),
requireConfirmation = requireConfirmation,
)
}

View File

@@ -158,7 +158,7 @@ object BiometricViewBinder {
view.updateFingerprintAffordanceSize(iconController)
}
if (iconController is HackyCoexIconController) {
iconController.faceMode = !viewModel.isConfirmationRequested.first()
iconController.faceMode = !viewModel.isConfirmationRequired.first()
}
// the icon controller must be created before this happens for the legacy

View File

@@ -61,8 +61,11 @@ constructor(
/** If the user has successfully authenticated and confirmed (when explicitly required). */
val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()
/** If the API caller requested explicit confirmation after successful authentication. */
val isConfirmationRequested: Flow<Boolean> = interactor.isConfirmationRequested
/**
* If the API caller or the user's personal preferences require explicit confirmation after
* successful authentication.
*/
val isConfirmationRequired: Flow<Boolean> = interactor.isConfirmationRequired
/** The kind of credential the user has. */
val credentialKind: Flow<PromptKind> = interactor.credentialKind
@@ -91,7 +94,7 @@ constructor(
_forceLargeSize,
_forceMediumSize,
modalities,
interactor.isConfirmationRequested,
interactor.isConfirmationRequired,
fingerprintStartMode,
) { forceLarge, forceMedium, modalities, confirmationRequired, fpStartMode ->
when {
@@ -383,7 +386,7 @@ constructor(
private suspend fun needsExplicitConfirmation(modality: BiometricModality): Boolean {
val availableModalities = modalities.first()
val confirmationRequested = interactor.isConfirmationRequested.first()
val confirmationRequested = interactor.isConfirmationRequired.first()
if (availableModalities.hasFaceAndFingerprint) {
// coex only needs confirmation when face is successful, unless it happens on the

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics.data.repository
import android.database.ContentObserver
import android.os.Handler
import android.provider.Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.util.mockito.captureMany
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
private const val USER_ID = 8
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class FaceSettingsRepositoryImplTest : SysuiTestCase() {
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
private val testScope = TestScope()
@Mock private lateinit var mainHandler: Handler
@Mock private lateinit var secureSettings: SecureSettings
private lateinit var repository: FaceSettingsRepositoryImpl
@Before
fun setup() {
repository = FaceSettingsRepositoryImpl(mainHandler, secureSettings)
}
@Test
fun createsOneRepositoryPerUser() =
testScope.runTest {
val userRepo = repository.forUser(USER_ID)
assertThat(userRepo.userId).isEqualTo(USER_ID)
assertThat(repository.forUser(USER_ID)).isSameInstanceAs(userRepo)
assertThat(repository.forUser(USER_ID + 1)).isNotSameInstanceAs(userRepo)
}
@Test
fun startsRepoImmediatelyWithAllSettingKeys() =
testScope.runTest {
val userRepo = repository.forUser(USER_ID)
val keys =
captureMany<String> {
verify(secureSettings)
.registerContentObserverForUser(capture(), anyBoolean(), any(), eq(USER_ID))
}
assertThat(keys).containsExactly(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION)
}
@Test
fun forwardsSettingsValues() = runTest {
val userRepo = repository.forUser(USER_ID)
val intAsBooleanSettings =
listOf(
FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION to
collectLastValue(userRepo.alwaysRequireConfirmationInApps)
)
for ((setting, accessor) in intAsBooleanSettings) {
val observer =
withArgCaptor<ContentObserver> {
verify(secureSettings)
.registerContentObserverForUser(
eq(setting),
anyBoolean(),
capture(),
eq(USER_ID)
)
}
for (value in listOf(true, false)) {
secureSettings.mockIntSetting(setting, if (value) 1 else 0)
observer.onChange(false)
assertThat(accessor()).isEqualTo(value)
}
}
}
private fun SecureSettings.mockIntSetting(key: String, value: Int) {
whenever(getIntForUser(eq(key), anyInt(), eq(USER_ID))).thenReturn(value)
}
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics.data.repository
import android.hardware.biometrics.PromptInfo
@@ -5,12 +21,16 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.biometrics.shared.model.PromptKind
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runBlockingTest
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -21,61 +41,109 @@ import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
private const val USER_ID = 9
private const val CHALLENGE = 90L
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class PromptRepositoryImplTest : SysuiTestCase() {
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
private val testScope = TestScope()
private val faceSettings = FakeFaceSettingsRepository()
@Mock private lateinit var authController: AuthController
private lateinit var repository: PromptRepositoryImpl
@Before
fun setup() {
repository = PromptRepositoryImpl(authController)
repository = PromptRepositoryImpl(faceSettings, authController)
}
@Test
fun isShowing() = runBlockingTest {
whenever(authController.isShowing).thenReturn(true)
fun isShowing() =
testScope.runTest {
whenever(authController.isShowing).thenReturn(true)
val values = mutableListOf<Boolean>()
val job = launch { repository.isShowing.toList(values) }
assertThat(values).containsExactly(true)
val values = mutableListOf<Boolean>()
val job = launch { repository.isShowing.toList(values) }
runCurrent()
withArgCaptor<AuthController.Callback> {
verify(authController).addCallback(capture())
assertThat(values).containsExactly(true)
value.onBiometricPromptShown()
assertThat(values).containsExactly(true, true)
withArgCaptor<AuthController.Callback> {
verify(authController).addCallback(capture())
value.onBiometricPromptDismissed()
assertThat(values).containsExactly(true, true, false).inOrder()
value.onBiometricPromptShown()
runCurrent()
assertThat(values).containsExactly(true, true)
job.cancel()
verify(authController).removeCallback(eq(value))
value.onBiometricPromptDismissed()
runCurrent()
assertThat(values).containsExactly(true, true, false).inOrder()
job.cancel()
runCurrent()
verify(authController).removeCallback(eq(value))
}
}
}
@Test
fun setsAndUnsetsPrompt() = runBlockingTest {
val kind = PromptKind.Pin
val uid = 8
val challenge = 90L
val promptInfo = PromptInfo()
fun isConfirmationRequired_whenNotForced() =
testScope.runTest {
faceSettings.setUserSettings(USER_ID, alwaysRequireConfirmationInApps = false)
val isConfirmationRequired by collectLastValue(repository.isConfirmationRequired)
repository.setPrompt(promptInfo, uid, challenge, kind)
for (case in listOf(true, false)) {
repository.setPrompt(
PromptInfo().apply { isConfirmationRequested = case },
USER_ID,
CHALLENGE,
PromptKind.Biometric()
)
assertThat(repository.kind.value).isEqualTo(kind)
assertThat(repository.userId.value).isEqualTo(uid)
assertThat(repository.challenge.value).isEqualTo(challenge)
assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
assertThat(isConfirmationRequired).isEqualTo(case)
}
}
repository.unsetPrompt()
@Test
fun isConfirmationRequired_whenForced() =
testScope.runTest {
faceSettings.setUserSettings(USER_ID, alwaysRequireConfirmationInApps = true)
val isConfirmationRequired by collectLastValue(repository.isConfirmationRequired)
assertThat(repository.promptInfo.value).isNull()
assertThat(repository.userId.value).isNull()
assertThat(repository.challenge.value).isNull()
}
for (case in listOf(true, false)) {
repository.setPrompt(
PromptInfo().apply { isConfirmationRequested = case },
USER_ID,
CHALLENGE,
PromptKind.Biometric()
)
assertThat(isConfirmationRequired).isTrue()
}
}
@Test
fun setsAndUnsetsPrompt() =
testScope.runTest {
val kind = PromptKind.Pin
val promptInfo = PromptInfo()
repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind)
assertThat(repository.kind.value).isEqualTo(kind)
assertThat(repository.userId.value).isEqualTo(USER_ID)
assertThat(repository.challenge.value).isEqualTo(CHALLENGE)
assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
repository.unsetPrompt()
assertThat(repository.promptInfo.value).isNull()
assertThat(repository.userId.value).isNull()
assertThat(repository.challenge.value).isNull()
}
}

View File

@@ -106,17 +106,11 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() {
val currentPrompt by collectLastValue(interactor.prompt)
val credentialKind by collectLastValue(interactor.credentialKind)
val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed)
val isExplicitConfirmationRequired by collectLastValue(interactor.isConfirmationRequested)
val isExplicitConfirmationRequired by collectLastValue(interactor.isConfirmationRequired)
assertThat(currentPrompt).isNull()
interactor.useBiometricsForAuthentication(
info,
confirmationRequired,
USER_ID,
CHALLENGE,
modalities
)
interactor.useBiometricsForAuthentication(info, USER_ID, CHALLENGE, modalities)
assertThat(currentPrompt).isNotNull()
assertThat(currentPrompt?.title).isEqualTo(TITLE)

View File

@@ -631,7 +631,6 @@ private fun PromptSelectorInteractor.initializePrompt(
}
useBiometricsForAuthentication(
info,
requireConfirmation,
USER_ID,
CHALLENGE,
BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.biometrics.data.repository
import kotlinx.coroutines.flow.flowOf
/** Fake settings for tests. */
class FakeFaceSettingsRepository : FaceSettingsRepository {
private val userRepositories = mutableMapOf<Int, FaceUserSettingsRepository>()
/** Add fixed settings for a user. */
fun setUserSettings(userId: Int, alwaysRequireConfirmationInApps: Boolean = false) {
userRepositories[userId] =
object : FaceUserSettingsRepository {
override val userId = userId
override val alwaysRequireConfirmationInApps =
flowOf(alwaysRequireConfirmationInApps)
}
}
override fun forUser(id: Int?) = userRepositories[id] ?: FaceUserSettingsRepositoryImpl.Empty
}

View File

@@ -31,13 +31,20 @@ class FakePromptRepository : PromptRepository {
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
requireConfirmation: Boolean,
) = setPrompt(promptInfo, userId, gatekeeperChallenge, kind, forceConfirmation = false)
fun setPrompt(
promptInfo: PromptInfo,
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
forceConfirmation: Boolean = false,
) {
_promptInfo.value = promptInfo
_userId.value = userId
_challenge.value = gatekeeperChallenge
_kind.value = kind
_isConfirmationRequired.value = requireConfirmation
_isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation
}
override fun unsetPrompt() {