1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ddmlib; 18 19 import com.android.ddmlib.Log.LogLevel; 20 import com.android.ddmlib.log.LogReceiver; 21 22 import java.io.IOException; 23 import java.io.UnsupportedEncodingException; 24 import java.net.InetSocketAddress; 25 import java.nio.ByteBuffer; 26 import java.nio.ByteOrder; 27 import java.nio.channels.SocketChannel; 28 29 /** 30 * Helper class to handle requests and connections to adb. 31 * <p/>{@link DebugBridgeServer} is the public API to connection to adb, while {@link AdbHelper} 32 * does the low level stuff. 33 * <p/>This currently uses spin-wait non-blocking I/O. A Selector would be more efficient, 34 * but seems like overkill for what we're doing here. 35 */ 36 final class AdbHelper { 37 38 // public static final long kOkay = 0x59414b4fL; 39 // public static final long kFail = 0x4c494146L; 40 41 static final int WAIT_TIME = 5; // spin-wait sleep, in ms 42 43 static final String DEFAULT_ENCODING = "ISO-8859-1"; //$NON-NLS-1$ 44 45 /** do not instantiate */ AdbHelper()46 private AdbHelper() { 47 } 48 49 /** 50 * Response from ADB. 51 */ 52 static class AdbResponse { AdbResponse()53 public AdbResponse() { 54 // ioSuccess = okay = timeout = false; 55 message = ""; 56 } 57 58 public boolean ioSuccess; // read all expected data, no timeoutes 59 60 public boolean okay; // first 4 bytes in response were "OKAY"? 61 62 public boolean timeout; // TODO: implement 63 64 public String message; // diagnostic string 65 } 66 67 /** 68 * Create and connect a new pass-through socket, from the host to a port on 69 * the device. 70 * 71 * @param adbSockAddr 72 * @param device the device to connect to. Can be null in which case the connection will be 73 * to the first available device. 74 * @param devicePort the port we're opening 75 */ open(InetSocketAddress adbSockAddr, Device device, int devicePort)76 public static SocketChannel open(InetSocketAddress adbSockAddr, 77 Device device, int devicePort) throws IOException { 78 79 SocketChannel adbChan = SocketChannel.open(adbSockAddr); 80 try { 81 adbChan.socket().setTcpNoDelay(true); 82 adbChan.configureBlocking(false); 83 84 // if the device is not -1, then we first tell adb we're looking to 85 // talk to a specific device 86 setDevice(adbChan, device); 87 88 byte[] req = createAdbForwardRequest(null, devicePort); 89 // Log.hexDump(req); 90 91 if (write(adbChan, req) == false) 92 throw new IOException("failed submitting request to ADB"); //$NON-NLS-1$ 93 94 AdbResponse resp = readAdbResponse(adbChan, false); 95 if (!resp.okay) 96 throw new IOException("connection request rejected"); //$NON-NLS-1$ 97 98 adbChan.configureBlocking(true); 99 } catch (IOException ioe) { 100 adbChan.close(); 101 throw ioe; 102 } 103 104 return adbChan; 105 } 106 107 /** 108 * Creates and connects a new pass-through socket, from the host to a port on 109 * the device. 110 * 111 * @param adbSockAddr 112 * @param device the device to connect to. Can be null in which case the connection will be 113 * to the first available device. 114 * @param pid the process pid to connect to. 115 */ createPassThroughConnection(InetSocketAddress adbSockAddr, Device device, int pid)116 public static SocketChannel createPassThroughConnection(InetSocketAddress adbSockAddr, 117 Device device, int pid) throws IOException { 118 119 SocketChannel adbChan = SocketChannel.open(adbSockAddr); 120 try { 121 adbChan.socket().setTcpNoDelay(true); 122 adbChan.configureBlocking(false); 123 124 // if the device is not -1, then we first tell adb we're looking to 125 // talk to a specific device 126 setDevice(adbChan, device); 127 128 byte[] req = createJdwpForwardRequest(pid); 129 // Log.hexDump(req); 130 131 if (write(adbChan, req) == false) 132 throw new IOException("failed submitting request to ADB"); //$NON-NLS-1$ 133 134 AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); 135 if (!resp.okay) 136 throw new IOException("connection request rejected: " + resp.message); //$NON-NLS-1$ 137 138 adbChan.configureBlocking(true); 139 } catch (IOException ioe) { 140 adbChan.close(); 141 throw ioe; 142 } 143 144 return adbChan; 145 } 146 147 /** 148 * Creates a port forwarding request for adb. This returns an array 149 * containing "####tcp:{port}:{addStr}". 150 * @param addrStr the host. Can be null. 151 * @param port the port on the device. This does not need to be numeric. 152 */ createAdbForwardRequest(String addrStr, int port)153 private static byte[] createAdbForwardRequest(String addrStr, int port) { 154 String reqStr; 155 156 if (addrStr == null) 157 reqStr = "tcp:" + port; 158 else 159 reqStr = "tcp:" + port + ":" + addrStr; 160 return formAdbRequest(reqStr); 161 } 162 163 /** 164 * Creates a port forwarding request to a jdwp process. This returns an array 165 * containing "####jwdp:{pid}". 166 * @param pid the jdwp process pid on the device. 167 */ createJdwpForwardRequest(int pid)168 private static byte[] createJdwpForwardRequest(int pid) { 169 String reqStr = String.format("jdwp:%1$d", pid); //$NON-NLS-1$ 170 return formAdbRequest(reqStr); 171 } 172 173 /** 174 * Create an ASCII string preceeded by four hex digits. The opening "####" 175 * is the length of the rest of the string, encoded as ASCII hex (case 176 * doesn't matter). "port" and "host" are what we want to forward to. If 177 * we're on the host side connecting into the device, "addrStr" should be 178 * null. 179 */ formAdbRequest(String req)180 static byte[] formAdbRequest(String req) { 181 String resultStr = String.format("%04X%s", req.length(), req); //$NON-NLS-1$ 182 byte[] result; 183 try { 184 result = resultStr.getBytes(DEFAULT_ENCODING); 185 } catch (UnsupportedEncodingException uee) { 186 uee.printStackTrace(); // not expected 187 return null; 188 } 189 assert result.length == req.length() + 4; 190 return result; 191 } 192 193 /** 194 * Reads the response from ADB after a command. 195 * @param chan The socket channel that is connected to adb. 196 * @param readDiagString If true, we're expecting an OKAY response to be 197 * followed by a diagnostic string. Otherwise, we only expect the 198 * diagnostic string to follow a FAIL. 199 */ readAdbResponse(SocketChannel chan, boolean readDiagString)200 static AdbResponse readAdbResponse(SocketChannel chan, boolean readDiagString) 201 throws IOException { 202 203 AdbResponse resp = new AdbResponse(); 204 205 byte[] reply = new byte[4]; 206 if (read(chan, reply) == false) { 207 return resp; 208 } 209 resp.ioSuccess = true; 210 211 if (isOkay(reply)) { 212 resp.okay = true; 213 } else { 214 readDiagString = true; // look for a reason after the FAIL 215 resp.okay = false; 216 } 217 218 // not a loop -- use "while" so we can use "break" 219 while (readDiagString) { 220 // length string is in next 4 bytes 221 byte[] lenBuf = new byte[4]; 222 if (read(chan, lenBuf) == false) { 223 Log.w("ddms", "Expected diagnostic string not found"); 224 break; 225 } 226 227 String lenStr = replyToString(lenBuf); 228 229 int len; 230 try { 231 len = Integer.parseInt(lenStr, 16); 232 } catch (NumberFormatException nfe) { 233 Log.w("ddms", "Expected digits, got '" + lenStr + "': " 234 + lenBuf[0] + " " + lenBuf[1] + " " + lenBuf[2] + " " 235 + lenBuf[3]); 236 Log.w("ddms", "reply was " + replyToString(reply)); 237 break; 238 } 239 240 byte[] msg = new byte[len]; 241 if (read(chan, msg) == false) { 242 Log.w("ddms", "Failed reading diagnostic string, len=" + len); 243 break; 244 } 245 246 resp.message = replyToString(msg); 247 Log.v("ddms", "Got reply '" + replyToString(reply) + "', diag='" 248 + resp.message + "'"); 249 250 break; 251 } 252 253 return resp; 254 } 255 256 /** 257 * Retrieve the frame buffer from the device. 258 */ getFrameBuffer(InetSocketAddress adbSockAddr, Device device)259 public static RawImage getFrameBuffer(InetSocketAddress adbSockAddr, Device device) 260 throws IOException { 261 262 RawImage imageParams = new RawImage(); 263 byte[] request = formAdbRequest("framebuffer:"); //$NON-NLS-1$ 264 byte[] nudge = { 265 0 266 }; 267 byte[] reply; 268 269 SocketChannel adbChan = null; 270 try { 271 adbChan = SocketChannel.open(adbSockAddr); 272 adbChan.configureBlocking(false); 273 274 // if the device is not -1, then we first tell adb we're looking to talk 275 // to a specific device 276 setDevice(adbChan, device); 277 278 if (write(adbChan, request) == false) 279 throw new IOException("failed asking for frame buffer"); 280 281 AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); 282 if (!resp.ioSuccess || !resp.okay) { 283 Log.w("ddms", "Got timeout or unhappy response from ADB fb req: " 284 + resp.message); 285 adbChan.close(); 286 return null; 287 } 288 289 // first the protocol version. 290 reply = new byte[4]; 291 if (read(adbChan, reply) == false) { 292 Log.w("ddms", "got partial reply from ADB fb:"); 293 Log.hexDump("ddms", LogLevel.WARN, reply, 0, reply.length); 294 adbChan.close(); 295 return null; 296 } 297 ByteBuffer buf = ByteBuffer.wrap(reply); 298 buf.order(ByteOrder.LITTLE_ENDIAN); 299 300 int version = buf.getInt(); 301 302 // get the header size (this is a count of int) 303 int headerSize = RawImage.getHeaderSize(version); 304 305 // read the header 306 reply = new byte[headerSize * 4]; 307 if (read(adbChan, reply) == false) { 308 Log.w("ddms", "got partial reply from ADB fb:"); 309 Log.hexDump("ddms", LogLevel.WARN, reply, 0, reply.length); 310 adbChan.close(); 311 return null; 312 } 313 buf = ByteBuffer.wrap(reply); 314 buf.order(ByteOrder.LITTLE_ENDIAN); 315 316 // fill the RawImage with the header 317 if (imageParams.readHeader(version, buf) == false) { 318 Log.e("Screenshot", "Unsupported protocol: " + version); 319 return null; 320 } 321 322 Log.d("ddms", "image params: bpp=" + imageParams.bpp + ", size=" 323 + imageParams.size + ", width=" + imageParams.width 324 + ", height=" + imageParams.height); 325 326 if (write(adbChan, nudge) == false) 327 throw new IOException("failed nudging"); 328 329 reply = new byte[imageParams.size]; 330 if (read(adbChan, reply) == false) { 331 Log.w("ddms", "got truncated reply from ADB fb data"); 332 adbChan.close(); 333 return null; 334 } 335 336 imageParams.data = reply; 337 } finally { 338 if (adbChan != null) { 339 adbChan.close(); 340 } 341 } 342 343 return imageParams; 344 } 345 346 /** 347 * Execute a command on the device and retrieve the output. The output is 348 * handed to "rcvr" as it arrives. 349 */ executeRemoteCommand(InetSocketAddress adbSockAddr, String command, Device device, IShellOutputReceiver rcvr)350 public static void executeRemoteCommand(InetSocketAddress adbSockAddr, 351 String command, Device device, IShellOutputReceiver rcvr) 352 throws IOException { 353 Log.v("ddms", "execute: running " + command); 354 355 SocketChannel adbChan = null; 356 try { 357 adbChan = SocketChannel.open(adbSockAddr); 358 adbChan.configureBlocking(false); 359 360 // if the device is not -1, then we first tell adb we're looking to 361 // talk 362 // to a specific device 363 setDevice(adbChan, device); 364 365 byte[] request = formAdbRequest("shell:" + command); //$NON-NLS-1$ 366 if (write(adbChan, request) == false) 367 throw new IOException("failed submitting shell command"); 368 369 AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); 370 if (!resp.ioSuccess || !resp.okay) { 371 Log.e("ddms", "ADB rejected shell command (" + command + "): " + resp.message); 372 throw new IOException("sad result from adb: " + resp.message); 373 } 374 375 byte[] data = new byte[16384]; 376 ByteBuffer buf = ByteBuffer.wrap(data); 377 while (true) { 378 int count; 379 380 if (rcvr != null && rcvr.isCancelled()) { 381 Log.v("ddms", "execute: cancelled"); 382 break; 383 } 384 385 count = adbChan.read(buf); 386 if (count < 0) { 387 // we're at the end, we flush the output 388 rcvr.flush(); 389 Log.v("ddms", "execute '" + command + "' on '" + device + "' : EOF hit. Read: " 390 + count); 391 break; 392 } else if (count == 0) { 393 try { 394 Thread.sleep(WAIT_TIME * 5); 395 } catch (InterruptedException ie) { 396 } 397 } else { 398 if (rcvr != null) { 399 rcvr.addOutput(buf.array(), buf.arrayOffset(), buf.position()); 400 } 401 buf.rewind(); 402 } 403 } 404 } finally { 405 if (adbChan != null) { 406 adbChan.close(); 407 } 408 Log.v("ddms", "execute: returning"); 409 } 410 } 411 412 /** 413 * Runs the Event log service on the {@link Device}, and provides its output to the 414 * {@link LogReceiver}. 415 * @param adbSockAddr the socket address to connect to adb 416 * @param device the Device on which to run the service 417 * @param rcvr the {@link LogReceiver} to receive the log output 418 * @throws IOException 419 */ runEventLogService(InetSocketAddress adbSockAddr, Device device, LogReceiver rcvr)420 public static void runEventLogService(InetSocketAddress adbSockAddr, Device device, 421 LogReceiver rcvr) throws IOException { 422 runLogService(adbSockAddr, device, "events", rcvr); //$NON-NLS-1$ 423 } 424 425 /** 426 * Runs a log service on the {@link Device}, and provides its output to the {@link LogReceiver}. 427 * @param adbSockAddr the socket address to connect to adb 428 * @param device the Device on which to run the service 429 * @param logName the name of the log file to output 430 * @param rcvr the {@link LogReceiver} to receive the log output 431 * @throws IOException 432 */ runLogService(InetSocketAddress adbSockAddr, Device device, String logName, LogReceiver rcvr)433 public static void runLogService(InetSocketAddress adbSockAddr, Device device, String logName, 434 LogReceiver rcvr) throws IOException { 435 SocketChannel adbChan = null; 436 437 try { 438 adbChan = SocketChannel.open(adbSockAddr); 439 adbChan.configureBlocking(false); 440 441 // if the device is not -1, then we first tell adb we're looking to talk 442 // to a specific device 443 setDevice(adbChan, device); 444 445 byte[] request = formAdbRequest("log:" + logName); 446 if (write(adbChan, request) == false) { 447 throw new IOException("failed to submit the log command"); 448 } 449 450 AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); 451 if (!resp.ioSuccess || !resp.okay) { 452 throw new IOException("Device rejected log command: " + resp.message); 453 } 454 455 byte[] data = new byte[16384]; 456 ByteBuffer buf = ByteBuffer.wrap(data); 457 while (true) { 458 int count; 459 460 if (rcvr != null && rcvr.isCancelled()) { 461 break; 462 } 463 464 count = adbChan.read(buf); 465 if (count < 0) { 466 break; 467 } else if (count == 0) { 468 try { 469 Thread.sleep(WAIT_TIME * 5); 470 } catch (InterruptedException ie) { 471 } 472 } else { 473 if (rcvr != null) { 474 rcvr.parseNewData(buf.array(), buf.arrayOffset(), buf.position()); 475 } 476 buf.rewind(); 477 } 478 } 479 } finally { 480 if (adbChan != null) { 481 adbChan.close(); 482 } 483 } 484 } 485 486 /** 487 * Creates a port forwarding between a local and a remote port. 488 * @param adbSockAddr the socket address to connect to adb 489 * @param device the device on which to do the port fowarding 490 * @param localPort the local port to forward 491 * @param remotePort the remote port. 492 * @return <code>true</code> if success. 493 * @throws IOException 494 */ createForward(InetSocketAddress adbSockAddr, Device device, int localPort, int remotePort)495 public static boolean createForward(InetSocketAddress adbSockAddr, Device device, int localPort, 496 int remotePort) throws IOException { 497 498 SocketChannel adbChan = null; 499 try { 500 adbChan = SocketChannel.open(adbSockAddr); 501 adbChan.configureBlocking(false); 502 503 byte[] request = formAdbRequest(String.format( 504 "host-serial:%1$s:forward:tcp:%2$d;tcp:%3$d", //$NON-NLS-1$ 505 device.getSerialNumber(), localPort, remotePort)); 506 507 if (write(adbChan, request) == false) { 508 throw new IOException("failed to submit the forward command."); 509 } 510 511 AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); 512 if (!resp.ioSuccess || !resp.okay) { 513 throw new IOException("Device rejected command: " + resp.message); 514 } 515 } finally { 516 if (adbChan != null) { 517 adbChan.close(); 518 } 519 } 520 521 return true; 522 } 523 524 /** 525 * Remove a port forwarding between a local and a remote port. 526 * @param adbSockAddr the socket address to connect to adb 527 * @param device the device on which to remove the port fowarding 528 * @param localPort the local port of the forward 529 * @param remotePort the remote port. 530 * @return <code>true</code> if success. 531 * @throws IOException 532 */ removeForward(InetSocketAddress adbSockAddr, Device device, int localPort, int remotePort)533 public static boolean removeForward(InetSocketAddress adbSockAddr, Device device, int localPort, 534 int remotePort) throws IOException { 535 536 SocketChannel adbChan = null; 537 try { 538 adbChan = SocketChannel.open(adbSockAddr); 539 adbChan.configureBlocking(false); 540 541 byte[] request = formAdbRequest(String.format( 542 "host-serial:%1$s:killforward:tcp:%2$d;tcp:%3$d", //$NON-NLS-1$ 543 device.getSerialNumber(), localPort, remotePort)); 544 545 if (!write(adbChan, request)) { 546 throw new IOException("failed to submit the remove forward command."); 547 } 548 549 AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); 550 if (!resp.ioSuccess || !resp.okay) { 551 throw new IOException("Device rejected command: " + resp.message); 552 } 553 } finally { 554 if (adbChan != null) { 555 adbChan.close(); 556 } 557 } 558 559 return true; 560 } 561 562 /** 563 * Checks to see if the first four bytes in "reply" are OKAY. 564 */ isOkay(byte[] reply)565 static boolean isOkay(byte[] reply) { 566 return reply[0] == (byte)'O' && reply[1] == (byte)'K' 567 && reply[2] == (byte)'A' && reply[3] == (byte)'Y'; 568 } 569 570 /** 571 * Converts an ADB reply to a string. 572 */ replyToString(byte[] reply)573 static String replyToString(byte[] reply) { 574 String result; 575 try { 576 result = new String(reply, DEFAULT_ENCODING); 577 } catch (UnsupportedEncodingException uee) { 578 uee.printStackTrace(); // not expected 579 result = ""; 580 } 581 return result; 582 } 583 584 /** 585 * Reads from the socket until the array is filled, or no more data is coming (because 586 * the socket closed or the timeout expired). 587 * 588 * @param chan the opened socket to read from. It must be in non-blocking 589 * mode for timeouts to work 590 * @param data the buffer to store the read data into. 591 * @return "true" if all data was read. 592 * @throws IOException 593 */ read(SocketChannel chan, byte[] data)594 static boolean read(SocketChannel chan, byte[] data) { 595 try { 596 read(chan, data, -1, DdmPreferences.getTimeOut()); 597 } catch (IOException e) { 598 Log.d("ddms", "readAll: IOException: " + e.getMessage()); 599 return false; 600 } 601 602 return true; 603 } 604 605 /** 606 * Reads from the socket until the array is filled, the optional length 607 * is reached, or no more data is coming (because the socket closed or the 608 * timeout expired). After "timeout" milliseconds since the 609 * previous successful read, this will return whether or not new data has 610 * been found. 611 * 612 * @param chan the opened socket to read from. It must be in non-blocking 613 * mode for timeouts to work 614 * @param data the buffer to store the read data into. 615 * @param length the length to read or -1 to fill the data buffer completely 616 * @param timeout The timeout value. A timeout of zero means "wait forever". 617 * @throws IOException 618 */ read(SocketChannel chan, byte[] data, int length, int timeout)619 static void read(SocketChannel chan, byte[] data, int length, int timeout) throws IOException { 620 ByteBuffer buf = ByteBuffer.wrap(data, 0, length != -1 ? length : data.length); 621 int numWaits = 0; 622 623 while (buf.position() != buf.limit()) { 624 int count; 625 626 count = chan.read(buf); 627 if (count < 0) { 628 Log.d("ddms", "read: channel EOF"); 629 throw new IOException("EOF"); 630 } else if (count == 0) { 631 // TODO: need more accurate timeout? 632 if (timeout != 0 && numWaits * WAIT_TIME > timeout) { 633 Log.d("ddms", "read: timeout"); 634 throw new IOException("timeout"); 635 } 636 // non-blocking spin 637 try { 638 Thread.sleep(WAIT_TIME); 639 } catch (InterruptedException ie) { 640 } 641 numWaits++; 642 } else { 643 numWaits = 0; 644 } 645 } 646 } 647 648 /** 649 * Write until all data in "data" is written or the connection fails. 650 * @param chan the opened socket to write to. 651 * @param data the buffer to send. 652 * @return "true" if all data was written. 653 */ write(SocketChannel chan, byte[] data)654 static boolean write(SocketChannel chan, byte[] data) { 655 try { 656 write(chan, data, -1, DdmPreferences.getTimeOut()); 657 } catch (IOException e) { 658 Log.e("ddms", e); 659 return false; 660 } 661 662 return true; 663 } 664 665 /** 666 * Write until all data in "data" is written, the optional length is reached, 667 * the timeout expires, or the connection fails. Returns "true" if all 668 * data was written. 669 * @param chan the opened socket to write to. 670 * @param data the buffer to send. 671 * @param length the length to write or -1 to send the whole buffer. 672 * @param timeout The timeout value. A timeout of zero means "wait forever". 673 * @throws IOException 674 */ write(SocketChannel chan, byte[] data, int length, int timeout)675 static void write(SocketChannel chan, byte[] data, int length, int timeout) 676 throws IOException { 677 ByteBuffer buf = ByteBuffer.wrap(data, 0, length != -1 ? length : data.length); 678 int numWaits = 0; 679 680 while (buf.position() != buf.limit()) { 681 int count; 682 683 count = chan.write(buf); 684 if (count < 0) { 685 Log.d("ddms", "write: channel EOF"); 686 throw new IOException("channel EOF"); 687 } else if (count == 0) { 688 // TODO: need more accurate timeout? 689 if (timeout != 0 && numWaits * WAIT_TIME > timeout) { 690 Log.d("ddms", "write: timeout"); 691 throw new IOException("timeout"); 692 } 693 // non-blocking spin 694 try { 695 Thread.sleep(WAIT_TIME); 696 } catch (InterruptedException ie) { 697 } 698 numWaits++; 699 } else { 700 numWaits = 0; 701 } 702 } 703 } 704 705 /** 706 * tells adb to talk to a specific device 707 * 708 * @param adbChan the socket connection to adb 709 * @param device The device to talk to. 710 * @throws IOException 711 */ setDevice(SocketChannel adbChan, Device device)712 static void setDevice(SocketChannel adbChan, Device device) 713 throws IOException { 714 // if the device is not -1, then we first tell adb we're looking to talk 715 // to a specific device 716 if (device != null) { 717 String msg = "host:transport:" + device.getSerialNumber(); //$NON-NLS-1$ 718 byte[] device_query = formAdbRequest(msg); 719 720 if (write(adbChan, device_query) == false) 721 throw new IOException("failed submitting device (" + device + 722 ") request to ADB"); 723 724 AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */); 725 if (!resp.okay) 726 throw new IOException("device (" + device + 727 ") request rejected: " + resp.message); 728 } 729 730 } 731 } 732