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() {