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
This commit is contained in:
Austin Delgado
2022-09-21 21:09:37 +00:00
parent 37dca6bea8
commit 3c84cadaf9
3 changed files with 150 additions and 25 deletions

View File

@@ -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,33 +107,47 @@ constructor(
MotionEvent.ACTION_MOVE -> {
onFingerDown = true
if (!view.isDisplayConfigured && alternateTouchProvider.isPresent) {
biometricExecutor.execute {
alternateTouchProvider
.get()
.onPointerDown(
requestId,
event.x.toInt(),
event.y.toInt(),
event.touchMinor,
event.touchMajor
)
}
fgExecutor.execute {
if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
keyguardUpdateMonitor.onUdfpsPointerDown(requestId.toInt())
}
}
view.processMotionEvent(event)
view.configureDisplay {
biometricExecutor.execute { alternateTouchProvider.get().onUiReady() }
val goodOverlap =
if (featureFlags.isEnabled(Flags.NEW_ELLIPSE_DETECTION)) {
isGoodEllipseOverlap(event)
} else {
isGoodCentroidOverlap(event)
}
if (!processedMotionEvent && goodOverlap) {
biometricExecutor.execute {
alternateTouchProvider
.get()
.onPointerDown(
requestId,
event.rawX.toInt(),
event.rawY.toInt(),
event.touchMinor,
event.touchMajor
)
}
fgExecutor.execute {
if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
keyguardUpdateMonitor.onUdfpsPointerDown(requestId.toInt())
}
}
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)
)
}
}
}

View File

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

View File

@@ -1,5 +1,5 @@
/*
* 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)