PM#goToSleep and PM#wakeUp only affects default display group

This CL changes the behavior of PM#goToSleep to instead turn off the
power for the default display group. PM#wakeUp turns on the default
display group.

If all display groups are turned off, the device will go to sleep. If
any display group is on, the device will remain interactive.

This results in no behavior change if there is a single display group.
However if there are multiple display groups then it will be possible to
interact with ones that are on while others are off.

Bug: 138328918
Bug: 170935083
Bug: 175919649
Test: atest PowerManagerServiceTest
Change-Id: I8b475c38886eda3e43d6e8da7e68102a25da1584
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 5a03ade..421a07b 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -47,28 +47,40 @@
      * begins adjusting the power state to match what was requested.
      * </p>
      *
+     * @param groupId The identifier for the display group being requested to change power state
      * @param request The requested power state.
-     * @param waitForNegativeProximity If true, issues a request to wait for
+     * @param waitForNegativeProximity If {@code true}, issues a request to wait for
      * negative proximity before turning the screen back on, assuming the screen
      * was turned off by the proximity sensor.
-     * @return True if display is ready, false if there are important changes that must
-     * be made asynchronously (such as turning the screen on), in which case the caller
-     * should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged()}
-     * then try the request again later until the state converges.
+     * @return {@code true} if display group is ready, {@code false} if there are important
+     * changes that must be made asynchronously (such as turning the screen on), in which case
+     * the caller should grab a wake lock, watch for {@link DisplayPowerCallbacks#onStateChanged}
+     * then try the request again later until the state converges. If the provided {@code groupId}
+     * cannot be found then {@code true} will be returned.
      */
-    public abstract boolean requestPowerState(DisplayPowerRequest request,
+    public abstract boolean requestPowerState(int groupId, DisplayPowerRequest request,
             boolean waitForNegativeProximity);
 
     /**
-     * Returns true if the proximity sensor screen-off function is available.
+     * Returns {@code true} if the proximity sensor screen-off function is available.
      */
     public abstract boolean isProximitySensorAvailable();
 
     /**
-     * Returns the id of the {@link com.android.server.display.DisplayGroup} to which the provided
-     * display belongs.
+     * Registers a display group listener which will be informed of the addition, removal, or change
+     * of display groups.
+     *
+     * @param listener The listener to register.
      */
-    public abstract int getDisplayGroupId(int displayId);
+    public abstract void registerDisplayGroupListener(DisplayGroupListener listener);
+
+    /**
+     * Unregisters a display group listener which will be informed of the addition, removal, or
+     * change of display groups.
+     *
+     * @param listener The listener to unregister.
+     */
+    public abstract void unregisterDisplayGroupListener(DisplayGroupListener listener);
 
     /**
      * Screenshot for internal system-only use such as rotation, etc.  This method includes
@@ -451,7 +463,7 @@
         void onStateChanged();
         void onProximityPositive();
         void onProximityNegative();
-        void onDisplayStateChange(int state); // one of the Display state constants
+        void onDisplayStateChange(boolean allInactive, boolean allOff);
 
         void acquireSuspendBlocker();
         void releaseSuspendBlocker();
@@ -465,4 +477,33 @@
     public interface DisplayTransactionListener {
         void onDisplayTransaction(Transaction t);
     }
+
+    /**
+     * Called when there are changes to {@link com.android.server.display.DisplayGroup
+     * DisplayGroups}.
+     */
+    public interface DisplayGroupListener {
+        /**
+         * A new display group with the provided {@code groupId} was added.
+         * This is guaranteed to be called <i>before</i> any corresponding calls to
+         * {@link android.hardware.display.DisplayManager.DisplayListener} are made.
+         */
+        void onDisplayGroupAdded(int groupId);
+
+        /**
+         * The display group with the provided {@code groupId} was removed.
+         *
+         * This is guaranteed to be called <i>after</i> any corresponding calls to
+         * {@link android.hardware.display.DisplayManager.DisplayListener} are made.
+         */
+        void onDisplayGroupRemoved(int groupId);
+
+        /**
+         * The display group with the provided {@code groupId} has changed.
+         *
+         * This is guaranteed to be called <i>after</i> any corresponding calls to
+         * {@link android.hardware.display.DisplayManager.DisplayListener} are made.
+         */
+        void onDisplayGroupChanged(int groupId);
+    }
 }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index b951aca..8dd6119 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -416,9 +416,21 @@
     public static final int GO_TO_SLEEP_REASON_QUIESCENT = 10;
 
     /**
+     * Go to sleep reason code: The last powered on display group has been removed.
      * @hide
      */
-    public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_QUIESCENT;
+    public static final int GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED = 11;
+
+    /**
+     * Go to sleep reason code: Every display group has been turned off.
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF = 12;
+
+    /**
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_MAX = GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF;
 
     /**
      * @hide
@@ -435,6 +447,8 @@
             case GO_TO_SLEEP_REASON_ACCESSIBILITY: return "accessibility";
             case GO_TO_SLEEP_REASON_FORCE_SUSPEND: return "force_suspend";
             case GO_TO_SLEEP_REASON_INATTENTIVE: return "inattentive";
+            case GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED: return "display_group_removed";
+            case GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF: return "display_groups_turned_off";
             default: return Integer.toString(sleepReason);
         }
     }
@@ -521,6 +535,8 @@
             WAKE_REASON_WAKE_KEY,
             WAKE_REASON_WAKE_MOTION,
             WAKE_REASON_HDMI,
+            WAKE_REASON_DISPLAY_GROUP_ADDED,
+            WAKE_REASON_DISPLAY_GROUP_TURNED_ON,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface WakeReason{}
@@ -589,6 +605,18 @@
     public static final int WAKE_REASON_LID = 9;
 
     /**
+     * Wake up reason code: Waking due to display group being added.
+     * @hide
+     */
+    public static final int WAKE_REASON_DISPLAY_GROUP_ADDED = 10;
+
+    /**
+     * Wake up reason code: Waking due to display group being powered on.
+     * @hide
+     */
+    public static final int WAKE_REASON_DISPLAY_GROUP_TURNED_ON = 11;
+
+    /**
      * Convert the wake reason to a string for debugging purposes.
      * @hide
      */
@@ -604,6 +632,8 @@
             case WAKE_REASON_WAKE_MOTION: return "WAKE_REASON_WAKE_MOTION";
             case WAKE_REASON_HDMI: return "WAKE_REASON_HDMI";
             case WAKE_REASON_LID: return "WAKE_REASON_LID";
+            case WAKE_REASON_DISPLAY_GROUP_ADDED: return "WAKE_REASON_DISPLAY_GROUP_ADDED";
+            case WAKE_REASON_DISPLAY_GROUP_TURNED_ON: return "WAKE_REASON_DISPLAY_GROUP_TURNED_ON";
             default: return Integer.toString(wakeReason);
         }
     }
@@ -1193,8 +1223,15 @@
         }
     }
 
-   /**
-     * Forces the device to go to sleep.
+    /**
+     * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+     * to turn off.
+     *
+     * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+     * turned on it will be turned off. If all displays are off as a result of this action the
+     * device will be put to sleep. If the {@link com.android.server.display.DisplayGroup#DEFAULT
+     * default display group} is already off then nothing will happen.
+     *
      * <p>
      * Overrides all the wake locks that are held.
      * This is what happens when the power key is pressed to turn off the screen.
@@ -1217,7 +1254,14 @@
     }
 
     /**
-     * Forces the device to go to sleep.
+     * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+     * to turn off.
+     *
+     * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+     * turned on it will be turned off. If all displays are off as a result of this action the
+     * device will be put to sleep. If the {@link com.android.server.display.DisplayGroup#DEFAULT
+     * default display group} is already off then nothing will happen.
+     *
      * <p>
      * Overrides all the wake locks that are held.
      * This is what happens when the power key is pressed to turn off the screen.
@@ -1247,9 +1291,15 @@
     }
 
     /**
-     * Forces the device to wake up from sleep.
+     * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+     * to turn on.
+     *
+     * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+     * turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If
+     * the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is already
+     * on then nothing will happen.
+     *
      * <p>
-     * If the device is currently asleep, wakes it up, otherwise does nothing.
      * This is what happens when the power key is pressed to turn on the screen.
      * </p><p>
      * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
@@ -1272,9 +1322,15 @@
     }
 
     /**
-     * Forces the device to wake up from sleep.
+     * Forces the {@link com.android.server.display.DisplayGroup#DEFAULT default display group}
+     * to turn on.
+     *
+     * <p>If the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is
+     * turned off it will be turned on. Additionally, if the device is asleep it will be awoken. If
+     * the {@link com.android.server.display.DisplayGroup#DEFAULT default display group} is already
+     * on then nothing will happen.
+     *
      * <p>
-     * If the device is currently asleep, wakes it up, otherwise does nothing.
      * This is what happens when the power key is pressed to turn on the screen.
      * </p><p>
      * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
@@ -1301,9 +1357,13 @@
     }
 
     /**
-     * Forces the device to wake up from sleep.
+     * Forces the {@link android.view.Display#DEFAULT_DISPLAY default display} to turn on.
+     *
+     * <p>If the {@link android.view.Display#DEFAULT_DISPLAY default display} is turned off it will
+     * be turned on. Additionally, if the device is asleep it will be awoken. If the {@link
+     * android.view.Display#DEFAULT_DISPLAY default display} is already on then nothing will happen.
+     *
      * <p>
-     * If the device is currently asleep, wakes it up, otherwise does nothing.
      * This is what happens when the power key is pressed to turn on the screen.
      * </p><p>
      * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
diff --git a/services/core/java/com/android/server/display/DisplayGroup.java b/services/core/java/com/android/server/display/DisplayGroup.java
index 2ba8758..1b5065a 100644
--- a/services/core/java/com/android/server/display/DisplayGroup.java
+++ b/services/core/java/com/android/server/display/DisplayGroup.java
@@ -21,7 +21,8 @@
 
 /**
  * Represents a collection of {@link LogicalDisplay}s which act in unison for certain behaviors and
- * operations.
+ * operations; particularly display-state.
+ *
  * @hide
  */
 public class DisplayGroup {
@@ -35,17 +36,44 @@
         mGroupId = groupId;
     }
 
+    /** Returns the identifier for the Group. */
     int getGroupId() {
         return mGroupId;
     }
 
-    void addDisplay(LogicalDisplay display) {
+    /**
+     * Adds the provided {@code display} to the Group
+     *
+     * @param display the {@link LogicalDisplay} to add to the Group
+     */
+    void addDisplayLocked(LogicalDisplay display) {
         if (!mDisplays.contains(display)) {
             mDisplays.add(display);
         }
     }
 
-    boolean removeDisplay(LogicalDisplay display) {
+    /**
+     * Removes the provided {@code display} from the Group.
+     *
+     * @param display The {@link LogicalDisplay} to remove from the Group.
+     * @return {@code true} if the {@code display} was removed; otherwise {@code false}
+     */
+    boolean removeDisplayLocked(LogicalDisplay display) {
         return mDisplays.remove(display);
     }
+
+    /** Returns {@code true} if there are no {@link LogicalDisplay LogicalDisplays} in the Group. */
+    boolean isEmptyLocked() {
+        return mDisplays.isEmpty();
+    }
+
+    /** Returns the number of {@link LogicalDisplay LogicalDisplays} in the Group. */
+    int getSizeLocked() {
+        return mDisplays.size();
+    }
+
+    /** Returns the ID of the {@link LogicalDisplay} at the provided {@code index}. */
+    int getIdLocked(int index) {
+        return mDisplays.get(index).getDisplayIdLocked();
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 60e4595..0e1adee 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -56,6 +56,7 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayGroupListener;
 import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
 import android.hardware.display.DisplayViewport;
 import android.hardware.display.DisplayedContentSample;
@@ -190,6 +191,7 @@
     private static final int MSG_UPDATE_VIEWPORT = 5;
     private static final int MSG_LOAD_BRIGHTNESS_CONFIGURATION = 6;
     private static final int MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE = 7;
+    private static final int MSG_DELIVER_DISPLAY_GROUP_EVENT = 8;
 
     private final Context mContext;
     private final DisplayManagerHandler mHandler;
@@ -237,36 +239,54 @@
     private final CopyOnWriteArrayList<DisplayTransactionListener> mDisplayTransactionListeners =
             new CopyOnWriteArrayList<DisplayTransactionListener>();
 
+    /** List of all display group listeners. */
+    private final CopyOnWriteArrayList<DisplayGroupListener> mDisplayGroupListeners =
+            new CopyOnWriteArrayList<>();
+
     /** All {@link DisplayPowerController}s indexed by {@link LogicalDisplay} ID. */
     private final SparseArray<DisplayPowerController> mDisplayPowerControllers =
             new SparseArray<>();
 
     /** {@link DisplayBlanker} used by all {@link DisplayPowerController}s. */
     private final DisplayBlanker mDisplayBlanker = new DisplayBlanker() {
+        // Synchronized to avoid race conditions when updating multiple display states.
         @Override
-        public void requestDisplayState(int displayId, int state, float brightness) {
-            // TODO (b/168210494): Stop applying default display state to all displays.
-            if (displayId != Display.DEFAULT_DISPLAY) {
-                return;
-            }
-            final int[] displayIds;
+        public synchronized void requestDisplayState(int displayId, int state, float brightness) {
+            boolean allInactive = true;
+            boolean allOff = true;
+            final boolean stateChanged;
             synchronized (mSyncRoot) {
-                displayIds = mLogicalDisplayMapper.getDisplayIdsLocked();
+                final int index = mDisplayStates.indexOfKey(displayId);
+                final int newState = mDisplayStates.valueAt(index);
+                stateChanged = index == -1 || state != newState;
+                if (stateChanged) {
+                    final int size = mDisplayStates.size();
+                    for (int i = 0; i < size; i++) {
+                        final int displayState = i == index ? newState : state;
+                        if (displayState != Display.STATE_OFF) {
+                            allOff = false;
+                        }
+                        if (Display.isActiveState(displayState)) {
+                            allInactive = false;
+                        }
+                        if (!allOff && !allInactive) {
+                            break;
+                        }
+                    }
+                }
             }
 
             // The order of operations is important for legacy reasons.
             if (state == Display.STATE_OFF) {
-                for (int id : displayIds) {
-                    requestDisplayStateInternal(id, state, brightness);
-                }
+                requestDisplayStateInternal(displayId, state, brightness);
             }
 
-            mDisplayPowerCallbacks.onDisplayStateChange(state);
+            if (stateChanged) {
+                mDisplayPowerCallbacks.onDisplayStateChange(allInactive, allOff);
+            }
 
             if (state != Display.STATE_OFF) {
-                for (int id : displayIds) {
-                    requestDisplayStateInternal(id, state, brightness);
-                }
+                requestDisplayStateInternal(displayId, state, brightness);
             }
         }
     };
@@ -1151,7 +1171,7 @@
 
     private void handleLogicalDisplayRemovedLocked(@NonNull LogicalDisplay display) {
         final int displayId = display.getDisplayIdLocked();
-        mDisplayPowerControllers.delete(displayId);
+        mDisplayPowerControllers.removeReturnOld(displayId).stop();
         mDisplayStates.delete(displayId);
         mDisplayBrightnesses.delete(displayId);
         DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
@@ -1668,6 +1688,11 @@
         mHandler.sendMessage(msg);
     }
 
+    private void sendDisplayGroupEvent(int groupId, int event) {
+        Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_GROUP_EVENT, groupId, event);
+        mHandler.sendMessage(msg);
+    }
+
     private void sendDisplayEventFrameRateOverrideLocked(int displayId) {
         Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT_FRAME_RATE_OVERRIDE,
                 displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
@@ -1712,6 +1737,35 @@
         mTempCallbacks.clear();
     }
 
+    // Runs on Handler thread.
+    // Delivers display group event notifications to callbacks.
+    private void deliverDisplayGroupEvent(int groupId, int event) {
+        if (DEBUG) {
+            Slog.d(TAG, "Delivering display group event: groupId=" + groupId + ", event="
+                    + event);
+        }
+
+        switch (event) {
+            case LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED:
+                for (DisplayGroupListener listener : mDisplayGroupListeners) {
+                    listener.onDisplayGroupAdded(groupId);
+                }
+                break;
+
+            case LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED:
+                for (DisplayGroupListener listener : mDisplayGroupListeners) {
+                    listener.onDisplayGroupChanged(groupId);
+                }
+                break;
+
+            case LogicalDisplayMapper.DISPLAY_GROUP_EVENT_REMOVED:
+                for (DisplayGroupListener listener : mDisplayGroupListeners) {
+                    listener.onDisplayGroupRemoved(groupId);
+                }
+                break;
+        }
+    }
+
     private IMediaProjectionManager getProjectionService() {
         if (mProjectionService == null) {
             IBinder b = ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE);
@@ -1934,6 +1988,11 @@
                     }
                     deliverDisplayEvent(msg.arg1, uids, msg.arg2);
                     break;
+
+                case MSG_DELIVER_DISPLAY_GROUP_EVENT:
+                    deliverDisplayGroupEvent(msg.arg1, msg.arg2);
+                    break;
+
             }
         }
     }
@@ -1965,6 +2024,11 @@
         }
 
         @Override
+        public void onDisplayGroupEventLocked(int groupId, int event) {
+            sendDisplayGroupEvent(groupId, event);
+        }
+
+        @Override
         public void onTraversalRequested() {
             synchronized (mSyncRoot) {
                 scheduleTraversalLocked(false);
@@ -2699,11 +2763,25 @@
         }
 
         @Override
-        public boolean requestPowerState(DisplayPowerRequest request,
+        public boolean requestPowerState(int groupId, DisplayPowerRequest request,
                 boolean waitForNegativeProximity) {
             synchronized (mSyncRoot) {
-                return mDisplayPowerControllers.get(Display.DEFAULT_DISPLAY)
-                        .requestPowerState(request, waitForNegativeProximity);
+                final DisplayGroup displayGroup = mLogicalDisplayMapper.getDisplayGroupLocked(
+                        groupId);
+                if (displayGroup == null) {
+                    return true;
+                }
+
+                final int size = displayGroup.getSizeLocked();
+                boolean ready = true;
+                for (int i = 0; i < size; i++) {
+                    final DisplayPowerController displayPowerController =
+                            mDisplayPowerControllers.get(displayGroup.getIdLocked(i));
+                    ready &= displayPowerController.requestPowerState(request,
+                            waitForNegativeProximity);
+                }
+
+                return ready;
             }
         }
 
@@ -2716,10 +2794,13 @@
         }
 
         @Override
-        public int getDisplayGroupId(int displayId) {
-            synchronized (mSyncRoot) {
-                return mLogicalDisplayMapper.getDisplayGroupIdLocked(displayId);
-            }
+        public void registerDisplayGroupListener(DisplayGroupListener listener) {
+            mDisplayGroupListeners.add(listener);
+        }
+
+        @Override
+        public void unregisterDisplayGroupListener(DisplayGroupListener listener) {
+            mDisplayGroupListeners.remove(listener);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 811625b..5c795cd 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -683,6 +683,17 @@
         // TODO: b/175821789 - Support high brightness on multiple (folding) displays
     }
 
+    /**
+     * Unregisters all listeners and interrupts all running threads; halting future work.
+     *
+     * This method should be called when the DisplayPowerController is no longer in use; i.e. when
+     * the {@link #mDisplayId display} has been removed.
+     */
+    public void stop() {
+        mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
+        mPowerState.stop();
+    }
+
     private void sendUpdatePowerState() {
         synchronized (mLock) {
             sendUpdatePowerStateLocked();
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 54f30a9..51ba065 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -72,6 +72,8 @@
 
     private Runnable mCleanListener;
 
+    private volatile boolean mStopped;
+
     public DisplayPowerState(DisplayBlanker blanker, ColorFade colorFade, int displayId) {
         mHandler = new Handler(true /*async*/);
         mChoreographer = Choreographer.getInstance();
@@ -263,6 +265,18 @@
         }
     }
 
+    /**
+     * Interrupts all running threads; halting future work.
+     *
+     * This method should be called when the DisplayPowerState is no longer in use; i.e. when
+     * the {@link #mDisplayId display} has been removed.
+     */
+    public void stop() {
+        mHandler.removeCallbacksAndMessages(null);
+        mStopped = true;
+        mPhotonicModulator.interrupt();
+    }
+
     public void dump(PrintWriter pw) {
         pw.println();
         pw.println("Display Power State:");
@@ -427,7 +441,11 @@
                     if (!stateChanged && !backlightChanged) {
                         try {
                             mLock.wait();
-                        } catch (InterruptedException ex) { }
+                        } catch (InterruptedException ex) {
+                            if (mStopped) {
+                                return;
+                            }
+                        }
                         continue;
                     }
                     mActualState = state;
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index a127858..5bd4088 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -17,7 +17,6 @@
 package com.android.server.display;
 
 import android.content.Context;
-import android.os.Process;
 import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.IndentingPrintWriter;
@@ -55,6 +54,10 @@
     public static final int LOGICAL_DISPLAY_EVENT_SWAPPED = 4;
     public static final int LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED = 5;
 
+    public static final int DISPLAY_GROUP_EVENT_ADDED = 1;
+    public static final int DISPLAY_GROUP_EVENT_CHANGED = 2;
+    public static final int DISPLAY_GROUP_EVENT_REMOVED = 3;
+
     /**
      * Temporary display info, used for comparing display configurations.
      */
@@ -99,7 +102,7 @@
     private int mNextNonDefaultGroupId = DisplayGroup.DEFAULT + 1;
 
     /** A mapping from logical display id to display group. */
-    private final SparseArray<DisplayGroup> mDisplayGroups = new SparseArray<>();
+    private final SparseArray<DisplayGroup> mDisplayIdToGroupMap = new SparseArray<>();
 
     private final DisplayDeviceRepository mDisplayDeviceRepo;
     private final Listener mListener;
@@ -150,10 +153,6 @@
         return null;
     }
 
-    public int[] getDisplayIdsLocked() {
-        return getDisplayIdsLocked(Process.SYSTEM_UID);
-    }
-
     public int[] getDisplayIdsLocked(int callingUid) {
         final int count = mLogicalDisplays.size();
         int[] displayIds = new int[count];
@@ -178,13 +177,16 @@
         }
     }
 
-    public int getDisplayGroupIdLocked(int displayId) {
-        final DisplayGroup displayGroup = mDisplayGroups.get(displayId);
-        if (displayGroup != null) {
-            return displayGroup.getGroupId();
+    public DisplayGroup getDisplayGroupLocked(int groupId) {
+        final int size = mDisplayIdToGroupMap.size();
+        for (int i = 0; i < size; i++) {
+            final DisplayGroup displayGroup = mDisplayIdToGroupMap.valueAt(i);
+            if (displayGroup.getGroupId() == groupId) {
+                return displayGroup;
+            }
         }
 
-        return -1;
+        return null;
     }
 
     public void dumpLocked(PrintWriter pw) {
@@ -317,17 +319,31 @@
         mLogicalDisplays.put(displayId, display);
 
         final DisplayGroup displayGroup;
-        if (isDefault || (deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0) {
+        final boolean addNewDisplayGroup =
+                isDefault || (deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0;
+        if (addNewDisplayGroup) {
             final int groupId = assignDisplayGroupIdLocked(isDefault);
             displayGroup = new DisplayGroup(groupId);
         } else {
-            displayGroup = mDisplayGroups.get(Display.DEFAULT_DISPLAY);
+            displayGroup = mDisplayIdToGroupMap.get(Display.DEFAULT_DISPLAY);
         }
-        displayGroup.addDisplay(display);
-        mDisplayGroups.append(displayId, displayGroup);
+        displayGroup.addDisplayLocked(display);
+        mDisplayIdToGroupMap.append(displayId, displayGroup);
+
+        if (addNewDisplayGroup) {
+            // Group added events happen before Logical Display added events.
+            mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
+                    LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED);
+        }
 
         mListener.onLogicalDisplayEventLocked(display,
                 LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_ADDED);
+
+        if (!addNewDisplayGroup) {
+            // Group changed events happen after Logical Display added events.
+            mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
+                    LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
+        }
     }
 
     /**
@@ -344,31 +360,45 @@
             DisplayEventReceiver.FrameRateOverride[] frameRatesOverrides =
                     display.getFrameRateOverrides();
             display.updateLocked(mDisplayDeviceRepo);
+            final DisplayGroup changedDisplayGroup;
             if (!display.isValidLocked()) {
                 mLogicalDisplays.removeAt(i);
-                mDisplayGroups.removeReturnOld(displayId).removeDisplay(display);
+                final DisplayGroup displayGroup = mDisplayIdToGroupMap.removeReturnOld(displayId);
+                displayGroup.removeDisplayLocked(display);
 
                 mListener.onLogicalDisplayEventLocked(display,
                         LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_REMOVED);
+
+                changedDisplayGroup = displayGroup;
             } else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) {
                 final int flags = display.getDisplayInfoLocked().flags;
-                final DisplayGroup defaultDisplayGroup = mDisplayGroups.get(
+                final DisplayGroup defaultDisplayGroup = mDisplayIdToGroupMap.get(
                         Display.DEFAULT_DISPLAY);
                 if ((flags & Display.FLAG_OWN_DISPLAY_GROUP) != 0) {
                     // The display should have its own DisplayGroup.
-                    if (defaultDisplayGroup.removeDisplay(display)) {
+                    if (defaultDisplayGroup.removeDisplayLocked(display)) {
                         final int groupId = assignDisplayGroupIdLocked(false);
                         final DisplayGroup displayGroup = new DisplayGroup(groupId);
-                        displayGroup.addDisplay(display);
-                        mDisplayGroups.append(display.getDisplayIdLocked(), displayGroup);
+                        displayGroup.addDisplayLocked(display);
+                        mDisplayIdToGroupMap.append(display.getDisplayIdLocked(), displayGroup);
+                        mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
+                                LogicalDisplayMapper.DISPLAY_GROUP_EVENT_ADDED);
+                        changedDisplayGroup = defaultDisplayGroup;
+                    } else {
+                        changedDisplayGroup = null;
                     }
                 } else {
                     // The display should be a part of the default DisplayGroup.
-                    final DisplayGroup displayGroup = mDisplayGroups.get(displayId);
+                    final DisplayGroup displayGroup = mDisplayIdToGroupMap.get(displayId);
                     if (displayGroup != defaultDisplayGroup) {
-                        displayGroup.removeDisplay(display);
-                        defaultDisplayGroup.addDisplay(display);
-                        mDisplayGroups.put(displayId, defaultDisplayGroup);
+                        displayGroup.removeDisplayLocked(display);
+                        defaultDisplayGroup.addDisplayLocked(display);
+                        mListener.onDisplayGroupEventLocked(displayGroup.getGroupId(),
+                                LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED);
+                        mDisplayIdToGroupMap.put(displayId, defaultDisplayGroup);
+                        changedDisplayGroup = displayGroup;
+                    } else {
+                        changedDisplayGroup = null;
                     }
                 }
 
@@ -380,6 +410,7 @@
             } else if (!display.getPendingFrameRateOverrideUids().isEmpty()) {
                 mListener.onLogicalDisplayEventLocked(display,
                         LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);
+                changedDisplayGroup = null;
             } else {
                 // While applications shouldn't know nor care about the non-overridden info, we
                 // still need to let WindowManager know so it can update its own internal state for
@@ -389,6 +420,15 @@
                     mListener.onLogicalDisplayEventLocked(display,
                             LogicalDisplayMapper.LOGICAL_DISPLAY_EVENT_CHANGED);
                 }
+                changedDisplayGroup = null;
+            }
+
+            // CHANGED and REMOVED DisplayGroup events should always fire after Display events.
+            if (changedDisplayGroup != null) {
+                final int event = changedDisplayGroup.isEmptyLocked()
+                        ? LogicalDisplayMapper.DISPLAY_GROUP_EVENT_REMOVED
+                        : LogicalDisplayMapper.DISPLAY_GROUP_EVENT_CHANGED;
+                mListener.onDisplayGroupEventLocked(changedDisplayGroup.getGroupId(), event);
             }
         }
     }
@@ -424,6 +464,7 @@
 
     public interface Listener {
         void onLogicalDisplayEventLocked(LogicalDisplay display, int event);
+        void onDisplayGroupEventLocked(int groupId, int event);
         void onTraversalRequested();
     }
 }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7caf739..1fc5cb2 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -446,8 +446,25 @@
     volatile int mPowerKeyPressCounter;
     volatile boolean mEndCallKeyHandled;
     volatile boolean mCameraGestureTriggeredDuringGoingToSleep;
-    volatile boolean mGoingToSleep;
-    volatile boolean mRequestedOrGoingToSleep;
+
+    /**
+     * {@code true} if the device is entering a low-power state; {@code false otherwise}.
+     *
+     * <p>This differs from {@link #mRequestedOrSleepingDefaultDisplay} which tracks the power state
+     * of the {@link #mDefaultDisplay default display} versus the power state of the entire device.
+     */
+    volatile boolean mDeviceGoingToSleep;
+
+    /**
+     * {@code true} if the {@link #mDefaultDisplay default display} is entering or was requested to
+     * enter a low-power state; {@code false otherwise}.
+     *
+     * <p>This differs from {@link #mDeviceGoingToSleep} which tracks the power state of the entire
+     * device versus the power state of the {@link #mDefaultDisplay default display}.
+     */
+    // TODO(b/178103325): Track sleep/requested sleep for every display.
+    volatile boolean mRequestedOrSleepingDefaultDisplay;
+
     volatile boolean mRecentsVisible;
     volatile boolean mNavBarVirtualKeyHapticFeedbackEnabled = true;
     volatile boolean mPictureInPictureVisible;
@@ -914,7 +931,7 @@
         if (gestureService != null) {
             gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive,
                     mTmpBoolean);
-            if (mTmpBoolean.value && mRequestedOrGoingToSleep) {
+            if (mTmpBoolean.value && mRequestedOrSleepingDefaultDisplay) {
                 mCameraGestureTriggeredDuringGoingToSleep = true;
             }
         }
@@ -1061,13 +1078,14 @@
                 case SHORT_PRESS_POWER_NOTHING:
                     break;
                 case SHORT_PRESS_POWER_GO_TO_SLEEP:
-                    goToSleepFromPowerButton(eventTime, 0);
+                    sleepDefaultDisplayFromPowerButton(eventTime, 0);
                     break;
                 case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP:
-                    goToSleepFromPowerButton(eventTime, PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
+                    sleepDefaultDisplayFromPowerButton(eventTime,
+                            PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
                     break;
                 case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME:
-                    if (goToSleepFromPowerButton(eventTime,
+                    if (sleepDefaultDisplayFromPowerButton(eventTime,
                             PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE)) {
                         launchHomeFromHotKey(DEFAULT_DISPLAY);
                     }
@@ -1095,11 +1113,11 @@
     }
 
     /**
-     * Sends the device to sleep as a result of a power button press.
+     * Sends the default display to sleep as a result of a power button press.
      *
-     * @return True if the was device was sent to sleep, false if sleep was suppressed.
+     * @return True if the device was sent to sleep, false if the device did not sleep.
      */
-    private boolean goToSleepFromPowerButton(long eventTime, int flags) {
+    private boolean sleepDefaultDisplayFromPowerButton(long eventTime, int flags) {
         // Before we actually go to sleep, we check the last wakeup reason.
         // If the device very recently woke up from a gesture (like user lifting their device)
         // then ignore the sleep instruction. This is because users have developed
@@ -1119,12 +1137,12 @@
             }
         }
 
-        goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, flags);
+        sleepDefaultDisplay(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, flags);
         return true;
     }
 
-    private void goToSleep(long eventTime, int reason, int flags) {
-        mRequestedOrGoingToSleep = true;
+    private void sleepDefaultDisplay(long eventTime, int reason, int flags) {
+        mRequestedOrSleepingDefaultDisplay = true;
         mPowerManager.goToSleep(eventTime, reason, flags);
     }
 
@@ -1161,7 +1179,8 @@
                             Settings.Global.THEATER_MODE_ON, 1);
 
                     if (mGoToSleepOnButtonPressTheaterMode && interactive) {
-                        goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
+                        sleepDefaultDisplay(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
+                                0);
                     }
                 }
                 break;
@@ -1269,7 +1288,7 @@
             case SHORT_PRESS_SLEEP_GO_TO_SLEEP:
             case SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME:
                 Slog.i(TAG, "sleepRelease() calling goToSleep(GO_TO_SLEEP_REASON_SLEEP_BUTTON)");
-                goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0);
+                sleepDefaultDisplay(eventTime, PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON, 0);
                 break;
         }
     }
@@ -3682,7 +3701,7 @@
                             }
                             if ((mEndcallBehavior
                                     & Settings.System.END_BUTTON_BEHAVIOR_SLEEP) != 0) {
-                                goToSleep(event.getEventTime(),
+                                sleepDefaultDisplay(event.getEventTime(),
                                         PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
                                 isWakeKey = false;
                             }
@@ -3725,10 +3744,12 @@
                 // Any activity on the power button stops the accessibility shortcut
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false; // wake-up will be handled separately
+                final boolean isDefaultDisplayOn = Display.isOnState(mDefaultDisplay.getState());
+                final boolean interactiveAndOn = interactive && isDefaultDisplayOn;
                 if (down) {
-                    interceptPowerKeyDown(event, interactive);
+                    interceptPowerKeyDown(event, interactiveAndOn);
                 } else {
-                    interceptPowerKeyUp(event, interactive, canceled);
+                    interceptPowerKeyUp(event, interactiveAndOn, canceled);
                 }
                 break;
             }
@@ -4237,8 +4258,7 @@
                     + WindowManagerPolicyConstants.offReasonToString(why) + ")");
         }
 
-        mGoingToSleep = true;
-        mRequestedOrGoingToSleep = true;
+        mDeviceGoingToSleep = true;
 
         if (mKeyguardDelegate != null) {
             mKeyguardDelegate.onStartedGoingToSleep(why);
@@ -4255,8 +4275,7 @@
         }
         MetricsLogger.histogram(mContext, "screen_timeout", mLockScreenTimeout / 1000);
 
-        mGoingToSleep = false;
-        mRequestedOrGoingToSleep = false;
+        mDeviceGoingToSleep = false;
         mDefaultDisplayPolicy.setAwake(false);
 
         // We must get this work done here because the power manager will drop
@@ -4372,6 +4391,7 @@
 
         if (DEBUG_WAKEUP) Slog.i(TAG, "Screen turned off...");
 
+        mRequestedOrSleepingDefaultDisplay = false;
         updateScreenOffSleepToken(true);
         mDefaultDisplayPolicy.screenTurnedOff();
         synchronized (mLock) {
@@ -4438,6 +4458,7 @@
             return;
         }
 
+        mRequestedOrSleepingDefaultDisplay = true;
         mWindowManagerFuncs.screenTurningOff(screenOffListener);
         synchronized (mLock) {
             if (mKeyguardDelegate != null) {
@@ -4523,7 +4544,7 @@
 
     @Override
     public boolean okToAnimate() {
-        return mDefaultDisplayPolicy.isAwake() && !mGoingToSleep;
+        return mDefaultDisplayPolicy.isAwake() && !mDeviceGoingToSleep;
     }
 
     /** {@inheritDoc} */
@@ -4899,7 +4920,7 @@
                     mWindowManagerFuncs.lockDeviceNow();
                     break;
                 case LID_BEHAVIOR_SLEEP:
-                    goToSleep(SystemClock.uptimeMillis(),
+                    sleepDefaultDisplay(SystemClock.uptimeMillis(),
                             PowerManager.GO_TO_SLEEP_REASON_LID_SWITCH,
                             PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
                     break;
diff --git a/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java b/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java
new file mode 100644
index 0000000..8ebeea3
--- /dev/null
+++ b/services/core/java/com/android/server/power/DisplayGroupPowerStateMapper.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2020 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.power;
+
+import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
+import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
+import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
+
+import static com.android.server.power.DisplayGroupPowerStateMapper.DisplayGroupPowerChangeListener.DISPLAY_GROUP_ADDED;
+import static com.android.server.power.DisplayGroupPowerStateMapper.DisplayGroupPowerChangeListener.DISPLAY_GROUP_CHANGED;
+import static com.android.server.power.DisplayGroupPowerStateMapper.DisplayGroupPowerChangeListener.DISPLAY_GROUP_REMOVED;
+
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.os.Handler;
+import android.os.PowerManagerInternal;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.display.DisplayGroup;
+
+/**
+ * Responsible for creating {@link DisplayPowerRequest}s and associating them with
+ * {@link com.android.server.display.DisplayGroup}s.
+ *
+ * Each {@link com.android.server.display.DisplayGroup} has a single {@link DisplayPowerRequest}
+ * which is used to request power state changes to every display in the group.
+ */
+public class DisplayGroupPowerStateMapper {
+
+    private static final String TAG = "DisplayPowerRequestMapper";
+
+    /** Lock obtained from {@link PowerManagerService}. */
+    private final Object mLock;
+
+    /** Listener to inform of changes to display groups. */
+    private final DisplayGroupPowerChangeListener mListener;
+
+    /** A mapping from DisplayGroup Id to DisplayGroup information. */
+    @GuardedBy("mLock")
+    private final SparseArray<DisplayGroupInfo> mDisplayGroupInfos = new SparseArray<>();
+
+    /** A cached array of DisplayGroup Ids. */
+    @GuardedBy("mLock")
+    private int[] mDisplayGroupIds;
+
+    private final DisplayManagerInternal.DisplayGroupListener mDisplayGroupListener =
+            new DisplayManagerInternal.DisplayGroupListener() {
+                @Override
+                public void onDisplayGroupAdded(int groupId) {
+                    synchronized (mLock) {
+                        if (mDisplayGroupInfos.contains(groupId)) {
+                            Slog.e(TAG, "Tried to add already existing group:" + groupId);
+                            return;
+                        }
+                        // For now, only the default group supports sandman.
+                        final boolean supportsSandman = groupId == DisplayGroup.DEFAULT;
+                        final DisplayGroupInfo displayGroupInfo = new DisplayGroupInfo(
+                                new DisplayPowerRequest(),
+                                getGlobalWakefulnessLocked(), /* ready= */ false,
+                                supportsSandman);
+                        mDisplayGroupInfos.append(groupId, displayGroupInfo);
+                        mDisplayGroupIds = ArrayUtils.appendInt(mDisplayGroupIds, groupId);
+                        mListener.onDisplayGroupEventLocked(DISPLAY_GROUP_ADDED, groupId);
+                    }
+                }
+
+                @Override
+                public void onDisplayGroupRemoved(int groupId) {
+                    synchronized (mLock) {
+                        if (!mDisplayGroupInfos.contains(groupId)) {
+                            Slog.e(TAG, "Tried to remove non-existent group:" + groupId);
+                            return;
+                        }
+                        mDisplayGroupInfos.delete(groupId);
+                        mDisplayGroupIds = ArrayUtils.removeInt(mDisplayGroupIds, groupId);
+                        mListener.onDisplayGroupEventLocked(DISPLAY_GROUP_REMOVED, groupId);
+                    }
+                }
+
+                @Override
+                public void onDisplayGroupChanged(int groupId) {
+                    synchronized (mLock) {
+                        mListener.onDisplayGroupEventLocked(DISPLAY_GROUP_CHANGED, groupId);
+                    }
+                }
+            };
+
+    DisplayGroupPowerStateMapper(Object lock, DisplayManagerInternal displayManagerInternal,
+            DisplayGroupPowerChangeListener listener) {
+        mLock = lock;
+        mListener = listener;
+        displayManagerInternal.registerDisplayGroupListener(mDisplayGroupListener);
+
+        final DisplayGroupInfo displayGroupInfo = new DisplayGroupInfo(
+                new DisplayPowerRequest(), WAKEFULNESS_AWAKE, /* ready= */
+                false, /* supportsSandman= */ true);
+        mDisplayGroupInfos.append(DisplayGroup.DEFAULT, displayGroupInfo);
+        mDisplayGroupIds = new int[]{DisplayGroup.DEFAULT};
+    }
+
+    DisplayPowerRequest getPowerRequestLocked(int groupId) {
+        return mDisplayGroupInfos.get(groupId).displayPowerRequest;
+    }
+
+    int[] getDisplayGroupIdsLocked() {
+        return mDisplayGroupIds;
+    }
+
+    int getWakefulnessLocked(int groupId) {
+        return mDisplayGroupInfos.get(groupId).wakefulness;
+    }
+
+    void setLastPowerOnTimeLocked(int groupId, long eventTime) {
+        mDisplayGroupInfos.get(groupId).lastPowerOnTime = eventTime;
+    }
+
+    long getLastPowerOnTimeLocked(int groupId) {
+        return mDisplayGroupInfos.get(groupId).lastPowerOnTime;
+    }
+
+    /**
+     * Returns the amalgamated wakefulness of all {@link DisplayGroup DisplayGroups}.
+     *
+     * <p>This will be the highest wakeful state of all {@link DisplayGroup DisplayGroups}; ordered
+     * from highest to lowest:
+     * <ol>
+     *     <li>{@link PowerManagerInternal#WAKEFULNESS_AWAKE}
+     *     <li>{@link PowerManagerInternal#WAKEFULNESS_DREAMING}
+     *     <li>{@link PowerManagerInternal#WAKEFULNESS_DOZING}
+     *     <li>{@link PowerManagerInternal#WAKEFULNESS_ASLEEP}
+     * </ol>
+     */
+    int getGlobalWakefulnessLocked() {
+        final int size = mDisplayGroupInfos.size();
+        int deviceWakefulness = WAKEFULNESS_ASLEEP;
+        for (int i = 0; i < size; i++) {
+            final int wakefulness = mDisplayGroupInfos.valueAt(i).wakefulness;
+            if (wakefulness == WAKEFULNESS_AWAKE) {
+                return WAKEFULNESS_AWAKE;
+            } else if (wakefulness == WAKEFULNESS_DREAMING
+                    && (deviceWakefulness == WAKEFULNESS_ASLEEP
+                    || deviceWakefulness == WAKEFULNESS_DOZING)) {
+                deviceWakefulness = WAKEFULNESS_DREAMING;
+            } else if (wakefulness == WAKEFULNESS_DOZING
+                    && deviceWakefulness == WAKEFULNESS_ASLEEP) {
+                deviceWakefulness = WAKEFULNESS_DOZING;
+            }
+        }
+
+        return deviceWakefulness;
+    }
+
+    /**
+     * Sets the {@code wakefulness} value for the {@link DisplayGroup} specified by the provided
+     * {@code groupId}.
+     *
+     * @return {@code true} if the wakefulness value was changed; {@code false} otherwise.
+     */
+    boolean setWakefulnessLocked(int groupId, int wakefulness) {
+        final DisplayGroupInfo displayGroupInfo = mDisplayGroupInfos.get(groupId);
+        if (displayGroupInfo.wakefulness != wakefulness) {
+            displayGroupInfo.wakefulness = wakefulness;
+            return true;
+        }
+
+        return false;
+    }
+
+    boolean isSandmanSummoned(int groupId) {
+        return mDisplayGroupInfos.get(groupId).sandmanSummoned;
+    }
+
+    boolean isSandmanSupported(int groupId) {
+        return mDisplayGroupInfos.get(groupId).supportsSandman;
+    }
+
+    /**
+     * Sets whether or not the sandman is summoned for the given {@code groupId}.
+     *
+     * @param groupId         Signifies the DisplayGroup for which to summon or unsummon the
+     *                        sandman.
+     * @param sandmanSummoned {@code true} to summon the sandman; {@code false} to unsummon.
+     */
+    void setSandmanSummoned(int groupId, boolean sandmanSummoned) {
+        final DisplayGroupInfo displayGroupInfo = mDisplayGroupInfos.get(groupId);
+        displayGroupInfo.sandmanSummoned = displayGroupInfo.supportsSandman && sandmanSummoned;
+    }
+
+    /**
+     * Returns {@code true} if every display in the specified group has its requested state matching
+     * its actual state.
+     *
+     * @param groupId The identifier for the display group to check for readiness.
+     */
+    boolean isReady(int groupId) {
+        return mDisplayGroupInfos.get(groupId).ready;
+    }
+
+    /** Returns {@code true} if every display has its requested state matching its actual state. */
+    boolean areAllDisplaysReadyLocked() {
+        final int size = mDisplayGroupInfos.size();
+        for (int i = 0; i < size; i++) {
+            if (!mDisplayGroupInfos.valueAt(i).ready) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Sets whether the displays specified by the provided {@code groupId} are all ready.
+     *
+     * <p>A display is ready if its reported
+     * {@link DisplayManagerInternal.DisplayPowerCallbacks#onStateChanged() actual state} matches
+     * its {@link DisplayManagerInternal#requestPowerState requested state}.
+     *
+     * @param groupId The identifier for the display group.
+     * @param ready   {@code true} if every display in the group is ready; otherwise {@code false}.
+     * @return {@code true} if the ready state changed; otherwise {@code false}.
+     */
+    boolean setDisplayGroupReadyLocked(int groupId, boolean ready) {
+        final DisplayGroupInfo displayGroupInfo = mDisplayGroupInfos.get(groupId);
+        if (displayGroupInfo.ready != ready) {
+            displayGroupInfo.ready = ready;
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Interface through which an interested party may be informed of {@link DisplayGroup} events.
+     */
+    interface DisplayGroupPowerChangeListener {
+        int DISPLAY_GROUP_ADDED = 0;
+        int DISPLAY_GROUP_REMOVED = 1;
+        int DISPLAY_GROUP_CHANGED = 2;
+
+        void onDisplayGroupEventLocked(int event, int groupId);
+    }
+
+    private static final class DisplayGroupInfo {
+        final DisplayPowerRequest displayPowerRequest;
+        int wakefulness;
+        boolean ready;
+        long lastPowerOnTime;
+        boolean sandmanSummoned;
+
+        /** {@code true} if this DisplayGroup supports dreaming; otherwise {@code false}. */
+        boolean supportsSandman;
+
+        DisplayGroupInfo(DisplayPowerRequest displayPowerRequest, int wakefulness, boolean ready,
+                boolean supportsSandman) {
+            this.displayPowerRequest = displayPowerRequest;
+            this.wakefulness = wakefulness;
+            this.ready = ready;
+            this.supportsSandman = supportsSandman;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java b/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java
deleted file mode 100644
index 6477552..0000000
--- a/services/core/java/com/android/server/power/DisplayPowerRequestMapper.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2020 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.power;
-
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
-import android.os.Handler;
-import android.util.SparseArray;
-import android.util.SparseIntArray;
-import android.view.Display;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.server.display.DisplayGroup;
-
-/**
- * Responsible for creating {@link DisplayPowerRequest}s and associating them with
- * {@link com.android.server.display.DisplayGroup}s.
- *
- * Each {@link com.android.server.display.DisplayGroup} has a single {@link DisplayPowerRequest}
- * which is used to request power state changes to every display in the group.
- */
-class DisplayPowerRequestMapper {
-
-    private final Object mLock = new Object();
-
-    /** A mapping from LogicalDisplay Id to DisplayGroup Id. */
-    @GuardedBy("mLock")
-    private final SparseIntArray mDisplayGroupIds = new SparseIntArray();
-
-    /** A mapping from DisplayGroup Id to DisplayPowerRequest. */
-    @GuardedBy("mLock")
-    private final SparseArray<DisplayPowerRequest> mDisplayPowerRequests = new SparseArray<>();
-
-    private final DisplayManagerInternal mDisplayManagerInternal;
-
-    private final DisplayManager.DisplayListener mDisplayListener =
-            new DisplayManager.DisplayListener() {
-
-                @Override
-                public void onDisplayAdded(int displayId) {
-                    synchronized (mLock) {
-                        if (mDisplayGroupIds.indexOfKey(displayId) >= 0) {
-                            return;
-                        }
-                        final int displayGroupId = mDisplayManagerInternal.getDisplayGroupId(
-                                displayId);
-                        if (!mDisplayPowerRequests.contains(displayGroupId)) {
-                            // A new DisplayGroup was created; create a new DisplayPowerRequest.
-                            mDisplayPowerRequests.append(displayGroupId, new DisplayPowerRequest());
-                        }
-                        mDisplayGroupIds.append(displayId, displayGroupId);
-                    }
-                }
-
-                @Override
-                public void onDisplayRemoved(int displayId) {
-                    synchronized (mLock) {
-                        final int index = mDisplayGroupIds.indexOfKey(displayId);
-                        if (index < 0) {
-                            return;
-                        }
-                        final int displayGroupId = mDisplayGroupIds.valueAt(index);
-                        mDisplayGroupIds.removeAt(index);
-
-                        if (mDisplayGroupIds.indexOfValue(displayGroupId) < 0) {
-                            // The DisplayGroup no longer exists; delete the DisplayPowerRequest.
-                            mDisplayPowerRequests.delete(displayGroupId);
-                        }
-                    }
-                }
-
-                @Override
-                public void onDisplayChanged(int displayId) {
-                    synchronized (mLock) {
-                        final int newDisplayGroupId = mDisplayManagerInternal.getDisplayGroupId(
-                                displayId);
-                        final int oldDisplayGroupId = mDisplayGroupIds.get(displayId);
-
-                        if (!mDisplayPowerRequests.contains(newDisplayGroupId)) {
-                            // A new DisplayGroup was created; create a new DisplayPowerRequest.
-                            mDisplayPowerRequests.append(newDisplayGroupId,
-                                    new DisplayPowerRequest());
-                        }
-                        mDisplayGroupIds.put(displayId, newDisplayGroupId);
-
-                        if (mDisplayGroupIds.indexOfValue(oldDisplayGroupId) < 0) {
-                            // The DisplayGroup no longer exists; delete the DisplayPowerRequest.
-                            mDisplayPowerRequests.delete(oldDisplayGroupId);
-                        }
-                    }
-                }
-            };
-
-    DisplayPowerRequestMapper(DisplayManager displayManager,
-            DisplayManagerInternal displayManagerInternal, Handler handler) {
-        mDisplayManagerInternal = displayManagerInternal;
-        displayManager.registerDisplayListener(mDisplayListener, handler);
-        mDisplayPowerRequests.append(DisplayGroup.DEFAULT, new DisplayPowerRequest());
-        mDisplayGroupIds.append(Display.DEFAULT_DISPLAY, DisplayGroup.DEFAULT);
-    }
-
-    DisplayPowerRequest get(int displayId) {
-        synchronized (mLock) {
-            return mDisplayPowerRequests.get(mDisplayGroupIds.get(displayId));
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 955e1cd..8a3bd0d 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -18,6 +18,10 @@
 
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT;
+import static android.os.PowerManager.GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED;
+import static android.os.PowerManager.GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF;
+import static android.os.PowerManager.WAKE_REASON_DISPLAY_GROUP_ADDED;
+import static android.os.PowerManager.WAKE_REASON_DISPLAY_GROUP_TURNED_ON;
 import static android.os.PowerManagerInternal.MODE_DEVICE_IDLE;
 import static android.os.PowerManagerInternal.MODE_DISPLAY_INACTIVE;
 import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
@@ -42,7 +46,6 @@
 import android.hardware.SensorManager;
 import android.hardware.SystemSensorManager;
 import android.hardware.display.AmbientDisplayConfiguration;
-import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.hardware.power.Boost;
@@ -107,6 +110,7 @@
 import com.android.server.UserspaceRebootLogger;
 import com.android.server.Watchdog;
 import com.android.server.am.BatteryStatsService;
+import com.android.server.display.DisplayGroup;
 import com.android.server.lights.LightsManager;
 import com.android.server.lights.LogicalLight;
 import com.android.server.policy.WindowManagerPolicy;
@@ -176,6 +180,8 @@
     private static final int DIRTY_VR_MODE_CHANGED = 1 << 13;
     // Dirty bit: attentive timer may have timed out
     private static final int DIRTY_ATTENTIVE = 1 << 14;
+    // Dirty bit: display group power state has changed
+    private static final int DIRTY_DISPLAY_GROUP_POWER_UPDATED = 1 << 15;
 
     // Summarizes the state of all active wakelocks.
     private static final int WAKE_LOCK_CPU = 1 << 0;
@@ -297,10 +303,6 @@
     private int mWakefulnessRaw;
     private boolean mWakefulnessChanging;
 
-    // True if the sandman has just been summoned for the first time since entering the
-    // dreaming or dozing state.  Indicates whether a new dream should begin.
-    private boolean mSandmanSummoned;
-
     // True if MSG_SANDMAN has been scheduled.
     private boolean mSandmanScheduled;
 
@@ -351,11 +353,7 @@
 
     // Manages the desired power state of displays. The actual state may lag behind the
     // requested because it is updated asynchronously by the display power controller.
-    private DisplayPowerRequestMapper mDisplayPowerRequestMapper;
-
-    // True if the display power state has been fully applied, which means the display
-    // is actually on or actually off or whatever was requested.
-    private boolean mDisplayReady;
+    private DisplayGroupPowerStateMapper mDisplayGroupPowerStateMapper;
 
     // The suspend blocker used to keep the CPU alive when an application has acquired
     // a wake lock.
@@ -625,6 +623,39 @@
     // but the DreamService has not yet been told to start (it's an async process).
     private boolean mDozeStartInProgress;
 
+    private final class DisplayGroupPowerChangeListener implements
+            DisplayGroupPowerStateMapper.DisplayGroupPowerChangeListener {
+        @Override
+        public void onDisplayGroupEventLocked(int event, int groupId) {
+            final int oldWakefulness = getWakefulnessLocked();
+            final int newWakefulness = mDisplayGroupPowerStateMapper.getGlobalWakefulnessLocked();
+            if (oldWakefulness != newWakefulness) {
+                final int reason;
+                switch (newWakefulness) {
+                    case WAKEFULNESS_AWAKE:
+                        reason = event == DISPLAY_GROUP_ADDED ? WAKE_REASON_DISPLAY_GROUP_ADDED
+                                : WAKE_REASON_DISPLAY_GROUP_TURNED_ON;
+                        break;
+                    case WAKEFULNESS_DOZING:
+                        reason = event == DISPLAY_GROUP_REMOVED
+                                ? GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED
+                                : GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF;
+                        break;
+                    default:
+                        reason = 0;
+                }
+
+                setGlobalWakefulnessLocked(
+                        mDisplayGroupPowerStateMapper.getGlobalWakefulnessLocked(),
+                        mClock.uptimeMillis(), reason, Process.SYSTEM_UID, Process.SYSTEM_UID,
+                        mContext.getOpPackageName(), "groupId: " + groupId);
+            }
+
+            mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED;
+            updatePowerStateLocked();
+        }
+    }
+
     private final class ForegroundProfileObserver extends SynchronousUserSwitchObserver {
         @Override
         public void onUserSwitching(@UserIdInt int newUserId) throws RemoteException {
@@ -868,6 +899,12 @@
         void invalidateIsInteractiveCaches() {
             PowerManager.invalidateIsInteractiveCaches();
         }
+
+        DisplayGroupPowerStateMapper createDisplayPowerRequestMapper(Object lock,
+                DisplayManagerInternal displayManagerInternal,
+                DisplayGroupPowerStateMapper.DisplayGroupPowerChangeListener listener) {
+            return new DisplayGroupPowerStateMapper(lock, displayManagerInternal, listener);
+        }
     }
 
     final Constants mConstants;
@@ -1037,7 +1074,7 @@
                         now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
 
                 if (sQuiescent) {
-                    goToSleepNoUpdateLocked(mClock.uptimeMillis(),
+                    sleepDisplayGroupNoUpdateLocked(DisplayGroup.DEFAULT, mClock.uptimeMillis(),
                             PowerManager.GO_TO_SLEEP_REASON_QUIESCENT,
                             PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
                 }
@@ -1055,8 +1092,8 @@
             mPolicy = getLocalService(WindowManagerPolicy.class);
             mBatteryManagerInternal = getLocalService(BatteryManagerInternal.class);
             mAttentionDetector.systemReady(mContext);
-            mDisplayPowerRequestMapper = new DisplayPowerRequestMapper(mContext.getSystemService(
-                    DisplayManager.class), mDisplayManagerInternal, mHandler);
+            mDisplayGroupPowerStateMapper = mInjector.createDisplayPowerRequestMapper(mLock,
+                    mDisplayManagerInternal, new DisplayGroupPowerChangeListener());
 
             SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper());
 
@@ -1373,9 +1410,11 @@
                 opPackageName = wakeLock.mPackageName;
                 opUid = wakeLock.mOwnerUid;
             }
-            wakeUpNoUpdateLocked(mClock.uptimeMillis(),
-                    PowerManager.WAKE_REASON_APPLICATION, wakeLock.mTag,
-                    opUid, opPackageName, opUid);
+            for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
+                wakeDisplayGroupNoUpdateLocked(id, mClock.uptimeMillis(),
+                        PowerManager.WAKE_REASON_APPLICATION, wakeLock.mTag,
+                        opUid, opPackageName, opUid);
+            }
         }
     }
 
@@ -1664,74 +1703,69 @@
         }
     }
 
-    private void wakeUpInternal(long eventTime, @WakeReason int reason, String details, int uid,
-            String opPackageName, int opUid) {
+    private void wakeDisplayGroup(int groupId, long eventTime, @WakeReason int reason,
+            String details, int uid, String opPackageName, int opUid) {
         synchronized (mLock) {
-            if (wakeUpNoUpdateLocked(eventTime, reason, details, uid, opPackageName, opUid)) {
+            if (wakeDisplayGroupNoUpdateLocked(groupId, eventTime, reason, details, uid,
+                    opPackageName, opUid)) {
                 updatePowerStateLocked();
             }
         }
     }
 
-    private boolean wakeUpNoUpdateLocked(long eventTime, @WakeReason int reason, String details,
-            int reasonUid, String opPackageName, int opUid) {
+    private boolean wakeDisplayGroupNoUpdateLocked(int groupId, long eventTime,
+            @WakeReason int reason, String details, int uid, String opPackageName, int opUid) {
         if (DEBUG_SPEW) {
-            Slog.d(TAG, "wakeUpNoUpdateLocked: eventTime=" + eventTime + ", uid=" + reasonUid);
+            Slog.d(TAG, "wakeDisplayGroupNoUpdateLocked: eventTime=" + eventTime
+                    +", groupId=" + groupId + ", uid=" + uid);
         }
 
-        if (eventTime < mLastSleepTime || getWakefulnessLocked() == WAKEFULNESS_AWAKE
-                || mForceSuspendActive || !mSystemReady) {
+        if (eventTime < mLastSleepTime || mForceSuspendActive || !mSystemReady) {
             return false;
         }
 
-        Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, 0);
+        final int currentState = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId);
+        if (currentState == WAKEFULNESS_AWAKE) {
+            return false;
+        }
 
-        Trace.traceBegin(Trace.TRACE_TAG_POWER, "wakeUp");
+        Trace.traceBegin(Trace.TRACE_TAG_POWER, "powerOnDisplay");
         try {
-            Slog.i(TAG, "Waking up from "
-                    + PowerManagerInternal.wakefulnessToString(getWakefulnessLocked())
-                    + " (uid=" + reasonUid
+            Slog.i(TAG, "Powering on display group from"
+                    + PowerManagerInternal.wakefulnessToString(currentState)
+                    + " (groupId=" + groupId
+                    + ", uid=" + uid
                     + ", reason=" + PowerManager.wakeReasonToString(reason)
                     + ", details=" + details
                     + ")...");
+            Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, groupId);
 
-            mLastWakeTime = eventTime;
-            mLastWakeReason = reason;
-            setWakefulnessLocked(WAKEFULNESS_AWAKE, reason, eventTime);
-
-            mNotifier.onWakeUp(reason, details, reasonUid, opPackageName, opUid);
-            userActivityNoUpdateLocked(
-                    eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, reasonUid);
-
-            if (sQuiescent) {
-                mDirty |= DIRTY_QUIESCENT;
-            }
+            setWakefulnessLocked(groupId, WAKEFULNESS_AWAKE, eventTime, uid, reason, opUid,
+                    opPackageName, details);
+            mDisplayGroupPowerStateMapper.setLastPowerOnTimeLocked(groupId, eventTime);
+            mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED;
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
         }
+
         return true;
     }
 
-    private void goToSleepInternal(long eventTime, int reason, int flags, int uid) {
+    private void sleepDisplayGroup(int groupId, long eventTime, int reason, int flags,
+            int uid) {
         synchronized (mLock) {
-            if (goToSleepNoUpdateLocked(eventTime, reason, flags, uid)) {
+            if (sleepDisplayGroupNoUpdateLocked(groupId, eventTime, reason, flags, uid)) {
                 updatePowerStateLocked();
             }
         }
     }
 
-    /**
-     * Puts the system in doze.
-     *
-     * This method is called goToSleep for historical reasons but actually attempts to DOZE,
-     * and only tucks itself in to SLEEP if requested with the flag
-     * {@link PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE}.
-     */
-    @SuppressWarnings("deprecation")
-    private boolean goToSleepNoUpdateLocked(long eventTime, int reason, int flags, int uid) {
+    private boolean sleepDisplayGroupNoUpdateLocked(int groupId, long eventTime, int reason,
+            int flags, int uid) {
         if (DEBUG_SPEW) {
-            Slog.d(TAG, "goToSleepNoUpdateLocked: eventTime=" + eventTime
-                    + ", reason=" + reason + ", flags=" + flags + ", uid=" + uid);
+            Slog.d(TAG, "powerOffDisplayGroupNoUpdateLocked: eventTime=" + eventTime
+                    + ", groupId=" + groupId + ", reason=" + reason + ", flags=" + flags
+                    + ", uid=" + uid);
         }
 
         if (eventTime < mLastWakeTime
@@ -1742,55 +1776,44 @@
             return false;
         }
 
-        Trace.traceBegin(Trace.TRACE_TAG_POWER, "goToSleep");
+        final int wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId);
+        if (!PowerManagerInternal.isInteractive(wakefulness)) {
+            return false;
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_POWER, "powerOffDisplay");
         try {
             reason = Math.min(PowerManager.GO_TO_SLEEP_REASON_MAX,
                     Math.max(reason, PowerManager.GO_TO_SLEEP_REASON_MIN));
-            Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason)
-                    + " (uid " + uid + ")...");
+            Slog.i(TAG, "Powering off display group due to "
+                    + PowerManager.sleepReasonToString(reason) + " (groupId= " + groupId
+                    + ", uid= " + uid + ")...");
 
-            mLastSleepTime = eventTime;
-            mLastSleepReason = reason;
-            mSandmanSummoned = true;
-            mDozeStartInProgress = true;
-            setWakefulnessLocked(WAKEFULNESS_DOZING, reason, eventTime);
-
-            // Report the number of wake locks that will be cleared by going to sleep.
-            int numWakeLocksCleared = 0;
-            final int numWakeLocks = mWakeLocks.size();
-            for (int i = 0; i < numWakeLocks; i++) {
-                final WakeLock wakeLock = mWakeLocks.get(i);
-                switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
-                    case PowerManager.FULL_WAKE_LOCK:
-                    case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
-                    case PowerManager.SCREEN_DIM_WAKE_LOCK:
-                        numWakeLocksCleared += 1;
-                        break;
-                }
-            }
-            EventLogTags.writePowerSleepRequested(numWakeLocksCleared);
-
-            // Skip dozing if requested.
+            mDisplayGroupPowerStateMapper.setSandmanSummoned(groupId, true);
+            setWakefulnessLocked(groupId, WAKEFULNESS_DOZING, eventTime, uid, reason,
+                    /* opUid= */ 0, /* opPackageName= */ null, /* details= */ null);
             if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) {
-                reallyGoToSleepNoUpdateLocked(eventTime, uid);
+                reallySleepDisplayGroupNoUpdateLocked(groupId, eventTime, uid);
             }
+            mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED;
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
         }
         return true;
     }
 
-    private void napInternal(long eventTime, int uid) {
+    private void dreamDisplayGroup(int groupId, long eventTime, int uid) {
         synchronized (mLock) {
-            if (napNoUpdateLocked(eventTime, uid)) {
+            if (dreamDisplayGroupNoUpdateLocked(groupId, eventTime, uid)) {
                 updatePowerStateLocked();
             }
         }
     }
 
-    private boolean napNoUpdateLocked(long eventTime, int uid) {
+    private boolean dreamDisplayGroupNoUpdateLocked(int groupId, long eventTime, int uid) {
         if (DEBUG_SPEW) {
-            Slog.d(TAG, "napNoUpdateLocked: eventTime=" + eventTime + ", uid=" + uid);
+            Slog.d(TAG, "dreamDisplayGroupNoUpdateLocked: eventTime=" + eventTime
+                    + ", uid=" + uid);
         }
 
         if (eventTime < mLastWakeTime || getWakefulnessLocked() != WAKEFULNESS_AWAKE
@@ -1798,36 +1821,42 @@
             return false;
         }
 
-        Trace.traceBegin(Trace.TRACE_TAG_POWER, "nap");
+        Trace.traceBegin(Trace.TRACE_TAG_POWER, "napDisplayGroup");
         try {
-            Slog.i(TAG, "Nap time (uid " + uid +")...");
+            Slog.i(TAG, "Napping display group (groupId=" + groupId + ", uid=" + uid + ")...");
 
-            mSandmanSummoned = true;
-            setWakefulnessLocked(WAKEFULNESS_DREAMING, 0, eventTime);
+            mDisplayGroupPowerStateMapper.setSandmanSummoned(groupId, true);
+            setWakefulnessLocked(groupId, WAKEFULNESS_DREAMING, eventTime, uid, /* reason= */
+                    0, /* opUid= */ 0, /* opPackageName= */ null, /* details= */ null);
+            mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED;
+
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
         }
         return true;
     }
 
-    // Done dozing, drop everything and go to sleep.
-    private boolean reallyGoToSleepNoUpdateLocked(long eventTime, int uid) {
+    private boolean reallySleepDisplayGroupNoUpdateLocked(int groupId, long eventTime, int uid) {
         if (DEBUG_SPEW) {
-            Slog.d(TAG, "reallyGoToSleepNoUpdateLocked: eventTime=" + eventTime
+            Slog.d(TAG, "reallySleepDisplayGroupNoUpdateLocked: eventTime=" + eventTime
                     + ", uid=" + uid);
         }
 
         if (eventTime < mLastWakeTime || getWakefulnessLocked() == WAKEFULNESS_ASLEEP
-                || !mBootCompleted || !mSystemReady) {
+                || !mBootCompleted || !mSystemReady
+                || mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId)
+                == WAKEFULNESS_ASLEEP) {
             return false;
         }
 
-        Trace.traceBegin(Trace.TRACE_TAG_POWER, "reallyGoToSleep");
+        Trace.traceBegin(Trace.TRACE_TAG_POWER, "reallySleepDisplayGroup");
         try {
-            Slog.i(TAG, "Sleeping (uid " + uid +")...");
+            Slog.i(TAG, "Sleeping display group (groupId=" + groupId + ", uid=" + uid +")...");
 
-            setWakefulnessLocked(WAKEFULNESS_ASLEEP, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
-                    eventTime);
+            setWakefulnessLocked(groupId, WAKEFULNESS_ASLEEP, eventTime, uid,
+                    PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,  /* opUid= */ 0,
+                    /* opPackageName= */ null, /* details= */ null);
+            mDirty |= DIRTY_DISPLAY_GROUP_POWER_UPDATED;
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_POWER);
         }
@@ -1835,8 +1864,62 @@
     }
 
     @VisibleForTesting
-    void setWakefulnessLocked(int wakefulness, int reason, long eventTime) {
-        if (getWakefulnessLocked() != wakefulness) {
+    void setWakefulnessLocked(int groupId, int wakefulness, long eventTime, int uid, int reason,
+            int opUid, String opPackageName, String details) {
+        if (mDisplayGroupPowerStateMapper.setWakefulnessLocked(groupId, wakefulness)) {
+            setGlobalWakefulnessLocked(mDisplayGroupPowerStateMapper.getGlobalWakefulnessLocked(),
+                    eventTime, reason, uid, opUid, opPackageName, details);
+        }
+    }
+
+    private void setGlobalWakefulnessLocked(int wakefulness, long eventTime, int reason, int uid,
+            int opUid, String opPackageName, String details) {
+        if (getWakefulnessLocked() == wakefulness) {
+            return;
+        }
+
+        // Phase 1: Handle pre-wakefulness change bookkeeping.
+        final String traceMethodName;
+        switch (wakefulness) {
+            case WAKEFULNESS_ASLEEP:
+                traceMethodName = "reallyGoToSleep";
+                Slog.i(TAG, "Sleeping (uid " + uid + ")...");
+                break;
+
+            case WAKEFULNESS_AWAKE:
+                traceMethodName = "wakeUp";
+                Slog.i(TAG, "Waking up from "
+                        + PowerManagerInternal.wakefulnessToString(getWakefulnessLocked())
+                        + " (uid=" + uid
+                        + ", reason=" + PowerManager.wakeReasonToString(reason)
+                        + ", details=" + details
+                        + ")...");
+                mLastWakeTime = eventTime;
+                mLastWakeReason = reason;
+                break;
+
+            case WAKEFULNESS_DREAMING:
+                traceMethodName = "nap";
+                Slog.i(TAG, "Nap time (uid " + uid + ")...");
+                break;
+
+            case WAKEFULNESS_DOZING:
+                traceMethodName = "goToSleep";
+                Slog.i(TAG, "Going to sleep due to " + PowerManager.sleepReasonToString(reason)
+                        + " (uid " + uid + ")...");
+
+                mLastSleepTime = eventTime;
+                mLastSleepReason = reason;
+                mDozeStartInProgress = true;
+                break;
+
+            default:
+                throw new IllegalArgumentException("Unexpected wakefulness: " + wakefulness);
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_POWER, traceMethodName);
+        try {
+            // Phase 2: Handle wakefulness change and bookkeeping.
             // Under lock, invalidate before set ensures caches won't return stale values.
             mInjector.invalidateIsInteractiveCaches();
             mWakefulnessRaw = wakefulness;
@@ -1850,6 +1933,37 @@
                 mNotifier.onWakefulnessChangeStarted(wakefulness, reason, eventTime);
             }
             mAttentionDetector.onWakefulnessChangeStarted(wakefulness);
+
+            // Phase 3: Handle post-wakefulness change bookkeeping.
+            switch (wakefulness) {
+                case WAKEFULNESS_AWAKE:
+                    mNotifier.onWakeUp(reason, details, uid, opPackageName, opUid);
+                    userActivityNoUpdateLocked(
+                            eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, uid);
+                    if (sQuiescent) {
+                        mDirty |= DIRTY_QUIESCENT;
+                    }
+                    break;
+
+                case WAKEFULNESS_DOZING:
+                    // Report the number of wake locks that will be cleared by going to sleep.
+                    int numWakeLocksCleared = 0;
+                    final int numWakeLocks = mWakeLocks.size();
+                    for (int i = 0; i < numWakeLocks; i++) {
+                        final WakeLock wakeLock = mWakeLocks.get(i);
+                        switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
+                            case PowerManager.FULL_WAKE_LOCK:
+                            case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
+                            case PowerManager.SCREEN_DIM_WAKE_LOCK:
+                                numWakeLocksCleared += 1;
+                                break;
+                        }
+                    }
+                    EventLogTags.writePowerSleepRequested(numWakeLocksCleared);
+                    break;
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_POWER);
         }
     }
 
@@ -1872,7 +1986,7 @@
     }
 
     private void finishWakefulnessChangeIfNeededLocked() {
-        if (mWakefulnessChanging && mDisplayReady) {
+        if (mWakefulnessChanging && mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked()) {
             if (getWakefulnessLocked() == WAKEFULNESS_DOZING
                     && (mWakeLockSummary & WAKE_LOCK_DOZE) == 0) {
                 return; // wait until dream has enabled dozing
@@ -1884,13 +1998,6 @@
                     || getWakefulnessLocked() == WAKEFULNESS_ASLEEP) {
                 logSleepTimeoutRecapturedLocked();
             }
-            if (getWakefulnessLocked() == WAKEFULNESS_AWAKE) {
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, 0);
-                final int latencyMs = (int) (mClock.uptimeMillis() - mLastWakeTime);
-                if (latencyMs >= SCREEN_ON_LATENCY_WARNING_MS) {
-                    Slog.w(TAG, "Screen on took " + latencyMs + " ms");
-                }
-            }
             mWakefulnessChanging = false;
             mNotifier.onWakefulnessChangeFinished();
         }
@@ -1989,7 +2096,6 @@
         if ((dirty & DIRTY_BATTERY_STATE) != 0) {
             final boolean wasPowered = mIsPowered;
             final int oldPlugType = mPlugType;
-            final boolean oldLevelLow = mBatteryLevelLow;
             mIsPowered = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
             mPlugType = mBatteryManagerInternal.getPlugType();
             mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();
@@ -2018,7 +2124,8 @@
                 final long now = mClock.uptimeMillis();
                 if (shouldWakeUpWhenPluggedOrUnpluggedLocked(wasPowered, oldPlugType,
                         dockedOnWirelessCharger)) {
-                    wakeUpNoUpdateLocked(now, PowerManager.WAKE_REASON_PLUGGED_IN,
+                    wakeDisplayGroupNoUpdateLocked(DisplayGroup.DEFAULT, now,
+                            PowerManager.WAKE_REASON_PLUGGED_IN,
                             "android.server.power:PLUGGED:" + mIsPowered, Process.SYSTEM_UID,
                             mContext.getOpPackageName(), Process.SYSTEM_UID);
                 }
@@ -2300,7 +2407,8 @@
                     nextTimeout = mLastUserActivityTimeNoChangeLights + screenOffTimeout;
                     if (now < nextTimeout) {
                         final DisplayPowerRequest displayPowerRequest =
-                                mDisplayPowerRequestMapper.get(Display.DEFAULT_DISPLAY);
+                                mDisplayGroupPowerStateMapper.getPowerRequestLocked(
+                                        DisplayGroup.DEFAULT);
                         if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_BRIGHT
                                 || displayPowerRequest.policy == DisplayPowerRequest.POLICY_VR) {
                             mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
@@ -2555,13 +2663,23 @@
                 }
                 final long time = mClock.uptimeMillis();
                 if (isAttentiveTimeoutExpired(time)) {
-                    changed = goToSleepNoUpdateLocked(time, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
-                            PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
+                    // TODO (b/175764389): Support per-display timeouts.
+                    for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
+                        changed = sleepDisplayGroupNoUpdateLocked(id, time,
+                                PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
+                                PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
+                    }
                 } else if (shouldNapAtBedTimeLocked()) {
-                    changed = napNoUpdateLocked(time, Process.SYSTEM_UID);
+                    // TODO (b/175764389): Support per-display timeouts.
+                    for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
+                        changed = dreamDisplayGroupNoUpdateLocked(id, time, Process.SYSTEM_UID);
+                    }
                 } else {
-                    changed = goToSleepNoUpdateLocked(time,
-                            PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, 0, Process.SYSTEM_UID);
+                    // TODO (b/175764389): Support per-display timeouts.
+                    for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
+                        changed = sleepDisplayGroupNoUpdateLocked(id, time,
+                                PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, 0, Process.SYSTEM_UID);
+                    }
                 }
             }
         }
@@ -2644,7 +2762,7 @@
                 | DIRTY_STAY_ON
                 | DIRTY_PROXIMITY_POSITIVE
                 | DIRTY_BATTERY_STATE)) != 0 || displayBecameReady) {
-            if (mDisplayReady) {
+            if (mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked()) {
                 scheduleSandmanLocked();
             }
         }
@@ -2659,6 +2777,14 @@
         }
     }
 
+    private void handleSandman() {
+        for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
+            if (mDisplayGroupPowerStateMapper.isSandmanSupported(id)) {
+                handleSandman(id);
+            }
+        }
+    }
+
     /**
      * Called when the device enters or exits a dreaming or dozing state.
      *
@@ -2666,16 +2792,18 @@
      * the dream and we don't want to hold our lock while doing so.  There is a risk that
      * the device will wake or go to sleep in the meantime so we have to handle that case.
      */
-    private void handleSandman() { // runs on handler thread
+    private void handleSandman(int groupId) { // runs on handler thread
         // Handle preconditions.
         final boolean startDreaming;
         final int wakefulness;
         synchronized (mLock) {
             mSandmanScheduled = false;
+            // TODO (b/175764708): Support per-display doze.
             wakefulness = getWakefulnessLocked();
-            if (mSandmanSummoned && mDisplayReady) {
-                startDreaming = canDreamLocked() || canDozeLocked();
-                mSandmanSummoned = false;
+            if (mDisplayGroupPowerStateMapper.isSandmanSummoned(groupId)
+                    && mDisplayGroupPowerStateMapper.isReady(groupId)) {
+                startDreaming = canDreamLocked(groupId) || canDozeLocked();
+                mDisplayGroupPowerStateMapper.setSandmanSummoned(groupId, false);
             } else {
                 startDreaming = false;
             }
@@ -2714,14 +2842,15 @@
 
             // If preconditions changed, wait for the next iteration to determine
             // whether the dream should continue (or be restarted).
-            if (mSandmanSummoned || getWakefulnessLocked() != wakefulness) {
+            if (mDisplayGroupPowerStateMapper.isSandmanSummoned(groupId)
+                    || mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId) != wakefulness) {
                 return; // wait for next cycle
             }
 
             // Determine whether the dream should continue.
             long now = mClock.uptimeMillis();
             if (wakefulness == WAKEFULNESS_DREAMING) {
-                if (isDreaming && canDreamLocked()) {
+                if (isDreaming && canDreamLocked(groupId)) {
                     if (mDreamsBatteryLevelDrainCutoffConfig >= 0
                             && mBatteryLevel < mBatteryLevelWhenDreamStarted
                                     - mDreamsBatteryLevelDrainCutoffConfig
@@ -2741,16 +2870,13 @@
 
                 // Dream has ended or will be stopped.  Update the power state.
                 if (isItBedTimeYetLocked()) {
-                    int flags = 0;
-                    if (isAttentiveTimeoutExpired(now)) {
-                        flags |= PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE;
-                    }
-                    goToSleepNoUpdateLocked(now, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, flags,
-                            Process.SYSTEM_UID);
+                    final int flags = isAttentiveTimeoutExpired(now)
+                            ? PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE : 0;
+                    sleepDisplayGroupNoUpdateLocked(groupId, now,
+                            PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, flags, Process.SYSTEM_UID);
                     updatePowerStateLocked();
                 } else {
-                    wakeUpNoUpdateLocked(now,
-                            PowerManager.WAKE_REASON_UNKNOWN,
+                    wakeDisplayGroupNoUpdateLocked(groupId, now, PowerManager.WAKE_REASON_UNKNOWN,
                             "android.server.power:DREAM_FINISHED", Process.SYSTEM_UID,
                             mContext.getOpPackageName(), Process.SYSTEM_UID);
                     updatePowerStateLocked();
@@ -2761,7 +2887,7 @@
                 }
 
                 // Doze has ended or will be stopped.  Update the power state.
-                reallyGoToSleepNoUpdateLocked(now, Process.SYSTEM_UID);
+                reallySleepDisplayGroupNoUpdateLocked(groupId, now, Process.SYSTEM_UID);
                 updatePowerStateLocked();
             }
         }
@@ -2773,11 +2899,11 @@
     }
 
     /**
-     * Returns true if the device is allowed to dream in its current state.
+     * Returns true if the {@code groupId} is allowed to dream in its current state.
      */
-    private boolean canDreamLocked() {
+    private boolean canDreamLocked(int groupId) {
         final DisplayPowerRequest displayPowerRequest =
-                mDisplayPowerRequestMapper.get(Display.DEFAULT_DISPLAY);
+                mDisplayGroupPowerStateMapper.getPowerRequestLocked(groupId);
         if (getWakefulnessLocked() != WAKEFULNESS_DREAMING
                 || !mDreamsSupportedConfig
                 || !mDreamsEnabledSetting
@@ -2815,91 +2941,113 @@
 
     /**
      * Updates the display power state asynchronously.
-     * When the update is finished, mDisplayReady will be set to true.  The display
-     * controller posts a message to tell us when the actual display power state
+     * When the update is finished, the ready state of the displays will be updated.  The display
+     * controllers post a message to tell us when the actual display power state
      * has been updated so we come back here to double-check and finish up.
      *
      * This function recalculates the display power state each time.
      *
-     * @return True if the display became ready.
+     * @return {@code true} if all displays became ready; {@code false} otherwise
      */
     private boolean updateDisplayPowerStateLocked(int dirty) {
-        final boolean oldDisplayReady = mDisplayReady;
+        final boolean oldDisplayReady = mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked();
         if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS
                 | DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_BOOT_COMPLETED
                 | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST | DIRTY_VR_MODE_CHANGED |
-                DIRTY_QUIESCENT)) != 0) {
+                DIRTY_QUIESCENT | DIRTY_DISPLAY_GROUP_POWER_UPDATED)) != 0) {
             if ((dirty & DIRTY_QUIESCENT) != 0) {
                 sQuiescent = false;
             }
 
-            final DisplayPowerRequest displayPowerRequest = mDisplayPowerRequestMapper.get(
-                    Display.DEFAULT_DISPLAY);
-            displayPowerRequest.policy = getDesiredScreenPolicyLocked();
+            for (final int groupId : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
+                final DisplayPowerRequest displayPowerRequest =
+                        mDisplayGroupPowerStateMapper.getPowerRequestLocked(groupId);
+                displayPowerRequest.policy = getDesiredScreenPolicyLocked(groupId);
 
-            // Determine appropriate screen brightness and auto-brightness adjustments.
-            final boolean autoBrightness;
-            final float screenBrightnessOverride;
-            if (!mBootCompleted) {
-                // Keep the brightness steady during boot. This requires the
-                // bootloader brightness and the default brightness to be identical.
-                autoBrightness = false;
-                screenBrightnessOverride = mScreenBrightnessDefault;
-            } else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) {
-                autoBrightness = false;
-                screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager;
-            } else {
-                autoBrightness = (mScreenBrightnessModeSetting ==
-                        Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
-                screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-            }
+                // Determine appropriate screen brightness and auto-brightness adjustments.
+                final boolean autoBrightness;
+                final float screenBrightnessOverride;
+                if (!mBootCompleted) {
+                    // Keep the brightness steady during boot. This requires the
+                    // bootloader brightness and the default brightness to be identical.
+                    autoBrightness = false;
+                    screenBrightnessOverride = mScreenBrightnessDefault;
+                } else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) {
+                    autoBrightness = false;
+                    screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager;
+                } else {
+                    autoBrightness = (mScreenBrightnessModeSetting ==
+                            Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+                    screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+                }
 
-            // Update display power request.
-            displayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
-            displayPowerRequest.useAutoBrightness = autoBrightness;
-            displayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked();
-            displayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness();
+                // Update display power request.
+                displayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
+                displayPowerRequest.useAutoBrightness = autoBrightness;
+                displayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked();
+                displayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness();
 
-            updatePowerRequestFromBatterySaverPolicy(displayPowerRequest);
+                updatePowerRequestFromBatterySaverPolicy(displayPowerRequest);
 
-            if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
-                displayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
-                if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0
-                        && !mDrawWakeLockOverrideFromSidekick) {
-                    if (displayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND) {
-                        displayPowerRequest.dozeScreenState = Display.STATE_DOZE;
+                if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
+                    displayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
+                    if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0
+                            && !mDrawWakeLockOverrideFromSidekick) {
+                        if (displayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND) {
+                            displayPowerRequest.dozeScreenState = Display.STATE_DOZE;
+                        }
+                        if (displayPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND) {
+                            displayPowerRequest.dozeScreenState = Display.STATE_ON;
+                        }
                     }
-                    if (displayPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND) {
-                        displayPowerRequest.dozeScreenState = Display.STATE_ON;
+                    displayPowerRequest.dozeScreenBrightness =
+                            mDozeScreenBrightnessOverrideFromDreamManagerFloat;
+                } else {
+                    displayPowerRequest.dozeScreenState = Display.STATE_UNKNOWN;
+                    displayPowerRequest.dozeScreenBrightness =
+                            PowerManager.BRIGHTNESS_INVALID_FLOAT;
+                }
+
+                final boolean ready = mDisplayManagerInternal.requestPowerState(groupId,
+                        displayPowerRequest, mRequestWaitForNegativeProximity);
+
+                if (DEBUG_SPEW) {
+                    Slog.d(TAG, "updateDisplayPowerStateLocked: displayReady=" + ready
+                            + ", groupId=" + groupId
+                            + ", policy=" + displayPowerRequest.policy
+                            + ", mWakefulness="
+                            + mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId)
+                            + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary)
+                            + ", mUserActivitySummary=0x" + Integer.toHexString(
+                            mUserActivitySummary)
+                            + ", mBootCompleted=" + mBootCompleted
+                            + ", screenBrightnessOverride="
+                            + displayPowerRequest.screenBrightnessOverride
+                            + ", useAutoBrightness=" + displayPowerRequest.useAutoBrightness
+                            + ", mScreenBrightnessBoostInProgress="
+                            + mScreenBrightnessBoostInProgress
+                            + ", mIsVrModeEnabled= " + mIsVrModeEnabled
+                            + ", sQuiescent=" + sQuiescent);
+                }
+
+                final boolean displayReadyStateChanged =
+                        mDisplayGroupPowerStateMapper.setDisplayGroupReadyLocked(groupId, ready);
+                if (ready && displayReadyStateChanged
+                        && mDisplayGroupPowerStateMapper.getWakefulnessLocked(
+                        groupId) == WAKEFULNESS_AWAKE) {
+                    Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, TRACE_SCREEN_ON, groupId);
+                    final int latencyMs = (int) (mClock.uptimeMillis()
+                            - mDisplayGroupPowerStateMapper.getLastPowerOnTimeLocked(groupId));
+                    if (latencyMs >= SCREEN_ON_LATENCY_WARNING_MS) {
+                        Slog.w(TAG, "Screen on took " + latencyMs + " ms");
                     }
                 }
-                displayPowerRequest.dozeScreenBrightness =
-                        mDozeScreenBrightnessOverrideFromDreamManagerFloat;
-            } else {
-                displayPowerRequest.dozeScreenState = Display.STATE_UNKNOWN;
-                displayPowerRequest.dozeScreenBrightness =
-                        PowerManager.BRIGHTNESS_INVALID_FLOAT;
             }
 
-            mDisplayReady = mDisplayManagerInternal.requestPowerState(displayPowerRequest,
-                    mRequestWaitForNegativeProximity);
             mRequestWaitForNegativeProximity = false;
 
-            if (DEBUG_SPEW) {
-                Slog.d(TAG, "updateDisplayPowerStateLocked: mDisplayReady=" + mDisplayReady
-                        + ", policy=" + displayPowerRequest.policy
-                        + ", mWakefulness=" + getWakefulnessLocked()
-                        + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary)
-                        + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary)
-                        + ", mBootCompleted=" + mBootCompleted
-                        + ", screenBrightnessOverride=" + screenBrightnessOverride
-                        + ", useAutoBrightness=" + autoBrightness
-                        + ", mScreenBrightnessBoostInProgress=" + mScreenBrightnessBoostInProgress
-                        + ", mIsVrModeEnabled= " + mIsVrModeEnabled
-                        + ", sQuiescent=" + sQuiescent);
-            }
         }
-        return mDisplayReady && !oldDisplayReady;
+        return mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked() && !oldDisplayReady;
     }
 
     private void updateScreenBrightnessBoostLocked(int dirty) {
@@ -2933,12 +3081,11 @@
     }
 
     @VisibleForTesting
-    int getDesiredScreenPolicyLocked() {
-        if (getWakefulnessLocked() == WAKEFULNESS_ASLEEP || sQuiescent) {
+    int getDesiredScreenPolicyLocked(int groupId) {
+        final int wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId);
+        if (wakefulness == WAKEFULNESS_ASLEEP || sQuiescent) {
             return DisplayPowerRequest.POLICY_OFF;
-        }
-
-        if (getWakefulnessLocked() == WAKEFULNESS_DOZING) {
+        } else if (wakefulness == WAKEFULNESS_DOZING) {
             if ((mWakeLockSummary & WAKE_LOCK_DOZE) != 0) {
                 return DisplayPowerRequest.POLICY_DOZE;
             }
@@ -2968,7 +3115,6 @@
 
     private final DisplayManagerInternal.DisplayPowerCallbacks mDisplayPowerCallbacks =
             new DisplayManagerInternal.DisplayPowerCallbacks() {
-        private int mDisplayState = Display.STATE_UNKNOWN;
 
         @Override
         public void onStateChanged() {
@@ -2999,29 +3145,25 @@
         }
 
         @Override
-        public void onDisplayStateChange(int state) {
+        public void onDisplayStateChange(boolean allInactive, boolean allOff) {
             // This method is only needed to support legacy display blanking behavior
             // where the display's power state is coupled to suspend or to the power HAL.
             // The order of operations matters here.
             synchronized (mLock) {
-                if (mDisplayState != state) {
-                    mDisplayState = state;
-                    setPowerModeInternal(MODE_DISPLAY_INACTIVE,
-                            !Display.isActiveState(state));
-                    if (state == Display.STATE_OFF) {
-                        if (!mDecoupleHalInteractiveModeFromDisplayConfig) {
-                            setHalInteractiveModeLocked(false);
-                        }
-                        if (!mDecoupleHalAutoSuspendModeFromDisplayConfig) {
-                            setHalAutoSuspendModeLocked(true);
-                        }
-                    } else {
-                        if (!mDecoupleHalAutoSuspendModeFromDisplayConfig) {
-                            setHalAutoSuspendModeLocked(false);
-                        }
-                        if (!mDecoupleHalInteractiveModeFromDisplayConfig) {
-                            setHalInteractiveModeLocked(true);
-                        }
+                setPowerModeInternal(MODE_DISPLAY_INACTIVE, allInactive);
+                if (allOff) {
+                    if (!mDecoupleHalInteractiveModeFromDisplayConfig) {
+                        setHalInteractiveModeLocked(false);
+                    }
+                    if (!mDecoupleHalAutoSuspendModeFromDisplayConfig) {
+                        setHalAutoSuspendModeLocked(true);
+                    }
+                } else {
+                    if (!mDecoupleHalAutoSuspendModeFromDisplayConfig) {
+                        setHalAutoSuspendModeLocked(false);
+                    }
+                    if (!mDecoupleHalInteractiveModeFromDisplayConfig) {
+                        setHalInteractiveModeLocked(true);
                     }
                 }
             }
@@ -3036,13 +3178,6 @@
         public void releaseSuspendBlocker() {
             mDisplaySuspendBlocker.release();
         }
-
-        @Override
-        public String toString() {
-            synchronized (this) {
-                return "state=" + Display.stateToString(mDisplayState);
-            }
-        }
     };
 
     private boolean shouldUseProximitySensorLocked() {
@@ -3058,9 +3193,13 @@
         final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0);
         final boolean needDisplaySuspendBlocker = needDisplaySuspendBlockerLocked();
         final boolean autoSuspend = !needDisplaySuspendBlocker;
-        final DisplayPowerRequest displayPowerRequest = mDisplayPowerRequestMapper.get(
-                Display.DEFAULT_DISPLAY);
-        final boolean interactive = displayPowerRequest.isBrightOrDim();
+        final DisplayPowerRequest displayPowerRequest =
+                mDisplayGroupPowerStateMapper.getPowerRequestLocked(DisplayGroup.DEFAULT);
+        final int[] groupIds = mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked();
+        boolean interactive = false;
+        for (int id : groupIds) {
+            interactive |= mDisplayGroupPowerStateMapper.getPowerRequestLocked(id).isBrightOrDim();
+        }
 
         // Disable auto-suspend if needed.
         // FIXME We should consider just leaving auto-suspend enabled forever since
@@ -3090,7 +3229,7 @@
             // until the display is actually ready so that all transitions have
             // completed.  This is probably a good sign that things have gotten
             // too tangled over here...
-            if (interactive || mDisplayReady) {
+            if (interactive || mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked()) {
                 setHalInteractiveModeLocked(interactive);
             }
         }
@@ -3116,29 +3255,10 @@
      * We do so if the screen is on or is in transition between states.
      */
     private boolean needDisplaySuspendBlockerLocked() {
-        if (!mDisplayReady) {
+        if (!mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked()) {
             return true;
         }
-        final DisplayPowerRequest displayPowerRequest = mDisplayPowerRequestMapper.get(
-                Display.DEFAULT_DISPLAY);
-        if (displayPowerRequest.isBrightOrDim()) {
-            // If we asked for the screen to be on but it is off due to the proximity
-            // sensor then we may suspend but only if the configuration allows it.
-            // On some hardware it may not be safe to suspend because the proximity
-            // sensor may not be correctly configured as a wake-up source.
-            if (!displayPowerRequest.useProximitySensor || !mProximityPositive
-                    || !mSuspendWhenScreenOffDueToProximityConfig) {
-                return true;
-            }
-        }
 
-        if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE
-                && displayPowerRequest.dozeScreenState == Display.STATE_ON) {
-            // Although we are in DOZE and would normally allow the device to suspend,
-            // the doze service has explicitly requested the display to remain in the ON
-            // state which means we should hold the display suspend blocker.
-            return true;
-        }
         if (mScreenBrightnessBoostInProgress) {
             return true;
         }
@@ -3152,6 +3272,30 @@
             return true;
         }
 
+        final int[] groupIds = mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked();
+        for (int id : groupIds) {
+            final DisplayPowerRequest displayPowerRequest =
+                    mDisplayGroupPowerStateMapper.getPowerRequestLocked(id);
+            if (displayPowerRequest.isBrightOrDim()) {
+                // If we asked for the screen to be on but it is off due to the proximity
+                // sensor then we may suspend but only if the configuration allows it.
+                // On some hardware it may not be safe to suspend because the proximity
+                // sensor may not be correctly configured as a wake-up source.
+                if (!displayPowerRequest.useProximitySensor || !mProximityPositive
+                        || !mSuspendWhenScreenOffDueToProximityConfig) {
+                    return true;
+                }
+            }
+
+            if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE
+                    && displayPowerRequest.dozeScreenState == Display.STATE_ON) {
+                // Although we are in DOZE and would normally allow the device to suspend,
+                // the doze service has explicitly requested the display to remain in the ON
+                // state which means we should hold the display suspend blocker.
+                return true;
+            }
+        }
+
         // Let the system suspend if the screen is off or dozing.
         return false;
     }
@@ -3685,9 +3829,15 @@
             synchronized (mLock) {
                 mForceSuspendActive = true;
                 // Place the system in an non-interactive state
-                goToSleepInternal(mClock.uptimeMillis(),
-                        PowerManager.GO_TO_SLEEP_REASON_FORCE_SUSPEND,
-                        PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, uid);
+                boolean updatePowerState = false;
+                for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
+                    updatePowerState |= sleepDisplayGroupNoUpdateLocked(id, mClock.uptimeMillis(),
+                            PowerManager.GO_TO_SLEEP_REASON_FORCE_SUSPEND,
+                            PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, uid);
+                }
+                if (updatePowerState) {
+                    updatePowerStateLocked();
+                }
 
                 // Disable all the partial wake locks as well
                 updateWakeLockDisabledStatesLocked();
@@ -3827,7 +3977,6 @@
             pw.println("  mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary));
             pw.println("  mRequestWaitForNegativeProximity=" + mRequestWaitForNegativeProximity);
             pw.println("  mSandmanScheduled=" + mSandmanScheduled);
-            pw.println("  mSandmanSummoned=" + mSandmanSummoned);
             pw.println("  mBatteryLevelLow=" + mBatteryLevelLow);
             pw.println("  mLightDeviceIdleMode=" + mLightDeviceIdleMode);
             pw.println("  mDeviceIdleMode=" + mDeviceIdleMode);
@@ -3845,7 +3994,6 @@
                     + TimeUtils.formatUptime(mLastScreenBrightnessBoostTime));
             pw.println("  mScreenBrightnessBoostInProgress="
                     + mScreenBrightnessBoostInProgress);
-            pw.println("  mDisplayReady=" + mDisplayReady);
             pw.println("  mHoldingWakeLockSuspendBlocker=" + mHoldingWakeLockSuspendBlocker);
             pw.println("  mHoldingDisplaySuspendBlocker=" + mHoldingDisplaySuspendBlocker);
 
@@ -4080,7 +4228,6 @@
                     PowerManagerServiceDumpProto.IS_REQUEST_WAIT_FOR_NEGATIVE_PROXIMITY,
                     mRequestWaitForNegativeProximity);
             proto.write(PowerManagerServiceDumpProto.IS_SANDMAN_SCHEDULED, mSandmanScheduled);
-            proto.write(PowerManagerServiceDumpProto.IS_SANDMAN_SUMMONED, mSandmanSummoned);
             proto.write(PowerManagerServiceDumpProto.IS_BATTERY_LEVEL_LOW, mBatteryLevelLow);
             proto.write(PowerManagerServiceDumpProto.IS_LIGHT_DEVICE_IDLE_MODE, mLightDeviceIdleMode);
             proto.write(PowerManagerServiceDumpProto.IS_DEVICE_IDLE_MODE, mDeviceIdleMode);
@@ -4107,7 +4254,6 @@
             proto.write(
                     PowerManagerServiceDumpProto.IS_SCREEN_BRIGHTNESS_BOOST_IN_PROGRESS,
                     mScreenBrightnessBoostInProgress);
-            proto.write(PowerManagerServiceDumpProto.IS_DISPLAY_READY, mDisplayReady);
             proto.write(
                     PowerManagerServiceDumpProto.IS_HOLDING_WAKE_LOCK_SUSPEND_BLOCKER,
                     mHoldingWakeLockSuspendBlocker);
@@ -4921,7 +5067,8 @@
             final int uid = Binder.getCallingUid();
             final long ident = Binder.clearCallingIdentity();
             try {
-                wakeUpInternal(eventTime, reason, details, uid, opPackageName, uid);
+                wakeDisplayGroup(DisplayGroup.DEFAULT, eventTime, reason, details, uid,
+                        opPackageName, uid);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -4939,7 +5086,7 @@
             final int uid = Binder.getCallingUid();
             final long ident = Binder.clearCallingIdentity();
             try {
-                goToSleepInternal(eventTime, reason, flags, uid);
+                sleepDisplayGroup(DisplayGroup.DEFAULT, eventTime, reason, flags, uid);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -4957,7 +5104,7 @@
             final int uid = Binder.getCallingUid();
             final long ident = Binder.clearCallingIdentity();
             try {
-                napInternal(eventTime, uid);
+                dreamDisplayGroup(DisplayGroup.DEFAULT, eventTime, uid);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -5590,7 +5737,7 @@
             // also tells us that we're not already ignoring the proximity sensor.
 
             final DisplayPowerRequest displayPowerRequest =
-                    mDisplayPowerRequestMapper.get(Display.DEFAULT_DISPLAY);
+                    mDisplayGroupPowerStateMapper.getPowerRequestLocked(DisplayGroup.DEFAULT);
             if (displayPowerRequest.useProximitySensor && mProximityPositive) {
                 mDisplayManagerInternal.ignoreProximitySensorUntilChanged();
                 return true;
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index 7f35511..f07197c 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -77,6 +77,7 @@
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.display.DisplayGroup;
 import com.android.server.lights.LightsManager;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.power.PowerManagerService.BatteryReceiver;
@@ -97,9 +98,12 @@
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * Tests for {@link com.android.server.power.PowerManagerService}.
@@ -178,7 +182,8 @@
                 .thenReturn(mPowerSaveState);
         when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(false);
         when(mInattentiveSleepWarningControllerMock.isShown()).thenReturn(false);
-        when(mDisplayManagerInternalMock.requestPowerState(any(), anyBoolean())).thenReturn(true);
+        when(mDisplayManagerInternalMock.requestPowerState(anyInt(), any(),
+                anyBoolean())).thenReturn(true);
         when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
         when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
 
@@ -399,30 +404,33 @@
     @Test
     public void testGetDesiredScreenPolicy_WithVR() throws Exception {
         createService();
+        mService.systemReady(null);
         // Brighten up the screen
-        mService.setWakefulnessLocked(WAKEFULNESS_AWAKE, PowerManager.WAKE_REASON_UNKNOWN, 0);
-        assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo(
+        mService.setWakefulnessLocked(DisplayGroup.DEFAULT, WAKEFULNESS_AWAKE, 0, 0, 0, 0, null,
+                null);
+        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
                 DisplayPowerRequest.POLICY_BRIGHT);
 
         // Move to VR
         mService.setVrModeEnabled(true);
-        assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo(
+        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
                 DisplayPowerRequest.POLICY_VR);
 
         // Then take a nap
-        mService.setWakefulnessLocked(WAKEFULNESS_ASLEEP, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
-                0);
-        assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo(
+        mService.setWakefulnessLocked(DisplayGroup.DEFAULT, WAKEFULNESS_ASLEEP, 0, 0, 0, 0, null,
+                null);
+        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
                 DisplayPowerRequest.POLICY_OFF);
 
         // Wake up to VR
-        mService.setWakefulnessLocked(WAKEFULNESS_AWAKE, PowerManager.WAKE_REASON_UNKNOWN, 0);
-        assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo(
+        mService.setWakefulnessLocked(DisplayGroup.DEFAULT, WAKEFULNESS_AWAKE, 0, 0, 0, 0, null,
+                null);
+        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
                 DisplayPowerRequest.POLICY_VR);
 
         // And back to normal
         mService.setVrModeEnabled(false);
-        assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo(
+        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
                 DisplayPowerRequest.POLICY_BRIGHT);
     }
 
@@ -673,8 +681,9 @@
     }
 
     @Test
-    public void testForceSuspend_forceSuspendFailurePropogated() {
+    public void testForceSuspend_forceSuspendFailurePropagated() throws Exception {
         createService();
+        startSystem();
         when(mNativeWrapperMock.nativeForceSuspend()).thenReturn(false);
         assertThat(mService.getBinderServiceInstance().forceSuspend()).isFalse();
     }
@@ -838,7 +847,7 @@
         createService();
         startSystem();
 
-        assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo(
+        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
                 DisplayPowerRequest.POLICY_BRIGHT);
     }
 
@@ -857,11 +866,8 @@
     public void testQuiescentBoot_DesiredScreenPolicyShouldBeOff() throws Exception {
         when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
         createService();
-        assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo(
-                DisplayPowerRequest.POLICY_OFF);
-
         startSystem();
-        assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo(
+        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
                 DisplayPowerRequest.POLICY_OFF);
     }
 
@@ -871,7 +877,7 @@
         createService();
         startSystem();
         forceAwake();
-        assertThat(mService.getDesiredScreenPolicyLocked()).isEqualTo(
+        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
                 DisplayPowerRequest.POLICY_BRIGHT);
     }
 
@@ -1143,4 +1149,85 @@
         assertFalse(
                 mService.getBinderServiceInstance().setPowerModeChecked(Mode.INTERACTIVE, false));
     }
+
+    @Test
+    public void testMultiDisplay_wakefulnessUpdates() throws Exception {
+        final int nonDefaultDisplayGroupId = DisplayGroup.DEFAULT + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(DisplayGroup.DEFAULT, WAKEFULNESS_ASLEEP, 0, 0, 0, 0, null,
+                null);
+        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
+                null, null);
+        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+
+        mService.setWakefulnessLocked(DisplayGroup.DEFAULT, WAKEFULNESS_AWAKE, 0, 0, 0, 0, null,
+                null);
+        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
+
+    @Test
+    public void testMultiDisplay_addDisplayGroup_preservesWakefulness() throws Exception {
+        final int nonDefaultDisplayGroupId = DisplayGroup.DEFAULT + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+
+        createService();
+        startSystem();
+
+        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(DisplayGroup.DEFAULT, WAKEFULNESS_ASLEEP, 0, 0, 0, 0, null,
+                null);
+        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+    }
+
+    @Test
+    public void testMultiDisplay_removeDisplayGroup_updatesWakefulness() throws Exception {
+        final int nonDefaultDisplayGroupId = DisplayGroup.DEFAULT + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+
+        createService();
+        startSystem();
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        mService.setWakefulnessLocked(DisplayGroup.DEFAULT, WAKEFULNESS_ASLEEP, 0, 0, 0, 0, null,
+                null);
+        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        listener.get().onDisplayGroupRemoved(nonDefaultDisplayGroupId);
+        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
+
+        mService.setWakefulnessLocked(DisplayGroup.DEFAULT, WAKEFULNESS_AWAKE, 0, 0, 0, 0, null,
+                null);
+        assertThat(mService.getWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+    }
 }