From 15f23551164828e4b278718114dee22e22740617 Mon Sep 17 00:00:00 2001 From: Beverly Date: Wed, 12 Jul 2023 21:07:26 +0000 Subject: [PATCH] Fade out UDFPS on shadeExpand & dialogProgress When the UDFPS view refactor is enabled, fade out UDFPS views when the shade expands to full shade and to QS. Also, don't show the UDFPS view when a dialog shows. Test: atest UdfpsKeyguardInteractorTest UdfpsLockscreenViewModelTest Test: manually enable udfps view refactor and pull down notification shade (from the middle of LS and from the top). Launch dialogs such as the power menu. See UDFPS lockscreen view hides + shows as expected Bug: 278719514 Change-Id: I5cbcb217b7bd5df7a7f19cb118b843c3de0db744 --- .../interactor/UdfpsKeyguardInteractor.kt | 28 +++ .../ui/viewmodel/UdfpsLockscreenViewModel.kt | 130 +++++++---- .../phone/SystemUIDialogManagerExt.kt | 34 +++ .../interactor/UdfpsKeyguardInteractorTest.kt | 65 +++++- .../ui/viewmodel/UdfpsAodViewModelTest.kt | 7 + .../UdfpsFingerprintViewModelTest.kt | 42 ++-- .../viewmodel/UdfpsLockscreenViewModelTest.kt | 208 +++++++++++++++++- 7 files changed, 454 insertions(+), 60 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogManagerExt.kt 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", + ) + ) + } }