Improve PowerGroup event notification

When the default display group is asleep/dozing and some other display
in another group becomes awake, we were sending incorrect and
misleading information from PowerManagerService to
PhoneWindowManager. In particular, we were conflating the "global
wakefulness" with the wakefulness of the default display, which led to
problems like the default display waking up when it shouldn't and
Always On Display ceasing to work.

This CL refactors things to make the distinction more clear between
global wakefulness changes and specific display PowerGroup changes.

Bug: 255688811
Test: atest PowerManagerServiceTest
Change-Id: I809d5fcd8eaa61dc2d07b63a0ae3b5aa924faa1c
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 247a5c0..375f53b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1004,7 +1004,7 @@
             return;
         }
 
-        final boolean interactive = Display.isOnState(mDefaultDisplay.getState());
+        final boolean interactive = mDefaultDisplayPolicy.isAwake();
 
         Slog.d(TAG, "powerPress: eventTime=" + eventTime + " interactive=" + interactive
                 + " count=" + count + " beganFromNonInteractive=" + beganFromNonInteractive
@@ -2201,8 +2201,10 @@
 
         // Match current screen state.
         if (!mPowerManager.isInteractive()) {
-            startedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
-            finishedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
+            startedGoingToSleep(Display.DEFAULT_DISPLAY_GROUP,
+                    PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
+            finishedGoingToSleep(Display.DEFAULT_DISPLAY_GROUP,
+                    PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
         }
 
         mWindowManagerInternal.registerAppTransitionListener(new AppTransitionListener() {
@@ -4095,7 +4097,7 @@
 
         // This could prevent some wrong state in multi-displays environment,
         // the default display may turned off but interactive is true.
-        final boolean isDefaultDisplayOn = Display.isOnState(mDefaultDisplay.getState());
+        final boolean isDefaultDisplayOn = mDefaultDisplayPolicy.isAwake();
         final boolean interactiveAndOn = interactive && isDefaultDisplayOn;
         if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
             handleKeyGesture(event, interactiveAndOn);
@@ -4795,17 +4797,40 @@
         }
     };
 
+    @Override
+    public void startedWakingUpGlobal(@WakeReason int reason) {
+
+    }
+
+    @Override
+    public void finishedWakingUpGlobal(@WakeReason int reason) {
+
+    }
+
+    @Override
+    public void startedGoingToSleepGlobal(@PowerManager.GoToSleepReason int reason) {
+        mDeviceGoingToSleep = true;
+    }
+
+    @Override
+    public void finishedGoingToSleepGlobal(@PowerManager.GoToSleepReason int reason) {
+        mDeviceGoingToSleep = false;
+    }
+
     // Called on the PowerManager's Notifier thread.
     @Override
-    public void startedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason) {
+    public void startedGoingToSleep(int displayGroupId,
+            @PowerManager.GoToSleepReason int pmSleepReason) {
         if (DEBUG_WAKEUP) {
-            Slog.i(TAG, "Started going to sleep... (why="
+            Slog.i(TAG, "Started going to sleep... (groupId=" + displayGroupId + " why="
                     + WindowManagerPolicyConstants.offReasonToString(
                             WindowManagerPolicyConstants.translateSleepReasonToOffReason(
                                     pmSleepReason)) + ")");
         }
+        if (displayGroupId != Display.DEFAULT_DISPLAY_GROUP) {
+            return;
+        }
 
-        mDeviceGoingToSleep = true;
         mRequestedOrSleepingDefaultDisplay = true;
 
         if (mKeyguardDelegate != null) {
@@ -4815,17 +4840,20 @@
 
     // Called on the PowerManager's Notifier thread.
     @Override
-    public void finishedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason) {
+    public void finishedGoingToSleep(int displayGroupId,
+            @PowerManager.GoToSleepReason int pmSleepReason) {
+        if (displayGroupId != Display.DEFAULT_DISPLAY_GROUP) {
+            return;
+        }
         EventLogTags.writeScreenToggled(0);
         if (DEBUG_WAKEUP) {
-            Slog.i(TAG, "Finished going to sleep... (why="
+            Slog.i(TAG, "Finished going to sleep... (groupId=" + displayGroupId + " why="
                     + WindowManagerPolicyConstants.offReasonToString(
                             WindowManagerPolicyConstants.translateSleepReasonToOffReason(
                                     pmSleepReason)) + ")");
         }
         MetricsLogger.histogram(mContext, "screen_timeout", mLockScreenTimeout / 1000);
 
-        mDeviceGoingToSleep = false;
         mRequestedOrSleepingDefaultDisplay = false;
         mDefaultDisplayPolicy.setAwake(false);
 
@@ -4850,26 +4878,18 @@
 
     // Called on the PowerManager's Notifier thread.
     @Override
-    public void onPowerGroupWakefulnessChanged(int groupId, int wakefulness,
-            @PowerManager.GoToSleepReason int pmSleepReason, int globalWakefulness) {
-        if (wakefulness != globalWakefulness
-                && wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
-                && groupId == Display.DEFAULT_DISPLAY_GROUP
-                && mKeyguardDelegate != null) {
-            mKeyguardDelegate.doKeyguardTimeout(null);
-        }
-    }
-
-    // Called on the PowerManager's Notifier thread.
-    @Override
-    public void startedWakingUp(@PowerManager.WakeReason int pmWakeReason) {
-        EventLogTags.writeScreenToggled(1);
+    public void startedWakingUp(int displayGroupId, @WakeReason int pmWakeReason) {
         if (DEBUG_WAKEUP) {
-            Slog.i(TAG, "Started waking up... (why="
+            Slog.i(TAG, "Started waking up... (groupId=" + displayGroupId + " why="
                     + WindowManagerPolicyConstants.onReasonToString(
-                            WindowManagerPolicyConstants.translateWakeReasonToOnReason(
-                                    pmWakeReason)) + ")");
+                    WindowManagerPolicyConstants.translateWakeReasonToOnReason(
+                            pmWakeReason)) + ")");
         }
+        if (displayGroupId != Display.DEFAULT_DISPLAY_GROUP) {
+            return;
+        }
+        EventLogTags.writeScreenToggled(1);
+
 
         mDefaultDisplayPolicy.setAwake(true);
 
@@ -4892,13 +4912,16 @@
 
     // Called on the PowerManager's Notifier thread.
     @Override
-    public void finishedWakingUp(@PowerManager.WakeReason int pmWakeReason) {
+    public void finishedWakingUp(int displayGroupId, @WakeReason int pmWakeReason) {
         if (DEBUG_WAKEUP) {
-            Slog.i(TAG, "Finished waking up... (why="
+            Slog.i(TAG, "Finished waking up... (groupId=" + displayGroupId + " why="
                     + WindowManagerPolicyConstants.onReasonToString(
                             WindowManagerPolicyConstants.translateWakeReasonToOnReason(
                                     pmWakeReason)) + ")");
         }
+        if (displayGroupId != Display.DEFAULT_DISPLAY_GROUP) {
+            return;
+        }
 
         if (mKeyguardDelegate != null) {
             mKeyguardDelegate.onFinishedWakingUp();
@@ -5378,8 +5401,8 @@
             }
         }
         mSideFpsEventHandler.onFingerprintSensorReady();
-        startedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
-        finishedWakingUp(PowerManager.WAKE_REASON_UNKNOWN);
+        startedWakingUp(Display.DEFAULT_DISPLAY_GROUP, PowerManager.WAKE_REASON_UNKNOWN);
+        finishedWakingUp(Display.DEFAULT_DISPLAY_GROUP, PowerManager.WAKE_REASON_UNKNOWN);
 
         int defaultDisplayState = mDisplayManager.getDisplay(DEFAULT_DISPLAY).getState();
         boolean defaultDisplayOn = defaultDisplayState == Display.STATE_ON;
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 5d558e9..909d019 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -67,10 +67,12 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.companion.virtual.VirtualDevice;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.hardware.display.VirtualDisplay;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.PowerManager;
@@ -762,50 +764,84 @@
     void setAllowLockscreenWhenOn(int displayId, boolean allow);
 
     /**
+     * Called when the global wakefulness is becoming awake.
+     *
+     * @param reason One of PowerManager.WAKE_REASON_*, detailing the reason for the change.
+     */
+    void startedWakingUpGlobal(@PowerManager.WakeReason int reason);
+
+    /**
+     * Called when the global wakefulness has finished becoming awake.
+     *
+     * @param reason One of PowerManager.WAKE_REASON_*, detailing the reason for the change.
+     */
+    void finishedWakingUpGlobal(@PowerManager.WakeReason int reason);
+
+    /**
+     * Called when the global wakefulness has started going to sleep.
+     *
+     * @param reason One of PowerManager.WAKE_REASON_*, detailing the reason for the change.
+     */
+    void startedGoingToSleepGlobal(@PowerManager.GoToSleepReason int reason);
+
+    /**
+     * Called when the global wakefulness has finished going to sleep.
+     *
+     * @param reason One of PowerManager.WAKE_REASON_*, detailing the reason for the change.
+     */
+    void finishedGoingToSleepGlobal(@PowerManager.GoToSleepReason int reason);
+
+    /**
      * Called when the device has started waking up.
      *
-     * @param pmWakeReason One of PowerManager.WAKE_REASON_*, detailing the specific reason we're
-     * waking up, such as WAKE_REASON_POWER_BUTTON or WAKE_REASON_GESTURE.
+     * @param displayGroupId The id of the display group that has started waking up. This will often
+     *                       be {@link Display#DEFAULT_DISPLAY_GROUP}, but it is possible for other
+     *                       display groups to exist, for example when there is a
+     *                       {@link VirtualDevice} with one or more {@link VirtualDisplay}s.
+     * @param pmWakeReason One of PowerManager.WAKE_REASON_*, detailing the specific reason this
+     *                     display group is waking up, such as WAKE_REASON_POWER_BUTTON or
+     *                     WAKE_REASON_GESTURE.
      */
-    void startedWakingUp(@PowerManager.WakeReason int pmWakeReason);
+    void startedWakingUp(int displayGroupId, @PowerManager.WakeReason int pmWakeReason);
 
     /**
      * Called when the device has finished waking up.
      *
-     * @param pmWakeReason One of PowerManager.WAKE_REASON_*, detailing the specific reason we're
-     * waking up, such as WAKE_REASON_POWER_BUTTON or WAKE_REASON_GESTURE.
+     * @param displayGroupId The id of the display group that has finished waking. This will often
+     *                       be {@link Display#DEFAULT_DISPLAY_GROUP}, but it is possible for other
+     *                       display groups to exist, for example when there is a
+     *                       {@link VirtualDevice} with one or more {@link VirtualDisplay}s.
+     * @param pmWakeReason One of PowerManager.WAKE_REASON_*, detailing the specific reason this
+     *                     display group is waking up, such as WAKE_REASON_POWER_BUTTON or
+     *                     WAKE_REASON_GESTURE.
      */
-    void finishedWakingUp(@PowerManager.WakeReason int pmWakeReason);
+    void finishedWakingUp(int displayGroupId, @PowerManager.WakeReason int pmWakeReason);
 
     /**
      * Called when the device has started going to sleep.
      *
+     * @param displayGroupId The id of the display group that has started going to sleep. This
+     *                       will often be {@link Display#DEFAULT_DISPLAY_GROUP}, but it is
+     *                       possible for other display groups to exist, for example when there is a
+     *                       {@link VirtualDevice} with one or more {@link VirtualDisplay}s.
      * @param pmSleepReason One of PowerManager.GO_TO_SLEEP_REASON_*, detailing the specific reason
-     * we're going to sleep, such as GO_TO_SLEEP_REASON_POWER_BUTTON or GO_TO_SLEEP_REASON_TIMEOUT.
+     *                      this display group is going to sleep, such as
+     *                      GO_TO_SLEEP_REASON_POWER_BUTTON or GO_TO_SLEEP_REASON_TIMEOUT.
      */
-    public void startedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason);
+    void startedGoingToSleep(int displayGroupId, @PowerManager.GoToSleepReason int pmSleepReason);
 
     /**
      * Called when the device has finished going to sleep.
      *
+     * @param displayGroupId The id of the display group that has finished going to sleep. This
+     *                       will often be {@link Display#DEFAULT_DISPLAY_GROUP}, but it is
+     *                       possible for other display groups to exist, for example when there is a
+     *                       {@link VirtualDevice} with one or more {@link VirtualDisplay}s.
      * @param pmSleepReason One of PowerManager.GO_TO_SLEEP_REASON_*, detailing the specific reason
-     * we're going to sleep, such as GO_TO_SLEEP_REASON_POWER_BUTTON or GO_TO_SLEEP_REASON_TIMEOUT.
+     *                      we're going to sleep, such as GO_TO_SLEEP_REASON_POWER_BUTTON or
+     *                      GO_TO_SLEEP_REASON_TIMEOUT.
      */
-    public void finishedGoingToSleep(@PowerManager.GoToSleepReason int pmSleepReason);
-
-    /**
-     * Called when a particular PowerGroup has changed wakefulness.
-     *
-     * @param groupId The id of the PowerGroup.
-     * @param wakefulness One of PowerManagerInternal.WAKEFULNESS_* indicating the wake state for
-     * the group
-     * @param pmSleepReason One of PowerManager.GO_TO_SLEEP_REASON_*, detailing the reason this
-     * group is going to sleep.
-     * @param globalWakefulness The global wakefulness, which may or may not match that of this
-     * group. One of PowerManagerInternal.WAKEFULNESS_*
-     */
-    void onPowerGroupWakefulnessChanged(int groupId, int wakefulness,
-            @PowerManager.GoToSleepReason int pmSleepReason, int globalWakefulness);
+    void finishedGoingToSleep(int displayGroupId, @PowerManager.GoToSleepReason int pmSleepReason);
 
     /**
      * Called when the display is about to turn on to show content.
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index d0ed9bf..9bc0ee22 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -51,6 +51,7 @@
 import android.telephony.TelephonyManager;
 import android.util.EventLog;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.WindowManagerPolicyConstants;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -87,7 +88,7 @@
  * tell the system when we go to sleep so that it can lock the keyguard if needed.
  * </p>
  */
-@VisibleForTesting
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
 public class Notifier {
     private static final String TAG = "PowerManagerNotifier";
 
@@ -150,14 +151,21 @@
     // begins charging wirelessly
     private final boolean mShowWirelessChargingAnimationConfig;
 
-    // The current interactive state.  This is set as soon as an interactive state
+    // Encapsulates interactivity information about a particular display group.
+    private static class Interactivity {
+        public boolean isInteractive = true;
+        public int changeReason;
+        public long changeStartTime; // In SystemClock.uptimeMillis()
+        public boolean isChanging;
+    }
+
+    private final SparseArray<Interactivity> mInteractivityByGroupId = new SparseArray<>();
+
+    // The current global interactive state.  This is set as soon as an interactive state
     // transition begins so as to capture the reason that it happened.  At some point
     // this state will propagate to the pending state then eventually to the
     // broadcasted state over the course of reporting the transition asynchronously.
-    private boolean mInteractive = true;
-    private int mInteractiveChangeReason;
-    private long mInteractiveChangeStartTime; // In SystemClock.uptimeMillis()
-    private boolean mInteractiveChanging;
+    private Interactivity mGlobalInteractivity = new Interactivity();
 
     // The pending interactive state that we will eventually want to broadcast.
     // This is designed so that we can collapse redundant sequences of awake/sleep
@@ -438,7 +446,8 @@
      * which case it will assume that the state did not fully converge before the
      * next transition began and will recover accordingly.
      */
-    public void onWakefulnessChangeStarted(final int wakefulness, int reason, long eventTime) {
+    public void onGlobalWakefulnessChangeStarted(final int wakefulness, int reason,
+            long eventTime) {
         final boolean interactive = PowerManagerInternal.isInteractive(wakefulness);
         if (DEBUG) {
             Slog.d(TAG, "onWakefulnessChangeStarted: wakefulness=" + wakefulness
@@ -456,10 +465,10 @@
 
         // Handle any early interactive state changes.
         // Finish pending incomplete ones from a previous cycle.
-        if (mInteractive != interactive) {
+        if (mGlobalInteractivity.isInteractive != interactive) {
             // Finish up late behaviors if needed.
-            if (mInteractiveChanging) {
-                handleLateInteractiveChange();
+            if (mGlobalInteractivity.isChanging) {
+                handleLateGlobalInteractiveChange();
             }
 
             // Start input as soon as we start waking up or going to sleep.
@@ -475,11 +484,11 @@
                             FrameworkStatsLog.INTERACTIVE_STATE_CHANGED__STATE__OFF);
 
             // Handle early behaviors.
-            mInteractive = interactive;
-            mInteractiveChangeReason = reason;
-            mInteractiveChangeStartTime = eventTime;
-            mInteractiveChanging = true;
-            handleEarlyInteractiveChange();
+            mGlobalInteractivity.isInteractive = interactive;
+            mGlobalInteractivity.isChanging = true;
+            mGlobalInteractivity.changeReason = reason;
+            mGlobalInteractivity.changeStartTime = eventTime;
+            handleEarlyGlobalInteractiveChange();
         }
     }
 
@@ -490,10 +499,34 @@
         if (DEBUG) {
             Slog.d(TAG, "onWakefulnessChangeFinished");
         }
+        for (int i = 0; i < mInteractivityByGroupId.size(); i++) {
+            int groupId = mInteractivityByGroupId.keyAt(i);
+            Interactivity interactivity = mInteractivityByGroupId.valueAt(i);
+            if (interactivity.isChanging) {
+                interactivity.isChanging = false;
+                handleLateInteractiveChange(groupId);
+            }
+        }
+        if (mGlobalInteractivity.isChanging) {
+            mGlobalInteractivity.isChanging = false;
+            handleLateGlobalInteractiveChange();
+        }
+    }
 
-        if (mInteractiveChanging) {
-            mInteractiveChanging = false;
-            handleLateInteractiveChange();
+
+    private void handleEarlyInteractiveChange(int groupId) {
+        synchronized (mLock) {
+            Interactivity interactivity = mInteractivityByGroupId.get(groupId);
+            if (interactivity == null) {
+                Slog.e(TAG, "no Interactivity entry for groupId:" + groupId);
+                return;
+            }
+            final int changeReason = interactivity.changeReason;
+            if (interactivity.isInteractive) {
+                mHandler.post(() -> mPolicy.startedWakingUp(groupId, changeReason));
+            } else {
+                mHandler.post(() -> mPolicy.startedGoingToSleep(groupId, changeReason));
+            }
         }
     }
 
@@ -501,13 +534,13 @@
      * Handle early interactive state changes such as getting applications or the lock
      * screen running and ready for the user to see (such as when turning on the screen).
      */
-    private void handleEarlyInteractiveChange() {
+    private void handleEarlyGlobalInteractiveChange() {
         synchronized (mLock) {
-            if (mInteractive) {
+            if (mGlobalInteractivity.isInteractive) {
                 // Waking up...
                 mHandler.post(() -> {
-                    mPolicy.startedWakingUp(mInteractiveChangeReason);
                     mDisplayManagerInternal.onEarlyInteractivityChange(true /*isInteractive*/);
+                    mPolicy.startedWakingUpGlobal(mGlobalInteractivity.changeReason);
                 });
 
                 // Send interactive broadcast.
@@ -516,37 +549,36 @@
                 updatePendingBroadcastLocked();
             } else {
                 // Going to sleep...
-                // Tell the policy that we started going to sleep.
                 mHandler.post(() -> {
-                    mPolicy.startedGoingToSleep(mInteractiveChangeReason);
                     mDisplayManagerInternal.onEarlyInteractivityChange(false /*isInteractive*/);
+                    mPolicy.startedGoingToSleepGlobal(mGlobalInteractivity.changeReason);
                 });
             }
         }
     }
 
     /**
-     * Handle late interactive state changes once they are finished so that the system can
-     * finish pending transitions (such as turning the screen off) before causing
-     * applications to change state visibly.
+     * Handle late global interactive state changes. Also see
+     * {@link #handleLateInteractiveChange(int)}.
      */
-    private void handleLateInteractiveChange() {
+    private void handleLateGlobalInteractiveChange() {
         synchronized (mLock) {
             final int interactiveChangeLatency =
-                    (int) (SystemClock.uptimeMillis() - mInteractiveChangeStartTime);
-            if (mInteractive) {
+                    (int) (SystemClock.uptimeMillis() - mGlobalInteractivity.changeStartTime);
+            if (mGlobalInteractivity.isInteractive) {
                 // Finished waking up...
                 mHandler.post(() -> {
                     LogMaker log = new LogMaker(MetricsEvent.SCREEN);
                     log.setType(MetricsEvent.TYPE_OPEN);
                     log.setSubtype(WindowManagerPolicyConstants.translateWakeReasonToOnReason(
-                            mInteractiveChangeReason));
+                            mGlobalInteractivity.changeReason));
                     log.setLatency(interactiveChangeLatency);
-                    log.addTaggedData(
-                            MetricsEvent.FIELD_SCREEN_WAKE_REASON, mInteractiveChangeReason);
+                    log.addTaggedData(MetricsEvent.FIELD_SCREEN_WAKE_REASON,
+                            mGlobalInteractivity.changeReason);
                     MetricsLogger.action(log);
                     EventLogTags.writePowerScreenState(1, 0, 0, 0, interactiveChangeLatency);
-                    mPolicy.finishedWakingUp(mInteractiveChangeReason);
+
+                    mPolicy.finishedWakingUpGlobal(mGlobalInteractivity.changeReason);
                 });
             } else {
                 // Finished going to sleep...
@@ -563,18 +595,19 @@
 
                 // Tell the policy we finished going to sleep.
                 final int offReason = WindowManagerPolicyConstants.translateSleepReasonToOffReason(
-                        mInteractiveChangeReason);
+                        mGlobalInteractivity.changeReason);
                 mHandler.post(() -> {
                     LogMaker log = new LogMaker(MetricsEvent.SCREEN);
                     log.setType(MetricsEvent.TYPE_CLOSE);
                     log.setSubtype(offReason);
                     log.setLatency(interactiveChangeLatency);
-                    log.addTaggedData(
-                            MetricsEvent.FIELD_SCREEN_SLEEP_REASON, mInteractiveChangeReason);
+                    log.addTaggedData(MetricsEvent.FIELD_SCREEN_SLEEP_REASON,
+                            mGlobalInteractivity.changeReason);
                     MetricsLogger.action(log);
                     EventLogTags.writePowerScreenState(
                             0, offReason, 0, 0, interactiveChangeLatency);
-                    mPolicy.finishedGoingToSleep(mInteractiveChangeReason);
+
+                    mPolicy.finishedGoingToSleepGlobal(mGlobalInteractivity.changeReason);
                 });
 
                 // Send non-interactive broadcast.
@@ -586,12 +619,62 @@
     }
 
     /**
+     * Handle late interactive state changes once they are finished so that the system can
+     * finish pending transitions (such as turning the screen off) before causing
+     * applications to change state visibly.
+     */
+    private void handleLateInteractiveChange(int groupId) {
+        synchronized (mLock) {
+            Interactivity interactivity = mInteractivityByGroupId.get(groupId);
+            if (interactivity == null) {
+                Slog.e(TAG, "no Interactivity entry for groupId:" + groupId);
+                return;
+            }
+            final int changeReason = interactivity.changeReason;
+            if (interactivity.isInteractive) {
+                mHandler.post(() -> mPolicy.finishedWakingUp(groupId, changeReason));
+            } else {
+                mHandler.post(() -> mPolicy.finishedGoingToSleep(groupId, changeReason));
+            }
+        }
+    }
+
+    /**
      * Called when an individual PowerGroup changes wakefulness.
      */
-    public void onPowerGroupWakefulnessChanged(int groupId, int groupWakefulness, int changeReason,
-            int globalWakefulness) {
-        mHandler.post(() -> mPolicy.onPowerGroupWakefulnessChanged(groupId, groupWakefulness,
-                changeReason, globalWakefulness));
+    public void onGroupWakefulnessChangeStarted(int groupId, int wakefulness, int changeReason,
+            long eventTime) {
+        final boolean isInteractive = PowerManagerInternal.isInteractive(wakefulness);
+
+        boolean isNewGroup = false;
+        Interactivity interactivity = mInteractivityByGroupId.get(groupId);
+        if (interactivity == null) {
+            isNewGroup = true;
+            interactivity = new Interactivity();
+            mInteractivityByGroupId.put(groupId, interactivity);
+        }
+        if (isNewGroup || interactivity.isInteractive != isInteractive) {
+            // Finish up late behaviors if needed.
+            if (interactivity.isChanging) {
+                handleLateInteractiveChange(groupId);
+            }
+
+            // Handle early behaviors.
+            interactivity.isInteractive = isInteractive;
+            interactivity.changeReason = changeReason;
+            interactivity.changeStartTime = eventTime;
+            interactivity.isChanging = true;
+            handleEarlyInteractiveChange(groupId);
+        }
+    }
+
+    /**
+     * Called when a PowerGroup has been removed.
+     *
+     * @param groupId which group was removed
+     */
+    public void onGroupRemoved(int groupId) {
+        mInteractivityByGroupId.remove(groupId);
     }
 
     /**
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index e392c24..9ff98be 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -686,6 +686,8 @@
         @Override
         public void onWakefulnessChangedLocked(int groupId, int wakefulness, long eventTime,
                 int reason, int uid, int opUid, String opPackageName, String details) {
+            mWakefulnessChanging = true;
+            mDirty |= DIRTY_WAKEFULNESS;
             if (wakefulness == WAKEFULNESS_AWAKE) {
                 // Kick user activity to prevent newly awake group from timing out instantly.
                 // The dream may end without user activity if the dream app crashes / is updated,
@@ -696,9 +698,8 @@
                         PowerManager.USER_ACTIVITY_EVENT_OTHER, flags, uid);
             }
             mDirty |= DIRTY_DISPLAY_GROUP_WAKEFULNESS;
+            mNotifier.onGroupWakefulnessChangeStarted(groupId, wakefulness, reason, eventTime);
             updateGlobalWakefulnessLocked(eventTime, reason, uid, opUid, opPackageName, details);
-            mNotifier.onPowerGroupWakefulnessChanged(groupId, wakefulness, reason,
-                    getGlobalWakefulnessLocked());
             updatePowerStateLocked();
         }
     }
@@ -2152,7 +2153,7 @@
             mDozeStartInProgress &= (newWakefulness == WAKEFULNESS_DOZING);
 
             if (mNotifier != null) {
-                mNotifier.onWakefulnessChangeStarted(newWakefulness, reason, eventTime);
+                mNotifier.onGlobalWakefulnessChangeStarted(newWakefulness, reason, eventTime);
             }
             mAttentionDetector.onWakefulnessChangeStarted(newWakefulness);
 
@@ -2163,15 +2164,6 @@
                     if (sQuiescent) {
                         mDirty |= DIRTY_QUIESCENT;
                     }
-                    PowerGroup defaultGroup = mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP);
-                    if (defaultGroup.getWakefulnessLocked() == WAKEFULNESS_DOZING) {
-                        // Workaround for b/187231320 where the AOD can get stuck in a "half on /
-                        // half off" state when a non-default-group VirtualDisplay causes the global
-                        // wakefulness to change to awake, even though the default display is
-                        // dozing. We set sandman summoned to restart dreaming to get it unstuck.
-                        // TODO(b/255688811) - fix this so that AOD never gets interrupted at all.
-                        defaultGroup.setSandmanSummonedLocked(true);
-                    }
                     break;
 
                 case WAKEFULNESS_ASLEEP:
@@ -2248,6 +2240,8 @@
 
     @GuardedBy("mLock")
     void onPowerGroupEventLocked(int event, PowerGroup powerGroup) {
+        mWakefulnessChanging = true;
+        mDirty |= DIRTY_WAKEFULNESS;
         final int groupId = powerGroup.getGroupId();
         if (event == DisplayGroupPowerChangeListener.DISPLAY_GROUP_REMOVED) {
             mPowerGroups.delete(groupId);
@@ -2260,6 +2254,11 @@
             // Kick user activity to prevent newly added group from timing out instantly.
             userActivityNoUpdateLocked(powerGroup, mClock.uptimeMillis(),
                     PowerManager.USER_ACTIVITY_EVENT_OTHER, /* flags= */ 0, Process.SYSTEM_UID);
+            mNotifier.onGroupWakefulnessChangeStarted(groupId,
+                    powerGroup.getWakefulnessLocked(), WAKE_REASON_DISPLAY_GROUP_ADDED,
+                    mClock.uptimeMillis());
+        } else if (event == DisplayGroupPowerChangeListener.DISPLAY_GROUP_REMOVED) {
+            mNotifier.onGroupRemoved(groupId);
         }
 
         if (oldWakefulness != newWakefulness) {
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 21a11bc..52bf244 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -40,6 +40,7 @@
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -1611,7 +1612,8 @@
         startSystem();
 
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        verify(mNotifierMock, never()).onWakefulnessChangeStarted(anyInt(), anyInt(), anyLong());
+        verify(mNotifierMock, never()).onGlobalWakefulnessChangeStarted(anyInt(), anyInt(),
+                anyLong());
     }
 
     @Test
@@ -1630,7 +1632,7 @@
         startSystem();
 
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
-        verify(mNotifierMock).onWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP), anyInt(),
+        verify(mNotifierMock).onGlobalWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP), anyInt(),
                 anyLong());
     }
 
@@ -2112,7 +2114,7 @@
     }
 
     @Test
-    public void testMultiDisplay_defaultDozing_addNewDisplayDefaultGoesBackToDoze() {
+    public void testMultiDisplay_addNewDisplay_becomeGloballyAwakeButDefaultRemainsDozing() {
         final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
         final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
         final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
@@ -2142,6 +2144,7 @@
         assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
                 WAKEFULNESS_DOZING);
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+        verify(mDreamManagerInternalMock).stopDream(anyBoolean(), anyString());
         verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
 
         listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
@@ -2152,7 +2155,10 @@
                 WAKEFULNESS_AWAKE);
         assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
                 WAKEFULNESS_DOZING);
-        verify(mDreamManagerInternalMock, times(2)).startDream(eq(true), anyString());
+
+        // Make sure there were no additional calls to stopDream or startDream
+        verify(mDreamManagerInternalMock, atMost(1)).stopDream(anyBoolean(), anyString());
+        verify(mDreamManagerInternalMock, atMost(1)).startDream(eq(true), anyString());
     }
 
     @Test
@@ -2169,7 +2175,7 @@
     }
 
     @Test
-    public void testMultiDisplay_onlyOneDisplaySleeps_onWakefulnessChangedEventFires() {
+    public void testMultiDisplay_onlyOneDisplaySleeps_onWakefulnessChangedEventsFire() {
         createService();
         startSystem();
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
@@ -2177,12 +2183,14 @@
         assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
                 WAKEFULNESS_ASLEEP);
 
-        verify(mNotifierMock).onPowerGroupWakefulnessChanged(eq(Display.DEFAULT_DISPLAY_GROUP),
-                eq(WAKEFULNESS_ASLEEP), anyInt(), eq(WAKEFULNESS_ASLEEP));
+        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(Display.DEFAULT_DISPLAY_GROUP),
+                eq(WAKEFULNESS_ASLEEP), eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
+        verify(mNotifierMock).onGlobalWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP),
+                eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
     }
 
     @Test
-    public void testMultiDisplay_bothDisplaysSleep_onWakefulnessChangedEventFiresCorrectly() {
+    public void testMultiDisplay_bothDisplaysSleep_onWakefulnessChangedEventsFireCorrectly() {
         final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
         final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
         final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
@@ -2201,10 +2209,10 @@
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
         listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
 
-        mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
-                null, null);
-        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0, 0, 0,
-                null, null);
+        mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, 0, 0,
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+        mService.setWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP, WAKEFULNESS_ASLEEP, 0, 0,
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
 
         assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
         assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
@@ -2212,14 +2220,16 @@
         assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
                 WAKEFULNESS_ASLEEP);
 
-        verify(mNotifierMock).onPowerGroupWakefulnessChanged(eq(nonDefaultDisplayGroupId),
-                eq(WAKEFULNESS_ASLEEP), anyInt(), eq(WAKEFULNESS_AWAKE));
-        verify(mNotifierMock).onPowerGroupWakefulnessChanged(eq(Display.DEFAULT_DISPLAY_GROUP),
-                eq(WAKEFULNESS_ASLEEP), anyInt(), eq(WAKEFULNESS_ASLEEP));
+        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(nonDefaultDisplayGroupId),
+                eq(WAKEFULNESS_ASLEEP), eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
+        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(Display.DEFAULT_DISPLAY_GROUP),
+                eq(WAKEFULNESS_ASLEEP), eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
+        verify(mNotifierMock).onGlobalWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP),
+                eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
     }
 
     @Test
-    public void testMultiDisplay_separateWakeStates_onWakefulnessChangedEventFiresCorrectly() {
+    public void testMultiDisplay_separateWakeStates_onWakefulnessChangedEventsFireCorrectly() {
         final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
         final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
         final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
@@ -2255,10 +2265,53 @@
                 WAKEFULNESS_ASLEEP);
         assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
                 WAKEFULNESS_AWAKE);
-        verify(mNotifierMock).onPowerGroupWakefulnessChanged(eq(Display.DEFAULT_DISPLAY_GROUP),
-                eq(WAKEFULNESS_ASLEEP), anyInt(), eq(WAKEFULNESS_AWAKE));
-        verify(mNotifierMock, never()).onPowerGroupWakefulnessChanged(
-                eq(nonDefaultDisplayGroupId), anyInt(), anyInt(), anyInt());
+        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(nonDefaultDisplayGroupId),
+                eq(WAKEFULNESS_AWAKE), eq(PowerManager.WAKE_REASON_DISPLAY_GROUP_ADDED), anyLong());
+        verify(mNotifierMock).onGroupWakefulnessChangeStarted(eq(Display.DEFAULT_DISPLAY_GROUP),
+                eq(WAKEFULNESS_ASLEEP), eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), anyLong());
+        verify(mNotifierMock, never()).onGlobalWakefulnessChangeStarted(eq(WAKEFULNESS_ASLEEP),
+                anyInt(), anyLong());
+    }
+
+    @Test
+    public void testMultiDisplay_oneDisplayGroupChanges_globalDoesNotChange() {
+        final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+        final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+        final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+                new AtomicReference<>();
+        doAnswer((Answer<Void>) invocation -> {
+            listener.set(invocation.getArgument(0));
+            return null;
+        }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+        final DisplayInfo info = new DisplayInfo();
+        info.displayGroupId = nonDefaultDisplayGroupId;
+        when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+        createService();
+        startSystem();
+
+        listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        long eventTime = mClock.now();
+        mService.setWakefulnessLocked(nonDefaultDisplayGroupId, WAKEFULNESS_ASLEEP, eventTime, 0,
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION, 0, null, null);
+
+        assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+                WAKEFULNESS_AWAKE);
+        assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+                WAKEFULNESS_ASLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        verify(mNotifierMock, never()).onGlobalWakefulnessChangeStarted(anyInt(), anyInt(),
+                anyLong());
+        verify(mNotifierMock, atMost(1)).onGroupWakefulnessChangeStarted(
+                eq(nonDefaultDisplayGroupId), eq(WAKEFULNESS_ASLEEP),
+                eq(PowerManager.GO_TO_SLEEP_REASON_APPLICATION), eq(eventTime));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 2665e19..5636795 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -233,8 +233,8 @@
         doNothing().when(mPhoneWindowManager).updateSettings();
         doNothing().when(mPhoneWindowManager).screenTurningOn(anyInt(), any());
         doNothing().when(mPhoneWindowManager).screenTurnedOn(anyInt());
-        doNothing().when(mPhoneWindowManager).startedWakingUp(anyInt());
-        doNothing().when(mPhoneWindowManager).finishedWakingUp(anyInt());
+        doNothing().when(mPhoneWindowManager).startedWakingUp(anyInt(), anyInt());
+        doNothing().when(mPhoneWindowManager).finishedWakingUp(anyInt(), anyInt());
 
         mPhoneWindowManager.init(new TestInjector(mContext, mWindowManagerFuncsImpl));
         mPhoneWindowManager.systemReady();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 2a2641e..1f7e3d9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -114,24 +114,35 @@
     }
 
     @Override
-    public void startedWakingUp(@WakeReason int wakeReason) {
+    public void startedWakingUpGlobal(@WakeReason int reason) {
     }
 
     @Override
-    public void finishedWakingUp(@WakeReason int wakeReason) {
+    public void finishedWakingUpGlobal(@WakeReason int reason) {
     }
 
     @Override
-    public void startedGoingToSleep(@GoToSleepReason int sleepReason) {
+    public void startedGoingToSleepGlobal(@GoToSleepReason int reason) {
     }
 
     @Override
-    public void finishedGoingToSleep(@GoToSleepReason int sleepReason) {
+    public void finishedGoingToSleepGlobal(@GoToSleepReason int reason) {
     }
 
     @Override
-    public void onPowerGroupWakefulnessChanged(int groupId, int wakefulness,
-            @GoToSleepReason int pmSleepReason, int globalWakefulness) {
+    public void startedWakingUp(int displayGroupId, @WakeReason int wakeReason) {
+    }
+
+    @Override
+    public void finishedWakingUp(int displayGroupId, @WakeReason int wakeReason) {
+    }
+
+    @Override
+    public void startedGoingToSleep(int displayGroupId, @GoToSleepReason int sleepReason) {
+    }
+
+    @Override
+    public void finishedGoingToSleep(int displayGroupId, @GoToSleepReason int sleepReason) {
     }
 
     @Override