[Test Week] Add VibratorWrapperTest

Number of new tested methods: 9

Bug: 353303621
Test: This is the unit test
Flag: NONE - unit test
Change-Id: Id6dde920d6365823cf859e97c7c0cfbb25e7270d
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index 51749a7..6bae1ba 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -31,6 +31,7 @@
 import android.provider.Settings;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.Utilities;
 
@@ -49,14 +50,14 @@
 
     public static final VibrationEffect EFFECT_CLICK =
             createPredefined(VibrationEffect.EFFECT_CLICK);
-    private static final Uri HAPTIC_FEEDBACK_URI =
-            Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED);
+    @VisibleForTesting
+    static final Uri HAPTIC_FEEDBACK_URI = Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED);
 
-    private static final float LOW_TICK_SCALE = 0.9f;
-    private static final float DRAG_TEXTURE_SCALE = 0.03f;
-    private static final float DRAG_COMMIT_SCALE = 0.5f;
-    private static final float DRAG_BUMP_SCALE = 0.4f;
-    private static final int DRAG_TEXTURE_EFFECT_SIZE = 200;
+    @VisibleForTesting static final float LOW_TICK_SCALE = 0.9f;
+    @VisibleForTesting static final float DRAG_TEXTURE_SCALE = 0.03f;
+    @VisibleForTesting static final float DRAG_COMMIT_SCALE = 0.5f;
+    @VisibleForTesting static final float DRAG_BUMP_SCALE = 0.4f;
+    @VisibleForTesting static final int DRAG_TEXTURE_EFFECT_SIZE = 200;
 
     @Nullable
     private final VibrationEffect mDragEffect;
@@ -73,22 +74,29 @@
      */
     public static final VibrationEffect OVERVIEW_HAPTIC = EFFECT_CLICK;
 
-    private final Context mContext;
     private final Vibrator mVibrator;
     private final boolean mHasVibrator;
-    private final SettingsCache.OnChangeListener mHapticChangeListener =
+
+    private final SettingsCache mSettingsCache;
+
+    @VisibleForTesting
+    final SettingsCache.OnChangeListener mHapticChangeListener =
             isEnabled -> mIsHapticFeedbackEnabled = isEnabled;
 
     private boolean mIsHapticFeedbackEnabled;
 
     private VibratorWrapper(Context context) {
-        mContext = context;
-        mVibrator = context.getSystemService(Vibrator.class);
+        this(context.getSystemService(Vibrator.class), SettingsCache.INSTANCE.get(context));
+    }
+
+    @VisibleForTesting
+    VibratorWrapper(Vibrator vibrator, SettingsCache settingsCache) {
+        mVibrator = vibrator;
         mHasVibrator = mVibrator.hasVibrator();
+        mSettingsCache = settingsCache;
         if (mHasVibrator) {
-            SettingsCache cache = SettingsCache.INSTANCE.get(mContext);
-            cache.register(HAPTIC_FEEDBACK_URI, mHapticChangeListener);
-            mIsHapticFeedbackEnabled = cache.getValue(HAPTIC_FEEDBACK_URI, 0);
+            mSettingsCache.register(HAPTIC_FEEDBACK_URI, mHapticChangeListener);
+            mIsHapticFeedbackEnabled = mSettingsCache.getValue(HAPTIC_FEEDBACK_URI, 0);
         } else {
             mIsHapticFeedbackEnabled = false;
         }
@@ -98,12 +106,7 @@
 
             // Drag texture, Commit, and Bump should only be used for premium phones.
             // Before using these haptics make sure check if the device can use it
-            VibrationEffect.Composition dragEffect = VibrationEffect.startComposition();
-            for (int i = 0; i < DRAG_TEXTURE_EFFECT_SIZE; i++) {
-                dragEffect.addPrimitive(
-                        PRIMITIVE_LOW_TICK, DRAG_TEXTURE_SCALE);
-            }
-            mDragEffect = dragEffect.compose();
+            mDragEffect = getDragEffect();
             mCommitEffect = VibrationEffect.startComposition().addPrimitive(
                     VibrationEffect.Composition.PRIMITIVE_TICK, DRAG_COMMIT_SCALE).compose();
             mBumpEffect = VibrationEffect.startComposition().addPrimitive(
@@ -124,8 +127,7 @@
     @Override
     public void close() {
         if (mHasVibrator) {
-            SettingsCache.INSTANCE.get(mContext)
-                    .unregister(HAPTIC_FEEDBACK_URI, mHapticChangeListener);
+            mSettingsCache.unregister(HAPTIC_FEEDBACK_URI, mHapticChangeListener);
         }
     }
 
@@ -215,4 +217,13 @@
             vibrate(primitiveLowTickEffect);
         }
     }
+
+    static VibrationEffect getDragEffect() {
+        VibrationEffect.Composition dragEffect = VibrationEffect.startComposition();
+        for (int i = 0; i < DRAG_TEXTURE_EFFECT_SIZE; i++) {
+            dragEffect.addPrimitive(
+                    PRIMITIVE_LOW_TICK, DRAG_TEXTURE_SCALE);
+        }
+        return dragEffect.compose();
+    }
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/VibratorWrapperTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/VibratorWrapperTest.kt
new file mode 100644
index 0000000..330c394
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/VibratorWrapperTest.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.media.AudioAttributes
+import android.os.SystemClock
+import android.os.VibrationEffect
+import android.os.VibrationEffect.Composition.PRIMITIVE_LOW_TICK
+import android.os.VibrationEffect.Composition.PRIMITIVE_TICK
+import android.os.Vibrator
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.util.VibratorWrapper.HAPTIC_FEEDBACK_URI
+import com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC
+import com.android.launcher3.util.VibratorWrapper.VIBRATION_ATTRS
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.never
+import org.mockito.kotlin.same
+import org.mockito.kotlin.verifyNoMoreInteractions
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class VibratorWrapperTest {
+
+    @Mock private lateinit var settingsCache: SettingsCache
+    @Mock private lateinit var vibrator: Vibrator
+    @Captor private lateinit var vibrationEffectCaptor: ArgumentCaptor<VibrationEffect>
+
+    private lateinit var underTest: VibratorWrapper
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        `when`(settingsCache.getValue(HAPTIC_FEEDBACK_URI, 0)).thenReturn(true)
+        `when`(vibrator.hasVibrator()).thenReturn(true)
+        `when`(vibrator.areAllPrimitivesSupported(PRIMITIVE_TICK)).thenReturn(true)
+        `when`(vibrator.areAllPrimitivesSupported(PRIMITIVE_LOW_TICK)).thenReturn(true)
+        `when`(vibrator.getPrimitiveDurations(PRIMITIVE_LOW_TICK)).thenReturn(intArrayOf(10))
+
+        underTest = VibratorWrapper(vibrator, settingsCache)
+    }
+
+    @Test
+    fun init_register_onChangeListener() {
+        verify(settingsCache).register(HAPTIC_FEEDBACK_URI, underTest.mHapticChangeListener)
+    }
+
+    @Test
+    fun close_unregister_onChangeListener() {
+        underTest.close()
+
+        verify(settingsCache).unregister(HAPTIC_FEEDBACK_URI, underTest.mHapticChangeListener)
+    }
+
+    @Test
+    fun vibrate() {
+        underTest.vibrate(OVERVIEW_HAPTIC)
+
+        awaitTasksCompleted()
+        verify(vibrator).vibrate(OVERVIEW_HAPTIC, VIBRATION_ATTRS)
+    }
+
+    @Test
+    fun vibrate_primitive_id() {
+        underTest.vibrate(PRIMITIVE_TICK, 1f, OVERVIEW_HAPTIC)
+
+        awaitTasksCompleted()
+        verify(vibrator).vibrate(vibrationEffectCaptor.capture(), same(VIBRATION_ATTRS))
+        val expectedEffect =
+            VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK, 1f).compose()
+        assertThat(vibrationEffectCaptor.value).isEqualTo(expectedEffect)
+    }
+
+    @Test
+    fun vibrate_with_invalid_primitive_id_use_fallback_effect() {
+        underTest.vibrate(-1, 1f, OVERVIEW_HAPTIC)
+
+        awaitTasksCompleted()
+        verify(vibrator).vibrate(OVERVIEW_HAPTIC, VIBRATION_ATTRS)
+    }
+
+    @Test
+    fun vibrate_for_taskbar_unstash() {
+        underTest.vibrateForTaskbarUnstash()
+
+        awaitTasksCompleted()
+        verify(vibrator).vibrate(vibrationEffectCaptor.capture(), same(VIBRATION_ATTRS))
+        val expectedEffect =
+            VibrationEffect.startComposition()
+                .addPrimitive(PRIMITIVE_LOW_TICK, VibratorWrapper.LOW_TICK_SCALE)
+                .compose()
+        assertThat(vibrationEffectCaptor.value).isEqualTo(expectedEffect)
+    }
+
+    @Test
+    fun vibrate_for_drag_bump() {
+        underTest.vibrateForDragBump()
+
+        awaitTasksCompleted()
+        verify(vibrator).vibrate(vibrationEffectCaptor.capture(), same(VIBRATION_ATTRS))
+        val expectedEffect =
+            VibrationEffect.startComposition()
+                .addPrimitive(PRIMITIVE_LOW_TICK, VibratorWrapper.DRAG_BUMP_SCALE)
+                .compose()
+        assertThat(vibrationEffectCaptor.value).isEqualTo(expectedEffect)
+    }
+
+    @Test
+    fun vibrate_for_drag_commit() {
+        underTest.vibrateForDragCommit()
+
+        awaitTasksCompleted()
+        verify(vibrator).vibrate(vibrationEffectCaptor.capture(), same(VIBRATION_ATTRS))
+        val expectedEffect =
+            VibrationEffect.startComposition()
+                .addPrimitive(PRIMITIVE_TICK, VibratorWrapper.DRAG_COMMIT_SCALE)
+                .compose()
+        assertThat(vibrationEffectCaptor.value).isEqualTo(expectedEffect)
+    }
+
+    @Test
+    fun vibrate_for_drag_texture() {
+        SystemClock.setCurrentTimeMillis(40000)
+
+        underTest.vibrateForDragTexture()
+
+        awaitTasksCompleted()
+        verify(vibrator).vibrate(vibrationEffectCaptor.capture(), same(VIBRATION_ATTRS))
+        assertThat(vibrationEffectCaptor.value).isEqualTo(VibratorWrapper.getDragEffect())
+    }
+
+    @Test
+    fun vibrate_for_drag_texture_within_time_window_noOp() {
+        SystemClock.setCurrentTimeMillis(40000)
+        underTest.vibrateForDragTexture()
+        awaitTasksCompleted()
+        reset(vibrator)
+
+        underTest.vibrateForDragTexture()
+
+        verifyNoMoreInteractions(vibrator)
+    }
+
+    @Test
+    fun haptic_feedback_disabled_no_vibrate() {
+        `when`(vibrator.hasVibrator()).thenReturn(false)
+        underTest = VibratorWrapper(vibrator, settingsCache)
+
+        underTest.vibrate(OVERVIEW_HAPTIC)
+
+        awaitTasksCompleted()
+        verify(vibrator, never())
+            .vibrate(any(VibrationEffect::class.java), any(AudioAttributes::class.java))
+    }
+
+    @Test
+    fun cancel_vibrate() {
+        underTest.cancelVibrate()
+
+        awaitTasksCompleted()
+        verify(vibrator).cancel()
+    }
+
+    private fun awaitTasksCompleted() {
+        Executors.UI_HELPER_EXECUTOR.submit<Any> { null }.get()
+    }
+}