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 android.os.Message;
     20 import android.util.Log;
     21 
     22 import com.android.tools.sdkcontroller.service.ControllerService;
     23 
     24 import java.io.IOException;
     25 import java.nio.ByteBuffer;
     26 import java.nio.ByteOrder;
     27 import java.util.ArrayList;
     28 import java.util.List;
     29 import java.util.concurrent.BlockingQueue;
     30 import java.util.concurrent.LinkedBlockingQueue;
     31 import java.util.concurrent.atomic.AtomicInteger;
     32 
     33 /**
     34  * Encapsulates basics of a connection with the emulator.
     35  * This class must be used as a base class for all the channelss that provide
     36  * particular type of emulation (such as sensors, multi-touch, etc.)
     37  * <p/>
     38  * Essentially, Channel is an implementation of a particular emulated functionality,
     39  * that defines logical format of the data transferred between the emulator and
     40  * SDK controller. For instance, "sensors" is a channel that emulates sensors,
     41  * and transfers sensor value changes from the device to the emulator. "Multi-touch"
     42  * is a channel that supports multi-touch emulation, and transfers multi-touch
     43  * events to the emulator, while receiving frame buffer updates from the emulator.
     44  * <p/>
     45  * Besides connection with the emulator, each channel may contain one or more UI
     46  * components associated with it. This class provides some basics for UI support,
     47  * including:
     48  * <p/>
     49  * - Providing a way to register / unregister a UI component with the channel.
     50  * <p/>
     51  * - Implementing posting of messages to emulator in opposite to direct message
     52  * sent. This is due to requirement that UI threads are prohibited from doing
     53  * network I/O.
     54  */
     55 public abstract class Channel {
     56 
     57     /**
     58      * Encapsulates a message posted to be sent to the emulator from a worker
     59      * thread. This class is used to describe a message that is posted in UI
     60      * thread, and then picked up in the worker thread.
     61      */
     62     private class SdkControllerMessage {
     63         /** Message type. */
     64         private int mMessageType;
     65         /** Message data (can be null). */
     66         private byte[] mMessage;
     67         /** Message data size */
     68         private int mMessageSize;
     69 
     70         /**
     71          * Construct message from an array.
     72          *
     73          * @param type Message type.
     74          * @param message Message data. Message data size is defined by size of
     75          *            the array.
     76          */
     77         public SdkControllerMessage(int type, byte[] message) {
     78             mMessageType = type;
     79             mMessage = message;
     80             mMessageSize = (message != null) ? message.length : 0;
     81         }
     82 
     83         /**
     84          * Construct message from a ByteBuffer.
     85          *
     86          * @param type Message type.
     87          * @param message Message data. Message data size is defined by
     88          *            position() property of the ByteBuffer.
     89          */
     90         public SdkControllerMessage(int type, ByteBuffer message) {
     91             mMessageType = type;
     92             if (message != null) {
     93                 mMessage = message.array();
     94                 mMessageSize = message.position();
     95             } else {
     96                 mMessage = null;
     97                 mMessageSize = 0;
     98             }
     99         }
    100 
    101         /**
    102          * Gets message type.
    103 
    104          *
    105          * @return Message type.
    106          */
    107         public int getMessageType() {
    108             return mMessageType;
    109         }
    110 
    111         /**
    112          * Gets message buffer.
    113          *
    114          * @return Message buffer.
    115          */
    116         public byte[] getMessage() {
    117             return mMessage;
    118         }
    119 
    120         /**
    121          * Gets message buffer size.
    122          *
    123          * @return Message buffer size.
    124          */
    125         public int getMessageSize() {
    126             return mMessageSize;
    127         }
    128     } // SdkControllerMessage
    129 
    130     /*
    131      * Names for currently implemented SDK controller channels.
    132      */
    133 
    134     /** Name for a channel that handles sensors emulation */
    135     public static final String SENSOR_CHANNEL = "sensors";
    136     /** Name for a channel that handles multi-touch emulation */
    137     public static final String MULTITOUCH_CHANNEL = "multi-touch";
    138 
    139     /*
    140      * Types of messages internally used by Channel class.
    141      */
    142 
    143     /** Service-side emulator is connected. */
    144     private static final int MSG_CONNECTED = -1;
    145     /** Service-side emulator is disconnected. */
    146     private static final int MSG_DISCONNECTED = -2;
    147     /** Service-side emulator is enabled. */
    148     private static final int MSG_ENABLED = -3;
    149     /** Service-side emulator is disabled. */
    150     private static final int MSG_DISABLED = -4;
    151 
    152     /** Tag for logging messages. */
    153     private static final String TAG = "SdkControllerChannel";
    154     /** Controls debug log. */
    155     private static final boolean DEBUG = false;
    156 
    157     /** Service that has created this object. */
    158     protected ControllerService mService;
    159 
    160     /*
    161      * Socket stuff.
    162      */
    163 
    164     /** Socket to use to to communicate with the emulator. */
    165     private Socket mSocket = null;
    166     /** Channel name ("sensors", "multi-touch", etc.) */
    167     private String mChannelName;
    168     /** Endianness of data transferred in this channel. */
    169     private ByteOrder mEndian;
    170 
    171     /*
    172      * Message posting support.
    173      */
    174 
    175     /** Total number of messages posted in this channel */
    176     private final AtomicInteger mMsgCount = new AtomicInteger(0);
    177     /** Flags whether or not message thread is running. */
    178     private volatile boolean mRunMsgQueue = true;
    179     /** Queue of messages pending transmission. */
    180     private final BlockingQueue<SdkControllerMessage>
    181             mMsgQueue = new LinkedBlockingQueue<SdkControllerMessage>();
    182     /** Message thread */
    183     private final Thread mMsgThread;
    184 
    185     /*
    186      * UI support.
    187      */
    188 
    189     /** Lists UI handlers attached to this channel. */
    190     private final List<android.os.Handler> mUiHandlers = new ArrayList<android.os.Handler>();
    191 
    192     /*
    193      * Abstract methods.
    194      */
    195 
    196     /**
    197      * This method is invoked when this channel is fully connected with its
    198      * counterpart in the emulator.
    199      */
    200     public abstract void onEmulatorConnected();
    201 
    202     /**
    203      * This method is invoked when this channel loses connection with its
    204      * counterpart in the emulator.
    205      */
    206     public abstract void onEmulatorDisconnected();
    207 
    208     /**
    209      * A message has been received from the emulator.
    210      *
    211      * @param msg_type Message type.
    212      * @param msg_data Message data. Message data size is defined by the length
    213      *            of the array wrapped by the ByteBuffer.
    214      */
    215     public abstract void onEmulatorMessage(int msg_type, ByteBuffer msg_data);
    216 
    217     /**
    218      * A query has been received from the emulator.
    219      *
    220      * @param query_id Identifies the query. This ID must be used when replying
    221      *            to the query.
    222      * @param query_type Query type.
    223      * @param query_data Query data. Query data size is defined by the length of
    224      *            the array wrapped by the ByteBuffer.
    225      */
    226     public abstract void onEmulatorQuery(int query_id, int query_type, ByteBuffer query_data);
    227 
    228     /*
    229      * Channel implementation.
    230      */
    231 
    232     /**
    233      * Constructs Channel instance.
    234      *
    235      * @param name Channel name.
    236      */
    237     public Channel(ControllerService service, String name) {
    238         mService = service;
    239         mChannelName = name;
    240         // Start the worker thread for posted messages.
    241         mMsgThread = new Thread(new Runnable() {
    242                 @Override
    243             public void run() {
    244                 if (DEBUG) Log.d(TAG, "MsgThread.started-" + mChannelName);
    245                 while (mRunMsgQueue) {
    246                     try {
    247                         SdkControllerMessage msg = mMsgQueue.take();
    248                         if (msg != null) {
    249                             sendMessage(
    250                                     msg.getMessageType(), msg.getMessage(), msg.getMessageSize());
    251                             mMsgCount.incrementAndGet();
    252                         }
    253                     } catch (InterruptedException e) {
    254                         Log.e(TAG, "MsgThread-" + mChannelName, e);
    255                     }
    256                 }
    257                 if (DEBUG) Log.d(TAG, "MsgThread.terminate-" + mChannelName);
    258             }
    259         }, "MsgThread-" + name);
    260         mMsgThread.start();
    261         if (DEBUG) Log.d(TAG, "Channel is constructed for " + mChannelName);
    262     }
    263 
    264     /**
    265      * Gets name for this channel.
    266      *
    267      * @return Emulator name.
    268      */
    269     public String getChannelName() {
    270         return mChannelName;
    271     }
    272 
    273     /**
    274      * Gets endianness for this channel.
    275      *
    276      * @return Channel endianness.
    277      */
    278     public ByteOrder getEndian() {
    279         return mEndian;
    280     }
    281 
    282     /**
    283      * Gets number of messages sent via postMessage method.
    284      *
    285      * @return Number of messages sent via postMessage method.
    286      */
    287     public int getMsgSentCount() {
    288         return mMsgCount.get();
    289     }
    290 
    291     /**
    292      * Checks if this channel is connected with the emulator.
    293      *
    294      * @return true if this channel is connected with the emulator, or false if it is
    295      *         not connected.
    296      */
    297     public boolean isConnected() {
    298         // Use local copy of the socket, ensuring it's not going to NULL while
    299         // we're working with it. If it gets closed, while we're in the middle
    300         // of data transfer - it's OK, since it will produce an exception, and
    301         // the caller will gracefully handle it.
    302         //
    303         // Same technique is used everywhere in this class where mSocket member
    304         // is touched.
    305         Socket socket = mSocket;
    306         return socket != null && socket.isConnected();
    307     }
    308 
    309     /**
    310      * Establishes connection with the emulator. This method is called by Connection
    311      * object when emulator successfully connects to this channel, or this channel
    312      * gets registered, and there is a pending socket connection for it.
    313      *
    314      * @param socket Channel connection socket.
    315      */
    316     public void connect(Socket socket) {
    317         mSocket = socket;
    318         mEndian = socket.getEndian();
    319         Logv("Channel " + mChannelName + " is now connected with the emulator.");
    320         // Notify the emulator that connection is established.
    321         sendMessage(MSG_CONNECTED, (byte[]) null);
    322 
    323         // Let the derived class know that emulator is connected, and start the
    324         // I/O loop in which we will receive data from the emulator. Note that
    325         // we start the loop after onEmulatorConnected call, since we don't want
    326         // to start dispatching messages before the derived class could set
    327         // itself up for receiving them.
    328         onEmulatorConnected();
    329         new Thread(new Runnable() {
    330                 @Override
    331             public void run() {
    332                 runIOLooper();
    333             }
    334         }, "ChannelIoLoop").start();
    335         mService.notifyStatusChanged();
    336     }
    337 
    338     /**
    339      * Disconnects this channel from the emulator.
    340      *
    341      * @return true if this channel has been disconnected in this call, or false if
    342      *         channel has been already disconnected when this method has been called.
    343      */
    344     public boolean disconnect() {
    345         // This is the only place in this class where we will null the
    346         // socket object. Since this method can be called concurrently from
    347         // different threads, lets do this under the lock.
    348         Socket socket;
    349         synchronized (this) {
    350             socket = mSocket;
    351             mSocket = null;
    352         }
    353         if (socket != null) {
    354             // Notify the emulator about channel disconnection before we close
    355             // the communication socket.
    356             try {
    357                 sendMessage(socket, MSG_DISCONNECTED, null, 0);
    358             } catch (IOException e) {
    359                 // Ignore I/O exception at this point. We don't care about
    360                 // it, since the socket is being closed anyways.
    361             }
    362             // This will eventually stop I/O looper thread.
    363             socket.close();
    364             mService.notifyStatusChanged();
    365         }
    366         return socket != null;
    367     }
    368 
    369     /**
    370      * Enables the emulation. Typically, this method is called for channels that are
    371      * dependent on UI to handle the emulation. For instance, multi-touch emulation is
    372      * disabled until at least one UI component is attached to the channel. So, for
    373      * multi-touch emulation this method is called when UI gets attached to the channel.
    374      */
    375     public void enable() {
    376         postMessage(MSG_ENABLED, (byte[]) null);
    377         mService.notifyStatusChanged();
    378     }
    379 
    380     /**
    381      * Disables the emulation. Just the opposite to enable(). For multi-touch this
    382      * method is called when UI detaches from the channel.
    383      */
    384     public void disable() {
    385         postMessage(MSG_DISABLED, (byte[]) null);
    386         mService.notifyStatusChanged();
    387     }
    388 
    389     /**
    390      * Sends message to the emulator.
    391      *
    392      * @param socket Socket to send the message to.
    393      * @param msg_type Message type.
    394      * @param msg Message data to send.
    395      * @param len Byte size of message data.
    396      * @throws IOException
    397      */
    398     private void sendMessage(Socket socket, int msg_type, byte[] msg, int len)
    399             throws IOException {
    400         // In async environment we must have message header and message data in
    401         // one block to prevent messages from other threads getting between the
    402         // header and the data. So, we can't sent header, and then the data. We
    403         // must combine them in one data block instead.
    404         ByteBuffer bb = ByteBuffer.allocate(ProtocolConstants.MESSAGE_HEADER_SIZE + len);
    405         bb.order(mEndian);
    406 
    407         // Initialize message header.
    408         bb.putInt(ProtocolConstants.PACKET_SIGNATURE);
    409         bb.putInt(ProtocolConstants.MESSAGE_HEADER_SIZE + len);
    410         bb.putInt(ProtocolConstants.PACKET_TYPE_MESSAGE);
    411         bb.putInt(msg_type);
    412 
    413         // Save message data (if there is any).
    414         if (len != 0) {
    415             bb.put(msg, 0, len);
    416         }
    417 
    418         socket.send(bb.array());
    419     }
    420 
    421     /**
    422      * Sends message to the emulator.
    423      *
    424      * @param msg_type Message type.
    425      * @param msg Message data to send. Message size is defined by the size of
    426      *            the array.
    427      * @return true on success, or false if data transmission has failed.
    428      */
    429     public boolean sendMessage(int msg_type, byte[] msg, int msg_len) {
    430         try {
    431             Socket socket = mSocket;
    432             if (socket != null) {
    433                 sendMessage(socket, msg_type, msg, msg_len);
    434                 return true;
    435             } else {
    436                 Logw("sendMessage is called on disconnected Channel " + mChannelName);
    437             }
    438         } catch (IOException e) {
    439             Loge("Exception " + e + " in sendMessage for Channel " + mChannelName);
    440             onIoFailure();
    441         }
    442         return false;
    443     }
    444 
    445     /**
    446      * Sends message to the emulator.
    447      *
    448      * @param msg_type Message type.
    449      * @param msg Message data to send. Message size is defined by the size of
    450      *            the array.
    451      * @return true on success, or false if data transmission has failed.
    452      */
    453     public boolean sendMessage(int msg_type, byte[] msg) {
    454         try {
    455             Socket socket = mSocket;
    456             if (socket != null) {
    457                 if (msg != null) {
    458                     sendMessage(socket, msg_type, msg, msg.length);
    459                 } else {
    460                     sendMessage(socket, msg_type, null, 0);
    461                 }
    462                 return true;
    463             } else {
    464                 Logw("sendMessage is called on disconnected Channel " + mChannelName);
    465             }
    466         } catch (IOException e) {
    467             Loge("Exception " + e + " in sendMessage for Channel " + mChannelName);
    468             onIoFailure();
    469         }
    470         return false;
    471     }
    472 
    473     /**
    474      * Sends message to the emulator.
    475      *
    476      * @param msg_type Message type.
    477      * @param msg Message data to send. Message size is defined by the
    478      *            position() property of the ByteBuffer.
    479      * @return true on success, or false if data transmission has failed.
    480      */
    481     public boolean sendMessage(int msg_type, ByteBuffer msg) {
    482         try {
    483             Socket socket = mSocket;
    484             if (socket != null) {
    485                 if (msg != null) {
    486                     sendMessage(socket, msg_type, msg.array(), msg.position());
    487                 } else {
    488                     sendMessage(socket, msg_type, null, 0);
    489                 }
    490                 return true;
    491             } else {
    492                 Logw("sendMessage is called on disconnected Channel " + mChannelName);
    493             }
    494         } catch (IOException e) {
    495             Loge("Exception " + e + " in sendMessage for Channel " + mChannelName);
    496             onIoFailure();
    497         }
    498         return false;
    499     }
    500 
    501     /**
    502      * Posts message to the emulator.
    503      *
    504      * @param msg_type Message type.
    505      * @param msg Message data to post. Message size is defined by the size of
    506      *            the array.
    507      */
    508     public void postMessage(int msg_type, byte[] msg) {
    509         try {
    510             mMsgQueue.put(new SdkControllerMessage(msg_type, msg));
    511         } catch (InterruptedException e) {
    512             Log.e(TAG, "mMessageQueue.put", e);
    513         }
    514     }
    515 
    516     /**
    517      * Posts message to the emulator.
    518      *
    519      * @param msg_type Message type.
    520      * @param msg Message data to post. Message size is defined by the
    521      *            position() property of the ByteBuffer.
    522      */
    523     public void postMessage(int msg_type, ByteBuffer msg) {
    524         try {
    525             mMsgQueue.put(new SdkControllerMessage(msg_type, msg));
    526         } catch (InterruptedException e) {
    527             Log.e(TAG, "mMessageQueue.put", e);
    528         }
    529     }
    530 
    531     /**
    532      * Sends query response to the emulator.
    533      *
    534      * @param query_id Query identifier.
    535      * @param qresp Response to the query.
    536      * @param len Byte size of query response data.
    537      * @return true on success, or false if data transmission has failed.
    538      */
    539     public boolean sendQueryResponse(int query_id, byte[] qresp, int len) {
    540         // Just like with messages, we must combine header and data in a single
    541         // transmitting block.
    542         ByteBuffer bb = ByteBuffer.allocate(ProtocolConstants.QUERY_RESP_HEADER_SIZE + len);
    543         bb.order(mEndian);
    544 
    545         // Initialize response header.
    546         bb.putInt(ProtocolConstants.PACKET_SIGNATURE);
    547         bb.putInt(ProtocolConstants.QUERY_RESP_HEADER_SIZE + len);
    548         bb.putInt(ProtocolConstants.PACKET_TYPE_QUERY_RESPONSE);
    549         bb.putInt(query_id);
    550 
    551         // Save response data (if there is any).
    552         if (qresp != null && len != 0) {
    553             bb.put(qresp, 0, len);
    554         }
    555 
    556         // Send the response.
    557         try {
    558             Socket socket = mSocket;
    559             if (socket != null) {
    560                 socket.send(bb.array());
    561                 return true;
    562             } else {
    563                 Logw("sendQueryResponse is called on disconnected Channel "
    564                         + mChannelName);
    565             }
    566         } catch (IOException e) {
    567             Loge("Exception " + e + " in sendQueryResponse for Channel " + mChannelName);
    568             onIoFailure();
    569         }
    570         return false;
    571     }
    572 
    573     /**
    574      * Sends query response to the emulator.
    575      *
    576      * @param query_id Query identifier.
    577      * @param qresp Response to the query. Query response size is defined by the
    578      *            size of the array.
    579      * @return true on success, or false if data transmission has failed.
    580      */
    581     public boolean sendQueryResponse(int query_id, byte[] qresp) {
    582         return (qresp != null) ? sendQueryResponse(query_id, qresp, qresp.length) :
    583                 sendQueryResponse(query_id, null, 0);
    584     }
    585 
    586     /**
    587      * Sends query response to the emulator.
    588      *
    589      * @param query_id Query identifier.
    590      * @param qresp Response to the query. Query response size is defined by the
    591      *            position() property of the ByteBuffer.
    592      * @return true on success, or false if data transmission has failed.
    593      */
    594     public boolean sendQueryResponse(int query_id, ByteBuffer qresp) {
    595         return (qresp != null) ? sendQueryResponse(query_id, qresp.array(), qresp.position()) :
    596                 sendQueryResponse(query_id, null, 0);
    597     }
    598 
    599     /**
    600      * Handles an I/O failure occurred in the channel.
    601      */
    602     private void onIoFailure() {
    603         // All I/O failures cause disconnection.
    604         if (disconnect()) {
    605             // Success of disconnect() indicates that I/O failure is not the
    606             // result of a disconnection request, but is in deed an I/O
    607             // failure. Report lost connection to the derived class.
    608             Loge("Connection with the emulator has been lost in Channel " + mChannelName);
    609             onEmulatorDisconnected();
    610         }
    611     }
    612 
    613     /**
    614      * Loops on the local socket, handling connection attempts.
    615      */
    616     private void runIOLooper() {
    617         if (DEBUG) Log.d(TAG, "In I/O looper for Channel " + mChannelName);
    618         // Initialize byte buffer large enough to receive packet header.
    619         ByteBuffer header = ByteBuffer.allocate(ProtocolConstants.PACKET_HEADER_SIZE);
    620         header.order(mEndian);
    621         try {
    622             // Since disconnection (which will null the mSocket) can be
    623             // requested from outside of this thread, it's simpler just to make
    624             // a copy of mSocket here, and work with that copy. Otherwise we
    625             // will have to go through a complex synchronization algorithm that
    626             // would decrease performance on normal runs. If socket gets closed
    627             // while we're in the middle of transfer, an exception will occur,
    628             // which we will catch and handle properly.
    629             Socket socket = mSocket;
    630             while (socket != null) {
    631                 // Reset header position.
    632                 header.position(0);
    633                 // This will receive total packet size + packet type.
    634                 socket.receive(header.array());
    635                 // First - signature.
    636                 final int signature = header.getInt();
    637                 assert signature == ProtocolConstants.PACKET_SIGNATURE;
    638                 // Next - packet size (including header).
    639                 int remains = header.getInt() - ProtocolConstants.PACKET_HEADER_SIZE;
    640                 // After the size comes packet type.
    641                 final int packet_type = header.getInt();
    642 
    643                 // Get the remainder of the data, and dispatch the packet to
    644                 // an appropriate handler.
    645                 switch (packet_type) {
    646                     case ProtocolConstants.PACKET_TYPE_MESSAGE:
    647                         // Read message header (one int: message type).
    648                         final int ext = ProtocolConstants.MESSAGE_HEADER_SIZE - ProtocolConstants.PACKET_HEADER_SIZE;
    649                         header.position(0);
    650                         socket.receive(header.array(), ext);
    651                         final int msg_type = header.getInt();
    652 
    653                         // Read message data.
    654                         remains -= ext;
    655                         final ByteBuffer msg_data = ByteBuffer.allocate(remains);
    656                         msg_data.order(mEndian);
    657                         socket.receive(msg_data.array());
    658 
    659                         // Dispatch message for handling.
    660                         onEmulatorMessage(msg_type, msg_data);
    661                         break;
    662 
    663                     case ProtocolConstants.PACKET_TYPE_QUERY:
    664                         // Read query ID and query type.
    665                         final int extq = ProtocolConstants.QUERY_HEADER_SIZE - ProtocolConstants.PACKET_HEADER_SIZE;
    666                         header.position(0);
    667                         socket.receive(header.array(), extq);
    668                         final int query_id = header.getInt();
    669                         final int query_type = header.getInt();
    670 
    671                         // Read query data.
    672                         remains -= extq;
    673                         final ByteBuffer query_data = ByteBuffer.allocate(remains);
    674                         query_data.order(mEndian);
    675                         socket.receive(query_data.array());
    676 
    677                         // Dispatch query for handling.
    678                         onEmulatorQuery(query_id, query_type, query_data);
    679                         break;
    680 
    681                     default:
    682                         // Unknown packet type. Just discard the remainder
    683                         // of the packet
    684                         Loge("Unknown packet type " + packet_type + " in Channel "
    685                                 + mChannelName);
    686                         final byte[] discard_data = new byte[remains];
    687                         socket.receive(discard_data);
    688                         break;
    689                 }
    690                 socket = mSocket;
    691             }
    692         } catch (IOException e) {
    693             Loge("Exception " + e + " in I/O looper for Channel " + mChannelName);
    694             onIoFailure();
    695         }
    696         if (DEBUG) Log.d(TAG, "Exiting I/O looper for Channel " + mChannelName);
    697     }
    698 
    699     /**
    700      * Indicates any UI handler is currently registered with the channel. If no UI
    701      * is displaying the channel's state, maybe the channel can skip UI related tasks.
    702      *
    703      * @return True if there's at least one UI handler registered.
    704      */
    705     public boolean hasUiHandler() {
    706         return !mUiHandlers.isEmpty();
    707     }
    708 
    709     /**
    710      * Registers a new UI handler.
    711      *
    712      * @param uiHandler A non-null UI handler to register. Ignored if the UI
    713      *            handler is null or already registered.
    714      */
    715     public void addUiHandler(android.os.Handler uiHandler) {
    716         assert uiHandler != null;
    717         if (uiHandler != null) {
    718             if (!mUiHandlers.contains(uiHandler)) {
    719                 mUiHandlers.add(uiHandler);
    720             }
    721         }
    722     }
    723 
    724     /**
    725      * Unregisters an UI handler.
    726      *
    727      * @param uiHandler A non-null UI listener to unregister. Ignored if the
    728      *            listener is null or already registered.
    729      */
    730     public void removeUiHandler(android.os.Handler uiHandler) {
    731         assert uiHandler != null;
    732         mUiHandlers.remove(uiHandler);
    733     }
    734 
    735     /**
    736      * Protected method to be used by handlers to send an event to all UI
    737      * handlers.
    738      *
    739      * @param event An integer event code with no specific parameters. To be
    740      *            defined by the handler itself.
    741      */
    742     protected void notifyUiHandlers(int event) {
    743         for (android.os.Handler uiHandler : mUiHandlers) {
    744             uiHandler.sendEmptyMessage(event);
    745         }
    746     }
    747 
    748     /**
    749      * Protected method to be used by handlers to send an event to all UI
    750      * handlers.
    751      *
    752      * @param msg An event with parameters. To be defined by the handler itself.
    753      */
    754     protected void notifyUiHandlers(Message msg) {
    755         for (android.os.Handler uiHandler : mUiHandlers) {
    756             uiHandler.sendMessage(msg);
    757         }
    758     }
    759 
    760     /**
    761      * A helper routine that expands ByteBuffer to contain given number of extra
    762      * bytes.
    763      *
    764      * @param buff Buffer to expand.
    765      * @param extra Number of bytes that are required to be available in the
    766      *            buffer after current position()
    767      * @return ByteBuffer, containing required number of available bytes.
    768      */
    769     public ByteBuffer ExpandIf(ByteBuffer buff, int extra) {
    770         if (extra <= buff.remaining()) {
    771             return buff;
    772         }
    773         ByteBuffer ret = ByteBuffer.allocate(buff.position() + extra);
    774         ret.order(buff.order());
    775         ret.put(buff.array(), 0, buff.position());
    776         return ret;
    777     }
    778 
    779     /***************************************************************************
    780      * Logging wrappers
    781      **************************************************************************/
    782 
    783     private void Loge(String log) {
    784         mService.addError(log);
    785         Log.e(TAG, log);
    786     }
    787 
    788     private void Logw(String log) {
    789         Log.w(TAG, log);
    790     }
    791 
    792     private void Logv(String log) {
    793         Log.v(TAG, log);
    794     }
    795 }
    796