Home | History | Annotate | Download | only in lib
      1 /*
      2  * Copyright (C) 2011 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.net.InetAddress;
     21 import java.net.InetSocketAddress;
     22 import java.net.Socket;
     23 import java.nio.ByteBuffer;
     24 import java.nio.channels.ClosedChannelException;
     25 import java.nio.channels.ClosedSelectorException;
     26 import java.nio.channels.SelectionKey;
     27 import java.nio.channels.Selector;
     28 import java.nio.channels.ServerSocketChannel;
     29 import java.nio.channels.SocketChannel;
     30 import java.nio.channels.spi.SelectorProvider;
     31 import java.util.Iterator;
     32 import java.util.Set;
     33 import java.util.Vector;
     34 
     35 import android.util.Log;
     36 
     37 /**
     38  * Encapsulates a connection with the emulator. The connection is established
     39  * over a TCP port forwarding enabled with 'adb forward' command.
     40  * <p/>
     41  * Communication with the emulator is performed via two socket channels
     42  * connected to the forwarded TCP port. One channel is a query channel that is
     43  * intended solely for receiving queries from the emulator. Another channel is
     44  * an event channel that is intended for sending notification messages (events)
     45  * to the emulator.
     46  * <p/>
     47  * EmulatorConnection is considered to be "connected" when both channels are connected.
     48  * EmulatorConnection is considered to be "disconnected" when connection with any of the
     49  * channels is lost.
     50  * <p/>
     51  * Instance of this class is operational only for a single connection with the
     52  * emulator. Once connection is established and then lost, a new instance of
     53  * this class must be created to establish new connection.
     54  * <p/>
     55  * Note that connection with the device over TCP port forwarding is extremely
     56  * fragile at the moment. For whatever reason the connection is even more
     57  * fragile if device uses asynchronous sockets (based on java.nio API). So, to
     58  * address this issue EmulatorConnection class implements two types of connections. One is
     59  * using synchronous sockets, and another is using asynchronous sockets. The
     60  * type of connection is selected when EmulatorConnection instance is created (see
     61  * comments to EmulatorConnection's constructor).
     62  * <p/>
     63  * According to the exchange protocol with the emulator, queries, responses to
     64  * the queries, and notification messages are all zero-terminated strings.
     65  */
     66 public class EmulatorConnection {
     67     /** Defines connection types supported by the EmulatorConnection class. */
     68     public enum EmulatorConnectionType {
     69         /** Use asynchronous connection (based on java.nio API). */
     70         ASYNC_CONNECTION,
     71         /** Use synchronous connection (based on synchronous Socket objects). */
     72         SYNC_CONNECTION,
     73     }
     74 
     75     /** TCP port reserved for the sensors emulation. */
     76     public static final int SENSORS_PORT = 1968;
     77     /** TCP port reserved for the multitouch emulation. */
     78     public static final int MULTITOUCH_PORT = 1969;
     79     /** Tag for logging messages. */
     80     private static final String TAG = "EmulatorConnection";
     81     /** EmulatorConnection events listener. */
     82     private EmulatorListener mListener;
     83     /** I/O selector (looper). */
     84     private Selector mSelector;
     85     /** Server socket channel. */
     86     private ServerSocketChannel mServerSocket;
     87     /** Query channel. */
     88     private EmulatorChannel mQueryChannel;
     89     /** Event channel. */
     90     private EmulatorChannel mEventChannel;
     91     /** Selector for the connection type. */
     92     private EmulatorConnectionType mConnectionType;
     93     /** Connection status */
     94     private boolean mIsConnected = false;
     95     /** Disconnection status */
     96     private boolean mIsDisconnected = false;
     97     /** Exit I/O loop flag. */
     98     private boolean mExitIoLoop = false;
     99     /** Disconnect flag. */
    100     private boolean mDisconnect = false;
    101 
    102     /***************************************************************************
    103      * EmulatorChannel - Base class for sync / async channels.
    104      **************************************************************************/
    105 
    106     /**
    107      * Encapsulates a base class for synchronous and asynchronous communication
    108      * channels.
    109      */
    110     private abstract class EmulatorChannel {
    111         /** Identifier for a query channel type. */
    112         private static final String QUERY_CHANNEL = "query";
    113         /** Identifier for an event channel type. */
    114         private static final String EVENT_CHANNEL = "event";
    115         /** BLOB query string. */
    116         private static final String BLOBL_QUERY = "$BLOB";
    117 
    118         /***********************************************************************
    119          * Abstract API
    120          **********************************************************************/
    121 
    122         /**
    123          * Sends a message via this channel.
    124          *
    125          * @param msg Zero-terminated message string to send.
    126          */
    127         public abstract void sendMessage(String msg) throws IOException;
    128 
    129         /**
    130          * Closes this channel.
    131          */
    132         abstract public void closeChannel() throws IOException;
    133 
    134         /***********************************************************************
    135          * Public API
    136          **********************************************************************/
    137 
    138         /**
    139          * Constructs EmulatorChannel instance.
    140          */
    141         public EmulatorChannel() {
    142         }
    143 
    144         /**
    145          * Handles a query received in this channel.
    146          *
    147          * @param socket A socket through which the query has been received.
    148          * @param query_str Query received from this channel. All queries are
    149          *            formatted as such: <query>:<query parameters> where -
    150          *            <query> Is a query name that identifies the query, and -
    151          *            <query parameters> represent parameters for the query.
    152          *            Query name and query parameters are separated with a ':'
    153          *            character.
    154          */
    155         public void onQueryReceived(Socket socket, String query_str) throws IOException {
    156             String query, query_param, response;
    157 
    158             // Lets see if query has parameters.
    159             int sep = query_str.indexOf(':');
    160             if (sep == -1) {
    161                 // Query has no parameters.
    162                 query = query_str;
    163                 query_param = "";
    164             } else {
    165                 // Separate query name from its parameters.
    166                 query = query_str.substring(0, sep);
    167                 // Make sure that substring after the ':' does contain
    168                 // something, otherwise the query is paramless.
    169                 query_param = (sep < (query_str.length() - 1)) ? query_str.substring(sep + 1) : "";
    170             }
    171 
    172             // Handle the query, obtain response string, and reply it back to
    173             // the emulator. Note that there is one special query: $BLOB, that
    174             // requires reading of a byte array of data first. The size of the
    175             // array is defined by the query parameter.
    176             if (query.compareTo(BLOBL_QUERY) == 0) {
    177                 // This is the BLOB query. It must have a parameter which
    178                 // contains byte size of the blob.
    179                 final int array_size = Integer.parseInt(query_param);
    180                 if (array_size > 0) {
    181                     // Read data from the query's socket.
    182                     byte[] array = new byte[array_size];
    183                     final int transferred = readSocketArray(socket, array);
    184                     if (transferred == array_size) {
    185                         // Handle blob query.
    186                         response = onBlobQuery(array);
    187                     } else {
    188                         response = "ko:Transfer failure\0";
    189                     }
    190                 } else {
    191                     response = "ko:Invalid parameter\0";
    192                 }
    193             } else {
    194                 response = onQuery(query, query_param);
    195                 if (response.length() == 0 || response.charAt(0) == '\0') {
    196                     Logw("No response to the query " + query_str);
    197                 }
    198             }
    199 
    200             if (response.length() != 0) {
    201                 if (response.charAt(response.length() - 1) != '\0') {
    202                     Logw("Response '" + response + "' to query '" + query
    203                             + "' does not contain zero-terminator.");
    204                 }
    205                 sendMessage(response);
    206             }
    207         }
    208     } // EmulatorChannel
    209 
    210     /***************************************************************************
    211      * EmulatorSyncChannel - Implements a synchronous channel.
    212      **************************************************************************/
    213 
    214     /**
    215      * Encapsulates a synchronous communication channel with the emulator.
    216      */
    217     private class EmulatorSyncChannel extends EmulatorChannel {
    218         /** Communication socket. */
    219         private Socket mSocket;
    220 
    221         /**
    222          * Constructs EmulatorSyncChannel instance.
    223          *
    224          * @param socket Connected ('accept'ed) communication socket.
    225          */
    226         public EmulatorSyncChannel(Socket socket) {
    227             mSocket = socket;
    228             // Start the reader thread.
    229             new Thread(new Runnable() {
    230                 @Override
    231                 public void run() {
    232                     theReader();
    233                 }
    234             }, "EmuSyncChannel").start();
    235         }
    236 
    237         /***********************************************************************
    238          * Abstract API implementation
    239          **********************************************************************/
    240 
    241         /**
    242          * Sends a message via this channel.
    243          *
    244          * @param msg Zero-terminated message string to send.
    245          */
    246         @Override
    247         public void sendMessage(String msg) throws IOException {
    248             if (msg.charAt(msg.length() - 1) != '\0') {
    249                 Logw("Missing zero-terminator in message '" + msg + "'");
    250             }
    251             mSocket.getOutputStream().write(msg.getBytes());
    252         }
    253 
    254         /**
    255          * Closes this channel.
    256          */
    257         @Override
    258         public void closeChannel() throws IOException {
    259             mSocket.close();
    260         }
    261 
    262         /***********************************************************************
    263          * EmulatorSyncChannel implementation
    264          **********************************************************************/
    265 
    266         /**
    267          * The reader thread: loops reading and dispatching queries.
    268          */
    269         private void theReader() {
    270             try {
    271                 for (;;) {
    272                     String query = readSocketString(mSocket);
    273                     onQueryReceived(mSocket, query);
    274                 }
    275             } catch (IOException e) {
    276                 onLostConnection();
    277             }
    278         }
    279     } // EmulatorSyncChannel
    280 
    281     /***************************************************************************
    282      * EmulatorAsyncChannel - Implements an asynchronous channel.
    283      **************************************************************************/
    284 
    285     /**
    286      * Encapsulates an asynchronous communication channel with the emulator.
    287      */
    288     private class EmulatorAsyncChannel extends EmulatorChannel {
    289         /** Communication socket channel. */
    290         private SocketChannel mChannel;
    291         /** I/O selection key for this channel. */
    292         private SelectionKey mSelectionKey;
    293         /** Accumulator for the query string received in this channel. */
    294         private String mQuery = "";
    295         /**
    296          * Preallocated character reader that is used when data is read from
    297          * this channel. See 'onRead' method for more details.
    298          */
    299         private ByteBuffer mIn = ByteBuffer.allocate(1);
    300         /**
    301          * Currently sent notification message(s). See 'sendMessage', and
    302          * 'onWrite' methods for more details.
    303          */
    304         private ByteBuffer mOut;
    305         /**
    306          * Array of pending notification messages. See 'sendMessage', and
    307          * 'onWrite' methods for more details.
    308          */
    309         private Vector<String> mNotifications = new Vector<String>();
    310 
    311         /**
    312          * Constructs EmulatorAsyncChannel instance.
    313          *
    314          * @param channel Accepted socket channel to use for communication.
    315          * @throws IOException
    316          */
    317         private EmulatorAsyncChannel(SocketChannel channel) throws IOException {
    318             // Mark character reader at the beginning, so we can reset it after
    319             // next read character has been pulled out from the buffer.
    320             mIn.mark();
    321 
    322             // Configure communication channel as non-blocking, and register
    323             // it with the I/O selector for reading.
    324             mChannel = channel;
    325             mChannel.configureBlocking(false);
    326             mSelectionKey = mChannel.register(mSelector, SelectionKey.OP_READ, this);
    327             // Start receiving read I/O.
    328             mSelectionKey.selector().wakeup();
    329         }
    330 
    331         /***********************************************************************
    332          * Abstract API implementation
    333          **********************************************************************/
    334 
    335         /**
    336          * Sends a message via this channel.
    337          *
    338          * @param msg Zero-terminated message string to send.
    339          */
    340         @Override
    341         public void sendMessage(String msg) throws IOException {
    342             if (msg.charAt(msg.length() - 1) != '\0') {
    343                 Logw("Missing zero-terminator in message '" + msg + "'");
    344             }
    345             synchronized (this) {
    346                 if (mOut != null) {
    347                     // Channel is busy with writing another message.
    348                     // Queue this one. It will be picked up later when current
    349                     // write operation is completed.
    350                     mNotifications.add(msg);
    351                     return;
    352                 }
    353 
    354                 // No other messages are in progress. Send this one outside of
    355                 // the lock.
    356                 mOut = ByteBuffer.wrap(msg.getBytes());
    357             }
    358             mChannel.write(mOut);
    359 
    360             // Lets see if we were able to send the entire message.
    361             if (mOut.hasRemaining()) {
    362                 // Write didn't complete. Schedule write I/O callback to
    363                 // pick up from where this write has left.
    364                 enableWrite();
    365                 return;
    366             }
    367 
    368             // Entire message has been sent. Lets see if other messages were
    369             // queued while we were busy sending this one.
    370             for (;;) {
    371                 synchronized (this) {
    372                     // Dequeue message that was yielding to this write.
    373                     if (!dequeueMessage()) {
    374                         // Writing is over...
    375                         disableWrite();
    376                         mOut = null;
    377                         return;
    378                     }
    379                 }
    380 
    381                 // Send queued message.
    382                 mChannel.write(mOut);
    383 
    384                 // Lets see if we were able to send the entire message.
    385                 if (mOut.hasRemaining()) {
    386                     // Write didn't complete. Schedule write I/O callback to
    387                     // pick up from where this write has left.
    388                     enableWrite();
    389                     return;
    390                 }
    391             }
    392         }
    393 
    394         /**
    395          * Closes this channel.
    396          */
    397         @Override
    398         public void closeChannel() throws IOException {
    399             mSelectionKey.cancel();
    400             synchronized (this) {
    401                 mNotifications.clear();
    402             }
    403             mChannel.close();
    404         }
    405 
    406         /***********************************************************************
    407          * EmulatorAsyncChannel implementation
    408          **********************************************************************/
    409 
    410         /**
    411          * Reads data from the channel. This method is invoked from the I/O loop
    412          * when data is available for reading on this channel. When reading from
    413          * a channel we read character-by-character, building the query string
    414          * until zero-terminator is read. When zero-terminator is read, we
    415          * handle the query, and start building the new query string.
    416          *
    417          * @throws IOException
    418          */
    419         private void onRead() throws IOException, ClosedChannelException {
    420             int count = mChannel.read(mIn);
    421             Logv("onRead: " + count);
    422             while (count == 1) {
    423                 final char c = (char) mIn.array()[0];
    424                 mIn.reset();
    425                 if (c == '\0') {
    426                     // Zero-terminator is read. Process the query, and reset
    427                     // the query string.
    428                     onQueryReceived(mChannel.socket(), mQuery);
    429                     mQuery = "";
    430                 } else {
    431                     // Continue building the query string.
    432                     mQuery += c;
    433                 }
    434                 count = mChannel.read(mIn);
    435             }
    436 
    437             if (count == -1) {
    438                 // Channel got disconnected.
    439                 throw new ClosedChannelException();
    440             } else {
    441                 // "Don't block" in effect. Will get back to reading as soon as
    442                 // read I/O is available.
    443                 assert (count == 0);
    444             }
    445         }
    446 
    447         /**
    448          * Writes data to the channel. This method is ivnoked from the I/O loop
    449          * when data is available for writing on this channel.
    450          *
    451          * @throws IOException
    452          */
    453         private void onWrite() throws IOException {
    454             if (mOut != null && mOut.hasRemaining()) {
    455                 // Continue writing to the channel.
    456                 mChannel.write(mOut);
    457                 if (mOut.hasRemaining()) {
    458                     // Write is still incomplete. Come back to it when write I/O
    459                     // becomes available.
    460                     return;
    461                 }
    462             }
    463 
    464             // We're done with the current message. Lets see if we've
    465             // accumulated some more while this write was in progress.
    466             synchronized (this) {
    467                 // Dequeue next message into mOut.
    468                 if (!dequeueMessage()) {
    469                     // Nothing left to write.
    470                     disableWrite();
    471                     mOut = null;
    472                     return;
    473                 }
    474                 // We don't really want to run a big loop here, flushing the
    475                 // message queue. The reason is that we're inside the I/O loop,
    476                 // so we don't want to block others for long. So, we will
    477                 // continue with queue flushing next time we're picked up by
    478                 // write I/O event.
    479             }
    480         }
    481 
    482         /**
    483          * Dequeues messages that were yielding to the write in progress.
    484          * Messages will be dequeued directly to the mOut, so it's ready to be
    485          * sent when this method returns. NOTE: This method must be called from
    486          * within synchronized(this).
    487          *
    488          * @return true if messages were dequeued, or false if message queue was
    489          *         empty.
    490          */
    491         private boolean dequeueMessage() {
    492             // It's tempting to dequeue all messages here, but in practice it's
    493             // less performant than dequeuing just one.
    494             if (!mNotifications.isEmpty()) {
    495                 mOut = ByteBuffer.wrap(mNotifications.remove(0).getBytes());
    496                 return true;
    497             } else {
    498                 return false;
    499             }
    500         }
    501 
    502         /**
    503          * Enables write I/O callbacks.
    504          */
    505         private void enableWrite() {
    506             mSelectionKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
    507             // Looks like we must wake up the selector. Otherwise it's not going
    508             // to immediately pick up on the change that we just made.
    509             mSelectionKey.selector().wakeup();
    510         }
    511 
    512         /**
    513          * Disables write I/O callbacks.
    514          */
    515         private void disableWrite() {
    516             mSelectionKey.interestOps(SelectionKey.OP_READ);
    517         }
    518     } // EmulatorChannel
    519 
    520     /***************************************************************************
    521      * EmulatorConnection public API
    522      **************************************************************************/
    523 
    524     /**
    525      * Constructs EmulatorConnection instance.
    526      * Caller must call {@link #connect(int, EmulatorConnectionType)} afterwards.
    527      *
    528      * @param listener EmulatorConnection event listener. Must not be null.
    529      */
    530     public EmulatorConnection(EmulatorListener listener) {
    531         mListener = listener;
    532     }
    533 
    534     /**
    535      * Connects the EmulatorConnection instance.
    536      * <p/>
    537      * Important: Apps targeting Honeycomb+ SDK are not allowed to do networking on their main
    538      * thread. The caller is responsible to make sure this is NOT called from a main UI thread.
    539      *
    540      * @param port TCP port where emulator connects.
    541      * @param ctype Defines connection type to use (sync / async). See comments
    542      *            to EmulatorConnection class for more info.
    543      * @return This object for chaining calls.
    544      */
    545     public EmulatorConnection connect(int port, EmulatorConnectionType ctype) {
    546         constructEmulator(port, ctype);
    547         return this;
    548     }
    549 
    550 
    551     /**
    552      * Disconnects the emulator.
    553      */
    554     public void disconnect() {
    555         mDisconnect = true;
    556         mSelector.wakeup();
    557     }
    558 
    559     /**
    560      * Constructs EmulatorConnection instance.
    561      * <p/>
    562      * Important: Apps targeting Honeycomb+ SDK are not allowed to do networking on their main
    563      * thread. The caller is responsible to make sure this is NOT called from a main UI thread.
    564      * <p/>
    565      * On error or success, this calls
    566      * {@link EmulatorListener#onEmulatorBindResult(boolean, Exception)} to indicate whether
    567      * the socket was properly bound.
    568      * The IO loop will start after the method reported a successful bind.
    569      *
    570      * @param port TCP port where emulator connects.
    571      * @param ctype Defines connection type to use (sync / async). See comments
    572      *            to EmulatorConnection class for more info.
    573      */
    574     private void constructEmulator(final int port, EmulatorConnectionType ctype) {
    575 
    576         try {
    577             mConnectionType = ctype;
    578             // Create I/O looper.
    579             mSelector = SelectorProvider.provider().openSelector();
    580 
    581             // Create non-blocking server socket that would listen for connections,
    582             // and bind it to the given port on the local host.
    583             mServerSocket = ServerSocketChannel.open();
    584             mServerSocket.configureBlocking(false);
    585             InetAddress local = InetAddress.getLocalHost();
    586             final InetSocketAddress address = new InetSocketAddress(local, port);
    587             mServerSocket.socket().bind(address);
    588 
    589             // Register 'accept' I/O on the server socket.
    590             mServerSocket.register(mSelector, SelectionKey.OP_ACCEPT);
    591         } catch (IOException e) {
    592             mListener.onEmulatorBindResult(false, e);
    593             return;
    594         }
    595 
    596         mListener.onEmulatorBindResult(true, null);
    597         Logv("EmulatorConnection listener is created for port " + port);
    598 
    599         // Start I/O looper and dispatcher.
    600         new Thread(new Runnable() {
    601             @Override
    602             public void run() {
    603                 runIOLooper();
    604             }
    605         }, "EmuCnxIoLoop").start();
    606     }
    607 
    608     /**
    609      * Sends a notification message to the emulator via 'event' channel.
    610      * <p/>
    611      * Important: Apps targeting Honeycomb+ SDK are not allowed to do networking on their main
    612      * thread. The caller is responsible to make sure this is NOT called from a main UI thread.
    613      *
    614      * @param msg
    615      */
    616     public void sendNotification(String msg) {
    617         if (mIsConnected) {
    618             try {
    619                 mEventChannel.sendMessage(msg);
    620             } catch (IOException e) {
    621                 onLostConnection();
    622             }
    623         } else {
    624             Logw("Attempt to send '" + msg + "' to a disconnected EmulatorConnection");
    625         }
    626     }
    627 
    628     /**
    629      * Sets or removes a listener to the events generated by this emulator
    630      * instance.
    631      *
    632      * @param listener Listener to set. Passing null with this parameter will
    633      *            remove the current listener (if there was one).
    634      */
    635     public void setEmulatorListener(EmulatorListener listener) {
    636         synchronized (this) {
    637             mListener = listener;
    638         }
    639         // Make sure that new listener knows the connection status.
    640         if (mListener != null) {
    641             if (mIsConnected) {
    642                 mListener.onEmulatorConnected();
    643             } else if (mIsDisconnected) {
    644                 mListener.onEmulatorDisconnected();
    645             }
    646         }
    647     }
    648 
    649     /***************************************************************************
    650      * EmulatorConnection events
    651      **************************************************************************/
    652 
    653     /**
    654      * Called when emulator is connected. NOTE: This method is called from the
    655      * I/O loop, so all communication with the emulator will be "on hold" until
    656      * this method returns.
    657      */
    658     private void onConnected() {
    659         EmulatorListener listener;
    660         synchronized (this) {
    661             listener = mListener;
    662         }
    663         if (listener != null) {
    664             listener.onEmulatorConnected();
    665         }
    666     }
    667 
    668     /**
    669      * Called when emulator is disconnected. NOTE: This method could be called
    670      * from the I/O loop, in which case all communication with the emulator will
    671      * be "on hold" until this method returns.
    672      */
    673     private void onDisconnected() {
    674         EmulatorListener listener;
    675         synchronized (this) {
    676             listener = mListener;
    677         }
    678         if (listener != null) {
    679             listener.onEmulatorDisconnected();
    680         }
    681     }
    682 
    683     /**
    684      * Called when a query is received from the emulator. NOTE: This method
    685      * could be called from the I/O loop, in which case all communication with
    686      * the emulator will be "on hold" until this method returns.
    687      *
    688      * @param query Name of the query received from the emulator.
    689      * @param param Query parameters.
    690      * @return Zero-terminated reply string. String must be formatted as such:
    691      *         "ok|ko[:reply data]"
    692      */
    693     private String onQuery(String query, String param) {
    694         EmulatorListener listener;
    695         synchronized (this) {
    696             listener = mListener;
    697         }
    698         if (listener != null) {
    699             return listener.onEmulatorQuery(query, param);
    700         } else {
    701             return "ko:Service is detached.\0";
    702         }
    703     }
    704 
    705     /**
    706      * Called when a BLOB query is received from the emulator. NOTE: This method
    707      * could be called from the I/O loop, in which case all communication with
    708      * the emulator will be "on hold" until this method returns.
    709      *
    710      * @param array Array containing blob data.
    711      * @return Zero-terminated reply string. String must be formatted as such:
    712      *         "ok|ko[:reply data]"
    713      */
    714     private String onBlobQuery(byte[] array) {
    715         EmulatorListener listener;
    716         synchronized (this) {
    717             listener = mListener;
    718         }
    719         if (listener != null) {
    720             return listener.onEmulatorBlobQuery(array);
    721         } else {
    722             return "ko:Service is detached.\0";
    723         }
    724     }
    725 
    726     /***************************************************************************
    727      * EmulatorConnection implementation
    728      **************************************************************************/
    729 
    730     /**
    731      * Loops on the selector, handling and dispatching I/O events.
    732      */
    733     private void runIOLooper() {
    734         try {
    735             Logv("Waiting on EmulatorConnection to connect...");
    736             // Check mExitIoLoop before calling 'select', and after in order to
    737             // detect condition when mSelector has been waken up to exit the
    738             // I/O loop.
    739             while (!mExitIoLoop && !mDisconnect &&
    740                     mSelector.select() >= 0 &&
    741                     !mExitIoLoop && !mDisconnect) {
    742                 Set<SelectionKey> readyKeys = mSelector.selectedKeys();
    743                 Iterator<SelectionKey> i = readyKeys.iterator();
    744                 while (i.hasNext()) {
    745                     SelectionKey sk = i.next();
    746                     i.remove();
    747                     if (sk.isAcceptable()) {
    748                         final int ready = sk.readyOps();
    749                         if ((ready & SelectionKey.OP_ACCEPT) != 0) {
    750                             // Accept new connection.
    751                             onAccept(((ServerSocketChannel) sk.channel()).accept());
    752                         }
    753                     } else {
    754                         // Read / write events are expected only on a 'query',
    755                         // or 'event' asynchronous channels.
    756                         EmulatorAsyncChannel esc = (EmulatorAsyncChannel) sk.attachment();
    757                         if (esc != null) {
    758                             final int ready = sk.readyOps();
    759                             if ((ready & SelectionKey.OP_READ) != 0) {
    760                                 // Read data.
    761                                 esc.onRead();
    762                             }
    763                             if ((ready & SelectionKey.OP_WRITE) != 0) {
    764                                 // Write data.
    765                                 esc.onWrite();
    766                             }
    767                         } else {
    768                             Loge("No emulator channel found in selection key.");
    769                         }
    770                     }
    771                 }
    772             }
    773         } catch (ClosedSelectorException e) {
    774         } catch (IOException e) {
    775         }
    776 
    777         // Destroy connection on any I/O failure.
    778         if (!mExitIoLoop) {
    779             onLostConnection();
    780         }
    781     }
    782 
    783     /**
    784      * Accepts new connection from the emulator.
    785      *
    786      * @param channel Connecting socket channel.
    787      * @throws IOException
    788      */
    789     private void onAccept(SocketChannel channel) throws IOException {
    790         // Make sure we're not connected yet.
    791         if (mEventChannel != null && mQueryChannel != null) {
    792             // We don't accept any more connections after both channels were
    793             // connected.
    794             Loge("EmulatorConnection is connecting to the already connected instance.");
    795             channel.close();
    796             return;
    797         }
    798 
    799         // According to the protocol, each channel identifies itself as a query
    800         // or event channel, sending a "cmd", or "event" message right after
    801         // the connection.
    802         Socket socket = channel.socket();
    803         String socket_type = readSocketString(socket);
    804         if (socket_type.contentEquals(EmulatorChannel.QUERY_CHANNEL)) {
    805             if (mQueryChannel == null) {
    806                 // TODO: Find better way to do that!
    807                 socket.getOutputStream().write("ok\0".getBytes());
    808                 if (mConnectionType == EmulatorConnectionType.ASYNC_CONNECTION) {
    809                     mQueryChannel = new EmulatorAsyncChannel(channel);
    810                     Logv("Asynchronous query channel is registered.");
    811                 } else {
    812                     mQueryChannel = new EmulatorSyncChannel(channel.socket());
    813                     Logv("Synchronous query channel is registered.");
    814                 }
    815             } else {
    816                 // TODO: Find better way to do that!
    817                 Loge("Duplicate query channel.");
    818                 socket.getOutputStream().write("ko:Duplicate\0".getBytes());
    819                 channel.close();
    820                 return;
    821             }
    822         } else if (socket_type.contentEquals(EmulatorChannel.EVENT_CHANNEL)) {
    823             if (mEventChannel == null) {
    824                 // TODO: Find better way to do that!
    825                 socket.getOutputStream().write("ok\0".getBytes());
    826                 if (mConnectionType == EmulatorConnectionType.ASYNC_CONNECTION) {
    827                     mEventChannel = new EmulatorAsyncChannel(channel);
    828                     Logv("Asynchronous event channel is registered.");
    829                 } else {
    830                     mEventChannel = new EmulatorSyncChannel(channel.socket());
    831                     Logv("Synchronous event channel is registered.");
    832                 }
    833             } else {
    834                 Loge("Duplicate event channel.");
    835                 socket.getOutputStream().write("ko:Duplicate\0".getBytes());
    836                 channel.close();
    837                 return;
    838             }
    839         } else {
    840             Loge("Unknown channel is connecting: " + socket_type);
    841             socket.getOutputStream().write("ko:Unknown channel type\0".getBytes());
    842             channel.close();
    843             return;
    844         }
    845 
    846         // Lets see if connection is complete...
    847         if (mEventChannel != null && mQueryChannel != null) {
    848             // When both, query and event channels are connected, the emulator
    849             // is considered to be connected.
    850             Logv("... EmulatorConnection is connected.");
    851             mIsConnected = true;
    852             onConnected();
    853         }
    854     }
    855 
    856     /**
    857      * Called when connection to any of the channels has been lost.
    858      */
    859     private void onLostConnection() {
    860         // Since we're multithreaded, there can be multiple "bangs" from those
    861         // threads. We should only handle the first one.
    862         boolean first_time = false;
    863         synchronized (this) {
    864             first_time = mIsConnected;
    865             mIsConnected = false;
    866             mIsDisconnected = true;
    867         }
    868         if (first_time) {
    869             Logw("Connection with the emulator is lost!");
    870             // Close all channels, exit the I/O loop, and close the selector.
    871             try {
    872                 if (mEventChannel != null) {
    873                     mEventChannel.closeChannel();
    874                 }
    875                 if (mQueryChannel != null) {
    876                     mQueryChannel.closeChannel();
    877                 }
    878                 if (mServerSocket != null) {
    879                     mServerSocket.close();
    880                 }
    881                 if (mSelector != null) {
    882                     mExitIoLoop = true;
    883                     mSelector.wakeup();
    884                     mSelector.close();
    885                 }
    886             } catch (IOException e) {
    887                 Loge("onLostConnection exception: " + e.getMessage());
    888             }
    889 
    890             // Notify the app about lost connection.
    891             onDisconnected();
    892         }
    893     }
    894 
    895     /**
    896      * Reads zero-terminated string from a synchronous socket.
    897      *
    898      * @param socket Socket to read string from. Must be a synchronous socket.
    899      * @return String read from the socket.
    900      * @throws IOException
    901      */
    902     private static String readSocketString(Socket socket) throws IOException {
    903         String str = "";
    904 
    905         // Current characted received from the input stream.
    906         int current_byte = 0;
    907 
    908         // With port forwarding there is no reliable way how to detect
    909         // socket disconnection, other than checking on the input stream
    910         // to die ("end of stream" condition). That condition is reported
    911         // when input stream's read() method returns -1.
    912         while (socket.isConnected() && current_byte != -1) {
    913             // Character by character read the input stream, and accumulate
    914             // read characters in the command string. The end of the command
    915             // is indicated with zero character.
    916             current_byte = socket.getInputStream().read();
    917             if (current_byte != -1) {
    918                 if (current_byte == 0) {
    919                     // String is completed.
    920                     return str;
    921                 } else {
    922                     // Append read character to the string.
    923                     str += (char) current_byte;
    924                 }
    925             }
    926         }
    927 
    928         // Got disconnected!
    929         throw new ClosedChannelException();
    930     }
    931 
    932     /**
    933      * Reads a block of data from a socket.
    934      *
    935      * @param socket Socket to read data from. Must be a synchronous socket.
    936      * @param array Array where to read data.
    937      * @return Number of bytes read from the socket, or -1 on an error.
    938      * @throws IOException
    939      */
    940     private static int readSocketArray(Socket socket, byte[] array) throws IOException {
    941         int in = 0;
    942         while (in < array.length) {
    943             final int ret = socket.getInputStream().read(array, in, array.length - in);
    944             if (ret == -1) {
    945                 // Got disconnected!
    946                 throw new ClosedChannelException();
    947             }
    948             in += ret;
    949         }
    950         return in;
    951     }
    952 
    953     /***************************************************************************
    954      * Logging wrappers
    955      **************************************************************************/
    956 
    957     private void Loge(String log) {
    958         Log.e(TAG, log);
    959     }
    960 
    961     private void Logw(String log) {
    962         Log.w(TAG, log);
    963     }
    964 
    965     private void Logv(String log) {
    966         Log.v(TAG, log);
    967     }
    968 }
    969