Loading packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +50 −12 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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. Loading @@ -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, Loading @@ -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 Loading @@ -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() } } } Loading @@ -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) Loading Loading @@ -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 Loading Loading @@ -318,4 +352,8 @@ class AuthRippleController @Inject constructor( help(pw) help(pw) } } } } companion object { const val RIPPLE_ANIMATION_DURATION: Long = 1533 } } } packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +2 −14 Original line number Original line Diff line number Diff line Loading @@ -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 /** /** Loading Loading @@ -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 } } Loading @@ -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 Loading @@ -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 -> Loading @@ -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() { Loading packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +14 −3 Original line number Original line Diff line number Diff line Loading @@ -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}. Loading Loading @@ -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()) { Loading Loading @@ -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; Loading packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +3 −1 Original line number Original line Diff line number Diff line Loading @@ -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); } } }; }; Loading Loading @@ -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); } } } } Loading packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +51 −7 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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() { Loading @@ -73,6 +82,8 @@ class AuthRippleControllerTest : SysuiTestCase() { authController, authController, configurationController, configurationController, keyguardUpdateMonitor, keyguardUpdateMonitor, keyguardStateController, wakefulnessLifecycle, commandRegistry, commandRegistry, notificationShadeWindowController, notificationShadeWindowController, bypassController, bypassController, Loading @@ -82,6 +93,7 @@ class AuthRippleControllerTest : SysuiTestCase() { rippleView rippleView ) ) controller.init() controller.init() `when`(statusBar.lightRevealScrim).thenReturn(lightRevealScrim) } } @Test @Test Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading
packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +50 −12 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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. Loading @@ -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, Loading @@ -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 Loading @@ -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() } } } Loading @@ -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) Loading Loading @@ -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 Loading Loading @@ -318,4 +352,8 @@ class AuthRippleController @Inject constructor( help(pw) help(pw) } } } } companion object { const val RIPPLE_ANIMATION_DURATION: Long = 1533 } } }
packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +2 −14 Original line number Original line Diff line number Diff line Loading @@ -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 /** /** Loading Loading @@ -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 } } Loading @@ -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 Loading @@ -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 -> Loading @@ -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() { Loading
packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +14 −3 Original line number Original line Diff line number Diff line Loading @@ -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}. Loading Loading @@ -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()) { Loading Loading @@ -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; Loading
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +3 −1 Original line number Original line Diff line number Diff line Loading @@ -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); } } }; }; Loading Loading @@ -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); } } } } Loading
packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +51 −7 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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() { Loading @@ -73,6 +82,8 @@ class AuthRippleControllerTest : SysuiTestCase() { authController, authController, configurationController, configurationController, keyguardUpdateMonitor, keyguardUpdateMonitor, keyguardStateController, wakefulnessLifecycle, commandRegistry, commandRegistry, notificationShadeWindowController, notificationShadeWindowController, bypassController, bypassController, Loading @@ -82,6 +93,7 @@ class AuthRippleControllerTest : SysuiTestCase() { rippleView rippleView ) ) controller.init() controller.init() `when`(statusBar.lightRevealScrim).thenReturn(lightRevealScrim) } } @Test @Test Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading