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

Commit 8c7e1e15 authored by Yeabkal Wubshit's avatar Yeabkal Wubshit
Browse files

Support bypassing interruption policy for scroll haptic feedback

The scroll haptic feedback should be played regardless of interruption
mode (as they are a system navigation feedback). This change first makes
changes to the VibratorManagerService to enable haptic feedback
vibrations to use bypass flags without any permission, and then enables
interruption policy bypassing for the SCROLL_* constants.

The guard on SCROLL_* constants' bypassing of interruption policy uses
the same flag as the one used for the scroll feedback APIs.

Bug: 289480045
Test: atest HapticFeedbackVibrationProviderTest
Test: atest VibratorManagerServiceTest
Change-Id: I1eb6368c3685e71eead334644d973a9d21f6d207
parent 3e920dd1
Loading
Loading
Loading
Loading
+31 −6
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ import android.os.VibratorInfo;
import android.util.Slog;
import android.util.SparseArray;
import android.view.HapticFeedbackConstants;
import android.view.flags.FeatureFlags;
import android.view.flags.FeatureFlagsImpl;

import com.android.internal.annotations.VisibleForTesting;

@@ -54,6 +56,7 @@ public final class HapticFeedbackVibrationProvider {
    // 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;
    private final FeatureFlags mViewFeatureFlags;

    /** @hide */
    public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) {
@@ -62,14 +65,16 @@ public final class HapticFeedbackVibrationProvider {

    /** @hide */
    public HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo) {
        this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo));
        this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo),
                new FeatureFlagsImpl());
    }

    /** @hide */
    @VisibleForTesting HapticFeedbackVibrationProvider(
            Resources res,
            VibratorInfo vibratorInfo,
            @Nullable SparseArray<VibrationEffect> hapticCustomizations) {
            @Nullable SparseArray<VibrationEffect> hapticCustomizations,
            FeatureFlags viewFeatureFlags) {
        mVibratorInfo = vibratorInfo;
        mHapticTextHandleEnabled = res.getBoolean(
                com.android.internal.R.bool.config_enableHapticTextHandle);
@@ -78,6 +83,7 @@ public final class HapticFeedbackVibrationProvider {
            hapticCustomizations = null;
        }
        mHapticCustomizations = hapticCustomizations;
        mViewFeatureFlags = viewFeatureFlags;

        mSafeModeEnabledVibrationEffect =
                effectHasCustomization(HapticFeedbackConstants.SAFE_MODE_ENABLED)
@@ -201,12 +207,16 @@ public final class HapticFeedbackVibrationProvider {
            default:
                attrs = TOUCH_VIBRATION_ATTRIBUTES;
        }

        int flags = 0;
        if (bypassVibrationIntensitySetting) {
            attrs = new VibrationAttributes.Builder(attrs)
                    .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
                    .build();
            flags |= VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
        }
        if (shouldBypassInterruptionPolicy(effectId, mViewFeatureFlags)) {
            flags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
        }
        return attrs;

        return flags == 0 ? attrs : new VibrationAttributes.Builder(attrs).setFlags(flags).build();
    }

    /** Dumps relevant state. */
@@ -295,4 +305,19 @@ public final class HapticFeedbackVibrationProvider {
            return null;
        }
    }

    private static boolean shouldBypassInterruptionPolicy(
            int effectId, FeatureFlags viewFeatureFlags) {
        switch (effectId) {
            case HapticFeedbackConstants.SCROLL_TICK:
            case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
            case HapticFeedbackConstants.SCROLL_LIMIT:
                // The SCROLL_* constants should bypass interruption filter, so that scroll haptics
                // can play regardless of focus modes like DND. Guard this behavior by the feature
                // flag controlling the general scroll feedback APIs.
                return viewFeatureFlags.scrollFeedbackApi();
            default:
                return false;
        }
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -448,6 +448,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
            String reason, IBinder token) {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
        try {
            attrs = fixupVibrationAttributes(attrs, effect);
            mContext.enforceCallingOrSelfPermission(
                    android.Manifest.permission.VIBRATE, "vibrate");
            return vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token);
@@ -457,7 +458,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
    }

    HalVibration vibrateWithoutPermissionCheck(int uid, int displayId, String opPkg,
            @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
            @NonNull CombinedVibration effect, @NonNull VibrationAttributes attrs,
            String reason, IBinder token) {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate no perm check, reason = " + reason);
        try {
@@ -468,7 +469,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
    }

    private HalVibration vibrateInternal(int uid, int displayId, String opPkg,
            @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
            @NonNull CombinedVibration effect, @NonNull VibrationAttributes attrs,
            String reason, IBinder token) {
        if (token == null) {
            Slog.e(TAG, "token must not be null");
@@ -478,7 +479,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
        if (!isEffectValid(effect)) {
            return null;
        }
        attrs = fixupVibrationAttributes(attrs, effect);
        // Create Vibration.Stats as close to the received request as possible, for tracking.
        HalVibration vib = new HalVibration(token, effect,
                new Vibration.CallerInfo(attrs, uid, displayId, opPkg, reason));
+64 −37
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.vibrator;

import static android.os.VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK;
import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK;
import static android.os.VibrationEffect.EFFECT_TEXTURE_TICK;
@@ -24,8 +26,13 @@ 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 android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS;
import static android.view.HapticFeedbackConstants.SCROLL_LIMIT;
import static android.view.HapticFeedbackConstants.SCROLL_TICK;


import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import static org.mockito.Mockito.when;

@@ -37,6 +44,7 @@ import android.os.VibrationEffect;
import android.os.VibratorInfo;
import android.util.AtomicFile;
import android.util.SparseArray;
import android.view.flags.FeatureFlags;

import androidx.test.InstrumentationRegistry;

@@ -59,23 +67,25 @@ public class HapticFeedbackVibrationProviderTest {
    private static final VibrationEffect PRIMITIVE_CLICK_EFFECT =
            VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK, 0.3497f).compose();

    private static final int[] SCROLL_FEEDBACK_CONSTANTS =
            new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK};
    private Context mContext = InstrumentationRegistry.getContext();
    private VibratorInfo mVibratorInfo = VibratorInfo.EMPTY_VIBRATOR_INFO;

    @Mock private Resources mResourcesMock;
    @Mock private FeatureFlags mViewFeatureFlags;

    @Test
    public void testNonExistentCustomization_useDefault() throws Exception {
        // No customization file is set.
        HapticFeedbackVibrationProvider hapticProvider =
                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();

        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, mVibratorInfo);
        hapticProvider = createProviderWithDefaultCustomizations();

        assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
                .isEqualTo(VibrationEffect.get(EFFECT_TICK));
@@ -84,8 +94,7 @@ public class HapticFeedbackVibrationProviderTest {
    @Test
    public void testExceptionParsingCustomizations_useDefault() throws Exception {
        setupCustomizationFile("<bad-xml></bad-xml>");
        HapticFeedbackVibrationProvider hapticProvider =
                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();

        assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
                .isEqualTo(VibrationEffect.get(EFFECT_TICK));
@@ -97,8 +106,7 @@ public class HapticFeedbackVibrationProviderTest {
        SparseArray<VibrationEffect> customizations = new SparseArray<>();
        customizations.put(CONTEXT_CLICK, PRIMITIVE_CLICK_EFFECT);

        HapticFeedbackVibrationProvider hapticProvider =
                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
        HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);

        // The override for `CONTEXT_CLICK` is used.
        assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
@@ -118,8 +126,7 @@ public class HapticFeedbackVibrationProviderTest {
                + "</haptic-feedback-constants>";
        setupCustomizationFile(xml);

        HapticFeedbackVibrationProvider hapticProvider =
                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();

        // The override for `CONTEXT_CLICK` is not used because the vibration is not supported.
        assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
@@ -137,15 +144,12 @@ public class HapticFeedbackVibrationProviderTest {
        customizations.put(TEXT_HANDLE_MOVE, PRIMITIVE_CLICK_EFFECT);

        // Test with a customization available for `TEXT_HANDLE_MOVE`.
        HapticFeedbackVibrationProvider hapticProvider =
                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
        HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);

        assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();

        // Test with no customization available for `TEXT_HANDLE_MOVE`.
        hapticProvider =
                new HapticFeedbackVibrationProvider(
                        mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null);
        hapticProvider = createProvider(/* customizations= */ null);

        assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
    }
@@ -158,16 +162,13 @@ public class HapticFeedbackVibrationProviderTest {
        customizations.put(TEXT_HANDLE_MOVE, PRIMITIVE_CLICK_EFFECT);

        // Test with a customization available for `TEXT_HANDLE_MOVE`.
        HapticFeedbackVibrationProvider hapticProvider =
                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
        HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);

        assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
                .isEqualTo(PRIMITIVE_CLICK_EFFECT);

        // Test with no customization available for `TEXT_HANDLE_MOVE`.
        hapticProvider =
                new HapticFeedbackVibrationProvider(
                        mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null);
        hapticProvider = createProvider(/* customizations= */ null);

        assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
                .isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
@@ -181,15 +182,13 @@ public class HapticFeedbackVibrationProviderTest {
        SparseArray<VibrationEffect> customizations = new SparseArray<>();
        customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);

        HapticFeedbackVibrationProvider hapticProvider =
                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
        HapticFeedbackVibrationProvider hapticProvider = createProvider(customizations);

        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
                .isEqualTo(PRIMITIVE_CLICK_EFFECT);

        mockSafeModeEnabledVibration(null);
        hapticProvider =
                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
        hapticProvider = createProvider(customizations);

        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
                .isEqualTo(PRIMITIVE_CLICK_EFFECT);
@@ -199,9 +198,7 @@ public class HapticFeedbackVibrationProviderTest {
    public void testNoValidCustomizationPresentForSafeModeEnabled_resourceBasedVibrationUsed()
                throws Exception {
        mockSafeModeEnabledVibration(10, 20, 30, 40);
        HapticFeedbackVibrationProvider hapticProvider =
                new HapticFeedbackVibrationProvider(
                        mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null);
        HapticFeedbackVibrationProvider hapticProvider = createProvider(/* customizations= */ null);

        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
                .isEqualTo(VibrationEffect.createWaveform(new long[] {10, 20, 30, 40}, -1));
@@ -211,35 +208,65 @@ public class HapticFeedbackVibrationProviderTest {
    public void testNoValidCustomizationAndResourcePresentForSafeModeEnabled_noVibrationUsed()
                throws Exception {
        mockSafeModeEnabledVibration(null);
        HapticFeedbackVibrationProvider hapticProvider =
                new HapticFeedbackVibrationProvider(
                        mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null);
        HapticFeedbackVibrationProvider hapticProvider = createProvider(/* customizations= */ null);

        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)).isNull();
    }

    @Test
    public void testVibrationAttribute_forNotBypassingIntensitySettings() {
        HapticFeedbackVibrationProvider hapticProvider =
                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();

        VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
                SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ false);

        assertThat(attrs.getFlags() & VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
                .isEqualTo(0);
        assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isFalse();
    }

    @Test
    public void testVibrationAttribute_forByassingIntensitySettings() {
        HapticFeedbackVibrationProvider hapticProvider =
                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();

        VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
                SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ true);

        assertThat(attrs.getFlags() & VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
                .isNotEqualTo(0);
        assertThat(attrs.isFlagSet(FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)).isTrue();
    }

    @Test
    public void testVibrationAttribute_scrollFeedback_scrollApiFlagOn_bypassInterruptPolicy() {
        when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(true);
        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();

        for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
                    effectId, /* bypassVibrationIntensitySetting= */ false);
            assertWithMessage("Expected FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
                   .that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isTrue();
        }
    }

    @Test
    public void testVibrationAttribute_scrollFeedback_scrollApiFlagOff_noBypassInterruptPolicy() {
        when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(false);
        HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();

        for (int effectId : SCROLL_FEEDBACK_CONSTANTS) {
            VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
                    effectId, /* bypassVibrationIntensitySetting= */ false);
            assertWithMessage("Expected no FLAG_BYPASS_INTERRUPTION_POLICY for effect " + effectId)
                   .that(attrs.isFlagSet(FLAG_BYPASS_INTERRUPTION_POLICY)).isFalse();
        }
    }

    private HapticFeedbackVibrationProvider createProviderWithDefaultCustomizations() {
        return createProvider(/* customizations= */ null);
    }

    private HapticFeedbackVibrationProvider createProvider(
            SparseArray<VibrationEffect> customizations) {
        return new HapticFeedbackVibrationProvider(
            mResourcesMock, mVibratorInfo, customizations, mViewFeatureFlags);
    }

    private void mockVibratorPrimitiveSupport(int... supportedPrimitives) {
+99 −10

File changed.

Preview size limit exceeded, changes collapsed.