Home | History | Annotate | Download | only in lib
      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