diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt index bba0e37d8ed07..c0308e6c5759f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt @@ -21,10 +21,16 @@ import android.animation.FloatEvaluator import android.animation.IntEvaluator import com.android.systemui.common.ui.data.repository.ConfigurationRepository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.statusbar.phone.SystemUIDialogManager +import com.android.systemui.statusbar.phone.hideAffordancesRequest import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart /** Encapsulates business logic for transitions between UDFPS states on the keyguard. */ @ExperimentalCoroutinesApi @@ -35,6 +41,8 @@ constructor( configRepo: ConfigurationRepository, burnInInteractor: BurnInInteractor, keyguardInteractor: KeyguardInteractor, + shadeRepository: ShadeRepository, + dialogManager: SystemUIDialogManager, ) { private val intEvaluator = IntEvaluator() private val floatEvaluator = FloatEvaluator() @@ -56,6 +64,26 @@ constructor( floatEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInProgress), ) } + + val dialogHideAffordancesRequest: Flow = dialogManager.hideAffordancesRequest + + val qsProgress: Flow = + shadeRepository.qsExpansion // swipe from top of LS + .map { (it * 2).coerceIn(0f, 1f) } + .onStart { emit(0f) } + + val shadeExpansion: Flow = + combine( + shadeRepository.udfpsTransitionToFullShadeProgress, // swipe from middle of LS + keyguardInteractor.statusBarState, // quick swipe from middle of LS + ) { shadeProgress, statusBarState -> + if (statusBarState == StatusBarState.SHADE_LOCKED) { + 1f + } else { + shadeProgress + } + } + .onStart { emit(0f) } } data class BurnInOffsets( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt index fd4b666a80fd8..d58f3c94b07a2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt @@ -21,13 +21,17 @@ import androidx.annotation.ColorInt import com.android.settingslib.Utils.getColorAttrDefaultColor import com.android.systemui.R import com.android.systemui.keyguard.domain.interactor.BurnInOffsets +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState import javax.inject.Inject import kotlin.math.roundToInt import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge @@ -38,6 +42,8 @@ open class UdfpsLockscreenViewModel( lockscreenColorResId: Int, alternateBouncerColorResId: Int, transitionInteractor: KeyguardTransitionInteractor, + udfpsKeyguardInteractor: UdfpsKeyguardInteractor, + keyguardInteractor: KeyguardInteractor, ) { private val toLockscreen: Flow = transitionInteractor.anyStateToLockscreenTransition.map { @@ -54,46 +60,53 @@ open class UdfpsLockscreenViewModel( } private val toAlternateBouncer: Flow = - transitionInteractor.anyStateToAlternateBouncerTransition.map { - TransitionViewModel( - alpha = 1f, - scale = - if (visibleInKeyguardState(it.from)) { - 1f - } else { - it.value - }, - color = getColorAttrDefaultColor(context, alternateBouncerColorResId), - ) + keyguardInteractor.statusBarState.flatMapLatest { statusBarState -> + transitionInteractor.anyStateToAlternateBouncerTransition.map { + TransitionViewModel( + alpha = 1f, + scale = + if (visibleInKeyguardState(it.from, statusBarState)) { + 1f + } else { + it.value + }, + color = getColorAttrDefaultColor(context, alternateBouncerColorResId), + ) + } } private val fadeOut: Flow = - merge( - transitionInteractor.anyStateToGoneTransition, - transitionInteractor.anyStateToAodTransition, - transitionInteractor.anyStateToOccludedTransition, - transitionInteractor.anyStateToPrimaryBouncerTransition, - transitionInteractor.anyStateToDreamingTransition, - ) - .map { - TransitionViewModel( - alpha = - if (visibleInKeyguardState(it.from)) { - 1f - it.value - } else { - 0f - }, - scale = 1f, - color = - if (it.from == KeyguardState.ALTERNATE_BOUNCER) { - getColorAttrDefaultColor(context, alternateBouncerColorResId) - } else { - getColorAttrDefaultColor(context, lockscreenColorResId) - }, + keyguardInteractor.statusBarState.flatMapLatest { statusBarState -> + merge( + transitionInteractor.anyStateToGoneTransition, + transitionInteractor.anyStateToAodTransition, + transitionInteractor.anyStateToOccludedTransition, + transitionInteractor.anyStateToPrimaryBouncerTransition, + transitionInteractor.anyStateToDreamingTransition, ) - } + .map { + TransitionViewModel( + alpha = + if (visibleInKeyguardState(it.from, statusBarState)) { + 1f - it.value + } else { + 0f + }, + scale = 1f, + color = + if (it.from == KeyguardState.ALTERNATE_BOUNCER) { + getColorAttrDefaultColor(context, alternateBouncerColorResId) + } else { + getColorAttrDefaultColor(context, lockscreenColorResId) + }, + ) + } + } - private fun visibleInKeyguardState(state: KeyguardState): Boolean { + private fun visibleInKeyguardState( + state: KeyguardState, + statusBarState: StatusBarState + ): Boolean { return when (state) { KeyguardState.OFF, KeyguardState.DOZING, @@ -102,17 +115,53 @@ open class UdfpsLockscreenViewModel( KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, KeyguardState.OCCLUDED -> false - KeyguardState.LOCKSCREEN, + KeyguardState.LOCKSCREEN -> statusBarState == StatusBarState.KEYGUARD KeyguardState.ALTERNATE_BOUNCER -> true } } - val transition: Flow = + private val keyguardStateTransition = merge( toAlternateBouncer, toLockscreen, fadeOut, ) + + private val dialogHideAffordancesAlphaMultiplier: Flow = + udfpsKeyguardInteractor.dialogHideAffordancesRequest.map { hideAffordances -> + if (hideAffordances) { + 0f + } else { + 1f + } + } + + private val alphaMultiplier: Flow = + combine( + transitionInteractor.startedKeyguardState, + dialogHideAffordancesAlphaMultiplier, + udfpsKeyguardInteractor.shadeExpansion, + udfpsKeyguardInteractor.qsProgress, + ) { startedKeyguardState, dialogHideAffordancesAlphaMultiplier, shadeExpansion, qsProgress + -> + if (startedKeyguardState == KeyguardState.ALTERNATE_BOUNCER) { + 1f + } else { + dialogHideAffordancesAlphaMultiplier * (1f - shadeExpansion) * (1f - qsProgress) + } + } + + val transition: Flow = + combine( + alphaMultiplier, + keyguardStateTransition, + ) { alphaMultiplier, keyguardStateTransition -> + TransitionViewModel( + alpha = keyguardStateTransition.alpha * alphaMultiplier, + scale = keyguardStateTransition.scale, + color = keyguardStateTransition.color, + ) + } val visible: Flow = transition.map { it.alpha != 0f } } @@ -123,12 +172,15 @@ constructor( val context: Context, transitionInteractor: KeyguardTransitionInteractor, interactor: UdfpsKeyguardInteractor, + keyguardInteractor: KeyguardInteractor, ) : UdfpsLockscreenViewModel( context, android.R.attr.textColorPrimary, com.android.internal.R.attr.materialColorOnPrimaryFixed, transitionInteractor, + interactor, + keyguardInteractor, ) { val dozeAmount: Flow = interactor.dozeAmount val burnInOffsets: Flow = interactor.burnInOffsets @@ -147,12 +199,16 @@ class BackgroundViewModel constructor( val context: Context, transitionInteractor: KeyguardTransitionInteractor, + interactor: UdfpsKeyguardInteractor, + keyguardInteractor: KeyguardInteractor, ) : UdfpsLockscreenViewModel( context, com.android.internal.R.attr.colorSurface, com.android.internal.R.attr.materialColorPrimaryFixed, transitionInteractor, + interactor, + keyguardInteractor, ) data class TransitionViewModel( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt new file mode 100644 index 0000000000000..fbc6b9524a6d6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt @@ -0,0 +1,34 @@ +/* + * 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.phone + +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow + +/** Whether dialogs are requesting for affordances to be hidden or not. */ +val SystemUIDialogManager.hideAffordancesRequest: Flow + get() = conflatedCallbackFlow { + val callback = + SystemUIDialogManager.Listener { hideAffordance -> + trySendWithFailureLogging(hideAffordance, "dialogHideAffordancesRequest") + } + registerListener(callback) + trySendWithFailureLogging(shouldHideAffordance(), "dialogHideAffordancesRequestInitial") + awaitClose { unregisterListener(callback) } + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt index 1baca2184e9ba..b019a21387dad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt @@ -33,6 +33,9 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.WakeSleepReason import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.phone.SystemUIDialogManager +import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock @@ -46,6 +49,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @ExperimentalCoroutinesApi @@ -63,19 +67,21 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() { private lateinit var fakeCommandQueue: FakeCommandQueue private lateinit var featureFlags: FakeFeatureFlags private lateinit var burnInInteractor: BurnInInteractor + private lateinit var shadeRepository: FakeShadeRepository @Mock private lateinit var burnInHelper: BurnInHelperWrapper + @Mock private lateinit var dialogManager: SystemUIDialogManager private lateinit var underTest: UdfpsKeyguardInteractor @Before fun setUp() { MockitoAnnotations.initMocks(this) - testScope = TestScope() configRepository = FakeConfigurationRepository() keyguardRepository = FakeKeyguardRepository() bouncerRepository = FakeKeyguardBouncerRepository() + shadeRepository = FakeShadeRepository() fakeCommandQueue = FakeCommandQueue() featureFlags = FakeFeatureFlags().apply { @@ -102,6 +108,8 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() { bouncerRepository, configRepository, ), + shadeRepository, + dialogManager, ) } @@ -142,6 +150,61 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() { assertThat(burnInOffsets?.burnInXOffset).isEqualTo(burnInXOffset) } + @Test + fun dialogHideAffordances() = + testScope.runTest { + val dialogHideAffordancesRequest by + collectLastValue(underTest.dialogHideAffordancesRequest) + runCurrent() + val captor = argumentCaptor() + verify(dialogManager).registerListener(captor.capture()) + + captor.value.shouldHideAffordances(false) + assertThat(dialogHideAffordancesRequest).isEqualTo(false) + + captor.value.shouldHideAffordances(true) + assertThat(dialogHideAffordancesRequest).isEqualTo(true) + + captor.value.shouldHideAffordances(false) + assertThat(dialogHideAffordancesRequest).isEqualTo(false) + } + + @Test + fun shadeExpansion_updates() = + testScope.runTest { + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + val shadeExpansion by collectLastValue(underTest.shadeExpansion) + assertThat(shadeExpansion).isEqualTo(0f) + + shadeRepository.setUdfpsTransitionToFullShadeProgress(.5f) + assertThat(shadeExpansion).isEqualTo(.5f) + + shadeRepository.setUdfpsTransitionToFullShadeProgress(.7f) + assertThat(shadeExpansion).isEqualTo(.7f) + + shadeRepository.setUdfpsTransitionToFullShadeProgress(.22f) + assertThat(shadeExpansion).isEqualTo(.22f) + + keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) + assertThat(shadeExpansion).isEqualTo(1f) + } + + @Test + fun qsProgress_updates() = + testScope.runTest { + val qsProgress by collectLastValue(underTest.qsProgress) + assertThat(qsProgress).isEqualTo(0f) + + shadeRepository.setQsExpansion(.22f) + assertThat(qsProgress).isEqualTo(.44f) + + shadeRepository.setQsExpansion(.5f) + assertThat(qsProgress).isEqualTo(1f) + + shadeRepository.setQsExpansion(.7f) + assertThat(qsProgress).isEqualTo(1f) + } + private fun initializeBurnInOffsets() { whenever(burnInHelper.burnInProgressOffset()).thenReturn(burnInProgress) whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(true))) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt index 436c09ca4a05a..b985b3ca83da0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt @@ -32,6 +32,8 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -58,7 +60,9 @@ class UdfpsAodViewModelTest : SysuiTestCase() { private lateinit var keyguardRepository: FakeKeyguardRepository private lateinit var fakeCommandQueue: FakeCommandQueue private lateinit var featureFlags: FakeFeatureFlags + private lateinit var shadeRepository: FakeShadeRepository + @Mock private lateinit var dialogManager: SystemUIDialogManager @Mock private lateinit var burnInHelper: BurnInHelperWrapper @Before @@ -70,6 +74,7 @@ class UdfpsAodViewModelTest : SysuiTestCase() { keyguardRepository = FakeKeyguardRepository() bouncerRepository = FakeKeyguardBouncerRepository() fakeCommandQueue = FakeCommandQueue() + shadeRepository = FakeShadeRepository() featureFlags = FakeFeatureFlags().apply { set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) @@ -93,6 +98,8 @@ class UdfpsAodViewModelTest : SysuiTestCase() { bouncerRepository, configRepository, ), + shadeRepository, + dialogManager, ) underTest = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt index a30e2a601e9da..0fbcec23f2478 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt @@ -33,6 +33,8 @@ import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -60,8 +62,10 @@ class UdfpsFingerprintViewModelTest : SysuiTestCase() { private lateinit var fakeCommandQueue: FakeCommandQueue private lateinit var featureFlags: FakeFeatureFlags private lateinit var transitionRepository: FakeKeyguardTransitionRepository + private lateinit var shadeRepository: FakeShadeRepository @Mock private lateinit var burnInHelper: BurnInHelperWrapper + @Mock private lateinit var dialogManager: SystemUIDialogManager @Before fun setUp() { @@ -79,35 +83,39 @@ class UdfpsFingerprintViewModelTest : SysuiTestCase() { } bouncerRepository = FakeKeyguardBouncerRepository() transitionRepository = FakeKeyguardTransitionRepository() + shadeRepository = FakeShadeRepository() val transitionInteractor = KeyguardTransitionInteractor( transitionRepository, testScope.backgroundScope, ) - val udfpsKeyguardInteractor = - UdfpsKeyguardInteractor( + val keyguardInteractor = + KeyguardInteractor( + keyguardRepository, + fakeCommandQueue, + featureFlags, + bouncerRepository, configRepository, - BurnInInteractor( - context, - burnInHelper, - testScope.backgroundScope, - configRepository, - FakeSystemClock(), - ), - KeyguardInteractor( - keyguardRepository, - fakeCommandQueue, - featureFlags, - bouncerRepository, - configRepository, - ), ) underTest = FingerprintViewModel( context, transitionInteractor, - udfpsKeyguardInteractor, + UdfpsKeyguardInteractor( + configRepository, + BurnInInteractor( + context, + burnInHelper, + testScope.backgroundScope, + configRepository, + FakeSystemClock(), + ), + keyguardInteractor, + shadeRepository, + dialogManager, + ), + keyguardInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt index d58ceee40c686..7b76a480892b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt @@ -20,12 +20,26 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.Utils import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.BurnInInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.statusbar.phone.SystemUIDialogManager +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.time.FakeSystemClock import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -35,6 +49,8 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito import org.mockito.MockitoAnnotations /** Tests UDFPS lockscreen view model transitions. */ @@ -48,26 +64,63 @@ class UdfpsLockscreenViewModelTest : SysuiTestCase() { private val alternateBouncerColor = Utils.getColorAttrDefaultColor(context, alternateBouncerResId) + @Mock private lateinit var dialogManager: SystemUIDialogManager + private lateinit var underTest: UdfpsLockscreenViewModel private lateinit var testScope: TestScope private lateinit var transitionRepository: FakeKeyguardTransitionRepository + private lateinit var configRepository: FakeConfigurationRepository + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var bouncerRepository: FakeKeyguardBouncerRepository + private lateinit var shadeRepository: FakeShadeRepository + private lateinit var featureFlags: FakeFeatureFlags @Before fun setUp() { MockitoAnnotations.initMocks(this) testScope = TestScope() transitionRepository = FakeKeyguardTransitionRepository() - val transitionInteractor = - KeyguardTransitionInteractor( - transitionRepository, - testScope.backgroundScope, + configRepository = FakeConfigurationRepository() + keyguardRepository = FakeKeyguardRepository() + bouncerRepository = FakeKeyguardBouncerRepository() + shadeRepository = FakeShadeRepository() + featureFlags = + FakeFeatureFlags().apply { + set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) + set(Flags.FACE_AUTH_REFACTOR, false) + } + val keyguardInteractor = + KeyguardInteractor( + keyguardRepository, + commandQueue = mock(), + featureFlags, + bouncerRepository, + configRepository, ) + underTest = UdfpsLockscreenViewModel( context, lockscreenColorResId, alternateBouncerResId, - transitionInteractor, + KeyguardTransitionInteractor( + transitionRepository, + testScope.backgroundScope, + ), + UdfpsKeyguardInteractor( + configRepository, + BurnInInteractor( + context, + burnInHelperWrapper = mock(), + testScope.backgroundScope, + configRepository, + FakeSystemClock(), + ), + keyguardInteractor, + shadeRepository, + dialogManager, + ), + keyguardInteractor, ) } @@ -125,6 +178,7 @@ class UdfpsLockscreenViewModelTest : SysuiTestCase() { testScope.runTest { val transition by collectLastValue(underTest.transition) val visible by collectLastValue(underTest.visible) + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) // TransitionState.STARTED: lockscreen -> AOD transitionRepository.sendTransitionStep( @@ -175,6 +229,56 @@ class UdfpsLockscreenViewModelTest : SysuiTestCase() { assertThat(visible).isFalse() } + @Test + fun lockscreenShadeLockedToAod() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) + + // TransitionState.STARTED: lockscreen -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "lockscreenToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(visible).isFalse() + + // TransitionState.RUNNING: lockscreen -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "lockscreenToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(visible).isFalse() + + // TransitionState.FINISHED: lockscreen -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "lockscreenToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(visible).isFalse() + } + @Test fun aodToLockscreen() = testScope.runTest { @@ -235,6 +339,7 @@ class UdfpsLockscreenViewModelTest : SysuiTestCase() { testScope.runTest { val transition by collectLastValue(underTest.transition) val visible by collectLastValue(underTest.visible) + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) // TransitionState.STARTED: lockscreen -> alternate bouncer transitionRepository.sendTransitionStep( @@ -398,6 +503,7 @@ class UdfpsLockscreenViewModelTest : SysuiTestCase() { testScope.runTest { val transition by collectLastValue(underTest.transition) val visible by collectLastValue(underTest.visible) + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) // TransitionState.STARTED: lockscreen -> occluded transitionRepository.sendTransitionStep( @@ -502,4 +608,96 @@ class UdfpsLockscreenViewModelTest : SysuiTestCase() { assertThat(transition?.color).isEqualTo(lockscreenColor) assertThat(visible).isTrue() } + + @Test + fun qsProgressChange() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + givenTransitionToLockscreenFinished() + + // qsExpansion = 0f + shadeRepository.setQsExpansion(0f) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(visible).isEqualTo(true) + + // qsExpansion = .25 + shadeRepository.setQsExpansion(.2f) + runCurrent() + assertThat(transition?.alpha).isEqualTo(.6f) + assertThat(visible).isEqualTo(true) + + // qsExpansion = .5 + shadeRepository.setQsExpansion(.5f) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(visible).isEqualTo(false) + + // qsExpansion = 1 + shadeRepository.setQsExpansion(1f) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(visible).isEqualTo(false) + } + + @Test + fun shadeExpansionChanged() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + givenTransitionToLockscreenFinished() + + // shadeExpansion = 0f + shadeRepository.setUdfpsTransitionToFullShadeProgress(0f) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(visible).isEqualTo(true) + + // shadeExpansion = .2 + shadeRepository.setUdfpsTransitionToFullShadeProgress(.2f) + runCurrent() + assertThat(transition?.alpha).isEqualTo(.8f) + assertThat(visible).isEqualTo(true) + + // shadeExpansion = .5 + shadeRepository.setUdfpsTransitionToFullShadeProgress(.5f) + runCurrent() + assertThat(transition?.alpha).isEqualTo(.5f) + assertThat(visible).isEqualTo(true) + + // shadeExpansion = 1 + shadeRepository.setUdfpsTransitionToFullShadeProgress(1f) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(visible).isEqualTo(false) + } + + @Test + fun dialogHideAffordancesRequestChanged() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + givenTransitionToLockscreenFinished() + runCurrent() + val captor = argumentCaptor() + Mockito.verify(dialogManager).registerListener(captor.capture()) + + captor.value.shouldHideAffordances(true) + assertThat(transition?.alpha).isEqualTo(0f) + + captor.value.shouldHideAffordances(false) + assertThat(transition?.alpha).isEqualTo(1f) + } + + private suspend fun givenTransitionToLockscreenFinished() { + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "givenTransitionToLockscreenFinished", + ) + ) + } }