Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 57d6f859 authored by Yeabkal Wubshit's avatar Yeabkal Wubshit
Browse files

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
parent 78d05e22
Loading
Loading
Loading
Loading
+149 −29
Original line number Diff line number Diff line
@@ -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);
        }
    }
}
+261 −0
Original line number Diff line number Diff line
/*
 * 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());
    }
}
+25 −0

File changed.

Preview size limit exceeded, changes collapsed.