1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 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 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, 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. 15 */ 16 17 package com.android.tools.sdkcontroller.lib; 18 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; 24 25 import android.util.Log; 26 import android.net.LocalServerSocket; 27 import android.net.LocalSocket; 28 29 import com.android.tools.sdkcontroller.lib.Channel; 30 import com.android.tools.sdkcontroller.service.ControllerService; 31 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. 70 */ 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; 78 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. 90 */ 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. 98 */ 99 private List<Channel> mChannels = new ArrayList<Channel>(); 100 101 /** 102 * Constructs Connection instance. 103 */ 104 public Connection(ControllerService service) { 105 mService = service; 106 if (DEBUG) Log.d(TAG, "SdkControllerConnection is constructed."); 107 } 108 109 /** 110 * Binds to the socket, and starts the listening thread. 111 */ 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(); 119 } 120 }, "SdkControllerConnectionIoLoop").start(); 121 } 122 123 /** 124 * Stops the listener, and closes the socket. 125 * 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. 128 */ 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; 137 } 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) { 145 } 146 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(); 151 } 152 mPendingSockets.clear(); 153 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(); 159 } 160 } 161 if (DEBUG) Log.d(TAG, "SdkControllerConnection is disconnected."); 162 } 163 return socket != null; 164 } 165 166 /** 167 * Registers SDK controller channel. 168 * 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. 172 */ 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; 178 } 179 } 180 if (DEBUG) Log.d(TAG, "Registering Channel " + channel.getChannelName()); 181 mChannels.add(channel); 182 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); 191 } 192 } 193 return true; 194 } 195 196 /** 197 * Checks if at least one socket connection exists with channel. 198 * 199 * @return true if at least one socket connection exists with channel. 200 */ 201 public boolean isEmulatorConnected() { 202 for (Channel channel : mChannels) { 203 if (channel.isConnected()) { 204 return true; 205 } 206 } 207 return !mPendingSockets.isEmpty(); 208 } 209 210 /** 211 * Gets Channel instance for the given channel name. 212 * 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. 216 */ 217 public Channel getChannel(String name) { 218 for (Channel channel : mChannels) { 219 if (channel.getChannelName().equals(name)) { 220 return channel; 221 } 222 } 223 return null; 224 } 225 226 /** 227 * Gets connected emulator socket that is pending for service-side channel 228 * registration. 229 * 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. 234 */ 235 private Socket getPendingSocket(String name) { 236 for (Socket socket : mPendingSockets) { 237 if (socket.getChannelName().equals(name)) { 238 return socket; 239 } 240 } 241 return null; 242 } 243 244 /** 245 * Wakes I/O looper waiting on connection with the emulator. 246 * 247 * @param socket Server socket waiting on connection. 248 */ 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."); 256 } 257 } 258 259 /** 260 * Loops on the local socket, handling emulator connection attempts. 261 */ 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; 276 } 277 socket = mServerSocket; 278 } 279 } catch (IOException e) { 280 Loge("Exception " + e + "SdkControllerConnection I/O looper."); 281 } 282 if (DEBUG) Log.d(TAG, "Exiting SdkControllerConnection I/O looper."); 283 284 // If we're exiting the internal loop for reasons other than an explicit 285 // disconnect request, we should reconnect again. 286 } while (disconnect()); 287 } 288 289 /** 290 * Accepts new connection from the emulator. 291 * 292 * @param sock Connecting socket. 293 * @throws IOException 294 */ 295 private void onAccept(LocalSocket sock) throws IOException { 296 final ByteBuffer handshake = ByteBuffer.allocate(ProtocolConstants.QUERY_HEADER_SIZE); 297 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); 304 305 // Right after that follows the handshake query header. 306 handshake.position(0); 307 Socket.receive(sock, handshake.array(), handshake.array().length); 308 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); 331 } 332 return; 333 } 334 335 // Receive query data. 336 final byte[] name_array = new byte[remains]; 337 Socket.receive(sock, name_array, name_array.length); 338 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); 346 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; 354 } 355 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); 359 360 // Respond to query depending on service-side channel availability 361 final Channel channel = getChannel(channel_name); 362 Socket sk = null; 363 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); 373 } 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); 387 } 388 } 389 390 // Send handshake reply. 391 sock.getOutputStream().write(handshake.array()); 392 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); 399 } 400 401 mService.notifyStatusChanged(); 402 } 403 404 /*************************************************************************** 405 * Logging wrappers 406 **************************************************************************/ 407 408 private void Loge(String log) { 409 mService.addError(log); 410 Log.e(TAG, log); 411 } 412 } 413