From 57d6f859586442af5aa648d5ab649bca0332feba Mon Sep 17 00:00:00 2001 From: Yeabkal Wubshit Date: Thu, 20 Jul 2023 03:28:56 -0700 Subject: [PATCH] Use custom haptic vibrations in HapticFeedbackVibrationProvider Adds logic to use the device-specific vibrations for haptic feedbacks if there exists a valid vibration for a constant. Bug: 291128479 Test: atest HapticFeedbackVibrationProviderTest Change-Id: Id3736baac27c2c1faa88502e79468258b93c25b8 --- .../HapticFeedbackVibrationProvider.java | 178 ++++++++++-- .../HapticFeedbackVibrationProviderTest.java | 261 ++++++++++++++++++ .../utils/android/os/test/FakeVibrator.java | 25 ++ 3 files changed, 435 insertions(+), 29 deletions(-) create mode 100644 services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java index 308ce4f9cb686..19dd0b26c50f3 100644 --- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java +++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java @@ -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 mHapticCustomizations; /** @hide */ public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) { + this(res, vibrator, loadHapticCustomizations(res)); + } + + /** @hide */ + @VisibleForTesting HapticFeedbackVibrationProvider( + Resources res, + Vibrator vibrator, + @Nullable SparseArray 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. + * + *

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. + * + *

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 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 customizations, Vibrator vibrator) { + Set 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); } } } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java new file mode 100644 index 0000000000000..cae811e9bb35e --- /dev/null +++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java @@ -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(""); + hapticProvider = new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator); + + assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK)) + .isEqualTo(VibrationEffect.get(EFFECT_TICK)); + } + + @Test + public void testExceptionParsingCustomizations_useDefault() throws Exception { + setupCustomizationFile(""); + 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 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 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 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 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 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 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 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()); + } +} diff --git a/services/tests/vibrator/utils/android/os/test/FakeVibrator.java b/services/tests/vibrator/utils/android/os/test/FakeVibrator.java index 56f49d4e0b7bd..23398e62ae3d3 100644 --- a/services/tests/vibrator/utils/android/os/test/FakeVibrator.java +++ b/services/tests/vibrator/utils/android/os/test/FakeVibrator.java @@ -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(); + } }