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
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
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
;
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:
40 * 'adb forward tcp:<TCP port number> localabstract:android.sdk.controller'
42 * The way communication between the emulator and SDK controller service works
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"
52 * 2. Channels are connected to emulator via separate socket instance (though all
53 * of the connections share the same socket address).
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.
59 * Given that, the main responsibilities of this class are:
61 * 1. Bind to "android.sdk.controller" socket, listening to emulator connections.
63 * 2. Maintain a list of service-side channels registered by the application.
65 * 3. Bind emulator connection with service-side channel via port name, provided by
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
;
84 * List of connected emulator sockets, pending for a channel to be registered.
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
>();
93 * List of registered app-side channels.
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
99 private List
<Channel
> mChannels
= new ArrayList
<Channel
>();
102 * Constructs Connection instance.
104 public Connection(ControllerService 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() {
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
);
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());
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
);
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()) {
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
)) {
227 * Gets connected emulator socket that is pending for service-side channel
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
)) {
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();
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.");
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) {
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
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
328 Loge("Unexpected handshake message type: " + msg_type
);
329 byte[] discard
= new byte[remains
];
330 Socket
.receive(sock
, discard
, discard
.length
);
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());
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
);
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
);
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
);
375 // Make sure that there are no other channel connections for this
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
);
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,
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
);
401 mService
.notifyStatusChanged();
404 /***************************************************************************
406 **************************************************************************/
408 private void Loge(String log
) {
409 mService
.addError(log
);