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

Commit 8698dbdf authored by Ilya Matyukhin's avatar Ilya Matyukhin
Browse files

Integrate new touch architecture with UdfpsController

Bug: 218388821
Bug: 218374828
Test: atest SystemUITests:com.android.systemui.biometrics
Change-Id: Ifb32d5f6a94b2b5a1daf9e972414476db4122047
parent aa4b1c42
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
     */
+168 −49
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
@@ -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;
@@ -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();
            }
+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()
            }
        }
    }
}
+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)
}
+71 −0
Original line number Diff line number Diff line
@@ -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),
@@ -90,3 +68,4 @@ fun calculateSensorPoints(sensorRect: Rect): Array<Point> {
            Point(sensorX + cornerOffset, sensorY + cornerOffset)
        )
    }
}
Loading