Add udfps/ and support a11y for the udfps enroll view.

This CL:
1. Adds udfps/ in settingslib/ for sharing methods.
2. Moves scale factor and UdfpsOverlayParams.java to settingslib/.
3. Adds an accessibility view for fingerprint sensor annoucement.

Test: atest UdfpsControllerOverlayTest
Test: manually tested on device:
      Turn on talkback and turn this flag on via adb command
      adb shell setprop
        sys.fflag.override.settings_show_udfps_enroll_in_settings true
Bug: 186873966, 260617060
Change-Id: I083ccfccb435ad5baf29afd377098dd127a63f80
This commit is contained in:
Hao Dong
2023-01-17 10:39:41 +08:00
parent 417fca9a22
commit a8872ef32f
21 changed files with 402 additions and 242 deletions

View File

@@ -1626,4 +1626,12 @@
<string name="back_navigation_animation_summary">Enable system animations for predictive back.</string>
<!-- Developer setting: enable animations when a back gesture is executed, full explanation[CHAR LIMIT=NONE] -->
<string name="back_navigation_animation_dialog">This setting enables system animations for predictive gesture animation. It requires setting per-app "enableOnBackInvokedCallback" to true in the manifest file.</string>
<!-- [CHAR LIMIT=NONE] 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>
</resources>

View File

@@ -1,4 +1,4 @@
package com.android.systemui.biometrics
package com.android.settingslib.udfps
import android.graphics.Rect
import android.view.Surface

View File

@@ -0,0 +1,159 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.udfps;
import android.content.Context;
import android.graphics.Point;
import android.util.DisplayUtils;
import android.util.Log;
import android.util.RotationUtils;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.MotionEvent;
import android.view.Surface;
import com.android.settingslib.R;
/** Utility class for working with udfps. */
public class UdfpsUtils {
private static final String TAG = "UdfpsUtils";
/**
* Gets the scale factor representing the user's current resolution / the stable (default)
* resolution.
*
* @param displayInfo The display information.
*/
public float getScaleFactor(DisplayInfo displayInfo) {
Display.Mode maxDisplayMode =
DisplayUtils.getMaximumResolutionDisplayMode(displayInfo.supportedModes);
float scaleFactor =
DisplayUtils.getPhysicalPixelDisplaySizeRatio(
maxDisplayMode.getPhysicalWidth(),
maxDisplayMode.getPhysicalHeight(),
displayInfo.getNaturalWidth(),
displayInfo.getNaturalHeight()
);
return (scaleFactor == Float.POSITIVE_INFINITY) ? 1f : scaleFactor;
}
/**
* Gets the touch in native coordinates. Map the touch to portrait mode if the device is in
* landscape mode.
*
* @param idx The pointer identifier.
* @param event The MotionEvent object containing full information about the event.
* @param udfpsOverlayParams The [UdfpsOverlayParams] used.
* @return The mapped touch event.
*/
public Point getTouchInNativeCoordinates(int idx, MotionEvent event,
UdfpsOverlayParams udfpsOverlayParams) {
Point portraitTouch = new Point((int) event.getRawX(idx), (int) event.getRawY(idx));
int rot = udfpsOverlayParams.getRotation();
if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
RotationUtils.rotatePoint(
portraitTouch,
RotationUtils.deltaRotation(rot, Surface.ROTATION_0),
udfpsOverlayParams.getLogicalDisplayWidth(),
udfpsOverlayParams.getLogicalDisplayHeight()
);
}
// Scale the coordinates to native resolution.
float scale = udfpsOverlayParams.getScaleFactor();
portraitTouch.x = (int) (portraitTouch.x / scale);
portraitTouch.y = (int) (portraitTouch.y / scale);
return portraitTouch;
}
/**
* 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.
*
* @return Whether the announcing string is null
*/
public String onTouchOutsideOfSensorArea(boolean touchExplorationEnabled,
Context context, int touchX, int touchY, UdfpsOverlayParams udfpsOverlayParams) {
if (!touchExplorationEnabled) {
return null;
}
String[] touchHints = context.getResources().getStringArray(
R.array.udfps_accessibility_touch_hints);
if (touchHints.length != 4) {
Log.e(TAG, "expected exactly 4 touch hints, got " + touchHints.length + "?");
return null;
}
// Scale the coordinates to native resolution.
float scale = udfpsOverlayParams.getScaleFactor();
float scaledSensorX = udfpsOverlayParams.getSensorBounds().centerX() / scale;
float scaledSensorY = udfpsOverlayParams.getSensorBounds().centerY() / scale;
String theStr =
onTouchOutsideOfSensorAreaImpl(
touchHints,
touchX,
touchY,
scaledSensorX,
scaledSensorY,
udfpsOverlayParams.getRotation()
);
Log.v(TAG, "Announcing touch outside : $theStr");
return 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.
*/
private String onTouchOutsideOfSensorAreaImpl(String[] touchHints, float touchX,
float touchY, float sensorX, float sensorY, int rotation) {
float xRelativeToSensor = touchX - sensorX;
// Touch coordinates are with respect to the upper left corner, so reverse
// this calculation
float yRelativeToSensor = sensorY - touchY;
var angleInRad = Math.atan2(yRelativeToSensor, xRelativeToSensor);
// 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
double degrees = Math.toDegrees(angleInRad);
double degreesPerBucket = 360.0 / touchHints.length;
double halfBucketDegrees = degreesPerBucket / 2.0;
// The mapping should be as follows
// [315, 360] && [0, 45] -> 0
// [45, 135] -> 1
int index = (int) ((degrees + halfBucketDegrees) % 360 / degreesPerBucket);
index %= touchHints.length;
// A rotation of 90 degrees corresponds to increasing the index by 1.
if (rotation == Surface.ROTATION_90) {
index = (index + 1) % touchHints.length;
}
if (rotation == Surface.ROTATION_270) {
index = (index + 3) % touchHints.length;
}
return touchHints[index];
}
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.udfps;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.graphics.Rect;
import android.view.Surface;
import com.android.settingslib.R;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(RobolectricTestRunner.class)
public class UdfpsUtilsTest {
@Rule
public final MockitoRule rule = MockitoJUnit.rule();
private Context mContext;
private String[] mTouchHints;
private UdfpsUtils mUdfpsUtils;
@Before
public void setUp() {
mContext = RuntimeEnvironment.application;
mTouchHints = mContext.getResources().getStringArray(
R.array.udfps_accessibility_touch_hints);
mUdfpsUtils = new UdfpsUtils();
}
@Test
public void testTouchOutsideAreaNoRotation() {
int rotation = Surface.ROTATION_0;
// touch at 0 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 0/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
)
).isEqualTo(mTouchHints[0]);
// touch at 90 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, -1/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
)
).isEqualTo(mTouchHints[1]);
// touch at 180 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
-1 /* touchX */, 0/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
)
).isEqualTo(mTouchHints[2]);
// touch at 270 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 1/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
)
).isEqualTo(mTouchHints[3]);
}
@Test
public void testTouchOutsideAreaNoRotation90Degrees() {
int rotation = Surface.ROTATION_90;
// touch at 0 degrees -> 90 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 0 /* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
)
).isEqualTo(mTouchHints[1]);
// touch at 90 degrees -> 180 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, -1 /* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
)
).isEqualTo(mTouchHints[2]);
// touch at 180 degrees -> 270 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
-1 /* touchX */, 0 /* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
)
).isEqualTo(mTouchHints[3]);
// touch at 270 degrees -> 0 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 1/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
)
).isEqualTo(mTouchHints[0]);
}
@Test
public void testTouchOutsideAreaNoRotation270Degrees() {
int rotation = Surface.ROTATION_270;
// touch at 0 degrees -> 270 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 0/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
)
).isEqualTo(mTouchHints[3]);
// touch at 90 degrees -> 0 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, -1/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
)
).isEqualTo(mTouchHints[0]);
// touch at 180 degrees -> 90 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
-1 /* touchX */, 0/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
)
).isEqualTo(mTouchHints[1]);
// touch at 270 degrees -> 180 degrees
assertThat(
mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
0 /* touchX */, 1/* touchY */,
new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
)
).isEqualTo(mTouchHints[2]);
}
}

View File

@@ -19,4 +19,12 @@
android:id="@+id/udfps_animation_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- The layout height/width are placeholders, which will be overwritten by
FingerprintSensorPropertiesInternal. -->
<View
android:id="@+id/udfps_enroll_accessibility_view"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/accessibility_fingerprint_label"/>
</com.android.systemui.biometrics.UdfpsFpmEmptyView>

View File

@@ -908,14 +908,6 @@
<!-- Message shown when non-bypass face authentication succeeds. [CHAR LIMIT=60] -->
<string name="keyguard_face_successful_unlock_alt1">Face recognized</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>

View File

@@ -55,7 +55,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserManager;
import android.util.DisplayUtils;
import android.util.Log;
import android.util.RotationUtils;
import android.util.SparseBooleanArray;
@@ -69,6 +68,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.os.SomeArgs;
import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.udfps.UdfpsOverlayParams;
import com.android.settingslib.udfps.UdfpsUtils;
import com.android.systemui.CoreStartable;
import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
@@ -169,6 +170,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
@NonNull private final UserManager mUserManager;
@NonNull private final LockPatternUtils mLockPatternUtils;
@NonNull private final InteractionJankMonitor mInteractionJankMonitor;
@NonNull private final UdfpsUtils mUdfpsUtils;
private final @Background DelayableExecutor mBackgroundExecutor;
private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
@@ -578,17 +580,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
*/
private void updateSensorLocations() {
mDisplay.getDisplayInfo(mCachedDisplayInfo);
final Display.Mode maxDisplayMode =
DisplayUtils.getMaximumResolutionDisplayMode(mCachedDisplayInfo.supportedModes);
final float scaleFactor = android.util.DisplayUtils.getPhysicalPixelDisplaySizeRatio(
maxDisplayMode.getPhysicalWidth(), maxDisplayMode.getPhysicalHeight(),
mCachedDisplayInfo.getNaturalWidth(), mCachedDisplayInfo.getNaturalHeight());
if (scaleFactor == Float.POSITIVE_INFINITY) {
mScaleFactor = 1f;
} else {
mScaleFactor = scaleFactor;
}
mScaleFactor = mUdfpsUtils.getScaleFactor(mCachedDisplayInfo);
updateUdfpsLocation();
updateFingerprintLocation();
updateFaceLocation();
@@ -732,7 +724,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
@NonNull InteractionJankMonitor jankMonitor,
@Main Handler handler,
@Background DelayableExecutor bgExecutor,
@NonNull VibratorHelper vibrator) {
@NonNull VibratorHelper vibrator,
@NonNull UdfpsUtils udfpsUtils) {
mContext = context;
mExecution = execution;
mUserManager = userManager;
@@ -753,6 +746,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
mSfpsEnrolledForUser = new SparseBooleanArray();
mFaceEnrolledForUser = new SparseBooleanArray();
mVibratorHelper = vibrator;
mUdfpsUtils = udfpsUtils;
mLogContextInteractor = logContextInteractor;
mBiometricPromptInteractor = biometricPromptInteractor;

View File

@@ -50,10 +50,8 @@ import android.os.Trace;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.util.Log;
import android.util.RotationUtils;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.VelocityTracker;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
@@ -66,6 +64,8 @@ import com.android.internal.logging.InstanceId;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.FaceAuthApiRequestReason;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.udfps.UdfpsOverlayParams;
import com.android.settingslib.udfps.UdfpsUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.biometrics.dagger.BiometricsBackground;
@@ -168,6 +168,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull private final SessionTracker mSessionTracker;
@NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor;
@NonNull private final SecureSettings mSecureSettings;
@NonNull private final UdfpsUtils mUdfpsUtils;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@@ -266,7 +267,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mUdfpsDisplayMode, mSecureSettings, requestId, reason, callback,
(view, event, fromUdfpsView) -> onTouch(requestId, event,
fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
mPrimaryBouncerInteractor, mAlternateBouncerInteractor)));
mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsUtils)));
}
@Override
@@ -475,27 +476,6 @@ public class UdfpsController implements DozeReceiver, Dumpable {
&& 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;
}
private void tryDismissingKeyguard() {
if (!mOnFingerDown) {
playStartHaptic();
@@ -713,7 +693,8 @@ public class UdfpsController implements DozeReceiver, Dumpable {
break;
}
// Map the touch to portrait mode if the device is in landscape mode.
final Point scaledTouch = getTouchInNativeCoordinates(event, idx);
final Point scaledTouch = mUdfpsUtils.getTouchInNativeCoordinates(
idx, event, mOverlayParams);
if (actionMoveWithinSensorArea) {
if (mVelocityTracker == null) {
// touches could be injected, so the velocity tracker may not have
@@ -757,16 +738,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
+ "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());
mOverlay.onTouchOutsideOfSensorArea(scaledTouch);
});
}
}
@@ -838,7 +810,8 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull SinglePointerTouchProcessor singlePointerTouchProcessor,
@NonNull SessionTracker sessionTracker,
@NonNull AlternateBouncerInteractor alternateBouncerInteractor,
@NonNull SecureSettings secureSettings) {
@NonNull SecureSettings secureSettings,
@NonNull UdfpsUtils udfpsUtils) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -880,6 +853,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mAlternateBouncerInteractor = alternateBouncerInteractor;
mSecureSettings = secureSettings;
mUdfpsUtils = udfpsUtils;
mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
? singlePointerTouchProcessor : null;

View File

@@ -20,6 +20,7 @@ import android.annotation.SuppressLint
import android.annotation.UiThread
import android.content.Context
import android.graphics.PixelFormat
import android.graphics.Point
import android.graphics.Rect
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
@@ -46,6 +47,8 @@ import android.view.accessibility.AccessibilityManager.TouchExplorationStateChan
import androidx.annotation.LayoutRes
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.settingslib.udfps.UdfpsUtils
import com.android.settingslib.udfps.UdfpsOverlayParams
import com.android.systemui.R
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dump.DumpManager
@@ -101,6 +104,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
private val primaryBouncerInteractor: PrimaryBouncerInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
private val udfpsUtils: UdfpsUtils
) {
/** The view, when [isShowing], or null. */
var overlayView: UdfpsView? = null
@@ -239,7 +243,9 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
FeatureFlagUtils.SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS)) {
// Enroll udfps UI is handled by settings, so use empty view here
UdfpsFpmEmptyViewController(
view.addUdfpsView(R.layout.udfps_fpm_empty_view),
view.addUdfpsView(R.layout.udfps_fpm_empty_view){
updateAccessibilityViewLocation(sensorBounds)
},
statusBarStateController,
shadeExpansionStateManager,
dialogManager,
@@ -348,81 +354,18 @@ class UdfpsControllerOverlay @JvmOverloads constructor(
* 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
fun onTouchOutsideOfSensorArea(scaledTouch: Point) {
val theStr =
udfpsUtils.onTouchOutsideOfSensorArea(
touchExplorationEnabled,
context,
scaledTouch.x,
scaledTouch.y,
overlayParams
)
if (theStr != null) {
animationViewController?.doAnnounceForAccessibility(theStr)
}
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. */

View File

@@ -16,7 +16,11 @@
package com.android.systemui.biometrics
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import com.android.systemui.R
/**
* View corresponding with udfps_fpm_empty_view.xml
@@ -32,4 +36,13 @@ class UdfpsFpmEmptyView(
private val fingerprintDrawable: UdfpsFpDrawable = UdfpsFpDrawable(context)
override fun getDrawable(): UdfpsDrawable = fingerprintDrawable
fun updateAccessibilityViewLocation(sensorBounds: Rect) {
val fingerprintAccessibilityView: View = findViewById(R.id.udfps_enroll_accessibility_view)
val params: ViewGroup.LayoutParams = fingerprintAccessibilityView.layoutParams
params.width = sensorBounds.width()
params.height = sensorBounds.height()
fingerprintAccessibilityView.layoutParams = params
fingerprintAccessibilityView.requestLayout()
}
}

View File

@@ -32,6 +32,7 @@ import android.view.MotionEvent
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.settingslib.udfps.UdfpsOverlayParams
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main

View File

@@ -25,6 +25,7 @@ import android.graphics.RectF
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.FrameLayout
import com.android.settingslib.udfps.UdfpsOverlayParams
private const val TAG = "UdfpsOverlayView"
private const val POINT_SIZE = 10f

View File

@@ -26,6 +26,7 @@ import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.widget.FrameLayout
import com.android.settingslib.udfps.UdfpsOverlayParams
import com.android.systemui.R
import com.android.systemui.doze.DozeReceiver

View File

@@ -16,6 +16,7 @@
package com.android.systemui.biometrics.dagger
import com.android.settingslib.udfps.UdfpsUtils
import com.android.systemui.biometrics.data.repository.PromptRepository
import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl
import com.android.systemui.biometrics.domain.interactor.CredentialInteractor
@@ -54,6 +55,9 @@ interface BiometricsModule {
@BiometricsBackground
fun providesPluginExecutor(threadFactory: ThreadFactory): Executor =
threadFactory.buildExecutorOnNewThread("biometrics")
@Provides
fun providesUdfpsUtils(): UdfpsUtils = UdfpsUtils()
}
}

View File

@@ -21,7 +21,7 @@ import android.util.RotationUtils
import android.view.MotionEvent
import android.view.MotionEvent.INVALID_POINTER_ID
import android.view.Surface
import com.android.systemui.biometrics.UdfpsOverlayParams
import com.android.settingslib.udfps.UdfpsOverlayParams
import com.android.systemui.biometrics.udfps.TouchProcessorResult.Failure
import com.android.systemui.biometrics.udfps.TouchProcessorResult.ProcessedTouch
import com.android.systemui.dagger.SysUISingleton

View File

@@ -17,7 +17,7 @@
package com.android.systemui.biometrics.udfps
import android.view.MotionEvent
import com.android.systemui.biometrics.UdfpsOverlayParams
import com.android.settingslib.udfps.UdfpsOverlayParams
/**
* Determines whether a finger entered or left the area of the under-display fingerprint sensor

View File

@@ -85,6 +85,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.udfps.UdfpsUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
@@ -167,6 +168,8 @@ public class AuthControllerTest extends SysuiTestCase {
private BiometricPromptCredentialInteractor mBiometricPromptCredentialInteractor;
@Mock
private CredentialViewModel mCredentialViewModel;
@Mock
private UdfpsUtils mUdfpsUtils;
@Captor
private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor;
@@ -958,7 +961,7 @@ public class AuthControllerTest extends SysuiTestCase {
mPanelInteractionDetector, mUserManager, mLockPatternUtils, mUdfpsLogger,
mLogContextInteractor, () -> mBiometricPromptCredentialInteractor,
() -> mCredentialViewModel, mInteractionJankMonitor, mHandler,
mBackgroundExecutor, mVibratorHelper);
mBackgroundExecutor, mVibratorHelper, mUdfpsUtils);
}
@Override

View File

@@ -38,6 +38,8 @@ import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.settingslib.udfps.UdfpsOverlayParams
import com.android.settingslib.udfps.UdfpsUtils
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
@@ -110,6 +112,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
@Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
@Mock private lateinit var udfpsUtils: UdfpsUtils
@Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -144,7 +147,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
configurationController, keyguardStateController, unlockedScreenOffAnimationController,
udfpsDisplayMode, secureSettings, REQUEST_ID, reason,
controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable
primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils
)
block()
}
@@ -400,109 +403,6 @@ 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(

View File

@@ -71,6 +71,8 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.udfps.UdfpsOverlayParams;
import com.android.settingslib.udfps.UdfpsUtils;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -230,10 +232,12 @@ public class UdfpsControllerTest extends SysuiTestCase {
private ScreenLifecycle.Observer mScreenObserver;
private FingerprintSensorPropertiesInternal mOpticalProps;
private FingerprintSensorPropertiesInternal mUltrasonicProps;
private UdfpsUtils mUdfpsUtils;
@Before
public void setUp() {
Execution execution = new FakeExecution();
mUdfpsUtils = new UdfpsUtils();
when(mLayoutInflater.inflate(R.layout.udfps_view, null, false))
.thenReturn(mUdfpsView);
@@ -305,7 +309,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
mAlternateBouncerInteractor, mSecureSettings);
mAlternateBouncerInteractor, mSecureSettings, mUdfpsUtils);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());

View File

@@ -25,6 +25,7 @@ import android.testing.ViewUtils
import android.view.LayoutInflater
import android.view.Surface
import androidx.test.filters.SmallTest
import com.android.settingslib.udfps.UdfpsOverlayParams
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any

View File

@@ -23,8 +23,8 @@ import android.view.MotionEvent.PointerProperties
import android.view.Surface
import android.view.Surface.Rotation
import androidx.test.filters.SmallTest
import com.android.settingslib.udfps.UdfpsOverlayParams
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.UdfpsOverlayParams
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith