Merge "Migrate LightRevealScrim and all dependencies to flows." into tm-qpr-dev am: dd1d999b88

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

Change-Id: Iee036942dc3b96ecad7dc82f1859eecca10c5108
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Josh Tsuji
2022-12-02 18:22:49 +00:00
committed by Automerger Merge Worker
28 changed files with 1273 additions and 131 deletions

View File

@@ -598,6 +598,10 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
getFingerprintSensorLocationInNaturalOrientation(), getFingerprintSensorLocationInNaturalOrientation(),
mCachedDisplayInfo); mCachedDisplayInfo);
} }
for (final Callback cb : mCallbacks) {
cb.onFingerprintLocationChanged();
}
} }
/** /**
@@ -619,6 +623,10 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
mCachedDisplayInfo mCachedDisplayInfo
); );
} }
for (final Callback cb : mCallbacks) {
cb.onFaceSensorLocationChanged();
}
} }
/** /**
@@ -1265,8 +1273,24 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
default void onBiometricPromptDismissed() {} default void onBiometricPromptDismissed() {}
/** /**
* The location in pixels can change due to resolution changes. * Called when the location of the fingerprint sensor changes. The location in pixels can
* change due to resolution changes.
*/
default void onFingerprintLocationChanged() {}
/**
* Called when the location of the under display fingerprint sensor changes. The location in
* pixels can change due to resolution changes.
*
* On devices with UDFPS, this is always called alongside
* {@link #onFingerprintLocationChanged}.
*/ */
default void onUdfpsLocationChanged() {} default void onUdfpsLocationChanged() {}
/**
* Called when the location of the face unlock sensor (typically the front facing camera)
* changes. The location in pixels can change due to resolution changes.
*/
default void onFaceSensorLocationChanged() {}
} }
} }

View File

@@ -29,6 +29,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils import com.android.settingslib.Utils
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.animation.Interpolators import com.android.systemui.animation.Interpolators
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.CircleReveal
@@ -71,6 +73,7 @@ class AuthRippleController @Inject constructor(
private val biometricUnlockController: BiometricUnlockController, private val biometricUnlockController: BiometricUnlockController,
private val udfpsControllerProvider: Provider<UdfpsController>, private val udfpsControllerProvider: Provider<UdfpsController>,
private val statusBarStateController: StatusBarStateController, private val statusBarStateController: StatusBarStateController,
private val featureFlags: FeatureFlags,
rippleView: AuthRippleView? rippleView: AuthRippleView?
) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback, ) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback,
WakefulnessLifecycle.Observer { WakefulnessLifecycle.Observer {
@@ -159,6 +162,10 @@ class AuthRippleController @Inject constructor(
private fun showUnlockedRipple() { private fun showUnlockedRipple() {
notificationShadeWindowController.setForcePluginOpen(true, this) notificationShadeWindowController.setForcePluginOpen(true, this)
// This code path is not used if the KeyguardTransitionRepository is managing the light
// reveal scrim.
if (!featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
val lightRevealScrim = centralSurfaces.lightRevealScrim val lightRevealScrim = centralSurfaces.lightRevealScrim
if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) { if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) {
circleReveal?.let { circleReveal?.let {
@@ -167,6 +174,7 @@ class AuthRippleController @Inject constructor(
startLightRevealScrimOnKeyguardFadingAway = true startLightRevealScrimOnKeyguardFadingAway = true
} }
} }
}
mView.startUnlockedRipple( mView.startUnlockedRipple(
/* end runnable */ /* end runnable */
@@ -177,6 +185,10 @@ class AuthRippleController @Inject constructor(
} }
override fun onKeyguardFadingAwayChanged() { override fun onKeyguardFadingAwayChanged() {
if (featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
return
}
if (keyguardStateController.isKeyguardFadingAway) { if (keyguardStateController.isKeyguardFadingAway) {
val lightRevealScrim = centralSurfaces.lightRevealScrim val lightRevealScrim = centralSurfaces.lightRevealScrim
if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) { if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) {

View File

@@ -164,6 +164,13 @@ object Flags {
// TODO(b/256513609): Tracking Bug // TODO(b/256513609): Tracking Bug
@JvmField val ACTIVE_UNLOCK_CHIPBAR = releasedFlag(217, "active_unlock_chipbar") @JvmField val ACTIVE_UNLOCK_CHIPBAR = releasedFlag(217, "active_unlock_chipbar")
/**
* Migrates control of the LightRevealScrim's reveal effect and amount from legacy code to the
* new KeyguardTransitionRepository.
*/
@JvmField
val LIGHT_REVEAL_MIGRATION = unreleasedFlag(218, "light_reveal_migration", teamfood = true)
// 300 - power menu // 300 - power menu
// TODO(b/254512600): Tracking Bug // TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")

View File

@@ -16,8 +16,11 @@
package com.android.systemui.keyguard.data.repository package com.android.systemui.keyguard.data.repository
import android.graphics.Point
import android.hardware.biometrics.BiometricSourceType
import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.biometrics.AuthController
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.Position import com.android.systemui.common.shared.model.Position
@@ -27,8 +30,8 @@ import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness
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.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -88,8 +91,8 @@ interface KeyguardRepository {
* enter to conserve battery when the device is locked and inactive. * enter to conserve battery when the device is locked and inactive.
* *
* Note that it is possible for the system to be transitioning into doze while this flow still * Note that it is possible for the system to be transitioning into doze while this flow still
* returns `false`. In order to account for that, observers should also use the [dozeAmount] * returns `false`. In order to account for that, observers should also use the
* flow to check if it's greater than `0` * [linearDozeAmount] flow to check if it's greater than `0`
*/ */
val isDozing: Flow<Boolean> val isDozing: Flow<Boolean>
@@ -111,7 +114,7 @@ interface KeyguardRepository {
* happens during an animation/transition into doze mode. An observer would be wise to account * happens during an animation/transition into doze mode. An observer would be wise to account
* for both flows if needed. * for both flows if needed.
*/ */
val dozeAmount: Flow<Float> val linearDozeAmount: Flow<Float>
/** Doze state information, as it transitions */ /** Doze state information, as it transitions */
val dozeTransitionModel: Flow<DozeTransitionModel> val dozeTransitionModel: Flow<DozeTransitionModel>
@@ -120,11 +123,20 @@ interface KeyguardRepository {
val statusBarState: Flow<StatusBarState> val statusBarState: Flow<StatusBarState>
/** Observable for device wake/sleep state */ /** Observable for device wake/sleep state */
val wakefulnessState: Flow<WakefulnessModel> val wakefulness: Flow<WakefulnessModel>
/** Observable for biometric unlock modes */ /** Observable for biometric unlock modes */
val biometricUnlockState: Flow<BiometricUnlockModel> val biometricUnlockState: Flow<BiometricUnlockModel>
/** Approximate location on the screen of the fingerprint sensor. */
val fingerprintSensorLocation: Flow<Point?>
/** Approximate location on the screen of the face unlock sensor/front facing camera. */
val faceSensorLocation: Flow<Point?>
/** Source of the most recent biometric unlock, such as fingerprint or face. */
val biometricUnlockSource: Flow<BiometricUnlockSource?>
/** /**
* Returns `true` if the keyguard is showing; `false` otherwise. * Returns `true` if the keyguard is showing; `false` otherwise.
* *
@@ -163,6 +175,7 @@ constructor(
private val keyguardStateController: KeyguardStateController, private val keyguardStateController: KeyguardStateController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val dozeTransitionListener: DozeTransitionListener, private val dozeTransitionListener: DozeTransitionListener,
private val authController: AuthController,
) : KeyguardRepository { ) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false) private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions = override val animateBottomAreaDozingTransitions =
@@ -281,11 +294,11 @@ constructor(
} }
.distinctUntilChanged() .distinctUntilChanged()
override val dozeAmount: Flow<Float> = conflatedCallbackFlow { override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {
val callback = val callback =
object : StatusBarStateController.StateListener { object : StatusBarStateController.StateListener {
override fun onDozeAmountChanged(linear: Float, eased: Float) { override fun onDozeAmountChanged(linear: Float, eased: Float) {
trySendWithFailureLogging(eased, TAG, "updated dozeAmount") trySendWithFailureLogging(linear, TAG, "updated dozeAmount")
} }
} }
@@ -348,58 +361,139 @@ constructor(
awaitClose { statusBarStateController.removeCallback(callback) } awaitClose { statusBarStateController.removeCallback(callback) }
} }
override val wakefulnessState: Flow<WakefulnessModel> = conflatedCallbackFlow {
val callback =
object : WakefulnessLifecycle.Observer {
override fun onStartedWakingUp() {
trySendWithFailureLogging(
WakefulnessModel.STARTING_TO_WAKE,
TAG,
"Wakefulness: starting to wake"
)
}
override fun onFinishedWakingUp() {
trySendWithFailureLogging(WakefulnessModel.AWAKE, TAG, "Wakefulness: awake")
}
override fun onStartedGoingToSleep() {
trySendWithFailureLogging(
WakefulnessModel.STARTING_TO_SLEEP,
TAG,
"Wakefulness: starting to sleep"
)
}
override fun onFinishedGoingToSleep() {
trySendWithFailureLogging(WakefulnessModel.ASLEEP, TAG, "Wakefulness: asleep")
}
}
wakefulnessLifecycle.addObserver(callback)
trySendWithFailureLogging(
wakefulnessIntToObject(wakefulnessLifecycle.getWakefulness()),
TAG,
"initial wakefulness state"
)
awaitClose { wakefulnessLifecycle.removeObserver(callback) }
}
override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow { override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
fun dispatchUpdate() {
trySendWithFailureLogging(
biometricModeIntToObject(biometricUnlockController.mode),
TAG,
"biometric mode"
)
}
val callback = val callback =
object : BiometricUnlockController.BiometricModeListener { object : BiometricUnlockController.BiometricModeListener {
override fun onModeChanged(@WakeAndUnlockMode mode: Int) { override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
trySendWithFailureLogging(biometricModeIntToObject(mode), TAG, "biometric mode") dispatchUpdate()
}
override fun onResetMode() {
dispatchUpdate()
} }
} }
biometricUnlockController.addBiometricModeListener(callback) biometricUnlockController.addBiometricModeListener(callback)
trySendWithFailureLogging( dispatchUpdate()
biometricModeIntToObject(biometricUnlockController.getMode()),
TAG,
"initial biometric mode"
)
awaitClose { biometricUnlockController.removeBiometricModeListener(callback) } awaitClose { biometricUnlockController.removeBiometricModeListener(callback) }
} }
override val wakefulness: Flow<WakefulnessModel> = conflatedCallbackFlow {
val observer =
object : WakefulnessLifecycle.Observer {
override fun onStartedWakingUp() {
dispatchNewState()
}
override fun onFinishedWakingUp() {
dispatchNewState()
}
override fun onPostFinishedWakingUp() {
dispatchNewState()
}
override fun onStartedGoingToSleep() {
dispatchNewState()
}
override fun onFinishedGoingToSleep() {
dispatchNewState()
}
private fun dispatchNewState() {
trySendWithFailureLogging(
WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
TAG,
"updated wakefulness state"
)
}
}
wakefulnessLifecycle.addObserver(observer)
trySendWithFailureLogging(
WakefulnessModel.fromWakefulnessLifecycle(wakefulnessLifecycle),
TAG,
"initial wakefulness state"
)
awaitClose { wakefulnessLifecycle.removeObserver(observer) }
}
override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow {
fun sendFpLocation() {
trySendWithFailureLogging(
authController.fingerprintSensorLocation,
TAG,
"AuthController.Callback#onFingerprintLocationChanged"
)
}
val callback =
object : AuthController.Callback {
override fun onFingerprintLocationChanged() {
sendFpLocation()
}
}
authController.addCallback(callback)
sendFpLocation()
awaitClose { authController.removeCallback(callback) }
}
override val faceSensorLocation: Flow<Point?> = conflatedCallbackFlow {
fun sendSensorLocation() {
trySendWithFailureLogging(
authController.faceSensorLocation,
TAG,
"AuthController.Callback#onFingerprintLocationChanged"
)
}
val callback =
object : AuthController.Callback {
override fun onFaceSensorLocationChanged() {
sendSensorLocation()
}
}
authController.addCallback(callback)
sendSensorLocation()
awaitClose { authController.removeCallback(callback) }
}
override val biometricUnlockSource: Flow<BiometricUnlockSource?> = conflatedCallbackFlow {
val callback =
object : KeyguardUpdateMonitorCallback() {
override fun onBiometricAuthenticated(
userId: Int,
biometricSourceType: BiometricSourceType?,
isStrongBiometric: Boolean
) {
trySendWithFailureLogging(
BiometricUnlockSource.fromBiometricSourceType(biometricSourceType),
TAG,
"onBiometricAuthenticated"
)
}
}
keyguardUpdateMonitor.registerCallback(callback)
trySendWithFailureLogging(null, TAG, "initial value")
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
override fun setAnimateDozingTransitions(animate: Boolean) { override fun setAnimateDozingTransitions(animate: Boolean) {
_animateBottomAreaDozingTransitions.value = animate _animateBottomAreaDozingTransitions.value = animate
} }
@@ -423,16 +517,6 @@ constructor(
} }
} }
private fun wakefulnessIntToObject(@Wakefulness value: Int): WakefulnessModel {
return when (value) {
0 -> WakefulnessModel.ASLEEP
1 -> WakefulnessModel.STARTING_TO_WAKE
2 -> WakefulnessModel.AWAKE
3 -> WakefulnessModel.STARTING_TO_SLEEP
else -> throw IllegalArgumentException("Invalid Wakefulness value: $value")
}
}
private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel { private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
return when (value) { return when (value) {
0 -> BiometricUnlockModel.NONE 0 -> BiometricUnlockModel.NONE

View File

@@ -27,4 +27,7 @@ interface KeyguardRepositoryModule {
fun keyguardTransitionRepository( fun keyguardTransitionRepository(
impl: KeyguardTransitionRepositoryImpl impl: KeyguardTransitionRepositoryImpl
): KeyguardTransitionRepository ): KeyguardTransitionRepository
@Binds
fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository
} }

View File

@@ -0,0 +1,149 @@
/*
* Copyright (C) 2022 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.keyguard.data.repository
import android.content.Context
import android.graphics.Point
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.WakeSleepReason
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LiftReveal
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.PowerButtonReveal
import javax.inject.Inject
import kotlin.math.max
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
val DEFAULT_REVEAL_EFFECT = LiftReveal
/**
* Encapsulates state relevant to the light reveal scrim, the view used to reveal/hide screen
* contents during transitions between AOD and lockscreen/unlocked.
*/
interface LightRevealScrimRepository {
/**
* The reveal effect that should be used for the next lock/unlock. We switch between either the
* biometric unlock effect (if wake and unlocking) or the non-biometric effect, and position it
* at the current screen position of the appropriate sensor.
*/
val revealEffect: Flow<LightRevealEffect>
}
@SysUISingleton
class LightRevealScrimRepositoryImpl
@Inject
constructor(
keyguardRepository: KeyguardRepository,
val context: Context,
) : LightRevealScrimRepository {
/** The reveal effect used if the device was locked/unlocked via the power button. */
private val powerButtonReveal =
PowerButtonReveal(
context.resources
.getDimensionPixelSize(R.dimen.physical_power_button_center_screen_location_y)
.toFloat()
)
/**
* Reveal effect to use for a fingerprint unlock. This is reconstructed if the fingerprint
* sensor location on the screen (in pixels) changes due to configuration changes.
*/
private val fingerprintRevealEffect: Flow<LightRevealEffect?> =
keyguardRepository.fingerprintSensorLocation.map {
it?.let { constructCircleRevealFromPoint(it) }
}
/**
* Reveal effect to use for a face unlock. This is reconstructed if the face sensor/front camera
* location on the screen (in pixels) changes due to configuration changes.
*/
private val faceRevealEffect: Flow<LightRevealEffect?> =
keyguardRepository.faceSensorLocation.map { it?.let { constructCircleRevealFromPoint(it) } }
/**
* The reveal effect we'll use for the next biometric unlock animation. We switch between the
* fingerprint/face unlock effect flows depending on the biometric unlock source.
*/
private val biometricRevealEffect: Flow<LightRevealEffect?> =
keyguardRepository.biometricUnlockSource.flatMapLatest { source ->
when (source) {
BiometricUnlockSource.FINGERPRINT_SENSOR -> fingerprintRevealEffect
BiometricUnlockSource.FACE_SENSOR -> faceRevealEffect
else -> flowOf(null)
}
}
/** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */
private val nonBiometricRevealEffect: Flow<LightRevealEffect?> =
keyguardRepository.wakefulness.map { wakefulnessModel ->
val wakingUpFromPowerButton =
wakefulnessModel.isWakingUpOrAwake &&
wakefulnessModel.lastWakeReason == WakeSleepReason.POWER_BUTTON
val sleepingFromPowerButton =
!wakefulnessModel.isWakingUpOrAwake &&
wakefulnessModel.lastSleepReason == WakeSleepReason.POWER_BUTTON
if (wakingUpFromPowerButton || sleepingFromPowerButton) {
powerButtonReveal
} else {
LiftReveal
}
}
override val revealEffect =
combine(
keyguardRepository.biometricUnlockState,
biometricRevealEffect,
nonBiometricRevealEffect
) { biometricUnlockState, biometricReveal, nonBiometricReveal ->
// Use the biometric reveal for any flavor of wake and unlocking.
when (biometricUnlockState) {
BiometricUnlockModel.WAKE_AND_UNLOCK,
BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM -> biometricReveal
else -> nonBiometricReveal
}
?: DEFAULT_REVEAL_EFFECT
}
.distinctUntilChanged()
private fun constructCircleRevealFromPoint(point: Point): LightRevealEffect {
return with(point) {
CircleReveal(
x,
y,
startRadius = 0,
endRadius =
max(max(x, context.display.width - x), max(y, context.display.height - y)),
)
}
}
}

View File

@@ -27,7 +27,6 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo
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.CoroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@SysUISingleton @SysUISingleton

View File

@@ -42,7 +42,7 @@ constructor(
override fun start() { override fun start() {
scope.launch { scope.launch {
keyguardInteractor.wakefulnessState keyguardInteractor.wakefulnessModel
.sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
.collect { pair -> .collect { pair ->
val (wakefulnessState, keyguardState) = pair val (wakefulnessState, keyguardState) = pair

View File

@@ -23,11 +23,10 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState
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.CoroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@SysUISingleton @SysUISingleton
@@ -42,13 +41,13 @@ constructor(
override fun start() { override fun start() {
scope.launch { scope.launch {
keyguardInteractor.wakefulnessState keyguardInteractor.wakefulnessModel
.sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) }) .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
.collect { pair -> .collect { pair ->
val (wakefulnessState, keyguardState) = pair val (wakefulnessState, keyguardState) = pair
if ( if (
keyguardState == KeyguardState.GONE && keyguardState == KeyguardState.GONE &&
wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
) { ) {
keyguardTransitionRepository.startTransition( keyguardTransitionRepository.startTransition(
TransitionInfo( TransitionInfo(

View File

@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor package com.android.systemui.keyguard.domain.interactor
import android.graphics.Point
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
@@ -41,7 +42,7 @@ constructor(
* The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
* all. * all.
*/ */
val dozeAmount: Flow<Float> = repository.dozeAmount val dozeAmount: Flow<Float> = repository.linearDozeAmount
/** Whether the system is in doze mode. */ /** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing val isDozing: Flow<Boolean> = repository.isDozing
/** Doze transition information. */ /** Doze transition information. */
@@ -58,7 +59,7 @@ constructor(
/** Whether the bouncer is showing or not. */ /** Whether the bouncer is showing or not. */
val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing
/** The device wake/sleep state */ /** The device wake/sleep state */
val wakefulnessState: Flow<WakefulnessModel> = repository.wakefulnessState val wakefulnessModel: Flow<WakefulnessModel> = repository.wakefulness
/** Observable for the [StatusBarState] */ /** Observable for the [StatusBarState] */
val statusBarState: Flow<StatusBarState> = repository.statusBarState val statusBarState: Flow<StatusBarState> = repository.statusBarState
/** /**
@@ -67,10 +68,15 @@ constructor(
*/ */
val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
/** The approximate location on the screen of the fingerprint sensor, if one is available. */
val fingerprintSensorLocation: Flow<Point?> = repository.fingerprintSensorLocation
/** The approximate location on the screen of the face unlock sensor, if one is available. */
val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
fun dozeTransitionTo(state: DozeStateModel): Flow<DozeTransitionModel> { fun dozeTransitionTo(state: DozeStateModel): Flow<DozeTransitionModel> {
return dozeTransitionModel.filter { it.to == state } return dozeTransitionModel.filter { it.to == state }
} }
fun isKeyguardShowing(): Boolean { fun isKeyguardShowing(): Boolean {
return repository.isKeyguardShowing() return repository.isKeyguardShowing()
} }

View File

@@ -37,7 +37,7 @@ constructor(
fun start() { fun start() {
scope.launch { scope.launch {
keyguardInteractor.wakefulnessState.collect { logger.v("WakefulnessState", it) } keyguardInteractor.wakefulnessModel.collect { logger.v("WakefulnessModel", it) }
} }
scope.launch { scope.launch {

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2022 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.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.LightRevealScrimRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
@ExperimentalCoroutinesApi
@SysUISingleton
class LightRevealScrimInteractor
@Inject
constructor(
transitionRepository: KeyguardTransitionRepository,
transitionInteractor: KeyguardTransitionInteractor,
lightRevealScrimRepository: LightRevealScrimRepository,
) {
/**
* Whenever a keyguard transition starts, sample the latest reveal effect from the repository
* and use that for the starting transition.
*
* We can't simply use the nextRevealEffect since the effect may change midway through a
* transition, but we don't want to change effects part way through. For example, if we're using
* a CircleReveal to animate a biometric unlock, but the biometric unlock mode changes to NONE
* from WAKE_AND_UNLOCK before the unlock animation ends, we don't want to end up switching to a
* LiftReveal.
*/
val lightRevealEffect: Flow<LightRevealEffect> =
transitionInteractor.startedKeyguardTransitionStep.sample(
lightRevealScrimRepository.revealEffect
)
/**
* 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 {
/**
* 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.
*/
fun willTransitionAffectRevealAmount(transition: TransitionStep): Boolean {
return willBeRevealedInState(transition.from) != willBeRevealedInState(transition.to)
}
/**
* Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
* state after the transition is complete. If false, scrim will be fully hidden.
*/
fun willBeRevealedInState(state: KeyguardState): Boolean {
return when (state) {
KeyguardState.OFF -> false
KeyguardState.DOZING -> false
KeyguardState.AOD -> false
KeyguardState.DREAMING -> true
KeyguardState.BOUNCER -> true
KeyguardState.LOCKSCREEN -> true
KeyguardState.GONE -> true
}
}
}
}

View File

@@ -25,7 +25,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.sample import com.android.systemui.util.kotlin.sample
import java.util.UUID import java.util.UUID
@@ -58,22 +58,26 @@ constructor(
keyguardInteractor.isBouncerShowing keyguardInteractor.isBouncerShowing
.sample( .sample(
combine( combine(
keyguardInteractor.wakefulnessState, keyguardInteractor.wakefulnessModel,
keyguardTransitionInteractor.startedKeyguardTransitionStep, keyguardTransitionInteractor.startedKeyguardTransitionStep,
) { a, b -> ) { wakefulnessModel, transitionStep ->
Pair(a, b) Pair(wakefulnessModel, transitionStep)
}, }
{ a, bc -> Triple(a, bc.first, bc.second) } ) { bouncerShowing, wakefulnessAndTransition ->
Triple(
bouncerShowing,
wakefulnessAndTransition.first,
wakefulnessAndTransition.second
) )
.collect { triple -> }
val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple .collect { (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) ->
if ( if (
!isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
) { ) {
val to = val to =
if ( if (
wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP || wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
wakefulnessState == WakefulnessModel.ASLEEP wakefulnessState.state == WakefulnessState.ASLEEP
) { ) {
KeyguardState.AOD KeyguardState.AOD
} else { } else {
@@ -100,14 +104,17 @@ constructor(
combine( combine(
keyguardTransitionInteractor.finishedKeyguardState, keyguardTransitionInteractor.finishedKeyguardState,
keyguardInteractor.statusBarState, keyguardInteractor.statusBarState,
) { a, b -> ) { finishedKeyguardState, statusBarState ->
Pair(a, b) Pair(finishedKeyguardState, statusBarState)
}, }
{ a, bc -> Triple(a, bc.first, bc.second) } ) { shadeModel, keyguardStateAndStatusBarState ->
Triple(
shadeModel,
keyguardStateAndStatusBarState.first,
keyguardStateAndStatusBarState.second
) )
.collect { triple -> }
val (shadeModel, keyguardState, statusBarState) = triple .collect { (shadeModel, keyguardState, statusBarState) ->
val id = transitionId val id = transitionId
if (id != null) { if (id != null) {
// An existing `id` means a transition is started, and calls to // An existing `id` means a transition is started, and calls to

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2022 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.shared.model
import android.hardware.biometrics.BiometricSourceType
/** Biometric unlock sensor sources, which we use to play sensor-specific animations. */
enum class BiometricUnlockSource {
/** The unlock was initiated by a fingerprint sensor authentication. */
FINGERPRINT_SENSOR,
/** The unlock was initiated by the front-facing camera or a nearby sensor. */
FACE_SENSOR;
companion object {
fun fromBiometricSourceType(type: BiometricSourceType?): BiometricUnlockSource? {
return when (type) {
BiometricSourceType.FINGERPRINT -> FINGERPRINT_SENSOR
BiometricSourceType.FACE -> FACE_SENSOR
BiometricSourceType.IRIS -> FACE_SENSOR
else -> null
}
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2022 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.shared.model
import android.os.PowerManager
/** The reason we're waking up or going to sleep, such as pressing the power button. */
enum class WakeSleepReason {
/** The physical power button was pressed to wake up or sleep the device. */
POWER_BUTTON,
/** Something else happened to wake up or sleep the device. */
OTHER;
companion object {
fun fromPowerManagerWakeReason(reason: Int): WakeSleepReason {
return when (reason) {
PowerManager.WAKE_REASON_POWER_BUTTON -> POWER_BUTTON
else -> OTHER
}
}
fun fromPowerManagerSleepReason(reason: Int): WakeSleepReason {
return when (reason) {
PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON -> POWER_BUTTON
else -> OTHER
}
}
}
}

View File

@@ -15,24 +15,34 @@
*/ */
package com.android.systemui.keyguard.shared.model package com.android.systemui.keyguard.shared.model
/** Model device wakefulness states. */ import com.android.systemui.keyguard.WakefulnessLifecycle
enum class WakefulnessModel {
/** The device is asleep and not interactive. */
ASLEEP,
/** Received a signal that the device is beginning to wake up. */
STARTING_TO_WAKE,
/** Device is now fully awake and interactive. */
AWAKE,
/** Signal that the device is now going to sleep. */
STARTING_TO_SLEEP;
/** Model device wakefulness states. */
data class WakefulnessModel(
val state: WakefulnessState,
val isWakingUpOrAwake: Boolean,
val lastWakeReason: WakeSleepReason,
val lastSleepReason: WakeSleepReason,
) {
companion object { companion object {
fun isSleepingOrStartingToSleep(model: WakefulnessModel): Boolean { fun isSleepingOrStartingToSleep(model: WakefulnessModel): Boolean {
return model == ASLEEP || model == STARTING_TO_SLEEP return model.state == WakefulnessState.ASLEEP ||
model.state == WakefulnessState.STARTING_TO_SLEEP
} }
fun isWakingOrStartingToWake(model: WakefulnessModel): Boolean { fun isWakingOrStartingToWake(model: WakefulnessModel): Boolean {
return model == AWAKE || model == STARTING_TO_WAKE return model.state == WakefulnessState.AWAKE ||
model.state == WakefulnessState.STARTING_TO_WAKE
}
fun fromWakefulnessLifecycle(wakefulnessLifecycle: WakefulnessLifecycle): WakefulnessModel {
return WakefulnessModel(
WakefulnessState.fromWakefulnessLifecycleInt(wakefulnessLifecycle.wakefulness),
wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_WAKING ||
wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_AWAKE,
WakeSleepReason.fromPowerManagerWakeReason(wakefulnessLifecycle.lastWakeReason),
WakeSleepReason.fromPowerManagerSleepReason(wakefulnessLifecycle.lastSleepReason),
)
} }
} }
} }

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2022 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.shared.model
import com.android.systemui.keyguard.WakefulnessLifecycle
enum class WakefulnessState {
/** The device is asleep and not interactive. */
ASLEEP,
/** Received a signal that the device is beginning to wake up. */
STARTING_TO_WAKE,
/** Device is now fully awake and interactive. */
AWAKE,
/** Signal that the device is now going to sleep. */
STARTING_TO_SLEEP;
companion object {
fun fromWakefulnessLifecycleInt(
@WakefulnessLifecycle.Wakefulness value: Int
): WakefulnessState {
return when (value) {
WakefulnessLifecycle.WAKEFULNESS_ASLEEP -> ASLEEP
WakefulnessLifecycle.WAKEFULNESS_WAKING -> STARTING_TO_WAKE
WakefulnessLifecycle.WAKEFULNESS_AWAKE -> AWAKE
WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP -> STARTING_TO_SLEEP
else -> throw IllegalArgumentException("Invalid Wakefulness value: $value")
}
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2022 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.ui.binder
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.LightRevealScrim
import kotlinx.coroutines.launch
object LightRevealScrimViewBinder {
@JvmStatic
fun bind(revealScrim: LightRevealScrim, viewModel: LightRevealScrimViewModel) {
revealScrim.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.revealAmount.collect { amount -> revealScrim.revealAmount = amount }
}
launch {
viewModel.lightRevealEffect.collect { effect ->
revealScrim.revealEffect = effect
}
}
}
}
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2022 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.ui.viewmodel
import com.android.systemui.keyguard.domain.interactor.LightRevealScrimInteractor
import com.android.systemui.statusbar.LightRevealEffect
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
/**
* 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.
*/
class LightRevealScrimViewModel @Inject constructor(interactor: LightRevealScrimInteractor) {
val lightRevealEffect: Flow<LightRevealEffect> = interactor.lightRevealEffect
val revealAmount: Flow<Float> = interactor.revealAmount
}

View File

@@ -514,7 +514,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
&& mPendingAuthenticated.userId == KeyguardUpdateMonitor.getCurrentUser(); && mPendingAuthenticated.userId == KeyguardUpdateMonitor.getCurrentUser();
} }
public int getMode() { public @WakeAndUnlockMode int getMode() {
return mMode; return mMode;
} }

View File

@@ -160,6 +160,8 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.ui.binder.LightRevealScrimViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher;
@@ -491,6 +493,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private final OngoingCallController mOngoingCallController; private final OngoingCallController mOngoingCallController;
private final StatusBarSignalPolicy mStatusBarSignalPolicy; private final StatusBarSignalPolicy mStatusBarSignalPolicy;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager; private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
private final Lazy<LightRevealScrimViewModel> mLightRevealScrimViewModelLazy;
/** Controller for the Shade. */ /** Controller for the Shade. */
@VisibleForTesting @VisibleForTesting
@@ -757,7 +760,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
DeviceStateManager deviceStateManager, DeviceStateManager deviceStateManager,
WiredChargingRippleController wiredChargingRippleController, WiredChargingRippleController wiredChargingRippleController,
IDreamManager dreamManager, IDreamManager dreamManager,
Lazy<CameraLauncher> cameraLauncherLazy) { Lazy<CameraLauncher> cameraLauncherLazy,
Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy) {
mContext = context; mContext = context;
mNotificationsController = notificationsController; mNotificationsController = notificationsController;
mFragmentService = fragmentService; mFragmentService = fragmentService;
@@ -871,6 +875,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
deviceStateManager.registerCallback(mMainExecutor, deviceStateManager.registerCallback(mMainExecutor,
new FoldStateListener(mContext, this::onFoldedStateChanged)); new FoldStateListener(mContext, this::onFoldedStateChanged));
wiredChargingRippleController.registerCallbacks(); wiredChargingRippleController.registerCallbacks();
mLightRevealScrimViewModelLazy = lightRevealScrimViewModelLazy;
} }
@Override @Override
@@ -1000,6 +1006,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override @Override
public void onKeyguardGoingAwayChanged() { public void onKeyguardGoingAwayChanged() {
if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
// This code path is not used if the KeyguardTransitionRepository is managing
// the lightreveal scrim.
return;
}
// The light reveal scrim should always be fully revealed by the time the keyguard // The light reveal scrim should always be fully revealed by the time the keyguard
// is done going away. Double check that this is true. // is done going away. Double check that this is true.
if (!mKeyguardStateController.isKeyguardGoingAway()) { if (!mKeyguardStateController.isKeyguardGoingAway()) {
@@ -1236,6 +1248,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront); mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront);
mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim); mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
LightRevealScrimViewBinder.bind(
mLightRevealScrim, mLightRevealScrimViewModelLazy.get());
}
mLightRevealScrim.setScrimOpaqueChangedListener((opaque) -> { mLightRevealScrim.setScrimOpaqueChangedListener((opaque) -> {
Runnable updateOpaqueness = () -> { Runnable updateOpaqueness = () -> {
mNotificationShadeWindowController.setLightRevealScrimOpaque( mNotificationShadeWindowController.setLightRevealScrimOpaque(
@@ -3309,6 +3327,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
return; return;
} }
if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
return;
}
final boolean wakingUpFromPowerButton = wakingUp final boolean wakingUpFromPowerButton = wakingUp
&& !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal) && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)
&& mWakefulnessLifecycle.getLastWakeReason() && mWakefulnessLifecycle.getLastWakeReason()
@@ -4073,8 +4095,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
return; return;
} }
if (!mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha()); mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha());
} }
}
@Override @Override
public void extendDozePulse(){ public void extendDozePulse(){
@@ -4254,6 +4278,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override @Override
public void onDozeAmountChanged(float linear, float eased) { public void onDozeAmountChanged(float linear, float eased) {
if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS) if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
&& !mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)
&& !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
mLightRevealScrim.setRevealAmount(1f - linear); mLightRevealScrim.setRevealAmount(1f - linear);
} }

View File

@@ -26,6 +26,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.LightRevealScrim
@@ -77,6 +78,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
@Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController> @Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController>
@Mock private lateinit var udfpsController: UdfpsController @Mock private lateinit var udfpsController: UdfpsController
@Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var lightRevealScrim: LightRevealScrim @Mock private lateinit var lightRevealScrim: LightRevealScrim
@Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal @Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
@@ -106,6 +108,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
biometricUnlockController, biometricUnlockController,
udfpsControllerProvider, udfpsControllerProvider,
statusBarStateController, statusBarStateController,
featureFlags,
rippleView rippleView
) )
controller.init() controller.init()

View File

@@ -16,10 +16,13 @@
package com.android.systemui.keyguard.data.repository package com.android.systemui.keyguard.data.repository
import android.graphics.Point
import android.hardware.biometrics.BiometricSourceType
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.common.shared.model.Position import com.android.systemui.common.shared.model.Position
import com.android.systemui.doze.DozeHost import com.android.systemui.doze.DozeHost
import com.android.systemui.doze.DozeMachine import com.android.systemui.doze.DozeMachine
@@ -27,9 +30,11 @@ import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.WakefulnessLifecycle
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.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -57,9 +62,10 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var dozeHost: DozeHost @Mock private lateinit var dozeHost: DozeHost
@Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var biometricUnlockController: BiometricUnlockController @Mock private lateinit var biometricUnlockController: BiometricUnlockController
@Mock private lateinit var dozeTransitionListener: DozeTransitionListener @Mock private lateinit var dozeTransitionListener: DozeTransitionListener
@Mock private lateinit var authController: AuthController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private lateinit var underTest: KeyguardRepositoryImpl private lateinit var underTest: KeyguardRepositoryImpl
@@ -76,6 +82,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
keyguardStateController, keyguardStateController,
keyguardUpdateMonitor, keyguardUpdateMonitor,
dozeTransitionListener, dozeTransitionListener,
authController,
) )
} }
@@ -198,7 +205,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
fun dozeAmount() = fun dozeAmount() =
runTest(UnconfinedTestDispatcher()) { runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<Float>() val values = mutableListOf<Float>()
val job = underTest.dozeAmount.onEach(values::add).launchIn(this) val job = underTest.linearDozeAmount.onEach(values::add).launchIn(this)
val captor = argumentCaptor<StatusBarStateController.StateListener>() val captor = argumentCaptor<StatusBarStateController.StateListener>()
verify(statusBarStateController).addCallback(captor.capture()) verify(statusBarStateController).addCallback(captor.capture())
@@ -207,7 +214,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
captor.value.onDozeAmountChanged(0.498f, 0.5f) captor.value.onDozeAmountChanged(0.498f, 0.5f)
captor.value.onDozeAmountChanged(0.661f, 0.65f) captor.value.onDozeAmountChanged(0.661f, 0.65f)
assertThat(values).isEqualTo(listOf(0f, 0.4f, 0.5f, 0.65f)) assertThat(values).isEqualTo(listOf(0f, 0.433f, 0.498f, 0.661f))
job.cancel() job.cancel()
verify(statusBarStateController).removeCallback(captor.value) verify(statusBarStateController).removeCallback(captor.value)
@@ -217,25 +224,36 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
fun wakefulness() = fun wakefulness() =
runTest(UnconfinedTestDispatcher()) { runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<WakefulnessModel>() val values = mutableListOf<WakefulnessModel>()
val job = underTest.wakefulnessState.onEach(values::add).launchIn(this) val job = underTest.wakefulness.onEach(values::add).launchIn(this)
val captor = argumentCaptor<WakefulnessLifecycle.Observer>() val captor = argumentCaptor<WakefulnessLifecycle.Observer>()
verify(wakefulnessLifecycle).addObserver(captor.capture()) verify(wakefulnessLifecycle).addObserver(captor.capture())
whenever(wakefulnessLifecycle.wakefulness)
.thenReturn(WakefulnessLifecycle.WAKEFULNESS_WAKING)
captor.value.onStartedWakingUp() captor.value.onStartedWakingUp()
whenever(wakefulnessLifecycle.wakefulness)
.thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE)
captor.value.onFinishedWakingUp() captor.value.onFinishedWakingUp()
whenever(wakefulnessLifecycle.wakefulness)
.thenReturn(WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP)
captor.value.onStartedGoingToSleep() captor.value.onStartedGoingToSleep()
whenever(wakefulnessLifecycle.wakefulness)
.thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP)
captor.value.onFinishedGoingToSleep() captor.value.onFinishedGoingToSleep()
assertThat(values) assertThat(values.map { it.state })
.isEqualTo( .isEqualTo(
listOf( listOf(
// Initial value will be ASLEEP // Initial value will be ASLEEP
WakefulnessModel.ASLEEP, WakefulnessState.ASLEEP,
WakefulnessModel.STARTING_TO_WAKE, WakefulnessState.STARTING_TO_WAKE,
WakefulnessModel.AWAKE, WakefulnessState.AWAKE,
WakefulnessModel.STARTING_TO_SLEEP, WakefulnessState.STARTING_TO_SLEEP,
WakefulnessModel.ASLEEP, WakefulnessState.ASLEEP,
) )
) )
@@ -329,14 +347,20 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>() val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
verify(biometricUnlockController).addBiometricModeListener(captor.capture()) verify(biometricUnlockController).addBiometricModeListener(captor.capture())
captor.value.onModeChanged(BiometricUnlockController.MODE_NONE) listOf(
captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK) BiometricUnlockController.MODE_NONE,
captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING) BiometricUnlockController.MODE_WAKE_AND_UNLOCK,
captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER) BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING,
captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE) BiometricUnlockController.MODE_SHOW_BOUNCER,
captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING) BiometricUnlockController.MODE_ONLY_WAKE,
captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER) BiometricUnlockController.MODE_UNLOCK_COLLAPSING,
captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM) BiometricUnlockController.MODE_DISMISS_BOUNCER,
BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM,
)
.forEach {
whenever(biometricUnlockController.mode).thenReturn(it)
captor.value.onModeChanged(it)
}
assertThat(values) assertThat(values)
.isEqualTo( .isEqualTo(
@@ -420,4 +444,104 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
job.cancel() job.cancel()
verify(dozeTransitionListener).removeCallback(listener) verify(dozeTransitionListener).removeCallback(listener)
} }
@Test
fun fingerprintSensorLocation() =
runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<Point?>()
val job = underTest.fingerprintSensorLocation.onEach(values::add).launchIn(this)
val captor = argumentCaptor<AuthController.Callback>()
verify(authController).addCallback(captor.capture())
// An initial, null value should be initially emitted so that flows combined with this
// one
// emit values immediately. The sensor location is expected to be nullable, so anyone
// consuming it should handle that properly.
assertThat(values).isEqualTo(listOf(null))
listOf(Point(500, 500), Point(0, 0), null, Point(250, 250))
.onEach {
whenever(authController.fingerprintSensorLocation).thenReturn(it)
captor.value.onFingerprintLocationChanged()
}
.also { dispatchedSensorLocations ->
assertThat(values).isEqualTo(listOf(null) + dispatchedSensorLocations)
}
job.cancel()
}
@Test
fun faceSensorLocation() =
runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<Point?>()
val job = underTest.faceSensorLocation.onEach(values::add).launchIn(this)
val captor = argumentCaptor<AuthController.Callback>()
verify(authController).addCallback(captor.capture())
// An initial, null value should be initially emitted so that flows combined with this
// one
// emit values immediately. The sensor location is expected to be nullable, so anyone
// consuming it should handle that properly.
assertThat(values).isEqualTo(listOf(null))
listOf(
Point(500, 500),
Point(0, 0),
null,
Point(250, 250),
)
.onEach {
whenever(authController.faceSensorLocation).thenReturn(it)
captor.value.onFaceSensorLocationChanged()
}
.also { dispatchedSensorLocations ->
assertThat(values).isEqualTo(listOf(null) + dispatchedSensorLocations)
}
job.cancel()
}
@Test
fun biometricUnlockSource() =
runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<BiometricUnlockSource?>()
val job = underTest.biometricUnlockSource.onEach(values::add).launchIn(this)
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(captor.capture())
// An initial, null value should be initially emitted so that flows combined with this
// one
// emit values immediately. The biometric unlock source is expected to be nullable, so
// anyone consuming it should handle that properly.
assertThat(values).isEqualTo(listOf(null))
listOf(
BiometricSourceType.FINGERPRINT,
BiometricSourceType.IRIS,
null,
BiometricSourceType.FACE,
BiometricSourceType.FINGERPRINT,
)
.onEach { biometricSourceType ->
captor.value.onBiometricAuthenticated(0, biometricSourceType, false)
}
assertThat(values)
.isEqualTo(
listOf(
null,
BiometricUnlockSource.FINGERPRINT_SENSOR,
BiometricUnlockSource.FACE_SENSOR,
null,
BiometricUnlockSource.FACE_SENSOR,
BiometricUnlockSource.FINGERPRINT_SENSOR,
)
)
job.cancel()
}
} }

View File

@@ -0,0 +1,167 @@
/*
* Copyright (C) 2022 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 android.graphics.Point
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LightRevealEffect
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(JUnit4::class)
class LightRevealScrimRepositoryTest : SysuiTestCase() {
private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
private lateinit var underTest: LightRevealScrimRepositoryImpl
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
fakeKeyguardRepository = FakeKeyguardRepository()
underTest = LightRevealScrimRepositoryImpl(fakeKeyguardRepository, context)
}
@Test
fun `nextRevealEffect - effect switches between default and biometric with no dupes`() =
runTest {
val values = mutableListOf<LightRevealEffect>()
val job = launch { underTest.revealEffect.collect { values.add(it) } }
// We should initially emit the default reveal effect.
runCurrent()
values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT })
// The source and sensor locations are still null, so we should still be using the
// default reveal despite a biometric unlock.
fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
runCurrent()
values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT },)
// We got a source but still have no sensor locations, so should be sticking with
// the default effect.
fakeKeyguardRepository.setBiometricUnlockSource(
BiometricUnlockSource.FINGERPRINT_SENSOR
)
runCurrent()
values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT },)
// We got a location for the face sensor, but we unlocked with fingerprint.
val faceLocation = Point(250, 0)
fakeKeyguardRepository.setFaceSensorLocation(faceLocation)
runCurrent()
values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT },)
// Now we have fingerprint sensor locations, and wake and unlock via fingerprint.
val fingerprintLocation = Point(500, 500)
fakeKeyguardRepository.setFingerprintSensorLocation(fingerprintLocation)
fakeKeyguardRepository.setBiometricUnlockSource(
BiometricUnlockSource.FINGERPRINT_SENSOR
)
fakeKeyguardRepository.setBiometricUnlockState(
BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
)
// We should now have switched to the circle reveal, at the fingerprint location.
runCurrent()
values.assertEffectsMatchPredicates(
{ it == DEFAULT_REVEAL_EFFECT },
{
it is CircleReveal &&
it.centerX == fingerprintLocation.x &&
it.centerY == fingerprintLocation.y
},
)
// Subsequent wake and unlocks should not emit duplicate, identical CircleReveals.
val valuesPrevSize = values.size
fakeKeyguardRepository.setBiometricUnlockState(
BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
)
fakeKeyguardRepository.setBiometricUnlockState(
BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
)
assertEquals(valuesPrevSize, values.size)
// Non-biometric unlock, we should return to the default reveal.
fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
runCurrent()
values.assertEffectsMatchPredicates(
{ it == DEFAULT_REVEAL_EFFECT },
{
it is CircleReveal &&
it.centerX == fingerprintLocation.x &&
it.centerY == fingerprintLocation.y
},
{ it == DEFAULT_REVEAL_EFFECT },
)
// We already have a face location, so switching to face source should update the
// CircleReveal.
fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR)
runCurrent()
fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
runCurrent()
values.assertEffectsMatchPredicates(
{ it == DEFAULT_REVEAL_EFFECT },
{
it is CircleReveal &&
it.centerX == fingerprintLocation.x &&
it.centerY == fingerprintLocation.y
},
{ it == DEFAULT_REVEAL_EFFECT },
{
it is CircleReveal &&
it.centerX == faceLocation.x &&
it.centerY == faceLocation.y
},
)
job.cancel()
}
/**
* Asserts that the list of LightRevealEffects satisfies the list of predicates, in order, with
* no leftover elements.
*/
private fun List<LightRevealEffect>.assertEffectsMatchPredicates(
vararg predicates: (LightRevealEffect) -> Boolean
) {
println(this)
assertEquals(predicates.size, this.size)
assertFalse(
zip(predicates) { effect, predicate -> predicate(effect) }.any { matched -> !matched }
)
}
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright (C) 2022 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.domain.interactor
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.LightRevealScrim
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(JUnit4::class)
class LightRevealScrimInteractorTest : SysuiTestCase() {
private val fakeKeyguardTransitionRepository = FakeKeyguardTransitionRepository()
private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository()
private val keyguardTransitionInteractor =
KeyguardTransitionInteractor(fakeKeyguardTransitionRepository)
private lateinit var underTest: LightRevealScrimInteractor
private val reveal1 =
object : LightRevealEffect {
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {}
}
private val reveal2 =
object : LightRevealEffect {
override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {}
}
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
underTest =
LightRevealScrimInteractor(
fakeKeyguardTransitionRepository,
keyguardTransitionInteractor,
fakeLightRevealScrimRepository
)
}
@Test
fun `lightRevealEffect - does not change during keyguard transition`() =
runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<LightRevealEffect>()
val job = underTest.lightRevealEffect.onEach(values::add).launchIn(this)
fakeLightRevealScrimRepository.setRevealEffect(reveal1)
// The reveal effect shouldn't emit anything until a keyguard transition starts.
assertEquals(values.size, 0)
// Once it starts, it should emit reveal1.
fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(transitionState = TransitionState.STARTED)
)
assertEquals(values, listOf(reveal1))
// Until the next transition starts, reveal2 should not be emitted.
fakeLightRevealScrimRepository.setRevealEffect(reveal2)
fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(transitionState = TransitionState.RUNNING)
)
fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(transitionState = TransitionState.FINISHED)
)
assertEquals(values, listOf(reveal1))
fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(transitionState = TransitionState.STARTED)
)
assertEquals(values, listOf(reveal1, reveal2))
job.cancel()
}
@Test
fun `revealAmount - inverted when appropriate`() =
runTest(UnconfinedTestDispatcher()) {
val values = mutableListOf<Float>()
val job = underTest.revealAmount.onEach(values::add).launchIn(this)
fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.AOD,
to = KeyguardState.LOCKSCREEN,
value = 0.3f
)
)
assertEquals(values, listOf(0.3f))
fakeKeyguardTransitionRepository.sendTransitionStep(
TransitionStep(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
value = 0.3f
)
)
assertEquals(values, listOf(0.3f, 0.7f))
job.cancel()
}
@Test
fun `revealAmount - ignores transitions that do not affect reveal amount`() =
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

@@ -106,6 +106,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.PluginDependencyProvider;
@@ -212,6 +213,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private NotificationPanelView mNotificationPanelView; @Mock private NotificationPanelView mNotificationPanelView;
@Mock private IStatusBarService mBarService; @Mock private IStatusBarService mBarService;
@Mock private IDreamManager mDreamManager; @Mock private IDreamManager mDreamManager;
@Mock private LightRevealScrimViewModel mLightRevealScrimViewModel;
@Mock private ScrimController mScrimController; @Mock private ScrimController mScrimController;
@Mock private DozeScrimController mDozeScrimController; @Mock private DozeScrimController mDozeScrimController;
@Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
@@ -497,7 +499,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mDeviceStateManager, mDeviceStateManager,
mWiredChargingRippleController, mWiredChargingRippleController,
mDreamManager, mDreamManager,
mCameraLauncherLazy) { mCameraLauncherLazy,
() -> mLightRevealScrimViewModel) {
@Override @Override
protected ViewRootImpl getViewRootImpl() { protected ViewRootImpl getViewRootImpl() {
return mViewRootImpl; return mViewRootImpl;

View File

@@ -17,11 +17,15 @@
package com.android.systemui.keyguard.data.repository package com.android.systemui.keyguard.data.repository
import android.graphics.Point
import com.android.systemui.common.shared.model.Position import com.android.systemui.common.shared.model.Position
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.DozeTransitionModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.StatusBarState 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.WakefulnessModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -49,7 +53,7 @@ class FakeKeyguardRepository : KeyguardRepository {
override val isDreaming: Flow<Boolean> = _isDreaming override val isDreaming: Flow<Boolean> = _isDreaming
private val _dozeAmount = MutableStateFlow(0f) private val _dozeAmount = MutableStateFlow(0f)
override val dozeAmount: Flow<Float> = _dozeAmount override val linearDozeAmount: Flow<Float> = _dozeAmount
private val _statusBarState = MutableStateFlow(StatusBarState.SHADE) private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
override val statusBarState: Flow<StatusBarState> = _statusBarState override val statusBarState: Flow<StatusBarState> = _statusBarState
@@ -57,8 +61,16 @@ class FakeKeyguardRepository : KeyguardRepository {
private val _dozeTransitionModel = MutableStateFlow(DozeTransitionModel()) private val _dozeTransitionModel = MutableStateFlow(DozeTransitionModel())
override val dozeTransitionModel: Flow<DozeTransitionModel> = _dozeTransitionModel override val dozeTransitionModel: Flow<DozeTransitionModel> = _dozeTransitionModel
private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP) private val _wakefulnessModel =
override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState MutableStateFlow(
WakefulnessModel(
WakefulnessState.ASLEEP,
false,
WakeSleepReason.OTHER,
WakeSleepReason.OTHER
)
)
override val wakefulness: Flow<WakefulnessModel> = _wakefulnessModel
private val _isUdfpsSupported = MutableStateFlow(false) private val _isUdfpsSupported = MutableStateFlow(false)
@@ -71,6 +83,15 @@ class FakeKeyguardRepository : KeyguardRepository {
private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE) private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState
private val _fingerprintSensorLocation = MutableStateFlow<Point?>(null)
override val fingerprintSensorLocation: Flow<Point?> = _fingerprintSensorLocation
private val _faceSensorLocation = MutableStateFlow<Point?>(null)
override val faceSensorLocation: Flow<Point?> = _faceSensorLocation
private val _biometricUnlockSource = MutableStateFlow<BiometricUnlockSource?>(null)
override val biometricUnlockSource: Flow<BiometricUnlockSource?> = _biometricUnlockSource
override fun isKeyguardShowing(): Boolean { override fun isKeyguardShowing(): Boolean {
return _isKeyguardShowing.value return _isKeyguardShowing.value
} }
@@ -99,6 +120,22 @@ class FakeKeyguardRepository : KeyguardRepository {
_dozeAmount.value = dozeAmount _dozeAmount.value = dozeAmount
} }
fun setBiometricUnlockState(state: BiometricUnlockModel) {
_biometricUnlockState.tryEmit(state)
}
fun setBiometricUnlockSource(source: BiometricUnlockSource?) {
_biometricUnlockSource.tryEmit(source)
}
fun setFaceSensorLocation(location: Point?) {
_faceSensorLocation.tryEmit(location)
}
fun setFingerprintSensorLocation(location: Point?) {
_fingerprintSensorLocation.tryEmit(location)
}
override fun isUdfpsSupported(): Boolean { override fun isUdfpsSupported(): Boolean {
return _isUdfpsSupported.value return _isUdfpsSupported.value
} }

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2022 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.systemui.statusbar.LightRevealEffect
import kotlinx.coroutines.flow.MutableStateFlow
/** Fake implementation of [LightRevealScrimRepository] */
class FakeLightRevealScrimRepository : LightRevealScrimRepository {
private val _revealEffect: MutableStateFlow<LightRevealEffect> =
MutableStateFlow(DEFAULT_REVEAL_EFFECT)
override val revealEffect = _revealEffect
fun setRevealEffect(effect: LightRevealEffect) {
_revealEffect.tryEmit(effect)
}
}