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

Commit 169d48d2 authored by Yeabkal Wubshit's avatar Yeabkal Wubshit Committed by Android (Google) Code Review
Browse files

Merge "Support bypassing interruption policy for scroll haptic feedback" into main

parents 5bafb6f3 8c7e1e15
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.