diff options
author | Eric Arseneau <earseneau@google.com> | 2021-12-13 11:13:18 -0800 |
---|---|---|
committer | Eric Arseneau <earseneau@google.com> | 2021-12-13 12:53:06 -0800 |
commit | 48cbb14e1815430efd7ff5086e7a4c70e75d475f (patch) | |
tree | a752bdec4c37856853b1e12f02daeebee65ffe13 /packages/SystemUI/src | |
parent | 88daf9ce34ae13a85aa6d41d42d7f6d5f7d07c1a (diff) | |
parent | ea8a98f046c81acd5bdb72169a00ce133e4d27c5 (diff) |
Merge mpr-2021-11-05
Change-Id: If7c68a17857eee4194f28413b938d5647820162c
Diffstat (limited to 'packages/SystemUI/src')
39 files changed, 1725 insertions, 375 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index 622419a86bfc..5c34bebdaa4e 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -22,7 +22,6 @@ import android.graphics.PointF; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.view.Gravity; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; @@ -45,7 +44,7 @@ public class LockIconView extends FrameLayout implements Dumpable { private int mRadius; private ImageView mLockIcon; - private ImageView mUnlockBgView; + private ImageView mBgView; private int mLockIconColor; @@ -58,19 +57,19 @@ public class LockIconView extends FrameLayout implements Dumpable { public void onFinishInflate() { super.onFinishInflate(); mLockIcon = findViewById(R.id.lock_icon); - mUnlockBgView = findViewById(R.id.lock_icon_bg); + mBgView = findViewById(R.id.lock_icon_bg); } void updateColorAndBackgroundVisibility(boolean useBackground) { - if (useBackground) { + if (useBackground && mLockIcon.getDrawable() != null) { mLockIconColor = Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorPrimary); - mUnlockBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg)); - mUnlockBgView.setVisibility(View.VISIBLE); + mBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg)); + mBgView.setVisibility(View.VISIBLE); } else { mLockIconColor = Utils.getColorAttrDefaultColor(getContext(), R.attr.wallpaperTextColorAccent); - mUnlockBgView.setVisibility(View.GONE); + mBgView.setVisibility(View.GONE); } mLockIcon.setImageTintList(ColorStateList.valueOf(mLockIconColor)); @@ -78,9 +77,14 @@ public class LockIconView extends FrameLayout implements Dumpable { void setImageDrawable(Drawable drawable) { mLockIcon.setImageDrawable(drawable); + if (drawable == null) { + mBgView.setVisibility(View.INVISIBLE); + } else { + mBgView.setVisibility(View.VISIBLE); + } } - void setCenterLocation(@NonNull PointF center, int radius) { + public void setCenterLocation(@NonNull PointF center, int radius) { mLockIconCenter = center; mRadius = radius; @@ -91,13 +95,11 @@ public class LockIconView extends FrameLayout implements Dumpable { mLockIconCenter.x + mRadius, mLockIconCenter.y + mRadius); - setX(mSensorRect.left); - setY(mSensorRect.top); - - final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( - (int) (mSensorRect.right - mSensorRect.left), - (int) (mSensorRect.bottom - mSensorRect.top)); - lp.gravity = Gravity.CENTER; + final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); + lp.width = (int) (mSensorRect.right - mSensorRect.left); + lp.height = (int) (mSensorRect.bottom - mSensorRect.top); + lp.topMargin = (int) mSensorRect.top; + lp.setMarginStart((int) mSensorRect.left); setLayoutParams(lp); } @@ -114,5 +116,6 @@ public class LockIconView extends FrameLayout implements Dumpable { public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { pw.println("Center in px (x, y)= (" + mLockIconCenter.x + ", " + mLockIconCenter.y + ")"); pw.println("Radius in pixels: " + mRadius); + pw.println("topLeft= (" + getX() + ", " + getY() + ")"); } } diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 509ac8a6d9fe..a41997ce3107 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -19,6 +19,8 @@ package com.android.keyguard; import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static com.android.systemui.classifier.Classifier.LOCK_ICON; +import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; +import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset; import android.content.Context; import android.content.res.Configuration; @@ -32,6 +34,7 @@ import android.media.AudioAttributes; import android.os.Process; import android.os.Vibrator; import android.util.DisplayMetrics; +import android.util.MathUtils; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; @@ -58,6 +61,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ViewController; import com.android.systemui.util.concurrency.DelayableExecutor; +import com.airbnb.lottie.LottieAnimationView; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Objects; @@ -92,6 +97,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @NonNull private final DelayableExecutor mExecutor; private boolean mUdfpsEnrolled; + @NonNull private LottieAnimationView mAodFp; + @NonNull private final AnimatedVectorDrawable mFpToUnlockIcon; @NonNull private final AnimatedVectorDrawable mLockToUnlockIcon; @NonNull private final Drawable mLockIcon; @@ -109,6 +116,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private boolean mIsKeyguardShowing; private boolean mUserUnlockedWithBiometric; private Runnable mCancelDelayedUpdateVisibilityRunnable; + private Runnable mOnGestureDetectedRunnable; private boolean mUdfpsSupported; private float mHeightPixels; @@ -118,6 +126,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private boolean mShowUnlockIcon; private boolean mShowLockIcon; + // for udfps when strong auth is required or unlocked on AOD + private boolean mShowAODFpIcon; + private final int mMaxBurnInOffsetX; + private final int mMaxBurnInOffsetY; + private float mInterpolatedDarkAmount; + private boolean mDownDetected; private boolean mDetectedLongPress; private final Rect mSensorTouchLocation = new Rect(); @@ -150,6 +164,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mVibrator = vibrator; final Context context = view.getContext(); + mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp); + mMaxBurnInOffsetX = context.getResources() + .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); + mMaxBurnInOffsetY = context.getResources() + .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); + mUnlockIcon = mView.getContext().getResources().getDrawable( R.drawable.ic_unlock, mView.getContext().getTheme()); @@ -173,15 +193,14 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Override protected void onViewAttached() { - // we check this here instead of onInit since the FingerprintManager + FaceManager may not - // have started up yet onInit - mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; - + updateIsUdfpsEnrolled(); updateConfiguration(); updateKeyguardShowing(); mUserUnlockedWithBiometric = false; + mIsBouncerShowing = mKeyguardViewController.isBouncerShowing(); mIsDozing = mStatusBarStateController.isDozing(); + mInterpolatedDarkAmount = mStatusBarStateController.getDozeAmount(); mRunningFPS = mKeyguardUpdateMonitor.isFingerprintDetectionRunning(); mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen(); mStatusBarState = mStatusBarStateController.getState(); @@ -189,15 +208,18 @@ public class LockIconViewController extends ViewController<LockIconView> impleme updateColors(); mConfigurationController.addCallback(mConfigurationListener); + mAuthController.addCallback(mAuthControllerCallback); mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); mStatusBarStateController.addCallback(mStatusBarStateListener); mKeyguardStateController.addCallback(mKeyguardStateCallback); mDownDetected = false; + updateBurnInOffsets(); updateVisibility(); } @Override protected void onViewDetached() { + mAuthController.removeCallback(mAuthControllerCallback); mConfigurationController.removeCallback(mConfigurationListener); mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); mStatusBarStateController.removeCallback(mStatusBarStateListener); @@ -227,7 +249,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mCancelDelayedUpdateVisibilityRunnable = null; } - if (!mIsKeyguardShowing) { + if (!mIsKeyguardShowing && !mIsDozing) { mView.setVisibility(View.INVISIBLE); return; } @@ -238,6 +260,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen() && (!mUdfpsEnrolled || !mRunningFPS); mShowUnlockIcon = mCanDismissLockScreen && isLockScreen(); + mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS; final CharSequence prevContentDescription = mView.getContentDescription(); if (mShowLockIcon) { @@ -260,10 +283,22 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } mView.setVisibility(View.VISIBLE); mView.setContentDescription(mUnlockedLabel); + } else if (mShowAODFpIcon) { + mView.setImageDrawable(null); + mView.setContentDescription(null); + mAodFp.setVisibility(View.VISIBLE); + mAodFp.setContentDescription(mCanDismissLockScreen ? mUnlockedLabel : mLockedLabel); + mView.setVisibility(View.VISIBLE); } else { mView.setVisibility(View.INVISIBLE); mView.setContentDescription(null); } + + if (!mShowAODFpIcon) { + mAodFp.setVisibility(View.INVISIBLE); + mAodFp.setContentDescription(null); + } + if (!Objects.equals(prevContentDescription, mView.getContentDescription()) && mView.getContentDescription() != null) { mView.announceForAccessibility(mView.getContentDescription()); @@ -340,10 +375,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("mUdfpsSupported: " + mUdfpsSupported); pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled); pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing); pw.println(" mShowUnlockIcon: " + mShowUnlockIcon); pw.println(" mShowLockIcon: " + mShowLockIcon); + pw.println(" mShowAODFpIcon: " + mShowAODFpIcon); pw.println(" mIsDozing: " + mIsDozing); pw.println(" mIsBouncerShowing: " + mIsBouncerShowing); pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric); @@ -351,17 +388,57 @@ public class LockIconViewController extends ViewController<LockIconView> impleme pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen); pw.println(" mStatusBarState: " + StatusBarState.toShortString(mStatusBarState)); pw.println(" mQsExpanded: " + mQsExpanded); + pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount); if (mView != null) { mView.dump(fd, pw, args); } } + /** Every minute, update the aod icon's burn in offset */ + public void dozeTimeTick() { + updateBurnInOffsets(); + } + + private void updateBurnInOffsets() { + float offsetX = MathUtils.lerp(0f, + getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */) + - mMaxBurnInOffsetX, mInterpolatedDarkAmount); + float offsetY = MathUtils.lerp(0f, + getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */) + - mMaxBurnInOffsetY, mInterpolatedDarkAmount); + float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount); + + mAodFp.setTranslationX(offsetX); + mAodFp.setTranslationY(offsetY); + mAodFp.setProgress(progress); + mAodFp.setAlpha(255 * mInterpolatedDarkAmount); + } + + private void updateIsUdfpsEnrolled() { + boolean wasUdfpsSupported = mUdfpsSupported; + boolean wasUdfpsEnrolled = mUdfpsEnrolled; + + mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; + mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled(); + if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) { + updateVisibility(); + } + } + private StatusBarStateController.StateListener mStatusBarStateListener = new StatusBarStateController.StateListener() { @Override + public void onDozeAmountChanged(float linear, float eased) { + mInterpolatedDarkAmount = eased; + updateBurnInOffsets(); + } + + @Override public void onDozingChanged(boolean isDozing) { mIsDozing = isDozing; + updateBurnInOffsets(); + updateIsUdfpsEnrolled(); updateVisibility(); } @@ -435,7 +512,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mKeyguardUpdateMonitor.getUserUnlockedWithBiometric( KeyguardUpdateMonitor.getCurrentUser()); } - mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled(); + updateIsUdfpsEnrolled(); updateVisibility(); } @@ -481,8 +558,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme // intercept all following touches until we see MotionEvent.ACTION_CANCEL UP or // MotionEvent.ACTION_UP (see #onTouchEvent) - mDownDetected = true; - if (mVibrator != null) { + if (mVibrator != null && !mDownDetected) { mVibrator.vibrate( Process.myUid(), getContext().getOpPackageName(), @@ -490,6 +566,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme "lockIcon-onDown", VIBRATION_SONIFICATION_ATTRIBUTES); } + + mDownDetected = true; return true; } @@ -497,8 +575,10 @@ public class LockIconViewController extends ViewController<LockIconView> impleme if (!wasClickableOnDownEvent()) { return; } + mDetectedLongPress = true; - if (mVibrator != null) { + if (onAffordanceClick() && mVibrator != null) { + // only vibrate if the click went through and wasn't intercepted by falsing mVibrator.vibrate( Process.myUid(), getContext().getOpPackageName(), @@ -506,8 +586,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme "lockIcon-onLongPress", VIBRATION_SONIFICATION_ATTRIBUTES); } - mDetectedLongPress = true; - onAffordanceClick(); } public boolean onSingleTapUp(MotionEvent e) { @@ -531,15 +609,24 @@ public class LockIconViewController extends ViewController<LockIconView> impleme return mDownDetected; } - private void onAffordanceClick() { + /** + * Whether we tried to launch the affordance. + * + * If falsing intercepts the click, returns false. + */ + private boolean onAffordanceClick() { if (mFalsingManager.isFalseTouch(LOCK_ICON)) { - return; + return false; } // pre-emptively set to true to hide view mIsBouncerShowing = true; updateVisibility(); + if (mOnGestureDetectedRunnable != null) { + mOnGestureDetectedRunnable.run(); + } mKeyguardViewController.showBouncer(/* scrim */ true); + return true; } }); @@ -548,16 +635,18 @@ public class LockIconViewController extends ViewController<LockIconView> impleme * in a 'clickable' state * @return whether to intercept the touch event */ - public boolean onTouchEvent(MotionEvent event) { + public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) { if (mSensorTouchLocation.contains((int) event.getX(), (int) event.getY()) - && mView.getVisibility() == View.VISIBLE) { + && (mView.getVisibility() == View.VISIBLE + || mAodFp.getVisibility() == View.VISIBLE)) { + mOnGestureDetectedRunnable = onGestureDetectedRunnable; mGestureDetector.onTouchEvent(event); } // we continue to intercept all following touches until we see MotionEvent.ACTION_CANCEL UP // or MotionEvent.ACTION_UP. this is to avoid passing the touch to NPV // after the lock icon disappears on device entry - if (mDownDetected && mDetectedLongPress) { + if (mDownDetected) { if (event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP) { mDownDetected = false; @@ -577,4 +666,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme public void setAlpha(float alpha) { mView.setAlpha(alpha); } + + private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { + @Override + public void onAllAuthenticatorsRegistered() { + updateIsUdfpsEnrolled(); + updateConfiguration(); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 0ce1846e7745..a4123c769d1e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -786,7 +786,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, .build(sensorIds, credentialAllowed, mFpProps, mFaceProps); } - interface Callback { + public interface Callback { /** * Called when authenticators are registered. If authenticators are already * registered before this call, this callback will never be triggered. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 1df8ad5e51fb..2630f119de00 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -16,14 +16,19 @@ package com.android.systemui.biometrics +import android.animation.ValueAnimator import android.content.Context import android.content.res.Configuration import android.graphics.PointF import android.hardware.biometrics.BiometricSourceType +import android.util.Log import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.settingslib.Utils +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.LightRevealEffect import com.android.systemui.statusbar.NotificationShadeWindowController @@ -34,9 +39,14 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.StatusBar import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.ViewController import java.io.PrintWriter import javax.inject.Inject +import javax.inject.Provider +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. @@ -49,37 +59,65 @@ class AuthRippleController @Inject constructor( private val authController: AuthController, private val configurationController: ConfigurationController, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val keyguardStateController: KeyguardStateController, + private val wakefulnessLifecycle: WakefulnessLifecycle, private val commandRegistry: CommandRegistry, private val notificationShadeWindowController: NotificationShadeWindowController, private val bypassController: KeyguardBypassController, private val biometricUnlockController: BiometricUnlockController, + private val udfpsControllerProvider: Provider<UdfpsController>, + private val statusBarStateController: StatusBarStateController, rippleView: AuthRippleView? -) : ViewController<AuthRippleView>(rippleView) { +) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback, + WakefulnessLifecycle.Observer { + + @VisibleForTesting + internal var startLightRevealScrimOnKeyguardFadingAway = false var fingerprintSensorLocation: PointF? = null private var faceSensorLocation: PointF? = null private var circleReveal: LightRevealEffect? = null + private var udfpsController: UdfpsController? = null + + private var dwellScale = 2f + private var expandedDwellScale = 2.5f + private var aodDwellScale = 1.9f + private var aodExpandedDwellScale = 2.3f + private var udfpsRadius: Float = -1f + + override fun onInit() { + mView.setAlphaInDuration(sysuiContext.resources.getInteger( + R.integer.auth_ripple_alpha_in_duration).toLong()) + } + @VisibleForTesting public override fun onViewAttached() { + authController.addCallback(authControllerCallback) updateRippleColor() updateSensorLocation() - authController.addCallback(authControllerCallback) + updateUdfpsDependentParams() + udfpsController?.addCallback(udfpsControllerCallback) configurationController.addCallback(configurationChangedListener) keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) + keyguardStateController.addCallback(this) + wakefulnessLifecycle.addObserver(this) commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() } } @VisibleForTesting public override fun onViewDetached() { + udfpsController?.removeCallback(udfpsControllerCallback) authController.removeCallback(authControllerCallback) keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) configurationController.removeCallback(configurationChangedListener) + keyguardStateController.removeCallback(this) + wakefulnessLifecycle.removeObserver(this) commandRegistry.unregisterCommand("auth-ripple") notificationShadeWindowController.setForcePluginOpen(false, this) } - private fun showRipple(biometricSourceType: BiometricSourceType?) { + fun showRipple(biometricSourceType: BiometricSourceType?) { if (!keyguardUpdateMonitor.isKeyguardVisible || keyguardUpdateMonitor.userNeedsStrongAuth()) { return @@ -88,43 +126,61 @@ class AuthRippleController @Inject constructor( if (biometricSourceType == BiometricSourceType.FINGERPRINT && fingerprintSensorLocation != null) { mView.setSensorLocation(fingerprintSensorLocation!!) - showRipple() + showUnlockedRipple() } else if (biometricSourceType == BiometricSourceType.FACE && faceSensorLocation != null) { if (!bypassController.canBypass()) { return } mView.setSensorLocation(faceSensorLocation!!) - showRipple() + showUnlockedRipple() } } - private fun showRipple() { + private fun showUnlockedRipple() { notificationShadeWindowController.setForcePluginOpen(true, 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 useCircleReveal = circleReveal != null && biometricUnlockController.isWakeAndUnlock val lightRevealScrim = statusBar.lightRevealScrim if (useCircleReveal) { lightRevealScrim?.revealEffect = circleReveal!! + startLightRevealScrimOnKeyguardFadingAway = true } - mView.startRipple( + mView.startUnlockedRipple( /* end runnable */ Runnable { 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() { fingerprintSensorLocation = authController.fingerprintSensorLocation faceSensorLocation = authController.faceAuthSensorLocation @@ -146,7 +202,23 @@ class AuthRippleController @Inject constructor( Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor) } - val keyguardUpdateMonitorCallback = + private fun showDwellRipple() { + if (statusBarStateController.isDozing) { + mView.startDwellRipple( + /* startRadius */ udfpsRadius, + /* endRadius */ udfpsRadius * aodDwellScale, + /* expandedRadius */ udfpsRadius * aodExpandedDwellScale, + /* isDozing */ true) + } else { + mView.startDwellRipple( + /* startRadius */ udfpsRadius, + /* endRadius */ udfpsRadius * dwellScale, + /* expandedRadius */ udfpsRadius * expandedDwellScale, + /* isDozing */ false) + } + } + + private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() { override fun onBiometricAuthenticated( userId: Int, @@ -155,9 +227,13 @@ class AuthRippleController @Inject constructor( ) { showRipple(biometricSourceType) } + + override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) { + mView.retractRipple() + } } - val configurationChangedListener = + private val configurationChangedListener = object : ConfigurationController.ConfigurationListener { override fun onConfigChanged(newConfig: Configuration?) { updateSensorLocation() @@ -173,14 +249,70 @@ class AuthRippleController @Inject constructor( } } - private val authControllerCallback = AuthController.Callback { updateSensorLocation() } + private val udfpsControllerCallback = + object : UdfpsController.Callback { + override fun onFingerDown() { + if (fingerprintSensorLocation == null) { + Log.e("AuthRipple", "fingerprintSensorLocation=null onFingerDown. " + + "Skip showing dwell ripple") + return + } + + mView.setSensorLocation(fingerprintSensorLocation!!) + showDwellRipple() + } + + override fun onFingerUp() { + mView.retractRipple() + } + } + + private val authControllerCallback = AuthController.Callback { + updateSensorLocation() + updateUdfpsDependentParams() + } + + private fun updateUdfpsDependentParams() { + authController.udfpsProps?.let { + if (it.size > 0) { + udfpsRadius = it[0].sensorRadius.toFloat() + udfpsController = udfpsControllerProvider.get() + + if (mView.isAttachedToWindow) { + udfpsController?.addCallback(udfpsControllerCallback) + } + } + } + } inner class AuthRippleCommand : Command { + fun printLockScreenDwellInfo(pw: PrintWriter) { + pw.println("lock screen dwell ripple: " + + "\n\tsensorLocation=$fingerprintSensorLocation" + + "\n\tdwellScale=$dwellScale" + + "\n\tdwellExpand=$expandedDwellScale") + } + + fun printAodDwellInfo(pw: PrintWriter) { + pw.println("aod dwell ripple: " + + "\n\tsensorLocation=$fingerprintSensorLocation" + + "\n\tdwellScale=$aodDwellScale" + + "\n\tdwellExpand=$aodExpandedDwellScale") + } + override fun execute(pw: PrintWriter, args: List<String>) { if (args.isEmpty()) { invalidCommand(pw) } else { when (args[0]) { + "dwell" -> { + showDwellRipple() + if (statusBarStateController.isDozing) { + printAodDwellInfo(pw) + } else { + printLockScreenDwellInfo(pw) + } + } "fingerprint" -> { pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation") showRipple(BiometricSourceType.FINGERPRINT) @@ -199,7 +331,7 @@ class AuthRippleController @Inject constructor( pw.println("custom ripple sensorLocation=" + args[1].toFloat() + ", " + args[2].toFloat()) mView.setSensorLocation(PointF(args[1].toFloat(), args[2].toFloat())) - showRipple() + showUnlockedRipple() } else -> invalidCommand(pw) } @@ -209,6 +341,7 @@ class AuthRippleController @Inject constructor( override fun help(pw: PrintWriter) { pw.println("Usage: adb shell cmd statusbar auth-ripple <command>") pw.println("Available commands:") + pw.println(" dwell") pw.println(" fingerprint") pw.println(" face") pw.println(" custom <x-location: int> <y-location: int>") @@ -219,4 +352,8 @@ class AuthRippleController @Inject constructor( help(pw) } } + + companion object { + const val RIPPLE_ANIMATION_DURATION: Long = 1533 + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index 95ea81003ecb..c6d26ffb9957 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -27,21 +27,40 @@ import android.util.AttributeSet 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.animation.Interpolators import com.android.systemui.statusbar.charging.RippleShader -private const val RIPPLE_ANIMATION_DURATION: Long = 1533 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f /** - * Expanding ripple effect on the transition from biometric authentication success to showing + * Expanding ripple effect + * - startUnlockedRipple for the transition from biometric authentication success to showing * launcher. + * - startDwellRipple for the ripple expansion out when the user has their finger down on the UDFPS + * sensor area + * - retractRipple for the ripple animation inwards to signal a failure */ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { - private var rippleInProgress: Boolean = false + private val retractInterpolator = PathInterpolator(.05f, .93f, .1f, 1f) + + private val dwellPulseDuration = 50L + private val dwellAlphaDuration = dwellPulseDuration + private val dwellAlpha: Float = 1f + private val dwellExpandDuration = 1200L - dwellPulseDuration + + private val aodDwellPulseDuration = 50L + private var aodDwellAlphaDuration = aodDwellPulseDuration + private var aodDwellAlpha: Float = .8f + private var aodDwellExpandDuration = 1200L - aodDwellPulseDuration + + private val retractDuration = 400L + private var alphaInDuration: Long = 0 + private var unlockedRippleInProgress: Boolean = false private val rippleShader = RippleShader() private val ripplePaint = Paint() - private var radius: Float = 0.0f + private var retractAnimator: Animator? = null + private var dwellPulseOutAnimator: Animator? = null + private var radius: Float = 0f set(value) { rippleShader.radius = value field = value @@ -62,51 +81,200 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at fun setSensorLocation(location: PointF) { origin = location - radius = maxOf(location.x, location.y, width - location.x, height - location.y) - .toFloat() + radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat() } - fun startRipple(onAnimationEnd: Runnable?, lightReveal: LightRevealScrim?) { - if (rippleInProgress) { - return // Ignore if ripple effect is already playing + fun setAlphaInDuration(duration: Long) { + alphaInDuration = duration + } + + /** + * Animate ripple inwards back to radius 0 + */ + fun retractRipple() { + if (retractAnimator?.isRunning == true) { + return // let the animation finish + } + + if (dwellPulseOutAnimator?.isRunning == true) { + val retractRippleAnimator = ValueAnimator.ofFloat(rippleShader.progress, 0f) + .apply { + interpolator = retractInterpolator + duration = retractDuration + addUpdateListener { animator -> + val now = animator.currentPlayTime + rippleShader.progress = animator.animatedValue as Float + rippleShader.time = now.toFloat() + + invalidate() + } + } + + val retractAlphaAnimator = ValueAnimator.ofInt(255, 0).apply { + interpolator = Interpolators.LINEAR + duration = retractDuration + addUpdateListener { animator -> + rippleShader.color = ColorUtils.setAlphaComponent( + rippleShader.color, + animator.animatedValue as Int + ) + invalidate() + } + } + + retractAnimator = AnimatorSet().apply { + playTogether(retractRippleAnimator, retractAlphaAnimator) + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?) { + dwellPulseOutAnimator?.cancel() + rippleShader.shouldFadeOutRipple = false + visibility = VISIBLE + } + + override fun onAnimationEnd(animation: Animator?) { + visibility = GONE + resetRippleAlpha() + } + }) + start() + } + } + } + + /** + * Ripple that moves animates from an outer ripple ring of + * startRadius => endRadius => expandedRadius + */ + fun startDwellRipple( + startRadius: Float, + endRadius: Float, + expandedRadius: Float, + isDozing: Boolean + ) { + if (unlockedRippleInProgress || dwellPulseOutAnimator?.isRunning == true) { + return } - val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply { - interpolator = PathInterpolator(0.4f, 0f, 0f, 1f) - duration = RIPPLE_ANIMATION_DURATION + // we divide by 4 because the desired startRadius and endRadius is for the ripple's outer + // ring see RippleShader + val startDwellProgress = startRadius / radius / 4f + val endInitialDwellProgress = endRadius / radius / 4f + val endExpandDwellProgress = expandedRadius / radius / 4f + + val alpha = if (isDozing) aodDwellAlpha else dwellAlpha + val pulseOutEndAlpha = (255 * alpha).toInt() + val expandDwellEndAlpha = kotlin.math.min((255 * (alpha + .25f)).toInt(), 255) + val dwellPulseOutRippleAnimator = ValueAnimator.ofFloat(startDwellProgress, + endInitialDwellProgress).apply { + interpolator = Interpolators.LINEAR_OUT_SLOW_IN + duration = if (isDozing) aodDwellPulseDuration else dwellPulseDuration addUpdateListener { animator -> val now = animator.currentPlayTime rippleShader.progress = animator.animatedValue as Float rippleShader.time = now.toFloat() - lightReveal?.revealAmount = animator.animatedValue as Float invalidate() } } - val revealAnimator = ValueAnimator.ofFloat(0f, 1f).apply { - interpolator = rippleAnimator.interpolator - startDelay = 10 - duration = rippleAnimator.duration + val dwellPulseOutAlphaAnimator = ValueAnimator.ofInt(0, pulseOutEndAlpha).apply { + interpolator = Interpolators.LINEAR + duration = if (isDozing) aodDwellAlphaDuration else dwellAlphaDuration addUpdateListener { animator -> - lightReveal?.revealAmount = animator.animatedValue as Float + rippleShader.color = ColorUtils.setAlphaComponent( + rippleShader.color, + animator.animatedValue as Int + ) + invalidate() } } - val alphaInAnimator = ValueAnimator.ofInt(0, 127).apply { - duration = 167 + // slowly animate outwards until we receive a call to retractRipple or startUnlockedRipple + val expandDwellRippleAnimator = ValueAnimator.ofFloat(endInitialDwellProgress, + endExpandDwellProgress).apply { + interpolator = Interpolators.LINEAR_OUT_SLOW_IN + duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration + addUpdateListener { animator -> + val now = animator.currentPlayTime + rippleShader.progress = animator.animatedValue as Float + rippleShader.time = now.toFloat() + + invalidate() + } + } + + val expandDwellAlphaAnimator = ValueAnimator.ofInt(pulseOutEndAlpha, expandDwellEndAlpha) + .apply { + interpolator = Interpolators.LINEAR + duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration addUpdateListener { animator -> rippleShader.color = ColorUtils.setAlphaComponent( - rippleShader.color, - animator.animatedValue as Int + rippleShader.color, + animator.animatedValue as Int ) invalidate() } } - val alphaOutAnimator = ValueAnimator.ofInt(127, 0).apply { - startDelay = 417 - duration = 1116 + val initialDwellPulseOutAnimator = AnimatorSet().apply { + playTogether(dwellPulseOutRippleAnimator, dwellPulseOutAlphaAnimator) + } + val expandDwellAnimator = AnimatorSet().apply { + playTogether(expandDwellRippleAnimator, expandDwellAlphaAnimator) + } + + dwellPulseOutAnimator = AnimatorSet().apply { + playSequentially( + initialDwellPulseOutAnimator, + expandDwellAnimator + ) + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?) { + retractAnimator?.cancel() + rippleShader.shouldFadeOutRipple = false + visibility = VISIBLE + } + + override fun onAnimationEnd(animation: Animator?) { + visibility = GONE + resetRippleAlpha() + } + }) + start() + } + } + + /** + * Ripple that bursts outwards from the position of the sensor to the edges of the screen + */ + fun startUnlockedRipple(onAnimationEnd: Runnable?) { + if (unlockedRippleInProgress) { + return // Ignore if ripple effect is already playing + } + + var rippleStart = 0f + var alphaDuration = alphaInDuration + if (dwellPulseOutAnimator?.isRunning == true || retractAnimator?.isRunning == true) { + rippleStart = rippleShader.progress + alphaDuration = 0 + dwellPulseOutAnimator?.cancel() + retractAnimator?.cancel() + } + + val rippleAnimator = ValueAnimator.ofFloat(rippleStart, 1f).apply { + interpolator = Interpolators.LINEAR_OUT_SLOW_IN + duration = AuthRippleController.RIPPLE_ANIMATION_DURATION + addUpdateListener { animator -> + val now = animator.currentPlayTime + rippleShader.progress = animator.animatedValue as Float + rippleShader.time = now.toFloat() + + invalidate() + } + } + + val alphaInAnimator = ValueAnimator.ofInt(0, 255).apply { + duration = alphaDuration addUpdateListener { animator -> rippleShader.color = ColorUtils.setAlphaComponent( rippleShader.color, @@ -119,19 +287,18 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at val animatorSet = AnimatorSet().apply { playTogether( rippleAnimator, - revealAnimator, - alphaInAnimator, - alphaOutAnimator + alphaInAnimator ) addListener(object : AnimatorListenerAdapter() { override fun onAnimationStart(animation: Animator?) { - rippleInProgress = true + unlockedRippleInProgress = true + rippleShader.shouldFadeOutRipple = true visibility = VISIBLE } override fun onAnimationEnd(animation: Animator?) { onAnimationEnd?.run() - rippleInProgress = false + unlockedRippleInProgress = false visibility = GONE } }) @@ -139,8 +306,16 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at animatorSet.start() } + fun resetRippleAlpha() { + rippleShader.color = ColorUtils.setAlphaComponent( + rippleShader.color, + 255 + ) + } + fun setColor(color: Int) { rippleShader.color = color + resetRippleAlpha() } override fun onDraw(canvas: Canvas?) { @@ -148,7 +323,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at // the active effect area. Values here should be kept in sync with the // animation implementation in the ripple shader. val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) * - (1 - rippleShader.progress)) * radius * 1.5f + (1 - rippleShader.progress)) * radius * 2f canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint) } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index e612fb4712fc..54f932184331 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -77,7 +77,9 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; import javax.inject.Inject; @@ -157,6 +159,7 @@ public class UdfpsController implements DozeReceiver { private Runnable mAodInterruptRunnable; private boolean mOnFingerDown; private boolean mAttemptedToDismissKeyguard; + private Set<Callback> mCallbacks = new HashSet<>(); @VisibleForTesting public static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES = @@ -210,12 +213,14 @@ public class UdfpsController implements DozeReceiver { } void onAcquiredGood() { + Log.d(TAG, "onAcquiredGood"); if (mEnrollHelper != null) { mEnrollHelper.animateIfLastStep(); } } void onEnrollmentHelp() { + Log.d(TAG, "onEnrollmentHelp"); if (mEnrollHelper != null) { mEnrollHelper.onEnrollmentHelp(); } @@ -759,6 +764,7 @@ public class UdfpsController implements DozeReceiver { UdfpsEnrollView enrollView = (UdfpsEnrollView) mInflater.inflate( R.layout.udfps_enroll_view, null); mView.addView(enrollView); + enrollView.updateSensorLocation(mSensorProps); return new UdfpsEnrollViewController( enrollView, mServerRequest.mEnrollHelper, @@ -781,6 +787,7 @@ public class UdfpsController implements DozeReceiver { mKeyguardViewMediator, mLockscreenShadeTransitionController, mConfigurationController, + mKeyguardStateController, this ); case IUdfpsOverlayController.REASON_AUTH_BP: @@ -841,6 +848,10 @@ public class UdfpsController implements DozeReceiver { return; } + if (!mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { + return; + } + mAodInterruptRunnable = () -> { mIsAodInterruptActive = true; // Since the sensor that triggers the AOD interrupt doesn't provide @@ -860,6 +871,20 @@ public class UdfpsController implements DozeReceiver { } /** + * Add a callback for fingerUp and fingerDown events + */ + public void addCallback(Callback cb) { + mCallbacks.add(cb); + } + + /** + * Remove callback + */ + public void removeCallback(Callback cb) { + mCallbacks.remove(cb); + } + + /** * Cancel updfs scan affordances - ability to hide the HbmSurfaceView (white circle) before * user explicitly lifts their finger. Generally, this should be called whenever udfps fails * or errors. @@ -913,6 +938,10 @@ public class UdfpsController implements DozeReceiver { mFingerprintManager.onUiReady(mSensorProps.sensorId); Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0); }); + + for (Callback cb : mCallbacks) { + cb.onFingerDown(); + } } private void onFingerUp() { @@ -925,6 +954,9 @@ public class UdfpsController implements DozeReceiver { } if (mOnFingerDown) { mFingerprintManager.onPointerUp(mSensorProps.sensorId); + for (Callback cb : mCallbacks) { + cb.onFingerUp(); + } } mOnFingerDown = false; if (mView.isIlluminationRequested()) { @@ -945,4 +977,19 @@ public class UdfpsController implements DozeReceiver { mView.setOnTouchListener(mOnTouchListener); } } + + /** + * Callback for fingerUp and fingerDown events. + */ + public interface Callback { + /** + * Called onFingerUp events. Will only be called if the finger was previously down. + */ + void onFingerUp(); + + /** + * Called onFingerDown events. + */ + void onFingerDown(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java index ea69b1d626ae..2034ff35be70 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java @@ -16,9 +16,11 @@ package com.android.systemui.biometrics; +import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.content.Context; +import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; @@ -26,11 +28,17 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Looper; +import android.util.TypedValue; import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.LinearInterpolator; +import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.graphics.ColorUtils; import com.android.systemui.R; /** @@ -39,11 +47,20 @@ import com.android.systemui.R; public class UdfpsEnrollDrawable extends UdfpsDrawable { private static final String TAG = "UdfpsAnimationEnroll"; - private static final long ANIM_DURATION = 800; + private static final long HINT_COLOR_ANIM_DELAY_MS = 233L; + private static final long HINT_COLOR_ANIM_DURATION_MS = 517L; + private static final long HINT_WIDTH_ANIM_DURATION_MS = 233L; + private static final long TARGET_ANIM_DURATION_LONG = 800L; + private static final long TARGET_ANIM_DURATION_SHORT = 600L; // 1 + SCALE_MAX is the maximum that the moving target will animate to private static final float SCALE_MAX = 0.25f; - @NonNull private final UdfpsEnrollProgressBarDrawable mProgressDrawable; + private static final float HINT_PADDING_DP = 10f; + private static final float HINT_MAX_WIDTH_DP = 6f; + private static final float HINT_ANGLE = 40f; + + private final Handler mHandler = new Handler(Looper.getMainLooper()); + @NonNull private final Drawable mMovingTargetFpIcon; @NonNull private final Paint mSensorOutlinePaint; @NonNull private final Paint mBlueFill; @@ -52,18 +69,41 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { @Nullable private UdfpsEnrollHelper mEnrollHelper; // Moving target animator set - @Nullable AnimatorSet mAnimatorSet; + @Nullable AnimatorSet mTargetAnimatorSet; // Moving target location float mCurrentX; float mCurrentY; // Moving target size float mCurrentScale = 1.f; + @ColorInt private final int mHintColorFaded; + @ColorInt private final int mHintColorHighlight; + private final float mHintMaxWidthPx; + private final float mHintPaddingPx; + + @NonNull private final Animator.AnimatorListener mTargetAnimListener; + + private boolean mShouldShowTipHint = false; + @NonNull private final Paint mTipHintPaint; + @Nullable private AnimatorSet mTipHintAnimatorSet; + @Nullable private ValueAnimator mTipHintColorAnimator; + @Nullable private ValueAnimator mTipHintWidthAnimator; + @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintColorUpdateListener; + @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintWidthUpdateListener; + @NonNull private final Animator.AnimatorListener mTipHintPulseListener; + + private boolean mShouldShowEdgeHint = false; + @NonNull private final Paint mEdgeHintPaint; + @Nullable private AnimatorSet mEdgeHintAnimatorSet; + @Nullable private ValueAnimator mEdgeHintColorAnimator; + @Nullable private ValueAnimator mEdgeHintWidthAnimator; + @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintColorUpdateListener; + @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintWidthUpdateListener; + @NonNull private final Animator.AnimatorListener mEdgeHintPulseListener; + UdfpsEnrollDrawable(@NonNull Context context) { super(context); - mProgressDrawable = new UdfpsEnrollProgressBarDrawable(context, this); - mSensorOutlinePaint = new Paint(0 /* flags */); mSensorOutlinePaint.setAntiAlias(true); mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_enroll_icon)); @@ -80,6 +120,117 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { mMovingTargetFpIcon.mutate(); mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon)); + + mHintColorFaded = getHintColorFaded(context); + mHintColorHighlight = context.getColor(R.color.udfps_enroll_progress); + mHintMaxWidthPx = Utils.dpToPixels(context, HINT_MAX_WIDTH_DP); + mHintPaddingPx = Utils.dpToPixels(context, HINT_PADDING_DP); + + mTargetAnimListener = new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + updateTipHintVisibility(); + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }; + + mTipHintPaint = new Paint(0 /* flags */); + mTipHintPaint.setAntiAlias(true); + mTipHintPaint.setColor(mHintColorFaded); + mTipHintPaint.setStyle(Paint.Style.STROKE); + mTipHintPaint.setStrokeCap(Paint.Cap.ROUND); + mTipHintPaint.setStrokeWidth(0f); + mTipHintColorUpdateListener = animation -> { + mTipHintPaint.setColor((int) animation.getAnimatedValue()); + invalidateSelf(); + }; + mTipHintWidthUpdateListener = animation -> { + mTipHintPaint.setStrokeWidth((float) animation.getAnimatedValue()); + invalidateSelf(); + }; + mTipHintPulseListener = new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + mHandler.postDelayed(() -> { + mTipHintColorAnimator = + ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorFaded); + mTipHintColorAnimator.setInterpolator(new LinearInterpolator()); + mTipHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS); + mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener); + mTipHintColorAnimator.start(); + }, HINT_COLOR_ANIM_DELAY_MS); + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }; + + mEdgeHintPaint = new Paint(0 /* flags */); + mEdgeHintPaint.setAntiAlias(true); + mEdgeHintPaint.setColor(mHintColorFaded); + mEdgeHintPaint.setStyle(Paint.Style.STROKE); + mEdgeHintPaint.setStrokeCap(Paint.Cap.ROUND); + mEdgeHintPaint.setStrokeWidth(0f); + mEdgeHintColorUpdateListener = animation -> { + mEdgeHintPaint.setColor((int) animation.getAnimatedValue()); + invalidateSelf(); + }; + mEdgeHintWidthUpdateListener = animation -> { + mEdgeHintPaint.setStrokeWidth((float) animation.getAnimatedValue()); + invalidateSelf(); + }; + mEdgeHintPulseListener = new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + mHandler.postDelayed(() -> { + mEdgeHintColorAnimator = + ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorFaded); + mEdgeHintColorAnimator.setInterpolator(new LinearInterpolator()); + mEdgeHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS); + mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener); + mEdgeHintColorAnimator.start(); + }, HINT_COLOR_ANIM_DELAY_MS); + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }; + } + + @ColorInt + private static int getHintColorFaded(@NonNull Context context) { + final TypedValue tv = new TypedValue(); + context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, tv, true); + final int alpha = (int) (tv.getFloat() * 255f); + + final int[] attrs = new int[] {android.R.attr.colorControlNormal}; + final TypedArray ta = context.obtainStyledAttributes(attrs); + try { + @ColorInt final int color = ta.getColor(0, context.getColor(R.color.white_disabled)); + return ColorUtils.setAlphaComponent(color, alpha); + } finally { + ta.recycle(); + } } void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) { @@ -100,59 +251,164 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { } void onEnrollmentProgress(int remaining, int totalSteps) { - mProgressDrawable.setEnrollmentProgress(remaining, totalSteps); + if (mEnrollHelper == null) { + return; + } - if (mEnrollHelper.isCenterEnrollmentComplete()) { - if (mAnimatorSet != null && mAnimatorSet.isRunning()) { - mAnimatorSet.end(); + if (!mEnrollHelper.isCenterEnrollmentStage()) { + if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) { + mTargetAnimatorSet.end(); } final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint(); + if (mCurrentX != point.x || mCurrentY != point.y) { + final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x); + x.addUpdateListener(animation -> { + mCurrentX = (float) animation.getAnimatedValue(); + invalidateSelf(); + }); + + final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y); + y.addUpdateListener(animation -> { + mCurrentY = (float) animation.getAnimatedValue(); + invalidateSelf(); + }); + + final boolean isMovingToCenter = point.x == 0f && point.y == 0f; + final long duration = isMovingToCenter + ? TARGET_ANIM_DURATION_SHORT + : TARGET_ANIM_DURATION_LONG; + + final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI); + scale.setDuration(duration); + scale.addUpdateListener(animation -> { + // Grow then shrink + mCurrentScale = 1 + + SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue()); + invalidateSelf(); + }); + + mTargetAnimatorSet = new AnimatorSet(); + + mTargetAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); + mTargetAnimatorSet.setDuration(duration); + mTargetAnimatorSet.addListener(mTargetAnimListener); + mTargetAnimatorSet.playTogether(x, y, scale); + mTargetAnimatorSet.start(); + } else { + updateTipHintVisibility(); + } + } else { + updateTipHintVisibility(); + } + + updateEdgeHintVisibility(); + } + + private void updateTipHintVisibility() { + final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isTipEnrollmentStage(); + if (mShouldShowTipHint == shouldShow) { + return; + } + mShouldShowTipHint = shouldShow; + + if (mTipHintWidthAnimator != null && mTipHintWidthAnimator.isRunning()) { + mTipHintWidthAnimator.cancel(); + } + + final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f; + mTipHintWidthAnimator = ValueAnimator.ofFloat(mTipHintPaint.getStrokeWidth(), targetWidth); + mTipHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); + mTipHintWidthAnimator.addUpdateListener(mTipHintWidthUpdateListener); + + if (shouldShow) { + startTipHintPulseAnimation(); + } else { + mTipHintWidthAnimator.start(); + } + } - final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x); - x.addUpdateListener(animation -> { - mCurrentX = (float) animation.getAnimatedValue(); - invalidateSelf(); - }); - - final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y); - y.addUpdateListener(animation -> { - mCurrentY = (float) animation.getAnimatedValue(); - invalidateSelf(); - }); - - final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI); - scale.setDuration(ANIM_DURATION); - scale.addUpdateListener(animation -> { - // Grow then shrink - mCurrentScale = 1 + - SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue()); - invalidateSelf(); - }); - - mAnimatorSet = new AnimatorSet(); - - mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); - mAnimatorSet.setDuration(ANIM_DURATION); - mAnimatorSet.playTogether(x, y, scale); - mAnimatorSet.start(); + private void updateEdgeHintVisibility() { + final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isEdgeEnrollmentStage(); + if (mShouldShowEdgeHint == shouldShow) { + return; + } + mShouldShowEdgeHint = shouldShow; + + if (mEdgeHintWidthAnimator != null && mEdgeHintWidthAnimator.isRunning()) { + mEdgeHintWidthAnimator.cancel(); + } + + final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f; + mEdgeHintWidthAnimator = + ValueAnimator.ofFloat(mEdgeHintPaint.getStrokeWidth(), targetWidth); + mEdgeHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); + mEdgeHintWidthAnimator.addUpdateListener(mEdgeHintWidthUpdateListener); + + if (shouldShow) { + startEdgeHintPulseAnimation(); + } else { + mEdgeHintWidthAnimator.start(); } } - void onLastStepAcquired() { - mProgressDrawable.onLastStepAcquired(); + private void startTipHintPulseAnimation() { + mHandler.removeCallbacksAndMessages(null); + if (mTipHintAnimatorSet != null && mTipHintAnimatorSet.isRunning()) { + mTipHintAnimatorSet.cancel(); + } + if (mTipHintColorAnimator != null && mTipHintColorAnimator.isRunning()) { + mTipHintColorAnimator.cancel(); + } + + mTipHintColorAnimator = ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorHighlight); + mTipHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); + mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener); + mTipHintColorAnimator.addListener(mTipHintPulseListener); + + mTipHintAnimatorSet = new AnimatorSet(); + mTipHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); + mTipHintAnimatorSet.playTogether(mTipHintColorAnimator, mTipHintWidthAnimator); + mTipHintAnimatorSet.start(); + } + + private void startEdgeHintPulseAnimation() { + mHandler.removeCallbacksAndMessages(null); + if (mEdgeHintAnimatorSet != null && mEdgeHintAnimatorSet.isRunning()) { + mEdgeHintAnimatorSet.cancel(); + } + if (mEdgeHintColorAnimator != null && mEdgeHintColorAnimator.isRunning()) { + mEdgeHintColorAnimator.cancel(); + } + + mEdgeHintColorAnimator = + ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorHighlight); + mEdgeHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS); + mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener); + mEdgeHintColorAnimator.addListener(mEdgeHintPulseListener); + + mEdgeHintAnimatorSet = new AnimatorSet(); + mEdgeHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator()); + mEdgeHintAnimatorSet.playTogether(mEdgeHintColorAnimator, mEdgeHintWidthAnimator); + mEdgeHintAnimatorSet.start(); + } + + private boolean isTipHintVisible() { + return mTipHintPaint.getStrokeWidth() > 0f; + } + + private boolean isEdgeHintVisible() { + return mEdgeHintPaint.getStrokeWidth() > 0f; } @Override public void draw(@NonNull Canvas canvas) { - mProgressDrawable.draw(canvas); - if (isIlluminationShowing()) { return; } // Draw moving target - if (mEnrollHelper.isCenterEnrollmentComplete()) { + if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) { canvas.save(); canvas.translate(mCurrentX, mCurrentY); @@ -172,11 +428,59 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { mFingerprintDrawable.setAlpha(mAlpha); mSensorOutlinePaint.setAlpha(mAlpha); } - } - @Override - public void onBoundsChange(@NonNull Rect rect) { - mProgressDrawable.setBounds(rect); + // Draw the finger tip or edges hint. + if (isTipHintVisible() || isEdgeHintVisible()) { + canvas.save(); + + // Make arcs start from the top, rather than the right. + canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY()); + + final float halfSensorHeight = Math.abs(mSensorRect.bottom - mSensorRect.top) / 2f; + final float halfSensorWidth = Math.abs(mSensorRect.right - mSensorRect.left) / 2f; + final float hintXOffset = halfSensorWidth + mHintPaddingPx; + final float hintYOffset = halfSensorHeight + mHintPaddingPx; + + if (isTipHintVisible()) { + canvas.drawArc( + mSensorRect.centerX() - hintXOffset, + mSensorRect.centerY() - hintYOffset, + mSensorRect.centerX() + hintXOffset, + mSensorRect.centerY() + hintYOffset, + -HINT_ANGLE / 2f, + HINT_ANGLE, + false /* useCenter */, + mTipHintPaint); + } + + if (isEdgeHintVisible()) { + // Draw right edge hint. + canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY()); + canvas.drawArc( + mSensorRect.centerX() - hintXOffset, + mSensorRect.centerY() - hintYOffset, + mSensorRect.centerX() + hintXOffset, + mSensorRect.centerY() + hintYOffset, + -HINT_ANGLE / 2f, + HINT_ANGLE, + false /* useCenter */, + mEdgeHintPaint); + + // Draw left edge hint. + canvas.rotate(180f, mSensorRect.centerX(), mSensorRect.centerY()); + canvas.drawArc( + mSensorRect.centerX() - hintXOffset, + mSensorRect.centerY() - hintYOffset, + mSensorRect.centerX() + hintXOffset, + mSensorRect.centerY() + hintYOffset, + -HINT_ANGLE / 2f, + HINT_ANGLE, + false /* useCenter */, + mEdgeHintPaint); + } + + canvas.restore(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java index 6a918a6c8d39..8ac6df7198b7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java @@ -44,11 +44,19 @@ public class UdfpsEnrollHelper { private static final String NEW_COORDS_OVERRIDE = "com.android.systemui.biometrics.UdfpsNewCoords"; - // Enroll with two center touches before going to guided enrollment - private static final int NUM_CENTER_TOUCHES = 2; + static final int ENROLL_STAGE_COUNT = 4; + + // TODO(b/198928407): Consolidate with FingerprintEnrollEnrolling + private static final int[] STAGE_THRESHOLDS = new int[] { + 2, // center + 18, // guided + 22, // fingertip + 38, // edges + }; interface Listener { void onEnrollmentProgress(int remaining, int totalSteps); + void onEnrollmentHelp(int remaining, int totalSteps); void onLastStepAcquired(); } @@ -65,6 +73,8 @@ public class UdfpsEnrollHelper { // interface makes no promises about monotonically increasing by one each time. private int mLocationsEnrolled = 0; + private int mCenterTouchCount = 0; + @Nullable Listener mListener; public UdfpsEnrollHelper(@NonNull Context context, int reason) { @@ -117,17 +127,43 @@ public class UdfpsEnrollHelper { } } + static int getStageThreshold(int index) { + return STAGE_THRESHOLDS[index]; + } + + static int getLastStageThreshold() { + return STAGE_THRESHOLDS[ENROLL_STAGE_COUNT - 1]; + } + boolean shouldShowProgressBar() { return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING; } void onEnrollmentProgress(int remaining) { - if (mTotalSteps == -1) { - mTotalSteps = remaining; - } + Log.d(TAG, "onEnrollmentProgress: remaining = " + remaining + + ", mRemainingSteps = " + mRemainingSteps + + ", mTotalSteps = " + mTotalSteps + + ", mLocationsEnrolled = " + mLocationsEnrolled + + ", mCenterTouchCount = " + mCenterTouchCount); if (remaining != mRemainingSteps) { mLocationsEnrolled++; + if (isCenterEnrollmentStage()) { + mCenterTouchCount++; + } + } + + if (mTotalSteps == -1) { + mTotalSteps = remaining; + + // Allocate (or subtract) any extra steps for the first enroll stage. + final int extraSteps = mTotalSteps - getLastStageThreshold(); + if (extraSteps != 0) { + for (int stageIndex = 0; stageIndex < ENROLL_STAGE_COUNT; stageIndex++) { + STAGE_THRESHOLDS[stageIndex] = + Math.max(0, STAGE_THRESHOLDS[stageIndex] + extraSteps); + } + } } mRemainingSteps = remaining; @@ -138,7 +174,9 @@ public class UdfpsEnrollHelper { } void onEnrollmentHelp() { - + if (mListener != null) { + mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps); + } } void setListener(Listener listener) { @@ -152,19 +190,39 @@ public class UdfpsEnrollHelper { } } - boolean isCenterEnrollmentComplete() { + boolean isCenterEnrollmentStage() { + if (mTotalSteps == -1 || mRemainingSteps == -1) { + return true; + } + return mTotalSteps - mRemainingSteps < STAGE_THRESHOLDS[0]; + } + + boolean isGuidedEnrollmentStage() { + if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) { + return false; + } + final int progressSteps = mTotalSteps - mRemainingSteps; + return progressSteps >= STAGE_THRESHOLDS[0] && progressSteps < STAGE_THRESHOLDS[1]; + } + + boolean isTipEnrollmentStage() { if (mTotalSteps == -1 || mRemainingSteps == -1) { return false; - } else if (mAccessibilityEnabled) { + } + final int progressSteps = mTotalSteps - mRemainingSteps; + return progressSteps >= STAGE_THRESHOLDS[1] && progressSteps < STAGE_THRESHOLDS[2]; + } + + boolean isEdgeEnrollmentStage() { + if (mTotalSteps == -1 || mRemainingSteps == -1) { return false; } - final int stepsEnrolled = mTotalSteps - mRemainingSteps; - return stepsEnrolled >= NUM_CENTER_TOUCHES; + return mTotalSteps - mRemainingSteps >= STAGE_THRESHOLDS[2]; } @NonNull PointF getNextGuidedEnrollmentPoint() { - if (mAccessibilityEnabled) { + if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) { return new PointF(0f, 0f); } @@ -174,13 +232,14 @@ public class UdfpsEnrollHelper { SCALE_OVERRIDE, SCALE, UserHandle.USER_CURRENT); } - final int index = mLocationsEnrolled - NUM_CENTER_TOUCHES; + final int index = mLocationsEnrolled - mCenterTouchCount; final PointF originalPoint = mGuidedEnrollmentPoints .get(index % mGuidedEnrollmentPoints.size()); return new PointF(originalPoint.x * scale, originalPoint.y * scale); } void animateIfLastStep() { + Log.d(TAG, "animateIfLastStep: mRemainingSteps = " + mRemainingSteps); if (mListener == null) { Log.e(TAG, "animateIfLastStep, null listener"); return; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java index 4195009937c2..b56543f4851b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java @@ -16,148 +16,107 @@ package com.android.systemui.biometrics; -import android.animation.ValueAnimator; import android.content.Context; -import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.ColorFilter; -import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.util.Log; -import android.util.TypedValue; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.systemui.R; +import java.util.ArrayList; +import java.util.List; /** * UDFPS enrollment progress bar. */ public class UdfpsEnrollProgressBarDrawable extends Drawable { + private static final String TAG = "UdfpsProgressBar"; - private static final String TAG = "UdfpsEnrollProgressBarDrawable"; - - private static final float PROGRESS_BAR_THICKNESS_DP = 12; - - @NonNull private final Context mContext; - @NonNull private final UdfpsEnrollDrawable mParent; - @NonNull private final Paint mBackgroundCirclePaint; - @NonNull private final Paint mProgressPaint; - - @Nullable private ValueAnimator mProgressAnimator; - private float mProgress; - private int mRotation; // After last step, rotate the progress bar once - private boolean mLastStepAcquired; - - public UdfpsEnrollProgressBarDrawable(@NonNull Context context, - @NonNull UdfpsEnrollDrawable parent) { - mContext = context; - mParent = parent; - - mBackgroundCirclePaint = new Paint(); - mBackgroundCirclePaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP)); - mBackgroundCirclePaint.setColor(context.getColor(R.color.white_disabled)); - mBackgroundCirclePaint.setAntiAlias(true); - mBackgroundCirclePaint.setStyle(Paint.Style.STROKE); - - // Background circle color + alpha - TypedArray tc = context.obtainStyledAttributes( - new int[] {android.R.attr.colorControlNormal}); - int tintColor = tc.getColor(0, mBackgroundCirclePaint.getColor()); - mBackgroundCirclePaint.setColor(tintColor); - tc.recycle(); - TypedValue alpha = new TypedValue(); - context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true); - mBackgroundCirclePaint.setAlpha((int) (alpha.getFloat() * 255)); - - // Progress should not be color extracted - mProgressPaint = new Paint(); - mProgressPaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP)); - mProgressPaint.setColor(context.getColor(R.color.udfps_enroll_progress)); - mProgressPaint.setAntiAlias(true); - mProgressPaint.setStyle(Paint.Style.STROKE); - mProgressPaint.setStrokeCap(Paint.Cap.ROUND); - } + private static final float SEGMENT_GAP_ANGLE = 12f; - void setEnrollmentProgress(int remaining, int totalSteps) { - // Add one so that the first steps actually changes progress, but also so that the last - // step ends at 1.0 - final float progress = (totalSteps - remaining + 1) / (float) (totalSteps + 1); - setEnrollmentProgress(progress); - } + @NonNull private final List<UdfpsEnrollProgressBarSegment> mSegments; - private void setEnrollmentProgress(float progress) { - if (mLastStepAcquired) { - return; + public UdfpsEnrollProgressBarDrawable(@NonNull Context context) { + mSegments = new ArrayList<>(UdfpsEnrollHelper.ENROLL_STAGE_COUNT); + float startAngle = SEGMENT_GAP_ANGLE / 2f; + final float sweepAngle = (360f / UdfpsEnrollHelper.ENROLL_STAGE_COUNT) - SEGMENT_GAP_ANGLE; + final Runnable invalidateRunnable = this::invalidateSelf; + for (int index = 0; index < UdfpsEnrollHelper.ENROLL_STAGE_COUNT; index++) { + mSegments.add(new UdfpsEnrollProgressBarSegment(context, getBounds(), startAngle, + sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable)); + startAngle += sweepAngle + SEGMENT_GAP_ANGLE; } + } - long animationDuration = 150; - - if (progress == 1.f) { - animationDuration = 400; - final ValueAnimator rotationAnimator = ValueAnimator.ofInt(0, 400); - rotationAnimator.setDuration(animationDuration); - rotationAnimator.addUpdateListener(animation -> { - Log.d(TAG, "Rotation: " + mRotation); - mRotation = (int) animation.getAnimatedValue(); - mParent.invalidateSelf(); - }); - rotationAnimator.start(); + void setEnrollmentProgress(int remaining, int totalSteps) { + if (remaining == totalSteps) { + // Show some progress for the initial touch. + setEnrollmentProgress(1); + } else { + setEnrollmentProgress(totalSteps - remaining); } + } - if (mProgressAnimator != null && mProgressAnimator.isRunning()) { - mProgressAnimator.cancel(); + private void setEnrollmentProgress(int progressSteps) { + Log.d(TAG, "setEnrollmentProgress: progressSteps = " + progressSteps); + + int segmentIndex = 0; + int prevThreshold = 0; + while (segmentIndex < mSegments.size()) { + final UdfpsEnrollProgressBarSegment segment = mSegments.get(segmentIndex); + final int threshold = UdfpsEnrollHelper.getStageThreshold(segmentIndex); + + if (progressSteps >= threshold && !segment.isFilledOrFilling()) { + Log.d(TAG, "setEnrollmentProgress: segment[" + segmentIndex + "] complete"); + segment.updateProgress(1f); + break; + } else if (progressSteps >= prevThreshold && progressSteps < threshold) { + final int relativeSteps = progressSteps - prevThreshold; + final int relativeThreshold = threshold - prevThreshold; + final float segmentProgress = (float) relativeSteps / (float) relativeThreshold; + Log.d(TAG, "setEnrollmentProgress: segment[" + segmentIndex + "] progress = " + + segmentProgress); + segment.updateProgress(segmentProgress); + break; + } + + segmentIndex++; + prevThreshold = threshold; } - mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress); - mProgressAnimator.setDuration(animationDuration); - mProgressAnimator.addUpdateListener(animation -> { - mProgress = (float) animation.getAnimatedValue(); - // Use the parent to invalidate, since it's the one that's attached as the view's - // drawable and has its callback set automatically. Invalidating via - // `this.invalidateSelf` actually does not invoke draw(), since this drawable's callback - // is not really set. - mParent.invalidateSelf(); - }); - mProgressAnimator.start(); + if (progressSteps >= UdfpsEnrollHelper.getLastStageThreshold()) { + Log.d(TAG, "setEnrollmentProgress: startCompletionAnimation"); + for (final UdfpsEnrollProgressBarSegment segment : mSegments) { + segment.startCompletionAnimation(); + } + } else { + Log.d(TAG, "setEnrollmentProgress: cancelCompletionAnimation"); + for (final UdfpsEnrollProgressBarSegment segment : mSegments) { + segment.cancelCompletionAnimation(); + } + } } void onLastStepAcquired() { - setEnrollmentProgress(1.f); - mLastStepAcquired = true; + Log.d(TAG, "setEnrollmentProgress: onLastStepAcquired"); + setEnrollmentProgress(UdfpsEnrollHelper.getLastStageThreshold()); } @Override public void draw(@NonNull Canvas canvas) { + Log.d(TAG, "setEnrollmentProgress: draw"); + canvas.save(); // Progress starts from the top, instead of the right - canvas.rotate(-90 + mRotation, getBounds().centerX(), getBounds().centerY()); - - // Progress bar "background track" - final float halfPaddingPx = Utils.dpToPixels(mContext, PROGRESS_BAR_THICKNESS_DP) / 2; - canvas.drawArc(halfPaddingPx, - halfPaddingPx, - getBounds().right - halfPaddingPx, - getBounds().bottom - halfPaddingPx, - 0, - 360, - false, - mBackgroundCirclePaint - ); - - final float progress = 360.f * mProgress; - // Progress - canvas.drawArc(halfPaddingPx, - halfPaddingPx, - getBounds().right - halfPaddingPx, - getBounds().bottom - halfPaddingPx, - 0, - progress, - false, - mProgressPaint - ); + canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY()); + + // Draw each of the enroll segments. + for (final UdfpsEnrollProgressBarSegment segment : mSegments) { + segment.draw(canvas); + } canvas.restore(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java new file mode 100644 index 000000000000..5f24380b6ce3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.biometrics; + +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.util.TypedValue; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.systemui.R; + +/** + * A single segment of the UDFPS enrollment progress bar. + */ +public class UdfpsEnrollProgressBarSegment { + private static final String TAG = "UdfpsProgressBarSegment"; + + private static final long PROGRESS_ANIMATION_DURATION_MS = 400L; + private static final long OVER_SWEEP_ANIMATION_DELAY_MS = 200L; + private static final long OVER_SWEEP_ANIMATION_DURATION_MS = 200L; + + private static final float STROKE_WIDTH_DP = 12f; + + private final Handler mHandler = new Handler(Looper.getMainLooper()); + + @NonNull private final Rect mBounds; + @NonNull private final Runnable mInvalidateRunnable; + private final float mStartAngle; + private final float mSweepAngle; + private final float mMaxOverSweepAngle; + private final float mStrokeWidthPx; + + @NonNull private final Paint mBackgroundPaint; + @NonNull private final Paint mProgressPaint; + + private boolean mIsFilledOrFilling = false; + + private float mProgress = 0f; + @Nullable private ValueAnimator mProgressAnimator; + @NonNull private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener; + + private float mOverSweepAngle = 0f; + @Nullable private ValueAnimator mOverSweepAnimator; + @Nullable private ValueAnimator mOverSweepReverseAnimator; + @NonNull private final ValueAnimator.AnimatorUpdateListener mOverSweepUpdateListener; + @NonNull private final Runnable mOverSweepAnimationRunnable; + + public UdfpsEnrollProgressBarSegment(@NonNull Context context, @NonNull Rect bounds, + float startAngle, float sweepAngle, float maxOverSweepAngle, + @NonNull Runnable invalidateRunnable) { + + mBounds = bounds; + mInvalidateRunnable = invalidateRunnable; + mStartAngle = startAngle; + mSweepAngle = sweepAngle; + mMaxOverSweepAngle = maxOverSweepAngle; + mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP); + + mBackgroundPaint = new Paint(); + mBackgroundPaint.setStrokeWidth(mStrokeWidthPx); + mBackgroundPaint.setColor(context.getColor(R.color.white_disabled)); + mBackgroundPaint.setAntiAlias(true); + mBackgroundPaint.setStyle(Paint.Style.STROKE); + mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND); + + // Background paint color + alpha + final int[] attrs = new int[] {android.R.attr.colorControlNormal}; + final TypedArray ta = context.obtainStyledAttributes(attrs); + @ColorInt final int tintColor = ta.getColor(0, mBackgroundPaint.getColor()); + mBackgroundPaint.setColor(tintColor); + ta.recycle(); + TypedValue alpha = new TypedValue(); + context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true); + mBackgroundPaint.setAlpha((int) (alpha.getFloat() * 255f)); + + // Progress should not be color extracted + mProgressPaint = new Paint(); + mProgressPaint.setStrokeWidth(mStrokeWidthPx); + mProgressPaint.setColor(context.getColor(R.color.udfps_enroll_progress)); + mProgressPaint.setAntiAlias(true); + mProgressPaint.setStyle(Paint.Style.STROKE); + mProgressPaint.setStrokeCap(Paint.Cap.ROUND); + + mProgressUpdateListener = animation -> { + mProgress = (float) animation.getAnimatedValue(); + mInvalidateRunnable.run(); + }; + + mOverSweepUpdateListener = animation -> { + mOverSweepAngle = (float) animation.getAnimatedValue(); + mInvalidateRunnable.run(); + }; + mOverSweepAnimationRunnable = () -> { + if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) { + mOverSweepAnimator.cancel(); + } + mOverSweepAnimator = ValueAnimator.ofFloat(mOverSweepAngle, mMaxOverSweepAngle); + mOverSweepAnimator.setDuration(OVER_SWEEP_ANIMATION_DURATION_MS); + mOverSweepAnimator.addUpdateListener(mOverSweepUpdateListener); + mOverSweepAnimator.start(); + }; + } + + /** + * Draws this segment to the given canvas. + */ + public void draw(@NonNull Canvas canvas) { + Log.d(TAG, "draw: mProgress = " + mProgress); + + final float halfPaddingPx = mStrokeWidthPx / 2f; + + if (mProgress < 1f) { + // Draw the unfilled background color of the segment. + canvas.drawArc( + halfPaddingPx, + halfPaddingPx, + mBounds.right - halfPaddingPx, + mBounds.bottom - halfPaddingPx, + mStartAngle, + mSweepAngle, + false /* useCenter */, + mBackgroundPaint); + } + + if (mProgress > 0f) { + // Draw the filled progress portion of the segment. + canvas.drawArc( + halfPaddingPx, + halfPaddingPx, + mBounds.right - halfPaddingPx, + mBounds.bottom - halfPaddingPx, + mStartAngle, + mSweepAngle * mProgress + mOverSweepAngle, + false /* useCenter */, + mProgressPaint); + } + } + + /** + * @return Whether this segment is filled or in the process of being filled. + */ + public boolean isFilledOrFilling() { + return mIsFilledOrFilling; + } + + /** + * Updates the fill progress of this segment, animating if necessary. + * + * @param progress The new fill progress, in the range [0, 1]. + */ + public void updateProgress(float progress) { + updateProgress(progress, PROGRESS_ANIMATION_DURATION_MS); + } + + private void updateProgress(float progress, long animationDurationMs) { + Log.d(TAG, "updateProgress: progress = " + progress + + ", duration = " + animationDurationMs); + + if (mProgress == progress) { + Log.d(TAG, "updateProgress skipped: progress == mProgress"); + return; + } + + mIsFilledOrFilling = progress >= 1f; + + if (mProgressAnimator != null && mProgressAnimator.isRunning()) { + mProgressAnimator.cancel(); + } + + mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress); + mProgressAnimator.setDuration(animationDurationMs); + mProgressAnimator.addUpdateListener(mProgressUpdateListener); + mProgressAnimator.start(); + } + + /** + * Queues and runs the completion animation for this segment. + */ + public void startCompletionAnimation() { + final boolean hasCallback = mHandler.hasCallbacks(mOverSweepAnimationRunnable); + if (hasCallback || mOverSweepAngle >= mMaxOverSweepAngle) { + Log.d(TAG, "startCompletionAnimation skipped: hasCallback = " + hasCallback + + ", mOverSweepAngle = " + mOverSweepAngle); + return; + } + + Log.d(TAG, "startCompletionAnimation: mProgress = " + mProgress + + ", mOverSweepAngle = " + mOverSweepAngle); + + // Reset sweep angle back to zero if the animation is being rolled back. + if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) { + mOverSweepReverseAnimator.cancel(); + mOverSweepAngle = 0f; + } + + // Start filling the segment if it isn't already. + if (mProgress < 1f) { + updateProgress(1f, OVER_SWEEP_ANIMATION_DELAY_MS); + } + + // Queue the animation to run after fill completes. + mHandler.postDelayed(mOverSweepAnimationRunnable, OVER_SWEEP_ANIMATION_DELAY_MS); + } + + /** + * Cancels (and reverses, if necessary) a queued or running completion animation. + */ + public void cancelCompletionAnimation() { + Log.d(TAG, "cancelCompletionAnimation: mProgress = " + mProgress + + ", mOverSweepAngle = " + mOverSweepAngle); + + // Cancel the animation if it's queued or running. + mHandler.removeCallbacks(mOverSweepAnimationRunnable); + if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) { + mOverSweepAnimator.cancel(); + } + + // Roll back the animation if it has at least partially run. + if (mOverSweepAngle > 0f) { + if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) { + mOverSweepReverseAnimator.cancel(); + } + + final float completion = mOverSweepAngle / mMaxOverSweepAngle; + final long proratedDuration = (long) (OVER_SWEEP_ANIMATION_DURATION_MS * completion); + mOverSweepReverseAnimator = ValueAnimator.ofFloat(mOverSweepAngle, 0f); + mOverSweepReverseAnimator.setDuration(proratedDuration); + mOverSweepReverseAnimator.addUpdateListener(mOverSweepUpdateListener); + mOverSweepReverseAnimator.start(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java index 2cdf49d6fc3c..6f02c64e4cf7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java @@ -17,9 +17,12 @@ package com.android.systemui.biometrics; import android.content.Context; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; import android.widget.ImageView; import androidx.annotation.NonNull; @@ -32,20 +35,25 @@ import com.android.systemui.R; */ public class UdfpsEnrollView extends UdfpsAnimationView { @NonNull private final UdfpsEnrollDrawable mFingerprintDrawable; + @NonNull private final UdfpsEnrollProgressBarDrawable mFingerprintProgressDrawable; @NonNull private final Handler mHandler; @NonNull private ImageView mFingerprintView; + @NonNull private ImageView mFingerprintProgressView; public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); mFingerprintDrawable = new UdfpsEnrollDrawable(mContext); + mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context); mHandler = new Handler(Looper.getMainLooper()); } @Override protected void onFinishInflate() { mFingerprintView = findViewById(R.id.udfps_enroll_animation_fp_view); + mFingerprintProgressView = findViewById(R.id.udfps_enroll_animation_fp_progress_view); mFingerprintView.setImageDrawable(mFingerprintDrawable); + mFingerprintProgressView.setImageDrawable(mFingerprintProgressDrawable); } @Override @@ -53,15 +61,36 @@ public class UdfpsEnrollView extends UdfpsAnimationView { return mFingerprintDrawable; } + void updateSensorLocation(@NonNull FingerprintSensorPropertiesInternal sensorProps) { + View fingerprintAccessibilityView = findViewById(R.id.udfps_enroll_accessibility_view); + final int sensorHeight = sensorProps.sensorRadius * 2; + final int sensorWidth = sensorHeight; + ViewGroup.LayoutParams params = fingerprintAccessibilityView.getLayoutParams(); + params.width = sensorWidth; + params.height = sensorHeight; + fingerprintAccessibilityView.setLayoutParams(params); + fingerprintAccessibilityView.requestLayout(); + } + void setEnrollHelper(UdfpsEnrollHelper enrollHelper) { mFingerprintDrawable.setEnrollHelper(enrollHelper); } void onEnrollmentProgress(int remaining, int totalSteps) { - mHandler.post(() -> mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps)); + mHandler.post(() -> { + mFingerprintProgressDrawable.setEnrollmentProgress(remaining, totalSteps); + mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps); + }); + } + + void onEnrollmentHelp(int remaining, int totalSteps) { + mHandler.post( + () -> mFingerprintProgressDrawable.setEnrollmentProgress(remaining, totalSteps)); } void onLastStepAcquired() { - mHandler.post(mFingerprintDrawable::onLastStepAcquired); + mHandler.post(() -> { + mFingerprintProgressDrawable.onLastStepAcquired(); + }); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java index 3dab010d917c..6cdd1c8b0d4e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java @@ -33,16 +33,21 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp @NonNull private final UdfpsEnrollHelper mEnrollHelper; @NonNull private final UdfpsEnrollHelper.Listener mEnrollHelperListener = new UdfpsEnrollHelper.Listener() { - @Override - public void onEnrollmentProgress(int remaining, int totalSteps) { - mView.onEnrollmentProgress(remaining, totalSteps); - } + @Override + public void onEnrollmentProgress(int remaining, int totalSteps) { + mView.onEnrollmentProgress(remaining, totalSteps); + } - @Override - public void onLastStepAcquired() { - mView.onLastStepAcquired(); - } - }; + @Override + public void onEnrollmentHelp(int remaining, int totalSteps) { + mView.onEnrollmentHelp(remaining, totalSteps); + } + + @Override + public void onLastStepAcquired() { + mView.onLastStepAcquired(); + } + }; protected UdfpsEnrollViewController( @NonNull UdfpsEnrollView view, @@ -74,7 +79,7 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp @NonNull @Override public PointF getTouchTranslation() { - if (!mEnrollHelper.isCenterEnrollmentComplete()) { + if (!mEnrollHelper.isGuidedEnrollmentStage()) { return new PointF(0, 0); } else { return mEnrollHelper.getNextGuidedEnrollmentPoint(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java index d46426a03621..9015396d26ab 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java @@ -155,6 +155,13 @@ public class UdfpsKeyguardView extends UdfpsAnimationView { updateAlpha(); } + /** + * @return alpha between 0 and 255 + */ + int getUnpausedAlpha() { + return mAlpha; + } + @Override protected int updateAlpha() { int alpha = super.updateAlpha(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 4896305daa2e..888672316c8a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -34,12 +34,12 @@ import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.DelayableExecutor; import java.io.FileDescriptor; import java.io.PrintWriter; - /** * Class that coordinates non-HBM animations during keyguard authentication. */ @@ -50,6 +50,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud @NonNull private final KeyguardViewMediator mKeyguardViewMediator; @NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController; @NonNull private final ConfigurationController mConfigurationController; + @NonNull private final KeyguardStateController mKeyguardStateController; @NonNull private final UdfpsController mUdfpsController; private boolean mShowingUdfpsBouncer; @@ -60,6 +61,9 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud private float mTransitionToFullShadeProgress; private float mLastDozeAmount; + private float mStatusBarExpansion; + private boolean mLaunchTransitionFadingAway; + /** * hidden amount of pin/pattern/password bouncer * {@link KeyguardBouncer#EXPANSION_VISIBLE} (0f) to @@ -79,6 +83,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud @NonNull KeyguardViewMediator keyguardViewMediator, @NonNull LockscreenShadeTransitionController transitionController, @NonNull ConfigurationController configurationController, + @NonNull KeyguardStateController keyguardStateController, @NonNull UdfpsController udfpsController) { super(view, statusBarStateController, statusBar, dumpManager); mKeyguardViewManager = statusBarKeyguardViewManager; @@ -87,6 +92,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mKeyguardViewMediator = keyguardViewMediator; mLockScreenShadeTransitionController = transitionController; mConfigurationController = configurationController; + mKeyguardStateController = keyguardStateController; mUdfpsController = udfpsController; } @@ -105,11 +111,14 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mUdfpsRequested = false; + mLaunchTransitionFadingAway = mKeyguardStateController.isLaunchTransitionFadingAway(); + mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); mStatusBarState = mStatusBarStateController.getState(); mQsExpanded = mKeyguardViewManager.isQsExpanded(); mInputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN; mIsBouncerVisible = mKeyguardViewManager.bouncerIsOrWillBeShowing(); mConfigurationController.addCallback(mConfigurationListener); + mStatusBar.addExpansionChangedListener(mStatusBarExpansionChangedListener); updateAlpha(); updatePauseAuth(); @@ -122,10 +131,12 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud super.onViewDetached(); mFaceDetectRunning = false; + mKeyguardStateController.removeCallback(mKeyguardStateControllerCallback); mStatusBarStateController.removeCallback(mStateListener); mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor); mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false); mConfigurationController.removeCallback(mConfigurationListener); + mStatusBar.removeExpansionChangedListener(mStatusBarExpansionChangedListener); if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) { mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null); } @@ -140,9 +151,11 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud pw.println("mQsExpanded=" + mQsExpanded); pw.println("mIsBouncerVisible=" + mIsBouncerVisible); pw.println("mInputBouncerHiddenAmount=" + mInputBouncerHiddenAmount); - pw.println("mAlpha=" + mView.getAlpha()); + pw.println("mStatusBarExpansion=" + mStatusBarExpansion); + pw.println("unpausedAlpha=" + mView.getUnpausedAlpha()); pw.println("mUdfpsRequested=" + mUdfpsRequested); pw.println("mView.mUdfpsRequested=" + mView.mUdfpsRequested); + pw.println("mLaunchTransitionFadingAway=" + mLaunchTransitionFadingAway); } /** @@ -154,10 +167,10 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud return false; } + boolean udfpsAffordanceWasNotShowing = shouldPauseAuth(); mShowingUdfpsBouncer = show; - updatePauseAuth(); if (mShowingUdfpsBouncer) { - if (mStatusBarState == StatusBarState.SHADE_LOCKED) { + if (udfpsAffordanceWasNotShowing) { mView.animateInUdfpsBouncer(null); } @@ -170,6 +183,8 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud } else { mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false); } + updateAlpha(); + updatePauseAuth(); return true; } @@ -189,6 +204,10 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud return false; } + if (mLaunchTransitionFadingAway) { + return true; + } + if (mStatusBarState != KEYGUARD) { return true; } @@ -237,12 +256,17 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud } private void updateAlpha() { - // fade icon on transition to showing bouncer + // fade icon on transitions to showing the status bar, but if mUdfpsRequested, then + // the keyguard is occluded by some application - so instead use the input bouncer + // hidden amount to determine the fade + float expansion = mUdfpsRequested ? mInputBouncerHiddenAmount : mStatusBarExpansion; int alpha = mShowingUdfpsBouncer ? 255 : (int) MathUtils.constrain( - MathUtils.map(.5f, .9f, 0f, 255f, mInputBouncerHiddenAmount), + MathUtils.map(.5f, .9f, 0f, 255f, expansion), 0f, 255f); - alpha *= (1.0f - mTransitionToFullShadeProgress); + if (!mShowingUdfpsBouncer) { + alpha *= (1.0f - mTransitionToFullShadeProgress); + } mView.setUnpausedAlpha(alpha); } @@ -287,6 +311,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud public void requestUdfps(boolean request, int color) { mUdfpsRequested = request; mView.requestUdfps(request, color); + updateAlpha(); updatePauseAuth(); } @@ -356,4 +381,23 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud mView.updateColor(); } }; + + private final StatusBar.ExpansionChangedListener mStatusBarExpansionChangedListener = + new StatusBar.ExpansionChangedListener() { + @Override + public void onExpansionChanged(float expansion, boolean expanded) { + mStatusBarExpansion = expansion; + updateAlpha(); + } + }; + + private final KeyguardStateController.Callback mKeyguardStateControllerCallback = + new KeyguardStateController.Callback() { + @Override + public void onLaunchTransitionFadingAwayChanged() { + mLaunchTransitionFadingAway = + mKeyguardStateController.isLaunchTransitionFadingAway(); + updatePauseAuth(); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java index e1349f2aba6d..40c28fab51b8 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java @@ -21,6 +21,7 @@ import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHT import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_PRIMARY_DEVIANCE; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE; import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER; +import static com.android.systemui.classifier.Classifier.LOCK_ICON; import static com.android.systemui.classifier.Classifier.SHADE_DRAG; import android.graphics.Point; @@ -89,7 +90,9 @@ class ZigZagClassifier extends FalsingClassifier { Result calculateFalsingResult( @Classifier.InteractionType int interactionType, double historyBelief, double historyConfidence) { - if (interactionType == BRIGHTNESS_SLIDER || interactionType == SHADE_DRAG) { + if (interactionType == BRIGHTNESS_SLIDER + || interactionType == SHADE_DRAG + || interactionType == LOCK_ICON) { return Result.passed(0); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java index 41964652ac49..657d9246be8f 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java @@ -214,6 +214,14 @@ public class DozeLog implements Dumpable { } /** + * Appends display state delayed by UDFPS event to the logs + * @param delayedDisplayState the display screen state that was delayed + */ + public void traceDisplayStateDelayedByUdfps(int delayedDisplayState) { + mLogger.logDisplayStateDelayedByUdfps(delayedDisplayState); + } + + /** * Appends display state changed event to the logs * @param displayState new DozeMachine state */ diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt index 9bc74be9b9c3..fe37d49980b2 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -160,6 +160,14 @@ class DozeLogger @Inject constructor( }) } + fun logDisplayStateDelayedByUdfps(delayedDisplayState: Int) { + buffer.log(TAG, INFO, { + str1 = Display.stateToString(delayedDisplayState) + }, { + "Delaying display state change to: $str1 due to UDFPS activity" + }) + } + fun logDisplayStateChanged(displayState: Int) { buffer.log(TAG, INFO, { str1 = Display.stateToString(displayState) diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index 470d2f364c1c..8d4ac75a0748 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -36,6 +36,7 @@ import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.doze.dagger.WrappedService; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.util.sensors.AsyncSensorManager; import java.io.PrintWriter; @@ -55,6 +56,16 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi "com.android.systemui.doze.AOD_BRIGHTNESS"; protected static final String BRIGHTNESS_BUCKET = "brightness_bucket"; + /** + * Just before the screen times out from user inactivity, DisplayPowerController dims the screen + * brightness to the lower of {@link #mScreenBrightnessDim}, or the current brightness minus + * DisplayPowerController#SCREEN_DIM_MINIMUM_REDUCTION_FLOAT. + * + * This value is 0.04f * 255, which converts SCREEN_DIM_MINIMUM_REDUCTION_FLOAT to the integer + * brightness values used by doze. + */ + private static final int SCREEN_DIM_MINIMUM_REDUCTION_INT = 10; + private final Context mContext; private final DozeMachine.Service mDozeService; private final DozeHost mDozeHost; @@ -81,13 +92,16 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi */ private int mDebugBrightnessBucket = -1; + private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; + @Inject public DozeScreenBrightness(Context context, @WrappedService DozeMachine.Service service, AsyncSensorManager sensorManager, @BrightnessSensor Optional<Sensor> lightSensorOptional, DozeHost host, Handler handler, AlwaysOnDisplayPolicy alwaysOnDisplayPolicy, WakefulnessLifecycle wakefulnessLifecycle, - DozeParameters dozeParameters) { + DozeParameters dozeParameters, + UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) { mContext = context; mDozeService = service; mSensorManager = sensorManager; @@ -96,6 +110,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi mDozeParameters = dozeParameters; mDozeHost = host; mHandler = handler; + mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mDefaultDozeBrightness = alwaysOnDisplayPolicy.defaultDozeBrightness; mScreenBrightnessDim = alwaysOnDisplayPolicy.dimBrightness; @@ -146,14 +161,15 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi } } - private void updateBrightnessAndReady(boolean force) { + public void updateBrightnessAndReady(boolean force) { if (force || mRegistered || mDebugBrightnessBucket != -1) { int sensorValue = mDebugBrightnessBucket == -1 ? mLastSensorValue : mDebugBrightnessBucket; int brightness = computeBrightness(sensorValue); boolean brightnessReady = brightness > 0; if (brightnessReady) { - mDozeService.setDozeScreenBrightness(clampToUserSetting(brightness)); + mDozeService.setDozeScreenBrightness( + clampToDimBrightnessForScreenOff(clampToUserSetting(brightness))); } int scrimOpacity = -1; @@ -205,13 +221,21 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi /** * Clamp the brightness to the dim brightness value used by PowerManagerService just before the * device times out and goes to sleep, if we are sleeping from a timeout. This ensures that we - * don't raise the brightness back to the user setting before playing the screen off animation. + * don't raise the brightness back to the user setting before or during the screen off + * animation. */ private int clampToDimBrightnessForScreenOff(int brightness) { - if (mDozeParameters.shouldControlUnlockedScreenOff() + if (mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying() && mWakefulnessLifecycle.getLastSleepReason() == PowerManager.GO_TO_SLEEP_REASON_TIMEOUT) { - return Math.min(mScreenBrightnessDim, brightness); + return Math.max( + PowerManager.BRIGHTNESS_OFF, + // Use the lower of either the dim brightness, or the current brightness reduced + // by the minimum dim amount. This is the same logic used in + // DisplayPowerController#updatePowerState to apply a minimum dim amount. + Math.min( + brightness - SCREEN_DIM_MINIMUM_REDUCTION_INT, + mScreenBrightnessDim)); } else { return brightness; } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java index 8c50a16b566f..038be48b53ee 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java @@ -26,6 +26,11 @@ import android.os.Handler; import android.util.Log; import android.view.Display; +import androidx.annotation.Nullable; + +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.biometrics.AuthController; +import com.android.systemui.biometrics.UdfpsController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.doze.dagger.WrappedService; @@ -34,6 +39,7 @@ import com.android.systemui.util.wakelock.SettableWakeLock; import com.android.systemui.util.wakelock.WakeLock; import javax.inject.Inject; +import javax.inject.Provider; /** * Controls the screen when dozing. @@ -56,23 +62,64 @@ public class DozeScreenState implements DozeMachine.Part { */ public static final int ENTER_DOZE_HIDE_WALLPAPER_DELAY = 2500; + /** + * Add an extra delay to the transition to DOZE when udfps is current activated before + * the display state transitions from ON => DOZE. + */ + public static final int UDFPS_DISPLAY_STATE_DELAY = 1200; + private final DozeMachine.Service mDozeService; private final Handler mHandler; private final Runnable mApplyPendingScreenState = this::applyPendingScreenState; private final DozeParameters mParameters; private final DozeHost mDozeHost; + private final AuthController mAuthController; + private final Provider<UdfpsController> mUdfpsControllerProvider; + @Nullable private UdfpsController mUdfpsController; + private final DozeLog mDozeLog; + private final DozeScreenBrightness mDozeScreenBrightness; private int mPendingScreenState = Display.STATE_UNKNOWN; private SettableWakeLock mWakeLock; @Inject - public DozeScreenState(@WrappedService DozeMachine.Service service, @Main Handler handler, - DozeHost host, DozeParameters parameters, WakeLock wakeLock) { + public DozeScreenState( + @WrappedService DozeMachine.Service service, + @Main Handler handler, + DozeHost host, + DozeParameters parameters, + WakeLock wakeLock, + AuthController authController, + Provider<UdfpsController> udfpsControllerProvider, + DozeLog dozeLog, + DozeScreenBrightness dozeScreenBrightness) { mDozeService = service; mHandler = handler; mParameters = parameters; mDozeHost = host; mWakeLock = new SettableWakeLock(wakeLock, TAG); + mAuthController = authController; + mUdfpsControllerProvider = udfpsControllerProvider; + mDozeLog = dozeLog; + mDozeScreenBrightness = dozeScreenBrightness; + + updateUdfpsController(); + if (mUdfpsController == null) { + mAuthController.addCallback(new AuthController.Callback() { + @Override + public void onAllAuthenticatorsRegistered() { + updateUdfpsController(); + } + }); + } + } + + private void updateUdfpsController() { + if (mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) { + mUdfpsController = mUdfpsControllerProvider.get(); + } else { + mUdfpsController = null; + } } @Override @@ -110,21 +157,28 @@ public class DozeScreenState implements DozeMachine.Part { mPendingScreenState = screenState; // Delay screen state transitions even longer while animations are running. - boolean shouldDelayTransition = newState == DOZE_AOD + boolean shouldDelayTransitionEnteringDoze = newState == DOZE_AOD && mParameters.shouldControlScreenOff() && !turningOn; - if (shouldDelayTransition) { + // Delay screen state transition longer if UDFPS is actively authenticating a fp + boolean shouldDelayTransitionForUDFPS = newState == DOZE_AOD + && mUdfpsController != null && mUdfpsController.isFingerDown(); + + if (shouldDelayTransitionEnteringDoze || shouldDelayTransitionForUDFPS) { mWakeLock.setAcquired(true); } if (!messagePending) { if (DEBUG) { Log.d(TAG, "Display state changed to " + screenState + " delayed by " - + (shouldDelayTransition ? ENTER_DOZE_DELAY : 1)); + + (shouldDelayTransitionEnteringDoze ? ENTER_DOZE_DELAY : 1)); } - if (shouldDelayTransition) { + if (shouldDelayTransitionEnteringDoze) { mHandler.postDelayed(mApplyPendingScreenState, ENTER_DOZE_DELAY); + } else if (shouldDelayTransitionForUDFPS) { + mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState); + mHandler.postDelayed(mApplyPendingScreenState, UDFPS_DISPLAY_STATE_DELAY); } else { mHandler.post(mApplyPendingScreenState); } @@ -139,6 +193,12 @@ public class DozeScreenState implements DozeMachine.Part { } private void applyPendingScreenState() { + if (mUdfpsController != null && mUdfpsController.isFingerDown()) { + mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState); + mHandler.postDelayed(mApplyPendingScreenState, UDFPS_DISPLAY_STATE_DELAY); + return; + } + applyScreenState(mPendingScreenState); mPendingScreenState = Display.STATE_UNKNOWN; } @@ -147,6 +207,12 @@ public class DozeScreenState implements DozeMachine.Part { if (screenState != Display.STATE_UNKNOWN) { if (DEBUG) Log.d(TAG, "setDozeScreenState(" + screenState + ")"); mDozeService.setDozeScreenState(screenState); + if (screenState == Display.STATE_DOZE) { + // If we're entering doze, update the doze screen brightness. We might have been + // clamping it to the dim brightness during the screen off animation, and we should + // now change it to the brightness we actually want according to the sensor. + mDozeScreenBrightness.updateBrightnessAndReady(false /* force */); + } mPendingScreenState = Display.STATE_UNKNOWN; mWakeLock.setAcquired(false); } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index a641ad4b338b..c4508e043c7d 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -97,7 +97,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks int backgroundAlpha = (int) (ScrimController.BUSY_SCRIM_ALPHA * 255); background.setAlpha(backgroundAlpha); mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(), - mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255); + (int) mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255); } else { float backgroundAlpha = mContext.getResources().getFloat( com.android.systemui.R.dimen.shutdown_scrim_behind_alpha); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 09810b3db107..5751c0fddde0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -821,6 +821,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, private final KeyguardStateController mKeyguardStateController; private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy; + private boolean mWallpaperSupportsAmbientMode; /** * Injected constructor. See {@link KeyguardModule}. @@ -2096,13 +2097,14 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, int flags = 0; if (mKeyguardViewControllerLazy.get().shouldDisableWindowAnimationsForUnlock() - || (mWakeAndUnlocking && !mPulsing) - || isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()) { + || mWakeAndUnlocking && !mWallpaperSupportsAmbientMode) { flags |= WindowManagerPolicyConstants .KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS; } 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; } if (mKeyguardViewControllerLazy.get().isUnlockWithWallpaper()) { @@ -2791,6 +2793,15 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, 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 { @WindowManager.TransitionOldType int mTransit; diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java index 89786ee880ad..a617850ef0ae 100644 --- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java @@ -139,7 +139,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener + " with ducking", e); } player.start(); - if (DEBUG) { Log.d(mTag, "player.start"); } + if (DEBUG) { Log.d(mTag, "player.start piid:" + player.getPlayerIId()); } } catch (Exception e) { if (player != null) { player.release(); @@ -155,7 +155,13 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener mPlayer = player; } if (mp != null) { - if (DEBUG) { Log.d(mTag, "mPlayer.release"); } + if (DEBUG) { + Log.d(mTag, "mPlayer.pause+release piid:" + player.getPlayerIId()); + } + mp.pause(); + try { + Thread.sleep(100); + } catch (InterruptedException ie) { } mp.release(); } this.notify(); @@ -244,6 +250,10 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener try { mp.stop(); } catch (Exception e) { } + if (DEBUG) { + Log.i(mTag, "About to release MediaPlayer piid:" + + mp.getPlayerIId() + " due to notif cancelled"); + } mp.release(); synchronized(mQueueAudioFocusLock) { if (mAudioManagerWithAudioFocus != null) { @@ -284,7 +294,7 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener public void onCompletion(MediaPlayer mp) { synchronized(mQueueAudioFocusLock) { if (mAudioManagerWithAudioFocus != null) { - if (DEBUG) Log.d(mTag, "onCompletion() abandonning AudioFocus"); + if (DEBUG) Log.d(mTag, "onCompletion() abandoning AudioFocus"); mAudioManagerWithAudioFocus.abandonAudioFocus(null); mAudioManagerWithAudioFocus = null; } else { @@ -310,6 +320,10 @@ public class NotificationPlayer implements OnCompletionListener, OnErrorListener } } if (mp != null) { + if (DEBUG) { + Log.i("NotificationPlayer", "About to release MediaPlayer piid:" + + mp.getPlayerIId() + " due to onCompletion"); + } mp.release(); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 541ee2c4fe8f..4a75810f86db 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -51,6 +51,7 @@ import com.android.systemui.qs.external.TileServices; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.plugins.PluginManager; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.phone.AutoTileManager; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarIconController; @@ -95,6 +96,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D private final UiEventLogger mUiEventLogger; private final InstanceIdSequence mInstanceIdSequence; private final CustomTileStatePersister mCustomTileStatePersister; + private final FeatureFlags mFeatureFlags; private final List<Callback> mCallbacks = new ArrayList<>(); private AutoTileManager mAutoTiles; @@ -122,7 +124,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D UiEventLogger uiEventLogger, UserTracker userTracker, SecureSettings secureSettings, - CustomTileStatePersister customTileStatePersister + CustomTileStatePersister customTileStatePersister, + FeatureFlags featureFlags ) { mIconController = iconController; mContext = context; @@ -144,6 +147,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D mUserTracker = userTracker; mSecureSettings = secureSettings; mCustomTileStatePersister = customTileStatePersister; + mFeatureFlags = featureFlags; mainHandler.post(() -> { // This is technically a hack to avoid circular dependency of @@ -265,7 +269,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) { newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode); } - final List<String> tileSpecs = loadTileSpecs(mContext, newValue); + final List<String> tileSpecs = loadTileSpecs(mContext, newValue, mFeatureFlags); int currentUser = mUserTracker.getUserId(); if (currentUser != mCurrentUser) { mUserContext = mUserTracker.getUserContext(); @@ -334,7 +338,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D if (newTiles.isEmpty() && !tileSpecs.isEmpty()) { // If we didn't manage to create any tiles, set it to empty (default) Log.d(TAG, "No valid tiles on tuning changed. Setting to default."); - changeTiles(currentSpecs, loadTileSpecs(mContext, "")); + changeTiles(currentSpecs, loadTileSpecs(mContext, "", mFeatureFlags)); } else { for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onTilesChanged(); @@ -389,7 +393,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D private void changeTileSpecs(Predicate<List<String>> changeFunction) { final String setting = mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser); - final List<String> tileSpecs = loadTileSpecs(mContext, setting); + final List<String> tileSpecs = loadTileSpecs(mContext, setting, mFeatureFlags); if (changeFunction.test(tileSpecs)) { saveTilesToSettings(tileSpecs); } @@ -478,7 +482,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D throw new RuntimeException("Default factory didn't create view for " + tile.getTileSpec()); } - protected static List<String> loadTileSpecs(Context context, String tileList) { + protected static List<String> loadTileSpecs( + Context context, String tileList, FeatureFlags featureFlags) { final Resources res = context.getResources(); if (TextUtils.isEmpty(tileList)) { @@ -511,6 +516,21 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D } } } + if (featureFlags.isProviderModelSettingEnabled()) { + if (!tiles.contains("internet")) { + if (tiles.contains("wifi")) { + // Replace the WiFi with Internet, and remove the Cell + tiles.set(tiles.indexOf("wifi"), "internet"); + tiles.remove("cell"); + } else if (tiles.contains("cell")) { + // Replace the Cell with Internet + tiles.set(tiles.indexOf("cell"), "internet"); + } + } else { + tiles.remove("wifi"); + tiles.remove("cell"); + } + } return tiles; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index 3c2f35b954ea..f2832b3d45ff 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -41,6 +41,7 @@ import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.util.leak.GarbageMonitor; import java.util.ArrayList; @@ -62,6 +63,7 @@ public class TileQueryHelper { private final Executor mBgExecutor; private final Context mContext; private final UserTracker mUserTracker; + private final FeatureFlags mFeatureFlags; private TileStateListener mListener; private boolean mFinished; @@ -71,12 +73,14 @@ public class TileQueryHelper { Context context, UserTracker userTracker, @Main Executor mainExecutor, - @Background Executor bgExecutor + @Background Executor bgExecutor, + FeatureFlags featureFlags ) { mContext = context; mMainExecutor = mainExecutor; mBgExecutor = bgExecutor; mUserTracker = userTracker; + mFeatureFlags = featureFlags; } public void setListener(TileStateListener listener) { @@ -117,6 +121,10 @@ public class TileQueryHelper { } final ArrayList<QSTile> tilesToAdd = new ArrayList<>(); + if (mFeatureFlags.isProviderModelSettingEnabled()) { + possibleTiles.remove("cell"); + possibleTiles.remove("wifi"); + } for (String spec : possibleTiles) { // Only add current and stock tiles that can be created from QSFactoryImpl. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt index dce19cf86b35..cfbe3b29783a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt @@ -54,22 +54,22 @@ open class BlurUtils @Inject constructor( /** * Translates a ratio from 0 to 1 to a blur radius in pixels. */ - fun blurRadiusOfRatio(ratio: Float): Int { + fun blurRadiusOfRatio(ratio: Float): Float { if (ratio == 0f) { - return 0 + return 0f } - return MathUtils.lerp(minBlurRadius.toFloat(), maxBlurRadius.toFloat(), ratio).toInt() + return MathUtils.lerp(minBlurRadius.toFloat(), maxBlurRadius.toFloat(), ratio) } /** * Translates a blur radius in pixels to a ratio between 0 to 1. */ - fun ratioOfBlurRadius(blur: Int): Float { - if (blur == 0) { + fun ratioOfBlurRadius(blur: Float): Float { + if (blur == 0f) { return 0f } return MathUtils.map(minBlurRadius.toFloat(), maxBlurRadius.toFloat(), - 0f /* maxStart */, 1f /* maxStop */, blur.toFloat()) + 0f /* maxStart */, 1f /* maxStop */, blur) } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 503b5c0ee4b0..1c933505172f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -811,13 +811,8 @@ public class KeyguardIndicationController { mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState); } } else if (mKeyguardUpdateMonitor.isScreenOn()) { - if (mKeyguardUpdateMonitor.isUdfpsAvailable()) { - showTransientIndication(mContext.getString(R.string.keyguard_unlock_press), - false /* isError */, true /* hideOnScreenOff */); - } else { - showTransientIndication(mContext.getString(R.string.keyguard_unlock), - false /* isError */, true /* hideOnScreenOff */); - } + showTransientIndication(mContext.getString(R.string.keyguard_unlock), + false /* isError */, true /* hideOnScreenOff */); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index 002c9c7d2544..b8334272c157 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -68,7 +68,7 @@ class NotificationShadeDepthController @Inject constructor( private const val VELOCITY_SCALE = 100f private const val MAX_VELOCITY = 3000f private const val MIN_VELOCITY = -MAX_VELOCITY - private const val INTERACTION_BLUR_FRACTION = 0.4f + private const val INTERACTION_BLUR_FRACTION = 0.8f private const val ANIMATION_BLUR_FRACTION = 1f - INTERACTION_BLUR_FRACTION private const val TAG = "DepthController" } @@ -92,8 +92,6 @@ class NotificationShadeDepthController @Inject constructor( // Only for dumpsys private var lastAppliedBlur = 0 - @VisibleForTesting - var shadeSpring = DepthAnimation() var shadeAnimation = DepthAnimation() @VisibleForTesting @@ -101,12 +99,16 @@ class NotificationShadeDepthController @Inject constructor( var brightnessMirrorVisible: Boolean = false set(value) { field = value - brightnessMirrorSpring.animateTo(if (value) blurUtils.blurRadiusOfRatio(1f) + brightnessMirrorSpring.animateTo(if (value) blurUtils.blurRadiusOfRatio(1f).toInt() else 0) } var qsPanelExpansion = 0f set(value) { + if (value.isNaN()) { + Log.w(TAG, "Invalid qs expansion") + return + } if (field == value) return field = value scheduleUpdate() @@ -134,15 +136,13 @@ class NotificationShadeDepthController @Inject constructor( field = value scheduleUpdate() - if (shadeSpring.radius == 0 && shadeAnimation.radius == 0) { + if (shadeExpansion == 0f && shadeAnimation.radius == 0f) { return } // Do not remove blurs when we're re-enabling them if (!value) { return } - shadeSpring.animateTo(0) - shadeSpring.finishIfRunning() shadeAnimation.animateTo(0) shadeAnimation.finishIfRunning() @@ -161,7 +161,7 @@ class NotificationShadeDepthController @Inject constructor( /** * Blur radius of the wake-up animation on this frame. */ - private var wakeAndUnlockBlurRadius = 0 + private var wakeAndUnlockBlurRadius = 0f set(value) { if (field == value) return field = value @@ -174,26 +174,30 @@ class NotificationShadeDepthController @Inject constructor( @VisibleForTesting val updateBlurCallback = Choreographer.FrameCallback { updateScheduled = false - val normalizedBlurRadius = MathUtils.constrain(shadeAnimation.radius, - blurUtils.minBlurRadius, blurUtils.maxBlurRadius) - var combinedBlur = (shadeSpring.radius * INTERACTION_BLUR_FRACTION + - normalizedBlurRadius * ANIMATION_BLUR_FRACTION).toInt() + val animationRadius = MathUtils.constrain(shadeAnimation.radius, + blurUtils.minBlurRadius.toFloat(), blurUtils.maxBlurRadius.toFloat()) + val expansionRadius = blurUtils.blurRadiusOfRatio( + Interpolators.getNotificationScrimAlpha( + if (shouldApplyShadeBlur()) shadeExpansion else 0f, false)) + var combinedBlur = (expansionRadius * INTERACTION_BLUR_FRACTION + + animationRadius * ANIMATION_BLUR_FRACTION) val qsExpandedRatio = qsPanelExpansion * shadeExpansion combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(qsExpandedRatio)) combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress)) - var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius).toFloat() + var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius) if (blursDisabledForAppLaunch) { shadeRadius = 0f } + var zoomOut = MathUtils.saturate(blurUtils.ratioOfBlurRadius(shadeRadius)) var blur = shadeRadius.toInt() // Make blur be 0 if it is necessary to stop blur effect. if (scrimsVisible) { blur = 0 + zoomOut = 0f } - val zoomOut = blurUtils.ratioOfBlurRadius(blur) if (!blurUtils.supportsBlursOnWindows()) { blur = 0 @@ -266,12 +270,11 @@ class NotificationShadeDepthController @Inject constructor( override fun onStateChanged(newState: Int) { updateShadeAnimationBlur( shadeExpansion, prevTracking, prevShadeVelocity, prevShadeDirection) - updateShadeBlur() + scheduleUpdate() } override fun onDozingChanged(isDozing: Boolean) { if (isDozing) { - shadeSpring.finishIfRunning() shadeAnimation.finishIfRunning() brightnessMirrorSpring.finishIfRunning() } @@ -336,7 +339,7 @@ class NotificationShadeDepthController @Inject constructor( prevTracking = tracking prevTimestamp = timestamp - updateShadeBlur() + scheduleUpdate() } private fun updateShadeAnimationBlur( @@ -399,15 +402,7 @@ class NotificationShadeDepthController @Inject constructor( } shadeAnimation.setStartVelocity(velocity) - shadeAnimation.animateTo(blurUtils.blurRadiusOfRatio(targetBlurNormalized)) - } - - private fun updateShadeBlur() { - var newBlur = 0 - if (shouldApplyShadeBlur()) { - newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion) - } - shadeSpring.animateTo(newBlur) + shadeAnimation.animateTo(blurUtils.blurRadiusOfRatio(targetBlurNormalized).toInt()) } private fun scheduleUpdate(viewToBlur: View? = null) { @@ -433,7 +428,8 @@ class NotificationShadeDepthController @Inject constructor( IndentingPrintWriter(pw, " ").let { it.println("StatusBarWindowBlurController:") it.increaseIndent() - it.println("shadeRadius: ${shadeSpring.radius}") + it.println("shadeExpansion: $shadeExpansion") + it.println("shouldApplyShaeBlur: ${shouldApplyShadeBlur()}") it.println("shadeAnimation: ${shadeAnimation.radius}") it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}") it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius") @@ -452,7 +448,7 @@ class NotificationShadeDepthController @Inject constructor( /** * Blur radius visible on the UI, in pixels. */ - var radius = 0 + var radius = 0f /** * Depth ratio of the current blur radius. @@ -473,12 +469,12 @@ class NotificationShadeDepthController @Inject constructor( private var springAnimation = SpringAnimation(this, object : FloatPropertyCompat<DepthAnimation>("blurRadius") { override fun setValue(rect: DepthAnimation?, value: Float) { - radius = value.toInt() + radius = value scheduleUpdate(view) } override fun getValue(rect: DepthAnimation?): Float { - return radius.toFloat() + return radius } }) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt index 146046b33375..5175977d4e81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt @@ -147,8 +147,12 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) { val fadeIn = subProgress(0f, 0.1f, value) val fadeOutNoise = subProgress(0.4f, 1f, value) - val fadeOutRipple = subProgress(0.3f, 1f, value) - val fadeCircle = subProgress(0f, 0.2f, value) + var fadeOutRipple = 0f + var fadeCircle = 0f + if (shouldFadeOutRipple) { + fadeCircle = subProgress(0f, 0.2f, value) + fadeOutRipple = subProgress(0.3f, 1f, value) + } setUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise)) setUniform("in_fadeCircle", 1 - fadeCircle) setUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple)) @@ -200,4 +204,6 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) { field = value setUniform("in_pixelDensity", value) } + + var shouldFadeOutRipple: Boolean = true } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java index 86c90c7bcb2e..9eb95c409009 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.row; -import android.annotation.ColorInt; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -28,15 +27,12 @@ import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.ViewState; public class FooterView extends StackScrollerDecorView { - private final int mClearAllTopPadding; private FooterViewButton mDismissButton; private FooterViewButton mManageButton; private boolean mShowHistory; public FooterView(Context context, AttributeSet attrs) { super(context, attrs); - mClearAllTopPadding = context.getResources().getDimensionPixelSize( - R.dimen.clear_all_padding_top); } @Override @@ -55,11 +51,6 @@ public class FooterView extends StackScrollerDecorView { mManageButton = findViewById(R.id.manage_text); } - public void setTextColor(@ColorInt int color) { - mManageButton.setTextColor(color); - mDismissButton.setTextColor(color); - } - public void setManageButtonClickListener(OnClickListener listener) { mManageButton.setOnClickListener(listener); } @@ -95,21 +86,25 @@ public class FooterView extends StackScrollerDecorView { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - int textColor = getResources().getColor(R.color.notif_pill_text); - Resources.Theme theme = getContext().getTheme(); - mDismissButton.setBackground( - getResources().getDrawable(R.drawable.notif_footer_btn_background, theme)); - mDismissButton.setTextColor(textColor); - mManageButton.setBackground( - getResources().getDrawable(R.drawable.notif_footer_btn_background, theme)); - mManageButton = findViewById(R.id.manage_text); + updateColors(); mDismissButton.setText(R.string.clear_all_notifications_text); - mManageButton.setTextColor(textColor); mDismissButton.setContentDescription( mContext.getString(R.string.accessibility_clear_all)); showHistory(mShowHistory); } + /** + * Update the text and background colors for the current color palette and night mode setting. + */ + public void updateColors() { + Resources.Theme theme = mContext.getTheme(); + int textColor = getResources().getColor(R.color.notif_pill_text, theme); + mDismissButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background)); + mDismissButton.setTextColor(textColor); + mManageButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background)); + mManageButton.setTextColor(textColor); + } + @Override public ExpandableViewState createExpandableViewState() { return new FooterViewState(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 289c32f17b31..0660daab3720 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -4231,7 +4231,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable final @ColorInt int textColor = Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary); mSectionsManager.setHeaderForegroundColor(textColor); - mFooterView.setTextColor(textColor); + mFooterView.updateColors(); mEmptyShadeView.setTextColor(textColor); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 6e201048abdb..2c76cfeff733 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -116,7 +116,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp /** * Mode in which fingerprint unlocks the device or passive auth (ie face auth) unlocks the - * device while being requested when keyguard is occluded. + * device while being requested when keyguard is occluded or showing. */ public static final int MODE_UNLOCK_COLLAPSING = 5; @@ -425,6 +425,11 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp if (!wasDeviceInteractive) { mPendingShowBouncer = true; } else { + mShadeController.animateCollapsePanels( + CommandQueue.FLAG_EXCLUDE_NONE, + true /* force */, + false /* delayed */, + BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR); mPendingShowBouncer = false; mKeyguardViewController.notifyKeyguardAuthenticated( false /* strongAuth */); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 5aa18e9a3356..373a276e5326 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -3666,6 +3666,7 @@ public class NotificationPanelViewController extends PanelViewController { } public void dozeTimeTick() { + mLockIconViewController.dozeTimeTick(); mKeyguardBottomArea.dozeTimeTick(); mKeyguardStatusViewController.dozeTimeTick(); if (mInterpolatedDarkAmount > 0) { @@ -3879,6 +3880,9 @@ public class NotificationPanelViewController extends PanelViewController { @Override protected TouchHandler createTouchHandler() { return new TouchHandler() { + + private long mLastTouchDownTime = -1L; + @Override public boolean onInterceptTouchEvent(MotionEvent event) { if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) { @@ -3908,6 +3912,19 @@ public class NotificationPanelViewController extends PanelViewController { @Override public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (event.getDownTime() == mLastTouchDownTime) { + // An issue can occur when swiping down after unlock, where multiple down + // events are received in this handler with identical downTimes. Until the + // source of the issue can be located, detect this case and ignore. + // see b/193350347 + Log.w(TAG, "Duplicate down event detected... ignoring"); + return true; + } + mLastTouchDownTime = event.getDownTime(); + } + + if (mBlockTouches || (mQsFullyExpanded && mQs != null && mQs.disallowPanelTouches())) { return false; @@ -3969,10 +3986,6 @@ public class NotificationPanelViewController extends PanelViewController { mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX()); } - if (mLockIconViewController.onTouchEvent(event)) { - return true; - } - handled |= super.onTouch(v, event); return !mDozing || mPulsing || handled; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java index b5d9bd67bd2d..66a6e723ede2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java @@ -35,6 +35,7 @@ import android.view.View; import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; +import com.android.keyguard.LockIconViewController; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dock.DockManager; @@ -88,6 +89,7 @@ public class NotificationShadeWindowViewController { private final NotificationShadeDepthController mDepthController; private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; + private final LockIconViewController mLockIconViewController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private GestureDetector mGestureDetector; @@ -138,7 +140,8 @@ public class NotificationShadeWindowViewController { NotificationPanelViewController notificationPanelViewController, SuperStatusBarViewFactory statusBarViewFactory, NotificationStackScrollLayoutController notificationStackScrollLayoutController, - StatusBarKeyguardViewManager statusBarKeyguardViewManager) { + StatusBarKeyguardViewManager statusBarKeyguardViewManager, + LockIconViewController lockIconViewController) { mInjectionInflationController = injectionInflationController; mCoordinator = coordinator; mPulseExpansionHandler = pulseExpansionHandler; @@ -163,6 +166,7 @@ public class NotificationShadeWindowViewController { mStatusBarViewFactory = statusBarViewFactory; mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mLockIconViewController = lockIconViewController; // This view is not part of the newly inflated expanded status bar. mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); @@ -235,6 +239,7 @@ public class NotificationShadeWindowViewController { if (!isCancel && mService.shouldIgnoreTouch()) { return false; } + if (isDown) { setTouchActive(true); mTouchCancelled = false; @@ -245,6 +250,7 @@ public class NotificationShadeWindowViewController { if (mTouchCancelled || mExpandAnimationRunning) { return false; } + mFalsingCollector.onTouchEvent(ev); mGestureDetector.onTouchEvent(ev); mStatusBarKeyguardViewManager.onTouch(ev); @@ -260,9 +266,17 @@ public class NotificationShadeWindowViewController { if (isDown) { mNotificationStackScrollLayoutController.closeControlsIfOutsideTouch(ev); } + if (mStatusBarStateController.isDozing()) { mService.mDozeScrimController.extendPulse(); } + mLockIconViewController.onTouchEvent( + ev, + () -> mService.wakeUpIfDozing( + SystemClock.uptimeMillis(), + mView, + "LOCK_ICON_TOUCH")); + // In case we start outside of the view bounds (below the status bar), we need to // dispatch // the touch manually as the view system can't accommodate for touches outside of diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index cfcea9684c3b..7d25aeef6f98 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -111,6 +111,20 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump */ private boolean mTransitioningToFullShade; + /** + * Is there currently an unocclusion animation running. Used to avoid bright flickers + * of the notification scrim. + */ + private boolean mUnOcclusionAnimationRunning; + + /** + * Set whether an unocclusion animation is currently running on the notification panel. Used + * to avoid bright flickers of the notification scrim. + */ + public void setUnocclusionAnimationRunning(boolean unocclusionAnimationRunning) { + mUnOcclusionAnimationRunning = unocclusionAnimationRunning; + } + @IntDef(prefix = {"VISIBILITY_"}, value = { TRANSPARENT, SEMI_TRANSPARENT, @@ -418,7 +432,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) { mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY; scheduleUpdate(); - } else if ((oldState == ScrimState.AOD // leaving doze + } else if (((oldState == ScrimState.AOD || oldState == ScrimState.PULSING) // leaving doze && (!mDozeParameters.getAlwaysOn() || mState == ScrimState.UNLOCKED)) || (mState == ScrimState.AOD && !mDozeParameters.getDisplayNeedsBlanking())) { // Scheduling a frame isn't enough when: @@ -466,6 +480,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump public void onExpandingFinished() { mTracking = false; + setUnocclusionAnimationRunning(false); } @VisibleForTesting @@ -694,6 +709,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mNotificationsTint = mState.getNotifTint(); mBehindTint = behindTint; } + if (mUnOcclusionAnimationRunning && mState == ScrimState.KEYGUARD) { + // We're unoccluding the keyguard and don't want to have a bright flash. + mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA; + mNotificationsTint = ScrimState.KEYGUARD.getNotifTint(); + } } if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) { throw new IllegalStateException("Scrim opacity is NaN for state: " + mState diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index 06811932ac0c..2c0de629de8a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -244,7 +244,8 @@ public enum ScrimState { ? mKeyguardFadingAwayDuration : StatusBar.FADE_KEYGUARD_DURATION; - mAnimateChange = !mLaunchingAffordanceWithPreview; + boolean fromAod = previousState == AOD || previousState == PULSING; + mAnimateChange = !mLaunchingAffordanceWithPreview && !fromAod; mFrontTint = Color.TRANSPARENT; mBehindTint = Color.BLACK; 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 d257165ae24e..e2194ad61b45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -592,6 +592,7 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode); mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode); + mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode); } }; @@ -1148,6 +1149,9 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarView.setPanel(mNotificationPanelViewController); mStatusBarView.setScrimController(mScrimController); mStatusBarView.setExpansionChangedListeners(mExpansionChangedListeners); + for (ExpansionChangedListener listener : mExpansionChangedListeners) { + sendInitialExpansionAmount(listener); + } // CollapsedStatusBarFragment re-inflated PhoneStatusBarView and both of // mStatusBarView.mExpanded and mStatusBarView.mBouncerShowing are false. @@ -3579,6 +3583,7 @@ public class StatusBar extends SystemUI implements DemoMode, public void animateKeyguardUnoccluding() { mNotificationPanelViewController.setExpandedFraction(0f); animateExpandNotificationsPanel(); + mScrimController.setUnocclusionAnimationRunning(true); } /** @@ -3909,7 +3914,8 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onDozeAmountChanged(float linear, float eased) { if (mFeatureFlags.useNewLockscreenAnimations() - && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { + && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal) + && !mBiometricUnlockController.isWakeAndUnlock()) { mLightRevealScrim.setRevealAmount(1f - linear); } } @@ -4466,10 +4472,8 @@ public class StatusBar extends SystemUI implements DemoMode, ScrimState state = mStatusBarKeyguardViewManager.bouncerNeedsScrimming() ? ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER; mScrimController.transitionTo(state); - } else if (isInLaunchTransition() - || mLaunchCameraWhenFinishedWaking - || launchingAffordanceWithPreview) { - // TODO(b/170133395) Investigate whether Emergency Gesture flag should be included here. + } else if (launchingAffordanceWithPreview) { + // We want to avoid animating when launching with a preview. mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); } else if (mBrightnessMirrorVisible) { mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR); @@ -4934,6 +4938,14 @@ public class StatusBar extends SystemUI implements DemoMode, public void addExpansionChangedListener(@NonNull ExpansionChangedListener listener) { mExpansionChangedListeners.add(listener); + sendInitialExpansionAmount(listener); + } + + private void sendInitialExpansionAmount(ExpansionChangedListener expansionChangedListener) { + if (mStatusBarView != null) { + expansionChangedListener.onExpansionChanged(mStatusBarView.getExpansionFraction(), + mStatusBarView.isExpanded()); + } } public void removeExpansionChangedListener(@NonNull ExpansionChangedListener listener) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 8a7708aaa8c2..3188a522dfad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -194,6 +194,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private boolean mLastGesturalNav; private boolean mLastIsDocked; private boolean mLastPulsing; + private boolean mLastAnimatedToSleep; private int mLastBiometricMode; private boolean mQsExpanded; private boolean mAnimatedToSleep; @@ -990,6 +991,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mLastBiometricMode = mBiometricUnlockController.getMode(); mLastGesturalNav = mGesturalNav; mLastIsDocked = mIsDocked; + mLastAnimatedToSleep = mAnimatedToSleep; mStatusBar.onKeyguardViewManagerStatesUpdated(); } @@ -1033,7 +1035,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb boolean hideWhileDozing = mLastDozing && mLastBiometricMode != MODE_WAKE_AND_UNLOCK_PULSING; boolean keyguardWithGestureNav = (keyguardShowing && !mLastDozing || mLastPulsing && !mLastIsDocked) && mLastGesturalNav; - return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing + return (!mLastAnimatedToSleep && !keyguardShowing && !hideWhileDozing || mLastBouncerShowing || mLastRemoteInputActive || keyguardWithGestureNav || mLastGlobalActionsVisible); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java index fcfc9670b8b0..705761854532 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java @@ -245,5 +245,11 @@ public interface KeyguardStateController extends CallbackController<Callback> { * animation. */ default void onKeyguardDismissAmountChanged() {} + + /** + * Triggered when the notification panel is starting or has finished + * fading away on transition to an app. + */ + default void onLaunchTransitionFadingAwayChanged() {} } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 64750bd803d5..f787ecf37372 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -343,6 +343,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum @Override public void setLaunchTransitionFadingAway(boolean fadingAway) { mLaunchTransitionFadingAway = fadingAway; + new ArrayList<>(mCallbacks).forEach(Callback::onLaunchTransitionFadingAwayChanged); } @Override |