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:
Beverly
2021-04-26 18:13:13 -04:00
parent d2c28835dd
commit f6346f4513
5 changed files with 122 additions and 23 deletions

View File

@@ -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"

View File

@@ -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>

View File

@@ -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();
}

View File

@@ -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);
}
}
}

View File

@@ -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