Remove Controller usage from NotificationShelf

Under a unidirectional architecture, Views should not have access to
their Controller

- AmbientState is View-related logic, so NotificationShelf can access
that information directly on mAmbientState instead of going through the
controller.

- NotificationShelfController needs to push the KeyguardBypassController
state into NotificationShelf, in order to reverse the data flow

  - Rather than NotificationShelf having a concept of "Bypass", change
  the API to "setCanModifyColorOfNotifications" (as opposed to
  "setBypassEnabled", for example)

Bug: 271161129
Test: atest SystemUITests
Change-Id: I1c1615d5c3b28e2237f77b786b4dad062ffbea37
This commit is contained in:
Steve Elliott
2023-04-04 18:56:31 -04:00
parent 6d5c413ae2
commit 3f3c624472
10 changed files with 375 additions and 40 deletions

View File

@@ -92,6 +92,9 @@ interface DeviceEntryFaceAuthRepository {
/** Current state of whether face authentication is running. */
val isAuthRunning: Flow<Boolean>
/** Whether bypass is currently enabled */
val isBypassEnabled: Flow<Boolean>
/**
* Trigger face authentication.
*
@@ -166,7 +169,7 @@ constructor(
override val isAuthenticated: Flow<Boolean>
get() = _isAuthenticated
private val bypassEnabled: Flow<Boolean> =
override val isBypassEnabled: Flow<Boolean> =
keyguardBypassController?.let {
conflatedCallbackFlow {
val callback =
@@ -222,7 +225,7 @@ constructor(
// & detection is supported & biometric unlock is not allowed.
listOf(
canFaceAuthOrDetectRun(),
logAndObserve(bypassEnabled, "bypassEnabled"),
logAndObserve(isBypassEnabled, "isBypassEnabled"),
logAndObserve(
biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(),
"nonStrongBiometricIsNotAllowed"

View File

@@ -24,6 +24,7 @@ import android.content.res.Resources;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -96,6 +97,8 @@ public class NotificationShelf extends ActivatableNotificationView implements St
private NotificationShelfController mController;
private float mActualWidth = -1;
private boolean mSensitiveRevealAnimEndabled;
private boolean mShelfRefactorFlagEnabled;
private boolean mCanModifyColorOfNotifications;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -425,7 +428,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St
transitionAmount = inShelfAmount;
}
// We don't want to modify the color if the notification is hun'd
if (isLastChild && mController.canModifyColorOfNotifications()) {
if (isLastChild && canModifyColorOfNotifications()) {
if (colorOfViewBeforeLast == NO_COLOR) {
colorOfViewBeforeLast = ownColorUntinted;
}
@@ -490,6 +493,14 @@ public class NotificationShelf extends ActivatableNotificationView implements St
}
}
private boolean canModifyColorOfNotifications() {
if (mShelfRefactorFlagEnabled) {
return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded();
} else {
return mController.canModifyColorOfNotifications();
}
}
private void updateCornerRoundnessOnScroll(
ActivatableNotificationView anv,
float viewStart,
@@ -959,10 +970,31 @@ public class NotificationShelf extends ActivatableNotificationView implements St
return false;
}
private void assertRefactorFlagDisabled() {
if (mShelfRefactorFlagEnabled) {
throw new IllegalStateException(
"Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled.");
}
}
private boolean checkRefactorFlagEnabled() {
if (!mShelfRefactorFlagEnabled) {
Log.wtf(TAG,
"Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is disabled.");
}
return mShelfRefactorFlagEnabled;
}
public void setController(NotificationShelfController notificationShelfController) {
assertRefactorFlagDisabled();
mController = notificationShelfController;
}
public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) {
if (!checkRefactorFlagEnabled()) return;
mCanModifyColorOfNotifications = canModifyColorOfNotifications;
}
public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
}
@@ -975,6 +1007,10 @@ public class NotificationShelf extends ActivatableNotificationView implements St
mSensitiveRevealAnimEndabled = enabled;
}
public void setRefactorFlagEnabled(boolean enabled) {
mShelfRefactorFlagEnabled = enabled;
}
/**
* This method resets the OnScroll roundness of a view to 0f
* <p>

View File

@@ -0,0 +1,44 @@
/*
* 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.statusbar.notification.shelf.domain.interactor
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
/** Interactor for the [NotificationShelf] */
@CentralSurfacesComponent.CentralSurfacesScope
class NotificationShelfInteractor
@Inject
constructor(
private val keyguardRepository: KeyguardRepository,
private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
) {
/** Is the system in a state where the shelf is just a static display of notification icons? */
val isShelfStatic: Flow<Boolean>
get() =
combine(
keyguardRepository.isKeyguardShowing,
deviceEntryFaceAuthRepository.isBypassEnabled,
) { isKeyguardShowing, isBypassEnabled ->
isKeyguardShowing && isBypassEnabled
}
}

View File

@@ -14,14 +14,17 @@
* limitations under the License.
*/
package com.android.systemui.statusbar.notification.shelf.view
package com.android.systemui.statusbar.notification.shelf.ui.viewbinder
import android.view.View
import android.view.View.OnAttachStateChangeListener
import android.view.accessibility.AccessibilityManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl
import com.android.systemui.statusbar.NotificationShelf
@@ -31,21 +34,16 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi
import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController
import com.android.systemui.statusbar.notification.row.ExpandableOutlineViewController
import com.android.systemui.statusbar.notification.row.ExpandableViewController
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.phone.NotificationTapHelper
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
import dagger.Binds
import dagger.Module
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject
/** Binds a [NotificationShelf] to its backend. */
interface NotificationShelfViewBinder {
fun bind(shelf: NotificationShelf)
}
/**
* Controller class for [NotificationShelf]. This implementation serves as a temporary wrapper
* around a [NotificationShelfViewBinder], so that external code can continue to depend on the
@@ -57,8 +55,7 @@ class NotificationShelfViewBinderWrapperControllerImpl
@Inject
constructor(
private val shelf: NotificationShelf,
private val viewBinder: NotificationShelfViewBinder,
private val keyguardBypassController: KeyguardBypassController,
private val viewModel: NotificationShelfViewModel,
featureFlags: FeatureFlags,
private val notifTapHelperFactory: NotificationTapHelper.Factory,
private val a11yManager: AccessibilityManager,
@@ -67,20 +64,19 @@ constructor(
private val statusBarStateController: SysuiStatusBarStateController,
) : NotificationShelfController {
private var ambientState: AmbientState? = null
override val view: NotificationShelf
get() = shelf
init {
shelf.apply {
setRefactorFlagEnabled(featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR))
useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES))
setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
}
}
fun init() {
viewBinder.bind(shelf)
NotificationShelfViewBinder.bind(viewModel, shelf)
ActivatableNotificationViewController(
shelf,
@@ -91,7 +87,6 @@ constructor(
falsingCollector,
)
.init()
shelf.setController(this)
val onAttachStateListener =
object : OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
@@ -117,10 +112,7 @@ constructor(
override val shelfIcons: NotificationIconContainer
get() = shelf.shelfIcons
override fun canModifyColorOfNotifications(): Boolean {
return (ambientState?.isShadeExpanded == true &&
!(ambientState?.isOnKeyguard == true && keyguardBypassController.bypassEnabled))
}
override fun canModifyColorOfNotifications(): Boolean = unsupported
override fun setOnActivatedListener(listener: ActivatableNotificationView.OnActivatedListener) {
shelf.setOnActivatedListener(listener)
@@ -128,25 +120,28 @@ constructor(
override fun bind(
ambientState: AmbientState,
notificationStackScrollLayoutController: NotificationStackScrollLayoutController
notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
) {
shelf.bind(ambientState, notificationStackScrollLayoutController)
this.ambientState = ambientState
}
override fun setOnClickListener(listener: View.OnClickListener) {
shelf.setOnClickListener(listener)
}
private val unsupported: Nothing
get() = error("Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is enabled")
}
@Module(includes = [PrivateShelfViewBinderModule::class]) object NotificationShelfViewBinderModule
@Module
private interface PrivateShelfViewBinderModule {
@Binds fun bindImpl(impl: NotificationShelfViewBinderImpl): NotificationShelfViewBinder
}
@CentralSurfacesScope
private class NotificationShelfViewBinderImpl @Inject constructor() : NotificationShelfViewBinder {
override fun bind(shelf: NotificationShelf) {}
/** Binds a [NotificationShelf] to its backend. */
object NotificationShelfViewBinder {
fun bind(viewModel: NotificationShelfViewModel, shelf: NotificationShelf) {
shelf.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.canModifyColorOfNotifications
.onEach(shelf::setCanModifyColorOfNotifications)
.launchIn(this)
}
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* 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.statusbar.notification.shelf.ui.viewmodel
import com.android.systemui.statusbar.NotificationShelf
import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
/** ViewModel for [NotificationShelf]. */
@CentralSurfacesScope
class NotificationShelfViewModel
@Inject
constructor(
private val interactor: NotificationShelfInteractor,
) {
/** Is the shelf allowed to modify the color of notifications in the host layout? */
val canModifyColorOfNotifications: Flow<Boolean>
get() = interactor.isShelfStatic.map { static -> !static }
}

View File

@@ -52,8 +52,7 @@ import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
import com.android.systemui.statusbar.notification.shelf.view.NotificationShelfViewBinderModule;
import com.android.systemui.statusbar.notification.shelf.view.NotificationShelfViewBinderWrapperControllerImpl;
import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
@@ -87,8 +86,7 @@ import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
@Module(subcomponents = StatusBarFragmentComponent.class,
includes = { NotificationShelfViewBinderModule.class })
@Module(subcomponents = StatusBarFragmentComponent.class)
public abstract class StatusBarViewModule {
public static final String SHADE_HEADER = "large_screen_shade_header";

View File

@@ -59,11 +59,10 @@ import com.android.systemui.statusbar.phone.FakeKeyguardStateController
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.captureMany
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.SystemClock
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -81,6 +80,7 @@ import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.isNull
import org.mockito.Mockito.mock
@@ -88,6 +88,8 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
import java.io.PrintWriter
import java.io.StringWriter
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -120,6 +122,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
private lateinit var authStatus: FlowValue<AuthenticationStatus?>
private lateinit var detectStatus: FlowValue<DetectionStatus?>
private lateinit var authRunning: FlowValue<Boolean?>
private lateinit var bypassEnabled: FlowValue<Boolean?>
private lateinit var lockedOut: FlowValue<Boolean?>
private lateinit var canFaceAuthRun: FlowValue<Boolean?>
private lateinit var authenticated: FlowValue<Boolean?>
@@ -725,6 +728,23 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
}
@Test
fun isBypassEnabledReflectsBypassControllerState() =
testScope.runTest {
initCollectors()
runCurrent()
val listeners = captureMany {
verify(bypassController, atLeastOnce())
.registerOnBypassStateChangedListener(capture())
}
listeners.forEach { it.onBypassStateChanged(true) }
assertThat(bypassEnabled()).isTrue()
listeners.forEach { it.onBypassStateChanged(false) }
assertThat(bypassEnabled()).isFalse()
}
@Test
fun detectDoesNotRunWhenNonStrongBiometricIsAllowed() =
testScope.runTest {
@@ -844,6 +864,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
lockedOut = collectLastValue(underTest.isLockedOut)
canFaceAuthRun = collectLastValue(underTest.canRunFaceAuth)
authenticated = collectLastValue(underTest.isAuthenticated)
bypassEnabled = collectLastValue(underTest.isBypassEnabled)
fakeUserRepository.setSelectedUserInfo(primaryUser)
}

View File

@@ -0,0 +1,71 @@
/*
* 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.
*/
@file:OptIn(ExperimentalCoroutinesApi::class)
package com.android.systemui.statusbar.notification.shelf.domain.interactor
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@SmallTest
class NotificationShelfInteractorTest : SysuiTestCase() {
private val keyguardRepository = FakeKeyguardRepository()
private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
private val underTest =
NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
@Test
fun shelfIsNotStatic_whenKeyguardNotShowing() = runTest {
val shelfStatic by collectLastValue(underTest.isShelfStatic)
keyguardRepository.setKeyguardShowing(false)
assertThat(shelfStatic).isFalse()
}
@Test
fun shelfIsNotStatic_whenKeyguardShowingAndNotBypass() = runTest {
val shelfStatic by collectLastValue(underTest.isShelfStatic)
keyguardRepository.setKeyguardShowing(true)
deviceEntryFaceAuthRepository.isBypassEnabled.value = false
assertThat(shelfStatic).isFalse()
}
@Test
fun shelfIsStatic_whenBypass() = runTest {
val shelfStatic by collectLastValue(underTest.isShelfStatic)
keyguardRepository.setKeyguardShowing(true)
deviceEntryFaceAuthRepository.isBypassEnabled.value = true
assertThat(shelfStatic).isTrue()
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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.
*/
@file:OptIn(ExperimentalCoroutinesApi::class)
package com.android.systemui.statusbar.notification.shelf.ui.viewmodel
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidTestingRunner::class)
@SmallTest
class NotificationShelfViewModelTest : SysuiTestCase() {
private val keyguardRepository = FakeKeyguardRepository()
private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
private val interactor =
NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
private val underTest = NotificationShelfViewModel(interactor)
@Test
fun canModifyColorOfNotifications_whenKeyguardNotShowing() = runTest {
val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
keyguardRepository.setKeyguardShowing(false)
assertThat(canModifyNotifColor).isTrue()
}
@Test
fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() = runTest {
val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
keyguardRepository.setKeyguardShowing(true)
deviceEntryFaceAuthRepository.isBypassEnabled.value = false
assertThat(canModifyNotifColor).isTrue()
}
@Test
fun cannotModifyColorOfNotifications_whenBypass() = runTest {
val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
keyguardRepository.setKeyguardShowing(true)
deviceEntryFaceAuthRepository.isBypassEnabled.value = true
assertThat(canModifyNotifColor).isFalse()
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.keyguard.data.repository
import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.keyguard.shared.model.AuthenticationStatus
import com.android.systemui.keyguard.shared.model.DetectionStatus
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
override val isAuthenticated = MutableStateFlow(false)
override val canRunFaceAuth = MutableStateFlow(false)
private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null)
override val authenticationStatus: Flow<AuthenticationStatus> =
_authenticationStatus.filterNotNull()
fun setAuthenticationStatus(status: AuthenticationStatus) {
_authenticationStatus.value = status
}
private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
override val detectionStatus: Flow<DetectionStatus>
get() = _detectionStatus.filterNotNull()
fun setDetectionStatus(status: DetectionStatus) {
_detectionStatus.value = status
}
override val isLockedOut = MutableStateFlow(false)
private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
_runningAuthRequest.asStateFlow()
override val isAuthRunning = _runningAuthRequest.map { it != null }
override val isBypassEnabled = MutableStateFlow(false)
override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
_runningAuthRequest.value = uiEvent to fallbackToDetection
}
override fun cancel() {
_runningAuthRequest.value = null
}
}