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
This commit is contained in:
Joe Bolinger
2021-12-08 16:25:40 -08:00
parent 05c83b715a
commit 0805cb8110
4 changed files with 729 additions and 424 deletions

View File

@@ -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<Callback> mCallbacks = new HashSet<>();
private final Set<Callback> 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();
}
}

View File

@@ -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 <reified T : View> 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

View File

@@ -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
}
}

View File

@@ -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