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