1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wm;
18
19
20import android.util.Slog;
21
22import java.net.ServerSocket;
23import java.net.Socket;
24import java.net.InetAddress;
25import java.util.concurrent.ExecutorService;
26import java.util.concurrent.Executors;
27import java.io.IOException;
28import java.io.BufferedReader;
29import java.io.InputStreamReader;
30import java.io.OutputStream;
31import java.io.BufferedWriter;
32import java.io.OutputStreamWriter;
33
34/**
35 * The ViewServer is local socket server that can be used to communicate with the
36 * views of the opened windows. Communication with the views is ensured by the
37 * {@link com.android.server.wm.WindowManagerService} and is a cross-process operation.
38 *
39 * {@hide}
40 */
41class ViewServer implements Runnable {
42    /**
43     * The default port used to start view servers.
44     */
45    public static final int VIEW_SERVER_DEFAULT_PORT = 4939;
46
47    private static final int VIEW_SERVER_MAX_CONNECTIONS = 10;
48
49    // Debug facility
50    private static final String LOG_TAG = "ViewServer";
51
52    private static final String VALUE_PROTOCOL_VERSION = "4";
53    private static final String VALUE_SERVER_VERSION = "4";
54
55    // Protocol commands
56    // Returns the protocol version
57    private static final String COMMAND_PROTOCOL_VERSION = "PROTOCOL";
58    // Returns the server version
59    private static final String COMMAND_SERVER_VERSION = "SERVER";
60    // Lists all of the available windows in the system
61    private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST";
62    // Keeps a connection open and notifies when the list of windows changes
63    private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST";
64    // Returns the focused window
65    private static final String COMMAND_WINDOW_MANAGER_GET_FOCUS = "GET_FOCUS";
66
67    private ServerSocket mServer;
68    private Thread mThread;
69
70    private final WindowManagerService mWindowManager;
71    private final int mPort;
72
73    private ExecutorService mThreadPool;
74
75    /**
76     * Creates a new ViewServer associated with the specified window manager on the
77     * specified local port. The server is not started by default.
78     *
79     * @param windowManager The window manager used to communicate with the views.
80     * @param port The port for the server to listen to.
81     *
82     * @see #start()
83     */
84    ViewServer(WindowManagerService windowManager, int port) {
85        mWindowManager = windowManager;
86        mPort = port;
87    }
88
89    /**
90     * Starts the server.
91     *
92     * @return True if the server was successfully created, or false if it already exists.
93     * @throws IOException If the server cannot be created.
94     *
95     * @see #stop()
96     * @see #isRunning()
97     * @see WindowManagerService#startViewServer(int)
98     */
99    boolean start() throws IOException {
100        if (mThread != null) {
101            return false;
102        }
103
104        mServer = new ServerSocket(mPort, VIEW_SERVER_MAX_CONNECTIONS, InetAddress.getLocalHost());
105        mThread = new Thread(this, "Remote View Server [port=" + mPort + "]");
106        mThreadPool = Executors.newFixedThreadPool(VIEW_SERVER_MAX_CONNECTIONS);
107        mThread.start();
108
109        return true;
110    }
111
112    /**
113     * Stops the server.
114     *
115     * @return True if the server was stopped, false if an error occured or if the
116     *         server wasn't started.
117     *
118     * @see #start()
119     * @see #isRunning()
120     * @see WindowManagerService#stopViewServer()
121     */
122    boolean stop() {
123        if (mThread != null) {
124
125            mThread.interrupt();
126            if (mThreadPool != null) {
127                try {
128                    mThreadPool.shutdownNow();
129                } catch (SecurityException e) {
130                    Slog.w(LOG_TAG, "Could not stop all view server threads");
131                }
132            }
133            mThreadPool = null;
134            mThread = null;
135            try {
136                mServer.close();
137                mServer = null;
138                return true;
139            } catch (IOException e) {
140                Slog.w(LOG_TAG, "Could not close the view server");
141            }
142        }
143        return false;
144    }
145
146    /**
147     * Indicates whether the server is currently running.
148     *
149     * @return True if the server is running, false otherwise.
150     *
151     * @see #start()
152     * @see #stop()
153     * @see WindowManagerService#isViewServerRunning()
154     */
155    boolean isRunning() {
156        return mThread != null && mThread.isAlive();
157    }
158
159    /**
160     * Main server loop.
161     */
162    public void run() {
163        while (Thread.currentThread() == mThread) {
164            // Any uncaught exception will crash the system process
165            try {
166                Socket client = mServer.accept();
167                if (mThreadPool != null) {
168                    mThreadPool.submit(new ViewServerWorker(client));
169                } else {
170                    try {
171                        client.close();
172                    } catch (IOException e) {
173                        e.printStackTrace();
174                    }
175                }
176            } catch (Exception e) {
177                Slog.w(LOG_TAG, "Connection error: ", e);
178            }
179        }
180    }
181
182    private static boolean writeValue(Socket client, String value) {
183        boolean result;
184        BufferedWriter out = null;
185        try {
186            OutputStream clientStream = client.getOutputStream();
187            out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);
188            out.write(value);
189            out.write("\n");
190            out.flush();
191            result = true;
192        } catch (Exception e) {
193            result = false;
194        } finally {
195            if (out != null) {
196                try {
197                    out.close();
198                } catch (IOException e) {
199                    result = false;
200                }
201            }
202        }
203        return result;
204    }
205
206    class ViewServerWorker implements Runnable, WindowManagerService.WindowChangeListener {
207        private Socket mClient;
208        private boolean mNeedWindowListUpdate;
209        private boolean mNeedFocusedWindowUpdate;
210
211        public ViewServerWorker(Socket client) {
212            mClient = client;
213            mNeedWindowListUpdate = false;
214            mNeedFocusedWindowUpdate = false;
215        }
216
217        public void run() {
218
219            BufferedReader in = null;
220            try {
221                in = new BufferedReader(new InputStreamReader(mClient.getInputStream()), 1024);
222
223                final String request = in.readLine();
224
225                String command;
226                String parameters;
227
228                int index = request.indexOf(' ');
229                if (index == -1) {
230                    command = request;
231                    parameters = "";
232                } else {
233                    command = request.substring(0, index);
234                    parameters = request.substring(index + 1);
235                }
236
237                boolean result;
238                if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) {
239                    result = writeValue(mClient, VALUE_PROTOCOL_VERSION);
240                } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) {
241                    result = writeValue(mClient, VALUE_SERVER_VERSION);
242                } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) {
243                    result = mWindowManager.viewServerListWindows(mClient);
244                } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) {
245                    result = mWindowManager.viewServerGetFocusedWindow(mClient);
246                } else if (COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) {
247                    result = windowManagerAutolistLoop();
248                } else {
249                    result = mWindowManager.viewServerWindowCommand(mClient,
250                            command, parameters);
251                }
252
253                if (!result) {
254                    Slog.w(LOG_TAG, "An error occurred with the command: " + command);
255                }
256            } catch(IOException e) {
257                Slog.w(LOG_TAG, "Connection error: ", e);
258            } finally {
259                if (in != null) {
260                    try {
261                        in.close();
262
263                    } catch (IOException e) {
264                        e.printStackTrace();
265                    }
266                }
267                if (mClient != null) {
268                    try {
269                        mClient.close();
270                    } catch (IOException e) {
271                        e.printStackTrace();
272                    }
273                }
274            }
275        }
276
277        public void windowsChanged() {
278            synchronized(this) {
279                mNeedWindowListUpdate = true;
280                notifyAll();
281            }
282        }
283
284        public void focusChanged() {
285            synchronized(this) {
286                mNeedFocusedWindowUpdate = true;
287                notifyAll();
288            }
289        }
290
291        private boolean windowManagerAutolistLoop() {
292            mWindowManager.addWindowChangeListener(this);
293            BufferedWriter out = null;
294            try {
295                out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream()));
296                while (!Thread.interrupted()) {
297                    boolean needWindowListUpdate = false;
298                    boolean needFocusedWindowUpdate = false;
299                    synchronized (this) {
300                        while (!mNeedWindowListUpdate && !mNeedFocusedWindowUpdate) {
301                            wait();
302                        }
303                        if (mNeedWindowListUpdate) {
304                            mNeedWindowListUpdate = false;
305                            needWindowListUpdate = true;
306                        }
307                        if (mNeedFocusedWindowUpdate) {
308                            mNeedFocusedWindowUpdate = false;
309                            needFocusedWindowUpdate = true;
310                        }
311                    }
312                    if (needWindowListUpdate) {
313                        out.write("LIST UPDATE\n");
314                        out.flush();
315                    }
316                    if (needFocusedWindowUpdate) {
317                        out.write("FOCUS UPDATE\n");
318                        out.flush();
319                    }
320                }
321            } catch (Exception e) {
322                // Ignore
323            } finally {
324                if (out != null) {
325                    try {
326                        out.close();
327                    } catch (IOException e) {
328                        // Ignore
329                    }
330                }
331                mWindowManager.removeWindowChangeListener(this);
332            }
333            return true;
334        }
335    }
336}
337