Loading core/res/res/values/config.xml +20 −0 Original line number Diff line number Diff line Loading @@ -4979,6 +4979,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. --> Loading core/res/res/values/symbols.xml +2 −0 Original line number Diff line number Diff line Loading @@ -2646,6 +2646,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" /> Loading packages/SystemUI/src/com/android/systemui/biometrics/EllipseOverlapDetectorParams.kt 0 → 100644 +13 −0 Original line number Diff line number Diff line 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) packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt +26 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -33,11 +36,31 @@ 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 { return BoundingBoxOverlapDetector() } } } } packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt +70 −28 Original line number Diff line number Diff line Loading @@ -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 { Loading @@ -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) ) } } Loading
core/res/res/values/config.xml +20 −0 Original line number Diff line number Diff line Loading @@ -4979,6 +4979,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. --> Loading
core/res/res/values/symbols.xml +2 −0 Original line number Diff line number Diff line Loading @@ -2646,6 +2646,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" /> Loading
packages/SystemUI/src/com/android/systemui/biometrics/EllipseOverlapDetectorParams.kt 0 → 100644 +13 −0 Original line number Diff line number Diff line 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)
packages/SystemUI/src/com/android/systemui/biometrics/dagger/UdfpsModule.kt +26 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -33,11 +36,31 @@ 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 { return BoundingBoxOverlapDetector() } } } }
packages/SystemUI/src/com/android/systemui/biometrics/udfps/EllipseOverlapDetector.kt +70 −28 Original line number Diff line number Diff line Loading @@ -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 { Loading @@ -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) ) } }