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