Merge "Announce feedback for udfps misses during enroll" into tm-dev am: 23751ef862
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18352775 Change-Id: I562d54685ccbcde6ea6f12fa6546a7f7d8b767b3 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -817,6 +817,14 @@
|
|||||||
<string name="keyguard_face_successful_unlock_press_alt_3">Face recognized. Press the unlock icon to open.</string>
|
<string name="keyguard_face_successful_unlock_press_alt_3">Face recognized. 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] -->
|
<!-- 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>
|
<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
|
* Called on touches outside of the view if listenForTouchesOutsideView returns true
|
||||||
*/
|
*/
|
||||||
open fun onTouchOutsideView() {}
|
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);
|
&& 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
|
@VisibleForTesting
|
||||||
boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
|
boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) {
|
||||||
if (mOverlay == null) {
|
if (mOverlay == null) {
|
||||||
@@ -434,6 +455,7 @@ public class UdfpsController implements DozeReceiver {
|
|||||||
mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */);
|
mKeyguardViewManager.notifyKeyguardAuthenticated(false /* strongAuth */);
|
||||||
mAttemptedToDismissKeyguard = true;
|
mAttemptedToDismissKeyguard = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Trace.endSection();
|
Trace.endSection();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -457,6 +479,8 @@ public class UdfpsController implements DozeReceiver {
|
|||||||
mAttemptedToDismissKeyguard = true;
|
mAttemptedToDismissKeyguard = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Map the touch to portrait mode if the device is in landscape mode.
|
||||||
|
final Point scaledTouch = getTouchInNativeCoordinates(event, idx);
|
||||||
if (actionMoveWithinSensorArea) {
|
if (actionMoveWithinSensorArea) {
|
||||||
if (mVelocityTracker == null) {
|
if (mVelocityTracker == null) {
|
||||||
// touches could be injected, so the velocity tracker may not have
|
// 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;
|
final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime;
|
||||||
if (!isIlluminationRequested && !mAcquiredReceived
|
if (!isIlluminationRequested && !mAcquiredReceived
|
||||||
&& !exceedsVelocityThreshold) {
|
&& !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();
|
final float scale = mOverlayParams.getScaleFactor();
|
||||||
int scaledX = (int) (portraitTouch.x / scale);
|
|
||||||
int scaledY = (int) (portraitTouch.y / scale);
|
|
||||||
float scaledMinor = minor / scale;
|
float scaledMinor = minor / scale;
|
||||||
float scaledMajor = major / 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);
|
Log.v(TAG, "onTouch | finger down: " + touchInfo);
|
||||||
mTouchLogTime = mSystemClock.elapsedRealtime();
|
mTouchLogTime = mSystemClock.elapsedRealtime();
|
||||||
mPowerManager.userActivity(mSystemClock.uptimeMillis(),
|
mPowerManager.userActivity(mSystemClock.uptimeMillis(),
|
||||||
@@ -511,6 +520,24 @@ public class UdfpsController implements DozeReceiver {
|
|||||||
} else {
|
} else {
|
||||||
Log.v(TAG, "onTouch | finger outside");
|
Log.v(TAG, "onTouch | finger outside");
|
||||||
onFingerUp(requestId, udfpsView);
|
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();
|
Trace.endSection();
|
||||||
|
|||||||
@@ -130,6 +130,8 @@ class UdfpsControllerOverlay(
|
|||||||
val animationViewController: UdfpsAnimationViewController<*>?
|
val animationViewController: UdfpsAnimationViewController<*>?
|
||||||
get() = overlayView?.animationViewController
|
get() = overlayView?.animationViewController
|
||||||
|
|
||||||
|
private var touchExplorationEnabled = false
|
||||||
|
|
||||||
/** Show the overlay or return false and do nothing if it is already showing. */
|
/** Show the overlay or return false and do nothing if it is already showing. */
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
fun show(controller: UdfpsController, params: UdfpsOverlayParams): Boolean {
|
fun show(controller: UdfpsController, params: UdfpsOverlayParams): Boolean {
|
||||||
@@ -154,14 +156,16 @@ class UdfpsControllerOverlay(
|
|||||||
}
|
}
|
||||||
|
|
||||||
windowManager.addView(this, coreLayoutParams.updateDimensions(animation))
|
windowManager.addView(this, coreLayoutParams.updateDimensions(animation))
|
||||||
|
touchExplorationEnabled = accessibilityManager.isTouchExplorationEnabled
|
||||||
overlayTouchListener = TouchExplorationStateChangeListener {
|
overlayTouchListener = TouchExplorationStateChangeListener {
|
||||||
if (accessibilityManager.isTouchExplorationEnabled) {
|
if (accessibilityManager.isTouchExplorationEnabled) {
|
||||||
setOnHoverListener { v, event -> onTouch(v, event, true) }
|
setOnHoverListener { v, event -> onTouch(v, event, true) }
|
||||||
setOnTouchListener(null)
|
setOnTouchListener(null)
|
||||||
|
touchExplorationEnabled = true
|
||||||
} else {
|
} else {
|
||||||
setOnHoverListener(null)
|
setOnHoverListener(null)
|
||||||
setOnTouchListener { v, event -> onTouch(v, event, true) }
|
setOnTouchListener { v, event -> onTouch(v, event, true) }
|
||||||
|
touchExplorationEnabled = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
accessibilityManager.addTouchExplorationStateChangeListener(
|
accessibilityManager.addTouchExplorationStateChangeListener(
|
||||||
@@ -179,7 +183,7 @@ class UdfpsControllerOverlay(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun inflateUdfpsAnimation(
|
fun inflateUdfpsAnimation(
|
||||||
view: UdfpsView,
|
view: UdfpsView,
|
||||||
controller: UdfpsController
|
controller: UdfpsController
|
||||||
): UdfpsAnimationViewController<*>? {
|
): UdfpsAnimationViewController<*>? {
|
||||||
@@ -278,6 +282,88 @@ class UdfpsControllerOverlay(
|
|||||||
enrollHelper?.onEnrollmentHelp()
|
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. */
|
/** Cancel this request. */
|
||||||
fun cancel() {
|
fun cancel() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -99,4 +99,10 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp
|
|||||||
public int getPaddingY() {
|
public int getPaddingY() {
|
||||||
return mEnrollProgressBarRadius;
|
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)).isTrue()
|
||||||
assertThat(controllerOverlay.matchesRequestId(REQUEST_ID + 1)).isFalse()
|
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(
|
private class EnrollListener(
|
||||||
|
|||||||
Reference in New Issue
Block a user