diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 4323bf8af5fa119a632269a8e22fbd80a68aec2a..f0c87a138a982b4e7d1fa57355d3556f6a4cc5d4 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -554,7 +554,8 @@ public final class DisplayManager {
             EVENT_FLAG_DISPLAY_CHANGED,
             EVENT_FLAG_DISPLAY_REMOVED,
             EVENT_FLAG_DISPLAY_BRIGHTNESS,
-            EVENT_FLAG_HDR_SDR_RATIO_CHANGED
+            EVENT_FLAG_HDR_SDR_RATIO_CHANGED,
+            EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventsMask {}
@@ -610,6 +611,13 @@ public final class DisplayManager {
      */
     public static final long EVENT_FLAG_HDR_SDR_RATIO_CHANGED = 1L << 4;
 
+    /**
+     * Event flag to register for a display's connection changed.
+     *
+     * @hide
+     */
+    public static final long EVENT_FLAG_DISPLAY_CONNECTION_CHANGED = 1L << 5;
+
     /** @hide */
     public DisplayManager(Context context) {
         mContext = context;
@@ -890,6 +898,25 @@ public final class DisplayManager {
         return mGlobal.getWifiDisplayStatus();
     }
 
+    /**
+     * Enable a connected display that is currently disabled.
+     * @hide
+     */
+    @RequiresPermission("android.permission.MANAGE_DISPLAYS")
+    public void enableConnectedDisplay(int displayId) {
+        mGlobal.enableConnectedDisplay(displayId);
+    }
+
+
+    /**
+     * Disable a connected display that is currently enabled.
+     * @hide
+     */
+    @RequiresPermission("android.permission.MANAGE_DISPLAYS")
+    public void disableConnectedDisplay(int displayId) {
+        mGlobal.disableConnectedDisplay(displayId);
+    }
+
     /**
      * Set the level of color saturation to apply to the display.
      * @param level The amount of saturation to apply, between 0 and 1 inclusive.
@@ -1633,6 +1660,24 @@ public final class DisplayManager {
          * @param displayId The id of the logical display that changed.
          */
         void onDisplayChanged(int displayId);
+
+        /**
+         * Called when a display is connected, but not necessarily used.
+         *
+         * A display is always connected before being added.
+         * @hide
+         */
+        default void onDisplayConnected(int displayId) { }
+
+        /**
+         * Called when a display is disconnected.
+         *
+         * If a display was added, a display is only disconnected after it has been removed. Note,
+         * however, that the display may have been disconnected by the time the removed event is
+         * received by the listener.
+         * @hide
+         */
+        default void onDisplayDisconnected(int displayId) { }
     }
 
     /**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 3be82bc2b4f9b98365d8d96c44f760e45b7ee2d3..6d6085b471d9bb2093d86d3aa4181fd62b5a2ab8 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -90,6 +90,8 @@ public final class DisplayManagerGlobal {
             EVENT_DISPLAY_REMOVED,
             EVENT_DISPLAY_BRIGHTNESS_CHANGED,
             EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED,
+            EVENT_DISPLAY_CONNECTED,
+            EVENT_DISPLAY_DISCONNECTED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DisplayEvent {}
@@ -99,6 +101,8 @@ public final class DisplayManagerGlobal {
     public static final int EVENT_DISPLAY_REMOVED = 3;
     public static final int EVENT_DISPLAY_BRIGHTNESS_CHANGED = 4;
     public static final int EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED = 5;
+    public static final int EVENT_DISPLAY_CONNECTED = 6;
+    public static final int EVENT_DISPLAY_DISCONNECTED = 7;
 
     @UnsupportedAppUsage
     private static DisplayManagerGlobal sInstance;
@@ -414,6 +418,9 @@ public final class DisplayManagerGlobal {
 
     private void updateCallbackIfNeededLocked() {
         int mask = calculateEventsMaskLocked();
+        if (DEBUG) {
+            Log.d(TAG, "Mask for listener: " + mask);
+        }
         if (mask != mRegisteredEventsMask) {
             try {
                 mDm.registerCallbackWithEventMask(mCallback, mask);
@@ -459,6 +466,33 @@ public final class DisplayManagerGlobal {
         }
     }
 
+    /**
+     * Enable a connected display that is currently disabled.
+     * @hide
+     */
+    @RequiresPermission("android.permission.MANAGE_DISPLAYS")
+    public void enableConnectedDisplay(int displayId) {
+        try {
+            mDm.enableConnectedDisplay(displayId);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Error trying to enable external display", ex);
+        }
+    }
+
+
+    /**
+     * Disable a connected display that is currently enabled.
+     * @hide
+     */
+    @RequiresPermission("android.permission.MANAGE_DISPLAYS")
+    public void disableConnectedDisplay(int displayId) {
+        try {
+            mDm.disableConnectedDisplay(displayId);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Error trying to enable external display", ex);
+        }
+    }
+
     public void startWifiDisplayScan() {
         synchronized (mLock) {
             if (mWifiDisplayScanNestCount++ == 0) {
@@ -1179,6 +1213,16 @@ public final class DisplayManagerGlobal {
                         mListener.onDisplayChanged(msg.arg1);
                     }
                     break;
+                case EVENT_DISPLAY_CONNECTED:
+                    if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
+                        mListener.onDisplayConnected(msg.arg1);
+                    }
+                    break;
+                case EVENT_DISPLAY_DISCONNECTED:
+                    if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
+                        mListener.onDisplayDisconnected(msg.arg1);
+                    }
+                    break;
             }
             if (DEBUG) {
                 Trace.endSection();
@@ -1302,6 +1346,10 @@ public final class DisplayManagerGlobal {
                 return "BRIGHTNESS_CHANGED";
             case EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED:
                 return "HDR_SDR_RATIO_CHANGED";
+            case EVENT_DISPLAY_CONNECTED:
+                return "EVENT_DISPLAY_CONNECTED";
+            case EVENT_DISPLAY_DISCONNECTED:
+                return "EVENT_DISPLAY_DISCONNECTED";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 18edbdbd99b5928cd08bcc5d1a1ff28792b97866..83de4e45cf2fb912b51556135de856a8e7086f5d 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -227,4 +227,12 @@ interface IDisplayManager {
 
     // Query overlay properties of the device
     OverlayProperties getOverlaySupport();
+
+    // Enable a connected display that is disabled.
+    @EnforcePermission("MANAGE_DISPLAYS")
+    void enableConnectedDisplay(int displayId);
+
+    // Disable a connected display that is enabled.
+    @EnforcePermission("MANAGE_DISPLAYS")
+    void disableConnectedDisplay(int displayId);
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c5b9ee9860f8aa285e0108f2c203ce1a1fcc8798..f3194b48abe486702ab573e7e5adf5efdd5ce055 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7706,6 +7706,15 @@
     <permission android:name="android.permission.WRITE_FLAGS"
         android:protectionLevel="signature" />
 
+    <!-- @hide Allows internal applications to manage displays.
+        <p>This means intercept internal signals about displays being (dis-)connected
+        and being able to enable or disable the external displays.
+        <p>Not for use by third-party applications.
+        <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.MANAGE_DISPLAYS"
+        android:protectionLevel="signature" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 323f65f4f066ac7f54490620072c8cdd335e5ecb..bd3b83ec889fe84eec33706e8bb407531d2e3d56 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -846,6 +846,8 @@
     <uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR" />
     <!-- Permission required for CTS test IntentRedirectionTest -->
     <uses-permission android:name="android.permission.QUERY_CLONED_APPS" />
+    <!-- Permission required for adb display commands `enable-display` and `disable-display`. -->
+    <uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
     <!-- Permission required for accessing all content provider mime types -->
     <uses-permission android:name="android.permission.GET_ANY_PROVIDER_TYPE" />
 
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 66a77a36dca92c505ff851a5df183cc93cbb1be1..714ff68c23694ee29733267a25b9c14a653e2761 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -181,6 +181,7 @@ java_library_static {
         "android.hardware.power.stats-V2-java",
         "android.hidl.manager-V1.2-java",
         "cbor-java",
+        "display_flags_lib",
         "icu4j_calendar_astronomer",
         "netd-client",
         "overlayable_policy_aidl-java",
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index fe445a6f24597d21f9d8c646307bd74c26cd6516..b994105de4060356949160eda2c8cdb2eb938580 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -21,6 +21,7 @@ import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
 import static android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT;
 import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.Manifest.permission.MANAGE_DISPLAYS;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
 import static android.hardware.display.DisplayManager.EventsMask;
@@ -44,6 +45,7 @@ import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.ROOT_UID;
 
 import android.Manifest;
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -152,6 +154,7 @@ import com.android.server.UiThread;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.display.DisplayDeviceConfig.SensorData;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.layout.Layout;
 import com.android.server.display.mode.DisplayModeDirector;
 import com.android.server.display.utils.SensorUtils;
@@ -509,6 +512,8 @@ public final class DisplayManagerService extends SystemService {
 
     private final DeviceConfigParameterProvider mConfigParameterProvider;
 
+    private final DisplayManagerFlags mFlags;
+
     /**
      * Applications use {@link android.view.Display#getRefreshRate} and
      * {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate.
@@ -538,12 +543,13 @@ public final class DisplayManagerService extends SystemService {
         super(context);
         mInjector = injector;
         mContext = context;
+        mFlags = injector.getFlags();
         mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper());
         mUiHandler = UiThread.getHandler();
         mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
         mLogicalDisplayMapper = new LogicalDisplayMapper(mContext, mDisplayDeviceRepo,
                 new LogicalDisplayListener(), mSyncRoot, mHandler,
-                new FoldSettingWrapper(mContext.getContentResolver()));
+                new FoldSettingWrapper(mContext.getContentResolver()), mFlags);
         mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
         mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
         Resources resources = mContext.getResources();
@@ -744,6 +750,11 @@ public final class DisplayManagerService extends SystemService {
         return mDisplayDeviceRepo;
     }
 
+    @VisibleForTesting
+    LogicalDisplayMapper getLogicalDisplayMapper() {
+        return mLogicalDisplayMapper;
+    }
+
     @VisibleForTesting
     boolean isMinimalPostProcessingAllowed() {
         synchronized (mSyncRoot) {
@@ -888,8 +899,14 @@ public final class DisplayManagerService extends SystemService {
             mDisplayStates.setValueAt(index, state);
             brightnessPair.brightness = brightnessState;
             brightnessPair.sdrBrightness = sdrBrightnessState;
-            runnable = updateDisplayStateLocked(mLogicalDisplayMapper.getDisplayLocked(displayId)
-                    .getPrimaryDisplayDeviceLocked());
+            // TODO(b/297503094) Preventing disabled display from being turned on should happen
+            // elsewhere.
+            LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
+            if (!display.isEnabledLocked() && state != Display.STATE_OFF) {
+                // If the display is disabled, any request other than turning it off should fail.
+                return;
+            }
+            runnable = updateDisplayStateLocked(display.getPrimaryDisplayDeviceLocked());
             if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
                 Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_POWER,
                         "requestDisplayStateInternal:" + displayId, displayId);
@@ -1796,7 +1813,17 @@ public final class DisplayManagerService extends SystemService {
         adapter.registerLocked();
     }
 
-    private void handleLogicalDisplayAddedLocked(LogicalDisplay display) {
+    @GuardedBy("mSyncRoot")
+    private void handleLogicalDisplayDisconnectedLocked(LogicalDisplay display) {
+        if (!mFlags.isConnectedDisplayManagementEnabled()) {
+            Slog.e(TAG, "DisplayDisconnected shouldn't be received when the flag is off");
+            return;
+        }
+        releaseDisplayAndEmitEvent(display, DisplayManagerGlobal.EVENT_DISPLAY_DISCONNECTED);
+    }
+
+    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+    private void setupLogicalDisplay(LogicalDisplay display) {
         final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
         final int displayId = display.getDisplayIdLocked();
         final boolean isDefault = displayId == Display.DEFAULT_DISPLAY;
@@ -1814,8 +1841,8 @@ public final class DisplayManagerService extends SystemService {
         } else {
             configurePreferredDisplayModeLocked(display);
         }
-        DisplayPowerControllerInterface dpc = addDisplayPowerControllerLocked(display);
 
+        DisplayPowerControllerInterface dpc = addDisplayPowerControllerLocked(display);
         if (dpc != null) {
             final int leadDisplayId = display.getLeadDisplayIdLocked();
             updateDisplayPowerControllerLeaderLocked(dpc, leadDisplayId);
@@ -1840,19 +1867,51 @@ public final class DisplayManagerService extends SystemService {
                 new BrightnessPair(brightnessDefault, brightnessDefault));
 
         DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
+    }
+
+    private void updateLogicalDisplayState(LogicalDisplay display) {
+        Runnable work = updateDisplayStateLocked(display.getPrimaryDisplayDeviceLocked());
+        if (work != null) {
+            work.run();
+        }
+        scheduleTraversalLocked(false);
+    }
+
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    private void handleLogicalDisplayConnectedLocked(LogicalDisplay display) {
+        if (!mFlags.isConnectedDisplayManagementEnabled()) {
+            Slog.e(TAG, "DisplayConnected shouldn't be received when the flag is off");
+            return;
+        }
+
+        setupLogicalDisplay(display);
+
+        // TODO(b/292196201) Remove when the display can be disabled before DPC is created.
+        if (display.getDisplayInfoLocked().type == Display.TYPE_EXTERNAL) {
+            display.setEnabledLocked(false);
+        }
+
+        sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED);
+
+        updateLogicalDisplayState(display);
+    }
+
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    private void handleLogicalDisplayAddedLocked(LogicalDisplay display) {
+        final int displayId = display.getDisplayIdLocked();
+        final boolean isDefault = displayId == Display.DEFAULT_DISPLAY;
+        if (!mFlags.isConnectedDisplayManagementEnabled()) {
+            setupLogicalDisplay(display);
+        }
 
         // Wake up waitForDefaultDisplay.
         if (isDefault) {
             mSyncRoot.notifyAll();
         }
 
-        sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
+        sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
 
-        Runnable work = updateDisplayStateLocked(device);
-        if (work != null) {
-            work.run();
-        }
-        scheduleTraversalLocked(false);
+        updateLogicalDisplayState(display);
     }
 
     private void handleLogicalDisplayChangedLocked(@NonNull LogicalDisplay display) {
@@ -1865,7 +1924,13 @@ public final class DisplayManagerService extends SystemService {
         // We don't bother invalidating the display info caches here because any changes to the
         // display info will trigger a cache invalidation inside of LogicalDisplay before we hit
         // this point.
-        sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+        sendDisplayEventIfEnabledLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+
+        applyDisplayChangedLocked(display);
+    }
+
+    private void applyDisplayChangedLocked(@NonNull LogicalDisplay display) {
+        final int displayId = display.getDisplayIdLocked();
         scheduleTraversalLocked(false);
         mPersistentDataStore.saveIfNeeded();
 
@@ -1920,7 +1985,28 @@ public final class DisplayManagerService extends SystemService {
     }
 
     private void handleLogicalDisplayRemovedLocked(@NonNull LogicalDisplay display) {
+        // With display management, the display is removed when disabled, and it might still exist.
+        // Resources must only be released when the disconnected signal is received.
+        if (mFlags.isConnectedDisplayManagementEnabled()) {
+            if (display.isValidLocked()) {
+                updateViewportPowerStateLocked(display);
+            }
+
+            // Note: This method is only called if the display was enabled before being removed.
+            sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
+
+            if (display.isValidLocked()) {
+                applyDisplayChangedLocked(display);
+            }
+            return;
+        }
+
+        releaseDisplayAndEmitEvent(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
+    }
+
+    private void releaseDisplayAndEmitEvent(LogicalDisplay display, int event) {
         final int displayId = display.getDisplayIdLocked();
+
         final DisplayPowerControllerInterface dpc =
                 mDisplayPowerControllers.removeReturnOld(displayId);
         if (dpc != null) {
@@ -1930,12 +2016,13 @@ public final class DisplayManagerService extends SystemService {
         mDisplayStates.delete(displayId);
         mDisplayBrightnesses.delete(displayId);
         DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
-        sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
+
+        sendDisplayEventLocked(display, event);
         scheduleTraversalLocked(false);
 
         if (mDisplayWindowPolicyControllers.contains(displayId)) {
-            final IVirtualDevice virtualDevice = mDisplayWindowPolicyControllers.removeReturnOld(
-                    displayId).first;
+            final IVirtualDevice virtualDevice =
+                    mDisplayWindowPolicyControllers.removeReturnOld(displayId).first;
             if (virtualDevice != null) {
                 mHandler.post(() -> {
                     getLocalService(VirtualDeviceManagerInternal.class)
@@ -1956,7 +2043,8 @@ public final class DisplayManagerService extends SystemService {
     }
 
     private void handleLogicalDisplayHdrSdrRatioChangedLocked(@NonNull LogicalDisplay display) {
-        sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED);
+        sendDisplayEventIfEnabledLocked(display,
+                DisplayManagerGlobal.EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED);
     }
 
     private void notifyDefaultDisplayDeviceUpdated(LogicalDisplay display) {
@@ -2812,15 +2900,21 @@ public final class DisplayManagerService extends SystemService {
         }
     }
 
-    private void sendDisplayEventLocked(@NonNull LogicalDisplay display, @DisplayEvent int event) {
+    // Send a display event if the display is enabled
+    private void sendDisplayEventIfEnabledLocked(@NonNull LogicalDisplay display,
+                                                 @DisplayEvent int event) {
         // Only send updates outside of DisplayManagerService for enabled displays
         if (display.isEnabledLocked()) {
-            int displayId = display.getDisplayIdLocked();
-            Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event);
-            mHandler.sendMessage(msg);
+            sendDisplayEventLocked(display, event);
         }
     }
 
+    private void sendDisplayEventLocked(@NonNull LogicalDisplay display, @DisplayEvent int event) {
+        int displayId = display.getDisplayIdLocked();
+        Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event);
+        mHandler.sendMessage(msg);
+    }
+
     private void sendDisplayGroupEvent(int groupId, int event) {
         Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_GROUP_EVENT, groupId, event);
         mHandler.sendMessage(msg);
@@ -3051,6 +3145,12 @@ public final class DisplayManagerService extends SystemService {
                 && mode.getRefreshRate() > 0.0f;
     }
 
+    void enableConnectedDisplay(int displayId, boolean enabled) {
+        synchronized (mSyncRoot) {
+            mLogicalDisplayMapper.setDisplayEnabledLocked(displayId, enabled);
+        }
+    }
+
     /**
      * This is the object that everything in the display manager locks on.
      * We make it an inner class within the {@link DisplayManagerService} to so that it is
@@ -3068,7 +3168,8 @@ public final class DisplayManagerService extends SystemService {
         }
 
         LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
-                Handler handler, DisplayAdapter.Listener displayAdapterListener) {
+                                                   Handler handler,
+                                                   DisplayAdapter.Listener displayAdapterListener) {
             return new LocalDisplayAdapter(syncRoot, context, handler, displayAdapterListener);
         }
 
@@ -3098,6 +3199,10 @@ public final class DisplayManagerService extends SystemService {
             IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE);
             return  IMediaProjectionManager.Stub.asInterface(b);
         }
+
+        DisplayManagerFlags getFlags() {
+            return new DisplayManagerFlags();
+        }
     }
 
     @VisibleForTesting
@@ -3167,7 +3272,8 @@ public final class DisplayManagerService extends SystemService {
 
     private void handleBrightnessChange(LogicalDisplay display) {
         synchronized (mSyncRoot) {
-            sendDisplayEventLocked(display, DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED);
+            sendDisplayEventIfEnabledLocked(display,
+                    DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED);
         }
     }
 
@@ -3261,6 +3367,8 @@ public final class DisplayManagerService extends SystemService {
     }
 
     private final class LogicalDisplayListener implements LogicalDisplayMapper.Listener {
+
+        @GuardedBy("mSyncRoot")
         @Override
         public void onLogicalDisplayEventLocked(LogicalDisplay display, int event) {
             switch (event) {
@@ -3291,6 +3399,14 @@ public final class DisplayManagerService extends SystemService {
                 case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED:
                     handleLogicalDisplayHdrSdrRatioChangedLocked(display);
                     break;
+
+                case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CONNECTED:
+                    handleLogicalDisplayConnectedLocked(display);
+                    break;
+
+                case LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_DISCONNECTED:
+                    handleLogicalDisplayDisconnectedLocked(display);
+                    break;
             }
         }
 
@@ -3367,6 +3483,10 @@ public final class DisplayManagerService extends SystemService {
                     return (mask & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0;
                 case DisplayManagerGlobal.EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED:
                     return (mask & DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED) != 0;
+                case DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED:
+                    // fallthrough
+                case DisplayManagerGlobal.EVENT_DISPLAY_DISCONNECTED:
+                    return (mask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0;
                 default:
                     // This should never happen.
                     Slog.e(TAG, "Unknown display event " + event);
@@ -3484,6 +3604,7 @@ public final class DisplayManagerService extends SystemService {
         }
 
         @Override // Binder call
+        @SuppressLint("AndroidFrameworkRequiresPermission") // Permission only required sometimes
         public void registerCallbackWithEventMask(IDisplayManagerCallback callback,
                 @EventsMask long eventsMask) {
             if (callback == null) {
@@ -3492,6 +3613,14 @@ public final class DisplayManagerService extends SystemService {
 
             final int callingPid = Binder.getCallingPid();
             final int callingUid = Binder.getCallingUid();
+
+            if (mFlags.isConnectedDisplayManagementEnabled()) {
+                if ((eventsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
+                    mContext.enforceCallingOrSelfPermission(MANAGE_DISPLAYS,
+                            "Permission required to get signals about connection events.");
+                }
+            }
+
             final long token = Binder.clearCallingIdentity();
             try {
                 registerCallbackInternal(callback, callingPid, callingUid, eventsMask);
@@ -3500,7 +3629,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
+        @EnforcePermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
         @Override // Binder call
         public void startWifiDisplayScan() {
             startWifiDisplayScan_enforcePermission();
@@ -3514,7 +3643,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
+        @EnforcePermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
         @Override // Binder call
         public void stopWifiDisplayScan() {
             stopWifiDisplayScan_enforcePermission();
@@ -3591,7 +3720,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
+        @EnforcePermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
         @Override // Binder call
         public void pauseWifiDisplay() {
             pauseWifiDisplay_enforcePermission();
@@ -3604,7 +3733,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
+        @EnforcePermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
         @Override // Binder call
         public void resumeWifiDisplay() {
             resumeWifiDisplay_enforcePermission();
@@ -3630,7 +3759,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+        @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
         @Override // Binder call
         public void setUserDisabledHdrTypes(int[] userDisabledFormats) {
             setUserDisabledHdrTypes_enforcePermission();
@@ -3656,7 +3785,7 @@ public final class DisplayManagerService extends SystemService {
             DisplayControl.overrideHdrTypes(displayToken, modes);
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+        @EnforcePermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
         @Override // Binder call
         public void setAreUserDisabledHdrTypesAllowed(boolean areUserDisabledHdrTypesAllowed) {
             setAreUserDisabledHdrTypesAllowed_enforcePermission();
@@ -3682,7 +3811,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE)
+        @EnforcePermission(android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE)
         @Override // Binder call
         public void requestColorMode(int displayId, int colorMode) {
             requestColorMode_enforcePermission();
@@ -3762,7 +3891,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.BRIGHTNESS_SLIDER_USAGE)
+        @EnforcePermission(android.Manifest.permission.BRIGHTNESS_SLIDER_USAGE)
         @Override // Binder call
         public ParceledListSlice<BrightnessChangeEvent> getBrightnessEvents(String callingPackage) {
             getBrightnessEvents_enforcePermission();
@@ -3794,7 +3923,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_AMBIENT_LIGHT_STATS)
+        @EnforcePermission(android.Manifest.permission.ACCESS_AMBIENT_LIGHT_STATS)
         @Override // Binder call
         public ParceledListSlice<AmbientBrightnessDayStats> getAmbientBrightnessStats() {
             getAmbientBrightnessStats_enforcePermission();
@@ -3811,7 +3940,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
+        @EnforcePermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public void setBrightnessConfigurationForUser(
                 BrightnessConfiguration c, @UserIdInt int userId, String packageName) {
@@ -3840,7 +3969,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
+        @EnforcePermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public void setBrightnessConfigurationForDisplay(BrightnessConfiguration c,
                 String uniqueId, int userId, String packageName) {
@@ -3859,7 +3988,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
+        @EnforcePermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public BrightnessConfiguration getBrightnessConfigurationForDisplay(String uniqueId,
                 int userId) {
@@ -3907,7 +4036,7 @@ public final class DisplayManagerService extends SystemService {
 
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
+        @EnforcePermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public BrightnessConfiguration getDefaultBrightnessConfiguration() {
             getDefaultBrightnessConfiguration_enforcePermission();
@@ -3922,7 +4051,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+        @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
         @Override
         public BrightnessInfo getBrightnessInfo(int displayId) {
             getBrightnessInfo_enforcePermission();
@@ -3953,7 +4082,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+        @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public void setTemporaryBrightness(int displayId, float brightness) {
             setTemporaryBrightness_enforcePermission();
@@ -3968,7 +4097,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+        @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public void setBrightness(int displayId, float brightness) {
             setBrightness_enforcePermission();
@@ -4010,7 +4139,7 @@ public final class DisplayManagerService extends SystemService {
             return brightness;
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
+        @EnforcePermission(android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS)
         @Override // Binder call
         public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
             setTemporaryAutoBrightnessAdjustment_enforcePermission();
@@ -4029,8 +4158,8 @@ public final class DisplayManagerService extends SystemService {
         public void onShellCommand(FileDescriptor in, FileDescriptor out,
                 FileDescriptor err, String[] args, ShellCallback callback,
                 ResultReceiver resultReceiver) {
-            new DisplayManagerShellCommand(DisplayManagerService.this).exec(this, in, out, err,
-                    args, callback, resultReceiver);
+            new DisplayManagerShellCommand(DisplayManagerService.this, mFlags).exec(this, in, out,
+                    err, args, callback, resultReceiver);
         }
 
         @Override // Binder call
@@ -4053,7 +4182,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE)
+        @EnforcePermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE)
         @Override // Binder call
         public void setUserPreferredDisplayMode(int displayId, Display.Mode mode) {
             setUserPreferredDisplayMode_enforcePermission();
@@ -4141,7 +4270,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS)
+        @EnforcePermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS)
         @Override // Binder call
         public void setShouldAlwaysRespectAppRequestedMode(boolean enabled) {
             setShouldAlwaysRespectAppRequestedMode_enforcePermission();
@@ -4153,7 +4282,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS)
+        @EnforcePermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS)
         @Override // Binder call
         public boolean shouldAlwaysRespectAppRequestedMode() {
             shouldAlwaysRespectAppRequestedMode_enforcePermission();
@@ -4165,7 +4294,7 @@ public final class DisplayManagerService extends SystemService {
             }
         }
 
-        @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE)
+        @EnforcePermission(android.Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE)
         @Override // Binder call
         public void setRefreshRateSwitchingType(int newValue) {
             setRefreshRateSwitchingType_enforcePermission();
@@ -4217,6 +4346,28 @@ public final class DisplayManagerService extends SystemService {
                 Binder.restoreCallingIdentity(token);
             }
         }
+
+        @EnforcePermission(MANAGE_DISPLAYS)
+        public void enableConnectedDisplay(int displayId) {
+            enableConnectedDisplay_enforcePermission();
+            if (!mFlags.isConnectedDisplayManagementEnabled()) {
+                Slog.w(TAG, "External display management is not enabled on your device: "
+                                    + "cannot enable display.");
+                return;
+            }
+            DisplayManagerService.this.enableConnectedDisplay(displayId, true);
+        }
+
+        @EnforcePermission(MANAGE_DISPLAYS)
+        public void disableConnectedDisplay(int displayId) {
+            disableConnectedDisplay_enforcePermission();
+            if (!mFlags.isConnectedDisplayManagementEnabled()) {
+                Slog.w(TAG, "External display management is not enabled on your device: "
+                                    + "cannot disable display.");
+                return;
+            }
+            DisplayManagerService.this.enableConnectedDisplay(displayId, false);
+        }
     }
 
     private static boolean isValidBrightness(float brightness) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index a4f49549c7eb70eaf40dabd953d5d36a8fbed1d7..9b022d8b666299bf616d6e6c763370ce261ce8b2 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -23,6 +23,8 @@ import android.os.ShellCommand;
 import android.util.Slog;
 import android.view.Display;
 
+import com.android.server.display.feature.DisplayManagerFlags;
+
 import java.io.PrintWriter;
 import java.util.Arrays;
 
@@ -30,9 +32,11 @@ class DisplayManagerShellCommand extends ShellCommand {
     private static final String TAG = "DisplayManagerShellCommand";
 
     private final DisplayManagerService mService;
+    private final DisplayManagerFlags mFlags;
 
-    DisplayManagerShellCommand(DisplayManagerService service) {
+    DisplayManagerShellCommand(DisplayManagerService service, DisplayManagerFlags flags) {
         mService = service;
+        mFlags = flags;
     }
 
     @Override
@@ -82,6 +86,10 @@ class DisplayManagerShellCommand extends ShellCommand {
                 return setDockedAndIdle();
             case "undock":
                 return unsetDockedAndIdle();
+            case "enable-display":
+                return setDisplayEnabled(true);
+            case "disable-display":
+                return setDisplayEnabled(false);
             default:
                 return handleDefaultCommands(cmd);
         }
@@ -142,6 +150,12 @@ class DisplayManagerShellCommand extends ShellCommand {
         pw.println("    Sets brightness to docked + idle screen brightness mode");
         pw.println("  undock");
         pw.println("    Sets brightness to active (normal) screen brightness mode");
+        if (mFlags.isConnectedDisplayManagementEnabled()) {
+            pw.println("  enable-display DISPLAY_ID");
+            pw.println("    Enable the DISPLAY_ID. Only possible if this is a connected display.");
+            pw.println("  disable-display DISPLAY_ID");
+            pw.println("    Disable the DISPLAY_ID. Only possible if this is a connected display.");
+        }
         pw.println();
         Intent.printIntentArgsHelp(pw , "");
     }
@@ -423,4 +437,26 @@ class DisplayManagerShellCommand extends ShellCommand {
         mService.setDockedAndIdleEnabled(false, Display.DEFAULT_DISPLAY);
         return 0;
     }
+
+    private int setDisplayEnabled(boolean enable) {
+        if (!mFlags.isConnectedDisplayManagementEnabled()) {
+            getErrPrintWriter()
+                    .println("Error: external display management is not available on this device.");
+            return 1;
+        }
+        final String displayIdText = getNextArg();
+        if (displayIdText == null) {
+            getErrPrintWriter().println("Error: no displayId specified");
+            return 1;
+        }
+        final int displayId;
+        try {
+            displayId = Integer.parseInt(displayIdText);
+        } catch (NumberFormatException e) {
+            getErrPrintWriter().println("Error: invalid displayId: '" + displayIdText + "'");
+            return 1;
+        }
+        mService.enableConnectedDisplay(displayId, enable);
+        return 0;
+    }
 }
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index b00b7a1f0e2aaae587f5d8778f16140c92aef583..924b1b3c66abefd96b3b2e38edb59d325ef8858e 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -83,14 +83,14 @@ final class LocalDisplayAdapter extends DisplayAdapter {
     private Context mOverlayContext;
 
     // Called with SyncRoot lock held.
-    public LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
-            Context context, Handler handler, Listener listener) {
+    LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context,
+            Handler handler, Listener listener) {
         this(syncRoot, context, handler, listener, new Injector());
     }
 
     @VisibleForTesting
-    LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
-            Context context, Handler handler, Listener listener, Injector injector) {
+    LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context, Handler handler,
+            Listener listener, Injector injector) {
         super(syncRoot, context, handler, listener, TAG);
         mInjector = injector;
         mSurfaceControlProxy = mInjector.getSurfaceControlProxy();
@@ -231,7 +231,6 @@ final class LocalDisplayAdapter extends DisplayAdapter {
         private SurfaceControl.DisplayMode mActiveSfDisplayMode;
         // The active display vsync period in SurfaceFlinger
         private float mActiveRenderFrameRate;
-
         private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides =
                 new DisplayEventReceiver.FrameRateOverride[0];
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 9c271ff54d6b318b338b4f43c90a115ee4a0a1f0..0405ebe8b5d2e015e9b0daebd50997d8aec197c5 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -193,7 +193,7 @@ final class LogicalDisplay {
     private SparseArray<SurfaceControl.RefreshRateRange> mThermalRefreshRateThrottling =
             new SparseArray<>();
 
-    public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
+    LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
         mDisplayId = displayId;
         mLayerStack = layerStack;
         mPrimaryDisplayDevice = primaryDisplayDevice;
@@ -872,7 +872,10 @@ final class LogicalDisplay {
      * @param enabled True if enabled, false otherwise.
      */
     public void setEnabledLocked(boolean enabled) {
-        mIsEnabled = enabled;
+        if (enabled != mIsEnabled) {
+            mDirty = true;
+            mIsEnabled = enabled;
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 26f8029cf5ac20a7d00a0509853db5a810ed2292..cbe0fc7e713bac75ae5923afd16f7e57e60a7fe9 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -40,6 +40,7 @@ import android.view.DisplayAddress;
 import android.view.DisplayInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.layout.DisplayIdProducer;
 import com.android.server.display.layout.Layout;
 import com.android.server.utils.FoldSettingWrapper;
@@ -71,6 +72,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
     public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 5;
     public static final int LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION = 6;
     public static final int LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED = 7;
+    public static final int LOGICAL_DISPLAY_EVENT_CONNECTED = 8;
+    public static final int LOGICAL_DISPLAY_EVENT_DISCONNECTED = 9;
 
     public static final int DISPLAY_GROUP_EVENT_ADDED = 1;
     public static final int DISPLAY_GROUP_EVENT_CHANGED = 2;
@@ -124,6 +127,9 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
     private final SparseArray<LogicalDisplay> mLogicalDisplays =
             new SparseArray<LogicalDisplay>();
 
+    // Cache whether or not the display was enabled on the last update.
+    private final SparseBooleanArray mDisplaysEnabledCache = new SparseBooleanArray();
+
     /** Map of all display groups indexed by display group id. */
     private final SparseArray<DisplayGroup> mDisplayGroups = new SparseArray<>();
 
@@ -188,19 +194,21 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
     private int mDeviceStateToBeAppliedAfterBoot = DeviceStateManager.INVALID_DEVICE_STATE;
     private boolean mBootCompleted = false;
     private boolean mInteractive;
+    private final DisplayManagerFlags mFlags;
 
     LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
             @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
-            @NonNull Handler handler, FoldSettingWrapper foldSettingWrapper) {
-        this(context, repo, listener, syncRoot, handler,
-                new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY
-                        : sNextNonDefaultDisplayId++), foldSettingWrapper);
+            @NonNull Handler handler, FoldSettingWrapper foldSettingWrapper,
+            DisplayManagerFlags flags) {
+        this(context, repo, listener, syncRoot, handler, new DeviceStateToLayoutMap(
+                (isDefault) -> isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++),
+                foldSettingWrapper, flags);
     }
 
     LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
             @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
             @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap,
-            FoldSettingWrapper foldSettingWrapper) {
+            FoldSettingWrapper foldSettingWrapper, DisplayManagerFlags flags) {
         mSyncRoot = syncRoot;
         mPowerManager = context.getSystemService(PowerManager.class);
         mInteractive = mPowerManager.isInteractive();
@@ -217,6 +225,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
                 com.android.internal.R.array.config_deviceStatesOnWhichToSleep));
         mDisplayDeviceRepo.addListener(this);
         mDeviceStateToLayoutMap = deviceStateToLayoutMap;
+        mFlags = flags;
     }
 
     @Override
@@ -664,10 +673,21 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
         }
     }
 
-    private void updateLogicalDisplaysLocked() {
+    @VisibleForTesting
+    void updateLogicalDisplays() {
+        synchronized (mSyncRoot) {
+            updateLogicalDisplaysLocked();
+        }
+    }
+
+    void updateLogicalDisplaysLocked() {
         updateLogicalDisplaysLocked(DisplayDeviceInfo.DIFF_EVERYTHING);
     }
 
+    private void updateLogicalDisplaysLocked(int diff) {
+        updateLogicalDisplaysLocked(diff, /* isSecondLoop= */ false);
+    }
+
     /**
      * Updates the rest of the display system once all the changes are applied for display
      * devices and logical displays. The includes releasing invalid/empty LogicalDisplays,
@@ -676,8 +696,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
      *
      * @param diff The DisplayDeviceInfo.DIFF_* of what actually changed to enable finer-grained
      *             display update listeners
+     * @param isSecondLoop If true, this is the second time this is called for the same change.
      */
-    private void updateLogicalDisplaysLocked(int diff) {
+    private void updateLogicalDisplaysLocked(int diff, boolean isSecondLoop) {
+        boolean reloop = false;
         // Go through all the displays and figure out if they need to be updated.
         // Loops in reverse so that displays can be removed during the loop without affecting the
         // rest of the loop.
@@ -694,11 +716,11 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
             final DisplayInfo newDisplayInfo = display.getDisplayInfoLocked();
             final int updateState = mUpdatedLogicalDisplays.get(displayId, UPDATE_STATE_NEW);
             final boolean wasPreviouslyUpdated = updateState != UPDATE_STATE_NEW;
+            final boolean wasPreviouslyEnabled = mDisplaysEnabledCache.get(displayId);
+            final boolean isCurrentlyEnabled = display.isEnabledLocked();
 
             // The display is no longer valid and needs to be removed.
             if (!display.isValidLocked()) {
-                mUpdatedLogicalDisplays.delete(displayId);
-
                 // Remove from group
                 final DisplayGroup displayGroup = getDisplayGroupLocked(
                         getDisplayGroupIdFromDisplayIdLocked(displayId));
@@ -709,8 +731,20 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
                 if (wasPreviouslyUpdated) {
                     // The display isn't actually removed from our internal data structures until
                     // after the notification is sent; see {@link #sendUpdatesForDisplaysLocked}.
-                    Slog.i(TAG, "Removing display: " + displayId);
-                    mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_REMOVED);
+                    if (mFlags.isConnectedDisplayManagementEnabled()) {
+                        if (mDisplaysEnabledCache.get(displayId)) {
+                            // We still need to send LOGICAL_DISPLAY_EVENT_DISCONNECTED
+                            reloop = true;
+                            mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_REMOVED);
+                        } else {
+                            mUpdatedLogicalDisplays.delete(displayId);
+                            mLogicalDisplaysToUpdate.put(displayId,
+                                    LOGICAL_DISPLAY_EVENT_DISCONNECTED);
+                        }
+                    } else {
+                        mUpdatedLogicalDisplays.delete(displayId);
+                        mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_REMOVED);
+                    }
                 } else {
                     // This display never left this class, safe to remove without notification
                     mLogicalDisplays.removeAt(i);
@@ -719,14 +753,23 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
 
             // The display is new.
             } else if (!wasPreviouslyUpdated) {
-                Slog.i(TAG, "Adding new display: " + displayId + ": " + newDisplayInfo);
-                mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_ADDED);
-
+                if (mFlags.isConnectedDisplayManagementEnabled()) {
+                    // We still need to send LOGICAL_DISPLAY_EVENT_ADDED
+                    reloop = true;
+                    mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CONNECTED);
+                } else {
+                    mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_ADDED);
+                }
             // Underlying displays device has changed to a different one.
             } else if (!TextUtils.equals(mTempDisplayInfo.uniqueId, newDisplayInfo.uniqueId)) {
                 mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_SWAPPED);
 
             // Something about the display device has changed.
+            } else if (mFlags.isConnectedDisplayManagementEnabled()
+                    && wasPreviouslyEnabled != isCurrentlyEnabled) {
+                int event = isCurrentlyEnabled ? LOGICAL_DISPLAY_EVENT_ADDED :
+                        LOGICAL_DISPLAY_EVENT_REMOVED;
+                mLogicalDisplaysToUpdate.put(displayId, event);
             } else if (wasDirty || !mTempDisplayInfo.equals(newDisplayInfo)) {
                 // If only the hdr/sdr ratio changed, then send just the event for that case
                 if ((diff == DisplayDeviceInfo.DIFF_HDR_SDR_RATIO)) {
@@ -789,9 +832,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION);
         sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_ADDED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REMOVED);
+        if (mFlags.isConnectedDisplayManagementEnabled()) {
+            sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DISCONNECTED);
+        }
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CHANGED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_SWAPPED);
+        if (mFlags.isConnectedDisplayManagementEnabled()) {
+            sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CONNECTED);
+        }
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_ADDED);
         sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED);
         sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_CHANGED);
@@ -799,6 +848,14 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
 
         mLogicalDisplaysToUpdate.clear();
         mDisplayGroupsToUpdate.clear();
+
+        if (reloop) {
+            if (isSecondLoop) {
+                Slog.wtf(TAG, "Trying to loop a third time");
+                return;
+            }
+            updateLogicalDisplaysLocked(diff, /* isSecondLoop= */ true);
+        }
     }
 
     /**
@@ -819,8 +876,22 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
                 Slog.d(TAG, "Sending " + displayEventToString(msg) + " for display=" + id
                         + " with device=" + uniqueId);
             }
+
+            if (mFlags.isConnectedDisplayManagementEnabled()) {
+                if (msg == LOGICAL_DISPLAY_EVENT_ADDED) {
+                    mDisplaysEnabledCache.put(id, true);
+                } else if (msg == LOGICAL_DISPLAY_EVENT_REMOVED) {
+                    mDisplaysEnabledCache.delete(id);
+                }
+            }
+
             mListener.onLogicalDisplayEventLocked(display, msg);
-            if (msg == LOGICAL_DISPLAY_EVENT_REMOVED) {
+
+            if (mFlags.isConnectedDisplayManagementEnabled()) {
+                if (msg == LOGICAL_DISPLAY_EVENT_DISCONNECTED) {
+                    mLogicalDisplays.delete(id);
+                }
+            } else if (msg == LOGICAL_DISPLAY_EVENT_REMOVED) {
                 // We wait until we sent the EVENT_REMOVED event before actually removing the
                 // display.
                 mLogicalDisplays.delete(id);
@@ -1083,7 +1154,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
         return display;
     }
 
-    private void setEnabledLocked(LogicalDisplay display, boolean isEnabled) {
+    @VisibleForTesting
+    void setEnabledLocked(LogicalDisplay display, boolean isEnabled) {
         final int displayId = display.getDisplayIdLocked();
         final DisplayInfo info = display.getDisplayInfoLocked();
 
@@ -1165,10 +1237,30 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
                 return "removed";
             case LOGICAL_DISPLAY_EVENT_HDR_SDR_RATIO_CHANGED:
                 return "hdr_sdr_ratio_changed";
+            case LOGICAL_DISPLAY_EVENT_CONNECTED:
+                return "connected";
+            case LOGICAL_DISPLAY_EVENT_DISCONNECTED:
+                return "disconnected";
         }
         return null;
     }
 
+    void setDisplayEnabledLocked(int displayId, boolean enabled) {
+        LogicalDisplay display = getDisplayLocked(displayId);
+        if (display == null) {
+            Slog.w(TAG, "Cannot find display " + displayId);
+            return;
+        }
+        boolean isEnabled = display.isEnabledLocked();
+        if (isEnabled == enabled) {
+            Slog.w(TAG, "Display is already " + (isEnabled ? "enabled" : "disabled") + ": "
+                    + displayId);
+            return;
+        }
+        setEnabledLocked(display, enabled);
+        updateLogicalDisplaysLocked();
+    }
+
     public interface Listener {
         void onLogicalDisplayEventLocked(LogicalDisplay display, int event);
         void onDisplayGroupEventLocked(int groupId, int event);
diff --git a/services/core/java/com/android/server/display/feature/Android.bp b/services/core/java/com/android/server/display/feature/Android.bp
new file mode 100644
index 0000000000000000000000000000000000000000..27c48eda83c6e165220cd8198d425298806fca8c
--- /dev/null
+++ b/services/core/java/com/android/server/display/feature/Android.bp
@@ -0,0 +1,12 @@
+aconfig_declarations {
+    name: "display_flags",
+    package: "com.android.server.display.feature.flags",
+    srcs: [
+        "*.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "display_flags_lib",
+    aconfig_declarations: "display_flags",
+}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
new file mode 100644
index 0000000000000000000000000000000000000000..78c2e9fe4f639deb851a57ab0e0dba84d7f6b3f7
--- /dev/null
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.feature;
+
+import android.util.Slog;
+
+import com.android.server.display.feature.flags.Flags;
+
+/**
+ * Utility class to read the flags used in the display manager server.
+ */
+public class DisplayManagerFlags {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "DisplayManagerFlags";
+    private static final boolean DEFAULT_IS_CONNECTED_DISPLAY_MANAGEMENT_ENABLED = false;
+    private boolean mIsConnectedDisplayManagementEnabled = false;
+    private boolean mIsConnectedDisplayManagementEnabledSet = false;
+
+    // TODO(b/297159910): Simplify using READ-ONLY flags when available.
+    /** Returns whether connected display management is enabled or not. */
+    public boolean isConnectedDisplayManagementEnabled() {
+        if (mIsConnectedDisplayManagementEnabledSet) {
+            if (DEBUG) {
+                Slog.d(TAG, "isConnectedDisplayManagementEnabled. Recall = "
+                        + mIsConnectedDisplayManagementEnabled);
+            }
+            return mIsConnectedDisplayManagementEnabled;
+        }
+        try {
+            mIsConnectedDisplayManagementEnabled = Flags.enableConnectedDisplayManagement();
+            if (DEBUG) {
+                Slog.d(TAG, "isConnectedDisplayManagementEnabled. Flag value = "
+                        + mIsConnectedDisplayManagementEnabled);
+            }
+        } catch (Throwable ex) {
+            if (DEBUG) {
+                Slog.i(TAG, "isConnectedDisplayManagementEnabled not available: set to "
+                        + DEFAULT_IS_CONNECTED_DISPLAY_MANAGEMENT_ENABLED, ex);
+            }
+            mIsConnectedDisplayManagementEnabled = DEFAULT_IS_CONNECTED_DISPLAY_MANAGEMENT_ENABLED;
+        }
+        mIsConnectedDisplayManagementEnabledSet = true;
+        return mIsConnectedDisplayManagementEnabled;
+    }
+}
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
new file mode 100644
index 0000000000000000000000000000000000000000..4d8c02b690d11b6cc2e0d730c9589c31bf0cb414
--- /dev/null
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.server.display.feature.flags"
+
+# Important: Flags must be accessed through DisplayManagerFlags.
+
+flag {
+    name: "enable_connected_display_management"
+    namespace: "display_manager"
+    description: "Feature flag for Connected Display managment"
+    bug: "280739508"
+}
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
index 4f555d9cc953dd4284e1cdd009406840cf8ed53b..fb14419a13c0ba7fab3d7546c7114b84fa7b3cb1 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -28,6 +28,7 @@ android_test {
 
     static_libs: [
         "androidx.test.ext.junit",
+        "androidx.test.rules",
         "frameworks-base-testutils",
         "junit",
         "junit-params",
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 979676e5f1be0aaf88f7ca58829ba731e6b1d9ca..d099693ffc82dae3cd5060516dd35a91283769fa 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -18,6 +18,7 @@ package com.android.server.display;
 
 import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
 import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
+import static android.Manifest.permission.MANAGE_DISPLAYS;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
@@ -42,7 +43,9 @@ import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -104,11 +107,14 @@ import com.android.server.SystemService;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.display.DisplayManagerService.DeviceStateListener;
 import com.android.server.display.DisplayManagerService.SyncRoot;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.lights.LightsManager;
 import com.android.server.sensors.SensorManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
+import com.google.common.truth.Expect;
+
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
 
@@ -125,6 +131,7 @@ import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.time.Duration;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -133,6 +140,7 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.LongStream;
 
+// TODO(b/297170420) Parameterize the test.
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class DisplayManagerServiceTest {
@@ -146,12 +154,31 @@ public class DisplayManagerServiceTest {
     private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
                     | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
                     | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
-
-    @Rule
+    private static final long STANDARD_AND_CONNECTION_DISPLAY_EVENTS =
+            STANDARD_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
+
+    private static final String EVENT_DISPLAY_ADDED = "EVENT_DISPLAY_ADDED";
+    private static final String EVENT_DISPLAY_REMOVED = "EVENT_DISPLAY_REMOVED";
+    private static final String EVENT_DISPLAY_CHANGED = "EVENT_DISPLAY_CHANGED";
+    private static final String EVENT_DISPLAY_BRIGHTNESS_CHANGED =
+            "EVENT_DISPLAY_BRIGHTNESS_CHANGED";
+    private static final String EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED =
+            "EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED";
+    private static final String EVENT_DISPLAY_CONNECTED = "EVENT_DISPLAY_CONNECTED";
+    private static final String EVENT_DISPLAY_DISCONNECTED = "EVENT_DISPLAY_DISCONNECTED";
+    private static final String DISPLAY_GROUP_EVENT_ADDED = "DISPLAY_GROUP_EVENT_ADDED";
+    private static final String DISPLAY_GROUP_EVENT_REMOVED = "DISPLAY_GROUP_EVENT_REMOVED";
+    private static final String DISPLAY_GROUP_EVENT_CHANGED = "DISPLAY_GROUP_EVENT_CHANGED";
+
+    @Rule(order = 0)
     public TestRule compatChangeRule = new PlatformCompatChangeRule();
+    @Rule(order = 1)
+    public Expect expect = Expect.create();
 
     private Context mContext;
 
+    private Resources mResources;
+
     private int mHdrConversionMode;
 
     private int mPreferredHdrOutputType;
@@ -188,6 +215,11 @@ public class DisplayManagerServiceTest {
             return mMockProjectionService;
         }
 
+        @Override
+        DisplayManagerFlags getFlags() {
+            return mMockFlags;
+        }
+
         @Override
         VirtualDisplayAdapter getVirtualDisplayAdapter(SyncRoot syncRoot, Context context,
                 Handler handler, DisplayAdapter.Listener displayAdapterListener) {
@@ -208,8 +240,8 @@ public class DisplayManagerServiceTest {
         @Override
         LocalDisplayAdapter getLocalDisplayAdapter(SyncRoot syncRoot, Context context,
                 Handler handler, DisplayAdapter.Listener displayAdapterListener) {
-            return new LocalDisplayAdapter(syncRoot, context, handler,
-                    displayAdapterListener, new LocalDisplayAdapter.Injector() {
+            return new LocalDisplayAdapter(syncRoot, context, handler, displayAdapterListener,
+                    new LocalDisplayAdapter.Injector() {
                         @Override
                         public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() {
                             return mSurfaceControlProxy;
@@ -262,10 +294,12 @@ public class DisplayManagerServiceTest {
     @Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
 
     @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
+    @Mock DisplayManagerFlags mMockFlags;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(false);
 
         LocalServices.removeServiceForTest(InputManagerInternal.class);
         LocalServices.addService(InputManagerInternal.class, mMockInputManagerInternal);
@@ -280,6 +314,9 @@ public class DisplayManagerServiceTest {
                 VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal);
         // TODO: b/287945043
         mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+        mResources = Mockito.spy(mContext.getResources());
+        manageDisplaysPermission(/* granted= */ false);
+        when(mContext.getResources()).thenReturn(mResources);
 
         VirtualDeviceManager vdm = new VirtualDeviceManager(mIVirtualDeviceManager, mContext);
         when(mContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
@@ -315,7 +352,7 @@ public class DisplayManagerServiceTest {
         // This is to update the display device config such that DisplayManagerService can ignore
         // the usage of SensorManager, which is available only after the PowerManagerService
         // is ready.
-        resetConfigToIgnoreSensorManager(mContext);
+        resetConfigToIgnoreSensorManager();
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         registerDefaultDisplays(displayManager);
         displayManager.systemReady(false /* safeMode */);
@@ -392,7 +429,7 @@ public class DisplayManagerServiceTest {
         // This is to update the display device config such that DisplayManagerService can ignore
         // the usage of SensorManager, which is available only after the PowerManagerService
         // is ready.
-        resetConfigToIgnoreSensorManager(mContext);
+        resetConfigToIgnoreSensorManager();
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         registerDefaultDisplays(displayManager);
         displayManager.systemReady(false /* safeMode */);
@@ -670,7 +707,7 @@ public class DisplayManagerServiceTest {
 
         Handler handler = displayManager.getDisplayHandler();
         waitForIdleHandler(handler);
-        assertTrue(callback.mDisplayChangedCalled);
+        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_CHANGED);
     }
 
     /**
@@ -696,7 +733,7 @@ public class DisplayManagerServiceTest {
      * Tests that we get a Runtime exception when we cannot initialize the virtual display.
      */
     @Test
-    public void testStartVirtualDisplayWithDefDisplay_NoVirtualDisplayAdapter() throws Exception {
+    public void testStartVirtualDisplayWithDefDisplay_NoVirtualDisplayAdapter() {
         DisplayManagerService displayManager = new DisplayManagerService(mContext,
                 new DisplayManagerService.Injector() {
                     @Override
@@ -1487,7 +1524,7 @@ public class DisplayManagerServiceTest {
      * list is updated.
      */
     @Test
-    public void testShouldNotifyChangeWhenDisplayInfoFrameRateOverrideChanged() throws Exception {
+    public void testShouldNotifyChangeWhenDisplayInfoFrameRateOverrideChanged() {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
         DisplayManagerService.BinderService displayManagerBinderService =
@@ -1504,7 +1541,8 @@ public class DisplayManagerServiceTest {
                 new DisplayEventReceiver.FrameRateOverride[]{
                         new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
                 });
-        assertTrue(callback.mDisplayChangedCalled);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
         callback.clear();
 
         updateFrameRateOverride(displayManager, displayDevice,
@@ -1512,7 +1550,8 @@ public class DisplayManagerServiceTest {
                         new DisplayEventReceiver.FrameRateOverride(myUid, 30f),
                         new DisplayEventReceiver.FrameRateOverride(1234, 30f),
                 });
-        assertFalse(callback.mDisplayChangedCalled);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED);
 
         updateFrameRateOverride(displayManager, displayDevice,
                 new DisplayEventReceiver.FrameRateOverride[]{
@@ -1520,7 +1559,8 @@ public class DisplayManagerServiceTest {
                         new DisplayEventReceiver.FrameRateOverride(1234, 30f),
                         new DisplayEventReceiver.FrameRateOverride(5678, 30f),
                 });
-        assertTrue(callback.mDisplayChangedCalled);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
         callback.clear();
 
         updateFrameRateOverride(displayManager, displayDevice,
@@ -1528,21 +1568,23 @@ public class DisplayManagerServiceTest {
                         new DisplayEventReceiver.FrameRateOverride(1234, 30f),
                         new DisplayEventReceiver.FrameRateOverride(5678, 30f),
                 });
-        assertTrue(callback.mDisplayChangedCalled);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
         callback.clear();
 
         updateFrameRateOverride(displayManager, displayDevice,
                 new DisplayEventReceiver.FrameRateOverride[]{
                         new DisplayEventReceiver.FrameRateOverride(5678, 30f),
                 });
-        assertFalse(callback.mDisplayChangedCalled);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED);
     }
 
     /**
      * Tests that the DisplayInfo is updated correctly with a frame rate override
      */
     @Test
-    public void testDisplayInfoFrameRateOverride() throws Exception {
+    public void testDisplayInfoFrameRateOverride() {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
         DisplayManagerService.BinderService displayManagerBinderService =
@@ -1579,7 +1621,7 @@ public class DisplayManagerServiceTest {
      * DisplayInfo#getRefreshRate
      */
     @Test
-    public void testDisplayInfoNonNativeFrameRateOverride() throws Exception {
+    public void testDisplayInfoNonNativeFrameRateOverride() {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerService.BinderService displayManagerBinderService =
@@ -1627,7 +1669,7 @@ public class DisplayManagerServiceTest {
      */
     @Test
     @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public void testDisplayInfoNonNativeFrameRateOverrideModeCompat() throws Exception {
+    public void testDisplayInfoNonNativeFrameRateOverrideModeCompat() {
         testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ false);
     }
 
@@ -1636,7 +1678,7 @@ public class DisplayManagerServiceTest {
      */
     @Test
     @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
-    public void testDisplayInfoNonNativeFrameRateOverrideMode() throws Exception {
+    public void testDisplayInfoNonNativeFrameRateOverrideMode() {
         testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ true);
     }
 
@@ -1644,7 +1686,7 @@ public class DisplayManagerServiceTest {
      * Tests that there is a display change notification if the render frame rate is updated
      */
     @Test
-    public void testShouldNotifyChangeWhenDisplayInfoRenderFrameRateChanged() throws Exception {
+    public void testShouldNotifyChangeWhenDisplayInfoRenderFrameRateChanged() {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
         DisplayManagerService.BinderService displayManagerBinderService =
@@ -1657,14 +1699,17 @@ public class DisplayManagerServiceTest {
                 displayManagerBinderService, displayDevice);
 
         updateRenderFrameRate(displayManager, displayDevice, 30f);
-        assertTrue(callback.mDisplayChangedCalled);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
         callback.clear();
 
         updateRenderFrameRate(displayManager, displayDevice, 30f);
-        assertFalse(callback.mDisplayChangedCalled);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        assertThat(callback.receivedEvents()).doesNotContain(EVENT_DISPLAY_CHANGED);
 
         updateRenderFrameRate(displayManager, displayDevice, 20f);
-        assertTrue(callback.mDisplayChangedCalled);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        assertThat(callback.receivedEvents()).contains(EVENT_DISPLAY_CHANGED);
         callback.clear();
     }
 
@@ -1672,7 +1717,7 @@ public class DisplayManagerServiceTest {
      * Tests that the DisplayInfo is updated correctly with a render frame rate
      */
     @Test
-    public void testDisplayInfoRenderFrameRate() throws Exception {
+    public void testDisplayInfoRenderFrameRate() {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
         DisplayManagerService.BinderService displayManagerBinderService =
@@ -1734,9 +1779,7 @@ public class DisplayManagerServiceTest {
 
         waitForIdleHandler(handler);
 
-        assertFalse(callback.mDisplayChangedCalled);
-        assertFalse(callback.mDisplayRemovedCalled);
-        assertTrue(callback.mDisplayAddedCalled);
+        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_ADDED);
     }
 
     /**
@@ -1766,9 +1809,7 @@ public class DisplayManagerServiceTest {
 
         waitForIdleHandler(handler);
 
-        assertFalse(callback.mDisplayChangedCalled);
-        assertFalse(callback.mDisplayRemovedCalled);
-        assertFalse(callback.mDisplayAddedCalled);
+        assertThat(callback.receivedEvents()).isEmpty();
     }
 
     /**
@@ -1780,12 +1821,15 @@ public class DisplayManagerServiceTest {
                 new DisplayManagerService(mContext, mShortMockedInjector);
         DisplayManagerService.BinderService displayManagerBinderService =
                 displayManager.new BinderService();
+        LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
 
         Handler handler = displayManager.getDisplayHandler();
         waitForIdleHandler(handler);
 
         FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
                 new float[]{60f});
+        LogicalDisplay display =
+                logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
 
         waitForIdleHandler(handler);
 
@@ -1794,14 +1838,12 @@ public class DisplayManagerServiceTest {
 
         waitForIdleHandler(handler);
 
+        display.setPrimaryDisplayDeviceLocked(null);
         displayManager.getDisplayDeviceRepository()
                 .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
-
         waitForIdleHandler(handler);
 
-        assertFalse(callback.mDisplayChangedCalled);
-        assertTrue(callback.mDisplayRemovedCalled);
-        assertFalse(callback.mDisplayAddedCalled);
+        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_REMOVED);
     }
 
     /**
@@ -1814,12 +1856,15 @@ public class DisplayManagerServiceTest {
                 new DisplayManagerService(mContext, mShortMockedInjector);
         DisplayManagerService.BinderService displayManagerBinderService =
                 displayManager.new BinderService();
+        LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
 
         Handler handler = displayManager.getDisplayHandler();
         waitForIdleHandler(handler);
 
         FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
                 new float[]{60f});
+        LogicalDisplay display =
+                logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
 
         waitForIdleHandler(handler);
 
@@ -1831,14 +1876,12 @@ public class DisplayManagerServiceTest {
 
         waitForIdleHandler(handler);
 
+        display.setPrimaryDisplayDeviceLocked(null);
         displayManager.getDisplayDeviceRepository()
                 .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
-
         waitForIdleHandler(handler);
 
-        assertFalse(callback.mDisplayChangedCalled);
-        assertFalse(callback.mDisplayRemovedCalled);
-        assertFalse(callback.mDisplayAddedCalled);
+        assertThat(callback.receivedEvents()).isEmpty();
     }
 
 
@@ -2052,8 +2095,315 @@ public class DisplayManagerServiceTest {
         assertNull(result);
     }
 
-    private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled)
-            throws Exception {
+    @Test
+    public void testConnectExternalDisplay_withoutDisplayManagement_shouldAddDisplay() {
+        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(false);
+        manageDisplaysPermission(/* granted= */ true);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+
+        FakeDisplayDevice displayDevice =
+                createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+
+        LogicalDisplay display =
+                logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+        assertThat(display.isEnabledLocked()).isTrue();
+        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_ADDED);
+
+    }
+
+    @Test
+    public void testConnectExternalDisplay_withDisplayManagement_shouldDisableDisplay() {
+        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+        manageDisplaysPermission(/* granted= */ true);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+        // Create default display device
+        createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+
+        bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+        localService.registerDisplayGroupListener(callback);
+
+        FakeDisplayDevice displayDevice =
+                createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+
+        LogicalDisplay display =
+                logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+        assertThat(display.isEnabledLocked()).isFalse();
+        assertThat(callback.receivedEvents()).containsExactly(DISPLAY_GROUP_EVENT_ADDED,
+                EVENT_DISPLAY_CONNECTED).inOrder();
+    }
+
+    @Test
+    public void testConnectInternalDisplay_withDisplayManagement_shouldConnectAndAddDisplay() {
+        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+        manageDisplaysPermission(/* granted= */ true);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+
+        createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+
+        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_CONNECTED,
+                EVENT_DISPLAY_ADDED).inOrder();
+    }
+
+    @Test
+    public void testEnableExternalDisplay_withDisplayManagement_shouldSignalDisplayAdded() {
+        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+        manageDisplaysPermission(/* granted= */ true);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        // Create default display device
+        createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+        bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+        FakeDisplayDevice displayDevice =
+                createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.clear();
+
+        LogicalDisplay display =
+                logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+        displayManager.enableConnectedDisplay(display.getDisplayIdLocked(), /* enabled= */ true);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+
+        assertThat(display.isEnabledLocked()).isTrue();
+        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_ADDED).inOrder();
+    }
+
+    @Test
+    public void testEnableExternalDisplay_withoutPermission_shouldThrowException() {
+        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        // Create default display device
+        createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+        bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+        FakeDisplayDevice displayDevice =
+                createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.clear();
+        LogicalDisplay display =
+                logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+        int displayId = display.getDisplayIdLocked();
+
+        assertThrows(SecurityException.class, () -> bs.enableConnectedDisplay(displayId));
+    }
+
+    @Test
+    public void testEnableInternalDisplay_withManageDisplays_shouldSignalAdded() {
+        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+        FakeDisplayDevice displayDevice =
+                createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        LogicalDisplay display =
+                logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+        logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ false);
+        logicalDisplayMapper.updateLogicalDisplays();
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.clear();
+
+        logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ true);
+        logicalDisplayMapper.updateLogicalDisplays();
+        waitForIdleHandler(displayManager.getDisplayHandler());
+
+        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_ADDED);
+    }
+
+    @Test
+    public void testDisableInternalDisplay_withDisplayManagement_shouldSignalRemove() {
+        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+        FakeDisplayDevice displayDevice =
+                createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.clear();
+        LogicalDisplay display =
+                logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+
+        logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ false);
+        logicalDisplayMapper.updateLogicalDisplays();
+        waitForIdleHandler(displayManager.getDisplayHandler());
+
+        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_REMOVED);
+    }
+
+    @Test
+    public void testDisableExternalDisplay_shouldSignalDisplayRemoved() {
+        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+        DisplayManagerInternal localService = displayManager.new LocalService();
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        // Create default display device
+        createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+        bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+        localService.registerDisplayGroupListener(callback);
+        FakeDisplayDevice displayDevice =
+                createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        LogicalDisplay display =
+                logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+        logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ true);
+        logicalDisplayMapper.updateLogicalDisplays();
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.clear();
+
+        logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ false);
+        logicalDisplayMapper.updateLogicalDisplays();
+        waitForIdleHandler(displayManager.getDisplayHandler());
+
+        assertThat(display.isEnabledLocked()).isFalse();
+        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_REMOVED);
+    }
+
+    @Test
+    public void testDisableExternalDisplay_withoutPermission_shouldThrowException() {
+        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        // Create default display device
+        createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+        bs.registerCallbackWithEventMask(callback, STANDARD_DISPLAY_EVENTS);
+        FakeDisplayDevice displayDevice =
+                createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        LogicalDisplay display =
+                logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+        int displayId = display.getDisplayIdLocked();
+        logicalDisplayMapper.setEnabledLocked(display, /* isEnabled= */ true);
+        logicalDisplayMapper.updateLogicalDisplays();
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.clear();
+
+        assertThrows(SecurityException.class, () -> bs.disableConnectedDisplay(displayId));
+    }
+
+    @Test
+    public void testRemoveExternalDisplay_whenDisabled_shouldSignalDisconnected() {
+        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+        manageDisplaysPermission(/* granted= */ true);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        DisplayManagerInternal localService = displayManager.new LocalService();
+        LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        // Create default display device
+        createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+        bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+        localService.registerDisplayGroupListener(callback);
+        FakeDisplayDevice displayDevice =
+                createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.clear();
+        LogicalDisplay display = logicalDisplayMapper.getDisplayLocked(displayDevice);
+        int groupId = display.getDisplayInfoLocked().displayGroupId;
+        DisplayGroup group = logicalDisplayMapper.getDisplayGroupLocked(groupId);
+        assertThat(group.getSizeLocked()).isEqualTo(1);
+
+        display.setPrimaryDisplayDeviceLocked(null);
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+
+        assertThat(group.getSizeLocked()).isEqualTo(0);
+        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_DISCONNECTED,
+                DISPLAY_GROUP_EVENT_REMOVED);
+    }
+
+    @Test
+    public void testRegisterCallback_withoutPermission_shouldThrow() {
+        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+
+        assertThrows(SecurityException.class, () -> bs.registerCallbackWithEventMask(callback,
+                STANDARD_AND_CONNECTION_DISPLAY_EVENTS));
+    }
+
+    @Test
+    public void testRemoveExternalDisplay_whenEnabled_shouldSignalRemovedAndDisconnected() {
+        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+        manageDisplaysPermission(/* granted= */ true);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+        FakeDisplayDevice displayDevice =
+                createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        LogicalDisplay display =
+                logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+        int displayId = display.getDisplayIdLocked();
+        displayManager.enableConnectedDisplay(displayId, /* enabled= */ true);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.clear();
+
+        display.setPrimaryDisplayDeviceLocked(null);
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+
+        assertThat(logicalDisplayMapper.getDisplayLocked(displayId, true)).isNull();
+        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_REMOVED,
+                EVENT_DISPLAY_DISCONNECTED).inOrder();
+    }
+
+    @Test
+    public void testRemoveInternalDisplay_whenEnabled_shouldSignalRemovedAndDisconnected() {
+        when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(true);
+        manageDisplaysPermission(/* granted= */ true);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+        LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper();
+        FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
+        bs.registerCallbackWithEventMask(callback, STANDARD_AND_CONNECTION_DISPLAY_EVENTS);
+        FakeDisplayDevice displayDevice =
+                createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL);
+        LogicalDisplay display =
+                logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+        callback.clear();
+
+        display.setPrimaryDisplayDeviceLocked(null);
+        displayManager.getDisplayDeviceRepository()
+                .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
+        waitForIdleHandler(displayManager.getDisplayHandler());
+
+        assertThat(logicalDisplayMapper.getDisplayLocked(displayDevice,
+                /* includeDisabled= */ true)).isNull();
+        assertThat(callback.receivedEvents()).containsExactly(EVENT_DISPLAY_REMOVED,
+                EVENT_DISPLAY_DISCONNECTED);
+    }
+    private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled) {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
         DisplayManagerService.BinderService displayManagerBinderService =
@@ -2086,8 +2436,7 @@ public class DisplayManagerServiceTest {
         assertEquals(expectedMode, displayInfo.getMode());
     }
 
-    private void testDisplayInfoRenderFrameRateModeCompat(boolean compatChangeEnabled)
-            throws Exception {
+    private void testDisplayInfoRenderFrameRateModeCompat(boolean compatChangeEnabled)  {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mShortMockedInjector);
         DisplayManagerService.BinderService displayManagerBinderService =
@@ -2214,6 +2563,15 @@ public class DisplayManagerServiceTest {
             DisplayManagerService displayManager,
             DisplayManagerService.BinderService displayManagerBinderService,
             FakeDisplayDevice displayDevice) {
+        return registerDisplayListenerCallback(displayManager, displayManagerBinderService,
+                displayDevice, STANDARD_DISPLAY_EVENTS);
+    }
+
+    private FakeDisplayManagerCallback registerDisplayListenerCallback(
+            DisplayManagerService displayManager,
+            DisplayManagerService.BinderService displayManagerBinderService,
+            FakeDisplayDevice displayDevice,
+            long displayEventsMask) {
         // Find the display id of the added FakeDisplayDevice
         int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
                 displayDevice);
@@ -2224,12 +2582,18 @@ public class DisplayManagerServiceTest {
         // register display listener callback
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback(displayId);
         displayManagerBinderService.registerCallbackWithEventMask(
-                callback, STANDARD_DISPLAY_EVENTS);
+                callback, displayEventsMask);
         return callback;
     }
 
     private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
-            float[] refreshRates) {
+                                                      float[] refreshRates) {
+        return createFakeDisplayDevice(displayManager, refreshRates, Display.TYPE_UNKNOWN);
+    }
+
+    private FakeDisplayDevice createFakeDisplayDevice(DisplayManagerService displayManager,
+                                                      float[] refreshRates,
+                                                      int displayType) {
         FakeDisplayDevice displayDevice = new FakeDisplayDevice();
         DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
         int width = 100;
@@ -2240,6 +2604,7 @@ public class DisplayManagerServiceTest {
                     new Display.Mode(i + 1, width, height, refreshRates[i]);
         }
         displayDeviceInfo.modeId = 1;
+        displayDeviceInfo.type = displayType;
         displayDeviceInfo.renderFrameRate = displayDeviceInfo.supportedModes[0].getRefreshRate();
         displayDeviceInfo.width = width;
         displayDeviceInfo.height = height;
@@ -2248,6 +2613,9 @@ public class DisplayManagerServiceTest {
                 Insets.of(0, 10, 0, 0),
                 zeroRect, new Rect(0, 0, 10, 10), zeroRect, zeroRect);
         displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
+        if (displayType == Display.TYPE_EXTERNAL) {
+            displayDeviceInfo.flags |= DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
+        }
         displayDeviceInfo.address = new TestUtils.TestDisplayAddress();
         displayDevice.setDisplayDeviceInfo(displayDeviceInfo);
         displayManager.getDisplayDeviceRepository()
@@ -2282,25 +2650,30 @@ public class DisplayManagerServiceTest {
         }
     }
 
-    private void resetConfigToIgnoreSensorManager(Context context) {
-        final Resources res = Mockito.spy(context.getResources());
-        doReturn(new int[]{-1}).when(res).getIntArray(R.array
+    private void resetConfigToIgnoreSensorManager() {
+        doReturn(new int[]{-1}).when(mResources).getIntArray(R.array
                 .config_ambientThresholdsOfPeakRefreshRate);
-        doReturn(new int[]{-1}).when(res).getIntArray(R.array
+        doReturn(new int[]{-1}).when(mResources).getIntArray(R.array
                 .config_brightnessThresholdsOfPeakRefreshRate);
-        doReturn(new int[]{-1}).when(res).getIntArray(R.array
+        doReturn(new int[]{-1}).when(mResources).getIntArray(R.array
                 .config_highDisplayBrightnessThresholdsOfFixedRefreshRate);
-        doReturn(new int[]{-1}).when(res).getIntArray(R.array
+        doReturn(new int[]{-1}).when(mResources).getIntArray(R.array
                 .config_highAmbientBrightnessThresholdsOfFixedRefreshRate);
+    }
 
-        when(context.getResources()).thenReturn(res);
+    private void manageDisplaysPermission(boolean granted) {
+        if (granted) {
+            doNothing().when(mContext).enforceCallingOrSelfPermission(eq(MANAGE_DISPLAYS), any());
+        } else {
+            doThrow(new SecurityException("MANAGE_DISPLAYS permission denied")).when(mContext)
+                    .enforceCallingOrSelfPermission(eq(MANAGE_DISPLAYS), any());
+        }
     }
 
-    private class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub {
+    private static class FakeDisplayManagerCallback extends IDisplayManagerCallback.Stub
+            implements DisplayManagerInternal.DisplayGroupListener {
         int mDisplayId;
-        boolean mDisplayAddedCalled = false;
-        boolean mDisplayChangedCalled = false;
-        boolean mDisplayRemovedCalled = false;
+        List<String> mReceivedEvents = new ArrayList<>();
 
         FakeDisplayManagerCallback(int displayId) {
             mDisplayId = displayId;
@@ -2316,23 +2689,55 @@ public class DisplayManagerServiceTest {
                 return;
             }
 
-            if (event == DisplayManagerGlobal.EVENT_DISPLAY_ADDED) {
-                mDisplayAddedCalled = true;
-            }
+            // We convert the event to a string for two reasons:
+            // 1 - The error produced is a lot easier to read
+            // 2 - The values used for display and group events are the same, strings are used to
+            // differentiate them easily.
+            mReceivedEvents.add(eventTypeToString(event));
+        }
 
-            if (event == DisplayManagerGlobal.EVENT_DISPLAY_CHANGED) {
-                mDisplayChangedCalled = true;
-            }
+        @Override
+        public void onDisplayGroupAdded(int groupId) {
+            mReceivedEvents.add(DISPLAY_GROUP_EVENT_ADDED);
+        }
 
-            if (event == DisplayManagerGlobal.EVENT_DISPLAY_REMOVED) {
-                mDisplayRemovedCalled = true;
-            }
+        @Override
+        public void onDisplayGroupRemoved(int groupId) {
+            mReceivedEvents.add(DISPLAY_GROUP_EVENT_REMOVED);
+        }
+
+        @Override
+        public void onDisplayGroupChanged(int groupId) {
+            mReceivedEvents.add(DISPLAY_GROUP_EVENT_CHANGED);
         }
 
         public void clear() {
-            mDisplayAddedCalled = false;
-            mDisplayChangedCalled = false;
-            mDisplayRemovedCalled = false;
+            mReceivedEvents.clear();
+        }
+
+        private String eventTypeToString(int eventType) {
+            switch (eventType) {
+                case DisplayManagerGlobal.EVENT_DISPLAY_ADDED:
+                    return EVENT_DISPLAY_ADDED;
+                case DisplayManagerGlobal.EVENT_DISPLAY_REMOVED:
+                    return EVENT_DISPLAY_REMOVED;
+                case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED:
+                    return EVENT_DISPLAY_CHANGED;
+                case DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED:
+                    return EVENT_DISPLAY_BRIGHTNESS_CHANGED;
+                case DisplayManagerGlobal.EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED:
+                    return EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED;
+                case DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED:
+                    return EVENT_DISPLAY_CONNECTED;
+                case DisplayManagerGlobal.EVENT_DISPLAY_DISCONNECTED:
+                    return EVENT_DISPLAY_DISCONNECTED;
+                default:
+                    return "UNKNOWN: " + eventType;
+            }
+        }
+
+        List<String> receivedEvents() {
+            return mReceivedEvents;
         }
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index aa0a2fea1a5a8c235f48c39be410becb2d2945d9..6cde5e3636571b60f5982762f7c479e38d7ae465 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -107,7 +107,6 @@ public class LocalDisplayAdapterTest {
     private LightsManager mMockedLightsManager;
     @Mock
     private LogicalLight mMockedBacklight;
-
     private Handler mHandler;
 
     private TestListener mListener = new TestListener();
@@ -1063,6 +1062,53 @@ public class LocalDisplayAdapterTest {
         assertThat(info.roundedCorners).isNull();
     }
 
+    @Test
+    public void test_createLocalExternalDisplay_displayManagementEnabled_shouldHaveDefaultGroup()
+            throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        display.info.isInternal = false;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        // Turn on / initialize
+        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+                0);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        mListener.changedDisplays.clear();
+
+        DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(info.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP).isEqualTo(0);
+    }
+    @Test
+    public void test_createLocalExternalDisplay_displayManagementDisabled_shouldNotHaveOwnGroup()
+            throws Exception {
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        display.info.isInternal = false;
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        // Turn on / initialize
+        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+                0);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        mListener.changedDisplays.clear();
+
+        DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(info.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP).isEqualTo(0);
+    }
+
     private void setupCutoutAndRoundedCorners() {
         String sampleCutout = "M 507,66\n"
                 + "a 33,33 0 1 0 66,0 33,33 0 1 0 -66,0\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 0fe6e64b3b5418c8d90ccc9ff92174da00c23ba5..69544356ab5bdb9dd63845c59659d9bce1e77008 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -30,6 +30,8 @@ import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_CHA
 import static com.android.server.display.DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED;
 import static com.android.server.display.DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY;
 import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CONNECTED;
+import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_DISCONNECTED;
 import static com.android.server.display.LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED;
 import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
 import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN;
@@ -44,6 +46,7 @@ import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -63,6 +66,7 @@ import android.view.DisplayInfo;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.layout.DisplayIdProducer;
 import com.android.server.display.layout.Layout;
 import com.android.server.utils.FoldSettingWrapper;
@@ -79,7 +83,9 @@ import org.mockito.Spy;
 import java.io.File;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -107,8 +113,11 @@ public class LogicalDisplayMapperTest {
     @Mock IThermalService mIThermalServiceMock;
     @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy =
             new DeviceStateToLayoutMap(mIdProducer, NON_EXISTING_FILE);
+    @Mock
+    DisplayManagerFlags mFlagsMock;
 
     @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor;
+    @Captor ArgumentCaptor<Integer> mDisplayEventCaptor;
 
     @Before
     public void setUp() {
@@ -154,11 +163,12 @@ public class LogicalDisplayMapperTest {
                 com.android.internal.R.array.config_deviceStatesOnWhichToSleep))
                 .thenReturn(new int[]{0});
 
+        when(mFlagsMock.isConnectedDisplayManagementEnabled()).thenReturn(false);
         mLooper = new TestLooper();
         mHandler = new Handler(mLooper.getLooper());
         mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo,
                 mListenerMock, new DisplayManagerService.SyncRoot(), mHandler,
-                mDeviceStateToLayoutMapSpy, mFoldSettingWrapperMock);
+                mDeviceStateToLayoutMapSpy, mFoldSettingWrapperMock, mFlagsMock);
     }
 
 
@@ -278,6 +288,67 @@ public class LogicalDisplayMapperTest {
         assertEquals(info(display1).address, info(device2).address);
     }
 
+    @Test
+    public void testDisplayDeviceAddAndRemove_withDisplayManagement() {
+        when(mFlagsMock.isConnectedDisplayManagementEnabled()).thenReturn(true);
+        DisplayDevice device = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+
+        // add
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_ADDED);
+
+        verify(mListenerMock, times(2)).onLogicalDisplayEventLocked(
+                mDisplayCaptor.capture(), mDisplayEventCaptor.capture());
+        LogicalDisplay added = mDisplayCaptor.getAllValues().get(0);
+        assertThat(mDisplayCaptor.getAllValues().get(1)).isEqualTo(added);
+        LogicalDisplay displayAdded = add(device);
+        assertThat(info(displayAdded).address).isEqualTo(info(device).address);
+        assertThat(id(displayAdded)).isEqualTo(DEFAULT_DISPLAY);
+        assertThat(mDisplayEventCaptor.getAllValues()).containsExactly(
+                LOGICAL_DISPLAY_EVENT_CONNECTED, LOGICAL_DISPLAY_EVENT_ADDED).inOrder();
+        clearInvocations(mListenerMock);
+
+        // remove
+        mDisplayDeviceRepo.onDisplayDeviceEvent(device, DISPLAY_DEVICE_EVENT_REMOVED);
+        verify(mListenerMock, times(2)).onLogicalDisplayEventLocked(
+                mDisplayCaptor.capture(), mDisplayEventCaptor.capture());
+        List<Integer> allEvents = mDisplayEventCaptor.getAllValues();
+        int numEvents = allEvents.size();
+        // Only extract the last two events
+        List<Integer> events = new ArrayList(2);
+        events.add(allEvents.get(numEvents - 2));
+        events.add(allEvents.get(numEvents - 1));
+        assertThat(events).containsExactly(
+                LOGICAL_DISPLAY_EVENT_REMOVED, LOGICAL_DISPLAY_EVENT_DISCONNECTED).inOrder();
+        List<LogicalDisplay> displays = mDisplayCaptor.getAllValues();
+        LogicalDisplay displayRemoved = displays.get(numEvents - 2);
+        assertThat(displays.get(numEvents - 1)).isEqualTo(displayRemoved);
+        assertThat(id(displayRemoved)).isEqualTo(DEFAULT_DISPLAY);
+        assertThat(displayRemoved).isEqualTo(displayAdded);
+    }
+
+    @Test
+    public void testDisplayDisableEnable_withDisplayManagement() {
+        when(mFlagsMock.isConnectedDisplayManagementEnabled()).thenReturn(true);
+        DisplayDevice device = createDisplayDevice(TYPE_INTERNAL, 600, 800,
+                FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
+        LogicalDisplay displayAdded = add(device);
+        assertThat(displayAdded.isEnabledLocked()).isTrue();
+
+        // Disable device
+        mLogicalDisplayMapper.setDisplayEnabledLocked(
+                displayAdded.getDisplayIdLocked(), /* isEnabled= */ false);
+        verify(mListenerMock).onLogicalDisplayEventLocked(mDisplayCaptor.capture(),
+                eq(LOGICAL_DISPLAY_EVENT_REMOVED));
+        clearInvocations(mListenerMock);
+
+        // Enable device
+        mLogicalDisplayMapper.setDisplayEnabledLocked(
+                displayAdded.getDisplayIdLocked(), /* isEnabled= */ true);
+        verify(mListenerMock).onLogicalDisplayEventLocked(mDisplayCaptor.capture(),
+                eq(LOGICAL_DISPLAY_EVENT_ADDED));
+    }
+
     @Test
     public void testGetDisplayIdsLocked() {
         add(createDisplayDevice(TYPE_INTERNAL, 600, 800,