Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 28a01ca4 authored by Beverly's avatar Beverly
Browse files

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
parent c45de709
Loading
Loading
Loading
Loading
+42 −4
Original line number Diff line number Diff line
@@ -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<AuthRippleView>(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 {
        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() {
+62 −33
Original line number Diff line number Diff line
@@ -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 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()
            rippleShader.distortionStrength = 1 - rippleShader.progress

                lightReveal?.revealAmount = animator.animatedValue as Float
                invalidate()
            }
        val alphaInAnimator = ValueAnimator.ofInt(0, 127)
        alphaInAnimator.duration = 167
        alphaInAnimator.addUpdateListener { alphaInAnimator ->
            rippleShader.color = ColorUtils.setAlphaComponent(rippleShader.color,
                alphaInAnimator.animatedValue as Int)
        }

        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)
        alphaOutAnimator.startDelay = 417
        alphaOutAnimator.duration = 1116
        alphaOutAnimator.addUpdateListener { alphaOutAnimator ->
            rippleShader.color = ColorUtils.setAlphaComponent(rippleShader.color,
                alphaOutAnimator.animatedValue as Int)
        }

        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
                }

        val animatorSet = AnimatorSet()
        animatorSet.playTogether(animator, alphaInAnimator, alphaOutAnimator)
        animatorSet.addListener(object : AnimatorListenerAdapter() {
                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) {
+4 −4
Original line number Diff line number Diff line
@@ -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 */,
+7 −42
Original line number Diff line number Diff line
@@ -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() {
+10 −7
Original line number Diff line number Diff line
@@ -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