Merge "Migrate LightRevealScrim and all dependencies to flows." into tm-qpr-dev
This commit is contained in:
@@ -598,6 +598,10 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
|
||||
getFingerprintSensorLocationInNaturalOrientation(),
|
||||
mCachedDisplayInfo);
|
||||
}
|
||||
|
||||
for (final Callback cb : mCallbacks) {
|
||||
cb.onFingerprintLocationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -619,6 +623,10 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
|
||||
mCachedDisplayInfo
|
||||
);
|
||||
}
|
||||
|
||||
for (final Callback cb : mCallbacks) {
|
||||
cb.onFaceSensorLocationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1265,8 +1273,24 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
|
||||
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() {}
|
||||
|
||||
/**
|
||||
* 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() {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback
|
||||
import com.android.settingslib.Utils
|
||||
import com.android.systemui.R
|
||||
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.plugins.statusbar.StatusBarStateController
|
||||
import com.android.systemui.statusbar.CircleReveal
|
||||
@@ -71,7 +73,8 @@ class AuthRippleController @Inject constructor(
|
||||
private val biometricUnlockController: BiometricUnlockController,
|
||||
private val udfpsControllerProvider: Provider<UdfpsController>,
|
||||
private val statusBarStateController: StatusBarStateController,
|
||||
rippleView: AuthRippleView?
|
||||
private val featureFlags: FeatureFlags,
|
||||
rippleView: AuthRippleView?
|
||||
) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback,
|
||||
WakefulnessLifecycle.Observer {
|
||||
|
||||
@@ -159,12 +162,17 @@ class AuthRippleController @Inject constructor(
|
||||
|
||||
private fun showUnlockedRipple() {
|
||||
notificationShadeWindowController.setForcePluginOpen(true, this)
|
||||
val lightRevealScrim = centralSurfaces.lightRevealScrim
|
||||
if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) {
|
||||
circleReveal?.let {
|
||||
lightRevealScrim?.revealAmount = 0f
|
||||
lightRevealScrim?.revealEffect = it
|
||||
startLightRevealScrimOnKeyguardFadingAway = true
|
||||
|
||||
// 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
|
||||
if (statusBarStateController.isDozing || biometricUnlockController.isWakeAndUnlock) {
|
||||
circleReveal?.let {
|
||||
lightRevealScrim?.revealAmount = 0f
|
||||
lightRevealScrim?.revealEffect = it
|
||||
startLightRevealScrimOnKeyguardFadingAway = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +185,10 @@ class AuthRippleController @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onKeyguardFadingAwayChanged() {
|
||||
if (featureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (keyguardStateController.isKeyguardFadingAway) {
|
||||
val lightRevealScrim = centralSurfaces.lightRevealScrim
|
||||
if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) {
|
||||
|
||||
@@ -164,6 +164,13 @@ object Flags {
|
||||
// TODO(b/256513609): Tracking Bug
|
||||
@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
|
||||
// TODO(b/254512600): Tracking Bug
|
||||
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
|
||||
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.KeyguardUpdateMonitorCallback
|
||||
import com.android.systemui.biometrics.AuthController
|
||||
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||
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.DozeTransitionListener
|
||||
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.BiometricUnlockSource
|
||||
import com.android.systemui.keyguard.shared.model.DozeStateModel
|
||||
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
|
||||
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.
|
||||
*
|
||||
* 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]
|
||||
* flow to check if it's greater than `0`
|
||||
* returns `false`. In order to account for that, observers should also use the
|
||||
* [linearDozeAmount] flow to check if it's greater than `0`
|
||||
*/
|
||||
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
|
||||
* for both flows if needed.
|
||||
*/
|
||||
val dozeAmount: Flow<Float>
|
||||
val linearDozeAmount: Flow<Float>
|
||||
|
||||
/** Doze state information, as it transitions */
|
||||
val dozeTransitionModel: Flow<DozeTransitionModel>
|
||||
@@ -120,11 +123,20 @@ interface KeyguardRepository {
|
||||
val statusBarState: Flow<StatusBarState>
|
||||
|
||||
/** Observable for device wake/sleep state */
|
||||
val wakefulnessState: Flow<WakefulnessModel>
|
||||
val wakefulness: Flow<WakefulnessModel>
|
||||
|
||||
/** Observable for biometric unlock modes */
|
||||
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.
|
||||
*
|
||||
@@ -163,6 +175,7 @@ constructor(
|
||||
private val keyguardStateController: KeyguardStateController,
|
||||
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
||||
private val dozeTransitionListener: DozeTransitionListener,
|
||||
private val authController: AuthController,
|
||||
) : KeyguardRepository {
|
||||
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
|
||||
override val animateBottomAreaDozingTransitions =
|
||||
@@ -281,11 +294,11 @@ constructor(
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val dozeAmount: Flow<Float> = conflatedCallbackFlow {
|
||||
override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {
|
||||
val callback =
|
||||
object : StatusBarStateController.StateListener {
|
||||
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) }
|
||||
}
|
||||
|
||||
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 {
|
||||
fun dispatchUpdate() {
|
||||
trySendWithFailureLogging(
|
||||
biometricModeIntToObject(biometricUnlockController.mode),
|
||||
TAG,
|
||||
"biometric mode"
|
||||
)
|
||||
}
|
||||
|
||||
val callback =
|
||||
object : BiometricUnlockController.BiometricModeListener {
|
||||
override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
|
||||
trySendWithFailureLogging(biometricModeIntToObject(mode), TAG, "biometric mode")
|
||||
dispatchUpdate()
|
||||
}
|
||||
|
||||
override fun onResetMode() {
|
||||
dispatchUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
biometricUnlockController.addBiometricModeListener(callback)
|
||||
trySendWithFailureLogging(
|
||||
biometricModeIntToObject(biometricUnlockController.getMode()),
|
||||
TAG,
|
||||
"initial biometric mode"
|
||||
)
|
||||
dispatchUpdate()
|
||||
|
||||
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) {
|
||||
_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 {
|
||||
return when (value) {
|
||||
0 -> BiometricUnlockModel.NONE
|
||||
|
||||
@@ -27,4 +27,7 @@ interface KeyguardRepositoryModule {
|
||||
fun keyguardTransitionRepository(
|
||||
impl: KeyguardTransitionRepositoryImpl
|
||||
): KeyguardTransitionRepository
|
||||
|
||||
@Binds
|
||||
fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,6 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo
|
||||
import com.android.systemui.util.kotlin.sample
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@SysUISingleton
|
||||
|
||||
@@ -42,7 +42,7 @@ constructor(
|
||||
|
||||
override fun start() {
|
||||
scope.launch {
|
||||
keyguardInteractor.wakefulnessState
|
||||
keyguardInteractor.wakefulnessModel
|
||||
.sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
|
||||
.collect { pair ->
|
||||
val (wakefulnessState, keyguardState) = pair
|
||||
|
||||
@@ -23,11 +23,10 @@ import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardState
|
||||
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 javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@SysUISingleton
|
||||
@@ -42,13 +41,13 @@ constructor(
|
||||
|
||||
override fun start() {
|
||||
scope.launch {
|
||||
keyguardInteractor.wakefulnessState
|
||||
keyguardInteractor.wakefulnessModel
|
||||
.sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
|
||||
.collect { pair ->
|
||||
val (wakefulnessState, keyguardState) = pair
|
||||
if (
|
||||
keyguardState == KeyguardState.GONE &&
|
||||
wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP
|
||||
wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP
|
||||
) {
|
||||
keyguardTransitionRepository.startTransition(
|
||||
TransitionInfo(
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package com.android.systemui.keyguard.domain.interactor
|
||||
|
||||
import android.graphics.Point
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardRepository
|
||||
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
|
||||
* all.
|
||||
*/
|
||||
val dozeAmount: Flow<Float> = repository.dozeAmount
|
||||
val dozeAmount: Flow<Float> = repository.linearDozeAmount
|
||||
/** Whether the system is in doze mode. */
|
||||
val isDozing: Flow<Boolean> = repository.isDozing
|
||||
/** Doze transition information. */
|
||||
@@ -58,7 +59,7 @@ constructor(
|
||||
/** Whether the bouncer is showing or not. */
|
||||
val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing
|
||||
/** The device wake/sleep state */
|
||||
val wakefulnessState: Flow<WakefulnessModel> = repository.wakefulnessState
|
||||
val wakefulnessModel: Flow<WakefulnessModel> = repository.wakefulness
|
||||
/** Observable for the [StatusBarState] */
|
||||
val statusBarState: Flow<StatusBarState> = repository.statusBarState
|
||||
/**
|
||||
@@ -67,10 +68,15 @@ constructor(
|
||||
*/
|
||||
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> {
|
||||
return dozeTransitionModel.filter { it.to == state }
|
||||
}
|
||||
|
||||
fun isKeyguardShowing(): Boolean {
|
||||
return repository.isKeyguardShowing()
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ constructor(
|
||||
|
||||
fun start() {
|
||||
scope.launch {
|
||||
keyguardInteractor.wakefulnessState.collect { logger.v("WakefulnessState", it) }
|
||||
keyguardInteractor.wakefulnessModel.collect { logger.v("WakefulnessModel", it) }
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.TransitionInfo
|
||||
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.util.kotlin.sample
|
||||
import java.util.UUID
|
||||
@@ -58,22 +58,26 @@ constructor(
|
||||
keyguardInteractor.isBouncerShowing
|
||||
.sample(
|
||||
combine(
|
||||
keyguardInteractor.wakefulnessState,
|
||||
keyguardInteractor.wakefulnessModel,
|
||||
keyguardTransitionInteractor.startedKeyguardTransitionStep,
|
||||
) { a, b ->
|
||||
Pair(a, b)
|
||||
},
|
||||
{ a, bc -> Triple(a, bc.first, bc.second) }
|
||||
)
|
||||
.collect { triple ->
|
||||
val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple
|
||||
) { wakefulnessModel, transitionStep ->
|
||||
Pair(wakefulnessModel, transitionStep)
|
||||
}
|
||||
) { bouncerShowing, wakefulnessAndTransition ->
|
||||
Triple(
|
||||
bouncerShowing,
|
||||
wakefulnessAndTransition.first,
|
||||
wakefulnessAndTransition.second
|
||||
)
|
||||
}
|
||||
.collect { (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) ->
|
||||
if (
|
||||
!isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
|
||||
) {
|
||||
val to =
|
||||
if (
|
||||
wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP ||
|
||||
wakefulnessState == WakefulnessModel.ASLEEP
|
||||
wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
|
||||
wakefulnessState.state == WakefulnessState.ASLEEP
|
||||
) {
|
||||
KeyguardState.AOD
|
||||
} else {
|
||||
@@ -100,14 +104,17 @@ constructor(
|
||||
combine(
|
||||
keyguardTransitionInteractor.finishedKeyguardState,
|
||||
keyguardInteractor.statusBarState,
|
||||
) { a, b ->
|
||||
Pair(a, b)
|
||||
},
|
||||
{ a, bc -> Triple(a, bc.first, bc.second) }
|
||||
)
|
||||
.collect { triple ->
|
||||
val (shadeModel, keyguardState, statusBarState) = triple
|
||||
|
||||
) { finishedKeyguardState, statusBarState ->
|
||||
Pair(finishedKeyguardState, statusBarState)
|
||||
}
|
||||
) { shadeModel, keyguardStateAndStatusBarState ->
|
||||
Triple(
|
||||
shadeModel,
|
||||
keyguardStateAndStatusBarState.first,
|
||||
keyguardStateAndStatusBarState.second
|
||||
)
|
||||
}
|
||||
.collect { (shadeModel, keyguardState, statusBarState) ->
|
||||
val id = transitionId
|
||||
if (id != null) {
|
||||
// An existing `id` means a transition is started, and calls to
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,24 +15,34 @@
|
||||
*/
|
||||
package com.android.systemui.keyguard.shared.model
|
||||
|
||||
/** Model device wakefulness states. */
|
||||
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;
|
||||
import com.android.systemui.keyguard.WakefulnessLifecycle
|
||||
|
||||
/** Model device wakefulness states. */
|
||||
data class WakefulnessModel(
|
||||
val state: WakefulnessState,
|
||||
val isWakingUpOrAwake: Boolean,
|
||||
val lastWakeReason: WakeSleepReason,
|
||||
val lastSleepReason: WakeSleepReason,
|
||||
) {
|
||||
companion object {
|
||||
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 {
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -514,7 +514,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
|
||||
&& mPendingAuthenticated.userId == KeyguardUpdateMonitor.getCurrentUser();
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
public @WakeAndUnlockMode int getMode() {
|
||||
return mMode;
|
||||
}
|
||||
|
||||
|
||||
@@ -160,6 +160,8 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
|
||||
import com.android.systemui.keyguard.KeyguardViewMediator;
|
||||
import com.android.systemui.keyguard.ScreenLifecycle;
|
||||
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.NavigationBarView;
|
||||
import com.android.systemui.plugins.DarkIconDispatcher;
|
||||
@@ -491,6 +493,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
|
||||
private final OngoingCallController mOngoingCallController;
|
||||
private final StatusBarSignalPolicy mStatusBarSignalPolicy;
|
||||
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
|
||||
private final Lazy<LightRevealScrimViewModel> mLightRevealScrimViewModelLazy;
|
||||
|
||||
/** Controller for the Shade. */
|
||||
@VisibleForTesting
|
||||
@@ -757,7 +760,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
|
||||
DeviceStateManager deviceStateManager,
|
||||
WiredChargingRippleController wiredChargingRippleController,
|
||||
IDreamManager dreamManager,
|
||||
Lazy<CameraLauncher> cameraLauncherLazy) {
|
||||
Lazy<CameraLauncher> cameraLauncherLazy,
|
||||
Lazy<LightRevealScrimViewModel> lightRevealScrimViewModelLazy) {
|
||||
mContext = context;
|
||||
mNotificationsController = notificationsController;
|
||||
mFragmentService = fragmentService;
|
||||
@@ -871,6 +875,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
|
||||
deviceStateManager.registerCallback(mMainExecutor,
|
||||
new FoldStateListener(mContext, this::onFoldedStateChanged));
|
||||
wiredChargingRippleController.registerCallbacks();
|
||||
|
||||
mLightRevealScrimViewModelLazy = lightRevealScrimViewModelLazy;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1000,6 +1006,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
|
||||
|
||||
@Override
|
||||
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
|
||||
// is done going away. Double check that this is true.
|
||||
if (!mKeyguardStateController.isKeyguardGoingAway()) {
|
||||
@@ -1236,6 +1248,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
|
||||
mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront);
|
||||
|
||||
mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
|
||||
|
||||
if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
|
||||
LightRevealScrimViewBinder.bind(
|
||||
mLightRevealScrim, mLightRevealScrimViewModelLazy.get());
|
||||
}
|
||||
|
||||
mLightRevealScrim.setScrimOpaqueChangedListener((opaque) -> {
|
||||
Runnable updateOpaqueness = () -> {
|
||||
mNotificationShadeWindowController.setLightRevealScrimOpaque(
|
||||
@@ -3308,6 +3326,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean wakingUpFromPowerButton = wakingUp
|
||||
&& !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)
|
||||
&& mWakefulnessLifecycle.getLastWakeReason()
|
||||
@@ -4072,7 +4094,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
|
||||
return;
|
||||
}
|
||||
|
||||
mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha());
|
||||
if (!mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)) {
|
||||
mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -4253,6 +4277,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
|
||||
@Override
|
||||
public void onDozeAmountChanged(float linear, float eased) {
|
||||
if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ANIMATIONS)
|
||||
&& !mFeatureFlags.isEnabled(Flags.LIGHT_REVEAL_MIGRATION)
|
||||
&& !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
|
||||
mLightRevealScrim.setRevealAmount(1f - linear);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.keyguard.KeyguardUpdateMonitorCallback
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.keyguard.WakefulnessLifecycle
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||
import com.android.systemui.statusbar.LightRevealScrim
|
||||
@@ -77,6 +78,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
|
||||
@Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController>
|
||||
@Mock private lateinit var udfpsController: UdfpsController
|
||||
@Mock private lateinit var statusBarStateController: StatusBarStateController
|
||||
@Mock private lateinit var featureFlags: FeatureFlags
|
||||
@Mock private lateinit var lightRevealScrim: LightRevealScrim
|
||||
@Mock private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
|
||||
|
||||
@@ -106,6 +108,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
|
||||
biometricUnlockController,
|
||||
udfpsControllerProvider,
|
||||
statusBarStateController,
|
||||
featureFlags,
|
||||
rippleView
|
||||
)
|
||||
controller.init()
|
||||
|
||||
@@ -16,10 +16,13 @@
|
||||
|
||||
package com.android.systemui.keyguard.data.repository
|
||||
|
||||
import android.graphics.Point
|
||||
import android.hardware.biometrics.BiometricSourceType
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.keyguard.KeyguardUpdateMonitorCallback
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.biometrics.AuthController
|
||||
import com.android.systemui.common.shared.model.Position
|
||||
import com.android.systemui.doze.DozeHost
|
||||
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.keyguard.WakefulnessLifecycle
|
||||
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.DozeTransitionModel
|
||||
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.statusbar.phone.BiometricUnlockController
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||
@@ -57,9 +62,10 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
|
||||
@Mock private lateinit var dozeHost: DozeHost
|
||||
@Mock private lateinit var keyguardStateController: KeyguardStateController
|
||||
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
|
||||
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
||||
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
|
||||
@Mock private lateinit var dozeTransitionListener: DozeTransitionListener
|
||||
@Mock private lateinit var authController: AuthController
|
||||
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
||||
|
||||
private lateinit var underTest: KeyguardRepositoryImpl
|
||||
|
||||
@@ -76,6 +82,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
|
||||
keyguardStateController,
|
||||
keyguardUpdateMonitor,
|
||||
dozeTransitionListener,
|
||||
authController,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -198,7 +205,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
|
||||
fun dozeAmount() =
|
||||
runTest(UnconfinedTestDispatcher()) {
|
||||
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>()
|
||||
verify(statusBarStateController).addCallback(captor.capture())
|
||||
@@ -207,7 +214,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
|
||||
captor.value.onDozeAmountChanged(0.498f, 0.5f)
|
||||
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()
|
||||
verify(statusBarStateController).removeCallback(captor.value)
|
||||
@@ -217,25 +224,36 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
|
||||
fun wakefulness() =
|
||||
runTest(UnconfinedTestDispatcher()) {
|
||||
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>()
|
||||
verify(wakefulnessLifecycle).addObserver(captor.capture())
|
||||
|
||||
whenever(wakefulnessLifecycle.wakefulness)
|
||||
.thenReturn(WakefulnessLifecycle.WAKEFULNESS_WAKING)
|
||||
captor.value.onStartedWakingUp()
|
||||
|
||||
whenever(wakefulnessLifecycle.wakefulness)
|
||||
.thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE)
|
||||
captor.value.onFinishedWakingUp()
|
||||
|
||||
whenever(wakefulnessLifecycle.wakefulness)
|
||||
.thenReturn(WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP)
|
||||
captor.value.onStartedGoingToSleep()
|
||||
|
||||
whenever(wakefulnessLifecycle.wakefulness)
|
||||
.thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP)
|
||||
captor.value.onFinishedGoingToSleep()
|
||||
|
||||
assertThat(values)
|
||||
assertThat(values.map { it.state })
|
||||
.isEqualTo(
|
||||
listOf(
|
||||
// Initial value will be ASLEEP
|
||||
WakefulnessModel.ASLEEP,
|
||||
WakefulnessModel.STARTING_TO_WAKE,
|
||||
WakefulnessModel.AWAKE,
|
||||
WakefulnessModel.STARTING_TO_SLEEP,
|
||||
WakefulnessModel.ASLEEP,
|
||||
WakefulnessState.ASLEEP,
|
||||
WakefulnessState.STARTING_TO_WAKE,
|
||||
WakefulnessState.AWAKE,
|
||||
WakefulnessState.STARTING_TO_SLEEP,
|
||||
WakefulnessState.ASLEEP,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -329,14 +347,20 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
|
||||
val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
|
||||
verify(biometricUnlockController).addBiometricModeListener(captor.capture())
|
||||
|
||||
captor.value.onModeChanged(BiometricUnlockController.MODE_NONE)
|
||||
captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
|
||||
captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
|
||||
captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER)
|
||||
captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE)
|
||||
captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING)
|
||||
captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER)
|
||||
captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
|
||||
listOf(
|
||||
BiometricUnlockController.MODE_NONE,
|
||||
BiometricUnlockController.MODE_WAKE_AND_UNLOCK,
|
||||
BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING,
|
||||
BiometricUnlockController.MODE_SHOW_BOUNCER,
|
||||
BiometricUnlockController.MODE_ONLY_WAKE,
|
||||
BiometricUnlockController.MODE_UNLOCK_COLLAPSING,
|
||||
BiometricUnlockController.MODE_DISMISS_BOUNCER,
|
||||
BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM,
|
||||
)
|
||||
.forEach {
|
||||
whenever(biometricUnlockController.mode).thenReturn(it)
|
||||
captor.value.onModeChanged(it)
|
||||
}
|
||||
|
||||
assertThat(values)
|
||||
.isEqualTo(
|
||||
@@ -420,4 +444,104 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
|
||||
job.cancel()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
|
||||
import com.android.systemui.keyguard.KeyguardViewMediator;
|
||||
import com.android.systemui.keyguard.ScreenLifecycle;
|
||||
import com.android.systemui.keyguard.WakefulnessLifecycle;
|
||||
import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
|
||||
import com.android.systemui.navigationbar.NavigationBarController;
|
||||
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
|
||||
import com.android.systemui.plugins.PluginDependencyProvider;
|
||||
@@ -212,6 +213,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
|
||||
@Mock private NotificationPanelView mNotificationPanelView;
|
||||
@Mock private IStatusBarService mBarService;
|
||||
@Mock private IDreamManager mDreamManager;
|
||||
@Mock private LightRevealScrimViewModel mLightRevealScrimViewModel;
|
||||
@Mock private ScrimController mScrimController;
|
||||
@Mock private DozeScrimController mDozeScrimController;
|
||||
@Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
|
||||
@@ -497,7 +499,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
|
||||
mDeviceStateManager,
|
||||
mWiredChargingRippleController,
|
||||
mDreamManager,
|
||||
mCameraLauncherLazy) {
|
||||
mCameraLauncherLazy,
|
||||
() -> mLightRevealScrimViewModel) {
|
||||
@Override
|
||||
protected ViewRootImpl getViewRootImpl() {
|
||||
return mViewRootImpl;
|
||||
|
||||
@@ -17,11 +17,15 @@
|
||||
|
||||
package com.android.systemui.keyguard.data.repository
|
||||
|
||||
import android.graphics.Point
|
||||
import com.android.systemui.common.shared.model.Position
|
||||
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.StatusBarState
|
||||
import com.android.systemui.keyguard.shared.model.WakeSleepReason
|
||||
import com.android.systemui.keyguard.shared.model.WakefulnessModel
|
||||
import com.android.systemui.keyguard.shared.model.WakefulnessState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -49,7 +53,7 @@ class FakeKeyguardRepository : KeyguardRepository {
|
||||
override val isDreaming: Flow<Boolean> = _isDreaming
|
||||
|
||||
private val _dozeAmount = MutableStateFlow(0f)
|
||||
override val dozeAmount: Flow<Float> = _dozeAmount
|
||||
override val linearDozeAmount: Flow<Float> = _dozeAmount
|
||||
|
||||
private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
|
||||
override val statusBarState: Flow<StatusBarState> = _statusBarState
|
||||
@@ -57,8 +61,16 @@ class FakeKeyguardRepository : KeyguardRepository {
|
||||
private val _dozeTransitionModel = MutableStateFlow(DozeTransitionModel())
|
||||
override val dozeTransitionModel: Flow<DozeTransitionModel> = _dozeTransitionModel
|
||||
|
||||
private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP)
|
||||
override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
|
||||
private val _wakefulnessModel =
|
||||
MutableStateFlow(
|
||||
WakefulnessModel(
|
||||
WakefulnessState.ASLEEP,
|
||||
false,
|
||||
WakeSleepReason.OTHER,
|
||||
WakeSleepReason.OTHER
|
||||
)
|
||||
)
|
||||
override val wakefulness: Flow<WakefulnessModel> = _wakefulnessModel
|
||||
|
||||
private val _isUdfpsSupported = MutableStateFlow(false)
|
||||
|
||||
@@ -71,6 +83,15 @@ class FakeKeyguardRepository : KeyguardRepository {
|
||||
private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
|
||||
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 {
|
||||
return _isKeyguardShowing.value
|
||||
}
|
||||
@@ -99,6 +120,22 @@ class FakeKeyguardRepository : KeyguardRepository {
|
||||
_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 {
|
||||
return _isUdfpsSupported.value
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user