2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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
17 package com
.android
.tools
.sdkcontroller
.lib
;
19 import android
.os
.Message
;
20 import android
.util
.Log
;
22 import com
.android
.tools
.sdkcontroller
.service
.ControllerService
;
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
;
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.)
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.
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,
49 * - Providing a way to register / unregister a UI component with the channel.
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
55 public abstract class Channel
{
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.
62 private class SdkControllerMessage
{
64 private int mMessageType
;
65 /** Message data (can be null). */
66 private byte[] mMessage
;
67 /** Message data size */
68 private int mMessageSize
;
71 * Construct message from an array.
73 * @param type Message type.
74 * @param message Message data. Message data size is defined by size of
77 public SdkControllerMessage(int type
, byte[] message
) {
80 mMessageSize
= (message
!= null) ? message
.length
: 0;
84 * Construct message from a ByteBuffer.
86 * @param type Message type.
87 * @param message Message data. Message data size is defined by
88 * position() property of the ByteBuffer.
90 public SdkControllerMessage(int type
, ByteBuffer message
) {
92 if (message
!= null) {
93 mMessage
= message
.array();
94 mMessageSize
= message
.position();
105 * @return Message type.
107 public int getMessageType() {
112 * Gets message buffer.
114 * @return Message buffer.
116 public byte[] getMessage() {
121 * Gets message buffer size.
123 * @return Message buffer size.
125 public int getMessageSize() {
128 } // SdkControllerMessage
131 * Names for currently implemented SDK controller channels.
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";
140 * Types of messages internally used by Channel class.
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;
152 /** Tag for logging messages. */
153 private static final String TAG
= "SdkControllerChannel";
154 /** Controls debug log. */
155 private static final boolean DEBUG
= false;
157 /** Service that has created this object. */
158 protected ControllerService mService
;
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
;
172 * Message posting support.
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
;
189 /** Lists UI handlers attached to this channel. */
190 private final List
<android
.os
.Handler
> mUiHandlers
= new ArrayList
<android
.os
.Handler
>();
197 * This method is invoked when this channel is fully connected with its
198 * counterpart in the emulator.
200 public abstract void onEmulatorConnected();
203 * This method is invoked when this channel loses connection with its
204 * counterpart in the emulator.
206 public abstract void onEmulatorDisconnected();
209 * A message has been received from the emulator.
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.
215 public abstract void onEmulatorMessage(int msg_type
, ByteBuffer msg_data
);
218 * A query has been received from the emulator.
220 * @param query_id Identifies the query. This ID must be used when replying
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.
226 public abstract void onEmulatorQuery(int query_id
, int query_type
, ByteBuffer query_data
);
229 * Channel implementation.
233 * Constructs Channel instance.
235 * @param name Channel name.
237 public Channel(ControllerService service
, String name
) {
240 // Start the worker thread for posted messages.
241 mMsgThread
= new Thread(new Runnable() {
244 if (DEBUG
) Log
.d(TAG
, "MsgThread.started-" + mChannelName
);
245 while (mRunMsgQueue
) {
247 SdkControllerMessage msg
= mMsgQueue
.take();
250 msg
.getMessageType(), msg
.getMessage(), msg
.getMessageSize());
251 mMsgCount
.incrementAndGet();
253 } catch (InterruptedException e
) {
254 Log
.e(TAG
, "MsgThread-" + mChannelName
, e
);
257 if (DEBUG
) Log
.d(TAG
, "MsgThread.terminate-" + mChannelName
);
259 }, "MsgThread-" + name
);
261 if (DEBUG
) Log
.d(TAG
, "Channel is constructed for " + mChannelName
);
265 * Gets name for this channel.
267 * @return Emulator name.
269 public String
getChannelName() {
274 * Gets endianness for this channel.
276 * @return Channel endianness.
278 public ByteOrder
getEndian() {
283 * Gets number of messages sent via postMessage method.
285 * @return Number of messages sent via postMessage method.
287 public int getMsgSentCount() {
288 return mMsgCount
.get();
292 * Checks if this channel is connected with the emulator.
294 * @return true if this channel is connected with the emulator, or false if it is
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.
303 // Same technique is used everywhere in this class where mSocket member
305 Socket socket
= mSocket
;
306 return socket
!= null && socket
.isConnected();
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.
314 * @param socket Channel connection socket.
316 public void connect(Socket 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);
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() {
334 }, "ChannelIoLoop").start();
335 mService
.notifyStatusChanged();
339 * Disconnects this channel from the emulator.
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.
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.
349 synchronized (this) {
353 if (socket
!= null) {
354 // Notify the emulator about channel disconnection before we close
355 // the communication socket.
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.
362 // This will eventually stop I/O looper thread.
364 mService
.notifyStatusChanged();
366 return socket
!= null;
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.
375 public void enable() {
376 postMessage(MSG_ENABLED
, (byte[]) null);
377 mService
.notifyStatusChanged();
381 * Disables the emulation. Just the opposite to enable(). For multi-touch this
382 * method is called when UI detaches from the channel.
384 public void disable() {
385 postMessage(MSG_DISABLED
, (byte[]) null);
386 mService
.notifyStatusChanged();
390 * Sends message to the emulator.
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
398 private void sendMessage(Socket socket
, int msg_type
, byte[] msg
, int len
)
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
);
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
);
413 // Save message data (if there is any).
418 socket
.send(bb
.array());
422 * Sends message to the emulator.
424 * @param msg_type Message type.
425 * @param msg Message data to send. Message size is defined by the size of
427 * @return true on success, or false if data transmission has failed.
429 public boolean sendMessage(int msg_type
, byte[] msg
, int msg_len
) {
431 Socket socket
= mSocket
;
432 if (socket
!= null) {
433 sendMessage(socket
, msg_type
, msg
, msg_len
);
436 Logw("sendMessage is called on disconnected Channel " + mChannelName
);
438 } catch (IOException e
) {
439 Loge("Exception " + e
+ " in sendMessage for Channel " + mChannelName
);
446 * Sends message to the emulator.
448 * @param msg_type Message type.
449 * @param msg Message data to send. Message size is defined by the size of
451 * @return true on success, or false if data transmission has failed.
453 public boolean sendMessage(int msg_type
, byte[] msg
) {
455 Socket socket
= mSocket
;
456 if (socket
!= null) {
458 sendMessage(socket
, msg_type
, msg
, msg
.length
);
460 sendMessage(socket
, msg_type
, null, 0);
464 Logw("sendMessage is called on disconnected Channel " + mChannelName
);
466 } catch (IOException e
) {
467 Loge("Exception " + e
+ " in sendMessage for Channel " + mChannelName
);
474 * Sends message to the emulator.
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.
481 public boolean sendMessage(int msg_type
, ByteBuffer msg
) {
483 Socket socket
= mSocket
;
484 if (socket
!= null) {
486 sendMessage(socket
, msg_type
, msg
.array(), msg
.position());
488 sendMessage(socket
, msg_type
, null, 0);
492 Logw("sendMessage is called on disconnected Channel " + mChannelName
);
494 } catch (IOException e
) {
495 Loge("Exception " + e
+ " in sendMessage for Channel " + mChannelName
);
502 * Posts message to the emulator.
504 * @param msg_type Message type.
505 * @param msg Message data to post. Message size is defined by the size of
508 public void postMessage(int msg_type
, byte[] msg
) {
510 mMsgQueue
.put(new SdkControllerMessage(msg_type
, msg
));
511 } catch (InterruptedException e
) {
512 Log
.e(TAG
, "mMessageQueue.put", e
);
517 * Posts message to the emulator.
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.
523 public void postMessage(int msg_type
, ByteBuffer msg
) {
525 mMsgQueue
.put(new SdkControllerMessage(msg_type
, msg
));
526 } catch (InterruptedException e
) {
527 Log
.e(TAG
, "mMessageQueue.put", e
);
532 * Sends query response to the emulator.
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.
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
);
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
);
551 // Save response data (if there is any).
552 if (qresp
!= null && len
!= 0) {
553 bb
.put(qresp
, 0, len
);
556 // Send the response.
558 Socket socket
= mSocket
;
559 if (socket
!= null) {
560 socket
.send(bb
.array());
563 Logw("sendQueryResponse is called on disconnected Channel "
566 } catch (IOException e
) {
567 Loge("Exception " + e
+ " in sendQueryResponse for Channel " + mChannelName
);
574 * Sends query response to the emulator.
576 * @param query_id Query identifier.
577 * @param qresp Response to the query. Query response size is defined by the
579 * @return true on success, or false if data transmission has failed.
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);
587 * Sends query response to the emulator.
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.
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);
600 * Handles an I/O failure occurred in the channel.
602 private void onIoFailure() {
603 // All I/O failures cause disconnection.
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();
614 * Loops on the local socket, handling connection attempts.
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
);
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.
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();
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
;
650 socket
.receive(header
.array(), ext
);
651 final int msg_type
= header
.getInt();
653 // Read message data.
655 final ByteBuffer msg_data
= ByteBuffer
.allocate(remains
);
656 msg_data
.order(mEndian
);
657 socket
.receive(msg_data
.array());
659 // Dispatch message for handling.
660 onEmulatorMessage(msg_type
, msg_data
);
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
;
667 socket
.receive(header
.array(), extq
);
668 final int query_id
= header
.getInt();
669 final int query_type
= header
.getInt();
673 final ByteBuffer query_data
= ByteBuffer
.allocate(remains
);
674 query_data
.order(mEndian
);
675 socket
.receive(query_data
.array());
677 // Dispatch query for handling.
678 onEmulatorQuery(query_id
, query_type
, query_data
);
682 // Unknown packet type. Just discard the remainder
684 Loge("Unknown packet type " + packet_type
+ " in Channel "
686 final byte[] discard_data
= new byte[remains
];
687 socket
.receive(discard_data
);
692 } catch (IOException e
) {
693 Loge("Exception " + e
+ " in I/O looper for Channel " + mChannelName
);
696 if (DEBUG
) Log
.d(TAG
, "Exiting I/O looper for Channel " + mChannelName
);
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.
703 * @return True if there's at least one UI handler registered.
705 public boolean hasUiHandler() {
706 return !mUiHandlers
.isEmpty();
710 * Registers a new UI handler.
712 * @param uiHandler A non-null UI handler to register. Ignored if the UI
713 * handler is null or already registered.
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
);
725 * Unregisters an UI handler.
727 * @param uiHandler A non-null UI listener to unregister. Ignored if the
728 * listener is null or already registered.
730 public void removeUiHandler(android
.os
.Handler uiHandler
) {
731 assert uiHandler
!= null;
732 mUiHandlers
.remove(uiHandler
);
736 * Protected method to be used by handlers to send an event to all UI
739 * @param event An integer event code with no specific parameters. To be
740 * defined by the handler itself.
742 protected void notifyUiHandlers(int event
) {
743 for (android
.os
.Handler uiHandler
: mUiHandlers
) {
744 uiHandler
.sendEmptyMessage(event
);
749 * Protected method to be used by handlers to send an event to all UI
752 * @param msg An event with parameters. To be defined by the handler itself.
754 protected void notifyUiHandlers(Message msg
) {
755 for (android
.os
.Handler uiHandler
: mUiHandlers
) {
756 uiHandler
.sendMessage(msg
);
761 * A helper routine that expands ByteBuffer to contain given number of extra
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.
769 public ByteBuffer
ExpandIf(ByteBuffer buff
, int extra
) {
770 if (extra
<= buff
.remaining()) {
773 ByteBuffer ret
= ByteBuffer
.allocate(buff
.position() + extra
);
774 ret
.order(buff
.order());
775 ret
.put(buff
.array(), 0, buff
.position());
779 /***************************************************************************
781 **************************************************************************/
783 private void Loge(String log
) {
784 mService
.addError(log
);
788 private void Logw(String log
) {
792 private void Logv(String log
) {