diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index 74d7d5fed2d8d..d7ae9ef841bf4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -166,6 +166,8 @@ public abstract class AuthBiometricView extends LinearLayout { private final Runnable mResetErrorRunnable; private final Runnable mResetHelpRunnable; + private Animator.AnimatorListener mJankListener; + private final OnClickListener mBackgroundClickListener = (view) -> { if (mState == STATE_AUTHENTICATED) { Log.w(TAG, "Ignoring background click after authenticated"); @@ -282,6 +284,10 @@ public abstract class AuthBiometricView extends LinearLayout { mRequireConfirmation = requireConfirmation && supportsRequireConfirmation(); } + void setJankListener(Animator.AnimatorListener jankListener) { + mJankListener = jankListener; + } + @VisibleForTesting final void updateSize(@AuthDialog.DialogSize int newSize) { Log.v(TAG, "Current size: " + mSize + " New size: " + newSize); @@ -372,6 +378,9 @@ public abstract class AuthBiometricView extends LinearLayout { } }); + if (mJankListener != null) { + as.addListener(mJankListener); + } as.play(iconAnimator).with(opacityAnimator); as.start(); // Animate the panel @@ -427,6 +436,9 @@ public abstract class AuthBiometricView extends LinearLayout { animators.add(translationAnimator); animators.add(opacityAnimator); + if (mJankListener != null) { + as.addListener(mJankListener); + } as.playTogether(animators); as.setDuration(mAnimationDurationLong * 2 / 3); as.start(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 3c2de895bf267..5639c78b00e25 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -19,6 +19,9 @@ package com.android.systemui.biometrics; import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT; import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode; +import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION; + +import android.animation.Animator; import android.annotation.DurationMillisLong; import android.annotation.IntDef; import android.annotation.NonNull; @@ -53,6 +56,7 @@ import android.widget.LinearLayout; import android.widget.ScrollView; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; @@ -85,6 +89,13 @@ public class AuthContainerView extends LinearLayout private static final int STATE_ANIMATING_OUT = 4; private static final int STATE_GONE = 5; + /** Shows biometric prompt dialog animation. */ + private static final String SHOW = "show"; + /** Dismiss biometric prompt dialog animation. */ + private static final String DISMISS = "dismiss"; + /** Transit biometric prompt dialog to pin, password, pattern credential panel. */ + private static final String TRANSIT = "transit"; + @Retention(RetentionPolicy.SOURCE) @IntDef({STATE_UNKNOWN, STATE_ANIMATING_IN, STATE_PENDING_DISMISS, STATE_SHOWING, STATE_ANIMATING_OUT, STATE_GONE}) @@ -99,6 +110,7 @@ public class AuthContainerView extends LinearLayout private final CredentialCallback mCredentialCallback; private final LockPatternUtils mLockPatternUtils; private final WakefulnessLifecycle mWakefulnessLifecycle; + private final InteractionJankMonitor mInteractionJankMonitor; @VisibleForTesting final BiometricCallback mBiometricCallback; @@ -208,10 +220,12 @@ public class AuthContainerView extends LinearLayout @Nullable List faceProps, @NonNull WakefulnessLifecycle wakefulnessLifecycle, @NonNull UserManager userManager, - @NonNull LockPatternUtils lockPatternUtils) { + @NonNull LockPatternUtils lockPatternUtils, + @NonNull InteractionJankMonitor jankMonitor) { mConfig.mSensorIds = sensorIds; return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle, - userManager, lockPatternUtils, new Handler(Looper.getMainLooper()), bgExecutor); + userManager, lockPatternUtils, jankMonitor, new Handler(Looper.getMainLooper()), + bgExecutor); } } @@ -264,6 +278,7 @@ public class AuthContainerView extends LinearLayout @NonNull WakefulnessLifecycle wakefulnessLifecycle, @NonNull UserManager userManager, @NonNull LockPatternUtils lockPatternUtils, + @NonNull InteractionJankMonitor jankMonitor, @NonNull Handler mainHandler, @NonNull @Background DelayableExecutor bgExecutor) { super(config.mContext); @@ -290,6 +305,7 @@ public class AuthContainerView extends LinearLayout mPanelView = mFrameLayout.findViewById(R.id.panel); mPanelController = new AuthPanelController(mContext, mPanelView); mBackgroundExecutor = bgExecutor; + mInteractionJankMonitor = jankMonitor; // Inflate biometric view only if necessary. if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) { @@ -329,6 +345,8 @@ public class AuthContainerView extends LinearLayout mBiometricView.setBackgroundView(mBackgroundView); mBiometricView.setUserId(mConfig.mUserId); mBiometricView.setEffectiveUserId(mEffectiveUserId); + mBiometricView.setJankListener(getJankListener(mBiometricView, TRANSIT, + AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS)); } // TODO: De-dupe the logic with AuthCredentialPasswordView @@ -447,6 +465,7 @@ public class AuthContainerView extends LinearLayout .translationY(0) .setDuration(animateDuration) .setInterpolator(mLinearOutSlowIn) + .setListener(getJankListener(mPanelView, SHOW, animateDuration)) .withLayer() .withEndAction(this::onDialogAnimatedIn) .start(); @@ -454,6 +473,7 @@ public class AuthContainerView extends LinearLayout .translationY(0) .setDuration(animateDuration) .setInterpolator(mLinearOutSlowIn) + .setListener(getJankListener(mBiometricScrollView, SHOW, animateDuration)) .withLayer() .start(); if (mCredentialView != null && mCredentialView.isAttachedToWindow()) { @@ -462,6 +482,7 @@ public class AuthContainerView extends LinearLayout .translationY(0) .setDuration(animateDuration) .setInterpolator(mLinearOutSlowIn) + .setListener(getJankListener(mCredentialView, SHOW, animateDuration)) .withLayer() .start(); } @@ -470,11 +491,49 @@ public class AuthContainerView extends LinearLayout .setDuration(animateDuration) .setInterpolator(mLinearOutSlowIn) .withLayer() + .setListener(getJankListener(this, SHOW, animateDuration)) .start(); }); } } + private Animator.AnimatorListener getJankListener(View v, String type, long timeout) { + return new Animator.AnimatorListener() { + @Override + public void onAnimationStart(@androidx.annotation.NonNull Animator animation) { + if (!v.isAttachedToWindow()) { + Log.w(TAG, "Un-attached view should not begin Jank trace."); + return; + } + mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder.withView( + CUJ_BIOMETRIC_PROMPT_TRANSITION, v).setTag(type).setTimeout(timeout)); + } + + @Override + public void onAnimationEnd(@androidx.annotation.NonNull Animator animation) { + if (!v.isAttachedToWindow()) { + Log.w(TAG, "Un-attached view should not end Jank trace."); + return; + } + mInteractionJankMonitor.end(CUJ_BIOMETRIC_PROMPT_TRANSITION); + } + + @Override + public void onAnimationCancel(@androidx.annotation.NonNull Animator animation) { + if (!v.isAttachedToWindow()) { + Log.w(TAG, "Un-attached view should not cancel Jank trace."); + return; + } + mInteractionJankMonitor.cancel(CUJ_BIOMETRIC_PROMPT_TRANSITION); + } + + @Override + public void onAnimationRepeat(@androidx.annotation.NonNull Animator animation) { + // no-op + } + }; + } + private static boolean shouldUpdatePositionForUdfps(@NonNull View view) { if (view instanceof AuthBiometricFingerprintView) { return ((AuthBiometricFingerprintView) view).isUdfps(); @@ -658,6 +717,7 @@ public class AuthContainerView extends LinearLayout .translationY(mTranslationY) .setDuration(animateDuration) .setInterpolator(mLinearOutSlowIn) + .setListener(getJankListener(mPanelView, DISMISS, animateDuration)) .withLayer() .withEndAction(endActionRunnable) .start(); @@ -665,6 +725,7 @@ public class AuthContainerView extends LinearLayout .translationY(mTranslationY) .setDuration(animateDuration) .setInterpolator(mLinearOutSlowIn) + .setListener(getJankListener(mBiometricScrollView, DISMISS, animateDuration)) .withLayer() .start(); if (mCredentialView != null && mCredentialView.isAttachedToWindow()) { @@ -672,6 +733,7 @@ public class AuthContainerView extends LinearLayout .translationY(mTranslationY) .setDuration(animateDuration) .setInterpolator(mLinearOutSlowIn) + .setListener(getJankListener(mCredentialView, DISMISS, animateDuration)) .withLayer() .start(); } @@ -679,6 +741,7 @@ public class AuthContainerView extends LinearLayout .alpha(0f) .setDuration(animateDuration) .setInterpolator(mLinearOutSlowIn) + .setListener(getJankListener(this, DISMISS, animateDuration)) .withLayer() .start(); }); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 8c59f373ec910..af0a1ae88b1ab 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -62,6 +62,7 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.os.SomeArgs; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.CoreStartable; @@ -143,6 +144,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba private boolean mAllFingerprintAuthenticatorsRegistered; @NonNull private final UserManager mUserManager; @NonNull private final LockPatternUtils mLockPatternUtils; + @NonNull private final InteractionJankMonitor mInteractionJankMonitor; private final @Background DelayableExecutor mBackgroundExecutor; @VisibleForTesting @@ -549,6 +551,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @NonNull UserManager userManager, @NonNull LockPatternUtils lockPatternUtils, @NonNull StatusBarStateController statusBarStateController, + @NonNull InteractionJankMonitor jankMonitor, @Main Handler handler, @Background DelayableExecutor bgExecutor) { super(context); @@ -566,6 +569,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba mSidefpsControllerFactory = sidefpsControllerFactory; mDisplayManager = displayManager; mWindowManager = windowManager; + mInteractionJankMonitor = jankMonitor; mUdfpsEnrolledForUser = new SparseBooleanArray(); mOrientationListener = new BiometricDisplayListener( @@ -1041,7 +1045,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba return getScaleFactor(); }) .build(bgExecutor, sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle, - userManager, lockPatternUtils); + userManager, lockPatternUtils, mInteractionJankMonitor); } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index bbc3968cbd242..31fe39a2a1703 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -34,6 +34,7 @@ import android.view.WindowInsets import android.view.WindowManager import android.widget.ScrollView import androidx.test.filters.SmallTest +import com.android.internal.jank.InteractionJankMonitor import com.android.internal.widget.LockPatternUtils import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -71,6 +72,8 @@ class AuthContainerViewTest : SysuiTestCase() { lateinit var wakefulnessLifecycle: WakefulnessLifecycle @Mock lateinit var windowToken: IBinder + @Mock + lateinit var interactionJankMonitor: InteractionJankMonitor private var authContainer: TestAuthContainerView? = null @@ -356,6 +359,7 @@ class AuthContainerViewTest : SysuiTestCase() { wakefulnessLifecycle, userManager, lockPatternUtils, + interactionJankMonitor, Handler(TestableLooper.get(this).looper), FakeExecutor(FakeSystemClock()) ) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index eefc412e3ffad..d948a99f8ad89 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -77,6 +77,7 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.R; +import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.WakefulnessLifecycle; @@ -150,6 +151,8 @@ public class AuthControllerTest extends SysuiTestCase { private LockPatternUtils mLockPatternUtils; @Mock private StatusBarStateController mStatusBarStateController; + @Mock + private InteractionJankMonitor mInteractionJankMonitor; @Captor ArgumentCaptor mAuthenticatorsRegisteredCaptor; @Captor @@ -788,8 +791,8 @@ public class AuthControllerTest extends SysuiTestCase { super(context, execution, commandQueue, activityTaskManager, windowManager, fingerprintManager, faceManager, udfpsControllerFactory, sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle, - mUserManager, mLockPatternUtils, statusBarStateController, mHandler, - mBackgroundExecutor); + mUserManager, mLockPatternUtils, statusBarStateController, + mInteractionJankMonitor, mHandler, mBackgroundExecutor); } @Override