FileDocCategorySizeDatePackage
AdbHelper.javaAPI DocAndroid 1.5 API25453Wed May 06 22:41:08 BST 2009com.android.ddmlib

AdbHelper

public final class AdbHelper extends Object
Helper class to handle requests and connections to adb.

{@link DebugBridgeServer} is the public API to connection to adb, while {@link AdbHelper} does the low level stuff.

This currently uses spin-wait non-blocking I/O. A Selector would be more efficient, but seems like overkill for what we're doing here.

Fields Summary
static final int
WAIT_TIME
public static final int
STD_TIMEOUT
static final String
DEFAULT_ENCODING
Constructors Summary
private AdbHelper()
do not instantiate

 //$NON-NLS-1$

        
      
    
Methods Summary
private static byte[]createAdbForwardRequest(java.lang.String addrStr, int port)
Creates a port forwarding request for adb. This returns an array containing "####tcp:{port}:{addStr}".

param
addrStr the host. Can be null.
param
port the port on the device. This does not need to be numeric.

        String reqStr;

        if (addrStr == null)
            reqStr = "tcp:" + port;
        else
            reqStr = "tcp:" + port + ":" + addrStr;
        return formAdbRequest(reqStr);
    
public static booleancreateForward(java.net.InetSocketAddress adbSockAddr, Device device, int localPort, int remotePort)
Creates a port forwarding between a local and a remote port.

param
adbSockAddr the socket address to connect to adb
param
device the device on which to do the port fowarding
param
localPort the local port to forward
param
remotePort the remote port.
return
true if success.
throws
IOException


        SocketChannel adbChan = null;
        try {
            adbChan = SocketChannel.open(adbSockAddr);
            adbChan.configureBlocking(false);
    
            byte[] request = formAdbRequest(String.format(
                    "host-serial:%1$s:forward:tcp:%2$d;tcp:%3$d", //$NON-NLS-1$
                    device.serialNumber, localPort, remotePort));
    
            if (write(adbChan, request) == false) {
                throw new IOException("failed to submit the forward command.");
            }
            
            AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
            if (!resp.ioSuccess || !resp.okay) {
                throw new IOException("Device rejected command: " + resp.message);
            }
        } finally {
            if (adbChan != null) {
                adbChan.close();
            }
        }
        
        return true;
    
private static byte[]createJdwpForwardRequest(int pid)
Creates a port forwarding request to a jdwp process. This returns an array containing "####jwdp:{pid}".

param
pid the jdwp process pid on the device.

        String reqStr = String.format("jdwp:%1$d", pid); //$NON-NLS-1$
        return formAdbRequest(reqStr);
    
public static java.nio.channels.SocketChannelcreatePassThroughConnection(java.net.InetSocketAddress adbSockAddr, Device device, int pid)
Creates and connects a new pass-through socket, from the host to a port on the device.

param
adbSockAddr
param
device the device to connect to. Can be null in which case the connection will be to the first available device.
param
pid the process pid to connect to.


        SocketChannel adbChan = SocketChannel.open(adbSockAddr);
        try {
            adbChan.socket().setTcpNoDelay(true);
            adbChan.configureBlocking(false);

            // if the device is not -1, then we first tell adb we're looking to
            // talk to a specific device
            setDevice(adbChan, device);

            byte[] req = createJdwpForwardRequest(pid);
            // Log.hexDump(req);

            if (write(adbChan, req) == false)
                throw new IOException("failed submitting request to ADB"); //$NON-NLS-1$

            AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
            if (!resp.okay)
                throw new IOException("connection request rejected: " + resp.message); //$NON-NLS-1$

            adbChan.configureBlocking(true);
        } catch (IOException ioe) {
            adbChan.close();
            throw ioe;
        }

        return adbChan;
    
public static voidexecuteRemoteCommand(java.net.InetSocketAddress adbSockAddr, java.lang.String command, Device device, IShellOutputReceiver rcvr)
Execute a command on the device and retrieve the output. The output is handed to "rcvr" as it arrives.

        Log.v("ddms", "execute: running " + command);

        SocketChannel adbChan = null;
        try {
            adbChan = SocketChannel.open(adbSockAddr);
            adbChan.configureBlocking(false);

            // if the device is not -1, then we first tell adb we're looking to
            // talk
            // to a specific device
            setDevice(adbChan, device);

            byte[] request = formAdbRequest("shell:" + command); //$NON-NLS-1$
            if (write(adbChan, request) == false)
                throw new IOException("failed submitting shell command");

            AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
            if (!resp.ioSuccess || !resp.okay) {
                Log.e("ddms", "ADB rejected shell command (" + command + "): " + resp.message);
                throw new IOException("sad result from adb: " + resp.message);
            }

            byte[] data = new byte[16384];
            ByteBuffer buf = ByteBuffer.wrap(data);
            while (true) {
                int count;

                if (rcvr != null && rcvr.isCancelled()) {
                    Log.v("ddms", "execute: cancelled");
                    break;
                }

                count = adbChan.read(buf);
                if (count < 0) {
                    // we're at the end, we flush the output
                    rcvr.flush();
                    Log.v("ddms", "execute '" + command + "' on '" + device + "' : EOF hit. Read: "
                            + count);
                    break;
                } else if (count == 0) {
                    try {
                        Thread.sleep(WAIT_TIME * 5);
                    } catch (InterruptedException ie) {
                    }
                } else {
                    if (rcvr != null) {
                        rcvr.addOutput(buf.array(), buf.arrayOffset(), buf.position());
                    }
                    buf.rewind();
                }
            }
        } finally {
            if (adbChan != null) {
                adbChan.close();
            }
            Log.v("ddms", "execute: returning");
        }
    
static byte[]formAdbRequest(java.lang.String req)
Create an ASCII string preceeded by four hex digits. The opening "####" is the length of the rest of the string, encoded as ASCII hex (case doesn't matter). "port" and "host" are what we want to forward to. If we're on the host side connecting into the device, "addrStr" should be null.

        String resultStr = String.format("%04X%s", req.length(), req); //$NON-NLS-1$
        byte[] result;
        try {
            result = resultStr.getBytes(DEFAULT_ENCODING);
        } catch (UnsupportedEncodingException uee) {
            uee.printStackTrace(); // not expected
            return null;
        }
        assert result.length == req.length() + 4;
        return result;
    
public static RawImagegetFrameBuffer(java.net.InetSocketAddress adbSockAddr, Device device)
Retrieve the frame buffer from the device.


        RawImage imageParams = new RawImage();
        byte[] request = formAdbRequest("framebuffer:"); //$NON-NLS-1$
        byte[] nudge = {
            0
        };
        byte[] reply;

        SocketChannel adbChan = null;
        try {
            adbChan = SocketChannel.open(adbSockAddr);
            adbChan.configureBlocking(false);
    
            // if the device is not -1, then we first tell adb we're looking to talk
            // to a specific device
            setDevice(adbChan, device);
    
            if (write(adbChan, request) == false)
                throw new IOException("failed asking for frame buffer");
    
            AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
            if (!resp.ioSuccess || !resp.okay) {
                Log.w("ddms", "Got timeout or unhappy response from ADB fb req: "
                        + resp.message);
                adbChan.close();
                return null;
            }
    
            reply = new byte[16];
            if (read(adbChan, reply) == false) {
                Log.w("ddms", "got partial reply from ADB fb:");
                Log.hexDump("ddms", LogLevel.WARN, reply, 0, reply.length);
                adbChan.close();
                return null;
            }
            ByteBuffer buf = ByteBuffer.wrap(reply);
            buf.order(ByteOrder.LITTLE_ENDIAN);
    
            imageParams.bpp = buf.getInt();
            imageParams.size = buf.getInt();
            imageParams.width = buf.getInt();
            imageParams.height = buf.getInt();
    
            Log.d("ddms", "image params: bpp=" + imageParams.bpp + ", size="
                    + imageParams.size + ", width=" + imageParams.width
                    + ", height=" + imageParams.height);
    
            if (write(adbChan, nudge) == false)
                throw new IOException("failed nudging");
    
            reply = new byte[imageParams.size];
            if (read(adbChan, reply) == false) {
                Log.w("ddms", "got truncated reply from ADB fb data");
                adbChan.close();
                return null;
            }
            imageParams.data = reply;
        } finally {
            if (adbChan != null) {
                adbChan.close();
            }
        }

        return imageParams;
    
static booleanisOkay(byte[] reply)
Checks to see if the first four bytes in "reply" are OKAY.

        return reply[0] == (byte)'O" && reply[1] == (byte)'K"
                && reply[2] == (byte)'A" && reply[3] == (byte)'Y";
    
public static java.nio.channels.SocketChannelopen(java.net.InetSocketAddress adbSockAddr, Device device, int devicePort)
Create and connect a new pass-through socket, from the host to a port on the device.

param
adbSockAddr
param
device the device to connect to. Can be null in which case the connection will be to the first available device.
param
devicePort the port we're opening


        SocketChannel adbChan = SocketChannel.open(adbSockAddr);
        try {
            adbChan.socket().setTcpNoDelay(true);
            adbChan.configureBlocking(false);

            // if the device is not -1, then we first tell adb we're looking to
            // talk to a specific device
            setDevice(adbChan, device);

            byte[] req = createAdbForwardRequest(null, devicePort);
            // Log.hexDump(req);

            if (write(adbChan, req) == false)
                throw new IOException("failed submitting request to ADB"); //$NON-NLS-1$

            AdbResponse resp = readAdbResponse(adbChan, false);
            if (!resp.okay)
                throw new IOException("connection request rejected"); //$NON-NLS-1$

            adbChan.configureBlocking(true);
        } catch (IOException ioe) {
            adbChan.close();
            throw ioe;
        }

        return adbChan;
    
static booleanread(java.nio.channels.SocketChannel chan, byte[] data)
Reads from the socket until the array is filled, or no more data is coming (because the socket closed or the timeout expired).

param
chan the opened socket to read from. It must be in non-blocking mode for timeouts to work
param
data the buffer to store the read data into.
return
"true" if all data was read.
throws
IOException

       try {
           read(chan, data, -1, STD_TIMEOUT);
       } catch (IOException e) {
           Log.d("ddms", "readAll: IOException: " + e.getMessage());
           return false;
       }
       
       return true;
    
static voidread(java.nio.channels.SocketChannel chan, byte[] data, int length, int timeout)
Reads from the socket until the array is filled, the optional length is reached, or no more data is coming (because the socket closed or the timeout expired). After "timeout" milliseconds since the previous successful read, this will return whether or not new data has been found.

param
chan the opened socket to read from. It must be in non-blocking mode for timeouts to work
param
data the buffer to store the read data into.
param
length the length to read or -1 to fill the data buffer completely
param
timeout The timeout value. A timeout of zero means "wait forever".
throws
IOException

        ByteBuffer buf = ByteBuffer.wrap(data, 0, length != -1 ? length : data.length);
        int numWaits = 0;

        while (buf.position() != buf.limit()) {
            int count;

            count = chan.read(buf);
            if (count < 0) {
                Log.d("ddms", "read: channel EOF");
                throw new IOException("EOF");
            } else if (count == 0) {
                // TODO: need more accurate timeout?
                if (timeout != 0 && numWaits * WAIT_TIME > timeout) {
                    Log.i("ddms", "read: timeout");
                    throw new IOException("timeout");
                }
                // non-blocking spin
                try {
                    Thread.sleep(WAIT_TIME);
                } catch (InterruptedException ie) {
                }
                numWaits++;
            } else {
                numWaits = 0;
            }
        }
    
static com.android.ddmlib.AdbHelper$AdbResponsereadAdbResponse(java.nio.channels.SocketChannel chan, boolean readDiagString)
Reads the response from ADB after a command.

param
chan The socket channel that is connected to adb.
param
readDiagString If true, we're expecting an OKAY response to be followed by a diagnostic string. Otherwise, we only expect the diagnostic string to follow a FAIL.


        AdbResponse resp = new AdbResponse();

        byte[] reply = new byte[4];
        if (read(chan, reply) == false) {
            return resp;
        }
        resp.ioSuccess = true;

        if (isOkay(reply)) {
            resp.okay = true;
        } else {
            readDiagString = true; // look for a reason after the FAIL
            resp.okay = false;
        }

        // not a loop -- use "while" so we can use "break"
        while (readDiagString) {
            // length string is in next 4 bytes
            byte[] lenBuf = new byte[4];
            if (read(chan, lenBuf) == false) {
                Log.w("ddms", "Expected diagnostic string not found");
                break;
            }

            String lenStr = replyToString(lenBuf);

            int len;
            try {
                len = Integer.parseInt(lenStr, 16);
            } catch (NumberFormatException nfe) {
                Log.w("ddms", "Expected digits, got '" + lenStr + "': "
                        + lenBuf[0] + " " + lenBuf[1] + " " + lenBuf[2] + " "
                        + lenBuf[3]);
                Log.w("ddms", "reply was " + replyToString(reply));
                break;
            }

            byte[] msg = new byte[len];
            if (read(chan, msg) == false) {
                Log.w("ddms", "Failed reading diagnostic string, len=" + len);
                break;
            }

            resp.message = replyToString(msg);
            Log.v("ddms", "Got reply '" + replyToString(reply) + "', diag='"
                    + resp.message + "'");

            break;
        }

        return resp;
    
public static booleanremoveForward(java.net.InetSocketAddress adbSockAddr, Device device, int localPort, int remotePort)
Remove a port forwarding between a local and a remote port.

param
adbSockAddr the socket address to connect to adb
param
device the device on which to remove the port fowarding
param
localPort the local port of the forward
param
remotePort the remote port.
return
true if success.
throws
IOException


        SocketChannel adbChan = null;
        try {
            adbChan = SocketChannel.open(adbSockAddr);
            adbChan.configureBlocking(false);
    
            byte[] request = formAdbRequest(String.format(
                    "host-serial:%1$s:killforward:tcp:%2$d;tcp:%3$d", //$NON-NLS-1$
                    device.serialNumber, localPort, remotePort));
    
            if (!write(adbChan, request)) {
                throw new IOException("failed to submit the remove forward command.");
            }
    
            AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
            if (!resp.ioSuccess || !resp.okay) {
                throw new IOException("Device rejected command: " + resp.message);
            }
        } finally {
            if (adbChan != null) {
                adbChan.close();
            }
        }

        return true;
    
static java.lang.StringreplyToString(byte[] reply)
Converts an ADB reply to a string.

        String result;
        try {
            result = new String(reply, DEFAULT_ENCODING);
        } catch (UnsupportedEncodingException uee) {
            uee.printStackTrace(); // not expected
            result = "";
        }
        return result;
    
public static voidrunEventLogService(java.net.InetSocketAddress adbSockAddr, Device device, com.android.ddmlib.log.LogReceiver rcvr)
Runs the Event log service on the {@link Device}, and provides its output to the {@link LogReceiver}.

param
adbSockAddr the socket address to connect to adb
param
device the Device on which to run the service
param
rcvr the {@link LogReceiver} to receive the log output
throws
IOException

        runLogService(adbSockAddr, device, "events", rcvr); //$NON-NLS-1$
    
public static voidrunLogService(java.net.InetSocketAddress adbSockAddr, Device device, java.lang.String logName, com.android.ddmlib.log.LogReceiver rcvr)
Runs a log service on the {@link Device}, and provides its output to the {@link LogReceiver}.

param
adbSockAddr the socket address to connect to adb
param
device the Device on which to run the service
param
logName the name of the log file to output
param
rcvr the {@link LogReceiver} to receive the log output
throws
IOException

        SocketChannel adbChan = null;
        
        try {
            adbChan = SocketChannel.open(adbSockAddr);
            adbChan.configureBlocking(false);
    
            // if the device is not -1, then we first tell adb we're looking to talk
            // to a specific device
            setDevice(adbChan, device);
    
            byte[] request = formAdbRequest("log:" + logName);
            if (write(adbChan, request) == false) {
                throw new IOException("failed to submit the log command");
            }
    
            AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
            if (!resp.ioSuccess || !resp.okay) {
                throw new IOException("Device rejected log command: " + resp.message);
            }
    
            byte[] data = new byte[16384];
            ByteBuffer buf = ByteBuffer.wrap(data);
            while (true) {
                int count;
    
                if (rcvr != null && rcvr.isCancelled()) {
                    break;
                }
    
                count = adbChan.read(buf);
                if (count < 0) {
                    break;
                } else if (count == 0) {
                    try {
                        Thread.sleep(WAIT_TIME * 5);
                    } catch (InterruptedException ie) {
                    }
                } else {
                    if (rcvr != null) {
                        rcvr.parseNewData(buf.array(), buf.arrayOffset(), buf.position());
                    }
                    buf.rewind();
                }
            }
        } finally {
            if (adbChan != null) {
                adbChan.close();
            }
        }
    
static voidsetDevice(java.nio.channels.SocketChannel adbChan, Device device)
tells adb to talk to a specific device

param
adbChan the socket connection to adb
param
device The device to talk to.
throws
IOException

        // if the device is not -1, then we first tell adb we're looking to talk
        // to a specific device
        if (device != null) {
            String msg = "host:transport:" + device.serialNumber; //$NON-NLS-1$
            byte[] device_query = formAdbRequest(msg);

            if (write(adbChan, device_query) == false)
                throw new IOException("failed submitting device (" + device +
                        ") request to ADB");

            AdbResponse resp = readAdbResponse(adbChan, false /* readDiagString */);
            if (!resp.okay)
                throw new IOException("device (" + device +
                        ") request rejected: " + resp.message);
        }

    
static booleanwrite(java.nio.channels.SocketChannel chan, byte[] data)
Write until all data in "data" is written or the connection fails.

param
chan the opened socket to write to.
param
data the buffer to send.
return
"true" if all data was written.

        try {
            write(chan, data, -1, STD_TIMEOUT);
        } catch (IOException e) {
            Log.e("ddms", e);
            return false;
        }

        return true;
    
static voidwrite(java.nio.channels.SocketChannel chan, byte[] data, int length, int timeout)
Write until all data in "data" is written, the optional length is reached, the timeout expires, or the connection fails. Returns "true" if all data was written.

param
chan the opened socket to write to.
param
data the buffer to send.
param
length the length to write or -1 to send the whole buffer.
param
timeout The timeout value. A timeout of zero means "wait forever".
throws
IOException

        ByteBuffer buf = ByteBuffer.wrap(data, 0, length != -1 ? length : data.length);
        int numWaits = 0;

        while (buf.position() != buf.limit()) {
            int count;

            count = chan.write(buf);
            if (count < 0) {
                Log.d("ddms", "write: channel EOF");
                throw new IOException("channel EOF");
            } else if (count == 0) {
                // TODO: need more accurate timeout?
                if (timeout != 0 && numWaits * WAIT_TIME > timeout) {
                    Log.i("ddms", "write: timeout");
                    throw new IOException("timeout");
                }
                // non-blocking spin
                try {
                    Thread.sleep(WAIT_TIME);
                } catch (InterruptedException ie) {
                }
                numWaits++;
            } else {
                numWaits = 0;
            }
        }