Merge "Use custom haptic vibrations in HapticFeedbackVibrationProvider" into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
0903f20e25
@@ -21,9 +21,16 @@ import android.content.res.Resources;
|
||||
import android.os.VibrationAttributes;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Provides the {@link VibrationEffect} and {@link VibrationAttributes} for haptic feedback.
|
||||
@@ -31,6 +38,8 @@ import java.io.PrintWriter;
|
||||
* @hide
|
||||
*/
|
||||
public final class HapticFeedbackVibrationProvider {
|
||||
private static final String TAG = "HapticFeedbackVibrationProvider";
|
||||
|
||||
private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES =
|
||||
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH);
|
||||
private static final VibrationAttributes PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES =
|
||||
@@ -42,15 +51,41 @@ public final class HapticFeedbackVibrationProvider {
|
||||
private final boolean mHapticTextHandleEnabled;
|
||||
// Vibrator effect for haptic feedback during boot when safe mode is enabled.
|
||||
private final VibrationEffect mSafeModeEnabledVibrationEffect;
|
||||
// Haptic feedback vibration customizations specific to the device.
|
||||
// If present and valid, a vibration here will be used for an effect.
|
||||
// Otherwise, the system's default vibration will be used.
|
||||
@Nullable private final SparseArray<VibrationEffect> mHapticCustomizations;
|
||||
|
||||
/** @hide */
|
||||
public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) {
|
||||
this(res, vibrator, loadHapticCustomizations(res));
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@VisibleForTesting HapticFeedbackVibrationProvider(
|
||||
Resources res,
|
||||
Vibrator vibrator,
|
||||
@Nullable SparseArray<VibrationEffect> hapticCustomizations) {
|
||||
mVibrator = vibrator;
|
||||
mHapticTextHandleEnabled = res.getBoolean(
|
||||
com.android.internal.R.bool.config_enableHapticTextHandle);
|
||||
|
||||
if (hapticCustomizations != null) {
|
||||
// Clean up the customizations to remove vibrations which may not ever be used due to
|
||||
// Vibrator properties or other device configurations.
|
||||
removeUnsupportedVibrations(hapticCustomizations, vibrator);
|
||||
if (hapticCustomizations.size() == 0) {
|
||||
hapticCustomizations = null;
|
||||
}
|
||||
}
|
||||
mHapticCustomizations = hapticCustomizations;
|
||||
|
||||
mSafeModeEnabledVibrationEffect =
|
||||
VibrationSettings.createEffectFromResource(
|
||||
res, com.android.internal.R.array.config_safeModeEnabledVibePattern);
|
||||
effectHasCustomization(HapticFeedbackConstants.SAFE_MODE_ENABLED)
|
||||
? mHapticCustomizations.get(HapticFeedbackConstants.SAFE_MODE_ENABLED)
|
||||
: VibrationSettings.createEffectFromResource(
|
||||
res,
|
||||
com.android.internal.R.array.config_safeModeEnabledVibePattern);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,7 +103,7 @@ public final class HapticFeedbackVibrationProvider {
|
||||
case HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE:
|
||||
case HapticFeedbackConstants.SCROLL_TICK:
|
||||
case HapticFeedbackConstants.SEGMENT_TICK:
|
||||
return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
|
||||
return getVibration(effectId, VibrationEffect.EFFECT_TICK);
|
||||
|
||||
case HapticFeedbackConstants.TEXT_HANDLE_MOVE:
|
||||
if (!mHapticTextHandleEnabled) {
|
||||
@@ -77,13 +112,16 @@ public final class HapticFeedbackVibrationProvider {
|
||||
// fallthrough
|
||||
case HapticFeedbackConstants.CLOCK_TICK:
|
||||
case HapticFeedbackConstants.SEGMENT_FREQUENT_TICK:
|
||||
return VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK);
|
||||
return getVibration(effectId, VibrationEffect.EFFECT_TEXTURE_TICK);
|
||||
|
||||
case HapticFeedbackConstants.KEYBOARD_RELEASE:
|
||||
case HapticFeedbackConstants.VIRTUAL_KEY_RELEASE:
|
||||
case HapticFeedbackConstants.ENTRY_BUMP:
|
||||
case HapticFeedbackConstants.DRAG_CROSSING:
|
||||
return VibrationEffect.get(VibrationEffect.EFFECT_TICK, false);
|
||||
return getVibration(
|
||||
effectId,
|
||||
VibrationEffect.EFFECT_TICK,
|
||||
/* fallbackForPredefinedEffect= */ false);
|
||||
|
||||
case HapticFeedbackConstants.KEYBOARD_TAP: // == KEYBOARD_PRESS
|
||||
case HapticFeedbackConstants.VIRTUAL_KEY:
|
||||
@@ -93,46 +131,42 @@ public final class HapticFeedbackVibrationProvider {
|
||||
case HapticFeedbackConstants.GESTURE_START:
|
||||
case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
|
||||
case HapticFeedbackConstants.SCROLL_LIMIT:
|
||||
return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
|
||||
return getVibration(effectId, VibrationEffect.EFFECT_CLICK);
|
||||
|
||||
case HapticFeedbackConstants.LONG_PRESS:
|
||||
case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
|
||||
case HapticFeedbackConstants.DRAG_START:
|
||||
case HapticFeedbackConstants.EDGE_SQUEEZE:
|
||||
return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
|
||||
return getVibration(effectId, VibrationEffect.EFFECT_HEAVY_CLICK);
|
||||
|
||||
case HapticFeedbackConstants.REJECT:
|
||||
return VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK);
|
||||
return getVibration(effectId, VibrationEffect.EFFECT_DOUBLE_CLICK);
|
||||
|
||||
case HapticFeedbackConstants.SAFE_MODE_ENABLED:
|
||||
return mSafeModeEnabledVibrationEffect;
|
||||
|
||||
case HapticFeedbackConstants.ASSISTANT_BUTTON:
|
||||
if (mVibrator.areAllPrimitivesSupported(
|
||||
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
|
||||
VibrationEffect.Composition.PRIMITIVE_TICK)) {
|
||||
// quiet ramp, short pause, then sharp tick
|
||||
return VibrationEffect.startComposition()
|
||||
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f)
|
||||
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f, 50)
|
||||
.compose();
|
||||
}
|
||||
// fallback for devices without composition support
|
||||
return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
|
||||
return getAssistantButtonVibration();
|
||||
|
||||
case HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE:
|
||||
return getScaledPrimitiveOrElseEffect(
|
||||
VibrationEffect.Composition.PRIMITIVE_TICK, 0.4f,
|
||||
return getVibration(
|
||||
effectId,
|
||||
VibrationEffect.Composition.PRIMITIVE_TICK,
|
||||
/* primitiveScale= */ 0.4f,
|
||||
VibrationEffect.EFFECT_TEXTURE_TICK);
|
||||
|
||||
case HapticFeedbackConstants.TOGGLE_ON:
|
||||
return getScaledPrimitiveOrElseEffect(
|
||||
VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f,
|
||||
return getVibration(
|
||||
effectId,
|
||||
VibrationEffect.Composition.PRIMITIVE_TICK,
|
||||
/* primitiveScale= */ 0.5f,
|
||||
VibrationEffect.EFFECT_TICK);
|
||||
|
||||
case HapticFeedbackConstants.TOGGLE_OFF:
|
||||
return getScaledPrimitiveOrElseEffect(
|
||||
VibrationEffect.Composition.PRIMITIVE_LOW_TICK, 0.2f,
|
||||
return getVibration(
|
||||
effectId,
|
||||
VibrationEffect.Composition.PRIMITIVE_LOW_TICK,
|
||||
/* primitiveScale= */ 0.2f,
|
||||
VibrationEffect.EFFECT_TEXTURE_TICK);
|
||||
|
||||
case HapticFeedbackConstants.NO_HAPTICS:
|
||||
@@ -181,14 +215,100 @@ public final class HapticFeedbackVibrationProvider {
|
||||
pw.print("mHapticTextHandleEnabled="); pw.println(mHapticTextHandleEnabled);
|
||||
}
|
||||
|
||||
private VibrationEffect getScaledPrimitiveOrElseEffect(
|
||||
int primitiveId, float scale, int elseEffectId) {
|
||||
private VibrationEffect getVibration(int effectId, int predefinedVibrationEffectId) {
|
||||
return getVibration(
|
||||
effectId, predefinedVibrationEffectId, /* fallbackForPredefinedEffect= */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the customized vibration for {@code hapticFeedbackId}, or
|
||||
* {@code predefinedVibrationEffectId} if a customization does not exist for the haptic
|
||||
* feedback.
|
||||
*
|
||||
* <p>If a customization does not exist and the default predefined effect is to be returned,
|
||||
* {@code fallbackForPredefinedEffect} will be used to decide whether or not to fallback
|
||||
* to a generic pattern if the predefined effect is not hardware supported.
|
||||
*
|
||||
* @see VibrationEffect#get(int, boolean)
|
||||
*/
|
||||
private VibrationEffect getVibration(
|
||||
int hapticFeedbackId,
|
||||
int predefinedVibrationEffectId,
|
||||
boolean fallbackForPredefinedEffect) {
|
||||
if (effectHasCustomization(hapticFeedbackId)) {
|
||||
return mHapticCustomizations.get(hapticFeedbackId);
|
||||
}
|
||||
return VibrationEffect.get(predefinedVibrationEffectId, fallbackForPredefinedEffect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the customized vibration for {@code hapticFeedbackId}, or some fallback vibration if
|
||||
* a customization does not exist for the ID.
|
||||
*
|
||||
* <p>The fallback will be a primitive composition formed of {@code primitiveId} and
|
||||
* {@code primitiveScale}, if the primitive is supported. Otherwise, it will be a predefined
|
||||
* vibration of {@code elsePredefinedVibrationEffectId}.
|
||||
*/
|
||||
private VibrationEffect getVibration(
|
||||
int hapticFeedbackId,
|
||||
int primitiveId,
|
||||
float primitiveScale,
|
||||
int elsePredefinedVibrationEffectId) {
|
||||
if (effectHasCustomization(hapticFeedbackId)) {
|
||||
return mHapticCustomizations.get(hapticFeedbackId);
|
||||
}
|
||||
if (mVibrator.areAllPrimitivesSupported(primitiveId)) {
|
||||
return VibrationEffect.startComposition()
|
||||
.addPrimitive(primitiveId, scale)
|
||||
.addPrimitive(primitiveId, primitiveScale)
|
||||
.compose();
|
||||
} else {
|
||||
return VibrationEffect.get(elseEffectId);
|
||||
return VibrationEffect.get(elsePredefinedVibrationEffectId);
|
||||
}
|
||||
}
|
||||
|
||||
private VibrationEffect getAssistantButtonVibration() {
|
||||
if (effectHasCustomization(HapticFeedbackConstants.ASSISTANT_BUTTON)) {
|
||||
return mHapticCustomizations.get(HapticFeedbackConstants.ASSISTANT_BUTTON);
|
||||
}
|
||||
if (mVibrator.areAllPrimitivesSupported(
|
||||
VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
|
||||
VibrationEffect.Composition.PRIMITIVE_TICK)) {
|
||||
// quiet ramp, short pause, then sharp tick
|
||||
return VibrationEffect.startComposition()
|
||||
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f)
|
||||
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1f, 50)
|
||||
.compose();
|
||||
}
|
||||
// fallback for devices without composition support
|
||||
return VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
|
||||
}
|
||||
|
||||
private boolean effectHasCustomization(int effectId) {
|
||||
return mHapticCustomizations != null && mHapticCustomizations.contains(effectId);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static SparseArray<VibrationEffect> loadHapticCustomizations(Resources res) {
|
||||
try {
|
||||
return HapticFeedbackCustomization.loadVibrations(res);
|
||||
} catch (IOException | HapticFeedbackCustomization.CustomizationParserException e) {
|
||||
Slog.e(TAG, "Unable to load haptic customizations.", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeUnsupportedVibrations(
|
||||
SparseArray<VibrationEffect> customizations, Vibrator vibrator) {
|
||||
Set<Integer> keysToRemove = new HashSet<>();
|
||||
for (int i = 0; i < customizations.size(); i++) {
|
||||
int key = customizations.keyAt(i);
|
||||
if (!vibrator.areVibrationFeaturesSupported(customizations.get(key))) {
|
||||
keysToRemove.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (int key : keysToRemove) {
|
||||
customizations.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* Copyright 2023 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.server.vibrator;
|
||||
|
||||
import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
|
||||
import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
|
||||
import static android.os.VibrationEffect.EFFECT_TEXTURE_TICK;
|
||||
import static android.os.VibrationEffect.EFFECT_TICK;
|
||||
import static android.view.HapticFeedbackConstants.CLOCK_TICK;
|
||||
import static android.view.HapticFeedbackConstants.CONTEXT_CLICK;
|
||||
import static android.view.HapticFeedbackConstants.SAFE_MODE_ENABLED;
|
||||
import static android.view.HapticFeedbackConstants.TEXT_HANDLE_MOVE;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.test.FakeVibrator;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import com.android.internal.R;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
public class HapticFeedbackVibrationProviderTest {
|
||||
@Rule public MockitoRule rule = MockitoJUnit.rule();
|
||||
|
||||
private static final VibrationEffect PRIMITIVE_TICK_EFFECT =
|
||||
VibrationEffect.startComposition().addPrimitive(PRIMITIVE_TICK, 0.2497f).compose();
|
||||
private static final VibrationEffect PRIMITIVE_CLICK_EFFECT =
|
||||
VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK, 0.3497f).compose();
|
||||
|
||||
private Context mContext = InstrumentationRegistry.getContext();
|
||||
private FakeVibrator mVibrator = new FakeVibrator(mContext);
|
||||
|
||||
@Mock private Resources mResourcesMock;
|
||||
|
||||
@Test
|
||||
public void testNonExistentCustomization_useDefault() throws Exception {
|
||||
// No customization file is set.
|
||||
HapticFeedbackVibrationProvider hapticProvider =
|
||||
new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator);
|
||||
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
|
||||
.isEqualTo(VibrationEffect.get(EFFECT_TICK));
|
||||
|
||||
// The customization file specifies no customization.
|
||||
setupCustomizationFile("<haptic-feedback-constants></haptic-feedback-constants>");
|
||||
hapticProvider = new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator);
|
||||
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
|
||||
.isEqualTo(VibrationEffect.get(EFFECT_TICK));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceptionParsingCustomizations_useDefault() throws Exception {
|
||||
setupCustomizationFile("<bad-xml></bad-xml>");
|
||||
HapticFeedbackVibrationProvider hapticProvider =
|
||||
new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator);
|
||||
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
|
||||
.isEqualTo(VibrationEffect.get(EFFECT_TICK));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUseValidCustomizedVibration() throws Exception {
|
||||
mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
|
||||
SparseArray<VibrationEffect> customizations = new SparseArray<>();
|
||||
customizations.put(CONTEXT_CLICK, PRIMITIVE_CLICK_EFFECT);
|
||||
|
||||
HapticFeedbackVibrationProvider hapticProvider =
|
||||
new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
|
||||
|
||||
// The override for `CONTEXT_CLICK` is used.
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
|
||||
.isEqualTo(PRIMITIVE_CLICK_EFFECT);
|
||||
// `CLOCK_TICK` has no override, so the default vibration is used.
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(CLOCK_TICK))
|
||||
.isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoNotUseInvalidCustomizedVibration() throws Exception {
|
||||
mockVibratorPrimitiveSupport(new int[] {});
|
||||
SparseArray<VibrationEffect> customizations = new SparseArray<>();
|
||||
customizations.put(CONTEXT_CLICK, PRIMITIVE_CLICK_EFFECT);
|
||||
|
||||
HapticFeedbackVibrationProvider hapticProvider =
|
||||
new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
|
||||
|
||||
// The override for `CONTEXT_CLICK` is not used because the vibration is not supported.
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
|
||||
.isEqualTo(VibrationEffect.get(EFFECT_TICK));
|
||||
// `CLOCK_TICK` has no override, so the default vibration is used.
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(CLOCK_TICK))
|
||||
.isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHapticTextDisabled_noVibrationReturnedForTextHandleMove() throws Exception {
|
||||
mockHapticTextSupport(false);
|
||||
mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
|
||||
SparseArray<VibrationEffect> customizations = new SparseArray<>();
|
||||
customizations.put(TEXT_HANDLE_MOVE, PRIMITIVE_CLICK_EFFECT);
|
||||
|
||||
// Test with a customization available for `TEXT_HANDLE_MOVE`.
|
||||
HapticFeedbackVibrationProvider hapticProvider =
|
||||
new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
|
||||
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
|
||||
|
||||
// Test with no customization available for `TEXT_HANDLE_MOVE`.
|
||||
hapticProvider =
|
||||
new HapticFeedbackVibrationProvider(
|
||||
mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
|
||||
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHapticTextEnabled_vibrationReturnedForTextHandleMove() throws Exception {
|
||||
mockHapticTextSupport(true);
|
||||
mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
|
||||
SparseArray<VibrationEffect> customizations = new SparseArray<>();
|
||||
customizations.put(TEXT_HANDLE_MOVE, PRIMITIVE_CLICK_EFFECT);
|
||||
|
||||
// Test with a customization available for `TEXT_HANDLE_MOVE`.
|
||||
HapticFeedbackVibrationProvider hapticProvider =
|
||||
new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
|
||||
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
|
||||
.isEqualTo(PRIMITIVE_CLICK_EFFECT);
|
||||
|
||||
// Test with no customization available for `TEXT_HANDLE_MOVE`.
|
||||
hapticProvider =
|
||||
new HapticFeedbackVibrationProvider(
|
||||
mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
|
||||
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
|
||||
.isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidCustomizationPresentForSafeModeEnabled_usedRegardlessOfVibrationResource()
|
||||
throws Exception {
|
||||
mockSafeModeEnabledVibration(10, 20, 30, 40);
|
||||
mockVibratorPrimitiveSupport(PRIMITIVE_CLICK);
|
||||
SparseArray<VibrationEffect> customizations = new SparseArray<>();
|
||||
customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
|
||||
|
||||
HapticFeedbackVibrationProvider hapticProvider =
|
||||
new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
|
||||
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
|
||||
.isEqualTo(PRIMITIVE_CLICK_EFFECT);
|
||||
|
||||
mockSafeModeEnabledVibration(null);
|
||||
hapticProvider =
|
||||
new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
|
||||
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
|
||||
.isEqualTo(PRIMITIVE_CLICK_EFFECT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoValidCustomizationPresentForSafeModeEnabled_resourceBasedVibrationUsed()
|
||||
throws Exception {
|
||||
mockSafeModeEnabledVibration(10, 20, 30, 40);
|
||||
SparseArray<VibrationEffect> customizations = new SparseArray<>();
|
||||
customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
|
||||
|
||||
// Test with a customization that is not supported by the vibrator.
|
||||
HapticFeedbackVibrationProvider hapticProvider =
|
||||
new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
|
||||
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
|
||||
.isEqualTo(VibrationEffect.createWaveform(new long[] {10, 20, 30, 40}, -1));
|
||||
|
||||
// Test with no customizations.
|
||||
hapticProvider =
|
||||
new HapticFeedbackVibrationProvider(
|
||||
mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
|
||||
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
|
||||
.isEqualTo(VibrationEffect.createWaveform(new long[] {10, 20, 30, 40}, -1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoValidCustomizationAndResourcePresentForSafeModeEnabled_noVibrationUsed()
|
||||
throws Exception {
|
||||
mockSafeModeEnabledVibration(null);
|
||||
SparseArray<VibrationEffect> customizations = new SparseArray<>();
|
||||
customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
|
||||
|
||||
// Test with a customization that is not supported by the vibrator.
|
||||
HapticFeedbackVibrationProvider hapticProvider =
|
||||
new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
|
||||
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)).isNull();
|
||||
|
||||
// Test with no customizations.
|
||||
hapticProvider =
|
||||
new HapticFeedbackVibrationProvider(
|
||||
mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
|
||||
|
||||
assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)).isNull();
|
||||
}
|
||||
|
||||
private void mockVibratorPrimitiveSupport(int... supportedPrimitives) {
|
||||
mVibrator = new FakeVibrator(mContext, supportedPrimitives);
|
||||
}
|
||||
|
||||
private void mockHapticTextSupport(boolean supported) {
|
||||
when(mResourcesMock.getBoolean(R.bool.config_enableHapticTextHandle)).thenReturn(supported);
|
||||
}
|
||||
|
||||
private void mockSafeModeEnabledVibration(int... vibrationPattern) {
|
||||
when(mResourcesMock.getIntArray(R.array.config_safeModeEnabledVibePattern))
|
||||
.thenReturn(vibrationPattern);
|
||||
}
|
||||
|
||||
private void setupCustomizationFile(String xml) throws Exception {
|
||||
File file = new File(mContext.getCacheDir(), "test.xml");
|
||||
file.createNewFile();
|
||||
|
||||
AtomicFile atomicXmlFile = new AtomicFile(file);
|
||||
FileOutputStream fos = atomicXmlFile.startWrite();
|
||||
fos.write(xml.getBytes());
|
||||
atomicXmlFile.finishWrite(fos);
|
||||
|
||||
when(mResourcesMock.getString(R.string.config_hapticFeedbackCustomizationFile))
|
||||
.thenReturn(file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
@@ -18,15 +18,23 @@ package android.os.test;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.hardware.vibrator.IVibrator;
|
||||
import android.os.VibrationAttributes;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
import android.os.VibratorInfo;
|
||||
|
||||
/** Fake implementation of {@link Vibrator} for service tests. */
|
||||
public final class FakeVibrator extends Vibrator {
|
||||
private final VibratorInfo mVibratorInfo;
|
||||
|
||||
public FakeVibrator(Context context) {
|
||||
this(context, /* supportedPrimitives= */ null);
|
||||
}
|
||||
|
||||
public FakeVibrator(Context context, int[] supportedPrimitives) {
|
||||
super(context);
|
||||
mVibratorInfo = buildInfoSupportingPrimitives(supportedPrimitives);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -39,6 +47,11 @@ public final class FakeVibrator extends Vibrator {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VibratorInfo getInfo() {
|
||||
return mVibratorInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, String reason,
|
||||
@NonNull VibrationAttributes attributes) {
|
||||
@@ -51,4 +64,16 @@ public final class FakeVibrator extends Vibrator {
|
||||
@Override
|
||||
public void cancel(int usageFilter) {
|
||||
}
|
||||
|
||||
private static VibratorInfo buildInfoSupportingPrimitives(int... primitives) {
|
||||
if (primitives == null || primitives.length == 0) {
|
||||
return VibratorInfo.EMPTY_VIBRATOR_INFO;
|
||||
}
|
||||
VibratorInfo.Builder builder = new VibratorInfo.Builder(/* id= */ 1)
|
||||
.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
|
||||
for (int primitive : primitives) {
|
||||
builder.setSupportedPrimitive(primitive, 10);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user