From 0805cb8110cea8c6a63bb7c7330d6b2518bdc856 Mon Sep 17 00:00:00 2001 From: Joe Bolinger Date: Wed, 8 Dec 2021 16:25:40 -0800 Subject: [PATCH] Split UdfpsController and backfill tests. This splits the monolithic original class into a smaller event handler (original) that delegates UI/request state to a new overlay state holder class, which is migrated to Kotlin. Test: atest UdfpsControllerOverlayTest UdfpsControllerTest Test: manual (enroll manually and BP test app) Bug: 205875955 Change-Id: I8d12a3d130694d2a3fdc1ccb44cf2d30acd92376 --- .../systemui/biometrics/UdfpsController.java | 430 ++++-------------- .../biometrics/UdfpsControllerOverlay.kt | 354 ++++++++++++++ .../biometrics/UdfpsControllerOverlayTest.kt | 288 ++++++++++++ .../biometrics/UdfpsControllerTest.java | 81 +--- 4 files changed, 729 insertions(+), 424 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 1da9f2178b46c..ebf73315c3031 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -24,15 +24,11 @@ import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.graphics.PixelFormat; -import android.graphics.Point; import android.graphics.RectF; -import android.hardware.biometrics.BiometricOverlayConstants; import android.hardware.biometrics.SensorLocationInternal; import android.hardware.display.DisplayManager; import android.hardware.fingerprint.FingerprintManager; @@ -42,16 +38,13 @@ import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; import android.os.Handler; import android.os.PowerManager; import android.os.Process; -import android.os.RemoteException; import android.os.Trace; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; import android.util.Log; -import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.Surface; import android.view.VelocityTracker; import android.view.View; import android.view.WindowManager; @@ -95,7 +88,7 @@ import kotlin.Unit; * controls/manages all UDFPS sensors. In other words, a single controller is registered with * {@link com.android.server.biometrics.sensors.fingerprint.FingerprintService}, and interfaces such * as {@link FingerprintManager#onPointerDown(int, int, int, float, float)} or - * {@link IUdfpsOverlayController#showUdfpsOverlay(int)} should all have + * {@link IUdfpsOverlayController#showUdfpsOverlay} should all have * {@code sensorId} parameters. */ @SuppressWarnings("deprecation") @@ -136,7 +129,6 @@ public class UdfpsController implements DozeReceiver { // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple // sensors, this, in addition to a lot of the code here, will be updated. @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps; - private final WindowManager.LayoutParams mCoreLayoutParams; // Tracks the velocity of a touch to help filter out the touches that move too fast. @Nullable private VelocityTracker mVelocityTracker; @@ -150,9 +142,8 @@ public class UdfpsController implements DozeReceiver { // TODO: We should probably try to make touch/illumination things more of a FSM private boolean mGoodCaptureReceived; - @Nullable private UdfpsView mView; // The current request from FingerprintService. Null if no current request. - @Nullable ServerRequest mServerRequest; + @Nullable UdfpsControllerOverlay mOverlay; // The fingerprint AOD trigger doesn't provide an ACTION_UP/ACTION_CANCEL event to tell us when // to turn off high brightness mode. To get around this limitation, the state of the AOD @@ -164,7 +155,7 @@ public class UdfpsController implements DozeReceiver { private Runnable mAodInterruptRunnable; private boolean mOnFingerDown; private boolean mAttemptedToDismissKeyguard; - private Set mCallbacks = new HashSet<>(); + private final Set mCallbacks = new HashSet<>(); @VisibleForTesting public static final VibrationAttributes VIBRATION_ATTRIBUTES = @@ -193,67 +184,20 @@ public class UdfpsController implements DozeReceiver { } }; - /** - * Keeps track of state within a single FingerprintService request. Note that this state - * persists across configuration changes, etc, since it is considered a single request. - * - * TODO: Perhaps we can move more global variables into here - */ - private static class ServerRequest { - // Reason the overlay has been requested. See IUdfpsOverlayController for definitions. - final int mRequestReason; - @NonNull final IUdfpsOverlayControllerCallback mCallback; - @Nullable final UdfpsEnrollHelper mEnrollHelper; - - ServerRequest(int requestReason, @NonNull IUdfpsOverlayControllerCallback callback, - @Nullable UdfpsEnrollHelper enrollHelper) { - mRequestReason = requestReason; - mCallback = callback; - mEnrollHelper = enrollHelper; - } - - void onEnrollmentProgress(int remaining) { - if (mEnrollHelper != null) { - mEnrollHelper.onEnrollmentProgress(remaining); - } - } - - void onAcquiredGood() { - if (mEnrollHelper != null) { - mEnrollHelper.animateIfLastStep(); - } - } - - void onEnrollmentHelp() { - if (mEnrollHelper != null) { - mEnrollHelper.onEnrollmentHelp(); - } - } - - void onUserCanceled() { - try { - mCallback.onUserCanceled(); - } catch (RemoteException e) { - Log.e(TAG, "Remote exception", e); - } - } - } - public class UdfpsOverlayController extends IUdfpsOverlayController.Stub { @Override public void showUdfpsOverlay(int sensorId, int reason, @NonNull IUdfpsOverlayControllerCallback callback) { - mFgExecutor.execute(() -> { - final UdfpsEnrollHelper enrollHelper; - if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR - || reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING) { - enrollHelper = new UdfpsEnrollHelper(mContext, mFingerprintManager, reason); - } else { - enrollHelper = null; - } - mServerRequest = new ServerRequest(reason, callback, enrollHelper); - updateOverlay(); - }); + mFgExecutor.execute( + () -> UdfpsController.this.showUdfpsOverlay(new UdfpsControllerOverlay( + mContext, mFingerprintManager, mInflater, mWindowManager, + mAccessibilityManager, mStatusBarStateController, + mPanelExpansionStateManager, mKeyguardViewManager, + mKeyguardUpdateMonitor, mDialogManager, mDumpManager, + mLockscreenShadeTransitionController, mConfigurationController, + mSystemClock, mKeyguardStateController, + mUnlockedScreenOffAnimationController, mSensorProps, mHbmProvider, + reason, callback, UdfpsController.this::onTouch))); } @Override @@ -266,57 +210,55 @@ public class UdfpsController implements DozeReceiver { + "mKeyguardUpdateMonitor.isFingerprintDetectionRunning()=true"); } - mServerRequest = null; - updateOverlay(); + UdfpsController.this.hideUdfpsOverlay(); }); } @Override public void onAcquiredGood(int sensorId) { mFgExecutor.execute(() -> { - if (mView == null) { - Log.e(TAG, "Null view when onAcquiredGood for sensorId: " + sensorId); + if (mOverlay == null) { + Log.e(TAG, "Null request when onAcquiredGood for sensorId: " + sensorId); return; } mGoodCaptureReceived = true; - mView.stopIllumination(); - if (mServerRequest != null) { - mServerRequest.onAcquiredGood(); - } else { - Log.e(TAG, "Null serverRequest when onAcquiredGood"); + final UdfpsView view = mOverlay.getOverlayView(); + if (view != null) { + view.stopIllumination(); } + mOverlay.onAcquiredGood(); }); } @Override public void onEnrollmentProgress(int sensorId, int remaining) { mFgExecutor.execute(() -> { - if (mServerRequest == null) { + if (mOverlay == null) { Log.e(TAG, "onEnrollProgress received but serverRequest is null"); return; } - mServerRequest.onEnrollmentProgress(remaining); + mOverlay.onEnrollmentProgress(remaining); }); } @Override public void onEnrollmentHelp(int sensorId) { mFgExecutor.execute(() -> { - if (mServerRequest == null) { + if (mOverlay == null) { Log.e(TAG, "onEnrollmentHelp received but serverRequest is null"); return; } - mServerRequest.onEnrollmentHelp(); + mOverlay.onEnrollmentHelp(); }); } @Override public void setDebugMessage(int sensorId, String message) { mFgExecutor.execute(() -> { - if (mView == null) { + if (mOverlay == null || mOverlay.isHiding()) { return; } - mView.setDebugMessage(message); + mOverlay.getOverlayView().setDebugMessage(message); }); } } @@ -341,14 +283,13 @@ public class UdfpsController implements DozeReceiver { private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (mServerRequest != null - && mServerRequest.mRequestReason != REASON_AUTH_KEYGUARD + if (mOverlay != null + && mOverlay.getRequestReason() != REASON_AUTH_KEYGUARD && Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { Log.d(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received, mRequestReason: " - + mServerRequest.mRequestReason); - mServerRequest.onUserCanceled(); - mServerRequest = null; - updateOverlay(); + + mOverlay.getRequestReason()); + mOverlay.cancel(); + hideUdfpsOverlay(); } } }; @@ -357,23 +298,12 @@ public class UdfpsController implements DozeReceiver { * Forwards touches to the udfps controller / view */ public boolean onTouch(MotionEvent event) { - if (mView == null) { + if (mOverlay == null || mOverlay.isHiding()) { return false; } - return onTouch(mView, event, false); + return onTouch(mOverlay.getOverlayView(), event, false); } - @SuppressLint("ClickableViewAccessibility") - private final UdfpsView.OnTouchListener mOnTouchListener = (view, event) -> - onTouch(view, event, true); - - @SuppressLint("ClickableViewAccessibility") - private final UdfpsView.OnHoverListener mOnHoverListener = (view, event) -> - onTouch(view, event, true); - - private final AccessibilityManager.TouchExplorationStateChangeListener - mTouchExplorationStateChangeListener = enabled -> updateTouchListener(); - /** * @param x coordinate * @param y coordinate @@ -387,15 +317,15 @@ public class UdfpsController implements DozeReceiver { return udfpsView.isWithinSensorArea(x, y); } - if (mView == null || mView.getAnimationViewController() == null) { + if (mOverlay == null || mOverlay.getAnimationViewController() == null) { return false; } - return !mView.getAnimationViewController().shouldPauseAuth() + return !mOverlay.getAnimationViewController().shouldPauseAuth() && getSensorLocation().contains(x, y); } - private boolean onTouch(View view, MotionEvent event, boolean fromUdfpsView) { + private boolean onTouch(@NonNull View view, @NonNull MotionEvent event, boolean fromUdfpsView) { UdfpsView udfpsView = (UdfpsView) view; final boolean isIlluminationRequested = udfpsView.isIlluminationRequested(); boolean handled = false; @@ -492,7 +422,7 @@ public class UdfpsController implements DozeReceiver { } } else { Log.v(TAG, "onTouch | finger outside"); - onFingerUp(); + onFingerUp(udfpsView); } } Trace.endSection(); @@ -509,7 +439,7 @@ public class UdfpsController implements DozeReceiver { } Log.v(TAG, "onTouch | finger up"); mAttemptedToDismissKeyguard = false; - onFingerUp(); + onFingerUp(udfpsView); mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); Trace.endSection(); break; @@ -521,8 +451,8 @@ public class UdfpsController implements DozeReceiver { } private boolean shouldTryToDismissKeyguard() { - return mView.getAnimationViewController() != null - && mView.getAnimationViewController() instanceof UdfpsKeyguardViewController + return mOverlay != null + && mOverlay.getAnimationViewController() instanceof UdfpsKeyguardViewController && mKeyguardStateController.canDismissLockScreen() && !mAttemptedToDismissKeyguard; } @@ -596,17 +526,6 @@ public class UdfpsController implements DozeReceiver { return Unit.INSTANCE; }); - mCoreLayoutParams = new WindowManager.LayoutParams( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, - 0 /* flags set in computeLayoutParams() */, - PixelFormat.TRANSLUCENT); - mCoreLayoutParams.setTitle(TAG); - mCoreLayoutParams.setFitInsetsTypes(0); - mCoreLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; - mCoreLayoutParams.layoutInDisplayCutoutMode = - WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - mCoreLayoutParams.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; - mFingerprintManager.setUdfpsOverlayController(new UdfpsOverlayController()); final IntentFilter filter = new IntentFilter(); @@ -644,8 +563,11 @@ public class UdfpsController implements DozeReceiver { @Override public void dozeTimeTick() { - if (mView != null) { - mView.dozeTimeTick(); + if (mOverlay != null) { + final UdfpsView view = mOverlay.getOverlayView(); + if (view != null) { + view.dozeTimeTick(); + } } } @@ -664,233 +586,63 @@ public class UdfpsController implements DozeReceiver { location.sensorLocationY + location.sensorRadius); } - private void updateOverlay() { - mExecution.assertIsMainThread(); - - if (mServerRequest != null) { - showUdfpsOverlay(mServerRequest); - } else { + private void redrawOverlay() { + UdfpsControllerOverlay overlay = mOverlay; + if (overlay != null) { hideUdfpsOverlay(); + showUdfpsOverlay(overlay); } } - private boolean shouldRotate(@Nullable UdfpsAnimationViewController animation) { - if (!(animation instanceof UdfpsKeyguardViewController)) { - // always rotate view if we're not on the keyguard - return true; - } - - // on the keyguard, make sure we don't rotate if we're going to sleep or not occluded - if (mKeyguardUpdateMonitor.isGoingToSleep() || !mKeyguardStateController.isOccluded()) { - return false; - } - - return true; - } - - private WindowManager.LayoutParams computeLayoutParams( - @Nullable UdfpsAnimationViewController animation) { - final int paddingX = animation != null ? animation.getPaddingX() : 0; - final int paddingY = animation != null ? animation.getPaddingY() : 0; - - mCoreLayoutParams.flags = Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; - if (animation != null && animation.listenForTouchesOutsideView()) { - mCoreLayoutParams.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH; - } - - // Default dimensions assume portrait mode. - final SensorLocationInternal location = mSensorProps.getLocation(); - mCoreLayoutParams.x = location.sensorLocationX - location.sensorRadius - paddingX; - mCoreLayoutParams.y = location.sensorLocationY - location.sensorRadius - paddingY; - mCoreLayoutParams.height = 2 * location.sensorRadius + 2 * paddingX; - mCoreLayoutParams.width = 2 * location.sensorRadius + 2 * paddingY; - - Point p = new Point(); - // Gets the size based on the current rotation of the display. - mContext.getDisplay().getRealSize(p); - - // Transform dimensions if the device is in landscape mode - switch (mContext.getDisplay().getRotation()) { - case Surface.ROTATION_90: - if (!shouldRotate(animation)) { - Log.v(TAG, "skip rotating udfps location ROTATION_90"); - break; - } else { - Log.v(TAG, "rotate udfps location ROTATION_90"); - } - mCoreLayoutParams.x = location.sensorLocationY - location.sensorRadius - - paddingX; - mCoreLayoutParams.y = p.y - location.sensorLocationX - location.sensorRadius - - paddingY; - break; - - case Surface.ROTATION_270: - if (!shouldRotate(animation)) { - Log.v(TAG, "skip rotating udfps location ROTATION_270"); - break; - } else { - Log.v(TAG, "rotate udfps location ROTATION_270"); - } - mCoreLayoutParams.x = p.x - location.sensorLocationY - location.sensorRadius - - paddingX; - mCoreLayoutParams.y = location.sensorLocationX - location.sensorRadius - - paddingY; - break; - - default: - // Do nothing to stay in portrait mode. - // Keyguard is always in portrait mode. - } - // avoid announcing window title - mCoreLayoutParams.accessibilityTitle = " "; - return mCoreLayoutParams; - } - - private void onOrientationChanged() { // When the configuration changes it's almost always necessary to destroy and re-create // the overlay's window to pass it the new LayoutParams. // Hiding the overlay will destroy its window. It's safe to hide the overlay regardless // of whether it is already hidden. final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth(); - hideUdfpsOverlay(); // If the overlay needs to be shown, this will re-create and show the overlay with the // updated LayoutParams. Otherwise, the overlay will remain hidden. - updateOverlay(); + redrawOverlay(); if (wasShowingAltAuth) { mKeyguardViewManager.showGenericBouncer(true); } } - private void showUdfpsOverlay(@NonNull ServerRequest request) { + private void showUdfpsOverlay(@NonNull UdfpsControllerOverlay overlay) { mExecution.assertIsMainThread(); - final int reason = request.mRequestReason; - if (mView == null) { - try { - Log.v(TAG, "showUdfpsOverlay | adding window reason=" + reason); - - mView = (UdfpsView) mInflater.inflate(R.layout.udfps_view, null, false); - mOnFingerDown = false; - mView.setSensorProperties(mSensorProps); - mView.setHbmProvider(mHbmProvider); - UdfpsAnimationViewController animation = inflateUdfpsAnimation(reason); - mAttemptedToDismissKeyguard = false; - if (animation != null) { - animation.init(); - mView.setAnimationViewController(animation); - } - mOrientationListener.enable(); - - // This view overlaps the sensor area, so prevent it from being selectable - // during a11y. - if (reason == BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR - || reason == BiometricOverlayConstants.REASON_ENROLL_ENROLLING - || reason == BiometricOverlayConstants.REASON_AUTH_BP) { - mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - } - - mWindowManager.addView(mView, computeLayoutParams(animation)); - mAccessibilityManager.addTouchExplorationStateChangeListener( - mTouchExplorationStateChangeListener); - updateTouchListener(); - } catch (RuntimeException e) { - Log.e(TAG, "showUdfpsOverlay | failed to add window", e); - } + mOverlay = overlay; + if (overlay.show(this)) { + Log.v(TAG, "showUdfpsOverlay | adding window reason=" + + overlay.getRequestReason()); + mOnFingerDown = false; + mAttemptedToDismissKeyguard = false; + mOrientationListener.enable(); } else { Log.v(TAG, "showUdfpsOverlay | the overlay is already showing"); } } - @Nullable - private UdfpsAnimationViewController inflateUdfpsAnimation(int reason) { - switch (reason) { - case BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR: - case BiometricOverlayConstants.REASON_ENROLL_ENROLLING: - UdfpsEnrollView enrollView = (UdfpsEnrollView) mInflater.inflate( - R.layout.udfps_enroll_view, null); - mView.addView(enrollView); - enrollView.updateSensorLocation(mSensorProps); - return new UdfpsEnrollViewController( - enrollView, - mServerRequest.mEnrollHelper, - mStatusBarStateController, - mPanelExpansionStateManager, - mDialogManager, - mDumpManager - ); - case BiometricOverlayConstants.REASON_AUTH_KEYGUARD: - UdfpsKeyguardView keyguardView = (UdfpsKeyguardView) - mInflater.inflate(R.layout.udfps_keyguard_view, null); - mView.addView(keyguardView); - return new UdfpsKeyguardViewController( - keyguardView, - mStatusBarStateController, - mPanelExpansionStateManager, - mKeyguardViewManager, - mKeyguardUpdateMonitor, - mDumpManager, - mLockscreenShadeTransitionController, - mConfigurationController, - mSystemClock, - mKeyguardStateController, - mUnlockedScreenOffAnimationController, - mDialogManager, - this - ); - case BiometricOverlayConstants.REASON_AUTH_BP: - // note: empty controller, currently shows no visual affordance - UdfpsBpView bpView = (UdfpsBpView) mInflater.inflate(R.layout.udfps_bp_view, null); - mView.addView(bpView); - return new UdfpsBpViewController( - bpView, - mStatusBarStateController, - mPanelExpansionStateManager, - mDialogManager, - mDumpManager - ); - case BiometricOverlayConstants.REASON_AUTH_OTHER: - case BiometricOverlayConstants.REASON_AUTH_SETTINGS: - UdfpsFpmOtherView authOtherView = (UdfpsFpmOtherView) - mInflater.inflate(R.layout.udfps_fpm_other_view, null); - mView.addView(authOtherView); - return new UdfpsFpmOtherViewController( - authOtherView, - mStatusBarStateController, - mPanelExpansionStateManager, - mDialogManager, - mDumpManager - ); - default: - Log.e(TAG, "Animation for reason " + reason + " not supported yet"); - return null; - } - } - private void hideUdfpsOverlay() { mExecution.assertIsMainThread(); - if (mView != null) { - Log.v(TAG, "hideUdfpsOverlay | removing window"); + if (mOverlay != null) { // Reset the controller back to its starting state. - onFingerUp(); - boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth(); - mWindowManager.removeView(mView); - mView.setOnTouchListener(null); - mView.setOnHoverListener(null); - mView.setAnimationViewController(null); - if (wasShowingAltAuth) { + final UdfpsView oldView = mOverlay.getOverlayView(); + if (oldView != null) { + onFingerUp(oldView); + } + final boolean removed = mOverlay.hide(); + if (mKeyguardViewManager.isShowingAlternateAuth()) { mKeyguardViewManager.resetAlternateAuth(true); } - mAccessibilityManager.removeTouchExplorationStateChangeListener( - mTouchExplorationStateChangeListener); - mView = null; + Log.v(TAG, "hideUdfpsOverlay | removing window: " + removed); } else { Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden"); } + mOverlay = null; mOrientationListener.disable(); } @@ -954,7 +706,9 @@ public class UdfpsController implements DozeReceiver { * the user lifts their finger. */ void onCancelUdfps() { - onFingerUp(); + if (mOverlay != null && mOverlay.getOverlayView() != null) { + onFingerUp(mOverlay.getOverlayView()); + } if (!mIsAodInterruptActive) { return; } @@ -971,12 +725,12 @@ public class UdfpsController implements DozeReceiver { private void onFingerDown(int x, int y, float minor, float major) { mExecution.assertIsMainThread(); - if (mView == null) { - Log.w(TAG, "Null view in onFingerDown"); + if (mOverlay == null) { + Log.w(TAG, "Null request in onFingerDown"); return; } - if (mView.getAnimationViewController() instanceof UdfpsKeyguardViewController + if (mOverlay.getAnimationViewController() instanceof UdfpsKeyguardViewController && !mStatusBarStateController.isDozing()) { mKeyguardBypassController.setUserHasDeviceEntryIntent(true); } @@ -991,25 +745,25 @@ public class UdfpsController implements DozeReceiver { mOnFingerDown = true; mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major); Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0); - Trace.beginAsyncSection("UdfpsController.e2e.startIllumination", 0); - mView.startIllumination(() -> { - mFingerprintManager.onUiReady(mSensorProps.sensorId); - Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0); - }); + + final UdfpsView view = mOverlay.getOverlayView(); + if (view != null) { + Trace.beginAsyncSection("UdfpsController.e2e.startIllumination", 0); + view.startIllumination(() -> { + mFingerprintManager.onUiReady(mSensorProps.sensorId); + Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0); + }); + } for (Callback cb : mCallbacks) { cb.onFingerDown(); } } - private void onFingerUp() { + private void onFingerUp(@NonNull UdfpsView view) { mExecution.assertIsMainThread(); mActivePointerId = -1; mGoodCaptureReceived = false; - if (mView == null) { - Log.w(TAG, "Null view in onFingerUp"); - return; - } if (mOnFingerDown) { mFingerprintManager.onPointerUp(mSensorProps.sensorId); for (Callback cb : mCallbacks) { @@ -1017,22 +771,8 @@ public class UdfpsController implements DozeReceiver { } } mOnFingerDown = false; - if (mView.isIlluminationRequested()) { - mView.stopIllumination(); - } - } - - private void updateTouchListener() { - if (mView == null) { - return; - } - - if (mAccessibilityManager.isTouchExplorationEnabled()) { - mView.setOnHoverListener(mOnHoverListener); - mView.setOnTouchListener(null); - } else { - mView.setOnHoverListener(null); - mView.setOnTouchListener(mOnTouchListener); + if (view.isIlluminationRequested()) { + view.stopIllumination(); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt new file mode 100644 index 0000000000000..e0912508d1780 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -0,0 +1,354 @@ +/* + * 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.annotation.SuppressLint +import android.annotation.UiThread +import android.content.Context +import android.graphics.PixelFormat +import android.graphics.Point +import android.hardware.biometrics.BiometricOverlayConstants +import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING +import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR +import android.hardware.biometrics.BiometricOverlayConstants.ShowReason +import android.hardware.biometrics.SensorLocationInternal +import android.hardware.fingerprint.FingerprintManager +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal +import android.hardware.fingerprint.IUdfpsOverlayControllerCallback +import android.os.RemoteException +import android.util.Log +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.Surface +import android.view.View +import android.view.WindowManager +import android.view.accessibility.AccessibilityManager +import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener +import androidx.annotation.LayoutRes +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.R +import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.LockscreenShadeTransitionController +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.statusbar.phone.SystemUIDialogManager +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.time.SystemClock + +private const val TAG = "UdfpsControllerOverlay" + +/** + * Keeps track of the overlay state and UI resources associated with a single FingerprintService + * request. This state can persist across configuration changes via the [show] and [hide] + * methods. + */ +@UiThread +class UdfpsControllerOverlay( + private val context: Context, + fingerprintManager: FingerprintManager, + private val inflater: LayoutInflater, + private val windowManager: WindowManager, + private val accessibilityManager: AccessibilityManager, + private val statusBarStateController: StatusBarStateController, + private val panelExpansionStateManager: PanelExpansionStateManager, + private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val dialogManager: SystemUIDialogManager, + private val dumpManager: DumpManager, + private val transitionController: LockscreenShadeTransitionController, + private val configurationController: ConfigurationController, + private val systemClock: SystemClock, + private val keyguardStateController: KeyguardStateController, + private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, + private val sensorProps: FingerprintSensorPropertiesInternal, + private var hbmProvider: UdfpsHbmProvider, + @ShowReason val requestReason: Int, + private val controllerCallback: IUdfpsOverlayControllerCallback, + private val onTouch: (View, MotionEvent, Boolean) -> Boolean +) { + /** The view, when [isShowing], or null. */ + var overlayView: UdfpsView? = null + private set + + private var overlayTouchListener: TouchExplorationStateChangeListener? = null + + private val coreLayoutParams = WindowManager.LayoutParams( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, + 0 /* flags set in computeLayoutParams() */, + PixelFormat.TRANSLUCENT + ).apply { + title = TAG + fitInsetsTypes = 0 + gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT + layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY + } + + /** A helper if the [requestReason] was due to enrollment. */ + val enrollHelper: UdfpsEnrollHelper? = if (requestReason.isEnrollmentReason()) { + UdfpsEnrollHelper(context, fingerprintManager, requestReason) + } else { + null + } + + /** If the overlay is currently showing. */ + val isShowing: Boolean + get() = overlayView != null + + /** Opposite of [isShowing]. */ + val isHiding: Boolean + get() = overlayView == null + + /** The animation controller if the overlay [isShowing]. */ + val animationViewController: UdfpsAnimationViewController<*>? + get() = overlayView?.animationViewController + + /** Show the overlay or return false and do nothing if it is already showing. */ + @SuppressLint("ClickableViewAccessibility") + fun show(controller: UdfpsController): Boolean { + if (overlayView == null) { + try { + overlayView = (inflater.inflate( + R.layout.udfps_view, null, false + ) as UdfpsView).apply { + sensorProperties = sensorProps + setHbmProvider(hbmProvider) + val animation = inflateUdfpsAnimation(this, controller) + if (animation != null) { + animation.init() + animationViewController = animation + } + // This view overlaps the sensor area + // prevent it from being selectable during a11y + if (requestReason.isImportantForAccessibility()) { + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + } + + windowManager.addView(this, + coreLayoutParams.updateForLocation(sensorProps.location, animation)) + + overlayTouchListener = TouchExplorationStateChangeListener { + if (accessibilityManager.isTouchExplorationEnabled) { + setOnHoverListener { v, event -> onTouch(v, event, true) } + setOnTouchListener(null) + } else { + setOnHoverListener(null) + setOnTouchListener { v, event -> onTouch(v, event, true) } + } + } + accessibilityManager.addTouchExplorationStateChangeListener( + overlayTouchListener!! + ) + overlayTouchListener?.onTouchExplorationStateChanged(true) + } + } catch (e: RuntimeException) { + Log.e(TAG, "showUdfpsOverlay | failed to add window", e) + } + return true + } + + Log.v(TAG, "showUdfpsOverlay | the overlay is already showing") + return false + } + + private fun inflateUdfpsAnimation( + view: UdfpsView, + controller: UdfpsController + ): UdfpsAnimationViewController<*>? { + return when (requestReason) { + REASON_ENROLL_FIND_SENSOR, + REASON_ENROLL_ENROLLING -> { + UdfpsEnrollViewController( + view.addUdfpsView(R.layout.udfps_enroll_view) { + updateSensorLocation(sensorProps) + }, + enrollHelper ?: throw IllegalStateException("no enrollment helper"), + statusBarStateController, + panelExpansionStateManager, + dialogManager, + dumpManager + ) + } + BiometricOverlayConstants.REASON_AUTH_KEYGUARD -> { + UdfpsKeyguardViewController( + view.addUdfpsView(R.layout.udfps_keyguard_view), + statusBarStateController, + panelExpansionStateManager, + statusBarKeyguardViewManager, + keyguardUpdateMonitor, + dumpManager, + transitionController, + configurationController, + systemClock, + keyguardStateController, + unlockedScreenOffAnimationController, + dialogManager, + controller + ) + } + BiometricOverlayConstants.REASON_AUTH_BP -> { + // note: empty controller, currently shows no visual affordance + UdfpsBpViewController( + view.addUdfpsView(R.layout.udfps_bp_view), + statusBarStateController, + panelExpansionStateManager, + dialogManager, + dumpManager + ) + } + BiometricOverlayConstants.REASON_AUTH_OTHER, + BiometricOverlayConstants.REASON_AUTH_SETTINGS -> { + UdfpsFpmOtherViewController( + view.addUdfpsView(R.layout.udfps_fpm_other_view), + statusBarStateController, + panelExpansionStateManager, + dialogManager, + dumpManager + ) + } + else -> { + Log.e(TAG, "Animation for reason $requestReason not supported yet") + null + } + } + } + + /** Hide the overlay or return false and do nothing if it is already hidden. */ + fun hide(): Boolean { + val wasShowing = isShowing + + overlayView?.apply { + if (isIlluminationRequested) { + stopIllumination() + } + windowManager.removeView(this) + setOnTouchListener(null) + setOnHoverListener(null) + animationViewController = null + overlayTouchListener?.let { + accessibilityManager.removeTouchExplorationStateChangeListener(it) + } + } + overlayView = null + overlayTouchListener = null + + return wasShowing + } + + fun onEnrollmentProgress(remaining: Int) { + enrollHelper?.onEnrollmentProgress(remaining) + } + + fun onAcquiredGood() { + enrollHelper?.animateIfLastStep() + } + + fun onEnrollmentHelp() { + enrollHelper?.onEnrollmentHelp() + } + + /** Cancel this request. */ + fun cancel() { + try { + controllerCallback.onUserCanceled() + } catch (e: RemoteException) { + Log.e(TAG, "Remote exception", e) + } + } + + private fun WindowManager.LayoutParams.updateForLocation( + location: SensorLocationInternal, + animation: UdfpsAnimationViewController<*>? + ): WindowManager.LayoutParams { + val paddingX = animation?.paddingX ?: 0 + val paddingY = animation?.paddingY ?: 0 + flags = (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS + or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH) + if (animation != null && animation.listenForTouchesOutsideView()) { + flags = flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + } + + // Default dimensions assume portrait mode. + x = location.sensorLocationX - location.sensorRadius - paddingX + y = location.sensorLocationY - location.sensorRadius - paddingY + height = 2 * location.sensorRadius + 2 * paddingX + width = 2 * location.sensorRadius + 2 * paddingY + + // Gets the size based on the current rotation of the display. + val p = Point() + context.display!!.getRealSize(p) + when (context.display!!.rotation) { + Surface.ROTATION_90 -> { + if (!shouldRotate(animation)) { + Log.v(TAG, "skip rotating udfps location ROTATION_90") + } else { + Log.v(TAG, "rotate udfps location ROTATION_90") + x = (location.sensorLocationY - location.sensorRadius - paddingX) + y = (p.y - location.sensorLocationX - location.sensorRadius - paddingY) + } + } + Surface.ROTATION_270 -> { + if (!shouldRotate(animation)) { + Log.v(TAG, "skip rotating udfps location ROTATION_270") + } else { + Log.v(TAG, "rotate udfps location ROTATION_270") + x = (p.x - location.sensorLocationY - location.sensorRadius - paddingX) + y = (location.sensorLocationX - location.sensorRadius - paddingY) + } + } + else -> {} + } + + // avoid announcing window title + accessibilityTitle = " " + + return this + } + + private fun shouldRotate(animation: UdfpsAnimationViewController<*>?): Boolean { + if (animation !is UdfpsKeyguardViewController) { + // always rotate view if we're not on the keyguard + return true + } + + // on the keyguard, make sure we don't rotate if we're going to sleep or not occluded + return !(keyguardUpdateMonitor.isGoingToSleep || !keyguardStateController.isOccluded) + } + + private inline fun UdfpsView.addUdfpsView( + @LayoutRes id: Int, + init: T.() -> Unit = {} + ): T { + val subView = inflater.inflate(id, null) as T + addView(subView) + subView.init() + return subView + } +} + +@ShowReason +private fun Int.isEnrollmentReason() = + this == REASON_ENROLL_FIND_SENSOR || this == REASON_ENROLL_ENROLLING + +@ShowReason +private fun Int.isImportantForAccessibility() = + this == REASON_ENROLL_FIND_SENSOR || + this == REASON_ENROLL_ENROLLING || + this == BiometricOverlayConstants.REASON_AUTH_BP diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt new file mode 100644 index 0000000000000..84eb93556c86d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -0,0 +1,288 @@ +/* + * 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.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP +import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD +import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER +import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS +import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING +import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR +import android.hardware.biometrics.BiometricOverlayConstants.ShowReason +import android.hardware.biometrics.SensorLocationInternal +import android.hardware.fingerprint.FingerprintManager +import android.hardware.fingerprint.IUdfpsOverlayControllerCallback +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.WindowManager +import android.view.accessibility.AccessibilityManager +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.LockscreenShadeTransitionController +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.statusbar.phone.SystemUIDialogManager +import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController +import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.time.SystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` as whenever +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper(setAsMainLooper = true) +class UdfpsControllerOverlayTest : SysuiTestCase() { + + @JvmField @Rule + var rule = MockitoJUnit.rule() + + @Mock private lateinit var fingerprintManager: FingerprintManager + @Mock private lateinit var inflater: LayoutInflater + @Mock private lateinit var windowManager: WindowManager + @Mock private lateinit var accessibilityManager: AccessibilityManager + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var panelExpansionStateManager: PanelExpansionStateManager + @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var dialogManager: SystemUIDialogManager + @Mock private lateinit var dumpManager: DumpManager + @Mock private lateinit var transitionController: LockscreenShadeTransitionController + @Mock private lateinit var configurationController: ConfigurationController + @Mock private lateinit var systemClock: SystemClock + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock + private lateinit var unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController + @Mock private lateinit var hbmProvider: UdfpsHbmProvider + @Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback + @Mock private lateinit var udfpsController: UdfpsController + @Mock private lateinit var udfpsView: UdfpsView + @Mock private lateinit var udfpsEnrollView: UdfpsEnrollView + + private val sensorProps = SensorLocationInternal("", 10, 100, 20) + .asFingerprintSensorProperties() + private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true } + private lateinit var controllerOverlay: UdfpsControllerOverlay + + @Before + fun setup() { + context.orCreateTestableResources.addOverride(R.integer.config_udfpsEnrollProgressBar, 20) + whenever(inflater.inflate(R.layout.udfps_view, null, false)) + .thenReturn(udfpsView) + whenever(inflater.inflate(R.layout.udfps_enroll_view, null)) + .thenReturn(udfpsEnrollView) + whenever(inflater.inflate(R.layout.udfps_bp_view, null)) + .thenReturn(mock(UdfpsBpView::class.java)) + whenever(inflater.inflate(R.layout.udfps_keyguard_view, null)) + .thenReturn(mock(UdfpsKeyguardView::class.java)) + whenever(inflater.inflate(R.layout.udfps_fpm_other_view, null)) + .thenReturn(mock(UdfpsFpmOtherView::class.java)) + whenever(udfpsEnrollView.context).thenReturn(context) + } + + private fun withReason(@ShowReason reason: Int, block: () -> Unit) { + controllerOverlay = UdfpsControllerOverlay( + context, fingerprintManager, inflater, windowManager, accessibilityManager, + statusBarStateController, panelExpansionStateManager, statusBarKeyguardViewManager, + keyguardUpdateMonitor, dialogManager, dumpManager, transitionController, + configurationController, systemClock, keyguardStateController, + unlockedScreenOffAnimationController, sensorProps, hbmProvider, reason, + controllerCallback, onTouch) + block() + } + + @Test + fun showUdfpsOverlay_bp() = withReason(REASON_AUTH_BP) { showUdfpsOverlay() } + + @Test + fun showUdfpsOverlay_keyguard() = withReason(REASON_AUTH_KEYGUARD) { showUdfpsOverlay() } + + @Test + fun showUdfpsOverlay_settings() = withReason(REASON_AUTH_SETTINGS) { showUdfpsOverlay() } + + @Test + fun showUdfpsOverlay_locate() = withReason(REASON_ENROLL_FIND_SENSOR) { + showUdfpsOverlay(isEnrollUseCase = true) + } + + @Test + fun showUdfpsOverlay_enroll() = withReason(REASON_ENROLL_ENROLLING) { + showUdfpsOverlay(isEnrollUseCase = true) + } + + @Test + fun showUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { showUdfpsOverlay() } + + private fun showUdfpsOverlay(isEnrollUseCase: Boolean = false) { + val didShow = controllerOverlay.show(udfpsController) + + verify(windowManager).addView(eq(controllerOverlay.overlayView), any()) + verify(udfpsView).setHbmProvider(eq(hbmProvider)) + verify(udfpsView).sensorProperties = eq(sensorProps) + verify(udfpsView).animationViewController = any() + verify(udfpsView).addView(any()) + + assertThat(didShow).isTrue() + assertThat(controllerOverlay.isShowing).isTrue() + assertThat(controllerOverlay.isHiding).isFalse() + assertThat(controllerOverlay.overlayView).isNotNull() + if (isEnrollUseCase) { + verify(udfpsEnrollView).updateSensorLocation(eq(sensorProps)) + assertThat(controllerOverlay.enrollHelper).isNotNull() + } else { + assertThat(controllerOverlay.enrollHelper).isNull() + } + } + + @Test + fun hideUdfpsOverlay_bp() = withReason(REASON_AUTH_BP) { hideUdfpsOverlay() } + + @Test + fun hideUdfpsOverlay_keyguard() = withReason(REASON_AUTH_KEYGUARD) { hideUdfpsOverlay() } + + @Test + fun hideUdfpsOverlay_settings() = withReason(REASON_AUTH_SETTINGS) { hideUdfpsOverlay() } + + @Test + fun hideUdfpsOverlay_locate() = withReason(REASON_ENROLL_FIND_SENSOR) { hideUdfpsOverlay() } + + @Test + fun hideUdfpsOverlay_enroll() = withReason(REASON_ENROLL_ENROLLING) { hideUdfpsOverlay() } + + @Test + fun hideUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { hideUdfpsOverlay() } + + private fun hideUdfpsOverlay() { + val didShow = controllerOverlay.show(udfpsController) + val view = controllerOverlay.overlayView + val didHide = controllerOverlay.hide() + + verify(windowManager).removeView(eq(view)) + + assertThat(didShow).isTrue() + assertThat(didHide).isTrue() + assertThat(controllerOverlay.overlayView).isNull() + assertThat(controllerOverlay.animationViewController).isNull() + assertThat(controllerOverlay.isShowing).isFalse() + assertThat(controllerOverlay.isHiding).isTrue() + } + + @Test + fun canNotHide() = withReason(REASON_AUTH_BP) { + assertThat(controllerOverlay.hide()).isFalse() + } + + @Test + fun canNotReshow() = withReason(REASON_AUTH_BP) { + assertThat(controllerOverlay.show(udfpsController)).isTrue() + assertThat(controllerOverlay.show(udfpsController)).isFalse() + } + + @Test + fun forwardEnrollProgressEvents() = withReason(REASON_ENROLL_ENROLLING) { + controllerOverlay.show(udfpsController) + + with(EnrollListener(controllerOverlay)) { + controllerOverlay.onEnrollmentProgress(/* remaining */20) + controllerOverlay.onAcquiredGood() + assertThat(progress).isTrue() + assertThat(help).isFalse() + assertThat(acquired).isFalse() + } + } + + @Test + fun forwardEnrollHelpEvents() = withReason(REASON_ENROLL_ENROLLING) { + controllerOverlay.show(udfpsController) + + with(EnrollListener(controllerOverlay)) { + controllerOverlay.onEnrollmentHelp() + assertThat(progress).isFalse() + assertThat(help).isTrue() + assertThat(acquired).isFalse() + } + } + + @Test + fun forwardEnrollAcquiredEvents() = withReason(REASON_ENROLL_ENROLLING) { + controllerOverlay.show(udfpsController) + + with(EnrollListener(controllerOverlay)) { + controllerOverlay.onEnrollmentProgress(/* remaining */ 1) + controllerOverlay.onAcquiredGood() + assertThat(progress).isTrue() + assertThat(help).isFalse() + assertThat(acquired).isTrue() + } + } + + @Test + fun cancels() = withReason(REASON_AUTH_BP) { + controllerOverlay.cancel() + verify(controllerCallback).onUserCanceled() + } + + @Test + fun stopIlluminatingOnHide() = withReason(REASON_AUTH_BP) { + whenever(udfpsView.isIlluminationRequested).thenReturn(true) + + controllerOverlay.show(udfpsController) + controllerOverlay.hide() + verify(udfpsView).stopIllumination() + } +} + +private class EnrollListener( + overlay: UdfpsControllerOverlay, + var progress: Boolean = false, + var help: Boolean = false, + var acquired: Boolean = false +) : UdfpsEnrollHelper.Listener { + + init { + overlay.enrollHelper!!.setListener(this) + } + + override fun onEnrollmentProgress(remaining: Int, totalSteps: Int) { + progress = true + } + + override fun onEnrollmentHelp(remaining: Int, totalSteps: Int) { + help = true + } + + override fun onLastStepAcquired() { + acquired = true + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 8fa2a9a1b8b8f..d5725786e0d27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -163,11 +163,11 @@ public class UdfpsControllerTest extends SysuiTestCase { @Mock private UdfpsEnrollView mEnrollView; @Mock - private UdfpsKeyguardView mKeyguardView; - @Mock private UdfpsBpView mBpView; @Mock private UdfpsFpmOtherView mFpmOtherView; + @Mock + private UdfpsKeyguardView mKeyguardView; private UdfpsAnimationViewController mUdfpsKeyguardViewController = mock(UdfpsKeyguardViewController.class); @Mock @@ -413,83 +413,6 @@ public class UdfpsControllerTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); } - @Test - public void showUdfpsOverlay_addsViewToWindow_bp() throws RemoteException { - showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_AUTH_BP); - } - - @Test - public void showUdfpsOverlay_addsViewToWindow_keyguard() throws RemoteException { - showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_AUTH_KEYGUARD); - } - - @Test - public void showUdfpsOverlay_addsViewToWindow_settings() throws RemoteException { - showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_AUTH_SETTINGS); - } - - @Test - public void showUdfpsOverlay_addsViewToWindow_enroll_locate() throws RemoteException { - showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR); - } - - @Test - public void showUdfpsOverlay_addsViewToWindow_enroll() throws RemoteException { - showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_ENROLL_ENROLLING); - } - - @Test - public void showUdfpsOverlay_addsViewToWindow_other() throws RemoteException { - showUdfpsOverlay_addsViewToWindow(BiometricOverlayConstants.REASON_AUTH_OTHER); - } - - private void showUdfpsOverlay_addsViewToWindow( - @BiometricOverlayConstants.ShowReason int reason) throws RemoteException { - mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, reason, - mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - verify(mWindowManager).addView(eq(mUdfpsView), any()); - } - - @Test - public void hideUdfpsOverlay_removesViewFromWindow_bp() throws RemoteException { - hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_AUTH_BP); - } - - @Test - public void hideUdfpsOverlay_removesViewFromWindow_keyguard() throws RemoteException { - hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_AUTH_KEYGUARD); - } - - @Test - public void hideUdfpsOverlay_removesViewFromWindow_settings() throws RemoteException { - hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_AUTH_SETTINGS); - } - - @Test - public void hideUdfpsOverlay_removesViewFromWindow_enroll_locate() throws RemoteException { - hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR); - } - - @Test - public void hideUdfpsOverlay_removesViewFromWindow_enroll() throws RemoteException { - hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_ENROLL_ENROLLING); - } - - @Test - public void hideUdfpsOverlay_removesViewFromWindow_other() throws RemoteException { - hideUdfpsOverlay_removesViewFromWindow(BiometricOverlayConstants.REASON_AUTH_OTHER); - } - - private void hideUdfpsOverlay_removesViewFromWindow( - @BiometricOverlayConstants.ShowReason int reason) throws RemoteException { - mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID); - mFgExecutor.runAllReady(); - verify(mWindowManager).removeView(eq(mUdfpsView)); - } - @Test public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException { // GIVEN overlay was showing and the udfps bouncer is showing