• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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          */
SdkControllerMessage(int type, byte[] message)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          */
SdkControllerMessage(int type, ByteBuffer message)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          */
getMessageType()107         public int getMessageType() {
108             return mMessageType;
109         }
110 
111         /**
112          * Gets message buffer.
113          *
114          * @return Message buffer.
115          */
getMessage()116         public byte[] getMessage() {
117             return mMessage;
118         }
119 
120         /**
121          * Gets message buffer size.
122          *
123          * @return Message buffer size.
124          */
getMessageSize()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      */
onEmulatorConnected()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      */
onEmulatorDisconnected()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      */
onEmulatorMessage(int msg_type, ByteBuffer msg_data)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      */
onEmulatorQuery(int query_id, int query_type, ByteBuffer query_data)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      */
Channel(ControllerService service, String name)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      */
getChannelName()269     public String getChannelName() {
270         return mChannelName;
271     }
272 
273     /**
274      * Gets endianness for this channel.
275      *
276      * @return Channel endianness.
277      */
getEndian()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      */
getMsgSentCount()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      */
isConnected()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      */
connect(Socket socket)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      */
disconnect()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      */
enable()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      */
disable()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      */
sendMessage(Socket socket, int msg_type, byte[] msg, int len)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      */
sendMessage(int msg_type, byte[] msg, int msg_len)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      */
sendMessage(int msg_type, byte[] msg)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      */
sendMessage(int msg_type, ByteBuffer msg)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      */
postMessage(int msg_type, byte[] msg)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      */
postMessage(int msg_type, ByteBuffer msg)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      */
sendQueryResponse(int query_id, byte[] qresp, int len)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      */
sendQueryResponse(int query_id, byte[] qresp)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      */
sendQueryResponse(int query_id, ByteBuffer qresp)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      */
onIoFailure()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      */
runIOLooper()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      */
hasUiHandler()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      */
addUiHandler(android.os.Handler uiHandler)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      */
removeUiHandler(android.os.Handler uiHandler)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      */
notifyUiHandlers(int event)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      */
notifyUiHandlers(Message msg)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      */
ExpandIf(ByteBuffer buff, int extra)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 
Loge(String log)783     private void Loge(String log) {
784         mService.addError(log);
785         Log.e(TAG, log);
786     }
787 
Logw(String log)788     private void Logw(String log) {
789         Log.w(TAG, log);
790     }
791 
Logv(String log)792     private void Logv(String log) {
793         Log.v(TAG, log);
794     }
795 }
796