Support touchExplorationEnabled on udfps
- When touchExplorationEnabled, touch events become hover events. This updates UdfpsController to handle hover events so the user can still authenticate when a11y options are enabled - Update UdfpsView contentDescription - Update LockIconViewController labels to support a11y Test: manual Bug: 185912108 Change-Id: Ic9590607105459506f8ce5d4d73f779853f1ed31
This commit is contained in:
@@ -20,7 +20,8 @@
|
||||
android:id="@+id/udfps_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
systemui:sensorTouchAreaCoefficient="0.5">
|
||||
systemui:sensorTouchAreaCoefficient="0.5"
|
||||
android:contentDescription="@string/accessibility_fingerprint_label">
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/animation_view"
|
||||
|
||||
@@ -338,6 +338,8 @@
|
||||
<string name="accessibility_voice_assist_button">Voice Assist</string>
|
||||
<!-- Content description of the unlock button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_unlock_button">Unlock</string>
|
||||
<!-- Content description of the lock icon for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_lock_icon">Device locked</string>
|
||||
<!-- Content description hint of the unlock button when fingerprint is on (not shown on the screen). [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_waiting_for_fingerprint">Waiting for fingerprint</string>
|
||||
<!-- Accessibility action of the unlock button when fingerpint is on (not shown on the screen). [CHAR LIMIT=NONE] -->
|
||||
@@ -2958,4 +2960,15 @@
|
||||
|
||||
<!-- Secondary label for alarm tile when there is no next alarm information [CHAR LIMIT=20] -->
|
||||
<string name="qs_alarm_tile_no_alarm">No alarm set</string>
|
||||
|
||||
<!-- Accessibility label for fingerprint sensor [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_fingerprint_label">Fingerprint sensor</string>
|
||||
<!-- Accessibility label for disabled udfps sensor [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_udfps_disabled_button">Fingerprint sensor disabled</string>
|
||||
<!-- Accessibility action for tapping on an affordance that will bring up the user's
|
||||
pin/pattern/password bouncer (ie: "Double tap to authenticate") [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_authenticate_hint">authenticate</string>
|
||||
<!-- Accessibility action for tapping on an affordance on an unlocked lock screen (ie: "Double
|
||||
tap to enter device") [CHAR LIMIT=NONE] -->
|
||||
<string name="accessibility_enter_hint">enter device</string>
|
||||
</resources>
|
||||
|
||||
@@ -29,9 +29,12 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
|
||||
|
||||
import com.android.settingslib.Utils;
|
||||
import com.android.systemui.Dumpable;
|
||||
@@ -63,6 +66,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
|
||||
@NonNull private final KeyguardStateController mKeyguardStateController;
|
||||
@NonNull private final FalsingManager mFalsingManager;
|
||||
@NonNull private final AuthController mAuthController;
|
||||
@NonNull private final AccessibilityManager mAccessibilityManager;
|
||||
|
||||
private boolean mHasUdfpsOrFaceAuthFeatures;
|
||||
private boolean mUdfpsEnrolled;
|
||||
@@ -93,19 +97,17 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
|
||||
@NonNull KeyguardStateController keyguardStateController,
|
||||
@NonNull FalsingManager falsingManager,
|
||||
@NonNull AuthController authController,
|
||||
@NonNull DumpManager dumpManager
|
||||
@NonNull DumpManager dumpManager,
|
||||
@NonNull AccessibilityManager accessibilityManager
|
||||
) {
|
||||
super(view);
|
||||
if (mView != null) {
|
||||
mView.setOnClickListener(v -> onAffordanceClick());
|
||||
mView.setOnLongClickListener(v -> onAffordanceClick());
|
||||
}
|
||||
mStatusBarStateController = statusBarStateController;
|
||||
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
|
||||
mAuthController = authController;
|
||||
mKeyguardViewController = keyguardViewController;
|
||||
mKeyguardStateController = keyguardStateController;
|
||||
mFalsingManager = falsingManager;
|
||||
mAccessibilityManager = accessibilityManager;
|
||||
|
||||
final Context context = view.getContext();
|
||||
mButton = context.getResources().getDrawable(
|
||||
@@ -121,6 +123,11 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
|
||||
dumpManager.registerDumpable("LockIconViewController", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInit() {
|
||||
mView.setAccessibilityDelegate(mAccessibilityDelegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewAttached() {
|
||||
// we check this here instead of onInit since the FingeprintManager + FaceManager may not
|
||||
@@ -163,6 +170,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
|
||||
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
|
||||
mStatusBarStateController.addCallback(mStatusBarStateListener);
|
||||
mKeyguardStateController.addCallback(mKeyguardStateCallback);
|
||||
mAccessibilityManager.addTouchExplorationStateChangeListener(
|
||||
mTouchExplorationStateChangeListener);
|
||||
|
||||
updateVisibility();
|
||||
}
|
||||
@@ -172,6 +181,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
|
||||
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
|
||||
mStatusBarStateController.removeCallback(mStatusBarStateListener);
|
||||
mKeyguardStateController.removeCallback(mKeyguardStateCallback);
|
||||
mAccessibilityManager.removeTouchExplorationStateChangeListener(
|
||||
mTouchExplorationStateChangeListener);
|
||||
}
|
||||
|
||||
public float getTop() {
|
||||
@@ -210,20 +221,56 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
|
||||
mShowLockIcon = !mUdfpsEnrolled && !mCanDismissLockScreen && isLockScreen()
|
||||
&& mFaceAuthEnrolled;
|
||||
|
||||
updateClickListener();
|
||||
if (mShowButton) {
|
||||
mView.setImageDrawable(mButton);
|
||||
mView.setVisibility(View.VISIBLE);
|
||||
mView.setContentDescription(getResources().getString(
|
||||
R.string.accessibility_udfps_disabled_button));
|
||||
} else if (mShowUnlockIcon) {
|
||||
mView.setImageDrawable(mUnlockIcon);
|
||||
mView.setVisibility(View.VISIBLE);
|
||||
mView.setContentDescription(getResources().getString(
|
||||
R.string.accessibility_unlock_button));
|
||||
} else if (mShowLockIcon) {
|
||||
mView.setImageDrawable(mLockIcon);
|
||||
mView.setVisibility(View.VISIBLE);
|
||||
mView.setContentDescription(getResources().getString(
|
||||
R.string.accessibility_lock_icon));
|
||||
} else {
|
||||
mView.setVisibility(View.INVISIBLE);
|
||||
mView.setContentDescription(null);
|
||||
}
|
||||
}
|
||||
|
||||
private final View.AccessibilityDelegate mAccessibilityDelegate =
|
||||
new View.AccessibilityDelegate() {
|
||||
private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityAuthenticateHint =
|
||||
new AccessibilityNodeInfo.AccessibilityAction(
|
||||
AccessibilityNodeInfoCompat.ACTION_CLICK,
|
||||
getResources().getString(R.string.accessibility_authenticate_hint));
|
||||
private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityEnterHint =
|
||||
new AccessibilityNodeInfo.AccessibilityAction(
|
||||
AccessibilityNodeInfoCompat.ACTION_CLICK,
|
||||
getResources().getString(R.string.accessibility_enter_hint));
|
||||
public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) {
|
||||
super.onInitializeAccessibilityNodeInfo(v, info);
|
||||
removeAllActions(info);
|
||||
if (mShowButton || mShowLockIcon) {
|
||||
info.addAction(mAccessibilityAuthenticateHint);
|
||||
} else if (mShowUnlockIcon) {
|
||||
info.addAction(mAccessibilityEnterHint);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeAllActions(AccessibilityNodeInfo info) {
|
||||
info.removeAction(mAccessibilityAuthenticateHint);
|
||||
info.removeAction(mAccessibilityEnterHint);
|
||||
info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
|
||||
mView.setLongClickable(false);
|
||||
}
|
||||
};
|
||||
|
||||
private boolean isLockScreen() {
|
||||
return !mIsDozing
|
||||
&& !mIsBouncerShowing
|
||||
@@ -231,6 +278,17 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
|
||||
&& mStatusBarState == StatusBarState.KEYGUARD;
|
||||
}
|
||||
|
||||
private void updateClickListener() {
|
||||
if (mView != null) {
|
||||
mView.setOnClickListener(v -> onAffordanceClick());
|
||||
if (mAccessibilityManager.isTouchExplorationEnabled()) {
|
||||
mView.setOnLongClickListener(null);
|
||||
} else {
|
||||
mView.setOnLongClickListener(v -> onAffordanceClick());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
|
||||
pw.println(" mShowBouncerButton: " + mShowButton);
|
||||
@@ -298,4 +356,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
|
||||
updateVisibility();
|
||||
}
|
||||
};
|
||||
|
||||
private final AccessibilityManager.TouchExplorationStateChangeListener
|
||||
mTouchExplorationStateChangeListener = enabled -> updateClickListener();
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.RectF;
|
||||
@@ -55,6 +54,7 @@ import android.view.Surface;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.keyguard.KeyguardUpdateMonitor;
|
||||
@@ -108,6 +108,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
|
||||
@NonNull private final Handler mMainHandler;
|
||||
@NonNull private final FalsingManager mFalsingManager;
|
||||
@NonNull private final PowerManager mPowerManager;
|
||||
@NonNull private final AccessibilityManager mAccessibilityManager;
|
||||
// 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.
|
||||
@VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
|
||||
@@ -276,6 +277,13 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
|
||||
private final UdfpsView.OnTouchListener mOnTouchListener = (view, event) ->
|
||||
onTouch(view, event, true);
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private final UdfpsView.OnHoverListener mOnHoverListener = (view, event) ->
|
||||
onTouch(view, event, true);
|
||||
|
||||
private final AccessibilityManager.TouchExplorationStateChangeListener
|
||||
mTouchExplorationStateChangeListener = enabled -> updateTouchListener();
|
||||
|
||||
/**
|
||||
* @param x coordinate
|
||||
* @param y coordinate
|
||||
@@ -300,6 +308,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
|
||||
udfpsView.onTouchOutsideView();
|
||||
break;
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
case MotionEvent.ACTION_HOVER_ENTER:
|
||||
// To simplify the lifecycle of the velocity tracker, make sure it's never null
|
||||
// after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP.
|
||||
if (mVelocityTracker == null) {
|
||||
@@ -322,6 +331,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
|
||||
break;
|
||||
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
case MotionEvent.ACTION_HOVER_MOVE:
|
||||
final int idx = mActivePointerId == -1
|
||||
? event.getPointerId(0)
|
||||
: event.findPointerIndex(mActivePointerId);
|
||||
@@ -388,6 +398,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
|
||||
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_HOVER_EXIT:
|
||||
mActivePointerId = -1;
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
@@ -409,10 +420,9 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
|
||||
|
||||
@Inject
|
||||
public UdfpsController(@NonNull Context context,
|
||||
@Main Resources resources,
|
||||
@NonNull LayoutInflater inflater,
|
||||
@Nullable FingerprintManager fingerprintManager,
|
||||
WindowManager windowManager,
|
||||
@NonNull WindowManager windowManager,
|
||||
@NonNull StatusBarStateController statusBarStateController,
|
||||
@Main DelayableExecutor fgExecutor,
|
||||
@NonNull StatusBar statusBar,
|
||||
@@ -421,7 +431,8 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
|
||||
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
|
||||
@NonNull KeyguardViewMediator keyguardViewMediator,
|
||||
@NonNull FalsingManager falsingManager,
|
||||
@NonNull PowerManager powerManager) {
|
||||
@NonNull PowerManager powerManager,
|
||||
@NonNull AccessibilityManager accessibilityManager) {
|
||||
mContext = context;
|
||||
// TODO (b/185124905): inject main handler and vibrator once done prototyping
|
||||
mMainHandler = new Handler(Looper.getMainLooper());
|
||||
@@ -440,6 +451,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
|
||||
mKeyguardViewMediator = keyguardViewMediator;
|
||||
mFalsingManager = falsingManager;
|
||||
mPowerManager = powerManager;
|
||||
mAccessibilityManager = accessibilityManager;
|
||||
|
||||
mSensorProps = findFirstUdfps();
|
||||
// At least one UDFPS sensor exists
|
||||
@@ -577,7 +589,9 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
|
||||
mView.setAnimationViewController(animation);
|
||||
|
||||
mWindowManager.addView(mView, computeLayoutParams(animation));
|
||||
mView.setOnTouchListener(mOnTouchListener);
|
||||
mAccessibilityManager.addTouchExplorationStateChangeListener(
|
||||
mTouchExplorationStateChangeListener);
|
||||
updateTouchListener();
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "showUdfpsOverlay | failed to add window", e);
|
||||
}
|
||||
@@ -650,7 +664,10 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
|
||||
onFingerUp();
|
||||
mWindowManager.removeView(mView);
|
||||
mView.setOnTouchListener(null);
|
||||
mView.setOnHoverListener(null);
|
||||
mView.setAnimationViewController(null);
|
||||
mAccessibilityManager.removeTouchExplorationStateChangeListener(
|
||||
mTouchExplorationStateChangeListener);
|
||||
mView = null;
|
||||
} else {
|
||||
Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
|
||||
@@ -758,4 +775,18 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
|
||||
return defaultEffect;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTouchListener() {
|
||||
if (mView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAccessibilityManager.isTouchExplorationEnabled()) {
|
||||
mView.setOnHoverListener(mOnHoverListener);
|
||||
mView.setOnTouchListener(null);
|
||||
} else {
|
||||
mView.setOnHoverListener(null);
|
||||
mView.setOnTouchListener(mOnTouchListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.hardware.biometrics.ComponentInfoInternal;
|
||||
import android.hardware.biometrics.SensorProperties;
|
||||
@@ -40,6 +39,7 @@ import android.testing.TestableLooper.RunWithLooper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.WindowManager;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
@@ -85,8 +85,6 @@ public class UdfpsControllerTest extends SysuiTestCase {
|
||||
|
||||
// Dependencies
|
||||
@Mock
|
||||
private Resources mResources;
|
||||
@Mock
|
||||
private LayoutInflater mLayoutInflater;
|
||||
@Mock
|
||||
private FingerprintManager mFingerprintManager;
|
||||
@@ -110,6 +108,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
|
||||
private FalsingManager mFalsingManager;
|
||||
@Mock
|
||||
private PowerManager mPowerManager;
|
||||
@Mock
|
||||
private AccessibilityManager mAccessibilityManager;
|
||||
|
||||
private FakeExecutor mFgExecutor;
|
||||
|
||||
@@ -151,7 +151,6 @@ public class UdfpsControllerTest extends SysuiTestCase {
|
||||
mFgExecutor = new FakeExecutor(new FakeSystemClock());
|
||||
mUdfpsController = new UdfpsController(
|
||||
mContext,
|
||||
mResources,
|
||||
mLayoutInflater,
|
||||
mFingerprintManager,
|
||||
mWindowManager,
|
||||
@@ -163,7 +162,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
|
||||
mKeyguardUpdateMonitor,
|
||||
mKeyguardViewMediator,
|
||||
mFalsingManager,
|
||||
mPowerManager);
|
||||
mPowerManager,
|
||||
mAccessibilityManager);
|
||||
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
|
||||
mOverlayController = mOverlayCaptor.getValue();
|
||||
|
||||
@@ -174,16 +174,9 @@ public class UdfpsControllerTest extends SysuiTestCase {
|
||||
when(mBrightnessValues.length()).thenReturn(2);
|
||||
when(mBrightnessValues.getFloat(0, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(1f);
|
||||
when(mBrightnessValues.getFloat(1, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(2f);
|
||||
when(mResources.obtainTypedArray(com.android.internal.R.array.config_screenBrightnessNits))
|
||||
.thenReturn(mBrightnessValues);
|
||||
when(mBrightnessBacklight.length()).thenReturn(2);
|
||||
when(mBrightnessBacklight.getFloat(0, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(1f);
|
||||
when(mBrightnessBacklight.getFloat(1, PowerManager.BRIGHTNESS_OFF_FLOAT)).thenReturn(2f);
|
||||
when(mResources.obtainTypedArray(
|
||||
com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
|
||||
.thenReturn(mBrightnessBacklight);
|
||||
when(mResources.getIntArray(com.android.internal.R.array.config_screenBrightnessBacklight))
|
||||
.thenReturn(new int[]{1, 2});
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user