Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0672ae40 authored by Ilya Matyukhin's avatar Ilya Matyukhin Committed by Android (Google) Code Review
Browse files

Merge changes from topic "new-udfps-touch" into tm-qpr-dev

* changes:
  Integrate new touch architecture with UdfpsController
  Change computed properties to regular in UdfpsOverlayParams
  Introduce testable UDFPS touch architecture
  Add rotatePointF to RotationUtils
  Add tryDismissingKeyguard() to UdfpsController
parents cfe5853c 8698dbdf
Loading
Loading
Loading
Loading
+54 −2
Original line number Diff line number Diff line
@@ -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
     */
+24 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.annotation.Dimension;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.view.Surface.Rotation;
import android.view.SurfaceControl;
@@ -192,6 +193,29 @@ public class RotationUtils {
        }
    }

    /**
     * Same as {@link #rotatePoint}, but for float coordinates.
     */
    public static void rotatePointF(PointF inOutPoint, @Rotation int rotation,
            float parentW, float parentH) {
        float origX = inOutPoint.x;
        switch (rotation) {
            case ROTATION_0:
                return;
            case ROTATION_90:
                inOutPoint.x = inOutPoint.y;
                inOutPoint.y = parentW - origX;
                return;
            case ROTATION_180:
                inOutPoint.x = parentW - inOutPoint.x;
                inOutPoint.y = parentH - inOutPoint.y;
                return;
            case ROTATION_270:
                inOutPoint.x = parentH - inOutPoint.y;
                inOutPoint.y = origX;
        }
    }

    /**
     * Sets a matrix such that given a rotation, it transforms physical display
     * coordinates to that rotation's logical coordinates.
+24 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.util;

import static android.util.RotationUtils.rotateBounds;
import static android.util.RotationUtils.rotatePoint;
import static android.util.RotationUtils.rotatePointF;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
@@ -25,6 +26,7 @@ import static android.view.Surface.ROTATION_90;
import static org.junit.Assert.assertEquals;

import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;

import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -79,4 +81,26 @@ public class RotationUtilsTest {
        rotatePoint(testResult, ROTATION_270, parentW, parentH);
        assertEquals(new Point(560, 60), testResult);
    }

    @Test
    public void testRotatePointF() {
        float parentW = 1000f;
        float parentH = 600f;
        PointF testPt = new PointF(60f, 40f);

        PointF testResult = new PointF(testPt);
        rotatePointF(testResult, ROTATION_90, parentW, parentH);
        assertEquals(40f, testResult.x, .1f);
        assertEquals(940f, testResult.y, .1f);

        testResult.set(testPt.x, testPt.y);
        rotatePointF(testResult, ROTATION_180, parentW, parentH);
        assertEquals(940f, testResult.x, .1f);
        assertEquals(560f, testResult.y, .1f);

        testResult.set(testPt.x, testPt.y);
        rotatePointF(testResult, ROTATION_270, parentW, parentH);
        assertEquals(560f, testResult.x, .1f);
        assertEquals(60f, testResult.y, .1f);
    }
}
+178 −59
Original line number Diff line number Diff line
@@ -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
@@ -434,8 +435,99 @@ public class UdfpsController implements DozeReceiver, Dumpable {
        return portraitTouch;
    }

    private void tryDismissingKeyguard() {
        if (!mOnFingerDown) {
            playStartHaptic();
        }
        mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */);
        mAttemptedToDismissKeyguard = true;
    }

    @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;
@@ -465,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");
@@ -495,11 +572,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
                }
                if ((withinSensorArea || fromUdfpsView) && shouldTryToDismissKeyguard()) {
                    Log.v(TAG, "onTouch | dismiss keyguard ACTION_DOWN");
                    if (!mOnFingerDown) {
                        playStartHaptic();
                    }
                    mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */);
                    mAttemptedToDismissKeyguard = true;
                    tryDismissingKeyguard();
                }

                Trace.endSection();
@@ -512,33 +585,13 @@ 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");
                        if (!mOnFingerDown) {
                            playStartHaptic();
                        }
                        mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */);
                        mAttemptedToDismissKeyguard = true;
                        tryDismissingKeyguard();
                        break;
                    }
                    // Map the touch to portrait mode if the device is in landscape mode.
@@ -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;
@@ -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();
            }
+25 −15
Original line number Diff line number Diff line
@@ -7,17 +7,23 @@ import android.view.Surface.Rotation
/**
 * Collection of parameters that define an under-display fingerprint sensor (UDFPS) overlay.
 *
 * @property sensorBounds coordinates of the bounding box around the sensor, in natural orientation,
 *     in pixels, for the current resolution.
 * @property naturalDisplayWidth width of the physical display, in natural orientation, in pixels,
 *     for the current resolution.
 * @property naturalDisplayHeight height of the physical display, in natural orientation, in pixels,
 *     for the current resolution.
 * @property scaleFactor ratio of a dimension in the current resolution to the corresponding
 *     dimension in the native resolution.
 * @property rotation current rotation of the display.
 * [sensorBounds] coordinates of the bounding box around the sensor in natural orientation, in
 * pixels, for the current resolution.
 *
 * [overlayBounds] coordinates of the UI overlay in natural orientation, in pixels, for the current
 * resolution.
 *
 * [naturalDisplayWidth] width of the physical display in natural orientation, in pixels, for the
 * current resolution.
 *
 * [naturalDisplayHeight] height of the physical display in natural orientation, in pixels, for the
 * current resolution.
 *
 * [scaleFactor] ratio of a dimension in the current resolution to the corresponding dimension in
 * the native resolution.
 *
 * [rotation] current rotation of the display.
 */

data class UdfpsOverlayParams(
    val sensorBounds: Rect = Rect(),
    val overlayBounds: Rect = Rect(),
@@ -26,17 +32,21 @@ data class UdfpsOverlayParams(
    val scaleFactor: Float = 1f,
    @Rotation val rotation: Int = Surface.ROTATION_0
) {

    /** Same as [sensorBounds], but in native resolution. */
    val nativeSensorBounds = Rect(sensorBounds).apply { scale(1f / scaleFactor) }

    /** See [android.view.DisplayInfo.logicalWidth] */
    val logicalDisplayWidth
        get() = if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
    val logicalDisplayWidth =
        if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
            naturalDisplayHeight
        } else {
            naturalDisplayWidth
        }

    /** See [android.view.DisplayInfo.logicalHeight] */
    val logicalDisplayHeight
        get() = if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
    val logicalDisplayHeight =
        if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
            naturalDisplayWidth
        } else {
            naturalDisplayHeight
Loading