Register VibrationSettings as a listener to:
1) virtual displays creation and removal.
2) App Uids running on virtual displays.
These will be used in combination to ignore vibration initiated from a virtual display.

This change also consolicates the fix to avoid Null reference when VirtualDeviceManger
is not present in the available system services. See also ag/19744341.

Test: VibrationSettingsTest, VibratorManagerServiceTest

bug: 189474679

Change-Id: Ie10dbfe65bb3ab9d62ed5c7b5ed7095a51bccf24
Merged-In: Ie10dbfe65bb3ab9d62ed5c7b5ed7095a51bccf24
(cherry picked from commit a0ae5c195186971e0b581451a3da856645a1fd6d)
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index a0d6ce1..fb9752f 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -30,7 +30,7 @@
     boolean unregisterVibratorStateListener(int vibratorId, in IVibratorStateListener listener);
     boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
             in CombinedVibration vibration, in VibrationAttributes attributes);
-    void vibrate(int uid, String opPkg, in CombinedVibration vibration,
+    void vibrate(int uid, int displayId, String opPkg, in CombinedVibration vibration,
             in VibrationAttributes attributes, String reason, IBinder token);
     void cancelVibrate(int usageFilter, IBinder token);
 }
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index c690df2..eb2a712 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -137,7 +137,8 @@
             return;
         }
         try {
-            mService.vibrate(uid, opPkg, effect, attributes, reason, mToken);
+            mService.vibrate(uid, mContext.getAssociatedDisplayId(), opPkg, effect, attributes,
+                    reason, mToken);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to vibrate.", e);
         }
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index 25a1f68..c211a5e 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -127,6 +127,7 @@
         IGNORED_FOR_RINGER_MODE = 23;
         IGNORED_FOR_SETTINGS = 24;
         IGNORED_SUPERSEDED = 25;
+        IGNORED_FROM_VIRTUAL_DEVICE = 26;
     }
 }
 
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index a375d0a..83caa0e 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -71,7 +71,8 @@
         IGNORED_FOR_POWER(VibrationProto.IGNORED_FOR_POWER),
         IGNORED_FOR_RINGER_MODE(VibrationProto.IGNORED_FOR_RINGER_MODE),
         IGNORED_FOR_SETTINGS(VibrationProto.IGNORED_FOR_SETTINGS),
-        IGNORED_SUPERSEDED(VibrationProto.IGNORED_SUPERSEDED);
+        IGNORED_SUPERSEDED(VibrationProto.IGNORED_SUPERSEDED),
+        IGNORED_FROM_VIRTUAL_DEVICE(VibrationProto.IGNORED_FROM_VIRTUAL_DEVICE);
 
         private final int mProtoEnumValue;
 
@@ -87,6 +88,7 @@
     public final VibrationAttributes attrs;
     public final long id;
     public final int uid;
+    public final int displayId;
     public final String opPkg;
     public final String reason;
     public final IBinder token;
@@ -113,12 +115,13 @@
     private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
 
     Vibration(IBinder token, int id, CombinedVibration effect,
-            VibrationAttributes attrs, int uid, String opPkg, String reason) {
+            VibrationAttributes attrs, int uid, int displayId, String opPkg, String reason) {
         this.token = token;
         this.mEffect = effect;
         this.id = id;
         this.attrs = attrs;
         this.uid = uid;
+        this.displayId = displayId;
         this.opPkg = opPkg;
         this.reason = reason;
         mStatus = Vibration.Status.RUNNING;
@@ -236,7 +239,7 @@
     /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */
     public Vibration.DebugInfo getDebugInfo() {
         return new Vibration.DebugInfo(mStatus, mStats, mEffect, mOriginalEffect, /* scale= */ 0,
-                attrs, uid, opPkg, reason);
+                attrs, uid, displayId, opPkg, reason);
     }
 
     /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
@@ -304,13 +307,14 @@
         private final float mScale;
         private final VibrationAttributes mAttrs;
         private final int mUid;
+        private final int mDisplayId;
         private final String mOpPkg;
         private final String mReason;
         private final Status mStatus;
 
         DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration effect,
                 @Nullable CombinedVibration originalEffect, float scale, VibrationAttributes attrs,
-                int uid, String opPkg, String reason) {
+                int uid, int displayId, String opPkg, String reason) {
             mCreateTime = stats.getCreateTimeDebug();
             mStartTime = stats.getStartTimeDebug();
             mEndTime = stats.getEndTimeDebug();
@@ -320,6 +324,7 @@
             mScale = scale;
             mAttrs = attrs;
             mUid = uid;
+            mDisplayId = displayId;
             mOpPkg = opPkg;
             mReason = reason;
             mStatus = status;
@@ -349,6 +354,8 @@
                     .append(mAttrs)
                     .append(", uid: ")
                     .append(mUid)
+                    .append(", displayId: ")
+                    .append(mDisplayId)
                     .append(", opPkg: ")
                     .append(mOpPkg)
                     .append(", reason: ")
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 8e6a290..6012993 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -56,10 +56,12 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
+import android.view.Display;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -157,6 +159,7 @@
     final UidObserver mUidObserver;
     @VisibleForTesting
     final SettingsBroadcastReceiver mSettingChangeReceiver;
+    final VirtualDeviceListener mVirtualDeviceListener;
 
     @GuardedBy("mLock")
     private final List<OnVibratorSettingsChanged> mListeners = new ArrayList<>();
@@ -193,6 +196,7 @@
         mSettingObserver = new SettingsContentObserver(handler);
         mUidObserver = new UidObserver();
         mSettingChangeReceiver = new SettingsBroadcastReceiver();
+        mVirtualDeviceListener = new VirtualDeviceListener();
 
         mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
                 .getSystemUiServiceComponent().getPackageName();
@@ -257,6 +261,13 @@
                     }
                 });
 
+        VirtualDeviceManagerInternal vdm = LocalServices.getService(
+                VirtualDeviceManagerInternal.class);
+        if (vdm != null) {
+            vdm.registerVirtualDisplayListener(mVirtualDeviceListener);
+            vdm.registerAppsOnVirtualDeviceListener(mVirtualDeviceListener);
+        }
+
         registerSettingsChangeReceiver(USER_SWITCHED_INTENT_FILTER);
         registerSettingsChangeReceiver(INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER);
 
@@ -364,13 +375,17 @@
      * null otherwise.
      */
     @Nullable
-    public Vibration.Status shouldIgnoreVibration(int uid, VibrationAttributes attrs) {
+    public Vibration.Status shouldIgnoreVibration(int uid, int displayId,
+            VibrationAttributes attrs) {
         final int usage = attrs.getUsage();
         synchronized (mLock) {
             if (!mUidObserver.isUidForeground(uid)
                     && !BACKGROUND_PROCESS_USAGE_ALLOWLIST.contains(usage)) {
                 return Vibration.Status.IGNORED_BACKGROUND;
             }
+            if (mVirtualDeviceListener.isAppOrDisplayOnAnyVirtualDevice(uid, displayId)) {
+                return Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE;
+            }
 
             if (mBatterySaverMode && !BATTERY_SAVER_USAGE_ALLOWLIST.contains(usage)) {
                 return Vibration.Status.IGNORED_FOR_POWER;
@@ -741,4 +756,73 @@
         public void onUidProcAdjChanged(int uid) {
         }
     }
+
+    /**
+     * Implementation of Virtual Device listeners for the changes of virtual displays and of apps
+     * running on any virtual device.
+     */
+    final class VirtualDeviceListener implements
+            VirtualDeviceManagerInternal.VirtualDisplayListener,
+            VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener {
+        @GuardedBy("mLock")
+        private final Set<Integer> mVirtualDisplays = new HashSet<>();
+        @GuardedBy("mLock")
+        private final Set<Integer> mAppsOnVirtualDevice = new HashSet<>();
+
+
+        @Override
+        public void onVirtualDisplayCreated(int displayId) {
+            synchronized (mLock) {
+                mVirtualDisplays.add(displayId);
+            }
+        }
+
+        @Override
+        public void onVirtualDisplayRemoved(int displayId) {
+            synchronized (mLock) {
+                mVirtualDisplays.remove(displayId);
+            }
+        }
+
+
+        @Override
+        public void onAppsOnAnyVirtualDeviceChanged(Set<Integer> allRunningUids) {
+            synchronized (mLock) {
+                mAppsOnVirtualDevice.clear();
+                mAppsOnVirtualDevice.addAll(allRunningUids);
+            }
+        }
+
+        /**
+         * @param uid:       uid of the calling app.
+         * @param displayId: the id of a Display.
+         * @return Returns true if:
+         * <ul>
+         *   <li> the displayId is valid, and it's owned by a virtual device.</li>
+         *   <li> the displayId is invalid, and the calling app (uid) is running on a virtual
+         *        device.</li>
+         * </ul>
+         */
+        public boolean isAppOrDisplayOnAnyVirtualDevice(int uid, int displayId) {
+            if (displayId == Display.DEFAULT_DISPLAY) {
+                // The default display is the primary physical display on the phone.
+                return false;
+            }
+
+            synchronized (mLock) {
+                if (displayId == Display.INVALID_DISPLAY) {
+                    // There is no Display object associated with the Context of calling
+                    // {@link SystemVibratorManager}, checking the calling UID instead.
+                    return mAppsOnVirtualDevice.contains(uid);
+                } else {
+                    // Other valid display IDs representing valid logical displays will be
+                    // checked
+                    // against the active virtual displays set built with the registered
+                    // {@link VirtualDisplayListener}.
+                    return mVirtualDisplays.contains(displayId);
+                }
+            }
+        }
+
+    }
 }
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 2f12a82..9727558 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -58,6 +58,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
+import android.view.Display;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -378,9 +379,9 @@
     }
 
     @Override // Binder call
-    public void vibrate(int uid, String opPkg, @NonNull CombinedVibration effect,
+    public void vibrate(int uid, int displayId, String opPkg, @NonNull CombinedVibration effect,
             @Nullable VibrationAttributes attrs, String reason, IBinder token) {
-        vibrateInternal(uid, opPkg, effect, attrs, reason, token);
+        vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token);
     }
 
     /**
@@ -389,8 +390,9 @@
      */
     @Nullable
     @VisibleForTesting
-    Vibration vibrateInternal(int uid, String opPkg, @NonNull CombinedVibration effect,
-            @Nullable VibrationAttributes attrs, String reason, IBinder token) {
+    Vibration vibrateInternal(int uid, int displayId, String opPkg,
+            @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
+            String reason, IBinder token) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
         try {
             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, "vibrate");
@@ -406,7 +408,7 @@
             attrs = fixupVibrationAttributes(attrs, effect);
             // Create Vibration.Stats as close to the received request as possible, for tracking.
             Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
-                    uid, opPkg, reason);
+                    uid, displayId, opPkg, reason);
             fillVibrationFallbacks(vib, effect);
 
             if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
@@ -424,7 +426,7 @@
                 Vibration.Status status = null;
 
                 // Check if user settings or DnD is set to ignore this vibration.
-                status = shouldIgnoreVibrationLocked(vib.uid, vib.opPkg, vib.attrs);
+                status = shouldIgnoreVibrationLocked(vib.uid, vib.displayId, vib.opPkg, vib.attrs);
 
                 // Check if something has external control, assume it's more important.
                 if ((status == null) && (mCurrentExternalVibration != null)) {
@@ -629,7 +631,7 @@
 
             Vibration vib = mCurrentVibration.getVibration();
             Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
-                    vib.uid, vib.opPkg, vib.attrs);
+                    vib.uid, vib.displayId, vib.opPkg, vib.attrs);
 
             if (inputDevicesChanged || (ignoreStatus != null)) {
                 if (DEBUG) {
@@ -659,7 +661,7 @@
                 continue;
             }
             Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
-                    vib.uid, vib.opPkg, vib.attrs);
+                    vib.uid, Display.DEFAULT_DISPLAY, vib.opPkg, vib.attrs);
             if (ignoreStatus == null) {
                 effect = mVibrationScaler.scale(effect, vib.attrs.getUsage());
             } else {
@@ -780,6 +782,12 @@
                             + attrs);
                 }
                 break;
+            case IGNORED_FROM_VIRTUAL_DEVICE:
+                if (DEBUG) {
+                    Slog.d(TAG, "Ignoring incoming vibration because it came from a virtual"
+                            + " device, attrs= " + attrs);
+                }
+                break;
             default:
                 if (DEBUG) {
                     Slog.d(TAG, "Vibration for uid=" + uid + " and with attrs=" + attrs
@@ -894,9 +902,10 @@
      */
     @GuardedBy("mLock")
     @Nullable
-    private Vibration.Status shouldIgnoreVibrationLocked(int uid, String opPkg,
+    private Vibration.Status shouldIgnoreVibrationLocked(int uid, int displayId, String opPkg,
             VibrationAttributes attrs) {
-        Vibration.Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(uid, attrs);
+        Vibration.Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(uid,
+                displayId, attrs);
         if (statusFromSettings != null) {
             return statusFromSettings;
         }
@@ -1442,6 +1451,9 @@
             return new Vibration.DebugInfo(
                     mStatus, stats, /* effect= */ null, /* originalEffect= */ null, scale,
                     externalVibration.getVibrationAttributes(), externalVibration.getUid(),
+                    // TODO(b/243604888): propagating displayID from IExternalVibration instead of
+                    // using INVALID_DISPLAY for all external vibrations.
+                    Display.INVALID_DISPLAY,
                     externalVibration.getPackage(), /* reason= */ null);
         }
 
@@ -1647,8 +1659,10 @@
             boolean alreadyUnderExternalControl = false;
             boolean waitForCompletion = false;
             synchronized (mLock) {
+                // TODO(b/243604888): propagating displayID from IExternalVibration instead of
+                // using INVALID_DISPLAY for all external vibrations.
                 Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
-                        vib.getUid(), vib.getPackage(), attrs);
+                        vib.getUid(), Display.INVALID_DISPLAY, vib.getPackage(), attrs);
                 if (ignoreStatus != null) {
                     vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
                     // Failed to start the vibration, end it and report metrics right away.
@@ -1840,8 +1854,8 @@
             // only cancel background vibrations.
             IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
                     : mShellCallbacksToken;
-            Vibration vib = vibrateInternal(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combined,
-                    attrs, commonOptions.description, deathBinder);
+            Vibration vib = vibrateInternal(Binder.getCallingUid(), Display.DEFAULT_DISPLAY,
+                    SHELL_PACKAGE_NAME, combined, attrs, commonOptions.description, deathBinder);
             if (vib != null && !commonOptions.background) {
                 try {
                     vib.waitForEnd();
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
index 4ef6530..aafbb11 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -66,12 +66,15 @@
 import android.os.vibrator.VibrationConfig;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
+import android.util.ArraySet;
+import android.view.Display;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
@@ -95,6 +98,7 @@
 public class VibrationSettingsTest {
 
     private static final int UID = 1;
+    private static final int VIRTUAL_DISPLAY_ID = 1;
     private static final String SYSUI_PACKAGE_NAME = "sysui";
     private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
     private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
@@ -117,15 +121,23 @@
     @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
     @Mock private VibrationSettings.OnVibratorSettingsChanged mListenerMock;
-    @Mock private PowerManagerInternal mPowerManagerInternalMock;
-    @Mock private PackageManagerInternal mPackageManagerInternalMock;
-    @Mock private VibrationConfig mVibrationConfigMock;
+    @Mock
+    private PowerManagerInternal mPowerManagerInternalMock;
+    @Mock
+    private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock;
+    @Mock
+    private PackageManagerInternal mPackageManagerInternalMock;
+    @Mock
+    private VibrationConfig mVibrationConfigMock;
 
     private TestLooper mTestLooper;
     private ContextWrapper mContextSpy;
     private AudioManager mAudioManager;
     private VibrationSettings mVibrationSettings;
     private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
+    private VirtualDeviceManagerInternal.VirtualDisplayListener mRegisteredVirtualDisplayListener;
+    private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener
+            mRegisteredAppsOnVirtualDeviceListener;
 
     @Before
     public void setUp() throws Exception {
@@ -140,11 +152,17 @@
         }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
         when(mPackageManagerInternalMock.getSystemUiServiceComponent())
                 .thenReturn(new ComponentName(SYSUI_PACKAGE_NAME, ""));
+        doAnswer(invocation -> {
+            mRegisteredVirtualDisplayListener = invocation.getArgument(0);
+            return null;
+        }).when(mVirtualDeviceManagerInternalMock).registerVirtualDisplayListener(any());
+        doAnswer(invocation -> {
+            mRegisteredAppsOnVirtualDeviceListener = invocation.getArgument(0);
+            return null;
+        }).when(mVirtualDeviceManagerInternalMock).registerAppsOnVirtualDeviceListener(any());
 
-        LocalServices.removeServiceForTest(PowerManagerInternal.class);
-        LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock);
-        LocalServices.removeServiceForTest(PackageManagerInternal.class);
-        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
+        removeServicesForTest();
+        addServicesForTest();
 
         setDefaultIntensity(VIBRATION_INTENSITY_MEDIUM);
         mAudioManager = mContextSpy.getSystemService(AudioManager.class);
@@ -168,6 +186,18 @@
     }
 
     @Test
+    public void create_withOnlyRequiredSystemServices() {
+        // The only core services that we depend on are PowerManager and PackageManager
+        removeServicesForTest();
+        LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
+
+        VibrationSettings minimalVibrationSettings = new VibrationSettings(mContextSpy,
+                new Handler(mTestLooper.getLooper()), mVibrationConfigMock);
+        minimalVibrationSettings.onSystemReady();
+    }
+
+    @Test
     public void addListener_settingsChangeTriggerListener() {
         mVibrationSettings.addListener(mListenerMock);
 
@@ -447,6 +477,65 @@
     }
 
     @Test
+    public void shouldIgnoreVibrationFromVirtualDisplays_displayNonVirtual_neverIgnored() {
+        // Vibrations from the primary display is never ignored regardless of the creation and
+        // removal of virtual displays and of the changes of apps running on virtual displays.
+        mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID);
+        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(
+                new ArraySet<>(Arrays.asList(UID)));
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY);
+        }
+
+        mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID);
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY);
+        }
+
+        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>());
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY);
+        }
+    }
+
+    @Test
+    public void shouldIgnoreVibrationFromVirtualDisplays_displayVirtual() {
+        // Ignore the vibration when the coming display id represents a virtual display.
+        mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID);
+
+        for (int usage : ALL_USAGES) {
+            assertVibrationIgnoredForUsageAndDisplay(usage, VIRTUAL_DISPLAY_ID,
+                    Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE);
+        }
+
+        // Stop ignoring when the virtual display is removed.
+        mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID);
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsageAndDisplay(usage, VIRTUAL_DISPLAY_ID);
+        }
+    }
+
+
+    @Test
+    public void shouldIgnoreVibrationFromVirtualDisplays_appsOnVirtualDisplay() {
+        // Ignore when the passed-in display id is invalid and the calling uid is on a virtual
+        // display.
+        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(
+                new ArraySet<>(Arrays.asList(UID)));
+        for (int usage : ALL_USAGES) {
+            assertVibrationIgnoredForUsageAndDisplay(usage, Display.INVALID_DISPLAY,
+                    Vibration.Status.IGNORED_FROM_VIRTUAL_DEVICE);
+        }
+
+        // Stop ignoring when the app is no longer on virtual display.
+        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>());
+        for (int usage : ALL_USAGES) {
+            assertVibrationNotIgnoredForUsageAndDisplay(usage, Display.INVALID_DISPLAY);
+        }
+
+    }
+
+    @Test
     public void shouldVibrateInputDevices_returnsSettingsValue() {
         setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1);
         assertTrue(mVibrationSettings.shouldVibrateInputDevices());
@@ -479,7 +568,7 @@
     @Test
     public void shouldCancelVibrationOnScreenOff_withSleepReasonInAllowlist_returnsAlwaysFalse() {
         long vibrateStartTime = 100;
-        int[] allowedSleepReasons = new int[] {
+        int[] allowedSleepReasons = new int[]{
                 PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
                 PowerManager.GO_TO_SLEEP_REASON_INATTENTIVE,
         };
@@ -646,11 +735,29 @@
         assertNotNull(mVibrationSettings.getFallbackEffect(VibrationEffect.EFFECT_DOUBLE_CLICK));
     }
 
+    private void removeServicesForTest() {
+        LocalServices.removeServiceForTest(PowerManagerInternal.class);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+    }
+
+    private void addServicesForTest() {
+        LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternalMock);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
+        LocalServices.addService(VirtualDeviceManagerInternal.class,
+                mVirtualDeviceManagerInternalMock);
+    }
+
     private void assertVibrationIgnoredForUsage(@VibrationAttributes.Usage int usage,
             Vibration.Status expectedStatus) {
+        assertVibrationIgnoredForUsageAndDisplay(usage, Display.DEFAULT_DISPLAY, expectedStatus);
+    }
+
+    private void assertVibrationIgnoredForUsageAndDisplay(@VibrationAttributes.Usage int usage,
+            int displayId, Vibration.Status expectedStatus) {
         assertEquals(errorMessageForUsage(usage),
                 expectedStatus,
-                mVibrationSettings.shouldIgnoreVibration(UID,
+                mVibrationSettings.shouldIgnoreVibration(UID, displayId,
                         VibrationAttributes.createForUsage(usage)));
     }
 
@@ -660,8 +767,20 @@
 
     private void assertVibrationNotIgnoredForUsageAndFlags(@VibrationAttributes.Usage int usage,
             @VibrationAttributes.Flag int flags) {
+        assertVibrationNotIgnoredForUsageAndFlagsAndDidsplay(usage, Display.DEFAULT_DISPLAY, flags);
+    }
+
+    private void assertVibrationNotIgnoredForUsageAndDisplay(@VibrationAttributes.Usage int usage,
+            int displayId) {
+        assertVibrationNotIgnoredForUsageAndFlagsAndDidsplay(usage, displayId, /* flags= */ 0);
+    }
+
+    private void assertVibrationNotIgnoredForUsageAndFlagsAndDidsplay(
+            @VibrationAttributes.Usage int usage, int displayId,
+            @VibrationAttributes.Flag int flags) {
         assertNull(errorMessageForUsage(usage),
                 mVibrationSettings.shouldIgnoreVibration(UID,
+                        displayId,
                         new VibrationAttributes.Builder()
                                 .setUsage(usage)
                                 .setFlags(flags)
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index ca162ef..0551bfc 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -94,6 +94,7 @@
 
     private static final int TEST_TIMEOUT_MILLIS = 900;
     private static final int UID = Process.ROOT_UID;
+    private static final int DISPLAY_ID = 10;
     private static final int VIBRATOR_ID = 1;
     private static final String PACKAGE_NAME = "package";
     private static final VibrationAttributes ATTRS = new VibrationAttributes.Builder().build();
@@ -1584,7 +1585,8 @@
     }
 
     private Vibration createVibration(long id, CombinedVibration effect) {
-        return new Vibration(mVibrationToken, (int) id, effect, ATTRS, UID, PACKAGE_NAME, "reason");
+        return new Vibration(mVibrationToken, (int) id, effect, ATTRS, UID, DISPLAY_ID,
+                PACKAGE_NAME, "reason");
     }
 
     private SparseArray<VibratorController> createVibratorControllers() {
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 36bec75..fe0a79c 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -76,7 +76,9 @@
 import android.os.vibrator.VibrationEffectSegment;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
+import android.util.ArraySet;
 import android.util.SparseBooleanArray;
+import android.view.Display;
 import android.view.InputDevice;
 
 import androidx.test.InstrumentationRegistry;
@@ -86,6 +88,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 
 import org.junit.After;
 import org.junit.Before;
@@ -115,6 +118,7 @@
 
     private static final int TEST_TIMEOUT_MILLIS = 1_000;
     private static final int UID = Process.ROOT_UID;
+    private static final int VIRTUAL_DISPLAY_ID = 1;
     private static final String PACKAGE_NAME = "package";
     private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
     private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
@@ -153,6 +157,8 @@
     private IBatteryStats mBatteryStatsMock;
     @Mock
     private VibratorFrameworkStatsLogger mVibratorFrameworkStatsLoggerMock;
+    @Mock
+    private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock;
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
 
@@ -162,6 +168,9 @@
     private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
     private VibratorManagerService.ExternalVibratorService mExternalVibratorService;
     private VibrationConfig mVibrationConfig;
+    private VirtualDeviceManagerInternal.VirtualDisplayListener mRegisteredVirtualDisplayListener;
+    private VirtualDeviceManagerInternal.AppsOnVirtualDeviceListener
+            mRegisteredAppsOnVirtualDeviceListener;
 
     @Before
     public void setUp() throws Exception {
@@ -186,6 +195,14 @@
             mRegisteredPowerModeListener = invocation.getArgument(0);
             return null;
         }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any());
+        doAnswer(invocation -> {
+            mRegisteredVirtualDisplayListener = invocation.getArgument(0);
+            return null;
+        }).when(mVirtualDeviceManagerInternalMock).registerVirtualDisplayListener(any());
+        doAnswer(invocation -> {
+            mRegisteredAppsOnVirtualDeviceListener = invocation.getArgument(0);
+            return null;
+        }).when(mVirtualDeviceManagerInternalMock).registerAppsOnVirtualDeviceListener(any());
 
         setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
         setUserSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, 1);
@@ -202,6 +219,7 @@
 
         addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock);
         addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock);
+        addLocalServiceMock(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternalMock);
 
         mTestLooper.startAutoDispatch();
     }
@@ -1167,6 +1185,64 @@
     }
 
     @Test
+    public void vibrate_withVitualDisplayChange_ignoreVibrationFromVirtualDisplay()
+            throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+        mRegisteredVirtualDisplayListener.onVirtualDisplayCreated(VIRTUAL_DISPLAY_ID);
+
+        vibrateWithDisplay(service,
+                VIRTUAL_DISPLAY_ID,
+                CombinedVibration.startParallel()
+                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
+                        .combine(),
+                HAPTIC_FEEDBACK_ATTRS);
+
+        // Haptic feedback ignored when it's from a virtual display.
+        assertFalse(waitUntil(s -> s.isVibrating(1), service, /* timeout= */ 50));
+
+        mRegisteredVirtualDisplayListener.onVirtualDisplayRemoved(VIRTUAL_DISPLAY_ID);
+        vibrateWithDisplay(service,
+                VIRTUAL_DISPLAY_ID,
+                CombinedVibration.startParallel()
+                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
+                        .combine(),
+                HAPTIC_FEEDBACK_ATTRS);
+        // Haptic feedback played normally when the virtual display is removed.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+    }
+
+    @Test
+    public void vibrate_withAppsOnVitualDisplayChange_ignoreVibrationFromVirtualDisplay()
+            throws Exception {
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(
+                new ArraySet<>(Arrays.asList(UID)));
+        vibrateWithDisplay(service,
+                Display.INVALID_DISPLAY,
+                CombinedVibration.startParallel()
+                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
+                        .combine(),
+                HAPTIC_FEEDBACK_ATTRS);
+
+        // Haptic feedback ignored when it's from an app running virtual display.
+        assertFalse(waitUntil(s -> s.isVibrating(1), service, /* timeout= */ 50));
+
+        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(new ArraySet<>());
+        vibrateWithDisplay(service,
+                Display.INVALID_DISPLAY,
+                CombinedVibration.startParallel()
+                        .addVibrator(1, VibrationEffect.createOneShot(1000, 100))
+                        .combine(),
+                HAPTIC_FEEDBACK_ATTRS);
+        // Haptic feedback played normally when the same app no long runs on a virtual display.
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+    }
+
+    @Test
     public void cancelVibrate_withoutUsageFilter_stopsVibrating() throws Exception {
         mockVibrators(1);
         VibratorManagerService service = createSystemReadyService();
@@ -1240,6 +1316,24 @@
     }
 
     @Test
+    public void onExternalVibration_ignoreVibrationFromVirtualDevices() throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        createSystemReadyService();
+
+        IBinder binderToken = mock(IBinder.class);
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS,
+                mock(IExternalVibrationController.class), binderToken);
+        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        assertNotEquals(IExternalVibratorService.SCALE_MUTE, scale);
+
+        mRegisteredAppsOnVirtualDeviceListener.onAppsOnAnyVirtualDeviceChanged(
+                new ArraySet<>(Arrays.asList(UID)));
+        scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        assertEquals(IExternalVibratorService.SCALE_MUTE, scale);
+    }
+
+    @Test
     public void onExternalVibration_setsExternalControl() throws Exception {
         mockVibrators(1);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
@@ -1820,7 +1914,8 @@
     private void vibrateAndWaitUntilFinished(VibratorManagerService service,
             CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException {
         Vibration vib =
-                service.vibrateInternal(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
+                service.vibrateInternal(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME, effect, attrs,
+                        "some reason", service);
         if (vib != null) {
             vib.waitForEnd();
         }
@@ -1833,7 +1928,12 @@
 
     private void vibrate(VibratorManagerService service, CombinedVibration effect,
             VibrationAttributes attrs) {
-        service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service);
+        vibrateWithDisplay(service, Display.DEFAULT_DISPLAY, effect, attrs);
+    }
+
+    private void vibrateWithDisplay(VibratorManagerService service, int displayId,
+            CombinedVibration effect, VibrationAttributes attrs) {
+        service.vibrate(UID, displayId, PACKAGE_NAME, effect, attrs, "some reason", service);
     }
 
     private boolean waitUntil(Predicate<VibratorManagerService> predicate,
diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
index e0f3f03..421ceb7 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
@@ -50,6 +50,7 @@
 public class VibratorManagerServicePermissionTest {
 
     private static final String PACKAGE_NAME = "com.android.framework.permission.tests";
+    private static final int DISPLAY_ID = 1;
     private static final CombinedVibration EFFECT =
             CombinedVibration.createParallel(
                     VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE));
@@ -106,7 +107,8 @@
     @Test
     public void testVibrateWithoutPermissionFails() throws RemoteException {
         expectSecurityException("VIBRATE");
-        mVibratorService.vibrate(Process.myUid(), PACKAGE_NAME, EFFECT, ATTRS, "testVibrate",
+        mVibratorService.vibrate(Process.myUid(), DISPLAY_ID, PACKAGE_NAME, EFFECT, ATTRS,
+                "testVibrate",
                 new Binder());
     }
 
@@ -115,7 +117,8 @@
             throws RemoteException {
         getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
                 Manifest.permission.VIBRATE);
-        mVibratorService.vibrate(Process.myUid(), PACKAGE_NAME, EFFECT, ATTRS, "testVibrate",
+        mVibratorService.vibrate(Process.myUid(), DISPLAY_ID, PACKAGE_NAME, EFFECT, ATTRS,
+                "testVibrate",
                 new Binder());
     }
 
@@ -124,7 +127,8 @@
         expectSecurityException("UPDATE_APP_OPS_STATS");
         getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
                 Manifest.permission.VIBRATE);
-        mVibratorService.vibrate(Process.SYSTEM_UID, "android", EFFECT, ATTRS, "testVibrate",
+        mVibratorService.vibrate(Process.SYSTEM_UID, DISPLAY_ID, "android", EFFECT, ATTRS,
+                "testVibrate",
                 new Binder());
     }
 
@@ -133,7 +137,8 @@
         getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
                 Manifest.permission.VIBRATE,
                 Manifest.permission.UPDATE_APP_OPS_STATS);
-        mVibratorService.vibrate(Process.SYSTEM_UID, "android", EFFECT, ATTRS, "testVibrate",
+        mVibratorService.vibrate(Process.SYSTEM_UID, DISPLAY_ID, "android", EFFECT, ATTRS,
+                "testVibrate",
                 new Binder());
     }