From 0a357dec2e9e602d0bef5baebf52e1f498f1346a Mon Sep 17 00:00:00 2001 From: Austin Delgado Date: Thu, 12 Jan 2023 17:19:43 -0800 Subject: [PATCH 1/2] Enable new touch detection by default on Master Change-Id: I6d1d81e60fc9299bcec91acaf10333b08a9f3bde Bug: 265455206 Test: atest SystemUITests:com.android.systemui.biometrics --- packages/SystemUI/src/com/android/systemui/flags/Flags.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 6012e3eb11a22..2cb9a78fac17a 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -542,9 +542,8 @@ object Flags { // 2200 - udfps // TODO(b/259264861): Tracking Bug - @JvmField val UDFPS_NEW_TOUCH_DETECTION = unreleasedFlag(2200, "udfps_new_touch_detection") - @JvmField val UDFPS_ELLIPSE_DEBUG_UI = unreleasedFlag(2201, "udfps_ellipse_debug") - @JvmField val UDFPS_ELLIPSE_DETECTION = unreleasedFlag(2202, "udfps_ellipse_detection") + @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection") + @JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag(2201, "udfps_ellipse_detection") // 2300 - stylus @JvmField val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used") From f28f635a89d3b9ef4998dfb7caccb728c4b240ba Mon Sep 17 00:00:00 2001 From: Austin Delgado Date: Tue, 17 Jan 2023 13:59:51 -0800 Subject: [PATCH 2/2] Make EllipseOverlapDetector configurable Bug: 264895014 Test: atest SystemUITests:com.android.systemui.biometrics Change-Id: I50c7d80f52e15eefecb842e237cffb7998284461 --- core/res/res/values/config.xml | 20 ++++ core/res/res/values/symbols.xml | 2 + .../EllipseOverlapDetectorParams.kt | 13 +++ .../systemui/biometrics/dagger/UdfpsModule.kt | 29 +++++- .../udfps/EllipseOverlapDetector.kt | 98 +++++++++++++------ .../udfps/EllipseOverlapDetectorTest.kt | 17 +--- 6 files changed, 136 insertions(+), 43 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/EllipseOverlapDetectorParams.kt diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index f544feb0ec130..d0b3b7253f404 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4979,6 +4979,26 @@ --> + + + 0,0,1.0,0,1 + 1,1,1.0,0,1 + 1,1,1.0,.4,1 + + + + 2 + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 9dc7835e03f2b..9fa1cafa7fcaf 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2646,6 +2646,8 @@ + + diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/EllipseOverlapDetectorParams.kt b/packages/SystemUI/src/com/android/systemui/biometrics/EllipseOverlapDetectorParams.kt new file mode 100644 index 0000000000000..ab9b690d4f6c6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/EllipseOverlapDetectorParams.kt @@ -0,0 +1,13 @@ +package com.android.systemui.biometrics + +/** + * Collection of parameters used by EllipseOverlapDetector + * + * [minOverlap] minimum percentage (float from 0-1) needed to be considered a valid overlap + * + * [targetSize] percentage (defined as a float of 0-1) of sensor that is considered the target, + * expands outward from center + * + * [stepSize] size of each step when iterating over sensor pixel grid + */ +class EllipseOverlapDetectorParams(val minOverlap: Float, val targetSize: Float, val stepSize: Int) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt index 001fed76c1249..20c3e4098e838 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt @@ -16,6 +16,9 @@ package com.android.systemui.biometrics.dagger +import android.content.res.Resources +import com.android.internal.R +import com.android.systemui.biometrics.EllipseOverlapDetectorParams import com.android.systemui.biometrics.udfps.BoundingBoxOverlapDetector import com.android.systemui.biometrics.udfps.EllipseOverlapDetector import com.android.systemui.biometrics.udfps.OverlapDetector @@ -33,10 +36,30 @@ interface UdfpsModule { @Provides @SysUISingleton fun providesOverlapDetector(featureFlags: FeatureFlags): OverlapDetector { - return if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) { - EllipseOverlapDetector() + if (featureFlags.isEnabled(Flags.UDFPS_ELLIPSE_DETECTION)) { + val selectedOption = + Resources.getSystem() + .getInteger(R.integer.config_selected_udfps_touch_detection) + val values = + Resources.getSystem() + .getStringArray(R.array.config_udfps_touch_detection_options)[ + selectedOption] + .split(",") + .map { it.toFloat() } + + return if (values[0] == 1f) { + EllipseOverlapDetector( + EllipseOverlapDetectorParams( + minOverlap = values[3], + targetSize = values[2], + stepSize = values[4].toInt() + ) + ) + } else { + BoundingBoxOverlapDetector() + } } else { - BoundingBoxOverlapDetector() + return BoundingBoxOverlapDetector() } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt index 682d38a8f1a83..2e2970f874bbf 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt @@ -18,22 +18,84 @@ package com.android.systemui.biometrics.udfps import android.graphics.Point import android.graphics.Rect -import androidx.annotation.VisibleForTesting +import android.util.Log +import com.android.systemui.biometrics.EllipseOverlapDetectorParams import com.android.systemui.dagger.SysUISingleton import kotlin.math.cos -import kotlin.math.pow import kotlin.math.sin +private enum class SensorPixelPosition { + OUTSIDE, // Pixel that falls outside of sensor circle + SENSOR, // Pixel within sensor circle + TARGET // Pixel within sensor center target +} + +private val isDebug = true +private val TAG = "EllipseOverlapDetector" + /** * 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 { - +class EllipseOverlapDetector(private val params: EllipseOverlapDetectorParams) : OverlapDetector { override fun isGoodOverlap(touchData: NormalizedTouchData, nativeSensorBounds: Rect): Boolean { - val points = calculateSensorPoints(nativeSensorBounds) - return points.count { checkPoint(it, touchData) } >= neededPoints + var isTargetTouched = false + var sensorPixels = 0 + var coveredPixels = 0 + for (y in nativeSensorBounds.top..nativeSensorBounds.bottom step params.stepSize) { + for (x in nativeSensorBounds.left..nativeSensorBounds.right step params.stepSize) { + // Check where pixel is within the sensor TODO: (b/265836919) This could be improved + // by precomputing these points + val pixelPosition = + isPartOfSensorArea( + x, + y, + nativeSensorBounds.centerX(), + nativeSensorBounds.centerY(), + nativeSensorBounds.width() / 2 + ) + if (pixelPosition != SensorPixelPosition.OUTSIDE) { + sensorPixels++ + + // Check if this pixel falls within ellipse touch + if (checkPoint(Point(x, y), touchData)) { + coveredPixels++ + + // Check that at least one covered pixel is within sensor target + isTargetTouched = + isTargetTouched or (pixelPosition == SensorPixelPosition.TARGET) + } + } + } + } + + val percentage: Float = coveredPixels.toFloat() / sensorPixels + if (isDebug) { + Log.v( + TAG, + "covered: $coveredPixels, sensor: $sensorPixels, " + + "percentage: $percentage, isCenterTouched: $isTargetTouched" + ) + } + + return percentage >= params.minOverlap && isTargetTouched + } + + /** Checks if point is in the sensor center target circle, outer circle, or outside of sensor */ + private fun isPartOfSensorArea(x: Int, y: Int, cX: Int, cY: Int, r: Int): SensorPixelPosition { + val dx = cX - x + val dy = cY - y + + val disSquared = dx * dx + dy * dy + + return if (disSquared <= (r * params.targetSize) * (r * params.targetSize)) { + SensorPixelPosition.TARGET + } else if (disSquared <= r * r) { + SensorPixelPosition.SENSOR + } else { + SensorPixelPosition.OUTSIDE + } } private fun checkPoint(point: Point, touchData: NormalizedTouchData): Boolean { @@ -45,29 +107,9 @@ class EllipseOverlapDetector(private val neededPoints: Int = 2) : OverlapDetecto 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) / (touchData.minor / 2).pow(2) + - (c - d).pow(2) / (touchData.major / 2).pow(2) + (a + b) * (a + b) / ((touchData.minor / 2) * (touchData.minor / 2)) + + (c - d) * (c - d) / ((touchData.major / 2) * (touchData.major / 2)) return result <= 1 } - - @VisibleForTesting - fun calculateSensorPoints(sensorBounds: Rect): List { - val sensorX = sensorBounds.centerX() - val sensorY = sensorBounds.centerY() - val cornerOffset: Int = sensorBounds.width() / 4 - val sideOffset: Int = sensorBounds.width() / 3 - - return listOf( - Point(sensorX - cornerOffset, sensorY - cornerOffset), - Point(sensorX, sensorY - sideOffset), - Point(sensorX + cornerOffset, sensorY - cornerOffset), - Point(sensorX - sideOffset, sensorY), - Point(sensorX, sensorY), - Point(sensorX + sideOffset, sensorY), - Point(sensorX - cornerOffset, sensorY + cornerOffset), - Point(sensorX, sensorY + sideOffset), - Point(sensorX + cornerOffset, sensorY + cornerOffset) - ) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt index af46d9b97abfd..4b41537208da5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetectorTest.kt @@ -16,30 +16,23 @@ package com.android.systemui.biometrics.udfps -import android.graphics.Point import android.graphics.Rect import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.EllipseOverlapDetectorParams import com.google.common.truth.Truth.assertThat -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized import org.junit.runners.Parameterized.Parameters -import org.mockito.Mockito.spy -import org.mockito.Mockito.`when` as whenEver @SmallTest @RunWith(Parameterized::class) class EllipseOverlapDetectorTest(val testCase: TestCase) : SysuiTestCase() { - val underTest = spy(EllipseOverlapDetector(neededPoints = 1)) - - @Before - fun setUp() { - // Use one single center point for testing, required or total number of points may change - whenEver(underTest.calculateSensorPoints(SENSOR)) - .thenReturn(listOf(Point(SENSOR.centerX(), SENSOR.centerY()))) - } + val underTest = + EllipseOverlapDetector( + EllipseOverlapDetectorParams(minOverlap = .4f, targetSize = .2f, stepSize = 1) + ) @Test fun isGoodOverlap() {