/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wm; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.util.Slog; 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; import java.io.OutputStream; import java.io.BufferedWriter; import java.io.OutputStreamWriter; /** * The ViewServer is local socket server that can be used to communicate with the * views of the opened windows. Communication with the views is ensured by the * {@link com.android.server.wm.WindowManagerService} and is a cross-process operation. * * {@hide} */ class ViewServer implements Runnable { /** * The default port used to start view servers. */ 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 = TAG_WITH_CLASS_NAME ? "ViewServer" : TAG_WM; private static final String VALUE_PROTOCOL_VERSION = "4"; private static final String VALUE_SERVER_VERSION = "4"; // Protocol commands // Returns the protocol version private static final String COMMAND_PROTOCOL_VERSION = "PROTOCOL"; // Returns the server version 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"; // Returns the focused window private static final String COMMAND_WINDOW_MANAGER_GET_FOCUS = "GET_FOCUS"; private ServerSocket mServer; private Thread mThread; private final WindowManagerService mWindowManager; private final int mPort; private ExecutorService mThreadPool; /** * Creates a new ViewServer associated with the specified window manager on the * specified local port. The server is not started by default. * * @param windowManager The window manager used to communicate with the views. * @param port The port for the server to listen to. * * @see #start() */ ViewServer(WindowManagerService windowManager, int port) { mWindowManager = windowManager; mPort = port; } /** * Starts the server. * * @return True if the server was successfully created, or false if it already exists. * @throws IOException If the server cannot be created. * * @see #stop() * @see #isRunning() * @see WindowManagerService#startViewServer(int) */ boolean start() throws IOException { if (mThread != null) { return false; } 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; } /** * Stops the server. * * @return True if the server was stopped, false if an error occured or if the * server wasn't started. * * @see #start() * @see #isRunning() * @see WindowManagerService#stopViewServer() */ 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(); mServer = null; return true; } catch (IOException e) { Slog.w(LOG_TAG, "Could not close the view server"); } } return false; } /** * Indicates whether the server is currently running. * * @return True if the server is running, false otherwise. * * @see #start() * @see #stop() * @see WindowManagerService#isViewServerRunning() */ boolean isRunning() { return mThread != null && mThread.isAlive(); } /** * Main server loop. */ public void run() { while (Thread.currentThread() == mThread) { // Any uncaught exception will crash the system process try { 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); } } } private static boolean writeValue(Socket client, String value) { boolean result; BufferedWriter out = null; try { OutputStream clientStream = client.getOutputStream(); out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024); out.write(value); out.write("\n"); out.flush(); result = true; } catch (Exception e) { result = false; } finally { if (out != null) { try { out.close(); } catch (IOException e) { result = false; } } } return result; } class ViewServerWorker implements Runnable, WindowManagerService.WindowChangeListener { private Socket mClient; private boolean mNeedWindowListUpdate; private boolean mNeedFocusedWindowUpdate; public ViewServerWorker(Socket client) { mClient = client; mNeedWindowListUpdate = false; mNeedFocusedWindowUpdate = false; } 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_GET_FOCUS.equalsIgnoreCase(command)) { result = mWindowManager.viewServerGetFocusedWindow(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 occurred 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(); } } public void focusChanged() { synchronized(this) { mNeedFocusedWindowUpdate = true; notifyAll(); } } private boolean windowManagerAutolistLoop() { mWindowManager.addWindowChangeListener(this); BufferedWriter out = null; try { out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream())); while (!Thread.interrupted()) { boolean needWindowListUpdate = false; boolean needFocusedWindowUpdate = false; synchronized (this) { while (!mNeedWindowListUpdate && !mNeedFocusedWindowUpdate) { wait(); } if (mNeedWindowListUpdate) { mNeedWindowListUpdate = false; needWindowListUpdate = true; } if (mNeedFocusedWindowUpdate) { mNeedFocusedWindowUpdate = false; needFocusedWindowUpdate = true; } } if (needWindowListUpdate) { out.write("LIST UPDATE\n"); out.flush(); } if (needFocusedWindowUpdate) { out.write("ACTION_FOCUS UPDATE\n"); out.flush(); } } } catch (Exception e) { // Ignore } finally { if (out != null) { try { out.close(); } catch (IOException e) { // Ignore } } mWindowManager.removeWindowChangeListener(this); } return true; } } }