diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-09-14 17:17:36 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2021-09-14 17:17:36 +0000 |
commit | e5e7baf75a002bc59e234033e0be944c23bcdc4d (patch) | |
tree | 0516b126eaf0350ddc8067e922851ebfffeee009 | |
parent | d7685815e4ee2a6992f0074377d16b2874238569 (diff) | |
parent | 4a32d22caf1b94417a47d162ea03a388b55bae6d (diff) | |
download | base-android12-d1-s3-release.tar.gz |
Merge cherrypicks of [15828443, 15828444, 15828333, 15828463, 15828464, 15828465, 15828466, 15828627, 15828628, 15828629, 15828510, 15827861, 15828445, 15828446, 15828647, 15828468, 15828121, 15828382, 15828383, 15828384, 15827400, 15827401, 15828648, 15828649, 15828650, 15828651, 15828652, 15828653, 15828654, 15828655, 15828656, 15828657, 15828658, 15828659, 15828593, 15828594, 15828595, 15828596, 15828597, 15828495, 15828660, 15828663, 15827862] into sparse-7706357-L85600000951124950android-12.0.0_r7android-12.0.0_r6android12-d1-s4-releaseandroid12-d1-s3-release
Change-Id: Ifa9a587f8449edf92bbbff56609b3825a17fd0a0
19 files changed, 904 insertions, 184 deletions
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index ff2d2eb3d334..fa7330fb84eb 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -98,6 +98,7 @@ public class Surface implements Parcelable { private static native int nativeSetFrameRate( long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy); + private static native void nativeDestroy(long nativeObject); public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR = new Parcelable.Creator<Surface>() { @@ -339,6 +340,9 @@ public class Surface implements Parcelable { */ @UnsupportedAppUsage public void destroy() { + if (mNativeObject != 0) { + nativeDestroy(mNativeObject); + } release(); } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index c1956e45653b..4b8b607de089 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -903,7 +903,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mSurfaceAlpha = 1f; synchronized (mSurfaceControlLock) { - mSurface.release(); + mSurface.destroy(); if (mBlastBufferQueue != null) { mBlastBufferQueue.destroy(); mBlastBufferQueue = null; diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index 8e7fae7aa061..d12c870d2591 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -50,6 +50,7 @@ import com.android.internal.util.FrameworkStatsLog; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.TimeUnit; /** * A class that allows the app to get the frame metrics from HardwareRendererObserver. @@ -103,6 +104,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener private boolean mCancelled = false; private FrameTrackerListener mListener; private boolean mTracingStarted = false; + private Runnable mWaitForFinishTimedOut; private static class JankInfo { long frameVsyncId; @@ -263,10 +265,16 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener if (mListener != null) { mListener.onCujEvents(mSession, ACTION_SESSION_END); } + // We don't remove observer here, + // will remove it when all the frame metrics in this duration are called back. + // See onFrameMetricsAvailable for the logic of removing the observer. + // Waiting at most 10 seconds for all callbacks to finish. + mWaitForFinishTimedOut = () -> { + Log.e(TAG, "force finish cuj because of time out:" + mSession.getName()); + finish(mJankInfos.size() - 1); + }; + mHandler.postDelayed(mWaitForFinishTimedOut, TimeUnit.SECONDS.toMillis(10)); } - // We don't remove observer here, - // will remove it when all the frame metrics in this duration are called back. - // See onFrameMetricsAvailable for the logic of removing the observer. } /** @@ -396,7 +404,8 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } private void finish(int indexOnOrAfterEnd) { - + mHandler.removeCallbacks(mWaitForFinishTimedOut); + mWaitForFinishTimedOut = null; mMetricsFinalized = true; // The tracing has been ended, remove the observer, see if need to trigger perfetto. @@ -481,7 +490,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } } if (DEBUG) { - Log.i(TAG, "FrameTracker: CUJ=" + mSession.getName() + Log.i(TAG, "finish: CUJ=" + mSession.getName() + " (" + mBeginVsyncId + "," + mEndVsyncId + ")" + " totalFrames=" + totalFramesCount + " missedAppFrames=" + missedAppFramesCount diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index aabcd7f82ac7..610cd7339001 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -103,7 +103,7 @@ public class InteractionJankMonitor { private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName(); private static final String DEFAULT_WORKER_NAME = TAG + "-Worker"; - private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5L); + private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(2L); private static final String SETTINGS_ENABLED_KEY = "enabled"; private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval"; private static final String SETTINGS_THRESHOLD_MISSED_FRAMES_KEY = diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 0957067de603..869b53df2837 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -449,6 +449,11 @@ static jint nativeSetFrameRate(JNIEnv* env, jclass clazz, jlong nativeObject, jf int(changeFrameRateStrategy)); } +static void nativeDestroy(JNIEnv* env, jclass clazz, jlong nativeObject) { + sp<Surface> surface(reinterpret_cast<Surface*>(nativeObject)); + surface->destroy(); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gSurfaceMethods[] = { @@ -477,6 +482,7 @@ static const JNINativeMethod gSurfaceMethods[] = { {"nativeSetAutoRefreshEnabled", "(JZ)I", (void*)nativeSetAutoRefreshEnabled}, {"nativeSetFrameRate", "(JFII)I", (void*)nativeSetFrameRate}, {"nativeGetFromBlastBufferQueue", "(JJ)J", (void*)nativeGetFromBlastBufferQueue}, + {"nativeDestroy", "(J)V", (void*)nativeDestroy}, }; int register_android_view_Surface(JNIEnv* env) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 39ea75d5d1da..ac7a18a3fbd4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -210,12 +210,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(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java index d4077563cfea..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,10 +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; + 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; @@ -51,17 +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); - mSensorOutlinePaint = new Paint(0 /* flags */); mSensorOutlinePaint.setAntiAlias(true); mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_enroll_icon)); @@ -78,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) { @@ -98,41 +251,154 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { } void onEnrollmentProgress(int remaining, int totalSteps) { - if (mEnrollHelper.isCenterEnrollmentComplete()) { - if (mAnimatorSet != null && mAnimatorSet.isRunning()) { - mAnimatorSet.end(); + if (mEnrollHelper == null) { + return; + } + + 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(); + }); - 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(); + 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(); + } + } + + 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(); + } + } + + 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 @@ -142,7 +408,7 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { } // Draw moving target - if (mEnrollHelper.isCenterEnrollmentComplete()) { + if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) { canvas.save(); canvas.translate(mCurrentX, mCurrentY); @@ -162,6 +428,59 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable { mFingerprintDrawable.setAlpha(mAlpha); mSensorOutlinePaint.setAlpha(mAlpha); } + + // 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 4c26e0c767d1..b56543f4851b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java @@ -16,141 +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 SEGMENT_GAP_ANGLE = 12f; - private static final float PROGRESS_BAR_THICKNESS_DP = 12; - - @NonNull private final Context mContext; - @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; + @NonNull private final List<UdfpsEnrollProgressBarSegment> mSegments; public UdfpsEnrollProgressBarDrawable(@NonNull Context context) { - mContext = context; - - 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); + 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; + } } 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); - } - - private void setEnrollmentProgress(float progress) { - if (mLastStepAcquired) { - return; + if (remaining == totalSteps) { + // Show some progress for the initial touch. + setEnrollmentProgress(1); + } else { + setEnrollmentProgress(totalSteps - remaining); } + } - 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(); - invalidateSelf(); - }); - rotationAnimator.start(); + 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; } - if (mProgressAnimator != null && mProgressAnimator.isRunning()) { - mProgressAnimator.cancel(); + 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(); + } } - - mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress); - mProgressAnimator.setDuration(animationDuration); - mProgressAnimator.addUpdateListener(animation -> { - mProgress = (float) animation.getAnimatedValue(); - invalidateSelf(); - }); - mProgressAnimator.start(); } 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 5e58e0841bd0..6f02c64e4cf7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java @@ -83,6 +83,11 @@ public class UdfpsEnrollView extends UdfpsAnimationView { }); } + void onEnrollmentHelp(int remaining, int totalSteps) { + mHandler.post( + () -> mFingerprintProgressDrawable.setEnrollmentProgress(remaining, totalSteps)); + } + void 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/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/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index 3ee3e55c749a..7f89b2629a6a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -139,7 +139,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { () -> mock(AutoTileManager.class), mock(DumpManager.class), mock(BroadcastDispatcher.class), Optional.of(mock(StatusBar.class)), mock(QSLogger.class), mock(UiEventLogger.class), mock(UserTracker.class), - mock(SecureSettings.class), mock(CustomTileStatePersister.class)); + mock(SecureSettings.class), mock(CustomTileStatePersister.class), mFeatureFlags); qs.setHost(host); qs.setListening(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index 9e97f801be3e..4cbad5f15c5f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -63,6 +63,7 @@ import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; 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; @@ -124,6 +125,8 @@ public class QSTileHostTest extends SysuiTestCase { private SecureSettings mSecureSettings; @Mock private CustomTileStatePersister mCustomTileStatePersister; + @Mock + private FeatureFlags mFeatureFlags; private Handler mHandler; private TestableLooper mLooper; @@ -137,9 +140,9 @@ public class QSTileHostTest extends SysuiTestCase { mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler, mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager, mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker, - mSecureSettings, mCustomTileStatePersister); + mSecureSettings, mCustomTileStatePersister, mFeatureFlags); setUpTileFactory(); - + when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(false); when(mSecureSettings.getStringForUser(eq(QSTileHost.TILES_SETTING), anyInt())) .thenReturn(""); } @@ -169,13 +172,13 @@ public class QSTileHostTest extends SysuiTestCase { @Test public void testLoadTileSpecs_emptySetting() { - List<String> tiles = QSTileHost.loadTileSpecs(mContext, ""); + List<String> tiles = QSTileHost.loadTileSpecs(mContext, "", mFeatureFlags); assertFalse(tiles.isEmpty()); } @Test public void testLoadTileSpecs_nullSetting() { - List<String> tiles = QSTileHost.loadTileSpecs(mContext, null); + List<String> tiles = QSTileHost.loadTileSpecs(mContext, null, mFeatureFlags); assertFalse(tiles.isEmpty()); } @@ -189,6 +192,55 @@ public class QSTileHostTest extends SysuiTestCase { } @Test + public void testRemoveWifiAndCellularWithoutInternet() { + when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true); + mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "wifi, spec1, cell, spec2"); + + assertEquals("internet", mQSTileHost.mTileSpecs.get(0)); + assertEquals("spec1", mQSTileHost.mTileSpecs.get(1)); + assertEquals("spec2", mQSTileHost.mTileSpecs.get(2)); + } + + @Test + public void testRemoveWifiAndCellularWithInternet() { + when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true); + mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "wifi, spec1, cell, spec2, internet"); + + assertEquals("spec1", mQSTileHost.mTileSpecs.get(0)); + assertEquals("spec2", mQSTileHost.mTileSpecs.get(1)); + assertEquals("internet", mQSTileHost.mTileSpecs.get(2)); + } + + @Test + public void testRemoveWifiWithoutInternet() { + when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true); + mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1, wifi, spec2"); + + assertEquals("spec1", mQSTileHost.mTileSpecs.get(0)); + assertEquals("internet", mQSTileHost.mTileSpecs.get(1)); + assertEquals("spec2", mQSTileHost.mTileSpecs.get(2)); + } + + @Test + public void testRemoveCellWithInternet() { + when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true); + mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1, spec2, cell, internet"); + + assertEquals("spec1", mQSTileHost.mTileSpecs.get(0)); + assertEquals("spec2", mQSTileHost.mTileSpecs.get(1)); + assertEquals("internet", mQSTileHost.mTileSpecs.get(2)); + } + + @Test + public void testNoWifiNoCellularNoInternet() { + when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(true); + mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2"); + + assertEquals("spec1", mQSTileHost.mTileSpecs.get(0)); + assertEquals("spec2", mQSTileHost.mTileSpecs.get(1)); + } + + @Test public void testSpecWithInvalidDoesNotUseDefault() { mContext.getOrCreateTestableResources() .addOverride(R.string.quick_settings_tiles, "spec1,spec2"); @@ -321,7 +373,7 @@ public class QSTileHostTest extends SysuiTestCase { @Test public void testLoadTileSpec_repeated() { - List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2"); + List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2", mFeatureFlags); assertEquals(2, specs.size()); assertEquals("spec1", specs.get(0)); @@ -332,7 +384,7 @@ public class QSTileHostTest extends SysuiTestCase { public void testLoadTileSpec_repeatedInDefault() { mContext.getOrCreateTestableResources() .addOverride(R.string.quick_settings_tiles_default, "spec1,spec1"); - List<String> specs = QSTileHost.loadTileSpecs(mContext, "default"); + List<String> specs = QSTileHost.loadTileSpecs(mContext, "default", mFeatureFlags); // Remove spurious tiles, like dbg:mem specs.removeIf(spec -> !"spec1".equals(spec)); @@ -343,7 +395,7 @@ public class QSTileHostTest extends SysuiTestCase { public void testLoadTileSpec_repeatedDefaultAndSetting() { mContext.getOrCreateTestableResources() .addOverride(R.string.quick_settings_tiles_default, "spec1"); - List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1"); + List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1", mFeatureFlags); // Remove spurious tiles, like dbg:mem specs.removeIf(spec -> !"spec1".equals(spec)); @@ -371,11 +423,12 @@ public class QSTileHostTest extends SysuiTestCase { Provider<AutoTileManager> autoTiles, DumpManager dumpManager, BroadcastDispatcher broadcastDispatcher, StatusBar statusBar, QSLogger qsLogger, UiEventLogger uiEventLogger, UserTracker userTracker, - SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister) { + SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister, + FeatureFlags featureFlags) { super(context, iconController, defaultFactory, mainHandler, bgLooper, pluginManager, tunerService, autoTiles, dumpManager, broadcastDispatcher, Optional.of(statusBar), qsLogger, uiEventLogger, userTracker, secureSettings, - customTileStatePersister); + customTileStatePersister, featureFlags); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java index 4efcc5c3fc73..c5b67091d197 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java @@ -58,6 +58,7 @@ import com.android.systemui.plugins.qs.QSIconView; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSTileHost; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -108,6 +109,8 @@ public class TileQueryHelperTest extends SysuiTestCase { private PackageManager mPackageManager; @Mock private UserTracker mUserTracker; + @Mock + private FeatureFlags mFeatureFlags; @Captor private ArgumentCaptor<List<TileQueryHelper.TileInfo>> mCaptor; @@ -133,12 +136,12 @@ public class TileQueryHelperTest extends SysuiTestCase { } } ).when(mQSTileHost).createTile(anyString()); - + when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(false); FakeSystemClock clock = new FakeSystemClock(); mMainExecutor = new FakeExecutor(clock); mBgExecutor = new FakeExecutor(clock); mTileQueryHelper = new TileQueryHelper( - mContext, mUserTracker, mMainExecutor, mBgExecutor); + mContext, mUserTracker, mMainExecutor, mBgExecutor, mFeatureFlags); mTileQueryHelper.setListener(mListener); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index 2b1840462291..01fa222896d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -48,6 +48,7 @@ import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSFactoryImpl; 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; @@ -98,6 +99,8 @@ public class TileServicesTest extends SysuiTestCase { private UserTracker mUserTracker; @Mock private SecureSettings mSecureSettings; + @Mock + private FeatureFlags mFeatureFlags; @Before public void setUp() throws Exception { @@ -119,7 +122,8 @@ public class TileServicesTest extends SysuiTestCase { mUiEventLogger, mUserTracker, mSecureSettings, - mock(CustomTileStatePersister.class)); + mock(CustomTileStatePersister.class), + mFeatureFlags); mTileService = new TestTileServices(host, Looper.getMainLooper(), mBroadcastDispatcher, mUserTracker); } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index edf832f0fc22..b4413a4447b7 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -1889,11 +1889,12 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { try { if (!mConfigurationProvider.isDisplayInfoNrAdvancedSupported( r.callingPackage, Binder.getCallingUserHandle())) { - telephonyDisplayInfo = + r.callback.onDisplayInfoChanged( getBackwardCompatibleTelephonyDisplayInfo( - telephonyDisplayInfo); + telephonyDisplayInfo)); + } else { + r.callback.onDisplayInfoChanged(telephonyDisplayInfo); } - r.callback.onDisplayInfoChanged(telephonyDisplayInfo); } catch (RemoteException ex) { mRemoveList.add(r.binder); } |