Add per-display Configuration change reporting

Adds reporting per-display configuration changes to
hierarchy listeners. On system-ui's side, the
DisplayWindowController then dispatches to other registrants
within the process.

Bug: 133381284
Test: Added testDisplayWindowListener
Change-Id: I68c4e9e229b97a9e91ad0d4f5276447a947c413b
diff --git a/core/java/android/view/IDisplayWindowListener.aidl b/core/java/android/view/IDisplayWindowListener.aidl
index 725cd6f..973a208 100644
--- a/core/java/android/view/IDisplayWindowListener.aidl
+++ b/core/java/android/view/IDisplayWindowListener.aidl
@@ -16,12 +16,16 @@
 
 package android.view;
 
+import android.content.res.Configuration;
+
 /**
  * Interface to listen for changes to display window-containers.
  *
- * This differs from DisplayManager's DisplayListener:
+ * This differs from DisplayManager's DisplayListener in a couple ways:
  *  - onDisplayAdded is always called after the display is actually added to the WM hierarchy.
  *    This corresponds to the DisplayContent and not the raw Dislay from DisplayManager.
+ *  - onDisplayConfigurationChanged is called for all configuration changes, not just changes
+ *    to displayinfo (eg. windowing-mode).
  *
  * @hide
  */
@@ -33,6 +37,11 @@
     void onDisplayAdded(int displayId);
 
     /**
+     * Called when a display's window-container configuration has changed.
+     */
+    void onDisplayConfigurationChanged(int displayId, in Configuration newConfig);
+
+    /**
      * Called when a display is removed from the hierarchy.
      */
     void onDisplayRemoved(int displayId);
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java
index f3487fb..19fff79 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java
@@ -16,8 +16,10 @@
 
 package com.android.systemui.wm;
 
+import android.content.res.Configuration;
 import android.os.Handler;
 import android.os.RemoteException;
+import android.util.Slog;
 import android.util.SparseArray;
 import android.view.IDisplayWindowListener;
 import android.view.IDisplayWindowRotationCallback;
@@ -40,6 +42,8 @@
  */
 @Singleton
 public class DisplayWindowController {
+    private static final String TAG = "DisplayWindowController";
+
     private final Handler mHandler;
 
     private final ArrayList<OnDisplayWindowRotationController> mRotationControllers =
@@ -84,8 +88,26 @@
                             DisplayRecord record = new DisplayRecord();
                             record.mDisplayId = displayId;
                             mDisplays.put(displayId, record);
-                            for (DisplayWindowListener l : mDisplayChangedListeners) {
-                                l.onDisplayAdded(displayId);
+                            for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
+                                mDisplayChangedListeners.get(i).onDisplayAdded(displayId);
+                            }
+                        }
+                    });
+                }
+
+                @Override
+                public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+                    mHandler.post(() -> {
+                        synchronized (mDisplays) {
+                            DisplayRecord dr = mDisplays.get(displayId);
+                            if (dr == null) {
+                                Slog.w(TAG, "Skipping Display Configuration change on non-added"
+                                        + " display.");
+                                return;
+                            }
+                            for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
+                                mDisplayChangedListeners.get(i).onDisplayConfigurationChanged(
+                                        displayId, newConfig);
                             }
                         }
                     });
@@ -118,8 +140,8 @@
     }
 
     /**
-     * Add a display window-container listener. It will get notified when displays are
-     * added/removed from the WM hierarchy.
+     * Add a display window-container listener. It will get notified whenever a display's
+     * configuration changes or when displays are added/removed from the WM hierarchy.
      */
     public void addDisplayWindowListener(DisplayWindowListener listener) {
         synchronized (mDisplays) {
@@ -165,7 +187,8 @@
     }
 
     /**
-     * Gets notified when a display is added/removed to the WM hierarchy.
+     * Gets notified when a display is added/removed to the WM hierarchy and when a display's
+     * window-configuration changes.
      *
      * @see IDisplayWindowListener
      */
@@ -176,6 +199,11 @@
         void onDisplayAdded(int displayId);
 
         /**
+         * Called when a display's window-container configuration changes.
+         */
+        void onDisplayConfigurationChanged(int displayId, Configuration newConfig);
+
+        /**
          * Called when a display is removed.
          */
         void onDisplayRemoved(int displayId);
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index 5258357..be7dfe5 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -1080,6 +1080,8 @@
                         ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
                 mService.mH.sendMessage(msg);
             }
+            mService.mWindowManager.mDisplayNotificationController.dispatchDisplayChanged(
+                    this, getConfiguration());
         }
         return changes;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
index dbc452f..2da76ea 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import android.content.res.Configuration;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.view.IDisplayWindowListener;
@@ -62,6 +63,28 @@
         mDisplayListeners.finishBroadcast();
     }
 
+    void dispatchDisplayChanged(ActivityDisplay display, Configuration newConfig) {
+        // Only report changed if this has actually been added to the hierarchy already.
+        boolean isInHierarchy = false;
+        for (int i = 0; i < display.getParent().getChildCount(); ++i) {
+            if (display.getParent().getChildAt(i) == display) {
+                isInHierarchy = true;
+            }
+        }
+        if (!isInHierarchy) {
+            return;
+        }
+        int count = mDisplayListeners.beginBroadcast();
+        for (int i = 0; i < count; ++i) {
+            try {
+                mDisplayListeners.getBroadcastItem(i).onDisplayConfigurationChanged(
+                        display.mDisplayId, newConfig);
+            } catch (RemoteException e) {
+            }
+        }
+        mDisplayListeners.finishBroadcast();
+    }
+
     void dispatchDisplayRemoved(ActivityDisplay display) {
         int count = mDisplayListeners.beginBroadcast();
         for (int i = 0; i < count; ++i) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index e9546a2..dae1052 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -26,7 +26,9 @@
 
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.view.IDisplayWindowListener;
 import android.view.WindowContainerTransaction;
 
 import androidx.test.filters.MediumTest;
@@ -35,6 +37,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+
 /**
  * Tests for the {@link ActivityTaskManagerService} class.
  *
@@ -93,5 +97,56 @@
         mService.applyContainerTransaction(t);
         assertEquals(newBounds, stack.getBounds());
     }
+
+    @Test
+    public void testDisplayWindowListener() {
+        final ArrayList<Integer> added = new ArrayList<>();
+        final ArrayList<Integer> changed = new ArrayList<>();
+        final ArrayList<Integer> removed = new ArrayList<>();
+        IDisplayWindowListener listener = new IDisplayWindowListener.Stub() {
+            @Override
+            public void onDisplayAdded(int displayId) {
+                added.add(displayId);
+            }
+
+            @Override
+            public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+                changed.add(displayId);
+            }
+
+            @Override
+            public void onDisplayRemoved(int displayId) {
+                removed.add(displayId);
+            }
+        };
+        mService.mWindowManager.registerDisplayWindowListener(listener);
+        // Check that existing displays call added
+        assertEquals(1, added.size());
+        assertEquals(0, changed.size());
+        assertEquals(0, removed.size());
+        added.clear();
+        // Check adding a display
+        ActivityDisplay newDisp1 = new TestActivityDisplay.Builder(mService, 600, 800).build();
+        assertEquals(1, added.size());
+        assertEquals(0, changed.size());
+        assertEquals(0, removed.size());
+        added.clear();
+        // Check that changes are reported
+        Configuration c = new Configuration(newDisp1.getRequestedOverrideConfiguration());
+        c.windowConfiguration.setBounds(new Rect(0, 0, 1000, 1300));
+        newDisp1.onRequestedOverrideConfigurationChanged(c);
+        mService.mRootActivityContainer.ensureVisibilityAndConfig(null /* starting */,
+                newDisp1.mDisplayId, false /* markFrozenIfConfigChanged */,
+                false /* deferResume */);
+        assertEquals(0, added.size());
+        assertEquals(1, changed.size());
+        assertEquals(0, removed.size());
+        changed.clear();
+        // Check that removal is reported
+        newDisp1.remove();
+        assertEquals(0, added.size());
+        assertEquals(0, changed.size());
+        assertEquals(1, removed.size());
+    }
 }