Merge "Announce feedback for udfps misses during enroll" into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
23751ef862
@@ -804,6 +804,14 @@
|
||||
<!-- Message shown when non-bypass face authentication succeeds and UDFPS is supported. Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
|
||||
<string name="keyguard_face_successful_unlock_press">Unlocked by face. Press the unlock icon to open.</string>
|
||||
|
||||
<!-- Messages shown when users press outside of udfps region during -->
|
||||
<string-array name="udfps_accessibility_touch_hints">
|
||||
<item>Move left</item>
|
||||
<item>Move down</item>
|
||||
<item>Move right</item>
|
||||
<item>Move up</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Message shown when face authentication fails and the pin pad is visible. [CHAR LIMIT=60] -->
|
||||
<string name="keyguard_retry">Swipe up to try again</string>
|
||||
|
||||
|
||||
@@ -192,4 +192,9 @@ abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
|
||||
* 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) {}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -99,4 +99,10 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp
|
||||
public int getPaddingY() {
|
||||
return mEnrollProgressBarRadius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAnnounceForAccessibility(String str) {
|
||||
mView.announceForAccessibility(str);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -367,6 +367,73 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
|
||||
assertThat(controllerOverlay.matchesRequestId(REQUEST_ID)).isTrue()
|
||||
assertThat(controllerOverlay.matchesRequestId(REQUEST_ID + 1)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTouchOutsideAreaNoRotation() = withReason(REASON_ENROLL_ENROLLING) {
|
||||
val touchHints =
|
||||
context.resources.getStringArray(R.array.udfps_accessibility_touch_hints)
|
||||
val rotation = Surface.ROTATION_0
|
||||
// touch at 0 degrees
|
||||
assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 0.0f /* y */,
|
||||
0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
|
||||
.isEqualTo(touchHints[0])
|
||||
// touch at 90 degrees
|
||||
assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, -1.0f /* y */,
|
||||
0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
|
||||
.isEqualTo(touchHints[1])
|
||||
// touch at 180 degrees
|
||||
assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(-1.0f /* x */, 0.0f /* y */,
|
||||
0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
|
||||
.isEqualTo(touchHints[2])
|
||||
// touch at 270 degrees
|
||||
assertThat(controllerOverlay.onTouchOutsideOfSensorAreaImpl(0.0f /* x */, 1.0f /* y */,
|
||||
0.0f /* sensorX */, 0.0f /* sensorY */, rotation))
|
||||
.isEqualTo(touchHints[3])
|
||||
}
|
||||
|
||||
fun testTouchOutsideAreaNoRotation90Degrees() = withReason(REASON_ENROLL_ENROLLING) {
|
||||
val touchHints =
|
||||
context.resources.getStringArray(R.array.udfps_accessibility_touch_hints)
|
||||
val rotation = Surface.ROTATION_90
|
||||
// touch at 0 degrees -> 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(
|
||||
|
||||
Reference in New Issue
Block a user