From 8698dbdf55113bc9c8c2571f5c8bc6922b753600 Mon Sep 17 00:00:00 2001 From: Ilya Matyukhin Date: Mon, 28 Nov 2022 04:36:59 +0000 Subject: [PATCH] Integrate new touch architecture with UdfpsController Bug: 218388821 Bug: 218374828 Test: atest SystemUITests:com.android.systemui.biometrics Change-Id: Ifb32d5f6a94b2b5a1daf9e972414476db4122047 --- .../fingerprint/FingerprintManager.java | 56 ++++- .../systemui/biometrics/UdfpsController.java | 217 ++++++++++++++---- .../biometrics/UdfpsEllipseDetection.kt | 92 -------- .../systemui/biometrics/dagger/UdfpsModule.kt | 43 ++++ .../udfps/BoundingBoxOverlapDetector.kt | 27 +++ .../udfps/EllipseOverlapDetector.kt | 71 ++++++ .../biometrics/udfps/OverlapDetector.kt | 24 ++ .../udfps/SinglePointerTouchProcessor.kt | 60 +++-- .../systemui/dagger/SystemUIModule.java | 2 + .../biometrics/UdfpsControllerTest.java | 105 ++++++++- .../udfps/BoundingBoxOverlapDetectorTest.kt | 109 +++++++++ .../udfps/SinglePointerTouchProcessorTest.kt | 56 +++-- .../biometrics/udfps/FakeOverlapDetector.kt | 27 +++ 13 files changed, 688 insertions(+), 201 deletions(-) delete mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt create mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt create mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt create mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt create mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 085bfca158c18..5c1da1112e2a2 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -938,7 +938,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing public void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major) { if (mService == null) { - Slog.w(TAG, "onFingerDown: no fingerprint service"); + Slog.w(TAG, "onPointerDown: no fingerprint service"); return; } @@ -955,7 +955,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void onPointerUp(long requestId, int sensorId) { if (mService == null) { - Slog.w(TAG, "onFingerDown: no fingerprint service"); + Slog.w(TAG, "onPointerUp: no fingerprint service"); return; } @@ -966,6 +966,58 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } } + /** + * TODO(b/218388821): The parameter list should be replaced with PointerContext. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void onPointerDown( + long requestId, + int sensorId, + int pointerId, + float x, + float y, + float minor, + float major, + float orientation, + long time, + long gestureStart, + boolean isAod) { + if (mService == null) { + Slog.w(TAG, "onPointerDown: no fingerprint service"); + return; + } + + // TODO(b/218388821): Propagate all the parameters to FingerprintService. + Slog.e(TAG, "onPointerDown: not implemented!"); + } + + /** + * TODO(b/218388821): The parameter list should be replaced with PointerContext. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void onPointerUp( + long requestId, + int sensorId, + int pointerId, + float x, + float y, + float minor, + float major, + float orientation, + long time, + long gestureStart, + boolean isAod) { + if (mService == null) { + Slog.w(TAG, "onPointerUp: no fingerprint service"); + return; + } + + // TODO(b/218388821): Propagate all the parameters to FingerprintService. + Slog.e(TAG, "onPointerUp: not implemented!"); + } + /** * @hide */ diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 0ff1ed616715a..5a81bd3e01b6c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -61,6 +61,11 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dumpable; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.biometrics.dagger.BiometricsBackground; +import com.android.systemui.biometrics.udfps.InteractionEvent; +import com.android.systemui.biometrics.udfps.NormalizedTouchData; +import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor; +import com.android.systemui.biometrics.udfps.TouchProcessor; +import com.android.systemui.biometrics.udfps.TouchProcessorResult; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; @@ -142,6 +147,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener; @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator; @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; + @Nullable private final TouchProcessor mTouchProcessor; // 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. @@ -165,7 +171,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { // The current request from FingerprintService. Null if no current request. @Nullable UdfpsControllerOverlay mOverlay; - @Nullable private UdfpsEllipseDetection mUdfpsEllipseDetection; // 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 @@ -322,10 +327,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (!mOverlayParams.equals(overlayParams)) { mOverlayParams = overlayParams; - if (mFeatureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) { - mUdfpsEllipseDetection.updateOverlayParams(overlayParams); - } - final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateBouncer(); // When the bounds change it's always necessary to re-create the overlay's window with @@ -444,6 +445,89 @@ public class UdfpsController implements DozeReceiver, Dumpable { @VisibleForTesting boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { + if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { + return newOnTouch(requestId, event, fromUdfpsView); + } else { + return oldOnTouch(requestId, event, fromUdfpsView); + } + } + + private boolean newOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { + if (!fromUdfpsView) { + Log.e(TAG, "ignoring the touch injected from outside of UdfpsView"); + return false; + } + if (mOverlay == null) { + Log.w(TAG, "ignoring onTouch with null overlay"); + return false; + } + if (!mOverlay.matchesRequestId(requestId)) { + Log.w(TAG, "ignoring stale touch event: " + requestId + " current: " + + mOverlay.getRequestId()); + return false; + } + + final TouchProcessorResult result = mTouchProcessor.processTouch(event, mActivePointerId, + mOverlayParams); + if (result instanceof TouchProcessorResult.Failure) { + Log.w(TAG, ((TouchProcessorResult.Failure) result).getReason()); + return false; + } + + final TouchProcessorResult.ProcessedTouch processedTouch = + (TouchProcessorResult.ProcessedTouch) result; + final NormalizedTouchData data = processedTouch.getTouchData(); + + mActivePointerId = processedTouch.getPointerOnSensorId(); + switch (processedTouch.getEvent()) { + case DOWN: + if (shouldTryToDismissKeyguard()) { + tryDismissingKeyguard(); + } + onFingerDown(requestId, + data.getPointerId(), + data.getX(), + data.getY(), + data.getMinor(), + data.getMajor(), + data.getOrientation(), + data.getTime(), + data.getGestureStart(), + mStatusBarStateController.isDozing()); + break; + + case UP: + case CANCEL: + if (InteractionEvent.CANCEL.equals(processedTouch.getEvent())) { + Log.w(TAG, "This is a CANCEL event that's reported as an UP event!"); + } + mAttemptedToDismissKeyguard = false; + onFingerUp(requestId, + mOverlay.getOverlayView(), + data.getPointerId(), + data.getX(), + data.getY(), + data.getMinor(), + data.getMajor(), + data.getOrientation(), + data.getTime(), + data.getGestureStart(), + mStatusBarStateController.isDozing()); + mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); + break; + + + default: + break; + } + + // We should only consume touches that are within the sensor. By returning "false" for + // touches outside of the sensor, we let other UI components consume these events and act on + // them appropriately. + return processedTouch.getTouchData().isWithinSensor(mOverlayParams.getNativeSensorBounds()); + } + + private boolean oldOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { if (mOverlay == null) { Log.w(TAG, "ignoring onTouch with null overlay"); return false; @@ -473,23 +557,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { mVelocityTracker.clear(); } - boolean withinSensorArea; - if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { - if (mFeatureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) { - // Ellipse detection - withinSensorArea = mUdfpsEllipseDetection.isGoodEllipseOverlap(event); - } else { - // Centroid with expanded overlay - withinSensorArea = - isWithinSensorArea(udfpsView, event.getRawX(), - event.getRawY(), fromUdfpsView); - } - } else { - // Centroid with sensor sized view - withinSensorArea = + final boolean withinSensorArea = isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView); - } - if (withinSensorArea) { Trace.beginAsyncSection("UdfpsController.e2e.onPointerDown", 0); Log.v(TAG, "onTouch | action down"); @@ -516,25 +585,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { ? event.getPointerId(0) : event.findPointerIndex(mActivePointerId); if (idx == event.getActionIndex()) { - boolean actionMoveWithinSensorArea; - if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { - if (mFeatureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) { - // Ellipse detection - actionMoveWithinSensorArea = - mUdfpsEllipseDetection.isGoodEllipseOverlap(event); - } else { - // Centroid with expanded overlay - actionMoveWithinSensorArea = - isWithinSensorArea(udfpsView, event.getRawX(idx), - event.getRawY(idx), fromUdfpsView); - } - } else { - // Centroid with sensor sized view - actionMoveWithinSensorArea = - isWithinSensorArea(udfpsView, event.getX(idx), - event.getY(idx), fromUdfpsView); - } - + final boolean actionMoveWithinSensorArea = + isWithinSensorArea(udfpsView, event.getX(idx), event.getY(idx), + fromUdfpsView); if ((fromUdfpsView || actionMoveWithinSensorArea) && shouldTryToDismissKeyguard()) { Log.v(TAG, "onTouch | dismiss keyguard ACTION_MOVE"); @@ -663,7 +716,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull ActivityLaunchAnimator activityLaunchAnimator, @NonNull Optional alternateTouchProvider, @NonNull @BiometricsBackground Executor biometricsExecutor, - @NonNull PrimaryBouncerInteractor primaryBouncerInteractor) { + @NonNull PrimaryBouncerInteractor primaryBouncerInteractor, + @NonNull SinglePointerTouchProcessor singlePointerTouchProcessor) { mContext = context; mExecution = execution; mVibrator = vibrator; @@ -704,6 +758,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { mBiometricExecutor = biometricsExecutor; mPrimaryBouncerInteractor = primaryBouncerInteractor; + mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) + ? singlePointerTouchProcessor : null; + mDumpManager.registerDumpable(TAG, this); mOrientationListener = new BiometricDisplayListener( @@ -728,10 +785,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { udfpsHapticsSimulator.setUdfpsController(this); udfpsShell.setUdfpsOverlayController(mUdfpsOverlayController); - - if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) { - mUdfpsEllipseDetection = new UdfpsEllipseDetection(mOverlayParams); - } } /** @@ -913,7 +966,36 @@ public class UdfpsController implements DozeReceiver, Dumpable { return mOnFingerDown; } - private void onFingerDown(long requestId, int x, int y, float minor, float major) { + private void onFingerDown( + long requestId, + int x, + int y, + float minor, + float major) { + onFingerDown( + requestId, + MotionEvent.INVALID_POINTER_ID /* pointerId */, + x, + y, + minor, + major, + 0f /* orientation */, + 0L /* time */, + 0L /* gestureStart */, + false /* isAod */); + } + + private void onFingerDown( + long requestId, + int pointerId, + float x, + float y, + float minor, + float major, + float orientation, + long time, + long gestureStart, + boolean isAod) { mExecution.assertIsMainThread(); if (mOverlay == null) { @@ -942,7 +1024,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { mOnFingerDown = true; if (mAlternateTouchProvider != null) { mBiometricExecutor.execute(() -> { - mAlternateTouchProvider.onPointerDown(requestId, x, y, minor, major); + mAlternateTouchProvider.onPointerDown(requestId, (int) x, (int) y, minor, major); }); mFgExecutor.execute(() -> { if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { @@ -950,7 +1032,13 @@ public class UdfpsController implements DozeReceiver, Dumpable { } }); } else { - mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, x, y, minor, major); + if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { + mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y, + minor, major, orientation, time, gestureStart, isAod); + } else { + mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, (int) x, + (int) y, minor, major); + } } Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0); final UdfpsView view = mOverlay.getOverlayView(); @@ -974,6 +1062,32 @@ public class UdfpsController implements DozeReceiver, Dumpable { } private void onFingerUp(long requestId, @NonNull UdfpsView view) { + onFingerUp( + requestId, + view, + MotionEvent.INVALID_POINTER_ID /* pointerId */, + 0f /* x */, + 0f /* y */, + 0f /* minor */, + 0f /* major */, + 0f /* orientation */, + 0L /* time */, + 0L /* gestureStart */, + false /* isAod */); + } + + private void onFingerUp( + long requestId, + @NonNull UdfpsView view, + int pointerId, + float x, + float y, + float minor, + float major, + float orientation, + long time, + long gestureStart, + boolean isAod) { mExecution.assertIsMainThread(); mActivePointerId = -1; mAcquiredReceived = false; @@ -988,7 +1102,12 @@ public class UdfpsController implements DozeReceiver, Dumpable { } }); } else { - mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId); + if (mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)) { + mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId, pointerId, x, + y, minor, major, orientation, time, gestureStart, isAod); + } else { + mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId); + } } for (Callback cb : mCallbacks) { cb.onFingerUp(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt deleted file mode 100644 index 8ae4775467df7..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2022 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.graphics.Point -import android.graphics.Rect -import android.util.RotationUtils -import android.view.MotionEvent -import kotlin.math.cos -import kotlin.math.pow -import kotlin.math.sin - -private const val TAG = "UdfpsEllipseDetection" - -private const val NEEDED_POINTS = 2 - -class UdfpsEllipseDetection(overlayParams: UdfpsOverlayParams) { - var sensorRect = Rect() - var points: Array = emptyArray() - - init { - sensorRect = Rect(overlayParams.sensorBounds) - - points = calculateSensorPoints(sensorRect) - } - - fun updateOverlayParams(params: UdfpsOverlayParams) { - sensorRect = Rect(params.sensorBounds) - - val rot = params.rotation - RotationUtils.rotateBounds( - sensorRect, - params.naturalDisplayWidth, - params.naturalDisplayHeight, - rot - ) - - points = calculateSensorPoints(sensorRect) - } - - fun isGoodEllipseOverlap(event: MotionEvent): Boolean { - return points.count { checkPoint(event, it) } >= NEEDED_POINTS - } - - private fun checkPoint(event: MotionEvent, point: Point): Boolean { - // Calculate if sensor point is within ellipse - // Formula: ((cos(o)(xE - xS) + sin(o)(yE - yS))^2 / a^2) + ((sin(o)(xE - xS) + cos(o)(yE - - // yS))^2 / b^2) <= 1 - val a: Float = cos(event.orientation) * (point.x - event.rawX) - val b: Float = sin(event.orientation) * (point.y - event.rawY) - val c: Float = sin(event.orientation) * (point.x - event.rawX) - val d: Float = cos(event.orientation) * (point.y - event.rawY) - val result = - (a + b).pow(2) / (event.touchMinor / 2).pow(2) + - (c - d).pow(2) / (event.touchMajor / 2).pow(2) - - return result <= 1 - } -} - -fun calculateSensorPoints(sensorRect: Rect): Array { - val sensorX = sensorRect.centerX() - val sensorY = sensorRect.centerY() - val cornerOffset: Int = sensorRect.width() / 4 - val sideOffset: Int = sensorRect.width() / 3 - - return arrayOf( - Point(sensorX - cornerOffset, sensorY - cornerOffset), - Point(sensorX, sensorY - sideOffset), - Point(sensorX + cornerOffset, sensorY - cornerOffset), - Point(sensorX - sideOffset, sensorY), - Point(sensorX, sensorY), - Point(sensorX + sideOffset, sensorY), - Point(sensorX - cornerOffset, sensorY + cornerOffset), - Point(sensorX, sensorY + sideOffset), - Point(sensorX + cornerOffset, sensorY + cornerOffset) - ) -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt new file mode 100644 index 0000000000000..001fed76c1249 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 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.dagger + +import com.android.systemui.biometrics.udfps.BoundingBoxOverlapDetector +import com.android.systemui.biometrics.udfps.EllipseOverlapDetector +import com.android.systemui.biometrics.udfps.OverlapDetector +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import dagger.Module +import dagger.Provides + +/** Dagger module for all things UDFPS. TODO(b/260558624): Move to BiometricsModule. */ +@Module +interface UdfpsModule { + companion object { + + @Provides + @SysUISingleton + fun providesOverlapDetector(featureFlags: FeatureFlags): OverlapDetector { + return if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) { + EllipseOverlapDetector() + } else { + BoundingBoxOverlapDetector() + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt new file mode 100644 index 0000000000000..79a0acb8bbc19 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 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.udfps + +import android.graphics.Rect +import com.android.systemui.dagger.SysUISingleton + +/** Returns whether the touch coordinates are within the sensor's bounding box. */ +@SysUISingleton +class BoundingBoxOverlapDetector : OverlapDetector { + override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean = + touchData.isWithinSensor(nativeSensorBounds) +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt new file mode 100644 index 0000000000000..8572242907520 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 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.udfps + +import android.graphics.Point +import android.graphics.Rect +import com.android.systemui.dagger.SysUISingleton +import kotlin.math.cos +import kotlin.math.pow +import kotlin.math.sin + +/** + * Approximates the touch as an ellipse and determines whether the ellipse has a sufficient overlap + * with the sensor. + */ +@SysUISingleton +class EllipseOverlapDetector(private val neededPoints: Int = 2) : OverlapDetector { + + override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean { + val points = calculateSensorPoints(nativeSensorBounds) + return points.count { checkPoint(it, touchData) } >= neededPoints + } + + private fun checkPoint(point: Point, touchData: NormalizedTouchData): Boolean { + // Calculate if sensor point is within ellipse + // Formula: ((cos(o)(xE - xS) + sin(o)(yE - yS))^2 / a^2) + ((sin(o)(xE - xS) + cos(o)(yE - + // yS))^2 / b^2) <= 1 + val a: Float = cos(touchData.orientation) * (point.x - touchData.x) + val b: Float = sin(touchData.orientation) * (point.y - touchData.y) + val c: Float = sin(touchData.orientation) * (point.x - touchData.x) + val d: Float = cos(touchData.orientation) * (point.y - touchData.y) + val result = + (a + b).pow(2) / (touchData.minor / 2).pow(2) + + (c - d).pow(2) / (touchData.major / 2).pow(2) + + return result <= 1 + } + + private fun calculateSensorPoints(sensorBounds: Rect): List { + val sensorX = sensorBounds.centerX() + val sensorY = sensorBounds.centerY() + val cornerOffset: Int = sensorBounds.width() / 4 + val sideOffset: Int = sensorBounds.width() / 3 + + return listOf( + Point(sensorX - cornerOffset, sensorY - cornerOffset), + Point(sensorX, sensorY - sideOffset), + Point(sensorX + cornerOffset, sensorY - cornerOffset), + Point(sensorX - sideOffset, sensorY), + Point(sensorX, sensorY), + Point(sensorX + sideOffset, sensorY), + Point(sensorX - cornerOffset, sensorY + cornerOffset), + Point(sensorX, sensorY + sideOffset), + Point(sensorX + cornerOffset, sensorY + cornerOffset) + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt new file mode 100644 index 0000000000000..0fec8ffbaa0aa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/OverlapDetector.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2022 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.udfps + +import android.graphics.Rect + +/** Determines whether the touch has a sufficient overlap with the sensor. */ +interface OverlapDetector { + fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt index dec8730e971f6..338bf66d197e3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt @@ -24,9 +24,15 @@ import android.view.Surface import com.android.systemui.biometrics.UdfpsOverlayParams import com.android.systemui.biometrics.udfps.TouchProcessorResult.Failure import com.android.systemui.biometrics.udfps.TouchProcessorResult.ProcessedTouch +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject -// TODO(b/259140693): Consider using an object pool of TouchProcessorResult to avoid allocations. -class SinglePointerTouchProcessor : TouchProcessor { +/** + * TODO(b/259140693): Consider using an object pool of TouchProcessorResult to avoid allocations. + */ +@SysUISingleton +class SinglePointerTouchProcessor @Inject constructor(val overlapDetector: OverlapDetector) : + TouchProcessor { override fun processTouch( event: MotionEvent, @@ -34,15 +40,21 @@ class SinglePointerTouchProcessor : TouchProcessor { overlayParams: UdfpsOverlayParams, ): TouchProcessorResult { + fun preprocess(): PreprocessedTouch { + // TODO(b/253085297): Add multitouch support. pointerIndex can be > 0 for ACTION_MOVE. + val pointerIndex = 0 + val touchData = event.normalize(pointerIndex, overlayParams) + val isGoodOverlap = + overlapDetector.isGoodOverlap(touchData, overlayParams.nativeSensorBounds) + return PreprocessedTouch(touchData, previousPointerOnSensorId, isGoodOverlap) + } + return when (event.actionMasked) { - MotionEvent.ACTION_DOWN -> - processActionDown(event.preprocess(previousPointerOnSensorId, overlayParams)) - MotionEvent.ACTION_MOVE -> - processActionMove(event.preprocess(previousPointerOnSensorId, overlayParams)) - MotionEvent.ACTION_UP -> - processActionUp(event.preprocess(previousPointerOnSensorId, overlayParams)) + MotionEvent.ACTION_DOWN -> processActionDown(preprocess()) + MotionEvent.ACTION_MOVE -> processActionMove(preprocess()) + MotionEvent.ACTION_UP -> processActionUp(preprocess()) MotionEvent.ACTION_CANCEL -> - processActionCancel(event.preprocess(previousPointerOnSensorId, overlayParams)) + processActionCancel(event.normalize(pointerIndex = 0, overlayParams)) else -> Failure("Unsupported MotionEvent." + MotionEvent.actionToString(event.actionMasked)) } @@ -52,11 +64,11 @@ class SinglePointerTouchProcessor : TouchProcessor { private data class PreprocessedTouch( val data: NormalizedTouchData, val previousPointerOnSensorId: Int, - val isWithinSensor: Boolean, + val isGoodOverlap: Boolean, ) private fun processActionDown(touch: PreprocessedTouch): TouchProcessorResult { - return if (touch.isWithinSensor) { + return if (touch.isGoodOverlap) { ProcessedTouch(InteractionEvent.DOWN, pointerOnSensorId = touch.data.pointerId, touch.data) } else { val event = @@ -73,8 +85,8 @@ private fun processActionMove(touch: PreprocessedTouch): TouchProcessorResult { val hadPointerOnSensor = touch.previousPointerOnSensorId != INVALID_POINTER_ID val interactionEvent = when { - touch.isWithinSensor && !hadPointerOnSensor -> InteractionEvent.DOWN - !touch.isWithinSensor && hadPointerOnSensor -> InteractionEvent.UP + touch.isGoodOverlap && !hadPointerOnSensor -> InteractionEvent.DOWN + !touch.isGoodOverlap && hadPointerOnSensor -> InteractionEvent.UP else -> InteractionEvent.UNCHANGED } val pointerOnSensorId = @@ -87,7 +99,7 @@ private fun processActionMove(touch: PreprocessedTouch): TouchProcessorResult { } private fun processActionUp(touch: PreprocessedTouch): TouchProcessorResult { - return if (touch.isWithinSensor) { + return if (touch.isGoodOverlap) { ProcessedTouch(InteractionEvent.UP, pointerOnSensorId = INVALID_POINTER_ID, touch.data) } else { val event = @@ -100,24 +112,8 @@ private fun processActionUp(touch: PreprocessedTouch): TouchProcessorResult { } } -private fun processActionCancel(touch: PreprocessedTouch): TouchProcessorResult { - return ProcessedTouch( - InteractionEvent.CANCEL, - pointerOnSensorId = INVALID_POINTER_ID, - touch.data - ) -} - -/** Returns [PreprocessedTouch], which is an input to all the action-specific functions. */ -private fun MotionEvent.preprocess( - previousPointerOnSensorId: Int, - overlayParams: UdfpsOverlayParams -): PreprocessedTouch { - // TODO(b/253085297): Add multitouch support. pointerIndex can be > 0 for ACTION_MOVE. - val pointerIndex = 0 - val touchData = normalize(pointerIndex, overlayParams) - val isWithinSensor = touchData.isWithinSensor(overlayParams.nativeSensorBounds) - return PreprocessedTouch(touchData, previousPointerOnSensorId, isWithinSensor) +private fun processActionCancel(data: NormalizedTouchData): TouchProcessorResult { + return ProcessedTouch(InteractionEvent.CANCEL, pointerOnSensorId = INVALID_POINTER_ID, data) } /** diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 95919c6b2c0df..b8e66735c7408 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -33,6 +33,7 @@ import com.android.systemui.assist.AssistModule; import com.android.systemui.biometrics.AlternateUdfpsTouchProvider; import com.android.systemui.biometrics.UdfpsDisplayModeProvider; import com.android.systemui.biometrics.dagger.BiometricsModule; +import com.android.systemui.biometrics.dagger.UdfpsModule; import com.android.systemui.classifier.FalsingModule; import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule; import com.android.systemui.controls.dagger.ControlsModule; @@ -156,6 +157,7 @@ import dagger.Provides; TelephonyRepositoryModule.class, TemporaryDisplayModule.class, TunerModule.class, + UdfpsModule.class, UserModule.class, UtilModule.class, NoteTaskModule.class, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index acdafe3e1c7db..b267a5c23a498 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -70,8 +70,13 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ActivityLaunchAnimator; +import com.android.systemui.biometrics.udfps.InteractionEvent; +import com.android.systemui.biometrics.udfps.NormalizedTouchData; +import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor; +import com.android.systemui.biometrics.udfps.TouchProcessorResult; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.plugins.FalsingManager; @@ -190,6 +195,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private AlternateUdfpsTouchProvider mAlternateTouchProvider; @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor; + @Mock + private SinglePointerTouchProcessor mSinglePointerTouchProcessor; // Capture listeners so that they can be used to send events @Captor @@ -275,7 +282,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mDisplayManager, mHandler, mConfigurationController, mSystemClock, mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker, mActivityLaunchAnimator, alternateTouchProvider, mBiometricsExecutor, - mPrimaryBouncerInteractor); + mPrimaryBouncerInteractor, mSinglePointerTouchProcessor); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); @@ -1086,4 +1093,100 @@ public class UdfpsControllerTest extends SysuiTestCase { anyString(), any()); } + + @Test + public void onTouch_withoutNewTouchDetection_shouldCallOldFingerprintManagerPath() + throws RemoteException { + // Disable new touch detection. + when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(false); + + // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. + initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); + + // Configure UdfpsView to accept the ACTION_DOWN event + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN that the overlay is showing and a11y touch exploration NOT enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + + // WHEN ACTION_DOWN is received + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricsExecutor.runAllReady(); + downEvent.recycle(); + + // AND ACTION_MOVE is received + MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); + mBiometricsExecutor.runAllReady(); + moveEvent.recycle(); + + // AND ACTION_UP is received + MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); + mBiometricsExecutor.runAllReady(); + upEvent.recycle(); + + // THEN the old FingerprintManager path is invoked. + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(), + anyFloat(), anyFloat()); + verify(mFingerprintManager).onPointerUp(anyLong(), anyInt()); + } + + @Test + public void onTouch_withNewTouchDetection_shouldCallOldFingerprintManagerPath() + throws RemoteException { + final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, + 0L); + final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( + InteractionEvent.DOWN, 1 /* pointerId */, touchData); + final TouchProcessorResult processorResultUp = new TouchProcessorResult.ProcessedTouch( + InteractionEvent.UP, 1 /* pointerId */, touchData); + + // Enable new touch detection. + when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); + + // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. + initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); + + // Configure UdfpsView to accept the ACTION_DOWN event + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN that the overlay is showing and a11y touch exploration NOT enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + + // WHEN ACTION_DOWN is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDown); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricsExecutor.runAllReady(); + downEvent.recycle(); + + // AND ACTION_UP is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultUp); + MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); + mBiometricsExecutor.runAllReady(); + upEvent.recycle(); + + // THEN the new FingerprintManager path is invoked. + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt new file mode 100644 index 0000000000000..4f89b69108f43 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetectorTest.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022 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.udfps + +import android.graphics.Rect +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters + +@SmallTest +@RunWith(Parameterized::class) +class BoundingBoxOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() { + val underTest = BoundingBoxOverlapDetector() + + @Test + fun isGoodOverlap() { + val touchData = TOUCH_DATA.copy(x = testCase.x.toFloat(), y = testCase.y.toFloat()) + val actual = underTest.isGoodOverlap(touchData, SENSOR) + + assertThat(actual).isEqualTo(testCase.expected) + } + + data class TestCase(val x: Int, val y: Int, val expected: Boolean) + + companion object { + @Parameters(name = "{0}") + @JvmStatic + fun data(): List = + listOf( + genPositiveTestCases( + validXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()), + validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY()) + ), + genNegativeTestCases( + invalidXs = listOf(SENSOR.left - 1, SENSOR.right + 1), + invalidYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1), + validXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()), + validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY()) + ) + ) + .flatten() + } +} + +/* Placeholder touch parameters. */ +private const val POINTER_ID = 42 +private const val NATIVE_MINOR = 2.71828f +private const val NATIVE_MAJOR = 3.14f +private const val ORIENTATION = 1.23f +private const val TIME = 12345699L +private const val GESTURE_START = 12345600L + +/* Template [NormalizedTouchData]. */ +private val TOUCH_DATA = + NormalizedTouchData( + POINTER_ID, + x = 0f, + y = 0f, + NATIVE_MINOR, + NATIVE_MAJOR, + ORIENTATION, + TIME, + GESTURE_START + ) + +private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 500 /* bottom */) + +private fun genTestCases( + xs: List, + ys: List, + expected: Boolean +): List { + return xs.flatMap { x -> + ys.map { y -> BoundingBoxOverlapDetectorTest.TestCase(x, y, expected) } + } +} + +private fun genPositiveTestCases( + validXs: List, + validYs: List, +) = genTestCases(validXs, validYs, expected = true) + +private fun genNegativeTestCases( + invalidXs: List, + invalidYs: List, + validXs: List, + validYs: List, +): List { + return genTestCases(invalidXs, validYs, expected = false) + + genTestCases(validXs, invalidYs, expected = false) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt index 2b66205d7a0d2..95c53b408056f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt @@ -34,10 +34,13 @@ import org.junit.runners.Parameterized.Parameters @SmallTest @RunWith(Parameterized::class) class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() { - private val underTest = SinglePointerTouchProcessor() + private val overlapDetector = FakeOverlapDetector() + private val underTest = SinglePointerTouchProcessor(overlapDetector) @Test fun processTouch() { + overlapDetector.shouldReturn = testCase.isGoodOverlap + val actual = underTest.processTouch( testCase.event, @@ -53,6 +56,7 @@ class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() data class TestCase( val event: MotionEvent, + val isGoodOverlap: Boolean, val previousPointerOnSensorId: Int, val overlayParams: UdfpsOverlayParams, val expected: TouchProcessorResult, @@ -87,28 +91,28 @@ class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() genPositiveTestCases( motionEventAction = MotionEvent.ACTION_DOWN, previousPointerOnSensorId = INVALID_POINTER_ID, - isWithinSensor = true, + isGoodOverlap = true, expectedInteractionEvent = InteractionEvent.DOWN, expectedPointerOnSensorId = POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_DOWN, previousPointerOnSensorId = POINTER_ID, - isWithinSensor = true, + isGoodOverlap = true, expectedInteractionEvent = InteractionEvent.DOWN, expectedPointerOnSensorId = POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_DOWN, previousPointerOnSensorId = INVALID_POINTER_ID, - isWithinSensor = false, + isGoodOverlap = false, expectedInteractionEvent = InteractionEvent.UNCHANGED, expectedPointerOnSensorId = INVALID_POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_DOWN, previousPointerOnSensorId = POINTER_ID, - isWithinSensor = false, + isGoodOverlap = false, expectedInteractionEvent = InteractionEvent.UP, expectedPointerOnSensorId = INVALID_POINTER_ID, ), @@ -116,28 +120,28 @@ class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() genPositiveTestCases( motionEventAction = MotionEvent.ACTION_MOVE, previousPointerOnSensorId = INVALID_POINTER_ID, - isWithinSensor = true, + isGoodOverlap = true, expectedInteractionEvent = InteractionEvent.DOWN, expectedPointerOnSensorId = POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_MOVE, previousPointerOnSensorId = POINTER_ID, - isWithinSensor = true, + isGoodOverlap = true, expectedInteractionEvent = InteractionEvent.UNCHANGED, expectedPointerOnSensorId = POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_MOVE, previousPointerOnSensorId = INVALID_POINTER_ID, - isWithinSensor = false, + isGoodOverlap = false, expectedInteractionEvent = InteractionEvent.UNCHANGED, expectedPointerOnSensorId = INVALID_POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_MOVE, previousPointerOnSensorId = POINTER_ID, - isWithinSensor = false, + isGoodOverlap = false, expectedInteractionEvent = InteractionEvent.UP, expectedPointerOnSensorId = INVALID_POINTER_ID, ), @@ -145,28 +149,28 @@ class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() genPositiveTestCases( motionEventAction = MotionEvent.ACTION_UP, previousPointerOnSensorId = INVALID_POINTER_ID, - isWithinSensor = true, + isGoodOverlap = true, expectedInteractionEvent = InteractionEvent.UP, expectedPointerOnSensorId = INVALID_POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_UP, previousPointerOnSensorId = POINTER_ID, - isWithinSensor = true, + isGoodOverlap = true, expectedInteractionEvent = InteractionEvent.UP, expectedPointerOnSensorId = INVALID_POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_UP, previousPointerOnSensorId = INVALID_POINTER_ID, - isWithinSensor = false, + isGoodOverlap = false, expectedInteractionEvent = InteractionEvent.UNCHANGED, expectedPointerOnSensorId = INVALID_POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_UP, previousPointerOnSensorId = POINTER_ID, - isWithinSensor = false, + isGoodOverlap = false, expectedInteractionEvent = InteractionEvent.UP, expectedPointerOnSensorId = INVALID_POINTER_ID, ), @@ -174,28 +178,28 @@ class SinglePointerTouchProcessorTest(val testCase: TestCase) : SysuiTestCase() genPositiveTestCases( motionEventAction = MotionEvent.ACTION_CANCEL, previousPointerOnSensorId = INVALID_POINTER_ID, - isWithinSensor = true, + isGoodOverlap = true, expectedInteractionEvent = InteractionEvent.CANCEL, expectedPointerOnSensorId = INVALID_POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_CANCEL, previousPointerOnSensorId = POINTER_ID, - isWithinSensor = true, + isGoodOverlap = true, expectedInteractionEvent = InteractionEvent.CANCEL, expectedPointerOnSensorId = INVALID_POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_CANCEL, previousPointerOnSensorId = INVALID_POINTER_ID, - isWithinSensor = false, + isGoodOverlap = false, expectedInteractionEvent = InteractionEvent.CANCEL, expectedPointerOnSensorId = INVALID_POINTER_ID, ), genPositiveTestCases( motionEventAction = MotionEvent.ACTION_CANCEL, previousPointerOnSensorId = POINTER_ID, - isWithinSensor = false, + isGoodOverlap = false, expectedInteractionEvent = InteractionEvent.CANCEL, expectedPointerOnSensorId = INVALID_POINTER_ID, ), @@ -376,7 +380,7 @@ private data class OrientationBasedInputs( private fun genPositiveTestCases( motionEventAction: Int, previousPointerOnSensorId: Int, - isWithinSensor: Boolean, + isGoodOverlap: Boolean, expectedInteractionEvent: InteractionEvent, expectedPointerOnSensorId: Int ): List { @@ -391,8 +395,8 @@ private fun genPositiveTestCases( return scaleFactors.flatMap { scaleFactor -> orientations.map { orientation -> val overlayParams = orientation.toOverlayParams(scaleFactor) - val nativeX = orientation.getNativeX(isWithinSensor) - val nativeY = orientation.getNativeY(isWithinSensor) + val nativeX = orientation.getNativeX(isGoodOverlap) + val nativeY = orientation.getNativeY(isGoodOverlap) val event = MOTION_EVENT.copy( action = motionEventAction, @@ -403,8 +407,8 @@ private fun genPositiveTestCases( ) val expectedTouchData = NORMALIZED_TOUCH_DATA.copy( - x = ROTATION_0_INPUTS.getNativeX(isWithinSensor), - y = ROTATION_0_INPUTS.getNativeY(isWithinSensor), + x = ROTATION_0_INPUTS.getNativeX(isGoodOverlap), + y = ROTATION_0_INPUTS.getNativeY(isGoodOverlap), ) val expected = TouchProcessorResult.ProcessedTouch( @@ -414,6 +418,7 @@ private fun genPositiveTestCases( ) SinglePointerTouchProcessorTest.TestCase( event = event, + isGoodOverlap = isGoodOverlap, previousPointerOnSensorId = previousPointerOnSensorId, overlayParams = overlayParams, expected = expected, @@ -425,12 +430,12 @@ private fun genPositiveTestCases( private fun genTestCasesForUnsupportedAction( motionEventAction: Int ): List { - val isWithinSensor = true + val isGoodOverlap = true val previousPointerOnSensorIds = listOf(INVALID_POINTER_ID, POINTER_ID) return previousPointerOnSensorIds.map { previousPointerOnSensorId -> val overlayParams = ROTATION_0_INPUTS.toOverlayParams(scaleFactor = 1f) - val nativeX = ROTATION_0_INPUTS.getNativeX(isWithinSensor) - val nativeY = ROTATION_0_INPUTS.getNativeY(isWithinSensor) + val nativeX = ROTATION_0_INPUTS.getNativeX(isGoodOverlap) + val nativeY = ROTATION_0_INPUTS.getNativeY(isGoodOverlap) val event = MOTION_EVENT.copy( action = motionEventAction, @@ -441,6 +446,7 @@ private fun genTestCasesForUnsupportedAction( ) SinglePointerTouchProcessorTest.TestCase( event = event, + isGoodOverlap = isGoodOverlap, previousPointerOnSensorId = previousPointerOnSensorId, overlayParams = overlayParams, expected = TouchProcessorResult.Failure(), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt new file mode 100644 index 0000000000000..8176dd07b84a2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/udfps/FakeOverlapDetector.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 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.udfps + +import android.graphics.Rect + +class FakeOverlapDetector : OverlapDetector { + var shouldReturn: Boolean = false + + override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean { + return shouldReturn + } +}