Move pre-check in A11yController to the new A11yWindowManager

Bug: 112046331
Test: a11y CTS & unit tests
Change-Id: If012cb3d9eb1404864a16592d44edbc0893c81b7
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index d6657f1..8415272 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -31,6 +31,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.IWindow;
@@ -83,6 +84,7 @@
             mInteractionConnections = new SparseArray<>();
     private final SparseArray<SparseArray<IBinder>> mWindowTokens = new SparseArray<>();
 
+    private final List<WindowInfo> mCachedWindowInfos = new ArrayList<>();
     private List<AccessibilityWindowInfo> mWindows;
 
     private RemoteAccessibilityConnection mPictureInPictureActionReplacingConnection;
@@ -173,25 +175,133 @@
     }
 
     /**
-     * Callbacks from from window manager when there's an accessibility change in windows.
+     * Callbacks from window manager when there's an accessibility change in windows.
      *
+     * @param forceSend Send the windows for accessibility even if they haven't changed.
      * @param windows The windows of current display for accessibility.
      */
     @Override
-    public void onWindowsForAccessibilityChanged(@NonNull List<WindowInfo> windows) {
+    public void onWindowsForAccessibilityChanged(boolean forceSend,
+            @NonNull List<WindowInfo> windows) {
         synchronized (mLock) {
             if (DEBUG) {
                 Slog.i(LOG_TAG, "Windows changed: " + windows);
             }
 
-            // Let the policy update the focused and active windows.
-            updateWindowsLocked(mAccessibilityUserManager.getCurrentUserIdLocked(), windows);
-
-            // Someone may be waiting for the windows - advertise it.
-            mLock.notifyAll();
+            if (shouldUpdateWindowsLocked(forceSend, windows)) {
+                cacheWindows(windows);
+                // Let the policy update the focused and active windows.
+                updateWindowsLocked(mAccessibilityUserManager.getCurrentUserIdLocked(), windows);
+                // Someone may be waiting for the windows - advertise it.
+                mLock.notifyAll();
+            }
         }
     }
 
+    private boolean shouldUpdateWindowsLocked(boolean forceSend,
+            @NonNull List<WindowInfo> windows) {
+        if (forceSend) {
+            return true;
+        }
+
+        final int windowCount = windows.size();
+        // We computed the windows and if they changed notify the client.
+        if (mCachedWindowInfos.size() != windowCount) {
+            // Different size means something changed.
+            return true;
+        } else if (!mCachedWindowInfos.isEmpty() || !windows.isEmpty()) {
+            // Since we always traverse windows from high to low layer
+            // the old and new windows at the same index should be the
+            // same, otherwise something changed.
+            for (int i = 0; i < windowCount; i++) {
+                WindowInfo oldWindow = mCachedWindowInfos.get(i);
+                WindowInfo newWindow = windows.get(i);
+                // We do not care for layer changes given the window
+                // order does not change. This brings no new information
+                // to the clients.
+                if (windowChangedNoLayer(oldWindow, newWindow)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private void cacheWindows(List<WindowInfo> windows) {
+        final int oldWindowCount = mCachedWindowInfos.size();
+        for (int i = oldWindowCount - 1; i >= 0; i--) {
+            mCachedWindowInfos.remove(i).recycle();
+        }
+        final int newWindowCount = windows.size();
+        for (int i = 0; i < newWindowCount; i++) {
+            WindowInfo newWindow = windows.get(i);
+            mCachedWindowInfos.add(WindowInfo.obtain(newWindow));
+        }
+    }
+
+    private boolean windowChangedNoLayer(WindowInfo oldWindow, WindowInfo newWindow) {
+        if (oldWindow == newWindow) {
+            return false;
+        }
+        if (oldWindow == null) {
+            return true;
+        }
+        if (newWindow == null) {
+            return true;
+        }
+        if (oldWindow.type != newWindow.type) {
+            return true;
+        }
+        if (oldWindow.focused != newWindow.focused) {
+            return true;
+        }
+        if (oldWindow.token == null) {
+            if (newWindow.token != null) {
+                return true;
+            }
+        } else if (!oldWindow.token.equals(newWindow.token)) {
+            return true;
+        }
+        if (oldWindow.parentToken == null) {
+            if (newWindow.parentToken != null) {
+                return true;
+            }
+        } else if (!oldWindow.parentToken.equals(newWindow.parentToken)) {
+            return true;
+        }
+        if (oldWindow.activityToken == null) {
+            if (newWindow.activityToken != null) {
+                return true;
+            }
+        } else if (!oldWindow.activityToken.equals(newWindow.activityToken)) {
+            return true;
+        }
+        if (!oldWindow.boundsInScreen.equals(newWindow.boundsInScreen)) {
+            return true;
+        }
+        if (oldWindow.childTokens != null && newWindow.childTokens != null
+                && !oldWindow.childTokens.equals(newWindow.childTokens)) {
+            return true;
+        }
+        if (!TextUtils.equals(oldWindow.title, newWindow.title)) {
+            return true;
+        }
+        if (oldWindow.accessibilityIdOfAnchor != newWindow.accessibilityIdOfAnchor) {
+            return true;
+        }
+        if (oldWindow.inPictureInPicture != newWindow.inPictureInPicture) {
+            return true;
+        }
+        if (oldWindow.hasFlagWatchOutsideTouch != newWindow.hasFlagWatchOutsideTouch) {
+            return true;
+        }
+        if (oldWindow.displayId != newWindow.displayId) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Start tracking windows changes from window manager.
      */
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 6b7187e..a2891e9 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -43,9 +43,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.text.TextUtils;
 import android.util.ArraySet;
-import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TypedValue;
@@ -1036,12 +1034,9 @@
 
         private static final boolean DEBUG = false;
 
-        private final SparseArray<WindowState> mTempWindowStates =
-                new SparseArray<WindowState>();
+        private final SparseArray<WindowState> mTempWindowStates = new SparseArray<>();
 
-        private final List<WindowInfo> mOldWindows = new ArrayList<WindowInfo>();
-
-        private final Set<IBinder> mTempBinderSet = new ArraySet<IBinder>();
+        private final Set<IBinder> mTempBinderSet = new ArraySet<>();
 
         private final RectF mTempRectF = new RectF();
 
@@ -1098,8 +1093,7 @@
                 Slog.i(LOG_TAG, "computeChangedWindows()");
             }
 
-            boolean windowsChanged = false;
-            List<WindowInfo> windows = new ArrayList<WindowInfo>();
+            List<WindowInfo> windows = new ArrayList<>();
 
             synchronized (mService.mGlobalLock) {
                 // Do not send the windows if there is no current focus as
@@ -1169,46 +1163,9 @@
 
                 visibleWindows.clear();
                 addedWindows.clear();
-
-                if (!forceSend) {
-                    // We computed the windows and if they changed notify the client.
-                    if (mOldWindows.size() != windows.size()) {
-                        // Different size means something changed.
-                        windowsChanged = true;
-                    } else if (!mOldWindows.isEmpty() || !windows.isEmpty()) {
-                        // Since we always traverse windows from high to low layer
-                        // the old and new windows at the same index should be the
-                        // same, otherwise something changed.
-                        for (int i = 0; i < windowCount; i++) {
-                            WindowInfo oldWindow = mOldWindows.get(i);
-                            WindowInfo newWindow = windows.get(i);
-                            // We do not care for layer changes given the window
-                            // order does not change. This brings no new information
-                            // to the clients.
-                            if (windowChangedNoLayer(oldWindow, newWindow)) {
-                                windowsChanged = true;
-                                break;
-                            }
-                        }
-                    }
-                }
-
-                if (forceSend || windowsChanged) {
-                    cacheWindows(windows);
-                }
             }
 
-            // Now we do not hold the lock, so send the windows over.
-            if (forceSend || windowsChanged) {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "Windows changed or force sending:" + windows);
-                }
-                mCallback.onWindowsForAccessibilityChanged(windows);
-            } else {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "No windows changed.");
-                }
-            }
+            mCallback.onWindowsForAccessibilityChanged(forceSend, windows);
 
             // Recycle the windows as we do not need them.
             clearAndRecycleWindows(windows);
@@ -1313,67 +1270,6 @@
             tokenOut.add(window.token);
         }
 
-        private void cacheWindows(List<WindowInfo> windows) {
-            final int oldWindowCount = mOldWindows.size();
-            for (int i = oldWindowCount - 1; i >= 0; i--) {
-                mOldWindows.remove(i).recycle();
-            }
-            final int newWindowCount = windows.size();
-            for (int i = 0; i < newWindowCount; i++) {
-                WindowInfo newWindow = windows.get(i);
-                mOldWindows.add(WindowInfo.obtain(newWindow));
-            }
-        }
-
-        private boolean windowChangedNoLayer(WindowInfo oldWindow, WindowInfo newWindow) {
-            if (oldWindow == newWindow) {
-                return false;
-            }
-            if (oldWindow == null) {
-                return true;
-            }
-            if (newWindow == null) {
-                return true;
-            }
-            if (oldWindow.type != newWindow.type) {
-                return true;
-            }
-            if (oldWindow.focused != newWindow.focused) {
-                return true;
-            }
-            if (oldWindow.token == null) {
-                if (newWindow.token != null) {
-                    return true;
-                }
-            } else if (!oldWindow.token.equals(newWindow.token)) {
-                return true;
-            }
-            if (oldWindow.parentToken == null) {
-                if (newWindow.parentToken != null) {
-                    return true;
-                }
-            } else if (!oldWindow.parentToken.equals(newWindow.parentToken)) {
-                return true;
-            }
-            if (!oldWindow.boundsInScreen.equals(newWindow.boundsInScreen)) {
-                return true;
-            }
-            if (oldWindow.childTokens != null && newWindow.childTokens != null
-                    && !oldWindow.childTokens.equals(newWindow.childTokens)) {
-                return true;
-            }
-            if (!TextUtils.equals(oldWindow.title, newWindow.title)) {
-                return true;
-            }
-            if (oldWindow.accessibilityIdOfAnchor != newWindow.accessibilityIdOfAnchor) {
-                return true;
-            }
-            if (oldWindow.displayId != newWindow.displayId) {
-                return true;
-            }
-            return false;
-        }
-
         private static void clearAndRecycleWindows(List<WindowInfo> windows) {
             final int windowCount = windows.size();
             for (int i = windowCount - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 40bec14..6910ce9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -51,9 +51,10 @@
         /**
          * Called when the windows for accessibility changed.
          *
+         * @param forceSend Send the windows for accessibility even if they haven't changed.
          * @param windows The windows for accessibility.
          */
-        public void onWindowsForAccessibilityChanged(List<WindowInfo> windows);
+        void onWindowsForAccessibilityChanged(boolean forceSend, List<WindowInfo> windows);
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 04ca40e..22408cc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -70,6 +70,8 @@
  */
 public class AccessibilityWindowManagerTest {
     private static final String PACKAGE_NAME = "com.android.server.accessibility";
+    private static final boolean FORCE_SEND = true;
+    private static final boolean SEND_ON_WINDOW_CHANGES = false;
     private static final int USER_SYSTEM_ID = UserHandle.USER_SYSTEM;
     private static final int NUM_GLOBAL_WINDOWS = 4;
     private static final int NUM_APP_WINDOWS = 4;
@@ -122,7 +124,7 @@
         mWindowInfos.get(DEFAULT_FOCUSED_INDEX).focused = true;
         // Turn on windows tracking, and update window info
         mA11yWindowManager.startTrackingWindows();
-        mA11yWindowManager.onWindowsForAccessibilityChanged(mWindowInfos);
+        mA11yWindowManager.onWindowsForAccessibilityChanged(FORCE_SEND, mWindowInfos);
         assertEquals(mA11yWindowManager.getWindowListLocked().size(),
                 mWindowInfos.size());
 
@@ -169,16 +171,16 @@
     @Test
     public void onWindowsChanged_duringTouchInteractAndFocusChange_shouldChangeActiveWindow() {
         final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
-        WindowInfo focuedWindowInfo = mWindowInfos.get(DEFAULT_FOCUSED_INDEX);
+        WindowInfo focusedWindowInfo = mWindowInfos.get(DEFAULT_FOCUSED_INDEX);
         assertEquals(activeWindowId, mA11yWindowManager.findWindowIdLocked(
-                USER_SYSTEM_ID, focuedWindowInfo.token));
+                USER_SYSTEM_ID, focusedWindowInfo.token));
 
-        focuedWindowInfo.focused = false;
-        focuedWindowInfo = mWindowInfos.get(DEFAULT_FOCUSED_INDEX + 1);
-        focuedWindowInfo.focused = true;
+        focusedWindowInfo.focused = false;
+        focusedWindowInfo = mWindowInfos.get(DEFAULT_FOCUSED_INDEX + 1);
+        focusedWindowInfo.focused = true;
 
         mA11yWindowManager.onTouchInteractionStart();
-        mA11yWindowManager.onWindowsForAccessibilityChanged(mWindowInfos);
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
         assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
     }
 
@@ -208,6 +210,52 @@
     }
 
     @Test
+    public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
+        final WindowInfo windowInfo = mWindowInfos.get(0);
+        final int correctLayer = mA11yWindowManager.getWindowListLocked().get(0).getLayer();
+        windowInfo.layer += 1;
+
+        mA11yWindowManager.onWindowsForAccessibilityChanged(FORCE_SEND, mWindowInfos);
+        assertNotEquals(correctLayer, mA11yWindowManager.getWindowListLocked().get(0).getLayer());
+    }
+
+    @Test
+    public void onWindowsChangedNoForceSend_layerChanged_shouldNotUpdateWindows() {
+        final WindowInfo windowInfo = mWindowInfos.get(0);
+        final int correctLayer = mA11yWindowManager.getWindowListLocked().get(0).getLayer();
+        windowInfo.layer += 1;
+
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
+        assertEquals(correctLayer, mA11yWindowManager.getWindowListLocked().get(0).getLayer());
+    }
+
+    @Test
+    public void onWindowsChangedNoForceSend_windowChanged_shouldUpdateWindows()
+            throws RemoteException {
+        final AccessibilityWindowInfo oldWindow = mA11yWindowManager.getWindowListLocked().get(0);
+        final IWindow token = addAccessibilityInteractionConnection(true);
+        final WindowInfo windowInfo = WindowInfo.obtain();
+        windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION;
+        windowInfo.token = token.asBinder();
+        windowInfo.layer = 0;
+        windowInfo.boundsInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+        mWindowInfos.set(0, windowInfo);
+
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
+        assertNotEquals(oldWindow, mA11yWindowManager.getWindowListLocked().get(0));
+    }
+
+    @Test
+    public void onWindowsChangedNoForceSend_focusChanged_shouldUpdateWindows() {
+        final WindowInfo focusedWindowInfo = mWindowInfos.get(DEFAULT_FOCUSED_INDEX);
+        final WindowInfo windowInfo = mWindowInfos.get(0);
+        focusedWindowInfo.focused = false;
+        windowInfo.focused = true;
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
+        assertTrue(mA11yWindowManager.getWindowListLocked().get(0).isFocused());
+    }
+
+    @Test
     public void removeAccessibilityInteractionConnection_byWindowToken_shouldRemoved() {
         for (int i = 0; i < NUM_OF_WINDOWS; i++) {
             final int windowId = mA11yWindowTokens.keyAt(i);
@@ -264,7 +312,7 @@
         windowInfo = mWindowInfos.get(1);
         windowInfo.boundsInScreen.set(0, SCREEN_HEIGHT / 2,
                 SCREEN_WIDTH, SCREEN_HEIGHT);
-        mA11yWindowManager.onWindowsForAccessibilityChanged(mWindowInfos);
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
 
         final List<AccessibilityWindowInfo> a11yWindows = mA11yWindowManager.getWindowListLocked();
         final Region outBounds = new Region();
@@ -291,7 +339,7 @@
         windowInfo = mWindowInfos.get(1);
         windowInfo.boundsInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
 
-        mA11yWindowManager.onWindowsForAccessibilityChanged(mWindowInfos);
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
         final List<AccessibilityWindowInfo> a11yWindows = mA11yWindowManager.getWindowListLocked();
         final Region outBounds = new Region();
         int windowId = a11yWindows.get(1).getId();
@@ -309,7 +357,7 @@
         windowInfo.boundsInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
         windowInfo = mWindowInfos.get(1);
         windowInfo.boundsInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
-        mA11yWindowManager.onWindowsForAccessibilityChanged(mWindowInfos);
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
 
         final List<AccessibilityWindowInfo> a11yWindows = mA11yWindowManager.getWindowListLocked();
         final Region outBounds = new Region();
@@ -498,7 +546,7 @@
     public void getPictureInPictureWindow_shouldNotNull() {
         assertNull(mA11yWindowManager.getPictureInPictureWindow());
         mWindowInfos.get(1).inPictureInPicture = true;
-        mA11yWindowManager.onWindowsForAccessibilityChanged(mWindowInfos);
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
 
         assertNotNull(mA11yWindowManager.getPictureInPictureWindow());
     }
@@ -511,7 +559,7 @@
                 mA11yWindowManager.getConnectionLocked(
                         USER_SYSTEM_ID, outsideWindowId).getRemote();
         mWindowInfos.get(0).hasFlagWatchOutsideTouch = true;
-        mA11yWindowManager.onWindowsForAccessibilityChanged(mWindowInfos);
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
 
         mA11yWindowManager.notifyOutsideTouch(USER_SYSTEM_ID, targetWindowId);
         verify(mockRemoteConnection).notifyOutsideTouch();