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