From 28a01ca4587a65c51c8f7f1c0a25179d1ff724c3 Mon Sep 17 00:00:00 2001 From: Beverly Date: Tue, 15 Jun 2021 11:56:13 -0400 Subject: [PATCH] Coordinate circle reveal with authRipple Move CircleReveal from StatusBar to AuthRippleController so we can coordinate the light reveal scrim with the auth ripple when the user uses fingerprint to authenticate on AOD. Test: manual Fixes: 190617092 Change-Id: If210cebb801ca0f66405401710bac6087a313eea --- .../biometrics/AuthRippleController.kt | 46 ++++++++- .../systemui/biometrics/AuthRippleView.kt | 99 ++++++++++++------- .../systemui/statusbar/LightRevealScrim.kt | 8 +- .../systemui/statusbar/phone/StatusBar.java | 49 ++------- .../biometrics/AuthRippleControllerTest.kt | 17 ++-- 5 files changed, 127 insertions(+), 92 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index cf577a37d6255..77cca2e3089ca 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -24,9 +24,13 @@ import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.settingslib.Utils +import com.android.systemui.statusbar.CircleReveal +import com.android.systemui.statusbar.LiftReveal +import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope @@ -49,10 +53,12 @@ class AuthRippleController @Inject constructor( private val commandRegistry: CommandRegistry, private val notificationShadeWindowController: NotificationShadeWindowController, private val bypassController: KeyguardBypassController, + private val biometricUnlockController: BiometricUnlockController, rippleView: AuthRippleView? ) : ViewController(rippleView) { var fingerprintSensorLocation: PointF? = null private var faceSensorLocation: PointF? = null + private var circleReveal: LightRevealEffect? = null @VisibleForTesting public override fun onViewAttached() { @@ -96,15 +102,47 @@ class AuthRippleController @Inject constructor( private fun showRipple() { notificationShadeWindowController.setForcePluginOpen(true, this) - mView.startRipple(Runnable { - notificationShadeWindowController.setForcePluginOpen(false, this) - }) + val biometricUnlockMode = biometricUnlockController.mode + 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 + if (useCircleReveal) { + lightRevealScrim?.revealEffect = circleReveal!! + } + + mView.startRipple( + /* end runnable */ + Runnable { + notificationShadeWindowController.setForcePluginOpen(false, this) + if (useCircleReveal) { + lightRevealScrim?.revealEffect = LiftReveal + } + }, + /* circleReveal */ + if (useCircleReveal) { + lightRevealScrim + } else { + null + } + ) } fun updateSensorLocation() { fingerprintSensorLocation = authController.fingerprintSensorLocation faceSensorLocation = authController.faceAuthSensorLocation - statusBar.updateCircleReveal() + fingerprintSensorLocation?.let { + circleReveal = CircleReveal( + it.x, + it.y, + 0f, + Math.max( + Math.max(it.x, statusBar.displayWidth - it.x), + Math.max(it.y, statusBar.displayHeight - it.y) + ) + ) + } } private fun updateRippleColor() { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index 75373abc5124f..dd73c4f8d0712 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -31,6 +31,7 @@ import android.util.MathUtils import android.view.View import android.view.animation.PathInterpolator import com.android.internal.graphics.ColorUtils +import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.charging.RippleShader private const val RIPPLE_ANIMATION_DURATION: Long = 1533 @@ -70,51 +71,79 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at .toFloat() } - fun startRipple(onAnimationEnd: Runnable?) { + fun startRipple(onAnimationEnd: Runnable?, lightReveal: LightRevealScrim?) { if (rippleInProgress) { return // Ignore if ripple effect is already playing } - val animator = ValueAnimator.ofFloat(0f, 1f) - animator.interpolator = PathInterpolator(0.4f, 0f, 0f, 1f) - animator.duration = RIPPLE_ANIMATION_DURATION - animator.addUpdateListener { animator -> - val now = animator.currentPlayTime - rippleShader.progress = animator.animatedValue as Float - rippleShader.time = now.toFloat() - rippleShader.distortionStrength = 1 - rippleShader.progress - invalidate() - } - val alphaInAnimator = ValueAnimator.ofInt(0, 127) - alphaInAnimator.duration = 167 - alphaInAnimator.addUpdateListener { alphaInAnimator -> - rippleShader.color = ColorUtils.setAlphaComponent(rippleShader.color, - alphaInAnimator.animatedValue as Int) - invalidate() - } - val alphaOutAnimator = ValueAnimator.ofInt(127, 0) - alphaOutAnimator.startDelay = 417 - alphaOutAnimator.duration = 1116 - alphaOutAnimator.addUpdateListener { alphaOutAnimator -> - rippleShader.color = ColorUtils.setAlphaComponent(rippleShader.color, - alphaOutAnimator.animatedValue as Int) - invalidate() + val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply { + interpolator = PathInterpolator(0.4f, 0f, 0f, 1f) + duration = RIPPLE_ANIMATION_DURATION + addUpdateListener { animator -> + val now = animator.currentPlayTime + rippleShader.progress = animator.animatedValue as Float + rippleShader.time = now.toFloat() + + lightReveal?.revealAmount = animator.animatedValue as Float + invalidate() + } } - val animatorSet = AnimatorSet() - animatorSet.playTogether(animator, alphaInAnimator, alphaOutAnimator) - animatorSet.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { - onAnimationEnd?.run() - rippleInProgress = false - visibility = GONE + val revealAnimator = ValueAnimator.ofFloat(0f, 1f).apply { + interpolator = rippleAnimator.interpolator + startDelay = 10 + duration = rippleAnimator.duration + addUpdateListener { animator -> + lightReveal?.revealAmount = animator.animatedValue as Float } - }) + } + + val alphaInAnimator = ValueAnimator.ofInt(0, 127).apply { + duration = 167 + addUpdateListener { animator -> + rippleShader.color = ColorUtils.setAlphaComponent( + rippleShader.color, + animator.animatedValue as Int + ) + invalidate() + } + } + + val alphaOutAnimator = ValueAnimator.ofInt(127, 0).apply { + startDelay = 417 + duration = 1116 + addUpdateListener { animator -> + rippleShader.color = ColorUtils.setAlphaComponent( + rippleShader.color, + animator.animatedValue as Int + ) + invalidate() + } + } + + val animatorSet = AnimatorSet().apply { + playTogether( + rippleAnimator, + revealAnimator, + alphaInAnimator, + alphaOutAnimator + ) + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?) { + rippleInProgress = true + visibility = VISIBLE + } + + override fun onAnimationEnd(animation: Animator?) { + onAnimationEnd?.run() + rippleInProgress = false + visibility = GONE + } + }) + } // TODO (b/185124905): custom haptic TBD // vibrate() animatorSet.start() - visibility = VISIBLE - rippleInProgress = true } fun setColor(color: Int) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt index 6a5f001ac2eed..ec648ad519a38 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt @@ -93,10 +93,10 @@ class CircleReveal( val endRadius: Float ) : LightRevealEffect { override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) { - val interpolatedAmount = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(amount) - val fadeAmount = - LightRevealEffect.getPercentPastThreshold(interpolatedAmount, 0.75f) - val radius = startRadius + ((endRadius - startRadius) * interpolatedAmount) + // reveal amount updates already have an interpolator, so we intentionally use the + // non-interpolated amount + val fadeAmount = LightRevealEffect.getPercentPastThreshold(amount, 0.5f) + val radius = startRadius + ((endRadius - startRadius) * amount) scrim.revealGradientEndColorAlpha = 1f - fadeAmount scrim.setRevealGradientBounds( centerX - radius /* left */, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 5ee5e489479df..59e5ff53b012b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -21,7 +21,6 @@ import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowType; import static android.app.StatusBarManager.WindowVisibleState; import static android.app.StatusBarManager.windowStateToString; -import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static android.view.InsetsState.ITYPE_STATUS_BAR; import static android.view.InsetsState.containsType; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; @@ -46,8 +45,6 @@ import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING; import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; import static com.android.wm.shell.bubbles.BubbleController.TASKBAR_CHANGED_BROADCAST; -import android.animation.ValueAnimator; -import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; @@ -121,6 +118,7 @@ import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.widget.DateTimeView; +import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; @@ -402,8 +400,6 @@ public class StatusBar extends SystemUI implements DemoMode, private LightRevealScrim mLightRevealScrim; private WiredChargingRippleController mChargingRippleAnimationController; private PowerButtonReveal mPowerButtonReveal; - private CircleReveal mCircleReveal; - private ValueAnimator mCircleRevealAnimator = ValueAnimator.ofFloat(0f, 1f); private final Object mQueueLock = new Object(); @@ -2803,11 +2799,11 @@ public class StatusBar extends SystemUI implements DemoMode, return mDisplayMetrics.density; } - float getDisplayWidth() { + public float getDisplayWidth() { return mDisplayMetrics.widthPixels; } - float getDisplayHeight() { + public float getDisplayHeight() { return mDisplayMetrics.heightPixels; } @@ -3513,9 +3509,6 @@ public class StatusBar extends SystemUI implements DemoMode, public void fadeKeyguardWhilePulsing() { mNotificationPanelViewController.fadeOut(0, FADE_KEYGUARD_DURATION_PULSING, ()-> { - if (shouldShowCircleReveal()) { - startCircleReveal(); - } hideKeyguard(); mStatusBarKeyguardViewManager.onKeyguardFadedAway(); }).start(); @@ -3856,7 +3849,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onDozeAmountChanged(float linear, float eased) { if (mFeatureFlags.useNewLockscreenAnimations() - && !mCircleRevealAnimator.isRunning()) { + && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { mLightRevealScrim.setRevealAmount(1f - linear); } } @@ -3879,7 +3872,7 @@ public class StatusBar extends SystemUI implements DemoMode, || (!isDozing && mWakefulnessLifecycle.getLastWakeReason() == PowerManager.WAKE_REASON_POWER_BUTTON)) { mLightRevealScrim.setRevealEffect(mPowerButtonReveal); - } else if (!mCircleRevealAnimator.isRunning()) { + } else if (!(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE); } @@ -3891,36 +3884,8 @@ public class StatusBar extends SystemUI implements DemoMode, Trace.endSection(); } - /** - * Update the parameters for the dozing circle reveal that animates when the user authenticates - * from AOD using the fingerprint sensor. - */ - public void updateCircleReveal() { - final PointF fpLocation = mAuthRippleController.getFingerprintSensorLocation(); - if (fpLocation != null) { - mCircleReveal = - new CircleReveal( - fpLocation.x, - fpLocation.y, - 0, - Math.max(Math.max(fpLocation.x, getDisplayWidth() - fpLocation.x), - Math.max(fpLocation.y, getDisplayHeight() - fpLocation.y))); - } - } - - private void startCircleReveal() { - mLightRevealScrim.setRevealEffect(mCircleReveal); - mCircleRevealAnimator.cancel(); - mCircleRevealAnimator.addUpdateListener(animation -> - mLightRevealScrim.setRevealAmount( - (float) mCircleRevealAnimator.getAnimatedValue())); - mCircleRevealAnimator.setDuration(900); - mCircleRevealAnimator.start(); - } - - private boolean shouldShowCircleReveal() { - return mCircleReveal != null && !mCircleRevealAnimator.isRunning() - && mBiometricUnlockController.getBiometricType() == FINGERPRINT; + public LightRevealScrim getLightRevealScrim() { + return mLightRevealScrim; } private void updateKeyguardState() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index 240fdf3a4e17a..d87a26b096fd9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -25,6 +25,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.policy.ConfigurationController @@ -53,6 +54,7 @@ class AuthRippleControllerTest : SysuiTestCase() { @Mock private lateinit var authController: AuthController @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController @Mock private lateinit var bypassController: KeyguardBypassController + @Mock private lateinit var biometricUnlockController: BiometricUnlockController @Before fun setUp() { @@ -66,6 +68,7 @@ class AuthRippleControllerTest : SysuiTestCase() { commandRegistry, notificationShadeWindowController, bypassController, + biometricUnlockController, rippleView ) controller.init() @@ -90,7 +93,7 @@ class AuthRippleControllerTest : SysuiTestCase() { // THEN update sensor location and show ripple verify(rippleView).setSensorLocation(fpsLocation) - verify(rippleView).startRipple(any()) + verify(rippleView).startRipple(any(), any()) } @Test @@ -111,7 +114,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN no ripple - verify(rippleView, never()).startRipple(any()) + verify(rippleView, never()).startRipple(any(), any()) } @Test @@ -132,7 +135,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN no ripple - verify(rippleView, never()).startRipple(any()) + verify(rippleView, never()).startRipple(any(), any()) } @Test @@ -156,7 +159,7 @@ class AuthRippleControllerTest : SysuiTestCase() { // THEN show ripple verify(rippleView).setSensorLocation(faceLocation) - verify(rippleView).startRipple(any()) + verify(rippleView).startRipple(any(), any()) } @Test @@ -176,7 +179,7 @@ class AuthRippleControllerTest : SysuiTestCase() { false /* isStrongBiometric */) // THEN no ripple - verify(rippleView, never()).startRipple(any()) + verify(rippleView, never()).startRipple(any(), any()) } @Test @@ -191,7 +194,7 @@ class AuthRippleControllerTest : SysuiTestCase() { 0 /* userId */, BiometricSourceType.FACE /* type */, false /* isStrongBiometric */) - verify(rippleView, never()).startRipple(any()) + verify(rippleView, never()).startRipple(any(), any()) } @Test @@ -206,7 +209,7 @@ class AuthRippleControllerTest : SysuiTestCase() { 0 /* userId */, BiometricSourceType.FINGERPRINT /* type */, false /* isStrongBiometric */) - verify(rippleView, never()).startRipple(any()) + verify(rippleView, never()).startRipple(any(), any()) } @Test