summaryrefslogtreecommitdiff
path: root/packages/SystemUI/src
diff options
context:
space:
mode:
authorEric Arseneau <earseneau@google.com>2021-12-13 11:13:18 -0800
committerEric Arseneau <earseneau@google.com>2021-12-13 12:53:06 -0800
commit48cbb14e1815430efd7ff5086e7a4c70e75d475f (patch)
treea752bdec4c37856853b1e12f02daeebee65ffe13 /packages/SystemUI/src
parent88daf9ce34ae13a85aa6d41d42d7f6d5f7d07c1a (diff)
parentea8a98f046c81acd5bdb72169a00ce133e4d27c5 (diff)
Merge mpr-2021-11-05
Change-Id: If7c68a17857eee4194f28413b938d5647820162c
Diffstat (limited to 'packages/SystemUI/src')
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconView.java33
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java129
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt181
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt239
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java394
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java177
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java256
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLog.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java78
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java1
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