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
This commit is contained in:
Juan Sebastian Martinez
2023-08-01 10:21:34 -07:00
parent f966e40a2e
commit 0ac981822d
2 changed files with 142 additions and 12 deletions

View File

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

View File

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