Merge "Revealing the light reveal scrim only during keyguard exit animation" into sc-dev

This commit is contained in:
Lucas Dupin
2021-09-17 21:32:24 +00:00
committed by Android (Google) Code Review
5 changed files with 120 additions and 37 deletions

View File

@@ -16,6 +16,7 @@
package com.android.systemui.biometrics package com.android.systemui.biometrics
import android.animation.ValueAnimator
import android.content.Context import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.PointF import android.graphics.PointF
@@ -26,6 +27,8 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback 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.keyguard.WakefulnessLifecycle
import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -36,12 +39,15 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.phone.StatusBar
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope
import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.ViewController import com.android.systemui.util.ViewController
import java.io.PrintWriter import java.io.PrintWriter
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider import javax.inject.Provider
import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.plugins.statusbar.StatusBarStateController
private const val WAKE_AND_UNLOCK_FADE_DURATION = 180L
/*** /***
* Controls the ripple effect that shows when authentication is successful. * Controls the ripple effect that shows when authentication is successful.
* The ripple uses the accent color of the current theme. * The ripple uses the accent color of the current theme.
@@ -53,6 +59,8 @@ class AuthRippleController @Inject constructor(
private val authController: AuthController, private val authController: AuthController,
private val configurationController: ConfigurationController, private val configurationController: ConfigurationController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val keyguardStateController: KeyguardStateController,
private val wakefulnessLifecycle: WakefulnessLifecycle,
private val commandRegistry: CommandRegistry, private val commandRegistry: CommandRegistry,
private val notificationShadeWindowController: NotificationShadeWindowController, private val notificationShadeWindowController: NotificationShadeWindowController,
private val bypassController: KeyguardBypassController, private val bypassController: KeyguardBypassController,
@@ -60,7 +68,11 @@ class AuthRippleController @Inject constructor(
private val udfpsControllerProvider: Provider<UdfpsController>, private val udfpsControllerProvider: Provider<UdfpsController>,
private val statusBarStateController: StatusBarStateController, private val statusBarStateController: StatusBarStateController,
rippleView: AuthRippleView? rippleView: AuthRippleView?
) : ViewController<AuthRippleView>(rippleView) { ) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback,
WakefulnessLifecycle.Observer {
@VisibleForTesting
internal var startLightRevealScrimOnKeyguardFadingAway = false
var fingerprintSensorLocation: PointF? = null var fingerprintSensorLocation: PointF? = null
private var faceSensorLocation: PointF? = null private var faceSensorLocation: PointF? = null
private var circleReveal: LightRevealEffect? = null private var circleReveal: LightRevealEffect? = null
@@ -87,6 +99,8 @@ class AuthRippleController @Inject constructor(
udfpsController?.addCallback(udfpsControllerCallback) udfpsController?.addCallback(udfpsControllerCallback)
configurationController.addCallback(configurationChangedListener) configurationController.addCallback(configurationChangedListener)
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
keyguardStateController.addCallback(this)
wakefulnessLifecycle.addObserver(this)
commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() } commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
} }
@@ -96,6 +110,8 @@ class AuthRippleController @Inject constructor(
authController.removeCallback(authControllerCallback) authController.removeCallback(authControllerCallback)
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
configurationController.removeCallback(configurationChangedListener) configurationController.removeCallback(configurationChangedListener)
keyguardStateController.removeCallback(this)
wakefulnessLifecycle.removeObserver(this)
commandRegistry.unregisterCommand("auth-ripple") commandRegistry.unregisterCommand("auth-ripple")
notificationShadeWindowController.setForcePluginOpen(false, this) notificationShadeWindowController.setForcePluginOpen(false, this)
@@ -123,30 +139,48 @@ class AuthRippleController @Inject constructor(
private fun showUnlockedRipple() { private fun showUnlockedRipple() {
notificationShadeWindowController.setForcePluginOpen(true, this) notificationShadeWindowController.setForcePluginOpen(true, this)
val biometricUnlockMode = biometricUnlockController.mode val useCircleReveal = circleReveal != null && biometricUnlockController.isWakeAndUnlock
val useCircleReveal = circleReveal != null &&
(biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK ||
biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING ||
biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
val lightRevealScrim = statusBar.lightRevealScrim val lightRevealScrim = statusBar.lightRevealScrim
if (useCircleReveal) { if (useCircleReveal) {
lightRevealScrim?.revealEffect = circleReveal!! lightRevealScrim?.revealEffect = circleReveal!!
startLightRevealScrimOnKeyguardFadingAway = true
} }
mView.startUnlockedRipple( mView.startUnlockedRipple(
/* end runnable */ /* end runnable */
Runnable { Runnable {
notificationShadeWindowController.setForcePluginOpen(false, this) notificationShadeWindowController.setForcePluginOpen(false, this)
},
/* circleReveal */
if (useCircleReveal) {
lightRevealScrim
} else {
null
} }
) )
} }
override fun onKeyguardFadingAwayChanged() {
if (keyguardStateController.isKeyguardFadingAway) {
val lightRevealScrim = statusBar.lightRevealScrim
if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) {
val revealAnimator = ValueAnimator.ofFloat(.1f, 1f).apply {
interpolator = Interpolators.LINEAR_OUT_SLOW_IN
duration = RIPPLE_ANIMATION_DURATION
startDelay = keyguardStateController.keyguardFadingAwayDelay
addUpdateListener { animator ->
if (lightRevealScrim.revealEffect != circleReveal) {
// if the something else took over the reveal, let's do nothing.
return@addUpdateListener
}
lightRevealScrim.revealAmount = animator.animatedValue as Float
}
}
revealAnimator.start()
startLightRevealScrimOnKeyguardFadingAway = false
}
}
}
override fun onStartedGoingToSleep() {
// reset the light reveal start in case we were pending an unlock
startLightRevealScrimOnKeyguardFadingAway = false
}
fun updateSensorLocation() { fun updateSensorLocation() {
fingerprintSensorLocation = authController.fingerprintSensorLocation fingerprintSensorLocation = authController.fingerprintSensorLocation
faceSensorLocation = authController.faceAuthSensorLocation faceSensorLocation = authController.faceAuthSensorLocation
@@ -318,4 +352,8 @@ class AuthRippleController @Inject constructor(
help(pw) help(pw)
} }
} }
companion object {
const val RIPPLE_ANIMATION_DURATION: Long = 1533
}
} }

View File

@@ -26,13 +26,10 @@ import android.graphics.PointF
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.view.animation.PathInterpolator import android.view.animation.PathInterpolator
import com.android.internal.R.attr.interpolator
import com.android.internal.graphics.ColorUtils import com.android.internal.graphics.ColorUtils
import com.android.systemui.animation.Interpolators import com.android.systemui.animation.Interpolators
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.charging.RippleShader import com.android.systemui.statusbar.charging.RippleShader
private const val RIPPLE_ANIMATION_DURATION: Long = 1533
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
/** /**
@@ -250,7 +247,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
/** /**
* Ripple that bursts outwards from the position of the sensor to the edges of the screen * Ripple that bursts outwards from the position of the sensor to the edges of the screen
*/ */
fun startUnlockedRipple(onAnimationEnd: Runnable?, lightReveal: LightRevealScrim?) { fun startUnlockedRipple(onAnimationEnd: Runnable?) {
if (unlockedRippleInProgress) { if (unlockedRippleInProgress) {
return // Ignore if ripple effect is already playing return // Ignore if ripple effect is already playing
} }
@@ -266,7 +263,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
val rippleAnimator = ValueAnimator.ofFloat(rippleStart, 1f).apply { val rippleAnimator = ValueAnimator.ofFloat(rippleStart, 1f).apply {
interpolator = Interpolators.LINEAR_OUT_SLOW_IN interpolator = Interpolators.LINEAR_OUT_SLOW_IN
duration = RIPPLE_ANIMATION_DURATION duration = AuthRippleController.RIPPLE_ANIMATION_DURATION
addUpdateListener { animator -> addUpdateListener { animator ->
val now = animator.currentPlayTime val now = animator.currentPlayTime
rippleShader.progress = animator.animatedValue as Float rippleShader.progress = animator.animatedValue as Float
@@ -276,14 +273,6 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
} }
} }
val revealAnimator = ValueAnimator.ofFloat(.1f, 1f).apply {
interpolator = rippleAnimator.interpolator
duration = rippleAnimator.duration
addUpdateListener { animator ->
lightReveal?.revealAmount = animator.animatedValue as Float
}
}
val alphaInAnimator = ValueAnimator.ofInt(0, 255).apply { val alphaInAnimator = ValueAnimator.ofInt(0, 255).apply {
duration = alphaDuration duration = alphaDuration
addUpdateListener { animator -> addUpdateListener { animator ->
@@ -298,7 +287,6 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
val animatorSet = AnimatorSet().apply { val animatorSet = AnimatorSet().apply {
playTogether( playTogether(
rippleAnimator, rippleAnimator,
revealAnimator,
alphaInAnimator alphaInAnimator
) )
addListener(object : AnimatorListenerAdapter() { addListener(object : AnimatorListenerAdapter() {

View File

@@ -818,6 +818,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
private final KeyguardStateController mKeyguardStateController; private final KeyguardStateController mKeyguardStateController;
private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy; private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
private boolean mWallpaperSupportsAmbientMode;
/** /**
* Injected constructor. See {@link KeyguardModule}. * Injected constructor. See {@link KeyguardModule}.
@@ -2089,13 +2090,14 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
int flags = 0; int flags = 0;
if (mKeyguardViewControllerLazy.get().shouldDisableWindowAnimationsForUnlock() if (mKeyguardViewControllerLazy.get().shouldDisableWindowAnimationsForUnlock()
|| (mWakeAndUnlocking && !mPulsing) || mWakeAndUnlocking && !mWallpaperSupportsAmbientMode) {
|| isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()) {
flags |= WindowManagerPolicyConstants flags |= WindowManagerPolicyConstants
.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; .KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
} }
if (mKeyguardViewControllerLazy.get().isGoingToNotificationShade() if (mKeyguardViewControllerLazy.get().isGoingToNotificationShade()
|| (mWakeAndUnlocking && mPulsing)) { || mWakeAndUnlocking && mWallpaperSupportsAmbientMode) {
// When the wallpaper supports ambient mode, the scrim isn't fully opaque during
// wake and unlock and we should fade in the app on top of the wallpaper
flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE; flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
} }
if (mKeyguardViewControllerLazy.get().isUnlockWithWallpaper()) { if (mKeyguardViewControllerLazy.get().isUnlockWithWallpaper()) {
@@ -2784,6 +2786,15 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
mPulsing = pulsing; mPulsing = pulsing;
} }
/**
* Set if the wallpaper supports ambient mode. This is used to trigger the right animation.
* In case it does support it, we have to fade in the incoming app, otherwise we'll reveal it
* with the light reveal scrim.
*/
public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
mWallpaperSupportsAmbientMode = supportsAmbientMode;
}
private static class StartKeyguardExitAnimParams { private static class StartKeyguardExitAnimParams {
@WindowManager.TransitionOldType int mTransit; @WindowManager.TransitionOldType int mTransit;

View File

@@ -592,6 +592,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode); mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode); mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode);
} }
}; };
@@ -3913,7 +3914,8 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override @Override
public void onDozeAmountChanged(float linear, float eased) { public void onDozeAmountChanged(float linear, float eased) {
if (mFeatureFlags.useNewLockscreenAnimations() if (mFeatureFlags.useNewLockscreenAnimations()
&& !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)
&& !mBiometricUnlockController.isWakeAndUnlock()) {
mLightRevealScrim.setRevealAmount(1f - linear); mLightRevealScrim.setRevealAmount(1f - linear);
} }
} }

View File

@@ -19,17 +19,23 @@ package com.android.systemui.biometrics
import android.graphics.PointF import android.graphics.PointF
import android.hardware.biometrics.BiometricSourceType import android.hardware.biometrics.BiometricSourceType
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
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.plugins.statusbar.StatusBarStateController import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.phone.StatusBar
import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -55,12 +61,15 @@ class AuthRippleControllerTest : SysuiTestCase() {
@Mock private lateinit var configurationController: ConfigurationController @Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var authController: AuthController @Mock private lateinit var authController: AuthController
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var bypassController: KeyguardBypassController @Mock private lateinit var bypassController: KeyguardBypassController
@Mock private lateinit var biometricUnlockController: BiometricUnlockController @Mock private lateinit var biometricUnlockController: BiometricUnlockController
@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 lightRevealScrim: LightRevealScrim
@Before @Before
fun setUp() { fun setUp() {
@@ -73,6 +82,8 @@ class AuthRippleControllerTest : SysuiTestCase() {
authController, authController,
configurationController, configurationController,
keyguardUpdateMonitor, keyguardUpdateMonitor,
keyguardStateController,
wakefulnessLifecycle,
commandRegistry, commandRegistry,
notificationShadeWindowController, notificationShadeWindowController,
bypassController, bypassController,
@@ -82,6 +93,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
rippleView rippleView
) )
controller.init() controller.init()
`when`(statusBar.lightRevealScrim).thenReturn(lightRevealScrim)
} }
@Test @Test
@@ -103,7 +115,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
// THEN update sensor location and show ripple // THEN update sensor location and show ripple
verify(rippleView).setSensorLocation(fpsLocation) verify(rippleView).setSensorLocation(fpsLocation)
verify(rippleView).startUnlockedRipple(any(), any()) verify(rippleView).startUnlockedRipple(any())
} }
@Test @Test
@@ -124,7 +136,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
false /* isStrongBiometric */) false /* isStrongBiometric */)
// THEN no ripple // THEN no ripple
verify(rippleView, never()).startUnlockedRipple(any(), any()) verify(rippleView, never()).startUnlockedRipple(any())
} }
@Test @Test
@@ -145,7 +157,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
false /* isStrongBiometric */) false /* isStrongBiometric */)
// THEN no ripple // THEN no ripple
verify(rippleView, never()).startUnlockedRipple(any(), any()) verify(rippleView, never()).startUnlockedRipple(any())
} }
@Test @Test
@@ -169,7 +181,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
// THEN show ripple // THEN show ripple
verify(rippleView).setSensorLocation(faceLocation) verify(rippleView).setSensorLocation(faceLocation)
verify(rippleView).startUnlockedRipple(any(), any()) verify(rippleView).startUnlockedRipple(any())
} }
@Test @Test
@@ -189,7 +201,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
false /* isStrongBiometric */) false /* isStrongBiometric */)
// THEN no ripple // THEN no ripple
verify(rippleView, never()).startUnlockedRipple(any(), any()) verify(rippleView, never()).startUnlockedRipple(any())
} }
@Test @Test
@@ -204,7 +216,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
0 /* userId */, 0 /* userId */,
BiometricSourceType.FACE /* type */, BiometricSourceType.FACE /* type */,
false /* isStrongBiometric */) false /* isStrongBiometric */)
verify(rippleView, never()).startUnlockedRipple(any(), any()) verify(rippleView, never()).startUnlockedRipple(any())
} }
@Test @Test
@@ -219,7 +231,39 @@ class AuthRippleControllerTest : SysuiTestCase() {
0 /* userId */, 0 /* userId */,
BiometricSourceType.FINGERPRINT /* type */, BiometricSourceType.FINGERPRINT /* type */,
false /* isStrongBiometric */) false /* isStrongBiometric */)
verify(rippleView, never()).startUnlockedRipple(any(), any()) verify(rippleView, never()).startUnlockedRipple(any())
}
@Test
fun registersAndDeregisters() {
controller.onViewAttached()
val captor = ArgumentCaptor
.forClass(KeyguardStateController.Callback::class.java)
verify(keyguardStateController).addCallback(captor.capture())
val captor2 = ArgumentCaptor
.forClass(WakefulnessLifecycle.Observer::class.java)
verify(wakefulnessLifecycle).addObserver(captor2.capture())
controller.onViewDetached()
verify(keyguardStateController).removeCallback(any())
verify(wakefulnessLifecycle).removeObserver(any())
}
@Test
@RunWithLooper(setAsMainLooper = true)
fun testAnimatorRunWhenWakeAndUnlock() {
val fpsLocation = PointF(5f, 5f)
`when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
controller.onViewAttached()
`when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
`when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
controller.showRipple(BiometricSourceType.FINGERPRINT)
assertTrue("reveal didn't start on keyguardFadingAway",
controller.startLightRevealScrimOnKeyguardFadingAway)
`when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true)
controller.onKeyguardFadingAwayChanged()
assertFalse("reveal triggers multiple times",
controller.startLightRevealScrimOnKeyguardFadingAway)
} }
@Test @Test