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

Commit 3c84cada authored by Austin Delgado's avatar Austin Delgado
Browse files

Add ellipse and centroid touch detection

Use "Use Ellipse Touch Detection" feature flag to switch between and run
'adb shell statusbar udpfs udpfsOverlay show' on lockscreen to show

Bug: 252897742
Test: N/A
Change-Id: I556c84052fd0884591f8cb251258b67e7039c681
parent 37dca6be
Loading
Loading
Loading
Loading
+90 −20
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.biometrics
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.PixelFormat
import android.graphics.Point
import android.graphics.Rect
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
@@ -31,14 +32,22 @@ import android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.Execution
import java.util.*
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlin.math.cos
import kotlin.math.pow
import kotlin.math.sin

private const val TAG = "UdfpsOverlay"

// Number of sensor points needed inside ellipse for good overlap
private const val NEEDED_POINTS = 3

@SuppressLint("ClickableViewAccessibility")
@SysUISingleton
class UdfpsOverlay
@@ -54,7 +63,8 @@ constructor(
    private val fgExecutor: DelayableExecutor,
    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
    private val authController: AuthController,
    private val udfpsLogger: UdfpsLogger
    private val udfpsLogger: UdfpsLogger,
    private var featureFlags: FeatureFlags
) : CoreStartable {

    /** The view, when [isShowing], or null. */
@@ -65,6 +75,8 @@ constructor(
    private var onFingerDown = false
    val size = windowManager.maximumWindowMetrics.bounds
    val udfpsProps: MutableList<FingerprintSensorPropertiesInternal> = mutableListOf()
    var points: Array<Point> = emptyArray()
    var processedMotionEvent = false

    private var params: UdfpsOverlayParams = UdfpsOverlayParams()

@@ -95,13 +107,23 @@ constructor(
            MotionEvent.ACTION_MOVE -> {
                onFingerDown = true
                if (!view.isDisplayConfigured && alternateTouchProvider.isPresent) {
                    view.processMotionEvent(event)

                    val goodOverlap =
                        if (featureFlags.isEnabled(Flags.NEW_ELLIPSE_DETECTION)) {
                            isGoodEllipseOverlap(event)
                        } else {
                            isGoodCentroidOverlap(event)
                        }

                    if (!processedMotionEvent && goodOverlap) {
                        biometricExecutor.execute {
                            alternateTouchProvider
                                .get()
                                .onPointerDown(
                                    requestId,
                                event.x.toInt(),
                                event.y.toInt(),
                                    event.rawX.toInt(),
                                    event.rawY.toInt(),
                                    event.touchMinor,
                                    event.touchMajor
                                )
@@ -115,13 +137,17 @@ constructor(
                        view.configureDisplay {
                            biometricExecutor.execute { alternateTouchProvider.get().onUiReady() }
                        }

                        processedMotionEvent = true
                    }
                }

                view.invalidate()
                true
            }
            MotionEvent.ACTION_UP,
            MotionEvent.ACTION_CANCEL -> {
                if (onFingerDown && alternateTouchProvider.isPresent) {
                if (processedMotionEvent && alternateTouchProvider.isPresent) {
                    biometricExecutor.execute {
                        alternateTouchProvider.get().onPointerUp(requestId)
                    }
@@ -130,18 +156,44 @@ constructor(
                            keyguardUpdateMonitor.onUdfpsPointerUp(requestId.toInt())
                        }
                    }

                    processedMotionEvent = false
                }
                onFingerDown = false

                if (view.isDisplayConfigured) {
                    view.unconfigureDisplay()
                }

                view.invalidate()
                true
            }
            else -> false
        }
    }

    fun isGoodEllipseOverlap(event: MotionEvent): Boolean {
        return points.count { checkPoint(event, it) } >= NEEDED_POINTS
    }

    fun isGoodCentroidOverlap(event: MotionEvent): Boolean {
        return params.sensorBounds.contains(event.rawX.toInt(), event.rawY.toInt())
    }

    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(0)(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 show(requestId: Long): Boolean {
        this.requestId = requestId
        if (overlayView == null && alternateTouchProvider.isPresent) {
@@ -151,6 +203,7 @@ constructor(
                    UdfpsDisplayMode(context, execution, authController, udfpsLogger)
                )
                it.setOnTouchListener { v, event -> onTouch(v, event) }
                it.sensorPoints = points
                overlayView = it
            }
            windowManager.addView(overlayView, coreLayoutParams)
@@ -201,6 +254,23 @@ constructor(
                    naturalDisplayHeight = size.height(),
                    scaleFactor = 1f
                )

            val sensorX = params.sensorBounds.centerX()
            val sensorY = params.sensorBounds.centerY()
            val gridOffset: Int = params.sensorBounds.width() / 3

            points =
                arrayOf(
                    Point(sensorX - gridOffset, sensorY - gridOffset),
                    Point(sensorX, sensorY - gridOffset),
                    Point(sensorX + gridOffset, sensorY - gridOffset),
                    Point(sensorX - gridOffset, sensorY),
                    Point(sensorX, sensorY),
                    Point(sensorX + gridOffset, sensorY),
                    Point(sensorX - gridOffset, sensorY + gridOffset),
                    Point(sensorX, sensorY + gridOffset),
                    Point(sensorX + gridOffset, sensorY + gridOffset)
                )
        }
    }
}
+54 −3
Original line number Diff line number Diff line
@@ -20,26 +20,39 @@ import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Point
import android.graphics.RectF
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.FrameLayout

private const val TAG = "UdfpsOverlayView"
private const val POINT_SIZE = 10f

class UdfpsOverlayView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {

    private val sensorRect = RectF()
    var overlayParams = UdfpsOverlayParams()
    private var mUdfpsDisplayMode: UdfpsDisplayMode? = null

    var overlayPaint = Paint()
    var sensorPaint = Paint()
    var touchPaint = Paint()
    var pointPaint = Paint()
    val centerPaint = Paint()

    var oval = RectF()

    /** True after the call to [configureDisplay] and before the call to [unconfigureDisplay]. */
    var isDisplayConfigured: Boolean = false
        private set

    var touchX: Float = 0f
    var touchY: Float = 0f
    var touchMinor: Float = 0f
    var touchMajor: Float = 0f
    var touchOrientation: Double = 0.0

    var sensorPoints: Array<Point>? = null

    init {
        this.setWillNotDraw(false)
    }
@@ -47,16 +60,23 @@ class UdfpsOverlayView(context: Context, attrs: AttributeSet?) : FrameLayout(con
    override fun onAttachedToWindow() {
        super.onAttachedToWindow()

        overlayPaint.color = Color.argb(120, 255, 0, 0)
        overlayPaint.color = Color.argb(100, 255, 0, 0)
        overlayPaint.style = Paint.Style.FILL

        touchPaint.color = Color.argb(200, 255, 255, 255)
        touchPaint.style = Paint.Style.FILL

        sensorPaint.color = Color.argb(150, 134, 204, 255)
        sensorPaint.style = Paint.Style.FILL

        pointPaint.color = Color.WHITE
        pointPaint.style = Paint.Style.FILL
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        // Draw overlay and sensor
        canvas.drawRect(overlayParams.overlayBounds, overlayPaint)
        canvas.drawRect(overlayParams.sensorBounds, sensorPaint)
        canvas.drawCircle(
@@ -65,6 +85,29 @@ class UdfpsOverlayView(context: Context, attrs: AttributeSet?) : FrameLayout(con
            overlayParams.sensorBounds.width().toFloat() / 2,
            centerPaint
        )

        // Draw Points
        sensorPoints?.forEach {
            canvas.drawCircle(it.x.toFloat(), it.y.toFloat(), POINT_SIZE, pointPaint)
        }

        // Draw touch oval
        canvas.save()
        canvas.rotate(Math.toDegrees(touchOrientation).toFloat(), touchX, touchY)

        oval.setEmpty()
        oval.set(
            touchX - touchMinor / 2,
            touchY + touchMajor / 2,
            touchX + touchMinor / 2,
            touchY - touchMajor / 2
        )

        canvas.drawOval(oval, touchPaint)

        // Draw center point
        canvas.drawCircle(touchX, touchY, POINT_SIZE, centerPaint)
        canvas.restore()
    }

    fun setUdfpsDisplayMode(udfpsDisplayMode: UdfpsDisplayMode?) {
@@ -80,4 +123,12 @@ class UdfpsOverlayView(context: Context, attrs: AttributeSet?) : FrameLayout(con
        isDisplayConfigured = false
        mUdfpsDisplayMode?.disable(null /* onDisabled */)
    }

    fun processMotionEvent(event: MotionEvent) {
        touchX = event.rawX
        touchY = event.rawY
        touchMinor = event.touchMinor
        touchMajor = event.touchMajor
        touchOrientation = event.orientation.toDouble()
    }
}
+5 −1
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 * 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.
@@ -124,6 +124,10 @@ object Flags {
     */
    @JvmField val DOZING_MIGRATION_1 = UnreleasedFlag(213, teamfood = true)

    @JvmField val NEW_ELLIPSE_DETECTION = UnreleasedFlag(214)

    @JvmField val NEW_UDFPS_OVERLAY = UnreleasedFlag(215)

    // 300 - power menu
    // TODO(b/254512600): Tracking Bug
    @JvmField val POWER_MENU_LITE = ReleasedFlag(300)