Roll NDK to pick std::deque patch.
[android_tools.git] / sdk / tools / apps / SdkController / src / com / android / tools / sdkcontroller / lib / Connection.java
blobcb508690593e932bc3f95775a501c492cafafaa7
1 /*
2 * Copyright (C) 2012 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
17 package com.android.tools.sdkcontroller.lib;
19 import java.io.IOException;
20 import java.nio.ByteBuffer;
21 import java.nio.ByteOrder;
22 import java.util.ArrayList;
23 import java.util.List;
25 import android.util.Log;
26 import android.net.LocalServerSocket;
27 import android.net.LocalSocket;
29 import com.android.tools.sdkcontroller.lib.Channel;
30 import com.android.tools.sdkcontroller.service.ControllerService;
32 /**
33 * Encapsulates a connection between SdkController service and the emulator. On
34 * the device side, the connection is bound to the UNIX-domain socket named
35 * 'android.sdk.controller'. On the emulator side the connection is established
36 * via TCP port that is used to forward I/O traffic on the host machine to
37 * 'android.sdk.controller' socket on the device. Typically, the port forwarding
38 * can be enabled using adb command:
39 * <p/>
40 * 'adb forward tcp:<TCP port number> localabstract:android.sdk.controller'
41 * <p/>
42 * The way communication between the emulator and SDK controller service works
43 * is as follows:
44 * <p/>
45 * 1. Both sides, emulator and the service have components that implement a particular
46 * type of emulation. For instance, AndroidSensorsPort in the emulator, and
47 * SensorChannel in the application implement sensors emulation.
48 * Emulation channels are identified by unique names. For instance, sensor emulation
49 * is done via "sensors" channel, multi-touch emulation is done via "multi-touch"
50 * channel, etc.
51 * <p/>
52 * 2. Channels are connected to emulator via separate socket instance (though all
53 * of the connections share the same socket address).
54 * <p/>
55 * 3. Connection is initiated by the emulator side, while the service provides
56 * its side (a channel) that implement functionality and exchange protocol required
57 * by the requested type of emulation.
58 * <p/>
59 * Given that, the main responsibilities of this class are:
60 * <p/>
61 * 1. Bind to "android.sdk.controller" socket, listening to emulator connections.
62 * <p/>
63 * 2. Maintain a list of service-side channels registered by the application.
64 * <p/>
65 * 3. Bind emulator connection with service-side channel via port name, provided by
66 * the emulator.
67 * <p/>
68 * 4. Monitor connection state with the emulator, and automatically restore the
69 * connection once it is lost.
71 public class Connection {
72 /** UNIX-domain name reserved for SDK controller. */
73 public static final String SDK_CONTROLLER_PORT = "android.sdk.controller";
74 /** Tag for logging messages. */
75 private static final String TAG = "SdkControllerConnection";
76 /** Controls debug logging */
77 private static final boolean DEBUG = false;
79 /** Server socket used to listen to emulator connections. */
80 private LocalServerSocket mServerSocket = null;
81 /** Service that has created this object. */
82 private ControllerService mService;
83 /**
84 * List of connected emulator sockets, pending for a channel to be registered.
85 * <p/>
86 * Emulator may connect to SDK controller before the app registers a channel
87 * for that connection. In this case (when app-side channel is not registered
88 * with this class) we will keep emulator connection in this list, pending
89 * for the app-side channel to register.
91 private List<Socket> mPendingSockets = new ArrayList<Socket>();
92 /**
93 * List of registered app-side channels.
94 * <p/>
95 * Channels that are kept in this list may be disconnected from (or pending
96 * connection with) the emulator, or they may be connected with the
97 * emulator.
99 private List<Channel> mChannels = new ArrayList<Channel>();
102 * Constructs Connection instance.
104 public Connection(ControllerService service) {
105 mService = service;
106 if (DEBUG) Log.d(TAG, "SdkControllerConnection is constructed.");
110 * Binds to the socket, and starts the listening thread.
112 public void connect() {
113 if (DEBUG) Log.d(TAG, "SdkControllerConnection is connecting...");
114 // Start connection listener.
115 new Thread(new Runnable() {
116 @Override
117 public void run() {
118 runIOLooper();
120 }, "SdkControllerConnectionIoLoop").start();
124 * Stops the listener, and closes the socket.
126 * @return true if connection has been stopped in this call, or false if it
127 * has been already stopped when this method has been called.
129 public boolean disconnect() {
130 // This is the only place in this class where we will null the
131 // socket object. Since this method can be called concurrently from
132 // different threads, lets do this under the lock.
133 LocalServerSocket socket;
134 synchronized (this) {
135 socket = mServerSocket;
136 mServerSocket = null;
138 if (socket != null) {
139 if (DEBUG) Log.d(TAG, "SdkControllerConnection is stopping I/O looper...");
140 // Stop accepting new connections.
141 wakeIOLooper(socket);
142 try {
143 socket.close();
144 } catch (Exception e) {
147 // Close all the pending sockets, and clear pending socket list.
148 if (DEBUG) Log.d(TAG, "SdkControllerConnection is closing pending sockets...");
149 for (Socket pending_socket : mPendingSockets) {
150 pending_socket.close();
152 mPendingSockets.clear();
154 // Disconnect all the emualtors.
155 if (DEBUG) Log.d(TAG, "SdkControllerConnection is disconnecting channels...");
156 for (Channel channel : mChannels) {
157 if (channel.disconnect()) {
158 channel.onEmulatorDisconnected();
161 if (DEBUG) Log.d(TAG, "SdkControllerConnection is disconnected.");
163 return socket != null;
167 * Registers SDK controller channel.
169 * @param channel SDK controller emulator to register.
170 * @return true if channel has been registered successfully, or false if channel
171 * with the same name is already registered.
173 public boolean registerChannel(Channel channel) {
174 for (Channel check_channel : mChannels) {
175 if (check_channel.getChannelName().equals(channel.getChannelName())) {
176 Loge("Registering a duplicate Channel " + channel.getChannelName());
177 return false;
180 if (DEBUG) Log.d(TAG, "Registering Channel " + channel.getChannelName());
181 mChannels.add(channel);
183 // Lets see if there is a pending socket for this channel.
184 for (Socket pending_socket : mPendingSockets) {
185 if (pending_socket.getChannelName().equals(channel.getChannelName())) {
186 // Remove the socket from the pending list, and connect the registered channel with it.
187 if (DEBUG) Log.d(TAG, "Found pending Socket for registering Channel "
188 + channel.getChannelName());
189 mPendingSockets.remove(pending_socket);
190 channel.connect(pending_socket);
193 return true;
197 * Checks if at least one socket connection exists with channel.
199 * @return true if at least one socket connection exists with channel.
201 public boolean isEmulatorConnected() {
202 for (Channel channel : mChannels) {
203 if (channel.isConnected()) {
204 return true;
207 return !mPendingSockets.isEmpty();
211 * Gets Channel instance for the given channel name.
213 * @param name Channel name to get Channel instance for.
214 * @return Channel instance for the given channel name, or NULL if no
215 * channel has been registered for that name.
217 public Channel getChannel(String name) {
218 for (Channel channel : mChannels) {
219 if (channel.getChannelName().equals(name)) {
220 return channel;
223 return null;
227 * Gets connected emulator socket that is pending for service-side channel
228 * registration.
230 * @param name Channel name to lookup Socket for.
231 * @return Connected emulator socket that is pending for service-side channel
232 * registration, or null if no socket is pending for service-size
233 * channel registration.
235 private Socket getPendingSocket(String name) {
236 for (Socket socket : mPendingSockets) {
237 if (socket.getChannelName().equals(name)) {
238 return socket;
241 return null;
245 * Wakes I/O looper waiting on connection with the emulator.
247 * @param socket Server socket waiting on connection.
249 private void wakeIOLooper(LocalServerSocket socket) {
250 // We wake the looper by connecting to the socket.
251 LocalSocket waker = new LocalSocket();
252 try {
253 waker.connect(socket.getLocalSocketAddress());
254 } catch (IOException e) {
255 Loge("Exception " + e + " in SdkControllerConnection while waking up the I/O looper.");
260 * Loops on the local socket, handling emulator connection attempts.
262 private void runIOLooper() {
263 if (DEBUG) Log.d(TAG, "In SdkControllerConnection I/O looper.");
264 do {
265 try {
266 // Create non-blocking server socket that would listen for connections,
267 // and bind it to the given port on the local host.
268 mServerSocket = new LocalServerSocket(SDK_CONTROLLER_PORT);
269 LocalServerSocket socket = mServerSocket;
270 while (socket != null) {
271 final LocalSocket sk = socket.accept();
272 if (mServerSocket != null) {
273 onAccept(sk);
274 } else {
275 break;
277 socket = mServerSocket;
279 } catch (IOException e) {
280 Loge("Exception " + e + "SdkControllerConnection I/O looper.");
282 if (DEBUG) Log.d(TAG, "Exiting SdkControllerConnection I/O looper.");
284 // If we're exiting the internal loop for reasons other than an explicit
285 // disconnect request, we should reconnect again.
286 } while (disconnect());
290 * Accepts new connection from the emulator.
292 * @param sock Connecting socket.
293 * @throws IOException
295 private void onAccept(LocalSocket sock) throws IOException {
296 final ByteBuffer handshake = ByteBuffer.allocate(ProtocolConstants.QUERY_HEADER_SIZE);
298 // By protocol, first byte received from newly connected emulator socket
299 // indicates host endianness.
300 Socket.receive(sock, handshake.array(), 1);
301 final ByteOrder endian = (handshake.getChar() == 0) ? ByteOrder.LITTLE_ENDIAN :
302 ByteOrder.BIG_ENDIAN;
303 handshake.order(endian);
305 // Right after that follows the handshake query header.
306 handshake.position(0);
307 Socket.receive(sock, handshake.array(), handshake.array().length);
309 // First int - signature
310 final int signature = handshake.getInt();
311 assert signature == ProtocolConstants.PACKET_SIGNATURE;
312 // Second int - total query size (including fixed query header)
313 final int remains = handshake.getInt() - ProtocolConstants.QUERY_HEADER_SIZE;
314 // After that - header type (which must be SDKCTL_PACKET_TYPE_QUERY)
315 final int msg_type = handshake.getInt();
316 assert msg_type == ProtocolConstants.PACKET_TYPE_QUERY;
317 // After that - query ID.
318 final int query_id = handshake.getInt();
319 // And finally, query type (which must be ProtocolConstants.QUERY_HANDSHAKE for
320 // handshake query)
321 final int query_type = handshake.getInt();
322 assert query_type == ProtocolConstants.QUERY_HANDSHAKE;
323 // Verify that received is a query.
324 if (msg_type != ProtocolConstants.PACKET_TYPE_QUERY) {
325 // Message type is not a query. Lets read and discard the remainder
326 // of the message.
327 if (remains > 0) {
328 Loge("Unexpected handshake message type: " + msg_type);
329 byte[] discard = new byte[remains];
330 Socket.receive(sock, discard, discard.length);
332 return;
335 // Receive query data.
336 final byte[] name_array = new byte[remains];
337 Socket.receive(sock, name_array, name_array.length);
339 // Prepare response header.
340 handshake.position(0);
341 handshake.putInt(ProtocolConstants.PACKET_SIGNATURE);
342 // Handshake reply is just one int.
343 handshake.putInt(ProtocolConstants.QUERY_RESP_HEADER_SIZE + 4);
344 handshake.putInt(ProtocolConstants.PACKET_TYPE_QUERY_RESPONSE);
345 handshake.putInt(query_id);
347 // Verify that received query is in deed a handshake query.
348 if (query_type != ProtocolConstants.QUERY_HANDSHAKE) {
349 // Query is not a handshake. Reply with failure.
350 Loge("Unexpected handshake query type: " + query_type);
351 handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_QUERY_UNKNOWN);
352 sock.getOutputStream().write(handshake.array());
353 return;
356 // Handshake query data consist of SDK controller channel name.
357 final String channel_name = new String(name_array);
358 if (DEBUG) Log.d(TAG, "Handshake received for channel " + channel_name);
360 // Respond to query depending on service-side channel availability
361 final Channel channel = getChannel(channel_name);
362 Socket sk = null;
364 if (channel != null) {
365 if (channel.isConnected()) {
366 // This is a duplicate connection.
367 Loge("Duplicate connection to a connected Channel " + channel_name);
368 handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_DUP);
369 } else {
370 // Connecting to a registered channel.
371 if (DEBUG) Log.d(TAG, "Emulator is connected to a registered Channel " + channel_name);
372 handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_CONNECTED);
374 } else {
375 // Make sure that there are no other channel connections for this
376 // channel name.
377 if (getPendingSocket(channel_name) != null) {
378 // This is a duplicate.
379 Loge("Duplicate connection to a pending Socket " + channel_name);
380 handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_DUP);
381 } else {
382 // Connecting to a channel that has not been registered yet.
383 if (DEBUG) Log.d(TAG, "Emulator is connected to a pending Socket " + channel_name);
384 handshake.putInt(ProtocolConstants.HANDSHAKE_RESP_NOPORT);
385 sk = new Socket(sock, channel_name, endian);
386 mPendingSockets.add(sk);
390 // Send handshake reply.
391 sock.getOutputStream().write(handshake.array());
393 // If a disconnected channel for emulator connection has been found,
394 // connect it.
395 if (channel != null && !channel.isConnected()) {
396 if (DEBUG) Log.d(TAG, "Connecting Channel " + channel_name + " with emulator.");
397 sk = new Socket(sock, channel_name, endian);
398 channel.connect(sk);
401 mService.notifyStatusChanged();
404 /***************************************************************************
405 * Logging wrappers
406 **************************************************************************/
408 private void Loge(String log) {
409 mService.addError(log);
410 Log.e(TAG, log);