Merge changes from topic "EnableNewTouch"

* changes:
  Make EllipseOverlapDetector configurable
  Enable new touch detection by default on Master
This commit is contained in:
Austin Delgado
2023-01-31 21:53:28 +00:00
committed by Android (Google) Code Review
7 changed files with 138 additions and 46 deletions

View File

@@ -5001,6 +5001,26 @@
-->
</integer-array>
<!-- The properties for handling UDFPS touch detection
<string-array name="config_udfps_touch_detection_options">
<item>[detector_type],[sensor_shape],[target_size],[min_ellipse_overlap_percentage]</item>
</string-array>
[detector_type]: 0 for bounding box detector, 1 for ellipse detector
[sensor_shape]: 0 for square, 1 for circle
[target_size]: percentage (defined as a float of 0-1) of sensor that is considered the target, expands outward from center
[min_ellipse_overlap_percentage]: minimum percentage (float from 0-1) needed to be considered a valid overlap
[step_size]: size of each step when iterating over sensor pixel grid
-->
<string-array name="config_udfps_touch_detection_options">
<item>0,0,1.0,0,1</item>
<item>1,1,1.0,0,1</item>
<item>1,1,1.0,.4,1</item>
</string-array>
<!-- The integer index of the selected option in config_udfps_touch_detection_options -->
<integer name="config_selected_udfps_touch_detection">2</integer>
<!-- An array of arrays of side fingerprint sensor properties relative to each display.
Note: this value is temporary and is expected to be queried directly
from the HAL in the future. -->

View File

@@ -2649,6 +2649,8 @@
<java-symbol type="array" name="config_biometric_sensors" />
<java-symbol type="bool" name="allow_test_udfps" />
<java-symbol type="array" name="config_udfps_sensor_props" />
<java-symbol type="array" name="config_udfps_touch_detection_options" />
<java-symbol type="integer" name="config_selected_udfps_touch_detection" />
<java-symbol type="array" name="config_sfps_sensor_props" />
<java-symbol type="bool" name="config_is_powerbutton_fps" />
<java-symbol type="array" name="config_udfps_enroll_stage_thresholds" />

View File

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

View File

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

View File

@@ -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<Point> {
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)
)
}
}

View File

@@ -572,9 +572,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")

View File

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