Ignore touches outside of Udfps Overlay bounds

Udfps overlay is fullscreen so unnecessary ellipse checks can be removed
by only checking touches that are within the overlay bounds (bottom half
of screen). These touches are likely way off from the sensor.

Bug: 274162395
Test: atest SystemUITests:com.android.systemui.biometrics
Change-Id: I56af14d9817b5b4719a20d40dcc69f961003b1be
This commit is contained in:
Austin Delgado
2023-04-28 14:06:06 -07:00
parent 2ce407b164
commit 34d34e0a81
11 changed files with 64 additions and 27 deletions

View File

@@ -36,6 +36,9 @@ data class UdfpsOverlayParams(
/** Same as [sensorBounds], but in native resolution. */
val nativeSensorBounds = Rect(sensorBounds).apply { scale(1f / scaleFactor) }
/** Same as [overlayBounds], but in native resolution. */
val nativeOverlayBounds = Rect(overlayBounds).apply { scale(1f / scaleFactor) }
/** See [android.view.DisplayInfo.logicalWidth] */
val logicalDisplayWidth =
if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {

View File

@@ -636,7 +636,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mOverlay.getOverlayView().getViewRootImpl().getInputToken());
}
return processedTouch.getTouchData().isWithinSensor(mOverlayParams.getNativeSensorBounds());
return processedTouch.getTouchData().isWithinBounds(mOverlayParams.getNativeSensorBounds());
}
private boolean oldOnTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {

View File

@@ -22,6 +22,11 @@ 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)
override fun isGoodOverlap(
touchData: NormalizedTouchData,
nativeSensorBounds: Rect,
nativeOverlayBounds: Rect,
): Boolean =
touchData.isWithinBounds(nativeOverlayBounds) &&
touchData.isWithinBounds(nativeSensorBounds)
}

View File

@@ -30,8 +30,8 @@ private enum class SensorPixelPosition {
TARGET // Pixel within sensor center target
}
private val isDebug = true
private val TAG = "EllipseOverlapDetector"
private const val isDebug = false
private const val TAG = "EllipseOverlapDetector"
/**
* Approximates the touch as an ellipse and determines whether the ellipse has a sufficient overlap
@@ -39,12 +39,21 @@ private val TAG = "EllipseOverlapDetector"
*/
@SysUISingleton
class EllipseOverlapDetector(private val params: EllipseOverlapDetectorParams) : OverlapDetector {
override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean {
// First, check if touch is within bounding box,
if (nativeSensorBounds.contains(touchData.x.toInt(), touchData.y.toInt())) {
override fun isGoodOverlap(
touchData: NormalizedTouchData,
nativeSensorBounds: Rect,
nativeOverlayBounds: Rect,
): Boolean {
// First, check if touch is within bounding box to exit early
if (touchData.isWithinBounds(nativeSensorBounds)) {
return true
}
// Check touch is within overlay bounds, not worth checking if outside
if (!touchData.isWithinBounds(nativeOverlayBounds)) {
return false
}
var isTargetTouched = false
var sensorPixels = 0
var coveredPixels = 0
@@ -77,7 +86,7 @@ class EllipseOverlapDetector(private val params: EllipseOverlapDetectorParams) :
val percentage: Float = coveredPixels.toFloat() / sensorPixels
if (isDebug) {
Log.v(
Log.d(
TAG,
"covered: $coveredPixels, sensor: $sensorPixels, " +
"percentage: $percentage, isCenterTouched: $isTargetTouched"

View File

@@ -51,16 +51,16 @@ data class NormalizedTouchData(
) {
/**
* [nativeSensorBounds] contains the location and dimensions of the sensor area in native
* resolution and natural orientation.
* [nativeBounds] contains the location and dimensions of the area in native resolution and
* natural orientation.
*
* Returns whether the coordinates of the given pointer are within the sensor's bounding box.
* Returns whether the coordinates of the given pointer are within the bounding box.
*/
fun isWithinSensor(nativeSensorBounds: Rect): Boolean {
return nativeSensorBounds.left <= x &&
nativeSensorBounds.right >= x &&
nativeSensorBounds.top <= y &&
nativeSensorBounds.bottom >= y
fun isWithinBounds(nativeBounds: Rect): Boolean {
return nativeBounds.left <= x &&
nativeBounds.right >= x &&
nativeBounds.top <= y &&
nativeBounds.bottom >= y
}
@JvmOverloads

View File

@@ -20,5 +20,9 @@ import android.graphics.Rect
/** Determines whether the touch has a sufficient overlap with the sensor. */
interface OverlapDetector {
fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean
fun isGoodOverlap(
touchData: NormalizedTouchData,
nativeSensorBounds: Rect,
nativeOverlayBounds: Rect
): Boolean
}

View File

@@ -46,7 +46,13 @@ class SinglePointerTouchProcessor @Inject constructor(val overlapDetector: Overl
val touchData = List(event.pointerCount) { event.normalize(it, overlayParams) }
val pointersOnSensor =
touchData
.filter { overlapDetector.isGoodOverlap(it, overlayParams.nativeSensorBounds) }
.filter {
overlapDetector.isGoodOverlap(
it,
overlayParams.nativeSensorBounds,
overlayParams.nativeOverlayBounds
)
}
.map { it.pointerId }
return PreprocessedTouch(touchData, previousPointerOnSensorId, pointersOnSensor)
}

View File

@@ -33,7 +33,7 @@ class BoundingBoxOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() {
@Test
fun isGoodOverlap() {
val touchData = TOUCH_DATA.copy(x = testCase.x.toFloat(), y = testCase.y.toFloat())
val actual = underTest.isGoodOverlap(touchData, SENSOR)
val actual = underTest.isGoodOverlap(touchData, SENSOR, OVERLAY)
assertThat(actual).isEqualTo(testCase.expected)
}
@@ -50,8 +50,10 @@ class BoundingBoxOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() {
validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY())
),
genNegativeTestCases(
invalidXs = listOf(SENSOR.left - 1, SENSOR.right + 1),
invalidYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1),
invalidXs =
listOf(SENSOR.left - 1, SENSOR.right + 1, OVERLAY.left, OVERLAY.right),
invalidYs =
listOf(SENSOR.top - 1, SENSOR.bottom + 1, OVERLAY.top, OVERLAY.bottom),
validXs = listOf(SENSOR.left, SENSOR.right, SENSOR.centerX()),
validYs = listOf(SENSOR.top, SENSOR.bottom, SENSOR.centerY())
)
@@ -82,6 +84,7 @@ private val TOUCH_DATA =
)
private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 500 /* bottom */)
private val OVERLAY = Rect(0 /* left */, 100 /* top */, 400 /* right */, 600 /* bottom */)
private fun genTestCases(
xs: List<Int>,

View File

@@ -43,7 +43,7 @@ class EllipseOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() {
minor = testCase.minor,
major = testCase.major
)
val actual = underTest.isGoodOverlap(touchData, SENSOR)
val actual = underTest.isGoodOverlap(touchData, SENSOR, OVERLAY)
assertThat(actual).isEqualTo(testCase.expected)
}
@@ -71,8 +71,10 @@ class EllipseOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() {
expected = true
),
genNegativeTestCase(
outerXs = listOf(SENSOR.left - 1, SENSOR.right + 1),
outerYs = listOf(SENSOR.top - 1, SENSOR.bottom + 1),
outerXs =
listOf(SENSOR.left - 1, SENSOR.right + 1, OVERLAY.left, OVERLAY.right),
outerYs =
listOf(SENSOR.top - 1, SENSOR.bottom + 1, OVERLAY.top, OVERLAY.bottom),
minor = 100f,
major = 100f,
expected = false
@@ -104,6 +106,7 @@ private val TOUCH_DATA =
)
private val SENSOR = Rect(100 /* left */, 200 /* top */, 300 /* right */, 400 /* bottom */)
private val OVERLAY = Rect(0 /* left */, 100 /* top */, 400 /* right */, 600 /* bottom */)
private fun genPositiveTestCases(
innerXs: List<Int>,

View File

@@ -16,7 +16,7 @@ class NormalizedTouchDataTest(val testCase: TestCase) : SysuiTestCase() {
@Test
fun isWithinSensor() {
val touchData = TOUCH_DATA.copy(x = testCase.x.toFloat(), y = testCase.y.toFloat())
val actual = touchData.isWithinSensor(SENSOR)
val actual = touchData.isWithinBounds(SENSOR)
assertThat(actual).isEqualTo(testCase.expected)
}

View File

@@ -21,7 +21,11 @@ import android.graphics.Rect
class FakeOverlapDetector : OverlapDetector {
var shouldReturn: Map<Int, Boolean> = mapOf()
override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean {
override fun isGoodOverlap(
touchData: NormalizedTouchData,
nativeSensorBounds: Rect,
nativeOverlayBounds: Rect
): Boolean {
return shouldReturn[touchData.pointerId]
?: error("Unexpected PointerId not declared in TestCase currentPointers")
}