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:
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user