Merge "Fix new lightreveal transition" into udc-qpr-dev am: de0c58bc60

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

Change-Id: I4bd5661ab213f75dd001c4b9ecb2b3aaa86abddc
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Andreas Miko
2023-07-23 20:22:55 +00:00
committed by Automerger Merge Worker
10 changed files with 257 additions and 178 deletions

View File

@@ -20,10 +20,13 @@ package com.android.systemui.keyguard.data.repository
import android.content.Context import android.content.Context
import android.graphics.Point import android.graphics.Point
import androidx.core.animation.Animator
import androidx.core.animation.ValueAnimator
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.WakeSleepReason.TAP
import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LiftReveal import com.android.systemui.statusbar.LiftReveal
import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.LightRevealEffect
@@ -31,9 +34,12 @@ import com.android.systemui.statusbar.PowerButtonReveal
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.max import kotlin.math.max
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@@ -52,6 +58,10 @@ interface LightRevealScrimRepository {
* at the current screen position of the appropriate sensor. * at the current screen position of the appropriate sensor.
*/ */
val revealEffect: Flow<LightRevealEffect> val revealEffect: Flow<LightRevealEffect>
val revealAmount: Flow<Float>
fun startRevealAmountAnimator(reveal: Boolean)
} }
@SysUISingleton @SysUISingleton
@@ -108,14 +118,31 @@ constructor(
/** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */ /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */
private val nonBiometricRevealEffect: Flow<LightRevealEffect?> = private val nonBiometricRevealEffect: Flow<LightRevealEffect?> =
keyguardRepository.wakefulness.flatMapLatest { wakefulnessModel -> keyguardRepository.wakefulness
.filter { it.isStartingToWake() || it.isStartingToSleep() }
.flatMapLatest { wakefulnessModel ->
when { when {
wakefulnessModel.isTransitioningFromPowerButton() -> powerButtonRevealEffect wakefulnessModel.isTransitioningFromPowerButton() -> powerButtonRevealEffect
wakefulnessModel.isAwakeFromTap() -> tapRevealEffect wakefulnessModel.isWakingFrom(TAP) -> tapRevealEffect
else -> flowOf(LiftReveal) else -> flowOf(LiftReveal)
} }
} }
private val revealAmountAnimator = ValueAnimator.ofFloat(0f, 1f).apply { duration = 500 }
override val revealAmount: Flow<Float> = callbackFlow {
val updateListener =
Animator.AnimatorUpdateListener {
trySend((it as ValueAnimator).animatedValue as Float)
}
revealAmountAnimator.addUpdateListener(updateListener)
awaitClose { revealAmountAnimator.removeUpdateListener(updateListener) }
}
override fun startRevealAmountAnimator(reveal: Boolean) {
if (reveal) revealAmountAnimator.start() else revealAmountAnimator.reverse()
}
override val revealEffect = override val revealEffect =
combine( combine(
keyguardRepository.biometricUnlockState, keyguardRepository.biometricUnlockState,

View File

@@ -17,28 +17,44 @@
package com.android.systemui.keyguard.domain.interactor package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.util.kotlin.sample import com.android.systemui.util.kotlin.sample
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.map
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
@SysUISingleton @SysUISingleton
class LightRevealScrimInteractor class LightRevealScrimInteractor
@Inject @Inject
constructor( constructor(
transitionRepository: KeyguardTransitionRepository, private val transitionInteractor: KeyguardTransitionInteractor,
transitionInteractor: KeyguardTransitionInteractor, private val lightRevealScrimRepository: LightRevealScrimRepository,
lightRevealScrimRepository: LightRevealScrimRepository, @Application private val scope: CoroutineScope,
) { ) {
init {
listenForStartedKeyguardTransitionStep()
}
private fun listenForStartedKeyguardTransitionStep() {
scope.launch {
transitionInteractor.startedKeyguardTransitionStep.collect {
if (willTransitionChangeEndState(it)) {
lightRevealScrimRepository.startRevealAmountAnimator(
willBeRevealedInState(it.to)
)
}
}
}
}
/** /**
* Whenever a keyguard transition starts, sample the latest reveal effect from the repository * Whenever a keyguard transition starts, sample the latest reveal effect from the repository
* and use that for the starting transition. * and use that for the starting transition.
@@ -54,17 +70,7 @@ constructor(
lightRevealScrimRepository.revealEffect lightRevealScrimRepository.revealEffect
) )
/** val revealAmount = lightRevealScrimRepository.revealAmount
* The reveal amount to use for the light reveal scrim, which is derived from the keyguard
* transition steps.
*/
val revealAmount: Flow<Float> =
transitionRepository.transitions
// Only listen to transitions that change the reveal amount.
.filter { willTransitionAffectRevealAmount(it) }
// Use the transition amount as the reveal amount, inverting it if we're transitioning
// to a non-revealed (hidden) state.
.map { step -> if (willBeRevealedInState(step.to)) step.value else 1f - step.value }
companion object { companion object {
@@ -72,7 +78,7 @@ constructor(
* Whether the transition requires a change in the reveal amount of the light reveal scrim. * Whether the transition requires a change in the reveal amount of the light reveal scrim.
* If not, we don't care about the transition and don't need to listen to it. * If not, we don't care about the transition and don't need to listen to it.
*/ */
fun willTransitionAffectRevealAmount(transition: TransitionStep): Boolean { fun willTransitionChangeEndState(transition: TransitionStep): Boolean {
return willBeRevealedInState(transition.from) != willBeRevealedInState(transition.to) return willBeRevealedInState(transition.from) != willBeRevealedInState(transition.to)
} }

View File

@@ -16,6 +16,13 @@
package com.android.systemui.keyguard.shared.model package com.android.systemui.keyguard.shared.model
import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.shared.model.WakeSleepReason.GESTURE
import com.android.systemui.keyguard.shared.model.WakeSleepReason.POWER_BUTTON
import com.android.systemui.keyguard.shared.model.WakeSleepReason.TAP
import com.android.systemui.keyguard.shared.model.WakefulnessState.ASLEEP
import com.android.systemui.keyguard.shared.model.WakefulnessState.AWAKE
import com.android.systemui.keyguard.shared.model.WakefulnessState.STARTING_TO_SLEEP
import com.android.systemui.keyguard.shared.model.WakefulnessState.STARTING_TO_WAKE
/** Model device wakefulness states. */ /** Model device wakefulness states. */
data class WakefulnessModel( data class WakefulnessModel(
@@ -23,33 +30,31 @@ data class WakefulnessModel(
val lastWakeReason: WakeSleepReason, val lastWakeReason: WakeSleepReason,
val lastSleepReason: WakeSleepReason, val lastSleepReason: WakeSleepReason,
) { ) {
fun isStartingToWake() = state == WakefulnessState.STARTING_TO_WAKE fun isStartingToWake() = state == STARTING_TO_WAKE
fun isStartingToSleep() = state == WakefulnessState.STARTING_TO_SLEEP fun isStartingToSleep() = state == STARTING_TO_SLEEP
private fun isAsleep() = state == WakefulnessState.ASLEEP private fun isAsleep() = state == ASLEEP
private fun isAwake() = state == AWAKE
fun isStartingToWakeOrAwake() = isStartingToWake() || isAwake()
fun isStartingToSleepOrAsleep() = isStartingToSleep() || isAsleep() fun isStartingToSleepOrAsleep() = isStartingToSleep() || isAsleep()
fun isDeviceInteractive() = !isAsleep() fun isDeviceInteractive() = !isAsleep()
fun isStartingToWakeOrAwake() = isStartingToWake() || state == WakefulnessState.AWAKE fun isWakingFrom(wakeSleepReason: WakeSleepReason) =
isStartingToWake() && lastWakeReason == wakeSleepReason
fun isStartingToSleepFromPowerButton() = fun isStartingToSleepFrom(wakeSleepReason: WakeSleepReason) =
isStartingToSleep() && lastWakeReason == WakeSleepReason.POWER_BUTTON isStartingToSleep() && lastSleepReason == wakeSleepReason
fun isWakingFromPowerButton() =
isStartingToWake() && lastWakeReason == WakeSleepReason.POWER_BUTTON
fun isTransitioningFromPowerButton() = fun isTransitioningFromPowerButton() =
isStartingToSleepFromPowerButton() || isWakingFromPowerButton() isStartingToSleepFrom(POWER_BUTTON) || isWakingFrom(POWER_BUTTON)
fun isAwakeFromTap() =
state == WakefulnessState.STARTING_TO_WAKE && lastWakeReason == WakeSleepReason.TAP
fun isDeviceInteractiveFromTapOrGesture(): Boolean { fun isDeviceInteractiveFromTapOrGesture(): Boolean {
return isDeviceInteractive() && return isDeviceInteractive() && (lastWakeReason == TAP || lastWakeReason == GESTURE)
(lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE)
} }
companion object { companion object {

View File

@@ -20,20 +20,18 @@ import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
/** View-model for the keyguard indication area view */ /** View-model for the keyguard indication area view */
@OptIn(ExperimentalCoroutinesApi::class)
class KeyguardIndicationAreaViewModel class KeyguardIndicationAreaViewModel
@Inject @Inject
constructor( constructor(
private val keyguardInteractor: KeyguardInteractor, private val keyguardInteractor: KeyguardInteractor,
private val bottomAreaInteractor: KeyguardBottomAreaInteractor, bottomAreaInteractor: KeyguardBottomAreaInteractor,
private val keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel, keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
private val burnInHelperWrapper: BurnInHelperWrapper, private val burnInHelperWrapper: BurnInHelperWrapper,
) { ) {

View File

@@ -19,12 +19,14 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.keyguard.domain.interactor.LightRevealScrimInteractor import com.android.systemui.keyguard.domain.interactor.LightRevealScrimInteractor
import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.LightRevealEffect
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
/** /**
* Models UI state for the light reveal scrim, which is used during screen on and off animations to * Models UI state for the light reveal scrim, which is used during screen on and off animations to
* draw a gradient that reveals/hides the contents of the screen. * draw a gradient that reveals/hides the contents of the screen.
*/ */
@OptIn(ExperimentalCoroutinesApi::class)
class LightRevealScrimViewModel @Inject constructor(interactor: LightRevealScrimInteractor) { class LightRevealScrimViewModel @Inject constructor(interactor: LightRevealScrimInteractor) {
val lightRevealEffect: Flow<LightRevealEffect> = interactor.lightRevealEffect val lightRevealEffect: Flow<LightRevealEffect> = interactor.lightRevealEffect
val revealAmount: Flow<Float> = interactor.revealAmount val revealAmount: Flow<Float> = interactor.revealAmount

View File

@@ -16,6 +16,7 @@
package com.android.systemui.shade package com.android.systemui.shade
import android.graphics.Point
import android.hardware.display.AmbientDisplayConfiguration import android.hardware.display.AmbientDisplayConfiguration
import android.os.PowerManager import android.os.PowerManager
import android.provider.Settings import android.provider.Settings
@@ -25,6 +26,7 @@ import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dock.DockManager import com.android.systemui.dock.DockManager
import com.android.systemui.dump.DumpManager import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.DozeInteractor
import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.FalsingManager.LOW_PENALTY import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -52,6 +54,7 @@ class PulsingGestureListener @Inject constructor(
private val ambientDisplayConfiguration: AmbientDisplayConfiguration, private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
private val statusBarStateController: StatusBarStateController, private val statusBarStateController: StatusBarStateController,
private val shadeLogger: ShadeLogger, private val shadeLogger: ShadeLogger,
private val dozeInteractor: DozeInteractor,
userTracker: UserTracker, userTracker: UserTracker,
tunerService: TunerService, tunerService: TunerService,
dumpManager: DumpManager dumpManager: DumpManager
@@ -86,6 +89,7 @@ class PulsingGestureListener @Inject constructor(
shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap) shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap)
if (proximityIsNotNear && isNotAFalseTap) { if (proximityIsNotNear && isNotAFalseTap) {
shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing") shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing")
dozeInteractor.setLastTapToWakePosition(Point(e.x.toInt(), e.y.toInt()))
powerInteractor.wakeUpIfDozing("PULSING_SINGLE_TAP", PowerManager.WAKE_REASON_TAP) powerInteractor.wakeUpIfDozing("PULSING_SINGLE_TAP", PowerManager.WAKE_REASON_TAP)
} }
return true return true

View File

@@ -17,31 +17,43 @@
package com.android.systemui.keyguard.data.repository package com.android.systemui.keyguard.data.repository
import android.graphics.Point import android.graphics.Point
import androidx.test.ext.junit.runners.AndroidJUnit4 import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.core.animation.AnimatorTestRule
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.RoboPilotTest import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
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.statusbar.CircleReveal import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.LightRevealEffect
import junit.framework.Assert.assertEquals import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse import junit.framework.Assert.assertFalse
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Before import org.junit.Before
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
@SmallTest @SmallTest
@RoboPilotTest @RoboPilotTest
@RunWith(AndroidJUnit4::class) @OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
class LightRevealScrimRepositoryTest : SysuiTestCase() { class LightRevealScrimRepositoryTest : SysuiTestCase() {
private lateinit var fakeKeyguardRepository: FakeKeyguardRepository private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
private lateinit var underTest: LightRevealScrimRepositoryImpl private lateinit var underTest: LightRevealScrimRepositoryImpl
@get:Rule val animatorTestRule = AnimatorTestRule()
@Before @Before
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
@@ -50,11 +62,17 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() {
} }
@Test @Test
fun nextRevealEffect_effectSwitchesBetweenDefaultAndBiometricWithNoDupes() = fun nextRevealEffect_effectSwitchesBetweenDefaultAndBiometricWithNoDupes() = runTest {
runTest {
val values = mutableListOf<LightRevealEffect>() val values = mutableListOf<LightRevealEffect>()
val job = launch { underTest.revealEffect.collect { values.add(it) } } val job = launch { underTest.revealEffect.collect { values.add(it) } }
fakeKeyguardRepository.setWakefulnessModel(
WakefulnessModel(
WakefulnessState.STARTING_TO_WAKE,
WakeSleepReason.OTHER,
WakeSleepReason.OTHER
)
)
// We should initially emit the default reveal effect. // We should initially emit the default reveal effect.
runCurrent() runCurrent()
values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT }) values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT })
@@ -70,9 +88,7 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() {
// We got a source but still have no sensor locations, so should be sticking with // We got a source but still have no sensor locations, so should be sticking with
// the default effect. // the default effect.
fakeKeyguardRepository.setBiometricUnlockSource( fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
BiometricUnlockSource.FINGERPRINT_SENSOR
)
runCurrent() runCurrent()
values.assertEffectsMatchPredicates( values.assertEffectsMatchPredicates(
@@ -91,12 +107,8 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() {
// Now we have fingerprint sensor locations, and wake and unlock via fingerprint. // Now we have fingerprint sensor locations, and wake and unlock via fingerprint.
val fingerprintLocation = Point(500, 500) val fingerprintLocation = Point(500, 500)
fakeKeyguardRepository.setFingerprintSensorLocation(fingerprintLocation) fakeKeyguardRepository.setFingerprintSensorLocation(fingerprintLocation)
fakeKeyguardRepository.setBiometricUnlockSource( fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR)
BiometricUnlockSource.FINGERPRINT_SENSOR fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING)
)
fakeKeyguardRepository.setBiometricUnlockState(
BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
)
// We should now have switched to the circle reveal, at the fingerprint location. // We should now have switched to the circle reveal, at the fingerprint location.
runCurrent() runCurrent()
@@ -111,9 +123,7 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() {
// Subsequent wake and unlocks should not emit duplicate, identical CircleReveals. // Subsequent wake and unlocks should not emit duplicate, identical CircleReveals.
val valuesPrevSize = values.size val valuesPrevSize = values.size
fakeKeyguardRepository.setBiometricUnlockState( fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING)
BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
)
fakeKeyguardRepository.setBiometricUnlockState( fakeKeyguardRepository.setBiometricUnlockState(
BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
) )
@@ -148,16 +158,33 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() {
it.centerY == fingerprintLocation.y it.centerY == fingerprintLocation.y
}, },
{ it == DEFAULT_REVEAL_EFFECT }, { it == DEFAULT_REVEAL_EFFECT },
{ { it is CircleReveal && it.centerX == faceLocation.x && it.centerY == faceLocation.y },
it is CircleReveal &&
it.centerX == faceLocation.x &&
it.centerY == faceLocation.y
},
) )
job.cancel() job.cancel()
} }
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
fun revealAmount_emitsTo1AfterAnimationStarted() =
runTest(UnconfinedTestDispatcher()) {
val value by collectLastValue(underTest.revealAmount)
underTest.startRevealAmountAnimator(true)
assertEquals(0.0f, value)
animatorTestRule.advanceTimeBy(500L)
assertEquals(1.0f, value)
}
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
fun revealAmount_emitsTo0AfterAnimationStartedReversed() =
runTest(UnconfinedTestDispatcher()) {
val value by collectLastValue(underTest.revealAmount)
underTest.startRevealAmountAnimator(false)
assertEquals(1.0f, value)
animatorTestRule.advanceTimeBy(500L)
assertEquals(0.0f, value)
}
/** /**
* Asserts that the list of LightRevealEffects satisfies the list of predicates, in order, with * Asserts that the list of LightRevealEffects satisfies the list of predicates, in order, with
* no leftover elements. * no leftover elements.

View File

@@ -27,27 +27,37 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.LightRevealScrim
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import org.mockito.Spy
@SmallTest @SmallTest
@RoboPilotTest @RoboPilotTest
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class LightRevealScrimInteractorTest : SysuiTestCase() { class LightRevealScrimInteractorTest : SysuiTestCase() {
private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository() private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository()
private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
@Spy private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
private val testScope = TestScope()
private val keyguardTransitionInteractor = private val keyguardTransitionInteractor =
KeyguardTransitionInteractorFactory.create( KeyguardTransitionInteractorFactory.create(
scope = TestScope().backgroundScope, scope = testScope.backgroundScope,
repository = fakeKeyguardTransitionRepository, repository = fakeKeyguardTransitionRepository,
) )
.keyguardTransitionInteractor .keyguardTransitionInteractor
@@ -69,9 +79,9 @@ class LightRevealScrimInteractorTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
underTest = underTest =
LightRevealScrimInteractor( LightRevealScrimInteractor(
fakeKeyguardTransitionRepository,
keyguardTransitionInteractor, keyguardTransitionInteractor,
fakeLightRevealScrimRepository fakeLightRevealScrimRepository,
testScope.backgroundScope
) )
} }
@@ -110,52 +120,36 @@ class LightRevealScrimInteractorTest : SysuiTestCase() {
} }
@Test @Test
fun revealAmount_invertedWhenAppropriate() = fun lightRevealEffect_startsAnimationOnlyForDifferentStateTargets() =
runTest(UnconfinedTestDispatcher()) { testScope.runTest {
val values = mutableListOf<Float>() fakeKeyguardTransitionRepository.sendTransitionStep(
val job = underTest.revealAmount.onEach(values::add).launchIn(this) TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.OFF,
to = KeyguardState.OFF
)
)
runCurrent()
verify(fakeLightRevealScrimRepository, never()).startRevealAmountAnimator(anyBoolean())
fakeKeyguardTransitionRepository.sendTransitionStep( fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep( TransitionStep(
from = KeyguardState.AOD, transitionState = TransitionState.STARTED,
to = KeyguardState.LOCKSCREEN, from = KeyguardState.DOZING,
value = 0.3f to = KeyguardState.LOCKSCREEN
) )
) )
runCurrent()
assertEquals(values, listOf(0.3f)) verify(fakeLightRevealScrimRepository).startRevealAmountAnimator(true)
fakeKeyguardTransitionRepository.sendTransitionStep( fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep( TransitionStep(
transitionState = TransitionState.STARTED,
from = KeyguardState.LOCKSCREEN, from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD, to = KeyguardState.DOZING
value = 0.3f
) )
) )
runCurrent()
assertEquals(values, listOf(0.3f, 0.7f)) verify(fakeLightRevealScrimRepository).startRevealAmountAnimator(false)
job.cancel()
}
@Test
fun revealAmount_ignoresTransitionsThatDoNotAffectRevealAmount() =
runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<Float>()
val job = underTest.revealAmount.onEach(values::add).launchIn(this)
fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(from = KeyguardState.DOZING, to = KeyguardState.AOD, value = 0.3f)
)
assertEquals(values, emptyList<Float>())
fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(from = KeyguardState.AOD, to = KeyguardState.DOZING, value = 0.3f)
)
assertEquals(values, emptyList<Float>())
job.cancel()
} }
} }

View File

@@ -29,6 +29,7 @@ import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dock.DockManager import com.android.systemui.dock.DockManager
import com.android.systemui.dump.DumpManager import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.DozeInteractor
import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.data.repository.FakePowerRepository
@@ -73,6 +74,8 @@ class PulsingGestureListenerTest : SysuiTestCase() {
@Mock @Mock
private lateinit var userTracker: UserTracker private lateinit var userTracker: UserTracker
@Mock @Mock
private lateinit var dozeInteractor: DozeInteractor
@Mock
private lateinit var screenOffAnimationController: ScreenOffAnimationController private lateinit var screenOffAnimationController: ScreenOffAnimationController
private lateinit var powerRepository: FakePowerRepository private lateinit var powerRepository: FakePowerRepository
@@ -98,6 +101,7 @@ class PulsingGestureListenerTest : SysuiTestCase() {
ambientDisplayConfiguration, ambientDisplayConfiguration,
statusBarStateController, statusBarStateController,
shadeLogger, shadeLogger,
dozeInteractor,
userTracker, userTracker,
tunerService, tunerService,
dumpManager dumpManager

View File

@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.repository package com.android.systemui.keyguard.data.repository
import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.LightRevealEffect
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
/** Fake implementation of [LightRevealScrimRepository] */ /** Fake implementation of [LightRevealScrimRepository] */
@@ -30,4 +31,15 @@ class FakeLightRevealScrimRepository : LightRevealScrimRepository {
fun setRevealEffect(effect: LightRevealEffect) { fun setRevealEffect(effect: LightRevealEffect) {
_revealEffect.tryEmit(effect) _revealEffect.tryEmit(effect)
} }
private val _revealAmount: MutableStateFlow<Float> = MutableStateFlow(0.0f)
override val revealAmount: Flow<Float> = _revealAmount
override fun startRevealAmountAnimator(reveal: Boolean) {
if (reveal) {
_revealAmount.value = 1.0f
} else {
_revealAmount.value = 0.0f
}
}
} }