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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user