Integrate new touch architecture with UdfpsController

Bug: 218388821
Bug: 218374828
Test: atest SystemUITests:com.android.systemui.biometrics
Change-Id: Ifb32d5f6a94b2b5a1daf9e972414476db4122047
This commit is contained in:
Ilya Matyukhin
2022-11-28 04:36:59 +00:00
parent aa4b1c42bc
commit 8698dbdf55
13 changed files with 688 additions and 201 deletions

View File

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

View File

@@ -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<AlternateUdfpsTouchProvider> 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();

View File

@@ -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<Point> = 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<Point> {
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)
)
}

View File

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

View File

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

View File

@@ -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<Point> {
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)
)
}
}

View File

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

View File

@@ -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)
}
/**

View File

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

View File

@@ -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());
}
}

View File

@@ -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<TestCase> =
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<Int>,
ys: List<Int>,
expected: Boolean
): List<BoundingBoxOverlapDetectorTest.TestCase> {
return xs.flatMap { x ->
ys.map { y -> BoundingBoxOverlapDetectorTest.TestCase(x, y, expected) }
}
}
private fun genPositiveTestCases(
validXs: List<Int>,
validYs: List<Int>,
) = genTestCases(validXs, validYs, expected = true)
private fun genNegativeTestCases(
invalidXs: List<Int>,
invalidYs: List<Int>,
validXs: List<Int>,
validYs: List<Int>,
): List<BoundingBoxOverlapDetectorTest.TestCase> {
return genTestCases(invalidXs, validYs, expected = false) +
genTestCases(validXs, invalidYs, expected = false)
}

View File

@@ -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<SinglePointerTouchProcessorTest.TestCase> {
@@ -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<SinglePointerTouchProcessorTest.TestCase> {
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(),

View File

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