Enable start haptic for udfps by default

When the user's touch first enters the udfps area, play the
keyboard pressed (click) haptic feedback.

Test: manual, atest UdfpsControllerTest
Fixes: 187535630
Bug: 185124905
Change-Id: I2e5231e74dbbc2617ba9f0f04127827fd14078e9
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 5f27400..5a50f0e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -105,7 +105,7 @@
     @NonNull private final DumpManager mDumpManager;
     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @NonNull private final KeyguardViewMediator mKeyguardViewMediator;
-    @NonNull private final Vibrator mVibrator;
+    @Nullable private final Vibrator mVibrator;
     @NonNull private final Handler mMainHandler;
     @NonNull private final FalsingManager mFalsingManager;
     @NonNull private final PowerManager mPowerManager;
@@ -135,7 +135,8 @@
     private boolean mScreenOn;
     private Runnable mAodInterruptRunnable;
 
-    private static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES =
+    @VisibleForTesting
+    static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES =
             new AudioAttributes.Builder()
                     .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                     .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
@@ -144,7 +145,8 @@
     private final VibrationEffect mEffectTick = VibrationEffect.get(VibrationEffect.EFFECT_TICK);
     private final VibrationEffect mEffectTextureTick =
             VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK);
-    private final VibrationEffect mEffectClick = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+    @VisibleForTesting
+    final VibrationEffect mEffectClick = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
     private final VibrationEffect mEffectHeavy =
             VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
     private final VibrationEffect mDoubleClick =
@@ -152,6 +154,9 @@
     private final Runnable mAcquiredVibration = new Runnable() {
         @Override
         public void run() {
+            if (mVibrator == null) {
+                return;
+            }
             String effect = Settings.Global.getString(mContext.getContentResolver(),
                     "udfps_acquired_type");
             mVibrator.vibrate(getVibration(effect, mEffectTick), VIBRATION_SONIFICATION_ATTRIBUTES);
@@ -389,24 +394,28 @@
                                     PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
 
                             // TODO: this should eventually be removed after ux testing
-                            final ContentResolver contentResolver = mContext.getContentResolver();
-                            int startEnabled = Settings.Global.getInt(contentResolver,
-                                    "udfps_start", 0);
-                            if (startEnabled > 0) {
-                                String startEffectSetting = Settings.Global.getString(
-                                        contentResolver, "udfps_start_type");
-                                mVibrator.vibrate(getVibration(startEffectSetting, mEffectClick),
-                                        VIBRATION_SONIFICATION_ATTRIBUTES);
+                            if (mVibrator != null) {
+                                final ContentResolver contentResolver =
+                                        mContext.getContentResolver();
+                                int startEnabled = Settings.Global.getInt(contentResolver,
+                                        "udfps_start", 1);
+                                if (startEnabled > 0) {
+                                    String startEffectSetting = Settings.Global.getString(
+                                            contentResolver, "udfps_start_type");
+                                    mVibrator.vibrate(getVibration(startEffectSetting,
+                                            mEffectClick), VIBRATION_SONIFICATION_ATTRIBUTES);
+                                }
+
+                                int acquiredEnabled = Settings.Global.getInt(contentResolver,
+                                        "udfps_acquired", 0);
+                                if (acquiredEnabled > 0) {
+                                    int delay = Settings.Global.getInt(contentResolver,
+                                            "udfps_acquired_delay", 500);
+                                    mMainHandler.removeCallbacks(mAcquiredVibration);
+                                    mMainHandler.postDelayed(mAcquiredVibration, delay);
+                                }
                             }
 
-                            int acquiredEnabled = Settings.Global.getInt(contentResolver,
-                                    "udfps_acquired", 0);
-                            if (acquiredEnabled > 0) {
-                                int delay = Settings.Global.getInt(contentResolver,
-                                        "udfps_acquired_delay", 500);
-                                mMainHandler.removeCallbacks(mAcquiredVibration);
-                                mMainHandler.postDelayed(mAcquiredVibration, delay);
-                            }
                             handled = true;
                         } else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) {
                             Log.v(TAG, "onTouch | finger move: " + touchInfo);
@@ -456,11 +465,12 @@
             @NonNull FalsingManager falsingManager,
             @NonNull PowerManager powerManager,
             @NonNull AccessibilityManager accessibilityManager,
-            @NonNull ScreenLifecycle screenLifecycle) {
+            @NonNull ScreenLifecycle screenLifecycle,
+            @Nullable Vibrator vibrator) {
         mContext = context;
         // TODO (b/185124905): inject main handler and vibrator once done prototyping
         mMainHandler = new Handler(Looper.getMainLooper());
-        mVibrator = context.getSystemService(Vibrator.class);
+        mVibrator = vibrator;
         mInflater = inflater;
         // The fingerprint manager is queried for UDFPS before this class is constructed, so the
         // fingerprint manager should never be null.
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 875696a..2530cfd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -35,6 +35,7 @@
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
 import android.os.PowerManager;
 import android.os.RemoteException;
+import android.os.Vibrator;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.LayoutInflater;
@@ -114,6 +115,8 @@
     private AccessibilityManager mAccessibilityManager;
     @Mock
     private ScreenLifecycle mScreenLifecycle;
+    @Mock
+    private Vibrator mVibrator;
 
     private FakeExecutor mFgExecutor;
 
@@ -170,7 +173,8 @@
                 mFalsingManager,
                 mPowerManager,
                 mAccessibilityManager,
-                mScreenLifecycle);
+                mScreenLifecycle,
+                mVibrator);
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -302,4 +306,29 @@
         // THEN no illumination because screen is off
         verify(mUdfpsView, never()).startIllumination(any());
     }
+
+    @Test
+    public void playHapticOnTouchUdfpsArea() throws RemoteException {
+        // Configure UdfpsView to accept the ACTION_DOWN event
+        when(mUdfpsView.isIlluminationRequested()).thenReturn(false);
+        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+        // GIVEN that the overlay is showing
+        mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
+                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        // WHEN ACTION_DOWN is received
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        MotionEvent downEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+        downEvent.recycle();
+        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+        moveEvent.recycle();
+
+        // THEN click haptic is played
+        verify(mVibrator).vibrate(mUdfpsController.mEffectClick,
+                UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES);
+    }
 }