2 * Copyright (C) 2007 The Android Open Source Project
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
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,
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.
17 package com.android.ddmlib;
19 import com.android.ddmlib.log.LogReceiver;
21 import java.io.IOException;
22 import java.io.UnsupportedEncodingException;
23 import java.net.InetSocketAddress;
24 import java.nio.ByteBuffer;
25 import java.nio.ByteOrder;
26 import java.nio.channels.SocketChannel;
29 * Helper class to handle requests and connections to adb.
30 * <p/>{@link DebugBridgeServer} is the public API to connection to adb, while {@link AdbHelper}
31 * does the low level stuff.
32 * <p/>This currently uses spin-wait non-blocking I/O. A Selector would be more efficient,
33 * but seems like overkill for what we're doing here.
35 final class AdbHelper {
37 // public static final long kOkay = 0x59414b4fL;
38 // public static final long kFail = 0x4c494146L;
40 static final int WAIT_TIME = 5; // spin-wait sleep, in ms
42 static final String DEFAULT_ENCODING = "ISO-8859-1"; //$NON-NLS-1$
44 /** do not instantiate */
51 static class AdbResponse {
52 public AdbResponse() {
56 public boolean okay; // first 4 bytes in response were "OKAY"?
58 public String message; // diagnostic string if #okay is false
62 * Create and connect a new pass-through socket, from the host to a port on
66 * @param device the device to connect to. Can be null in which case the connection will be
67 * to the first available device.
68 * @param devicePort the port we're opening
69 * @throws TimeoutException in case of timeout on the connection.
70 * @throws IOException in case of I/O error on the connection.
72 public static SocketChannel open(InetSocketAddress adbSockAddr,
73 Device device, int devicePort) throws IOException, TimeoutException {
75 SocketChannel adbChan = SocketChannel.open(adbSockAddr);
77 adbChan.socket().setTcpNoDelay(true);
78 adbChan.configureBlocking(false);
80 // if the device is not -1, then we first tell adb we're looking to
81 // talk to a specific device
82 setDevice(adbChan, device);
84 byte[] req = createAdbForwardRequest(null, devicePort);
89 AdbResponse resp = readAdbResponse(adbChan, false);
91 throw new IOException("connection request rejected"); //$NON-NLS-1$
94 adbChan.configureBlocking(true);
95 } catch (TimeoutException e) {
98 } catch (IOException e) {
107 * Creates and connects a new pass-through socket, from the host to a port on
111 * @param device the device to connect to. Can be null in which case the connection will be
112 * to the first available device.
113 * @param pid the process pid to connect to.
114 * @throws TimeoutException in case of timeout on the connection.
115 * @throws IOException in case of I/O error on the connection.
117 public static SocketChannel createPassThroughConnection(InetSocketAddress adbSockAddr,
118 Device device, int pid) throws TimeoutException, IOException {
120 SocketChannel adbChan = SocketChannel.open(adbSockAddr);
122 adbChan.socket().setTcpNoDelay(true);
123 adbChan.configureBlocking(false);
125 // if the device is not -1, then we first tell adb we're looking to
126 // talk to a specific device
127 setDevice(adbChan, device);
129 byte[] req = createJdwpForwardRequest(pid);
134 AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
136 throw new IOException("connection request rejected: " + resp.message); //$NON-NLS-1$
139 adbChan.configureBlocking(true);
140 } catch (TimeoutException e) {
143 } catch (IOException e) {
152 * Creates a port forwarding request for adb. This returns an array
153 * containing "####tcp:{port}:{addStr}".
154 * @param addrStr the host. Can be null.
155 * @param port the port on the device. This does not need to be numeric.
157 private static byte[] createAdbForwardRequest(String addrStr, int port) {
161 reqStr = "tcp:" + port;
163 reqStr = "tcp:" + port + ":" + addrStr;
164 return formAdbRequest(reqStr);
168 * Creates a port forwarding request to a jdwp process. This returns an array
169 * containing "####jwdp:{pid}".
170 * @param pid the jdwp process pid on the device.
172 private static byte[] createJdwpForwardRequest(int pid) {
173 String reqStr = String.format("jdwp:%1$d", pid); //$NON-NLS-1$
174 return formAdbRequest(reqStr);
178 * Create an ASCII string preceeded by four hex digits. The opening "####"
179 * is the length of the rest of the string, encoded as ASCII hex (case
180 * doesn't matter). "port" and "host" are what we want to forward to. If
181 * we're on the host side connecting into the device, "addrStr" should be
184 static byte[] formAdbRequest(String req) {
185 String resultStr = String.format("%04X%s", req.length(), req); //$NON-NLS-1$
188 result = resultStr.getBytes(DEFAULT_ENCODING);
189 } catch (UnsupportedEncodingException uee) {
190 uee.printStackTrace(); // not expected
193 assert result.length == req.length() + 4;
198 * Reads the response from ADB after a command.
199 * @param chan The socket channel that is connected to adb.
200 * @param readDiagString If true, we're expecting an OKAY response to be
201 * followed by a diagnostic string. Otherwise, we only expect the
202 * diagnostic string to follow a FAIL.
203 * @throws TimeoutException in case of timeout on the connection.
204 * @throws IOException in case of I/O error on the connection.
206 static AdbResponse readAdbResponse(SocketChannel chan, boolean readDiagString)
207 throws TimeoutException, IOException {
209 AdbResponse resp = new AdbResponse();
211 byte[] reply = new byte[4];
217 readDiagString = true; // look for a reason after the FAIL
221 // not a loop -- use "while" so we can use "break"
223 while (readDiagString) {
224 // length string is in next 4 bytes
225 byte[] lenBuf = new byte[4];
228 String lenStr = replyToString(lenBuf);
232 len = Integer.parseInt(lenStr, 16);
233 } catch (NumberFormatException nfe) {
234 Log.w("ddms", "Expected digits, got '" + lenStr + "': "
235 + lenBuf[0] + " " + lenBuf[1] + " " + lenBuf[2] + " "
237 Log.w("ddms", "reply was " + replyToString(reply));
241 byte[] msg = new byte[len];
244 resp.message = replyToString(msg);
245 Log.v("ddms", "Got reply '" + replyToString(reply) + "', diag='"
246 + resp.message + "'");
250 } catch (Exception e) {
251 // ignore those, since it's just reading the diagnose string, the response will
252 // contain okay==false anyway.
259 * Retrieve the frame buffer from the device.
260 * @throws TimeoutException in case of timeout on the connection.
261 * @throws IOException in case of I/O error on the connection.
263 static RawImage getFrameBuffer(InetSocketAddress adbSockAddr, Device device)
264 throws TimeoutException, IOException {
266 RawImage imageParams = new RawImage();
267 byte[] request = formAdbRequest("framebuffer:"); //$NON-NLS-1$
273 SocketChannel adbChan = null;
275 adbChan = SocketChannel.open(adbSockAddr);
276 adbChan.configureBlocking(false);
278 // if the device is not -1, then we first tell adb we're looking to talk
279 // to a specific device
280 setDevice(adbChan, device);
282 write(adbChan, request);
284 AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
285 if (resp.okay == false) {
286 throw new IOException(resp.message);
289 // first the protocol version.
291 read(adbChan, reply);
293 ByteBuffer buf = ByteBuffer.wrap(reply);
294 buf.order(ByteOrder.LITTLE_ENDIAN);
296 int version = buf.getInt();
298 // get the header size (this is a count of int)
299 int headerSize = RawImage.getHeaderSize(version);
302 reply = new byte[headerSize * 4];
303 read(adbChan, reply);
305 buf = ByteBuffer.wrap(reply);
306 buf.order(ByteOrder.LITTLE_ENDIAN);
308 // fill the RawImage with the header
309 if (imageParams.readHeader(version, buf) == false) {
310 Log.e("Screenshot", "Unsupported protocol: " + version);
314 Log.d("ddms", "image params: bpp=" + imageParams.bpp + ", size="
315 + imageParams.size + ", width=" + imageParams.width
316 + ", height=" + imageParams.height);
318 write(adbChan, nudge);
320 reply = new byte[imageParams.size];
321 read(adbChan, reply);
323 imageParams.data = reply;
325 if (adbChan != null) {
334 * Executes a shell command on the device and retrieve the output. The output is
335 * handed to <var>rcvr</var> as it arrives.
336 * @param adbSockAddr the {@link InetSocketAddress} to adb.
337 * @param command the shell command to execute
338 * @param device the {@link IDevice} on which to execute the command.
339 * @param rcvr the {@link IShellOutputReceiver} that will receives the output of the shell
341 * @param timeout timeout value in ms for the connection. 0 means no timeout.
342 * @throws TimeoutException in case of timeout on the connection.
343 * @throws IOException in case of I/O error on the connection.
345 static void executeRemoteCommand(InetSocketAddress adbSockAddr,
346 String command, IDevice device, IShellOutputReceiver rcvr, int timeout)
347 throws TimeoutException, IOException {
348 Log.v("ddms", "execute: running " + command);
350 SocketChannel adbChan = null;
352 adbChan = SocketChannel.open(adbSockAddr);
353 adbChan.configureBlocking(false);
355 // if the device is not -1, then we first tell adb we're looking to
357 // to a specific device
358 setDevice(adbChan, device);
360 byte[] request = formAdbRequest("shell:" + command); //$NON-NLS-1$
361 write(adbChan, request);
363 AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
364 if (resp.okay == false) {
365 Log.e("ddms", "ADB rejected shell command (" + command + "): " + resp.message);
366 throw new IOException("sad result from adb: " + resp.message);
369 byte[] data = new byte[16384];
370 ByteBuffer buf = ByteBuffer.wrap(data);
371 int timeoutCount = 0;
375 if (rcvr != null && rcvr.isCancelled()) {
376 Log.v("ddms", "execute: cancelled");
380 count = adbChan.read(buf);
382 // we're at the end, we flush the output
384 Log.v("ddms", "execute '" + command + "' on '" + device + "' : EOF hit. Read: "
387 } else if (count == 0) {
389 int wait = WAIT_TIME * 5;
390 timeoutCount += wait;
391 if (timeout > 0 && timeoutCount > timeout) {
392 throw new TimeoutException();
395 } catch (InterruptedException ie) {
401 // send data to receiver if present
403 rcvr.addOutput(buf.array(), buf.arrayOffset(), buf.position());
409 if (adbChan != null) {
412 Log.v("ddms", "execute: returning");
417 * Runs the Event log service on the {@link Device}, and provides its output to the
418 * {@link LogReceiver}.
419 * <p/>This call is blocking until {@link LogReceiver#isCancelled()} returns true.
420 * @param adbSockAddr the socket address to connect to adb
421 * @param device the Device on which to run the service
422 * @param rcvr the {@link LogReceiver} to receive the log output
423 * @throws TimeoutException in case of timeout on the connection.
424 * @throws IOException in case of I/O error on the connection.
426 public static void runEventLogService(InetSocketAddress adbSockAddr, Device device,
427 LogReceiver rcvr) throws TimeoutException, IOException {
428 runLogService(adbSockAddr, device, "events", rcvr); //$NON-NLS-1$
432 * Runs a log service on the {@link Device}, and provides its output to the {@link LogReceiver}.
433 * <p/>This call is blocking until {@link LogReceiver#isCancelled()} returns true.
434 * @param adbSockAddr the socket address to connect to adb
435 * @param device the Device on which to run the service
436 * @param logName the name of the log file to output
437 * @param rcvr the {@link LogReceiver} to receive the log output
438 * @throws TimeoutException in case of timeout on the connection.
439 * @throws IOException in case of I/O error on the connection.
441 public static void runLogService(InetSocketAddress adbSockAddr, Device device, String logName,
442 LogReceiver rcvr) throws TimeoutException, IOException {
443 SocketChannel adbChan = null;
446 adbChan = SocketChannel.open(adbSockAddr);
447 adbChan.configureBlocking(false);
449 // if the device is not -1, then we first tell adb we're looking to talk
450 // to a specific device
451 setDevice(adbChan, device);
453 byte[] request = formAdbRequest("log:" + logName);
454 write(adbChan, request);
456 AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
457 if (resp.okay == false) {
458 throw new IOException("Device rejected log command: " + resp.message);
461 byte[] data = new byte[16384];
462 ByteBuffer buf = ByteBuffer.wrap(data);
466 if (rcvr != null && rcvr.isCancelled()) {
470 count = adbChan.read(buf);
473 } else if (count == 0) {
475 Thread.sleep(WAIT_TIME * 5);
476 } catch (InterruptedException ie) {
480 rcvr.parseNewData(buf.array(), buf.arrayOffset(), buf.position());
486 if (adbChan != null) {
493 * Creates a port forwarding between a local and a remote port.
494 * @param adbSockAddr the socket address to connect to adb
495 * @param device the device on which to do the port fowarding
496 * @param localPort the local port to forward
497 * @param remotePort the remote port.
498 * @return <code>true</code> if success.
499 * @throws TimeoutException in case of timeout on the connection.
500 * @throws IOException in case of I/O error on the connection.
502 public static boolean createForward(InetSocketAddress adbSockAddr, Device device, int localPort,
503 int remotePort) throws TimeoutException, IOException {
505 SocketChannel adbChan = null;
507 adbChan = SocketChannel.open(adbSockAddr);
508 adbChan.configureBlocking(false);
510 byte[] request = formAdbRequest(String.format(
511 "host-serial:%1$s:forward:tcp:%2$d;tcp:%3$d", //$NON-NLS-1$
512 device.getSerialNumber(), localPort, remotePort));
514 write(adbChan, request);
516 AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
517 if (resp.okay == false) {
518 Log.w("create-forward", "Error creating forward: " + resp.message);
522 if (adbChan != null) {
529 * Remove a port forwarding between a local and a remote port.
530 * @param adbSockAddr the socket address to connect to adb
531 * @param device the device on which to remove the port fowarding
532 * @param localPort the local port of the forward
533 * @param remotePort the remote port.
534 * @return <code>true</code> if success.
535 * @throws TimeoutException in case of timeout on the connection.
536 * @throws IOException in case of I/O error on the connection.
538 public static boolean removeForward(InetSocketAddress adbSockAddr, Device device, int localPort,
539 int remotePort) throws TimeoutException, IOException {
541 SocketChannel adbChan = null;
543 adbChan = SocketChannel.open(adbSockAddr);
544 adbChan.configureBlocking(false);
546 byte[] request = formAdbRequest(String.format(
547 "host-serial:%1$s:killforward:tcp:%2$d;tcp:%3$d", //$NON-NLS-1$
548 device.getSerialNumber(), localPort, remotePort));
550 write(adbChan, request);
552 AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
553 if (resp.okay == false) {
554 Log.w("remove-forward", "Error creating forward: " + resp.message);
558 if (adbChan != null) {
565 * Checks to see if the first four bytes in "reply" are OKAY.
567 static boolean isOkay(byte[] reply) {
568 return reply[0] == (byte)'O' && reply[1] == (byte)'K'
569 && reply[2] == (byte)'A' && reply[3] == (byte)'Y';
573 * Converts an ADB reply to a string.
575 static String replyToString(byte[] reply) {
578 result = new String(reply, DEFAULT_ENCODING);
579 } catch (UnsupportedEncodingException uee) {
580 uee.printStackTrace(); // not expected
587 * Reads from the socket until the array is filled, or no more data is coming (because
588 * the socket closed or the timeout expired).
589 * <p/>This uses the default time out value.
591 * @param chan the opened socket to read from. It must be in non-blocking
592 * mode for timeouts to work
593 * @param data the buffer to store the read data into.
594 * @throws TimeoutException in case of timeout on the connection.
595 * @throws IOException in case of I/O error on the connection.
597 static void read(SocketChannel chan, byte[] data) throws TimeoutException, IOException {
598 read(chan, data, -1, DdmPreferences.getTimeOut());
602 * Reads from the socket until the array is filled, the optional length
603 * is reached, or no more data is coming (because the socket closed or the
604 * timeout expired). After "timeout" milliseconds since the
605 * previous successful read, this will return whether or not new data has
608 * @param chan the opened socket to read from. It must be in non-blocking
609 * mode for timeouts to work
610 * @param data the buffer to store the read data into.
611 * @param length the length to read or -1 to fill the data buffer completely
612 * @param timeout The timeout value. A timeout of zero means "wait forever".
614 static void read(SocketChannel chan, byte[] data, int length, int timeout)
615 throws TimeoutException, IOException {
616 ByteBuffer buf = ByteBuffer.wrap(data, 0, length != -1 ? length : data.length);
619 while (buf.position() != buf.limit()) {
622 count = chan.read(buf);
624 Log.d("ddms", "read: channel EOF");
625 throw new IOException("EOF");
626 } else if (count == 0) {
627 // TODO: need more accurate timeout?
628 if (timeout != 0 && numWaits * WAIT_TIME > timeout) {
629 Log.d("ddms", "read: timeout");
630 throw new TimeoutException();
634 Thread.sleep(WAIT_TIME);
635 } catch (InterruptedException ie) {
645 * Write until all data in "data" is written or the connection fails or times out.
646 * <p/>This uses the default time out value.
647 * @param chan the opened socket to write to.
648 * @param data the buffer to send.
649 * @throws TimeoutException in case of timeout on the connection.
650 * @throws IOException in case of I/O error on the connection.
652 static void write(SocketChannel chan, byte[] data) throws TimeoutException, IOException {
653 write(chan, data, -1, DdmPreferences.getTimeOut());
657 * Write until all data in "data" is written, the optional length is reached,
658 * the timeout expires, or the connection fails. Returns "true" if all
660 * @param chan the opened socket to write to.
661 * @param data the buffer to send.
662 * @param length the length to write or -1 to send the whole buffer.
663 * @param timeout The timeout value. A timeout of zero means "wait forever".
664 * @throws TimeoutException in case of timeout on the connection.
665 * @throws IOException in case of I/O error on the connection.
667 static void write(SocketChannel chan, byte[] data, int length, int timeout)
668 throws TimeoutException, IOException {
669 ByteBuffer buf = ByteBuffer.wrap(data, 0, length != -1 ? length : data.length);
672 while (buf.position() != buf.limit()) {
675 count = chan.write(buf);
677 Log.d("ddms", "write: channel EOF");
678 throw new IOException("channel EOF");
679 } else if (count == 0) {
680 // TODO: need more accurate timeout?
681 if (timeout != 0 && numWaits * WAIT_TIME > timeout) {
682 Log.d("ddms", "write: timeout");
683 throw new TimeoutException();
687 Thread.sleep(WAIT_TIME);
688 } catch (InterruptedException ie) {
698 * tells adb to talk to a specific device
700 * @param adbChan the socket connection to adb
701 * @param device The device to talk to.
702 * @throws TimeoutException in case of timeout on the connection.
703 * @throws IOException in case of I/O error on the connection.
705 static void setDevice(SocketChannel adbChan, IDevice device)
706 throws TimeoutException, IOException {
707 // if the device is not -1, then we first tell adb we're looking to talk
708 // to a specific device
709 if (device != null) {
710 String msg = "host:transport:" + device.getSerialNumber(); //$NON-NLS-1$
711 byte[] device_query = formAdbRequest(msg);
713 write(adbChan, device_query);
715 AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
717 throw new IOException("device (" + device +
718 ") request rejected: " + resp.message);
726 * @param into what to reboot into (recovery, bootloader). Or null to just reboot.
728 public static void reboot(String into, InetSocketAddress adbSockAddr,
729 Device device) throws TimeoutException, IOException {
732 request = formAdbRequest("reboot:"); //$NON-NLS-1$
734 request = formAdbRequest("reboot:" + into); //$NON-NLS-1$
737 SocketChannel adbChan = null;
739 adbChan = SocketChannel.open(adbSockAddr);
740 adbChan.configureBlocking(false);
742 // if the device is not -1, then we first tell adb we're looking to talk
743 // to a specific device
744 setDevice(adbChan, device);
746 write(adbChan, request);
748 if (adbChan != null) {