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
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 3d89afa..becbbf2 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -25,6 +25,8 @@
 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 @@
     // 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 @@
 
     /** @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 @@
             hapticCustomizations = null;
         }
         mHapticCustomizations = hapticCustomizations;
+        mViewFeatureFlags = viewFeatureFlags;
 
         mSafeModeEnabledVibrationEffect =
                 effectHasCustomization(HapticFeedbackConstants.SAFE_MODE_ENABLED)
@@ -201,12 +207,16 @@
             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;
         }
-        return attrs;
+        if (shouldBypassInterruptionPolicy(effectId, mViewFeatureFlags)) {
+            flags |= VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
+        }
+
+        return flags == 0 ? attrs : new VibrationAttributes.Builder(attrs).setFlags(flags).build();
     }
 
     /** Dumps relevant state. */
@@ -295,4 +305,19 @@
             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;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index ee3d697..45bd152 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -448,6 +448,7 @@
             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 @@
     }
 
     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 @@
     }
 
     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 @@
         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));
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index a91bd2b..0003555 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -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.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.VibratorInfo;
 import android.util.AtomicFile;
 import android.util.SparseArray;
+import android.view.flags.FeatureFlags;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -59,23 +67,25 @@
     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 @@
     @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 @@
         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 @@
                 + "</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 @@
         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 @@
         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 @@
         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 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 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) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 0eec9cd..40e0e84 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -31,6 +31,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
@@ -46,6 +47,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.res.Resources;
 import android.hardware.input.IInputManager;
@@ -87,6 +89,7 @@
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
 import android.view.InputDevice;
+import android.view.flags.FeatureFlags;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
@@ -172,6 +175,8 @@
     private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock;
     @Mock
     private AudioManager mAudioManagerMock;
+    @Mock
+    private FeatureFlags mViewFeatureFlags;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
 
@@ -321,7 +326,8 @@
                     HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
                             Resources resources, VibratorInfo vibratorInfo) {
                         return new HapticFeedbackVibrationProvider(
-                                resources, vibratorInfo, mHapticFeedbackVibrationMap);
+                                resources, vibratorInfo, mHapticFeedbackVibrationMap,
+                                mViewFeatureFlags);
                     }
                 });
         return mService;
@@ -649,6 +655,42 @@
     }
 
     @Test
+    public void vibrate_withoutBypassFlagsPermissions_bypassFlagsNotApplied() throws Exception {
+        denyPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        denyPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
+        denyPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING);
+
+        assertCanVibrateWithBypassFlags(false);
+    }
+
+    @Test
+    public void vibrate_withSecureSettingsPermission_bypassFlagsApplied() throws Exception {
+        grantPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        denyPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
+        denyPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING);
+
+        assertCanVibrateWithBypassFlags(true);
+    }
+
+    @Test
+    public void vibrate_withModifyPhoneStatePermission_bypassFlagsApplied() throws Exception {
+        denyPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        grantPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
+        denyPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING);
+
+        assertCanVibrateWithBypassFlags(true);
+    }
+
+    @Test
+    public void vibrate_withModifyAudioRoutingPermission_bypassFlagsApplied() throws Exception {
+        denyPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        denyPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
+        grantPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING);
+
+        assertCanVibrateWithBypassFlags(true);
+    }
+
+    @Test
     public void vibrate_withRingtone_usesRingerModeSettings() throws Exception {
         mockVibrators(1);
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
@@ -1166,7 +1208,7 @@
     }
 
     @Test
-    public void vibrate_withTriggerCallback_finishesVibration() throws Exception {
+    public void vibrate_withriggerCallback_finishesVibration() throws Exception {
         mockCapabilities(IVibratorManager.CAP_SYNC, IVibratorManager.CAP_PREPARE_COMPOSE);
         mockVibrators(1, 2);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
@@ -1303,10 +1345,18 @@
     }
 
     @Test
-    public void performHapticFeedback_doesNotRequirePermission() throws Exception {
+    public void performHapticFeedback_doesNotRequireVibrateOrBypassPermissions() throws Exception {
+        // Deny permissions that would have been required for regular vibrations, and check that
+        // the vibration proceed as expected to verify that haptic feedback does not need these
+        // permissions.
         denyPermission(android.Manifest.permission.VIBRATE);
+        denyPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        denyPermission(android.Manifest.permission.MODIFY_PHONE_STATE);
+        denyPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING);
+        // Flag override to enable the scroll feedack constants to bypass interruption policies.
+        when(mViewFeatureFlags.scrollFeedbackApi()).thenReturn(true);
         mHapticFeedbackVibrationMap.put(
-                HapticFeedbackConstants.KEYBOARD_TAP,
+                HapticFeedbackConstants.SCROLL_TICK,
                 VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
         mockVibrators(1);
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
@@ -1315,13 +1365,16 @@
 
         HalVibration vibration =
                 performHapticFeedbackAndWaitUntilFinished(
-                        service, HapticFeedbackConstants.KEYBOARD_TAP, /* always= */ true);
+                        service, HapticFeedbackConstants.SCROLL_TICK, /* always= */ true);
 
         List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments();
         assertEquals(1, playedSegments.size());
         PrebakedSegment segment = (PrebakedSegment) playedSegments.get(0);
         assertEquals(VibrationEffect.EFFECT_CLICK, segment.getEffectId());
-        assertEquals(VibrationAttributes.USAGE_TOUCH, vibration.callerInfo.attrs.getUsage());
+        VibrationAttributes attrs = vibration.callerInfo.attrs;
+        assertEquals(VibrationAttributes.USAGE_HARDWARE_FEEDBACK, attrs.getUsage());
+        assertTrue(attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF));
+        assertTrue(attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
     }
 
     @Test
@@ -2261,6 +2314,31 @@
         assertNull(metrics.halUnsupportedEffectsUsed);
     }
 
+    private void assertCanVibrateWithBypassFlags(boolean expectedCanApplyBypassFlags)
+            throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+
+        HalVibration vibration = vibrateAndWaitUntilFinished(
+                service,
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
+                new VibrationAttributes.Builder()
+                        .setUsage(VibrationAttributes.USAGE_TOUCH)
+                        .setFlags(
+                                VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF
+                                        | VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY)
+                        .build());
+
+        VibrationAttributes attrs = vibration.callerInfo.attrs;
+        assertEquals(
+                expectedCanApplyBypassFlags,
+                attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF));
+        assertEquals(
+                expectedCanApplyBypassFlags,
+                attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY));
+    }
+
     private VibrationEffectSegment expectedPrebaked(int effectId) {
         return expectedPrebaked(effectId, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
     }
@@ -2327,12 +2405,13 @@
         return vib;
     }
 
-    private void vibrateAndWaitUntilFinished(VibratorManagerService service, VibrationEffect effect,
-            VibrationAttributes attrs) throws InterruptedException {
-        vibrateAndWaitUntilFinished(service, CombinedVibration.createParallel(effect), attrs);
+    private HalVibration vibrateAndWaitUntilFinished(VibratorManagerService service,
+            VibrationEffect effect, VibrationAttributes attrs) throws InterruptedException {
+        return vibrateAndWaitUntilFinished(
+                service, CombinedVibration.createParallel(effect), attrs);
     }
 
-    private void vibrateAndWaitUntilFinished(VibratorManagerService service,
+    private HalVibration vibrateAndWaitUntilFinished(VibratorManagerService service,
             CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException {
         HalVibration vib =
                 service.vibrateWithPermissionCheck(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME,
@@ -2340,6 +2419,8 @@
         if (vib != null) {
             vib.waitForEnd();
         }
+
+        return vib;
     }
 
     private void vibrate(VibratorManagerService service, VibrationEffect effect,
@@ -2368,7 +2449,15 @@
         return predicateResult;
     }
 
+    private void grantPermission(String permission) {
+        when(mContextSpy.checkCallingOrSelfPermission(permission))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        doNothing().when(mContextSpy).enforceCallingOrSelfPermission(eq(permission), anyString());
+    }
+
     private void denyPermission(String permission) {
+        when(mContextSpy.checkCallingOrSelfPermission(permission))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
         doThrow(new SecurityException()).when(mContextSpy)
                 .enforceCallingOrSelfPermission(eq(permission), anyString());
     }