Fix AccessibilityService#getWindows time out

In our design, we computes the windows for each display.
Ideally the window list is initialized  when the window observer is
created.However, if the top focus window is null, the window list
won't beupdated unless there is the windows change on that display.

In general case, most of non-default displays don't have windows, so
their window list won't be initialized. It affects that the callee
needs to wait 5 seconds for time out mechanism.

To fix it, we collects the window observers that don't receive the
callback when it is constructed. Then sending the callback to them
when the window foucs is available next time.

Bug: 169542227
Test: manually check if windows reports correctly.

Change-Id: I1a3dc780de5873062b220bd1588c855138aa3657
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index ca5e38d..b2c1f5b 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -69,6 +69,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -92,6 +93,9 @@
     private SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver =
             new SparseArray<>();
 
+    // Set to true if initializing window population complete.
+    private boolean mAllObserversInitialized = true;
+
     public boolean setMagnificationCallbacksLocked(int displayId,
             MagnificationCallbacks callbacks) {
         boolean result = false;
@@ -110,7 +114,7 @@
             }
         } else {
             final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
-            if  (displayMagnifier == null) {
+            if (displayMagnifier == null) {
                 throw new IllegalStateException("Magnification callbacks already cleared!");
             }
             displayMagnifier.destroyLocked();
@@ -150,8 +154,10 @@
                         "Windows for accessibility callback of display "
                                 + displayId + " already set!");
             }
-            mWindowsForAccessibilityObserver.put(displayId,
-                    new WindowsForAccessibilityObserver(mService, displayId, callback));
+            final WindowsForAccessibilityObserver observer =
+                    new WindowsForAccessibilityObserver(mService, displayId, callback);
+            mWindowsForAccessibilityObserver.put(displayId, observer);
+            mAllObserversInitialized &= observer.mInitialized;
         } else {
             if (isEmbeddedDisplay(dc)) {
                 // If this display is an embedded one, its window observer should be removed along
@@ -275,6 +281,41 @@
         if (observer != null) {
             observer.performComputeChangedWindowsNotLocked(false);
         }
+        // Since we abandon initializing observers if no window has focus, make sure all observers
+        // are initialized.
+        sendCallbackToUninitializedObserversIfNeeded();
+    }
+
+    private void sendCallbackToUninitializedObserversIfNeeded() {
+        List<WindowsForAccessibilityObserver> unInitializedObservers;
+        synchronized (mService.mGlobalLock) {
+            if (mAllObserversInitialized) {
+                return;
+            }
+            if (mService.mRoot.getTopFocusedDisplayContent().mCurrentFocus == null) {
+                return;
+            }
+            unInitializedObservers = new ArrayList<>();
+            for (int i = mWindowsForAccessibilityObserver.size() - 1; i >= 0; --i) {
+                final WindowsForAccessibilityObserver observer =
+                        mWindowsForAccessibilityObserver.valueAt(i);
+                if (!observer.mInitialized) {
+                    unInitializedObservers.add(observer);
+                }
+            }
+            // Reset the flag to record the new added observer.
+            mAllObserversInitialized = true;
+        }
+
+        boolean areAllObserversInitialized = true;
+        for (int i = unInitializedObservers.size() - 1; i >= 0; --i) {
+            final  WindowsForAccessibilityObserver observer = unInitializedObservers.get(i);
+            observer.performComputeChangedWindowsNotLocked(true);
+            areAllObserversInitialized &= observer.mInitialized;
+        }
+        synchronized (mService.mGlobalLock) {
+            mAllObserversInitialized &= areAllObserversInitialized;
+        }
     }
 
     /**
@@ -361,6 +402,8 @@
                         + "Magnification display# " + mDisplayMagnifiers.keyAt(i));
             }
         }
+        pw.println(prefix
+                + "mWindowsForAccessibilityObserver=" + mWindowsForAccessibilityObserver);
     }
 
     private void removeObserverOfEmbeddedDisplay(WindowsForAccessibilityObserver
@@ -1214,6 +1257,9 @@
 
         private final IntArray mEmbeddedDisplayIdList = new IntArray(0);
 
+        // Set to true if initializing window population complete.
+        private boolean mInitialized;
+
         public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
                 int displayId,
                 WindowsForAccessibilityCallback callback) {
@@ -1272,10 +1318,17 @@
                 // the window manager is still looking for where to put it.
                 // We will do the work when we get a focus change callback.
                 final WindowState topFocusedWindowState = getTopFocusWindow();
-                if (topFocusedWindowState == null) return;
+                if (topFocusedWindowState == null) {
+                    if (DEBUG) {
+                        Slog.d(LOG_TAG, "top focused window is null, compute it again later");
+                    }
+                    return;
+                }
 
                 final DisplayContent dc = mService.mRoot.getDisplayContent(mDisplayId);
                 if (dc == null) {
+                    //It should not happen because it is created while adding the callback.
+                    Slog.w(LOG_TAG, "display content is null, should be created later");
                     return;
                 }
                 final Display display = dc.getDisplay();
@@ -1362,6 +1415,7 @@
 
             // Recycle the windows as we do not need them.
             clearAndRecycleWindows(windows);
+            mInitialized = true;
         }
 
         private boolean windowMattersToAccessibility(WindowState windowState,
@@ -1547,6 +1601,16 @@
             return mService.mRoot.getTopFocusedDisplayContent().mCurrentFocus;
         }
 
+        @Override
+        public String toString() {
+            return "WindowsForAccessibilityObserver{"
+                    + "mDisplayId=" + mDisplayId
+                    + ", mEmbeddedDisplayIdList="
+                    + Arrays.toString(mEmbeddedDisplayIdList.toArray())
+                    + ", mInitialized=" + mInitialized
+                    + '}';
+        }
+
         private class MyHandler extends Handler {
             public static final int MESSAGE_COMPUTE_CHANGED_WINDOWS = 1;