Loading core/java/android/hardware/fingerprint/FingerprintManager.java +54 −2 Original line number Diff line number Diff line Loading @@ -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; } Loading @@ -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; } Loading @@ -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 */ Loading packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +168 −49 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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"); Loading @@ -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"); Loading Loading @@ -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; Loading Loading @@ -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( Loading @@ -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); } } /** Loading Loading @@ -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) { Loading Loading @@ -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()) { Loading @@ -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(); Loading @@ -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; Loading @@ -987,9 +1101,14 @@ public class UdfpsController implements DozeReceiver, Dumpable { mKeyguardUpdateMonitor.onUdfpsPointerUp((int) requestId); } }); } else { 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(); } Loading packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt 0 → 100644 +43 −0 Original line number Diff line number Diff line /* * 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() } } } } packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt 0 → 100644 +27 −0 Original line number Diff line number Diff line /* * 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) } packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt→packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt +71 −0 Original line number Diff line number Diff line Loading @@ -14,71 +14,49 @@ * limitations under the License. */ package com.android.systemui.biometrics package com.android.systemui.biometrics.udfps import android.graphics.Point import android.graphics.Rect import android.util.RotationUtils import android.view.MotionEvent import com.android.systemui.dagger.SysUISingleton 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) } /** * 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 { fun isGoodEllipseOverlap(event: MotionEvent): Boolean { return points.count { checkPoint(event, it) } >= NEEDED_POINTS override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean { val points = calculateSensorPoints(nativeSensorBounds) return points.count { checkPoint(it, touchData) } >= neededPoints } private fun checkPoint(event: MotionEvent, point: Point): Boolean { 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(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 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) / (event.touchMinor / 2).pow(2) + (c - d).pow(2) / (event.touchMajor / 2).pow(2) (a + b).pow(2) / (touchData.minor / 2).pow(2) + (c - d).pow(2) / (touchData.major / 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 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 arrayOf( return listOf( Point(sensorX - cornerOffset, sensorY - cornerOffset), Point(sensorX, sensorY - sideOffset), Point(sensorX + cornerOffset, sensorY - cornerOffset), Loading @@ -90,3 +68,4 @@ fun calculateSensorPoints(sensorRect: Rect): Array<Point> { Point(sensorX + cornerOffset, sensorY + cornerOffset) ) } } Loading
core/java/android/hardware/fingerprint/FingerprintManager.java +54 −2 Original line number Diff line number Diff line Loading @@ -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; } Loading @@ -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; } Loading @@ -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 */ Loading
packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +168 −49 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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; Loading Loading @@ -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"); Loading @@ -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"); Loading Loading @@ -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; Loading Loading @@ -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( Loading @@ -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); } } /** Loading Loading @@ -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) { Loading Loading @@ -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()) { Loading @@ -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(); Loading @@ -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; Loading @@ -987,9 +1101,14 @@ public class UdfpsController implements DozeReceiver, Dumpable { mKeyguardUpdateMonitor.onUdfpsPointerUp((int) requestId); } }); } else { 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(); } Loading
packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt 0 → 100644 +43 −0 Original line number Diff line number Diff line /* * 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() } } } }
packages/SystemUI/src/com/android/systemui/biometrics/udfps/BoundingBoxOverlapDetector.kt 0 → 100644 +27 −0 Original line number Diff line number Diff line /* * 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) }
packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEllipseDetection.kt→packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt +71 −0 Original line number Diff line number Diff line Loading @@ -14,71 +14,49 @@ * limitations under the License. */ package com.android.systemui.biometrics package com.android.systemui.biometrics.udfps import android.graphics.Point import android.graphics.Rect import android.util.RotationUtils import android.view.MotionEvent import com.android.systemui.dagger.SysUISingleton 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) } /** * 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 { fun isGoodEllipseOverlap(event: MotionEvent): Boolean { return points.count { checkPoint(event, it) } >= NEEDED_POINTS override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean { val points = calculateSensorPoints(nativeSensorBounds) return points.count { checkPoint(it, touchData) } >= neededPoints } private fun checkPoint(event: MotionEvent, point: Point): Boolean { 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(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 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) / (event.touchMinor / 2).pow(2) + (c - d).pow(2) / (event.touchMajor / 2).pow(2) (a + b).pow(2) / (touchData.minor / 2).pow(2) + (c - d).pow(2) / (touchData.major / 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 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 arrayOf( return listOf( Point(sensorX - cornerOffset, sensorY - cornerOffset), Point(sensorX, sensorY - sideOffset), Point(sensorX + cornerOffset, sensorY - cornerOffset), Loading @@ -90,3 +68,4 @@ fun calculateSensorPoints(sensorRect: Rect): Array<Point> { Point(sensorX + cornerOffset, sensorY + cornerOffset) ) } }