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:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user