Merge "[Test Week] Add VibratorWrapperTest" into main

This commit is contained in:
Fengjiang Li
2024-07-19 23:28:13 +00:00
committed by Android (Google) Code Review
2 changed files with 225 additions and 22 deletions

View File

@@ -31,6 +31,7 @@ import android.os.Vibrator;
import android.provider.Settings;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.Utilities;
@@ -49,14 +50,14 @@ public class VibratorWrapper implements SafeCloseable {
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 class VibratorWrapper implements SafeCloseable {
*/
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 @@ public class VibratorWrapper implements SafeCloseable {
// 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 @@ public class VibratorWrapper implements SafeCloseable {
@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 @@ public class VibratorWrapper implements SafeCloseable {
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();
}
}

View File

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