diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 2c3d947ba9e23..7173c20479c3a 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -804,6 +804,14 @@ Unlocked by face. Press the unlock icon to open. + + + Move left + Move down + Move right + Move up + + Swipe up to try again diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt index 5cfbdb0caf2d2..742c65c2f8541 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt @@ -192,4 +192,9 @@ abstract class UdfpsAnimationViewController( * Called on touches outside of the view if listenForTouchesOutsideView returns true */ open fun onTouchOutsideView() {} + + /** + * Called when a view should announce an accessibility event. + */ + open fun doAnnounceForAccessibility(str: String) {} } \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 9febaa01eed71..88e467f6c2e2d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -381,6 +381,27 @@ public class UdfpsController implements DozeReceiver { && mOverlayParams.getSensorBounds().contains((int) x, (int) y); } + private Point getTouchInNativeCoordinates(@NonNull MotionEvent event, int idx) { + Point portraitTouch = new Point( + (int) event.getRawX(idx), + (int) event.getRawY(idx) + ); + final int rot = mOverlayParams.getRotation(); + if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) { + RotationUtils.rotatePoint(portraitTouch, + RotationUtils.deltaRotation(rot, Surface.ROTATION_0), + mOverlayParams.getLogicalDisplayWidth(), + mOverlayParams.getLogicalDisplayHeight() + ); + } + + // Scale the coordinates to native resolution. + final float scale = mOverlayParams.getScaleFactor(); + portraitTouch.x = (int) (portraitTouch.x / scale); + portraitTouch.y = (int) (portraitTouch.y / scale); + return portraitTouch; + } + @VisibleForTesting boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { if (mOverlay == null) { @@ -434,6 +455,7 @@ public class UdfpsController implements DozeReceiver { mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */); mAttemptedToDismissKeyguard = true; } + Trace.endSection(); break; @@ -457,6 +479,8 @@ public class UdfpsController implements DozeReceiver { mAttemptedToDismissKeyguard = true; break; } + // Map the touch to portrait mode if the device is in landscape mode. + final Point scaledTouch = getTouchInNativeCoordinates(event, idx); if (actionMoveWithinSensorArea) { if (mVelocityTracker == null) { // touches could be injected, so the velocity tracker may not have @@ -477,28 +501,13 @@ public class UdfpsController implements DozeReceiver { final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime; if (!isIlluminationRequested && !mAcquiredReceived && !exceedsVelocityThreshold) { - // Map the touch to portrait mode if the device is in landscape mode. - Point portraitTouch = new Point( - (int) event.getRawX(idx), - (int) event.getRawY(idx) - ); - final int rot = mOverlayParams.getRotation(); - if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) { - RotationUtils.rotatePoint(portraitTouch, - RotationUtils.deltaRotation(rot, Surface.ROTATION_0), - mOverlayParams.getLogicalDisplayWidth(), - mOverlayParams.getLogicalDisplayHeight() - ); - } - // Scale the coordinates to native resolution. final float scale = mOverlayParams.getScaleFactor(); - int scaledX = (int) (portraitTouch.x / scale); - int scaledY = (int) (portraitTouch.y / scale); float scaledMinor = minor / scale; float scaledMajor = major / scale; - onFingerDown(requestId, scaledX, scaledY, scaledMinor, scaledMajor); + onFingerDown(requestId, scaledTouch.x, scaledTouch.y, scaledMinor, + scaledMajor); Log.v(TAG, "onTouch | finger down: " + touchInfo); mTouchLogTime = mSystemClock.elapsedRealtime(); mPowerManager.userActivity(mSystemClock.uptimeMillis(), @@ -511,6 +520,24 @@ public class UdfpsController implements DozeReceiver { } else { Log.v(TAG, "onTouch | finger outside"); onFingerUp(requestId, udfpsView); + // Maybe announce for accessibility. + mFgExecutor.execute(() -> { + if (mOverlay == null) { + Log.e(TAG, "touch outside sensor area received" + + "but serverRequest is null"); + return; + } + // Scale the coordinates to native resolution. + final float scale = mOverlayParams.getScaleFactor(); + final float scaledSensorX = + mOverlayParams.getSensorBounds().centerX() / scale; + final float scaledSensorY = + mOverlayParams.getSensorBounds().centerY() / scale; + + mOverlay.onTouchOutsideOfSensorArea( + scaledTouch.x, scaledTouch.y, scaledSensorX, scaledSensorY, + mOverlayParams.getRotation()); + }); } } Trace.endSection(); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index faa93a5a2ad34..37db2bd51238c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -130,6 +130,8 @@ class UdfpsControllerOverlay( val animationViewController: UdfpsAnimationViewController<*>? get() = overlayView?.animationViewController + private var touchExplorationEnabled = false + /** Show the overlay or return false and do nothing if it is already showing. */ @SuppressLint("ClickableViewAccessibility") fun show(controller: UdfpsController, params: UdfpsOverlayParams): Boolean { @@ -154,14 +156,16 @@ class UdfpsControllerOverlay( } windowManager.addView(this, coreLayoutParams.updateDimensions(animation)) - + touchExplorationEnabled = accessibilityManager.isTouchExplorationEnabled overlayTouchListener = TouchExplorationStateChangeListener { if (accessibilityManager.isTouchExplorationEnabled) { setOnHoverListener { v, event -> onTouch(v, event, true) } setOnTouchListener(null) + touchExplorationEnabled = true } else { setOnHoverListener(null) setOnTouchListener { v, event -> onTouch(v, event, true) } + touchExplorationEnabled = false } } accessibilityManager.addTouchExplorationStateChangeListener( @@ -179,7 +183,7 @@ class UdfpsControllerOverlay( return false } - private fun inflateUdfpsAnimation( + fun inflateUdfpsAnimation( view: UdfpsView, controller: UdfpsController ): UdfpsAnimationViewController<*>? { @@ -278,6 +282,88 @@ class UdfpsControllerOverlay( enrollHelper?.onEnrollmentHelp() } + /** + * This function computes the angle of touch relative to the sensor and maps + * the angle to a list of help messages which are announced if accessibility is enabled. + * + */ + fun onTouchOutsideOfSensorArea( + touchX: Float, + touchY: Float, + sensorX: Float, + sensorY: Float, + rotation: Int + ) { + + if (!touchExplorationEnabled) { + return + } + val touchHints = + context.resources.getStringArray(R.array.udfps_accessibility_touch_hints) + if (touchHints.size != 4) { + Log.e(TAG, "expected exactly 4 touch hints, got $touchHints.size?") + return + } + val theStr = onTouchOutsideOfSensorAreaImpl(touchX, touchY, sensorX, sensorY, rotation) + Log.v(TAG, "Announcing touch outside : " + theStr) + animationViewController?.doAnnounceForAccessibility(theStr) + } + + /** + * This function computes the angle of touch relative to the sensor and maps + * the angle to a list of help messages which are announced if accessibility is enabled. + * + * There are 4 quadrants of the circle (90 degree arcs) + * + * [315, 360] && [0, 45) -> touchHints[0] = "Move Fingerprint to the left" + * [45, 135) -> touchHints[1] = "Move Fingerprint down" + * And so on. + */ + fun onTouchOutsideOfSensorAreaImpl( + touchX: Float, + touchY: Float, + sensorX: Float, + sensorY: Float, + rotation: Int + ): String { + val touchHints = + context.resources.getStringArray(R.array.udfps_accessibility_touch_hints) + + val xRelativeToSensor = touchX - sensorX + // Touch coordinates are with respect to the upper left corner, so reverse + // this calculation + val yRelativeToSensor = sensorY - touchY + + var angleInRad = + Math.atan2(yRelativeToSensor.toDouble(), xRelativeToSensor.toDouble()) + // If the radians are negative, that means we are counting clockwise. + // So we need to add 360 degrees + if (angleInRad < 0.0) { + angleInRad += 2.0 * Math.PI + } + // rad to deg conversion + val degrees = Math.toDegrees(angleInRad) + + val degreesPerBucket = 360.0 / touchHints.size + val halfBucketDegrees = degreesPerBucket / 2.0 + // The mapping should be as follows + // [315, 360] && [0, 45] -> 0 + // [45, 135] -> 1 + var index = (((degrees + halfBucketDegrees) % 360) / degreesPerBucket).toInt() + index %= touchHints.size + + // A rotation of 90 degrees corresponds to increasing the index by 1. + if (rotation == Surface.ROTATION_90) { + index = (index + 1) % touchHints.size + } + + if (rotation == Surface.ROTATION_270) { + index = (index + 3) % touchHints.size + } + + return touchHints[index] + } + /** Cancel this request. */ fun cancel() { try { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java index 2ed60e555c584..0b7bddee41fa3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java @@ -99,4 +99,10 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController 90 degrees + assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 0.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) + .isEqualTo(touchHints[1]) + // touch at 90 degrees -> 180 degrees + assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, -1.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) + .isEqualTo(touchHints[2]) + // touch at 180 degrees -> 270 degrees + assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(-1.0f /* x */, 0.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) + .isEqualTo(touchHints[3]) + // touch at 270 degrees -> 0 degrees + assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 1.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) + .isEqualTo(touchHints[0]) + } + + fun testTouchOutsideAreaNoRotation270Degrees() = withReason(REASON_ENROLL_ENROLLING) { + val touchHints = + context.resources.getStringArray(R.array.udfps_accessibility_touch_hints) + val rotation = Surface.ROTATION_270 + // touch at 0 degrees -> 270 degrees + assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 0.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) + .isEqualTo(touchHints[3]) + // touch at 90 degrees -> 0 degrees + assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, -1.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) + .isEqualTo(touchHints[0]) + // touch at 180 degrees -> 90 degrees + assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(-1.0f /* x */, 0.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) + .isEqualTo(touchHints[1]) + // touch at 270 degrees -> 180 degrees + assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 1.0f /* y */, + 0.0f /* sensorX */, 0.0f /* sensorY */, rotation)) + .isEqualTo(touchHints[2]) + } } private class EnrollListener(