Making sure that the list of windows updates automatically in hierarchy viewer (View Server side)

Change-Id: I0f49ee8b6950ad167bd224093150050e19fd1dd7
diff --git a/services/java/com/android/server/ViewServer.java b/services/java/com/android/server/ViewServer.java
index ae00438..9c7db9b 100644
--- a/services/java/com/android/server/ViewServer.java
+++ b/services/java/com/android/server/ViewServer.java
@@ -21,6 +21,8 @@
 import java.net.ServerSocket;
 import java.net.Socket;
 import java.net.InetAddress;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.io.IOException;
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
@@ -41,11 +43,13 @@
      */
     public static final int VIEW_SERVER_DEFAULT_PORT = 4939;
 
+    private static final int VIEW_SERVER_MAX_CONNECTIONS = 10;
+
     // Debug facility
     private static final String LOG_TAG = "ViewServer";
 
-    private static final String VALUE_PROTOCOL_VERSION = "2";
-    private static final String VALUE_SERVER_VERSION = "3";
+    private static final String VALUE_PROTOCOL_VERSION = "3";
+    private static final String VALUE_SERVER_VERSION = "4";
 
     // Protocol commands
     // Returns the protocol version
@@ -54,6 +58,8 @@
     private static final String COMMAND_SERVER_VERSION = "SERVER";
     // Lists all of the available windows in the system
     private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST";
+    // Keeps a connection open and notifies when the list of windows changes
+    private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST";
 
     private ServerSocket mServer;
     private Thread mThread;
@@ -61,6 +67,8 @@
     private final WindowManagerService mWindowManager;
     private final int mPort;
 
+    private ExecutorService mThreadPool;
+
     /**
      * Creates a new ViewServer associated with the specified window manager.
      * The server uses the default port {@link #VIEW_SERVER_DEFAULT_PORT}. The server
@@ -103,8 +111,9 @@
             return false;
         }
 
-        mServer = new ServerSocket(mPort, 1, InetAddress.getLocalHost());
+        mServer = new ServerSocket(mPort, VIEW_SERVER_MAX_CONNECTIONS, InetAddress.getLocalHost());
         mThread = new Thread(this, "Remote View Server [port=" + mPort + "]");
+        mThreadPool = Executors.newFixedThreadPool(VIEW_SERVER_MAX_CONNECTIONS);
         mThread.start();
 
         return true;
@@ -122,7 +131,16 @@
      */
     boolean stop() {
         if (mThread != null) {
+
             mThread.interrupt();
+            if (mThreadPool != null) {
+                try {
+                    mThreadPool.shutdownNow();
+                } catch (SecurityException e) {
+                    Slog.w(LOG_TAG, "Could not stop all view server threads");
+                }
+            }
+            mThreadPool = null;
             mThread = null;
             try {
                 mServer.close();
@@ -152,62 +170,21 @@
      * Main server loop.
      */
     public void run() {
-        final ServerSocket server = mServer;
-
         while (Thread.currentThread() == mThread) {
-            Socket client = null;
             // Any uncaught exception will crash the system process
             try {
-                client = server.accept();
-
-                BufferedReader in = null;
-                try {
-                    in = new BufferedReader(new InputStreamReader(client.getInputStream()), 1024);
-
-                    final String request = in.readLine();
-
-                    String command;
-                    String parameters;
-
-                    int index = request.indexOf(' ');
-                    if (index == -1) {
-                        command = request;
-                        parameters = "";
-                    } else {
-                        command = request.substring(0, index);
-                        parameters = request.substring(index + 1);
-                    }
-
-                    boolean result;
-                    if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) {
-                        result = writeValue(client, VALUE_PROTOCOL_VERSION);
-                    } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) {
-                        result = writeValue(client, VALUE_SERVER_VERSION);
-                    } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) {
-                        result = mWindowManager.viewServerListWindows(client);
-                    } else {
-                        result = mWindowManager.viewServerWindowCommand(client,
-                                command, parameters);
-                    }
-
-                    if (!result) {
-                        Slog.w(LOG_TAG, "An error occured with the command: " + command);
-                    }
-                } finally {
-                    if (in != null) {
-                        in.close();
-                    }
-                }
-            } catch (Exception e) {
-                Slog.w(LOG_TAG, "Connection error: ", e);
-            } finally {
-                if (client != null) {
+                Socket client = mServer.accept();
+                if(mThreadPool != null) {
+                    mThreadPool.submit(new ViewServerWorker(client));
+                } else {
                     try {
                         client.close();
                     } catch (IOException e) {
                         e.printStackTrace();
                     }
                 }
+            } catch (Exception e) {
+                Slog.w(LOG_TAG, "Connection error: ", e);
             }
         }
     }
@@ -235,4 +212,106 @@
         }
         return result;
     }
+
+    class ViewServerWorker implements Runnable, WindowManagerService.WindowChangeListener {
+        private Socket mClient;
+        private boolean mNeedWindowListUpdate;
+        public ViewServerWorker(Socket client) {
+            mClient = client;
+        }
+
+        public void run() {
+
+            BufferedReader in = null;
+            try {
+                in = new BufferedReader(new InputStreamReader(mClient.getInputStream()), 1024);
+
+                final String request = in.readLine();
+
+                String command;
+                String parameters;
+
+                int index = request.indexOf(' ');
+                if (index == -1) {
+                    command = request;
+                    parameters = "";
+                } else {
+                    command = request.substring(0, index);
+                    parameters = request.substring(index + 1);
+                }
+
+                boolean result;
+                if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) {
+                    result = writeValue(mClient, VALUE_PROTOCOL_VERSION);
+                } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) {
+                    result = writeValue(mClient, VALUE_SERVER_VERSION);
+                } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) {
+                    result = mWindowManager.viewServerListWindows(mClient);
+                } else if(COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) {
+                    result = windowManagerAutolistLoop();
+                } else {
+                    result = mWindowManager.viewServerWindowCommand(mClient,
+                            command, parameters);
+                }
+
+                if (!result) {
+                    Slog.w(LOG_TAG, "An error occured with the command: " + command);
+                }
+            } catch(IOException e) {
+                Slog.w(LOG_TAG, "Connection error: ", e);
+            } finally {
+                if (in != null) {
+                    try {
+                        in.close();
+
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+                if (mClient != null) {
+                    try {
+                        mClient.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+
+        public void windowsChanged() {
+            synchronized(this) {
+                mNeedWindowListUpdate = true;
+                notifyAll();
+            }
+        }
+
+        private boolean windowManagerAutolistLoop() {
+            mWindowManager.addWindowChangeListener(this);
+            BufferedWriter out = null;
+            try {
+                out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream()));
+                while (!Thread.interrupted()) {
+                    synchronized (this) {
+                        while (!mNeedWindowListUpdate) {
+                            wait();
+                        }
+                        mNeedWindowListUpdate = false;
+                    }
+                    out.write("UPDATE\n");
+                    out.flush();
+                }
+            } catch (Exception e) {
+                Slog.w(LOG_TAG, "Connection error: ", e);
+            } finally {
+                if (out != null) {
+                    try {
+                        out.close();
+                    } catch (IOException e) {
+                    }
+                }
+                mWindowManager.removeWindowChangeListener(this);
+            }
+            return true;
+        }
+    }
 }
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 38f1e1f..833d08e 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -489,6 +489,13 @@
     boolean mInTouchMode = false;
 
     private ViewServer mViewServer;
+    private ArrayList<WindowChangeListener> mWindowChangeListeners =
+        new ArrayList<WindowChangeListener>();
+    private boolean mWindowsChanged = false;
+
+    public interface WindowChangeListener {
+        public void windowsChanged();
+    }
 
     final Configuration mTempConfiguration = new Configuration();
     int mScreenLayout = Configuration.SCREENLAYOUT_SIZE_UNDEFINED;
@@ -662,6 +669,7 @@
             TAG, "Adding window " + window + " at "
             + (i+1) + " of " + mWindows.size() + " (after " + pos + ")");
         mWindows.add(i+1, window);
+        mWindowsChanged = true;
     }
 
     private void placeWindowBefore(Object pos, WindowState window) {
@@ -670,6 +678,7 @@
             TAG, "Adding window " + window + " at "
             + i + " of " + mWindows.size() + " (before " + pos + ")");
         mWindows.add(i, window);
+        mWindowsChanged = true;
     }
 
     //This method finds out the index of a window that has the same app token as
@@ -727,6 +736,7 @@
                                         TAG, "Adding window " + win + " at "
                                         + (newIdx+1) + " of " + N);
                                 localmWindows.add(newIdx+1, win);
+                                mWindowsChanged = true;
                             }
                         }
                     }
@@ -809,6 +819,7 @@
                                     TAG, "Adding window " + win + " at "
                                     + i + " of " + N);
                             localmWindows.add(i, win);
+                            mWindowsChanged = true;
                         }
                     }
                 }
@@ -826,6 +837,7 @@
                         TAG, "Adding window " + win + " at "
                         + i + " of " + N);
                 localmWindows.add(i, win);
+                mWindowsChanged = true;
             }
             if (addToToken) {
                 token.windows.add(tokenWindowsPos, win);
@@ -1034,6 +1046,7 @@
             if (DEBUG_WINDOW_MOVEMENT) Slog.v(
                     TAG, "Adding input method window " + win + " at " + pos);
             mWindows.add(pos, win);
+            mWindowsChanged = true;
             moveInputMethodDialogsLocked(pos+1);
             return;
         }
@@ -1075,6 +1088,7 @@
             if (wpos < interestingPos) interestingPos--;
             if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Temp removing at " + wpos + ": " + win);
             mWindows.remove(wpos);
+            mWindowsChanged = true;
             int NC = win.mChildWindows.size();
             while (NC > 0) {
                 NC--;
@@ -1101,6 +1115,7 @@
             if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "ReAdd removing from " + wpos
                     + ": " + win);
             mWindows.remove(wpos);
+            mWindowsChanged = true;
             reAddWindowLocked(wpos, win);
         }
     }
@@ -1561,6 +1576,7 @@
                     if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Wallpaper removing at "
                             + oldIndex + ": " + wallpaper);
                     localmWindows.remove(oldIndex);
+                    mWindowsChanged = true;
                     if (oldIndex < foundI) {
                         foundI--;
                     }
@@ -1572,6 +1588,7 @@
                         + " from " + oldIndex + " to " + foundI);
 
                 localmWindows.add(foundI, wallpaper);
+                mWindowsChanged = true;
                 changed |= ADJUST_WALLPAPER_LAYERS_CHANGED;
             }
         }
@@ -2078,6 +2095,7 @@
 
         mWindowMap.remove(win.mClient.asBinder());
         mWindows.remove(win);
+        mWindowsChanged = true;
         if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Final remove of window: " + win);
 
         if (mInputMethodWindow == win) {
@@ -3360,6 +3378,7 @@
                         if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG,
                                 "Removing starting window: " + startingWindow);
                         mWindows.remove(startingWindow);
+                        mWindowsChanged = true;
                         ttoken.windows.remove(startingWindow);
                         ttoken.allAppWindows.remove(startingWindow);
                         addWindowToListInOrderLocked(startingWindow, true);
@@ -3841,6 +3860,7 @@
             WindowState win = token.windows.get(i);
             if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Tmp removing app window " + win);
             mWindows.remove(win);
+            mWindowsChanged = true;
             int j = win.mChildWindows.size();
             while (j > 0) {
                 j--;
@@ -3945,6 +3965,7 @@
             mWindows.add(index, win);
             index++;
         }
+        mWindowsChanged = true;
         return index;
     }
 
@@ -4783,6 +4804,33 @@
         return success;
     }
 
+    public void addWindowChangeListener(WindowChangeListener listener) {
+        synchronized(mWindowMap) {
+            mWindowChangeListeners.add(listener);
+        }
+    }
+
+    public void removeWindowChangeListener(WindowChangeListener listener) {
+        synchronized(mWindowMap) {
+            mWindowChangeListeners.remove(listener);
+        }
+    }
+
+    private void notifyWindowsChanged() {
+        WindowChangeListener[] windowChangeListeners;
+        synchronized(mWindowMap) {
+            if(mWindowChangeListeners.isEmpty()) {
+                return;
+            }
+            windowChangeListeners = new WindowChangeListener[mWindowChangeListeners.size()];
+            windowChangeListeners = mWindowChangeListeners.toArray(windowChangeListeners);
+        }
+        int N = windowChangeListeners.length;
+        for(int i = 0; i < N; i++) {
+            windowChangeListeners[i].windowsChanged();
+        }
+    }
+
     private WindowState findWindow(int hashCode) {
         if (hashCode == -1) {
             return getFocusedWindow();
@@ -7672,6 +7720,7 @@
         public static final int ENABLE_SCREEN = 16;
         public static final int APP_FREEZE_TIMEOUT = 17;
         public static final int SEND_NEW_CONFIGURATION = 18;
+        public static final int WINDOWS_CHANGED = 19;
 
         private Session mLastReportedHold;
 
@@ -8003,6 +8052,16 @@
                     break;
                 }
 
+                case WINDOWS_CHANGED: {
+                    if (mWindowsChanged) {
+                        synchronized (mWindowMap) {
+                            mWindowsChanged = false;
+                        }
+                        notifyWindowsChanged();
+                    }
+                    break;
+                }
+
             }
         }
     }
@@ -8087,6 +8146,7 @@
             WindowState w = (WindowState)mWindows.get(i);
             if (w.mAppToken != null) {
                 WindowState win = (WindowState)mWindows.remove(i);
+                mWindowsChanged = true;
                 if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG,
                         "Rebuild removing window: " + win);
                 NW--;
@@ -8222,6 +8282,10 @@
                     requestAnimationLocked(0);
                 }
             }
+            if (mWindowsChanged && !mWindowChangeListeners.isEmpty()) {
+                mH.removeMessages(H.WINDOWS_CHANGED);
+                mH.sendMessage(mH.obtainMessage(H.WINDOWS_CHANGED));
+            }
         } catch (RuntimeException e) {
             mInLayout = false;
             Slog.e(TAG, "Unhandled exception while layout out windows", e);