Merge "Coordinate circle reveal with authRipple" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
603ade2fcc
@@ -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 {
|
||||
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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -2808,11 +2804,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;
|
||||
}
|
||||
|
||||
@@ -3542,9 +3538,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();
|
||||
@@ -3885,7 +3878,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);
|
||||
}
|
||||
}
|
||||
@@ -3908,7 +3901,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);
|
||||
}
|
||||
|
||||
@@ -3920,36 +3913,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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user