From 0ac981822da297e3eece69869c3d9e7f25c06141 Mon Sep 17 00:00:00 2001 From: Juan Sebastian Martinez Date: Tue, 1 Aug 2023 10:21:34 -0700 Subject: [PATCH] Using performHapticFeedback on UdfpsController Moving towards new haptics API that triggers haptics directly from the UI thread. The UdfpsController can use the new performHapticFeedback method as long as there is an overlay view available. The haptics for when a11y touch exploration is enabled were changed to the CONTEXT_CLICK constant. Consistent with the intended behavior, haptics for the case of an AOD interruption are set to the LONG_PRESS constant. The changes are controlled by a feature flag. Test: Unit tests were added to the UdfpsControllerTest class to verify vibrations when the feature flag is enabled or disabled. The cases verified correspond to AOD interruptions and a11y when touch exploration is enabled. Bug: 245528624 Change-Id: I413b38a7ce0acf8b4c63ba00e4e51173e0cdb805 --- .../systemui/biometrics/UdfpsController.java | 52 ++++++--- .../biometrics/UdfpsControllerTest.java | 102 ++++++++++++++++++ 2 files changed, 142 insertions(+), 12 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index ebff0b05a52d1..39a45f7f60a29 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -26,6 +26,7 @@ import static android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROL import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION; +import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import android.content.BroadcastReceiver; import android.content.Context; @@ -50,6 +51,7 @@ import android.os.Trace; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.util.Log; +import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -234,6 +236,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { public static final VibrationEffect EFFECT_CLICK = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); + public static final int LONG_PRESS = HapticFeedbackConstants.LONG_PRESS; + private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() { @Override public void onScreenTurnedOn() { @@ -926,12 +930,24 @@ public class UdfpsController implements DozeReceiver, Dumpable { @VisibleForTesting public void playStartHaptic() { if (mAccessibilityManager.isTouchExplorationEnabled()) { - mVibrator.vibrate( - Process.myUid(), - mContext.getOpPackageName(), - EFFECT_CLICK, - "udfps-onStart-click", - UDFPS_VIBRATION_ATTRIBUTES); + if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + if (mOverlay != null && mOverlay.getOverlayView() != null) { + mVibrator.performHapticFeedback( + mOverlay.getOverlayView(), + HapticFeedbackConstants.CONTEXT_CLICK + ); + } else { + Log.e(TAG, "No haptics played. Could not obtain overlay view to perform" + + "vibration. Either the controller overlay is null or has no view"); + } + } else { + mVibrator.vibrate( + Process.myUid(), + mContext.getOpPackageName(), + EFFECT_CLICK, + "udfps-onStart-click", + UDFPS_VIBRATION_ATTRIBUTES); + } } } @@ -1024,12 +1040,24 @@ public class UdfpsController implements DozeReceiver, Dumpable { mKeyguardViewManager.showPrimaryBouncer(true); // play the same haptic as the LockIconViewController longpress - mVibrator.vibrate( - Process.myUid(), - mContext.getOpPackageName(), - UdfpsController.EFFECT_CLICK, - "aod-lock-icon-longpress", - LOCK_ICON_VIBRATION_ATTRIBUTES); + if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) { + if (mOverlay != null && mOverlay.getOverlayView() != null) { + mVibrator.performHapticFeedback( + mOverlay.getOverlayView(), + UdfpsController.LONG_PRESS + ); + } else { + Log.e(TAG, "No haptics played. Could not obtain overlay view to perform" + + "vibration. Either the controller overlay is null or has no view"); + } + } else { + mVibrator.vibrate( + Process.myUid(), + mContext.getOpPackageName(), + UdfpsController.EFFECT_CLICK, + "aod-lock-icon-longpress", + LOCK_ICON_VIBRATION_ATTRIBUTES); + } return; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 58982d13481d0..7ab8e8b229a38 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -23,6 +23,7 @@ import static android.view.MotionEvent.ACTION_UP; import static com.android.internal.util.FunctionalUtils.ThrowingConsumer; import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION; +import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -61,6 +62,7 @@ import android.os.RemoteException; import android.os.VibrationAttributes; import android.testing.TestableLooper.RunWithLooper; import android.util.Pair; +import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; @@ -1127,6 +1129,36 @@ public class UdfpsControllerTest extends SysuiTestCase { UdfpsController.UDFPS_VIBRATION_ATTRIBUTES.getUsage()); } + @Test + public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled_oneWayHapticsEnabled() + throws RemoteException { + when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true); + // Configure UdfpsView to accept the ACTION_DOWN event + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN that the overlay is showing and a11y touch exploration enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + // WHEN ACTION_HOVER is received + verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture()); + MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0); + mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent); + enterEvent.recycle(); + MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0); + mHoverListenerCaptor.getValue().onHover(mUdfpsView, moveEvent); + moveEvent.recycle(); + + // THEN context click haptic is played + verify(mVibrator).performHapticFeedback( + any(), + eq(HapticFeedbackConstants.CONTEXT_CLICK) + ); + } + @Test public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled() throws RemoteException { // Configure UdfpsView to accept the ACTION_DOWN event @@ -1159,6 +1191,35 @@ public class UdfpsControllerTest extends SysuiTestCase { any()); } + @Test + public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled__oneWayHapticsEnabled() + throws RemoteException { + when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true); + // Configure UdfpsView to accept the ACTION_DOWN event + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN that the overlay is showing and a11y touch exploration NOT enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + // WHEN ACTION_DOWN is received + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricExecutor.runAllReady(); + downEvent.recycle(); + MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); + mBiometricExecutor.runAllReady(); + moveEvent.recycle(); + + // THEN NO haptic played + verify(mVibrator, never()).performHapticFeedback(any(), anyInt()); + } + @Test public void onTouch_withoutNewTouchDetection_shouldCallOldFingerprintManagerPath() throws RemoteException { @@ -1514,4 +1575,45 @@ public class UdfpsControllerTest extends SysuiTestCase { // THEN is fingerDown should be FALSE assertFalse(mUdfpsController.isFingerDown()); } + + @Test + public void playHaptic_onAodInterrupt_oneWayHapticsDisabled_onAcquiredBad_usesVibrate() + throws RemoteException { + // GIVEN UDFPS overlay is showing + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + // GIVEN there's been an AoD interrupt + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false); + mScreenObserver.onScreenTurnedOn(); + mUdfpsController.onAodInterrupt(0, 0, 0, 0); + + // THEN vibrate is used + verify(mVibrator).vibrate( + anyInt(), + anyString(), + eq(UdfpsController.EFFECT_CLICK), + eq("aod-lock-icon-longpress"), + eq(UdfpsController.LOCK_ICON_VIBRATION_ATTRIBUTES) + ); + } + + @Test + public void playHaptic_onAodInterrupt_oneWayHapticsEnabled_onAcquiredBad_performHapticFeedback() + throws RemoteException { + when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true); + // GIVEN UDFPS overlay is showing + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + // GIVEN there's been an AoD interrupt + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false); + mScreenObserver.onScreenTurnedOn(); + mUdfpsController.onAodInterrupt(0, 0, 0, 0); + + // THEN vibrate is used + verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS)); + } }