Refine getTransformationMatrix for windows in a re-parented display

Currently, the translation of the transformation matrix computed by
WindowState.getTransformationMatrix is related to its own display.
However, if the display has been re-parented, the translation might
be misplaced to the visual result. This CL makes it return the global
transformation matrix.

Bug: 129098348
Test: atest WindowStateTests
Change-Id: I38da5b84a11890bf0f4a57eb9d5b7e71bdcc16a9
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 6ce42ec..b6a5be8 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1108,6 +1108,7 @@
                 // the window manager is still looking for where to put it.
                 // We will do the work when we get a focus change callback.
                 // TODO(b/112273690): Support multiple displays
+                // TODO(b/129098348): Support embedded displays
                 if (mService.getDefaultDisplayContentLocked().mCurrentFocus == null) {
                     return;
                 }
@@ -1400,7 +1401,28 @@
                 if (w.isVisibleLw()) {
                     outWindows.put(mTempLayer++, w);
                 }
-            }, false /* traverseTopToBottom */ );
+            }, false /* traverseTopToBottom */);
+            mService.mRoot.forAllWindows(w -> {
+                final WindowState win = findRootDisplayParentWindow(w);
+                if (win != null && win.getDisplayContent().isDefaultDisplay && w.isVisibleLw()) {
+                    // TODO(b/129098348): insert windows on child displays into outWindows based on
+                    // root-display-parent window.
+                    outWindows.put(mTempLayer++, w);
+                }
+            }, false /* traverseTopToBottom */);
+        }
+
+        private WindowState findRootDisplayParentWindow(WindowState win) {
+            WindowState displayParentWindow = win.getDisplayContent().getParentWindow();
+            if (displayParentWindow == null) {
+                return null;
+            }
+            WindowState candidate = displayParentWindow;
+            while (candidate != null) {
+                displayParentWindow = candidate;
+                candidate = displayParentWindow.getDisplayContent().getParentWindow();
+            }
+            return displayParentWindow;
         }
 
         private class MyHandler extends Handler {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 3f783f6..b4b60ea 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -144,6 +144,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Insets;
 import android.graphics.Matrix;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
@@ -541,6 +542,10 @@
 
     private final InsetsStateController mInsetsStateController;
 
+    /** @see #getParentWindow() */
+    private WindowState mParentWindow;
+
+    private Point mLocationInParentWindow = new Point();
     private SurfaceControl mParentSurfaceControl;
     private InputWindowHandle mPortalWindowHandle;
 
@@ -4923,11 +4928,14 @@
 
     /**
      * Re-parent the DisplayContent's top surfaces, {@link #mWindowingLayer} and
-     * {@link #mOverlayLayer} to the specified surfaceControl.
+     * {@link #mOverlayLayer} to the specified SurfaceControl.
      *
+     * @param win The window which owns the SurfaceControl. This indicates the z-order of the
+     *            windows of this display against the windows on the parent display.
      * @param sc The new SurfaceControl, where the DisplayContent's surfaces will be re-parented to.
      */
-    void reparentDisplayContent(SurfaceControl sc) {
+    void reparentDisplayContent(WindowState win, SurfaceControl sc) {
+        mParentWindow = win;
         mParentSurfaceControl = sc;
         if (mPortalWindowHandle == null) {
             mPortalWindowHandle = createPortalWindowHandle(sc.toString());
@@ -4936,6 +4944,41 @@
                 .reparent(mWindowingLayer, sc).reparent(mOverlayLayer, sc);
     }
 
+    /**
+     * Get the window which owns the surface that this DisplayContent is re-parented to.
+     *
+     * @return the parent window.
+     */
+    WindowState getParentWindow() {
+        return mParentWindow;
+    }
+
+    /**
+     * Update the location of this display in the parent window. This enables windows in this
+     * display to compute the global transformation matrix.
+     *
+     * @param win The parent window of this display.
+     * @param x The x coordinate in the parent window.
+     * @param y The y coordinate in the parent window.
+     */
+    void updateLocation(WindowState win, int x, int y) {
+        if (mParentWindow != win) {
+            throw new IllegalArgumentException(
+                    "The given window is not the parent window of this display.");
+        }
+        if (mLocationInParentWindow.x != x || mLocationInParentWindow.y != y) {
+            mLocationInParentWindow.x = x;
+            mLocationInParentWindow.y = y;
+            if (mWmService.mAccessibilityController != null) {
+                mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
+            }
+        }
+    }
+
+    Point getLocationInParentWindow() {
+        return mLocationInParentWindow;
+    }
+
     @VisibleForTesting
     SurfaceControl getWindowingLayer() {
         return mWindowingLayer;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index b33f8c7..34273f3 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -426,6 +426,16 @@
     }
 
     @Override
+    public void reparentDisplayContent(IWindow window, SurfaceControl sc, int displayId) {
+        mService.reparentDisplayContent(window, sc, displayId);
+    }
+
+    @Override
+    public void updateDisplayContentLocation(IWindow window, int x, int y, int displayId) {
+        mService.updateDisplayContentLocation(window, x, y, displayId);
+    }
+
+    @Override
     public void updateTapExcludeRegion(IWindow window, int regionId, Region region) {
         final long identity = Binder.clearCallingIdentity();
         try {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4f849cd..7c1ea20 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1880,7 +1880,8 @@
 
                     // We need to report touchable region changes to accessibility.
                     if (mAccessibilityController != null
-                            && w.getDisplayContent().getDisplayId() == DEFAULT_DISPLAY) {
+                            && (w.getDisplayContent().getDisplayId() == DEFAULT_DISPLAY
+                                    || w.getDisplayContent().getParentWindow() != null)) {
                         mAccessibilityController.onSomeWindowResizedOrMovedLocked();
                     }
                 }
@@ -2012,7 +2013,8 @@
                 }
                 if (((attrChanges & LayoutParams.ACCESSIBILITY_TITLE_CHANGED) != 0)
                         && (mAccessibilityController != null)
-                        && (win.getDisplayId() == DEFAULT_DISPLAY)) {
+                        && (win.getDisplayId() == DEFAULT_DISPLAY
+                                || win.getDisplayContent().getParentWindow() != null)) {
                     // No move or resize, but the controller checks for title changes as well
                     mAccessibilityController.onSomeWindowResizedOrMovedLocked();
                 }
@@ -6703,6 +6705,61 @@
         }
     }
 
+    private void checkCallerOwnsDisplay(int displayId) {
+        final Display display = mDisplayManager.getDisplay(displayId);
+        if (display == null) {
+            throw new IllegalArgumentException(
+                    "Cannot find display for non-existent displayId: " + displayId);
+        }
+
+        final int callingUid = Binder.getCallingUid();
+        final int displayOwnerUid = display.getOwnerUid();
+        if (callingUid != displayOwnerUid) {
+            throw new SecurityException("The caller doesn't own the display.");
+        }
+    }
+
+    /** @see Session#reparentDisplayContent(IWindow, SurfaceControl, int)  */
+    void reparentDisplayContent(IWindow client, SurfaceControl sc, int displayId) {
+        checkCallerOwnsDisplay(displayId);
+
+        synchronized (mGlobalLock) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                final WindowState win = windowForClientLocked(null, client, false);
+                if (win == null) {
+                    Slog.w(TAG_WM, "Bad requesting window " + client);
+                    return;
+                }
+                getDisplayContentOrCreate(displayId, null).reparentDisplayContent(win, sc);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+
+    /** @see Session#updateDisplayContentLocation(IWindow, int, int, int)  */
+    void updateDisplayContentLocation(IWindow client, int x, int y, int displayId) {
+        checkCallerOwnsDisplay(displayId);
+
+        synchronized (mGlobalLock) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                final WindowState win = windowForClientLocked(null, client, false);
+                if (win == null) {
+                    Slog.w(TAG_WM, "Bad requesting window " + client);
+                    return;
+                }
+                final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+                if (displayContent != null) {
+                    displayContent.updateLocation(win, x, y);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+    }
+
     /**
      * Update a tap exclude region in the window identified by the provided id. Touches down on this
      * region will not:
@@ -7538,31 +7595,6 @@
     }
 
     @Override
-    public void reparentDisplayContent(int displayId, SurfaceControl sc) {
-        final Display display = mDisplayManager.getDisplay(displayId);
-        if (display == null) {
-            throw new IllegalArgumentException(
-                    "Can't reparent display for non-existent displayId: " + displayId);
-        }
-
-        final int callingUid = Binder.getCallingUid();
-        final int displayOwnerUid = display.getOwnerUid();
-        if (callingUid != displayOwnerUid) {
-            throw new SecurityException("Only owner of the display can reparent surfaces to it.");
-        }
-
-        synchronized (mGlobalLock) {
-            long token = Binder.clearCallingIdentity();
-            try {
-                DisplayContent displayContent = getDisplayContentOrCreate(displayId, null);
-                displayContent.reparentDisplayContent(sc);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-    }
-
-    @Override
     public boolean injectInputAfterTransactionsApplied(InputEvent ev, int mode) {
         boolean shouldWaitForAnimToComplete = false;
         if (ev instanceof KeyEvent) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 8123182..2a621be 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3142,7 +3142,8 @@
             }
 
             //TODO (multidisplay): Accessibility supported only for the default display.
-            if (mWmService.mAccessibilityController != null && getDisplayId() == DEFAULT_DISPLAY) {
+            if (mWmService.mAccessibilityController != null && (getDisplayId() == DEFAULT_DISPLAY
+                    || getDisplayContent().getParentWindow() != null)) {
                 mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
             }
 
@@ -4207,7 +4208,8 @@
         }
 
         //TODO (multidisplay): Accessibility is supported only for the default display.
-        if (mWmService.mAccessibilityController != null && getDisplayId() == DEFAULT_DISPLAY) {
+        if (mWmService.mAccessibilityController != null && (getDisplayId() == DEFAULT_DISPLAY
+                || getDisplayContent().getParentWindow() != null)) {
             mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
         }
 
@@ -4646,6 +4648,18 @@
         int x = mSurfacePosition.x;
         int y = mSurfacePosition.y;
 
+        // We might be on a display which has been re-parented to a view in another window, so here
+        // computes the global location of our display.
+        DisplayContent dc = getDisplayContent();
+        while (dc != null && dc.getParentWindow() != null) {
+            final WindowState displayParent = dc.getParentWindow();
+            x += displayParent.mWindowFrames.mFrame.left - displayParent.mAttrs.surfaceInsets.left
+                    + (dc.getLocationInParentWindow().x * displayParent.mGlobalScale + 0.5f);
+            y += displayParent.mWindowFrames.mFrame.top - displayParent.mAttrs.surfaceInsets.top
+                    + (dc.getLocationInParentWindow().y * displayParent.mGlobalScale + 0.5f);
+            dc = displayParent.getDisplayContent();
+        }
+
         // If changed, also adjust transformFrameToSurfacePosition
         final WindowContainer parent = getParent();
         if (isChildWindow()) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 98d055c..3a8d3b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -493,4 +493,30 @@
         assertTrue(window.isVisible());
         assertTrue(window.isVisibleByPolicy());
     }
+
+    @Test
+    public void testGetTransformationMatrix() {
+        synchronized (mWm.mGlobalLock) {
+            final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
+            win0.getFrameLw().offsetTo(1, 0);
+
+            final DisplayContent dc = createNewDisplay();
+            dc.reparentDisplayContent(win0, win0.getSurfaceControl());
+            dc.updateLocation(win0, 2, 0);
+
+            final float[] values = new float[9];
+            final Matrix matrix = new Matrix();
+            final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+            final WindowState win1 = createWindow(null, TYPE_APPLICATION, dc, "win1");
+            win1.mHasSurface = true;
+            win1.mSurfaceControl = mock(SurfaceControl.class);
+            win1.getFrameLw().offsetTo(3, 0);
+            win1.updateSurfacePosition(t);
+            win1.getTransformationMatrix(values, matrix);
+
+            matrix.getValues(values);
+            assertEquals(6f, values[Matrix.MTRANS_X], 0f);
+            assertEquals(0f, values[Matrix.MTRANS_Y], 0f);
+        }
+    }
 }