auto import from //depot/cupcake/@135843
This commit is contained in:
11
tools/ddms/libs/ddmlib/src/Android.mk
Normal file
11
tools/ddms/libs/ddmlib/src/Android.mk
Normal file
@@ -0,0 +1,11 @@
|
||||
# Copyright 2007 The Android Open Source Project
|
||||
#
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||
|
||||
LOCAL_MODULE := ddmlib
|
||||
|
||||
include $(BUILD_HOST_JAVA_LIBRARY)
|
||||
|
||||
714
tools/ddms/libs/ddmlib/src/com/android/ddmlib/AdbHelper.java
Normal file
714
tools/ddms/libs/ddmlib/src/com/android/ddmlib/AdbHelper.java
Normal file
@@ -0,0 +1,714 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import com.android.ddmlib.Log.LogLevel;
|
||||
import com.android.ddmlib.log.LogReceiver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
/**
|
||||
* Helper class to handle requests and connections to adb.
|
||||
* <p/>{@link DebugBridgeServer} is the public API to connection to adb, while {@link AdbHelper}
|
||||
* does the low level stuff.
|
||||
* <p/>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.
|
||||
*/
|
||||
final class AdbHelper {
|
||||
|
||||
// public static final long kOkay = 0x59414b4fL;
|
||||
// public static final long kFail = 0x4c494146L;
|
||||
|
||||
static final int WAIT_TIME = 5; // spin-wait sleep, in ms
|
||||
|
||||
public static final int STD_TIMEOUT = 5000; // standard delay, in ms
|
||||
|
||||
static final String DEFAULT_ENCODING = "ISO-8859-1"; //$NON-NLS-1$
|
||||
|
||||
/** do not instantiate */
|
||||
private AdbHelper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response from ADB.
|
||||
*/
|
||||
static class AdbResponse {
|
||||
public AdbResponse() {
|
||||
// ioSuccess = okay = timeout = false;
|
||||
message = "";
|
||||
}
|
||||
|
||||
public boolean ioSuccess; // read all expected data, no timeoutes
|
||||
|
||||
public boolean okay; // first 4 bytes in response were "OKAY"?
|
||||
|
||||
public boolean timeout; // TODO: implement
|
||||
|
||||
public String message; // diagnostic string
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static SocketChannel open(InetSocketAddress adbSockAddr,
|
||||
Device device, int devicePort) throws IOException {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public static SocketChannel createPassThroughConnection(InetSocketAddress adbSockAddr,
|
||||
Device device, int pid) throws IOException {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private static byte[] createAdbForwardRequest(String addrStr, int port) {
|
||||
String reqStr;
|
||||
|
||||
if (addrStr == null)
|
||||
reqStr = "tcp:" + port;
|
||||
else
|
||||
reqStr = "tcp:" + port + ":" + addrStr;
|
||||
return formAdbRequest(reqStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private static byte[] createJdwpForwardRequest(int pid) {
|
||||
String reqStr = String.format("jdwp:%1$d", pid); //$NON-NLS-1$
|
||||
return formAdbRequest(reqStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static byte[] formAdbRequest(String req) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static AdbResponse readAdbResponse(SocketChannel chan, boolean readDiagString)
|
||||
throws IOException {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the frame buffer from the device.
|
||||
*/
|
||||
public static RawImage getFrameBuffer(InetSocketAddress adbSockAddr, Device device)
|
||||
throws IOException {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command on the device and retrieve the output. The output is
|
||||
* handed to "rcvr" as it arrives.
|
||||
*/
|
||||
public static void executeRemoteCommand(InetSocketAddress adbSockAddr,
|
||||
String command, Device device, IShellOutputReceiver rcvr)
|
||||
throws IOException {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static void runEventLogService(InetSocketAddress adbSockAddr, Device device,
|
||||
LogReceiver rcvr) throws IOException {
|
||||
runLogService(adbSockAddr, device, "events", rcvr); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static void runLogService(InetSocketAddress adbSockAddr, Device device, String logName,
|
||||
LogReceiver rcvr) 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <code>true</code> if success.
|
||||
* @throws IOException
|
||||
*/
|
||||
public static boolean createForward(InetSocketAddress adbSockAddr, Device device, int localPort,
|
||||
int remotePort) 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <code>true</code> if success.
|
||||
* @throws IOException
|
||||
*/
|
||||
public static boolean removeForward(InetSocketAddress adbSockAddr, Device device, int localPort,
|
||||
int remotePort) 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if the first four bytes in "reply" are OKAY.
|
||||
*/
|
||||
static boolean isOkay(byte[] reply) {
|
||||
return reply[0] == (byte)'O' && reply[1] == (byte)'K'
|
||||
&& reply[2] == (byte)'A' && reply[3] == (byte)'Y';
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an ADB reply to a string.
|
||||
*/
|
||||
static String replyToString(byte[] reply) {
|
||||
String result;
|
||||
try {
|
||||
result = new String(reply, DEFAULT_ENCODING);
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
uee.printStackTrace(); // not expected
|
||||
result = "";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
static boolean read(SocketChannel chan, byte[] data) {
|
||||
try {
|
||||
read(chan, data, -1, STD_TIMEOUT);
|
||||
} catch (IOException e) {
|
||||
Log.d("ddms", "readAll: IOException: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
static void read(SocketChannel chan, byte[] data, int length, int timeout) 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static boolean write(SocketChannel chan, byte[] data) {
|
||||
try {
|
||||
write(chan, data, -1, STD_TIMEOUT);
|
||||
} catch (IOException e) {
|
||||
Log.e("ddms", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
static void write(SocketChannel chan, byte[] data, int length, int timeout)
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tells adb to talk to a specific device
|
||||
*
|
||||
* @param adbChan the socket connection to adb
|
||||
* @param device The device to talk to.
|
||||
* @throws IOException
|
||||
*/
|
||||
static void setDevice(SocketChannel adbChan, Device device)
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
/**
|
||||
* Holds an Allocation information.
|
||||
*/
|
||||
public class AllocationInfo implements Comparable<AllocationInfo>, IStackTraceInfo {
|
||||
private String mAllocatedClass;
|
||||
private int mAllocationSize;
|
||||
private short mThreadId;
|
||||
private StackTraceElement[] mStackTrace;
|
||||
|
||||
/*
|
||||
* Simple constructor.
|
||||
*/
|
||||
AllocationInfo(String allocatedClass, int allocationSize,
|
||||
short threadId, StackTraceElement[] stackTrace) {
|
||||
mAllocatedClass = allocatedClass;
|
||||
mAllocationSize = allocationSize;
|
||||
mThreadId = threadId;
|
||||
mStackTrace = stackTrace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the allocated class.
|
||||
*/
|
||||
public String getAllocatedClass() {
|
||||
return mAllocatedClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the allocation.
|
||||
*/
|
||||
public int getSize() {
|
||||
return mAllocationSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the id of the thread that performed the allocation.
|
||||
*/
|
||||
public short getThreadId() {
|
||||
return mThreadId;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IStackTraceInfo#getStackTrace()
|
||||
*/
|
||||
public StackTraceElement[] getStackTrace() {
|
||||
return mStackTrace;
|
||||
}
|
||||
|
||||
public int compareTo(AllocationInfo otherAlloc) {
|
||||
return otherAlloc.mAllocationSize - mAllocationSize;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
||||
/* //device/tools/ddms/libs/ddmlib/src/com/android/ddmlib/BadPacketException.java
|
||||
**
|
||||
** Copyright 2007, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
/**
|
||||
* Thrown if the contents of a packet are bad.
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
class BadPacketException extends RuntimeException {
|
||||
public BadPacketException()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public BadPacketException(String msg)
|
||||
{
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
222
tools/ddms/libs/ddmlib/src/com/android/ddmlib/ChunkHandler.java
Normal file
222
tools/ddms/libs/ddmlib/src/com/android/ddmlib/ChunkHandler.java
Normal file
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Subclass this with a class that handles one or more chunk types.
|
||||
*/
|
||||
abstract class ChunkHandler {
|
||||
|
||||
public static final int CHUNK_HEADER_LEN = 8; // 4-byte type, 4-byte len
|
||||
public static final ByteOrder CHUNK_ORDER = ByteOrder.BIG_ENDIAN;
|
||||
|
||||
public static final int CHUNK_FAIL = type("FAIL");
|
||||
|
||||
ChunkHandler() {}
|
||||
|
||||
/**
|
||||
* Client is ready. The monitor thread calls this method on all
|
||||
* handlers when the client is determined to be DDM-aware (usually
|
||||
* after receiving a HELO response.)
|
||||
*
|
||||
* The handler can use this opportunity to initialize client-side
|
||||
* activity. Because there's a fair chance we'll want to send a
|
||||
* message to the client, this method can throw an IOException.
|
||||
*/
|
||||
abstract void clientReady(Client client) throws IOException;
|
||||
|
||||
/**
|
||||
* Client has gone away. Can be used to clean up any resources
|
||||
* associated with this client connection.
|
||||
*/
|
||||
abstract void clientDisconnected(Client client);
|
||||
|
||||
/**
|
||||
* Handle an incoming chunk. The data, of chunk type "type", begins
|
||||
* at the start of "data" and continues to data.limit().
|
||||
*
|
||||
* If "isReply" is set, then "msgId" will be the ID of the request
|
||||
* we sent to the client. Otherwise, it's the ID generated by the
|
||||
* client for this event. Note that it's possible to receive chunks
|
||||
* in reply packets for which we are not registered.
|
||||
*
|
||||
* The handler may not modify the contents of "data".
|
||||
*/
|
||||
abstract void handleChunk(Client client, int type,
|
||||
ByteBuffer data, boolean isReply, int msgId);
|
||||
|
||||
/**
|
||||
* Handle chunks not recognized by handlers. The handleChunk() method
|
||||
* in sub-classes should call this if the chunk type isn't recognized.
|
||||
*/
|
||||
protected void handleUnknownChunk(Client client, int type,
|
||||
ByteBuffer data, boolean isReply, int msgId) {
|
||||
if (type == CHUNK_FAIL) {
|
||||
int errorCode, msgLen;
|
||||
String msg;
|
||||
|
||||
errorCode = data.getInt();
|
||||
msgLen = data.getInt();
|
||||
msg = getString(data, msgLen);
|
||||
Log.w("ddms", "WARNING: failure code=" + errorCode + " msg=" + msg);
|
||||
} else {
|
||||
Log.w("ddms", "WARNING: received unknown chunk " + name(type)
|
||||
+ ": len=" + data.limit() + ", reply=" + isReply
|
||||
+ ", msgId=0x" + Integer.toHexString(msgId));
|
||||
}
|
||||
Log.w("ddms", " client " + client + ", handler " + this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Utility function to copy a String out of a ByteBuffer.
|
||||
*
|
||||
* This is here because multiple chunk handlers can make use of it,
|
||||
* and there's nowhere better to put it.
|
||||
*/
|
||||
static String getString(ByteBuffer buf, int len) {
|
||||
char[] data = new char[len];
|
||||
for (int i = 0; i < len; i++)
|
||||
data[i] = buf.getChar();
|
||||
return new String(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to copy a String into a ByteBuffer.
|
||||
*/
|
||||
static void putString(ByteBuffer buf, String str) {
|
||||
int len = str.length();
|
||||
for (int i = 0; i < len; i++)
|
||||
buf.putChar(str.charAt(i));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a 4-character string to a 32-bit type.
|
||||
*/
|
||||
static int type(String typeName) {
|
||||
int val = 0;
|
||||
|
||||
if (typeName.length() != 4) {
|
||||
Log.e("ddms", "Type name must be 4 letter long");
|
||||
throw new RuntimeException("Type name must be 4 letter long");
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
val <<= 8;
|
||||
val |= (byte) typeName.charAt(i);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an integer type to a 4-character string.
|
||||
*/
|
||||
static String name(int type) {
|
||||
char[] ascii = new char[4];
|
||||
|
||||
ascii[0] = (char) ((type >> 24) & 0xff);
|
||||
ascii[1] = (char) ((type >> 16) & 0xff);
|
||||
ascii[2] = (char) ((type >> 8) & 0xff);
|
||||
ascii[3] = (char) (type & 0xff);
|
||||
|
||||
return new String(ascii);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a ByteBuffer with enough space to hold the JDWP packet
|
||||
* header and one chunk header in addition to the demands of the
|
||||
* chunk being created.
|
||||
*
|
||||
* "maxChunkLen" indicates the size of the chunk contents only.
|
||||
*/
|
||||
static ByteBuffer allocBuffer(int maxChunkLen) {
|
||||
ByteBuffer buf =
|
||||
ByteBuffer.allocate(JdwpPacket.JDWP_HEADER_LEN + 8 +maxChunkLen);
|
||||
buf.order(CHUNK_ORDER);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the slice of the JDWP packet buffer that holds just the
|
||||
* chunk data.
|
||||
*/
|
||||
static ByteBuffer getChunkDataBuf(ByteBuffer jdwpBuf) {
|
||||
ByteBuffer slice;
|
||||
|
||||
assert jdwpBuf.position() == 0;
|
||||
|
||||
jdwpBuf.position(JdwpPacket.JDWP_HEADER_LEN + CHUNK_HEADER_LEN);
|
||||
slice = jdwpBuf.slice();
|
||||
slice.order(CHUNK_ORDER);
|
||||
jdwpBuf.position(0);
|
||||
|
||||
return slice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the chunk header at the start of the chunk.
|
||||
*
|
||||
* Pass in the byte buffer returned by JdwpPacket.getPayload().
|
||||
*/
|
||||
static void finishChunkPacket(JdwpPacket packet, int type, int chunkLen) {
|
||||
ByteBuffer buf = packet.getPayload();
|
||||
|
||||
buf.putInt(0x00, type);
|
||||
buf.putInt(0x04, chunkLen);
|
||||
|
||||
packet.finishPacket(CHUNK_HEADER_LEN + chunkLen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the client is opened with the proper debugger port for the
|
||||
* specified application name, and if not, reopen it.
|
||||
* @param client
|
||||
* @param uiThread
|
||||
* @param appName
|
||||
* @return
|
||||
*/
|
||||
protected static Client checkDebuggerPortForAppName(Client client, String appName) {
|
||||
IDebugPortProvider provider = DebugPortManager.getProvider();
|
||||
if (provider != null) {
|
||||
Device device = client.getDevice();
|
||||
int newPort = provider.getPort(device, appName);
|
||||
|
||||
if (newPort != IDebugPortProvider.NO_STATIC_PORT &&
|
||||
newPort != client.getDebuggerListenPort()) {
|
||||
|
||||
AndroidDebugBridge bridge = AndroidDebugBridge.getBridge();
|
||||
if (bridge != null) {
|
||||
DeviceMonitor deviceMonitor = bridge.getDeviceMonitor();
|
||||
if (deviceMonitor != null) {
|
||||
deviceMonitor.addClientToDropAndReopen(client, newPort);
|
||||
client = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
768
tools/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java
Normal file
768
tools/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java
Normal file
@@ -0,0 +1,768 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
|
||||
import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* This represents a single client, usually a DAlvik VM process.
|
||||
* <p/>This class gives access to basic client information, as well as methods to perform actions
|
||||
* on the client.
|
||||
* <p/>More detailed information, usually updated in real time, can be access through the
|
||||
* {@link ClientData} class. Each <code>Client</code> object has its own <code>ClientData</code>
|
||||
* accessed through {@link #getClientData()}.
|
||||
*/
|
||||
public class Client {
|
||||
|
||||
private static final int SERVER_PROTOCOL_VERSION = 1;
|
||||
|
||||
/** Client change bit mask: application name change */
|
||||
public static final int CHANGE_NAME = 0x0001;
|
||||
/** Client change bit mask: debugger interest change */
|
||||
public static final int CHANGE_DEBUGGER_INTEREST = 0x0002;
|
||||
/** Client change bit mask: debugger port change */
|
||||
public static final int CHANGE_PORT = 0x0004;
|
||||
/** Client change bit mask: thread update flag change */
|
||||
public static final int CHANGE_THREAD_MODE = 0x0008;
|
||||
/** Client change bit mask: thread data updated */
|
||||
public static final int CHANGE_THREAD_DATA = 0x0010;
|
||||
/** Client change bit mask: heap update flag change */
|
||||
public static final int CHANGE_HEAP_MODE = 0x0020;
|
||||
/** Client change bit mask: head data updated */
|
||||
public static final int CHANGE_HEAP_DATA = 0x0040;
|
||||
/** Client change bit mask: native heap data updated */
|
||||
public static final int CHANGE_NATIVE_HEAP_DATA = 0x0080;
|
||||
/** Client change bit mask: thread stack trace updated */
|
||||
public static final int CHANGE_THREAD_STACKTRACE = 0x0100;
|
||||
/** Client change bit mask: allocation information updated */
|
||||
public static final int CHANGE_HEAP_ALLOCATIONS = 0x0200;
|
||||
/** Client change bit mask: allocation information updated */
|
||||
public static final int CHANGE_HEAP_ALLOCATION_STATUS = 0x0400;
|
||||
|
||||
/** Client change bit mask: combination of {@link Client#CHANGE_NAME},
|
||||
* {@link Client#CHANGE_DEBUGGER_INTEREST}, and {@link Client#CHANGE_PORT}.
|
||||
*/
|
||||
public static final int CHANGE_INFO = CHANGE_NAME | CHANGE_DEBUGGER_INTEREST | CHANGE_PORT;
|
||||
|
||||
private SocketChannel mChan;
|
||||
|
||||
// debugger we're associated with, if any
|
||||
private Debugger mDebugger;
|
||||
private int mDebuggerListenPort;
|
||||
|
||||
// list of IDs for requests we have sent to the client
|
||||
private HashMap<Integer,ChunkHandler> mOutstandingReqs;
|
||||
|
||||
// chunk handlers stash state data in here
|
||||
private ClientData mClientData;
|
||||
|
||||
// User interface state. Changing the value causes a message to be
|
||||
// sent to the client.
|
||||
private boolean mThreadUpdateEnabled;
|
||||
private boolean mHeapUpdateEnabled;
|
||||
|
||||
/*
|
||||
* Read/write buffers. We can get large quantities of data from the
|
||||
* client, e.g. the response to a "give me the list of all known classes"
|
||||
* request from the debugger. Requests from the debugger, and from us,
|
||||
* are much smaller.
|
||||
*
|
||||
* Pass-through debugger traffic is sent without copying. "mWriteBuffer"
|
||||
* is only used for data generated within Client.
|
||||
*/
|
||||
private static final int INITIAL_BUF_SIZE = 2*1024;
|
||||
private static final int MAX_BUF_SIZE = 200*1024*1024;
|
||||
private ByteBuffer mReadBuffer;
|
||||
|
||||
private static final int WRITE_BUF_SIZE = 256;
|
||||
private ByteBuffer mWriteBuffer;
|
||||
|
||||
private Device mDevice;
|
||||
|
||||
private int mConnState;
|
||||
|
||||
private static final int ST_INIT = 1;
|
||||
private static final int ST_NOT_JDWP = 2;
|
||||
private static final int ST_AWAIT_SHAKE = 10;
|
||||
private static final int ST_NEED_DDM_PKT = 11;
|
||||
private static final int ST_NOT_DDM = 12;
|
||||
private static final int ST_READY = 13;
|
||||
private static final int ST_ERROR = 20;
|
||||
private static final int ST_DISCONNECTED = 21;
|
||||
|
||||
|
||||
/**
|
||||
* Create an object for a new client connection.
|
||||
*
|
||||
* @param device the device this client belongs to
|
||||
* @param chan the connected {@link SocketChannel}.
|
||||
* @param pid the client pid.
|
||||
*/
|
||||
Client(Device device, SocketChannel chan, int pid) {
|
||||
mDevice = device;
|
||||
mChan = chan;
|
||||
|
||||
mReadBuffer = ByteBuffer.allocate(INITIAL_BUF_SIZE);
|
||||
mWriteBuffer = ByteBuffer.allocate(WRITE_BUF_SIZE);
|
||||
|
||||
mOutstandingReqs = new HashMap<Integer,ChunkHandler>();
|
||||
|
||||
mConnState = ST_INIT;
|
||||
|
||||
mClientData = new ClientData(pid);
|
||||
|
||||
mThreadUpdateEnabled = DdmPreferences.getInitialThreadUpdate();
|
||||
mHeapUpdateEnabled = DdmPreferences.getInitialHeapUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the {@link Client} object.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[Client pid: " + mClientData.getPid() + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Device} on which this Client is running.
|
||||
*/
|
||||
public Device getDevice() {
|
||||
return mDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the debugger port for this client.
|
||||
*/
|
||||
public int getDebuggerListenPort() {
|
||||
return mDebuggerListenPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if the client VM is DDM-aware.
|
||||
*
|
||||
* Calling here is only allowed after the connection has been
|
||||
* established.
|
||||
*/
|
||||
public boolean isDdmAware() {
|
||||
switch (mConnState) {
|
||||
case ST_INIT:
|
||||
case ST_NOT_JDWP:
|
||||
case ST_AWAIT_SHAKE:
|
||||
case ST_NEED_DDM_PKT:
|
||||
case ST_NOT_DDM:
|
||||
case ST_ERROR:
|
||||
case ST_DISCONNECTED:
|
||||
return false;
|
||||
case ST_READY:
|
||||
return true;
|
||||
default:
|
||||
assert false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if a debugger is currently attached to the client.
|
||||
*/
|
||||
public boolean isDebuggerAttached() {
|
||||
return mDebugger.isDebuggerAttached();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Debugger object associated with this client.
|
||||
*/
|
||||
Debugger getDebugger() {
|
||||
return mDebugger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ClientData} object containing this client information.
|
||||
*/
|
||||
public ClientData getClientData() {
|
||||
return mClientData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the client to execute its garbage collector.
|
||||
*/
|
||||
public void executeGarbageCollector() {
|
||||
try {
|
||||
HandleHeap.sendHPGC(this);
|
||||
} catch (IOException ioe) {
|
||||
Log.w("ddms", "Send of HPGC message failed");
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the thread update.
|
||||
* <p/>If <code>true</code> the VM will be able to send thread information. Thread information
|
||||
* must be requested with {@link #requestThreadUpdate()}.
|
||||
* @param enabled the enable flag.
|
||||
*/
|
||||
public void setThreadUpdateEnabled(boolean enabled) {
|
||||
mThreadUpdateEnabled = enabled;
|
||||
if (enabled == false) {
|
||||
mClientData.clearThreads();
|
||||
}
|
||||
|
||||
try {
|
||||
HandleThread.sendTHEN(this, enabled);
|
||||
} catch (IOException ioe) {
|
||||
// ignore it here; client will clean up shortly
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
|
||||
update(CHANGE_THREAD_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the thread update is enabled.
|
||||
*/
|
||||
public boolean isThreadUpdateEnabled() {
|
||||
return mThreadUpdateEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a thread update request. This is asynchronous.
|
||||
* <p/>The thread info can be accessed by {@link ClientData#getThreads()}. The notification
|
||||
* that the new data is available will be received through
|
||||
* {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
|
||||
* containing the mask {@link #CHANGE_THREAD_DATA}.
|
||||
*/
|
||||
public void requestThreadUpdate() {
|
||||
HandleThread.requestThreadUpdate(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a thread stack trace update request. This is asynchronous.
|
||||
* <p/>The thread info can be accessed by {@link ClientData#getThreads()} and
|
||||
* {@link ThreadInfo#getStackTrace()}.
|
||||
* <p/>The notification that the new data is available
|
||||
* will be received through {@link IClientChangeListener#clientChanged(Client, int)}
|
||||
* with a <code>changeMask</code> containing the mask {@link #CHANGE_THREAD_STACKTRACE}.
|
||||
*/
|
||||
public void requestThreadStackTrace(int threadId) {
|
||||
HandleThread.requestThreadStackCallRefresh(this, threadId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the heap update.
|
||||
* <p/>If <code>true</code>, any GC will cause the client to send its heap information.
|
||||
* <p/>The heap information can be accessed by {@link ClientData#getVmHeapData()}.
|
||||
* <p/>The notification that the new data is available
|
||||
* will be received through {@link IClientChangeListener#clientChanged(Client, int)}
|
||||
* with a <code>changeMask</code> containing the value {@link #CHANGE_HEAP_DATA}.
|
||||
* @param enabled the enable flag
|
||||
*/
|
||||
public void setHeapUpdateEnabled(boolean enabled) {
|
||||
mHeapUpdateEnabled = enabled;
|
||||
|
||||
try {
|
||||
HandleHeap.sendHPIF(this,
|
||||
enabled ? HandleHeap.HPIF_WHEN_EVERY_GC : HandleHeap.HPIF_WHEN_NEVER);
|
||||
|
||||
HandleHeap.sendHPSG(this,
|
||||
enabled ? HandleHeap.WHEN_GC : HandleHeap.WHEN_DISABLE,
|
||||
HandleHeap.WHAT_MERGE);
|
||||
} catch (IOException ioe) {
|
||||
// ignore it here; client will clean up shortly
|
||||
}
|
||||
|
||||
update(CHANGE_HEAP_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the heap update is enabled.
|
||||
* @see #setHeapUpdateEnabled(boolean)
|
||||
*/
|
||||
public boolean isHeapUpdateEnabled() {
|
||||
return mHeapUpdateEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a native heap update request. this is asynchronous.
|
||||
* <p/>The native heap info can be accessed by {@link ClientData#getNativeAllocationList()}.
|
||||
* The notification that the new data is available will be received through
|
||||
* {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
|
||||
* containing the mask {@link #CHANGE_NATIVE_HEAP_DATA}.
|
||||
*/
|
||||
public boolean requestNativeHeapInformation() {
|
||||
try {
|
||||
HandleNativeHeap.sendNHGT(this);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.e("ddmlib", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the Allocation tracker for this client.
|
||||
* <p/>If enabled, the VM will start tracking allocation informations. A call to
|
||||
* {@link #requestAllocationDetails()} will make the VM sends the information about all the
|
||||
* allocations that happened between the enabling and the request.
|
||||
* @param enable
|
||||
* @see #requestAllocationDetails()
|
||||
*/
|
||||
public void enableAllocationTracker(boolean enable) {
|
||||
try {
|
||||
HandleHeap.sendREAE(this, enable);
|
||||
} catch (IOException e) {
|
||||
Log.e("ddmlib", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the VM to send the enable status of the allocation tracking.
|
||||
* This is asynchronous.
|
||||
* <p/>The allocation status can be accessed by {@link ClientData#getAllocationStatus()}.
|
||||
* The notification that the new status is available will be received through
|
||||
* {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
|
||||
* containing the mask {@link #CHANGE_HEAP_ALLOCATION_STATUS}.
|
||||
*/
|
||||
public void requestAllocationStatus() {
|
||||
try {
|
||||
HandleHeap.sendREAQ(this);
|
||||
} catch (IOException e) {
|
||||
Log.e("ddmlib", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the VM to send the information about all the allocations that have
|
||||
* happened since the call to {@link #enableAllocationTracker(boolean)} with <var>enable</var>
|
||||
* set to <code>null</code>. This is asynchronous.
|
||||
* <p/>The allocation information can be accessed by {@link ClientData#getAllocations()}.
|
||||
* The notification that the new data is available will be received through
|
||||
* {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
|
||||
* containing the mask {@link #CHANGE_HEAP_ALLOCATIONS}.
|
||||
*/
|
||||
public void requestAllocationDetails() {
|
||||
try {
|
||||
HandleHeap.sendREAL(this);
|
||||
} catch (IOException e) {
|
||||
Log.e("ddmlib", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a kill message to the VM.
|
||||
*/
|
||||
public void kill() {
|
||||
try {
|
||||
HandleExit.sendEXIT(this, 1);
|
||||
} catch (IOException ioe) {
|
||||
Log.w("ddms", "Send of EXIT message failed");
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the client with a Selector.
|
||||
*/
|
||||
void register(Selector sel) throws IOException {
|
||||
if (mChan != null) {
|
||||
mChan.register(sel, SelectionKey.OP_READ, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client to accept debugger connection on the "selected debugger port".
|
||||
*
|
||||
* @see AndroidDebugBridge#setSelectedClient(Client)
|
||||
* @see DdmPreferences#setSelectedDebugPort(int)
|
||||
*/
|
||||
public void setAsSelectedClient() {
|
||||
MonitorThread monitorThread = MonitorThread.getInstance();
|
||||
if (monitorThread != null) {
|
||||
monitorThread.setSelectedClient(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this client is the current selected client, accepting debugger connection
|
||||
* on the "selected debugger port".
|
||||
*
|
||||
* @see #setAsSelectedClient()
|
||||
* @see AndroidDebugBridge#setSelectedClient(Client)
|
||||
* @see DdmPreferences#setSelectedDebugPort(int)
|
||||
*/
|
||||
public boolean isSelectedClient() {
|
||||
MonitorThread monitorThread = MonitorThread.getInstance();
|
||||
if (monitorThread != null) {
|
||||
return monitorThread.getSelectedClient() == this;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the client to open a server socket channel and listen for
|
||||
* connections on the specified port.
|
||||
*/
|
||||
void listenForDebugger(int listenPort) throws IOException {
|
||||
mDebuggerListenPort = listenPort;
|
||||
mDebugger = new Debugger(this, listenPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate the JDWP handshake.
|
||||
*
|
||||
* On failure, closes the socket and returns false.
|
||||
*/
|
||||
boolean sendHandshake() {
|
||||
assert mWriteBuffer.position() == 0;
|
||||
|
||||
try {
|
||||
// assume write buffer can hold 14 bytes
|
||||
JdwpPacket.putHandshake(mWriteBuffer);
|
||||
int expectedLen = mWriteBuffer.position();
|
||||
mWriteBuffer.flip();
|
||||
if (mChan.write(mWriteBuffer) != expectedLen)
|
||||
throw new IOException("partial handshake write");
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
Log.e("ddms-client", "IO error during handshake: " + ioe.getMessage());
|
||||
mConnState = ST_ERROR;
|
||||
close(true /* notify */);
|
||||
return false;
|
||||
}
|
||||
finally {
|
||||
mWriteBuffer.clear();
|
||||
}
|
||||
|
||||
mConnState = ST_AWAIT_SHAKE;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send a non-DDM packet to the client.
|
||||
*
|
||||
* Equivalent to sendAndConsume(packet, null).
|
||||
*/
|
||||
void sendAndConsume(JdwpPacket packet) throws IOException {
|
||||
sendAndConsume(packet, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a DDM packet to the client.
|
||||
*
|
||||
* Ideally, we can do this with a single channel write. If that doesn't
|
||||
* happen, we have to prevent anybody else from writing to the channel
|
||||
* until this packet completes, so we synchronize on the channel.
|
||||
*
|
||||
* Another goal is to avoid unnecessary buffer copies, so we write
|
||||
* directly out of the JdwpPacket's ByteBuffer.
|
||||
*/
|
||||
void sendAndConsume(JdwpPacket packet, ChunkHandler replyHandler)
|
||||
throws IOException {
|
||||
|
||||
if (mChan == null) {
|
||||
// can happen for e.g. THST packets
|
||||
Log.v("ddms", "Not sending packet -- client is closed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (replyHandler != null) {
|
||||
/*
|
||||
* Add the ID to the list of outstanding requests. We have to do
|
||||
* this before sending the packet, in case the response comes back
|
||||
* before our thread returns from the packet-send function.
|
||||
*/
|
||||
addRequestId(packet.getId(), replyHandler);
|
||||
}
|
||||
|
||||
synchronized (mChan) {
|
||||
try {
|
||||
packet.writeAndConsume(mChan);
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
removeRequestId(packet.getId());
|
||||
throw ioe;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward the packet to the debugger (if still connected to one).
|
||||
*
|
||||
* Consumes the packet.
|
||||
*/
|
||||
void forwardPacketToDebugger(JdwpPacket packet)
|
||||
throws IOException {
|
||||
|
||||
Debugger dbg = mDebugger;
|
||||
|
||||
if (dbg == null) {
|
||||
Log.i("ddms", "Discarding packet");
|
||||
packet.consume();
|
||||
} else {
|
||||
dbg.sendAndConsume(packet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data from our channel.
|
||||
*
|
||||
* This is called when data is known to be available, and we don't yet
|
||||
* have a full packet in the buffer. If the buffer is at capacity,
|
||||
* expand it.
|
||||
*/
|
||||
void read()
|
||||
throws IOException, BufferOverflowException {
|
||||
|
||||
int count;
|
||||
|
||||
if (mReadBuffer.position() == mReadBuffer.capacity()) {
|
||||
if (mReadBuffer.capacity() * 2 > MAX_BUF_SIZE) {
|
||||
Log.e("ddms", "Exceeded MAX_BUF_SIZE!");
|
||||
throw new BufferOverflowException();
|
||||
}
|
||||
Log.d("ddms", "Expanding read buffer to "
|
||||
+ mReadBuffer.capacity() * 2);
|
||||
|
||||
ByteBuffer newBuffer = ByteBuffer.allocate(mReadBuffer.capacity() * 2);
|
||||
|
||||
// copy entire buffer to new buffer
|
||||
mReadBuffer.position(0);
|
||||
newBuffer.put(mReadBuffer); // leaves "position" at end of copied
|
||||
|
||||
mReadBuffer = newBuffer;
|
||||
}
|
||||
|
||||
count = mChan.read(mReadBuffer);
|
||||
if (count < 0)
|
||||
throw new IOException("read failed");
|
||||
|
||||
if (Log.Config.LOGV) Log.v("ddms", "Read " + count + " bytes from " + this);
|
||||
//Log.hexDump("ddms", Log.DEBUG, mReadBuffer.array(),
|
||||
// mReadBuffer.arrayOffset(), mReadBuffer.position());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information for the first full JDWP packet in the buffer.
|
||||
*
|
||||
* If we don't yet have a full packet, return null.
|
||||
*
|
||||
* If we haven't yet received the JDWP handshake, we watch for it here
|
||||
* and consume it without admitting to have done so. Upon receipt
|
||||
* we send out the "HELO" message, which is why this can throw an
|
||||
* IOException.
|
||||
*/
|
||||
JdwpPacket getJdwpPacket() throws IOException {
|
||||
|
||||
/*
|
||||
* On entry, the data starts at offset 0 and ends at "position".
|
||||
* "limit" is set to the buffer capacity.
|
||||
*/
|
||||
if (mConnState == ST_AWAIT_SHAKE) {
|
||||
/*
|
||||
* The first thing we get from the client is a response to our
|
||||
* handshake. It doesn't look like a packet, so we have to
|
||||
* handle it specially.
|
||||
*/
|
||||
int result;
|
||||
|
||||
result = JdwpPacket.findHandshake(mReadBuffer);
|
||||
//Log.v("ddms", "findHand: " + result);
|
||||
switch (result) {
|
||||
case JdwpPacket.HANDSHAKE_GOOD:
|
||||
Log.i("ddms",
|
||||
"Good handshake from client, sending HELO to " + mClientData.getPid());
|
||||
JdwpPacket.consumeHandshake(mReadBuffer);
|
||||
mConnState = ST_NEED_DDM_PKT;
|
||||
HandleHello.sendHELO(this, SERVER_PROTOCOL_VERSION);
|
||||
// see if we have another packet in the buffer
|
||||
return getJdwpPacket();
|
||||
case JdwpPacket.HANDSHAKE_BAD:
|
||||
Log.i("ddms", "Bad handshake from client");
|
||||
if (MonitorThread.getInstance().getRetryOnBadHandshake()) {
|
||||
// we should drop the client, but also attempt to reopen it.
|
||||
// This is done by the DeviceMonitor.
|
||||
mDevice.getMonitor().addClientToDropAndReopen(this,
|
||||
IDebugPortProvider.NO_STATIC_PORT);
|
||||
} else {
|
||||
// mark it as bad, close the socket, and don't retry
|
||||
mConnState = ST_NOT_JDWP;
|
||||
close(true /* notify */);
|
||||
}
|
||||
break;
|
||||
case JdwpPacket.HANDSHAKE_NOTYET:
|
||||
Log.i("ddms", "No handshake from client yet.");
|
||||
break;
|
||||
default:
|
||||
Log.e("ddms", "Unknown packet while waiting for client handshake");
|
||||
}
|
||||
return null;
|
||||
} else if (mConnState == ST_NEED_DDM_PKT ||
|
||||
mConnState == ST_NOT_DDM ||
|
||||
mConnState == ST_READY) {
|
||||
/*
|
||||
* Normal packet traffic.
|
||||
*/
|
||||
if (mReadBuffer.position() != 0) {
|
||||
if (Log.Config.LOGV) Log.v("ddms",
|
||||
"Checking " + mReadBuffer.position() + " bytes");
|
||||
}
|
||||
return JdwpPacket.findPacket(mReadBuffer);
|
||||
} else {
|
||||
/*
|
||||
* Not expecting data when in this state.
|
||||
*/
|
||||
Log.e("ddms", "Receiving data in state = " + mConnState);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the specified ID to the list of request IDs for which we await
|
||||
* a response.
|
||||
*/
|
||||
private void addRequestId(int id, ChunkHandler handler) {
|
||||
synchronized (mOutstandingReqs) {
|
||||
if (Log.Config.LOGV) Log.v("ddms",
|
||||
"Adding req 0x" + Integer.toHexString(id) +" to set");
|
||||
mOutstandingReqs.put(id, handler);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the specified ID from the list, if present.
|
||||
*/
|
||||
void removeRequestId(int id) {
|
||||
synchronized (mOutstandingReqs) {
|
||||
if (Log.Config.LOGV) Log.v("ddms",
|
||||
"Removing req 0x" + Integer.toHexString(id) + " from set");
|
||||
mOutstandingReqs.remove(id);
|
||||
}
|
||||
|
||||
//Log.w("ddms", "Request " + Integer.toHexString(id)
|
||||
// + " could not be removed from " + this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether this is a response to a request we sent earlier.
|
||||
* If so, return the ChunkHandler responsible.
|
||||
*/
|
||||
ChunkHandler isResponseToUs(int id) {
|
||||
|
||||
synchronized (mOutstandingReqs) {
|
||||
ChunkHandler handler = mOutstandingReqs.get(id);
|
||||
if (handler != null) {
|
||||
if (Log.Config.LOGV) Log.v("ddms",
|
||||
"Found 0x" + Integer.toHexString(id)
|
||||
+ " in request set - " + handler);
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An earlier request resulted in a failure. This is the expected
|
||||
* response to a HELO message when talking to a non-DDM client.
|
||||
*/
|
||||
void packetFailed(JdwpPacket reply) {
|
||||
if (mConnState == ST_NEED_DDM_PKT) {
|
||||
Log.i("ddms", "Marking " + this + " as non-DDM client");
|
||||
mConnState = ST_NOT_DDM;
|
||||
} else if (mConnState != ST_NOT_DDM) {
|
||||
Log.w("ddms", "WEIRD: got JDWP failure packet on DDM req");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The MonitorThread calls this when it sees a DDM request or reply.
|
||||
* If we haven't seen a DDM packet before, we advance the state to
|
||||
* ST_READY and return "false". Otherwise, just return true.
|
||||
*
|
||||
* The idea is to let the MonitorThread know when we first see a DDM
|
||||
* packet, so we can send a broadcast to the handlers when a client
|
||||
* connection is made. This method is synchronized so that we only
|
||||
* send the broadcast once.
|
||||
*/
|
||||
synchronized boolean ddmSeen() {
|
||||
if (mConnState == ST_NEED_DDM_PKT) {
|
||||
mConnState = ST_READY;
|
||||
return false;
|
||||
} else if (mConnState != ST_READY) {
|
||||
Log.w("ddms", "WEIRD: in ddmSeen with state=" + mConnState);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the client socket channel. If there is a debugger associated
|
||||
* with us, close that too.
|
||||
*
|
||||
* Closing a channel automatically unregisters it from the selector.
|
||||
* However, we have to iterate through the selector loop before it
|
||||
* actually lets them go and allows the file descriptors to close.
|
||||
* The caller is expected to manage that.
|
||||
* @param notify Whether or not to notify the listeners of a change.
|
||||
*/
|
||||
void close(boolean notify) {
|
||||
Log.i("ddms", "Closing " + this.toString());
|
||||
|
||||
mOutstandingReqs.clear();
|
||||
|
||||
try {
|
||||
if (mChan != null) {
|
||||
mChan.close();
|
||||
mChan = null;
|
||||
}
|
||||
|
||||
if (mDebugger != null) {
|
||||
mDebugger.close();
|
||||
mDebugger = null;
|
||||
}
|
||||
}
|
||||
catch (IOException ioe) {
|
||||
Log.w("ddms", "failed to close " + this);
|
||||
// swallow it -- not much else to do
|
||||
}
|
||||
|
||||
mDevice.removeClient(this, notify);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this {@link Client} has a valid connection to the application VM.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return mChan != null;
|
||||
}
|
||||
|
||||
void update(int changeMask) {
|
||||
mDevice.update(this, changeMask);
|
||||
}
|
||||
}
|
||||
|
||||
502
tools/ddms/libs/ddmlib/src/com/android/ddmlib/ClientData.java
Normal file
502
tools/ddms/libs/ddmlib/src/com/android/ddmlib/ClientData.java
Normal file
@@ -0,0 +1,502 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import com.android.ddmlib.HeapSegment.HeapSegmentElement;
|
||||
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
|
||||
/**
|
||||
* Contains the data of a {@link Client}.
|
||||
*/
|
||||
public class ClientData {
|
||||
/* This is a place to stash data associated with a Client, such as thread
|
||||
* states or heap data. ClientData maps 1:1 to Client, but it's a little
|
||||
* cleaner if we separate the data out.
|
||||
*
|
||||
* Message handlers are welcome to stash arbitrary data here.
|
||||
*
|
||||
* IMPORTANT: The data here is written by HandleFoo methods and read by
|
||||
* FooPanel methods, which run in different threads. All non-trivial
|
||||
* access should be synchronized against the ClientData object.
|
||||
*/
|
||||
|
||||
|
||||
/** Temporary name of VM to be ignored. */
|
||||
private final static String PRE_INITIALIZED = "<pre-initialized>"; //$NON-NLS-1$
|
||||
|
||||
/** Debugger connection status: not waiting on one, not connected to one, but accepting
|
||||
* new connections. This is the default value. */
|
||||
public static final int DEBUGGER_DEFAULT = 1;
|
||||
/**
|
||||
* Debugger connection status: the application's VM is paused, waiting for a debugger to
|
||||
* connect to it before resuming. */
|
||||
public static final int DEBUGGER_WAITING = 2;
|
||||
/** Debugger connection status : Debugger is connected */
|
||||
public static final int DEBUGGER_ATTACHED = 3;
|
||||
/** Debugger connection status: The listening port for debugger connection failed to listen.
|
||||
* No debugger will be able to connect. */
|
||||
public static final int DEBUGGER_ERROR = 4;
|
||||
|
||||
/**
|
||||
* Allocation tracking status: unknown.
|
||||
* <p/>This happens right after a {@link Client} is discovered
|
||||
* by the {@link AndroidDebugBridge}, and before the {@link Client} answered the query regarding
|
||||
* its allocation tracking status.
|
||||
* @see Client#requestAllocationStatus()
|
||||
*/
|
||||
public static final int ALLOCATION_TRACKING_UNKNOWN = -1;
|
||||
/**
|
||||
* Allocation tracking status: the {@link Client} is not tracking allocations. */
|
||||
public static final int ALLOCATION_TRACKING_OFF = 0;
|
||||
/**
|
||||
* Allocation tracking status: the {@link Client} is tracking allocations. */
|
||||
public static final int ALLOCATION_TRACKING_ON = 1;
|
||||
|
||||
/**
|
||||
* Name of the value representing the max size of the heap, in the {@link Map} returned by
|
||||
* {@link #getVmHeapInfo(int)}
|
||||
*/
|
||||
public final static String HEAP_MAX_SIZE_BYTES = "maxSizeInBytes"; // $NON-NLS-1$
|
||||
/**
|
||||
* Name of the value representing the size of the heap, in the {@link Map} returned by
|
||||
* {@link #getVmHeapInfo(int)}
|
||||
*/
|
||||
public final static String HEAP_SIZE_BYTES = "sizeInBytes"; // $NON-NLS-1$
|
||||
/**
|
||||
* Name of the value representing the number of allocated bytes of the heap, in the
|
||||
* {@link Map} returned by {@link #getVmHeapInfo(int)}
|
||||
*/
|
||||
public final static String HEAP_BYTES_ALLOCATED = "bytesAllocated"; // $NON-NLS-1$
|
||||
/**
|
||||
* Name of the value representing the number of objects in the heap, in the {@link Map}
|
||||
* returned by {@link #getVmHeapInfo(int)}
|
||||
*/
|
||||
public final static String HEAP_OBJECTS_ALLOCATED = "objectsAllocated"; // $NON-NLS-1$
|
||||
|
||||
// is this a DDM-aware client?
|
||||
private boolean mIsDdmAware;
|
||||
|
||||
// the client's process ID
|
||||
private final int mPid;
|
||||
|
||||
// Java VM identification string
|
||||
private String mVmIdentifier;
|
||||
|
||||
// client's self-description
|
||||
private String mClientDescription;
|
||||
|
||||
// how interested are we in a debugger?
|
||||
private int mDebuggerInterest;
|
||||
|
||||
// Thread tracking (THCR, THDE).
|
||||
private TreeMap<Integer,ThreadInfo> mThreadMap;
|
||||
|
||||
/** VM Heap data */
|
||||
private final HeapData mHeapData = new HeapData();
|
||||
/** Native Heap data */
|
||||
private final HeapData mNativeHeapData = new HeapData();
|
||||
|
||||
private HashMap<Integer, HashMap<String, Long>> mHeapInfoMap =
|
||||
new HashMap<Integer, HashMap<String, Long>>();
|
||||
|
||||
|
||||
/** library map info. Stored here since the backtrace data
|
||||
* is computed on a need to display basis.
|
||||
*/
|
||||
private ArrayList<NativeLibraryMapInfo> mNativeLibMapInfo =
|
||||
new ArrayList<NativeLibraryMapInfo>();
|
||||
|
||||
/** Native Alloc info list */
|
||||
private ArrayList<NativeAllocationInfo> mNativeAllocationList =
|
||||
new ArrayList<NativeAllocationInfo>();
|
||||
private int mNativeTotalMemory;
|
||||
|
||||
private AllocationInfo[] mAllocations;
|
||||
private int mAllocationStatus = ALLOCATION_TRACKING_UNKNOWN;
|
||||
|
||||
/**
|
||||
* Heap Information.
|
||||
* <p/>The heap is composed of several {@link HeapSegment} objects.
|
||||
* <p/>A call to {@link #isHeapDataComplete()} will indicate if the segments (available through
|
||||
* {@link #getHeapSegments()}) represent the full heap.
|
||||
*/
|
||||
public static class HeapData {
|
||||
private TreeSet<HeapSegment> mHeapSegments = new TreeSet<HeapSegment>();
|
||||
private boolean mHeapDataComplete = false;
|
||||
private byte[] mProcessedHeapData;
|
||||
private Map<Integer, ArrayList<HeapSegmentElement>> mProcessedHeapMap;
|
||||
|
||||
/**
|
||||
* Abandon the current list of heap segments.
|
||||
*/
|
||||
public synchronized void clearHeapData() {
|
||||
/* Abandon the old segments instead of just calling .clear().
|
||||
* This lets the user hold onto the old set if it wants to.
|
||||
*/
|
||||
mHeapSegments = new TreeSet<HeapSegment>();
|
||||
mHeapDataComplete = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add raw HPSG chunk data to the list of heap segments.
|
||||
*
|
||||
* @param data The raw data from an HPSG chunk.
|
||||
*/
|
||||
synchronized void addHeapData(ByteBuffer data) {
|
||||
HeapSegment hs;
|
||||
|
||||
if (mHeapDataComplete) {
|
||||
clearHeapData();
|
||||
}
|
||||
|
||||
try {
|
||||
hs = new HeapSegment(data);
|
||||
} catch (BufferUnderflowException e) {
|
||||
System.err.println("Discarding short HPSG data (length " + data.limit() + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
mHeapSegments.add(hs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when all heap data has arrived.
|
||||
*/
|
||||
synchronized void sealHeapData() {
|
||||
mHeapDataComplete = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the heap data has been sealed.
|
||||
*/
|
||||
public boolean isHeapDataComplete() {
|
||||
return mHeapDataComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collected heap data, if sealed.
|
||||
*
|
||||
* @return The list of heap segments if the heap data has been sealed, or null if it hasn't.
|
||||
*/
|
||||
public Collection<HeapSegment> getHeapSegments() {
|
||||
if (isHeapDataComplete()) {
|
||||
return mHeapSegments;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the processed heap data.
|
||||
*
|
||||
* @param heapData The new heap data (can be null)
|
||||
*/
|
||||
public void setProcessedHeapData(byte[] heapData) {
|
||||
mProcessedHeapData = heapData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the processed heap data, if present.
|
||||
*
|
||||
* @return the processed heap data, or null.
|
||||
*/
|
||||
public byte[] getProcessedHeapData() {
|
||||
return mProcessedHeapData;
|
||||
}
|
||||
|
||||
public void setProcessedHeapMap(Map<Integer, ArrayList<HeapSegmentElement>> heapMap) {
|
||||
mProcessedHeapMap = heapMap;
|
||||
}
|
||||
|
||||
public Map<Integer, ArrayList<HeapSegmentElement>> getProcessedHeapMap() {
|
||||
return mProcessedHeapMap;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generic constructor.
|
||||
*/
|
||||
ClientData(int pid) {
|
||||
mPid = pid;
|
||||
|
||||
mDebuggerInterest = DEBUGGER_DEFAULT;
|
||||
mThreadMap = new TreeMap<Integer,ThreadInfo>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the process is DDM-aware.
|
||||
*/
|
||||
public boolean isDdmAware() {
|
||||
return mIsDdmAware;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets DDM-aware status.
|
||||
*/
|
||||
void isDdmAware(boolean aware) {
|
||||
mIsDdmAware = aware;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the process ID.
|
||||
*/
|
||||
public int getPid() {
|
||||
return mPid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Client's VM identifier.
|
||||
*/
|
||||
public String getVmIdentifier() {
|
||||
return mVmIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets VM identifier.
|
||||
*/
|
||||
void setVmIdentifier(String ident) {
|
||||
mVmIdentifier = ident;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client description.
|
||||
* <p/>This is generally the name of the package defined in the
|
||||
* <code>AndroidManifest.xml</code>.
|
||||
*
|
||||
* @return the client description or <code>null</code> if not the description was not yet
|
||||
* sent by the client.
|
||||
*/
|
||||
public String getClientDescription() {
|
||||
return mClientDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets client description.
|
||||
*
|
||||
* There may be a race between HELO and APNM. Rather than try
|
||||
* to enforce ordering on the device, we just don't allow an empty
|
||||
* name to replace a specified one.
|
||||
*/
|
||||
void setClientDescription(String description) {
|
||||
if (mClientDescription == null && description.length() > 0) {
|
||||
/*
|
||||
* The application VM is first named <pre-initialized> before being assigned
|
||||
* its real name.
|
||||
* Depending on the timing, we can get an APNM chunk setting this name before
|
||||
* another one setting the final actual name. So if we get a SetClientDescription
|
||||
* with this value we ignore it.
|
||||
*/
|
||||
if (PRE_INITIALIZED.equals(description) == false) {
|
||||
mClientDescription = description;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the debugger connection status. Possible values are {@link #DEBUGGER_DEFAULT},
|
||||
* {@link #DEBUGGER_WAITING}, {@link #DEBUGGER_ATTACHED}, and {@link #DEBUGGER_ERROR}.
|
||||
*/
|
||||
public int getDebuggerConnectionStatus() {
|
||||
return mDebuggerInterest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets debugger connection status.
|
||||
*/
|
||||
void setDebuggerConnectionStatus(int val) {
|
||||
mDebuggerInterest = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current heap info values for the specified heap.
|
||||
*
|
||||
* @param heapId The heap whose info to update
|
||||
* @param sizeInBytes The size of the heap, in bytes
|
||||
* @param bytesAllocated The number of bytes currently allocated in the heap
|
||||
* @param objectsAllocated The number of objects currently allocated in
|
||||
* the heap
|
||||
*/
|
||||
// TODO: keep track of timestamp, reason
|
||||
synchronized void setHeapInfo(int heapId, long maxSizeInBytes,
|
||||
long sizeInBytes, long bytesAllocated, long objectsAllocated) {
|
||||
HashMap<String, Long> heapInfo = new HashMap<String, Long>();
|
||||
heapInfo.put(HEAP_MAX_SIZE_BYTES, maxSizeInBytes);
|
||||
heapInfo.put(HEAP_SIZE_BYTES, sizeInBytes);
|
||||
heapInfo.put(HEAP_BYTES_ALLOCATED, bytesAllocated);
|
||||
heapInfo.put(HEAP_OBJECTS_ALLOCATED, objectsAllocated);
|
||||
mHeapInfoMap.put(heapId, heapInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link HeapData} object for the VM.
|
||||
*/
|
||||
public HeapData getVmHeapData() {
|
||||
return mHeapData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link HeapData} object for the native code.
|
||||
*/
|
||||
HeapData getNativeHeapData() {
|
||||
return mNativeHeapData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator over the list of known VM heap ids.
|
||||
* <p/>
|
||||
* The caller must synchronize on the {@link ClientData} object while iterating.
|
||||
*
|
||||
* @return an iterator over the list of heap ids
|
||||
*/
|
||||
public synchronized Iterator<Integer> getVmHeapIds() {
|
||||
return mHeapInfoMap.keySet().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most-recent info values for the specified VM heap.
|
||||
*
|
||||
* @param heapId The heap whose info should be returned
|
||||
* @return a map containing the info values for the specified heap.
|
||||
* Returns <code>null</code> if the heap ID is unknown.
|
||||
*/
|
||||
public synchronized Map<String, Long> getVmHeapInfo(int heapId) {
|
||||
return mHeapInfoMap.get(heapId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new thread to the list.
|
||||
*/
|
||||
synchronized void addThread(int threadId, String threadName) {
|
||||
ThreadInfo attr = new ThreadInfo(threadId, threadName);
|
||||
mThreadMap.put(threadId, attr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a thread from the list.
|
||||
*/
|
||||
synchronized void removeThread(int threadId) {
|
||||
mThreadMap.remove(threadId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of threads as {@link ThreadInfo} objects.
|
||||
* <p/>The list is empty until a thread update was requested with
|
||||
* {@link Client#requestThreadUpdate()}.
|
||||
*/
|
||||
public synchronized ThreadInfo[] getThreads() {
|
||||
Collection<ThreadInfo> threads = mThreadMap.values();
|
||||
return threads.toArray(new ThreadInfo[threads.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ThreadInfo} by thread id.
|
||||
*/
|
||||
synchronized ThreadInfo getThread(int threadId) {
|
||||
return mThreadMap.get(threadId);
|
||||
}
|
||||
|
||||
synchronized void clearThreads() {
|
||||
mThreadMap.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of {@link NativeAllocationInfo}.
|
||||
* @see Client#requestNativeHeapInformation()
|
||||
*/
|
||||
public synchronized List<NativeAllocationInfo> getNativeAllocationList() {
|
||||
return Collections.unmodifiableList(mNativeAllocationList);
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a new {@link NativeAllocationInfo} to the {@link Client}
|
||||
* @param allocInfo The {@link NativeAllocationInfo} to add.
|
||||
*/
|
||||
synchronized void addNativeAllocation(NativeAllocationInfo allocInfo) {
|
||||
mNativeAllocationList.add(allocInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the current malloc info.
|
||||
*/
|
||||
synchronized void clearNativeAllocationInfo() {
|
||||
mNativeAllocationList.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total native memory.
|
||||
* @see Client#requestNativeHeapInformation()
|
||||
*/
|
||||
public synchronized int getTotalNativeMemory() {
|
||||
return mNativeTotalMemory;
|
||||
}
|
||||
|
||||
synchronized void setTotalNativeMemory(int totalMemory) {
|
||||
mNativeTotalMemory = totalMemory;
|
||||
}
|
||||
|
||||
synchronized void addNativeLibraryMapInfo(long startAddr, long endAddr, String library) {
|
||||
mNativeLibMapInfo.add(new NativeLibraryMapInfo(startAddr, endAddr, library));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Iterator} on {@link NativeLibraryMapInfo} objects.
|
||||
* <p/>
|
||||
* The caller must synchronize on the {@link ClientData} object while iterating.
|
||||
*/
|
||||
public synchronized Iterator<NativeLibraryMapInfo> getNativeLibraryMapInfo() {
|
||||
return mNativeLibMapInfo.iterator();
|
||||
}
|
||||
|
||||
synchronized void setAllocationStatus(boolean enabled) {
|
||||
mAllocationStatus = enabled ? ALLOCATION_TRACKING_ON : ALLOCATION_TRACKING_OFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the allocation tracking status.
|
||||
* @see Client#requestAllocationStatus()
|
||||
*/
|
||||
public synchronized int getAllocationStatus() {
|
||||
return mAllocationStatus;
|
||||
}
|
||||
|
||||
synchronized void setAllocations(AllocationInfo[] allocs) {
|
||||
mAllocations = allocs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of tracked allocations.
|
||||
* @see Client#requestAllocationDetails()
|
||||
*/
|
||||
public synchronized AllocationInfo[] getAllocations() {
|
||||
return mAllocations;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import com.android.ddmlib.Log.LogLevel;
|
||||
|
||||
/**
|
||||
* Preferences for the ddm library.
|
||||
* <p/>This class does not handle storing the preferences. It is merely a central point for
|
||||
* applications using the ddmlib to override the default values.
|
||||
* <p/>Various components of the ddmlib query this class to get their values.
|
||||
* <p/>Calls to some <code>set##()</code> methods will update the components using the values
|
||||
* right away, while other methods will have no effect once {@link AndroidDebugBridge#init(boolean)}
|
||||
* has been called.
|
||||
* <p/>Check the documentation of each method.
|
||||
*/
|
||||
public final class DdmPreferences {
|
||||
|
||||
/** Default value for thread update flag upon client connection. */
|
||||
public final static boolean DEFAULT_INITIAL_THREAD_UPDATE = false;
|
||||
/** Default value for heap update flag upon client connection. */
|
||||
public final static boolean DEFAULT_INITIAL_HEAP_UPDATE = false;
|
||||
/** Default value for the selected client debug port */
|
||||
public final static int DEFAULT_SELECTED_DEBUG_PORT = 8700;
|
||||
/** Default value for the debug port base */
|
||||
public final static int DEFAULT_DEBUG_PORT_BASE = 8600;
|
||||
/** Default value for the logcat {@link LogLevel} */
|
||||
public final static LogLevel DEFAULT_LOG_LEVEL = LogLevel.ERROR;
|
||||
|
||||
private static boolean sThreadUpdate = DEFAULT_INITIAL_THREAD_UPDATE;
|
||||
private static boolean sInitialHeapUpdate = DEFAULT_INITIAL_HEAP_UPDATE;
|
||||
|
||||
private static int sSelectedDebugPort = DEFAULT_SELECTED_DEBUG_PORT;
|
||||
private static int sDebugPortBase = DEFAULT_DEBUG_PORT_BASE;
|
||||
private static LogLevel sLogLevel = DEFAULT_LOG_LEVEL;
|
||||
|
||||
/**
|
||||
* Returns the initial {@link Client} flag for thread updates.
|
||||
* @see #setInitialThreadUpdate(boolean)
|
||||
*/
|
||||
public static boolean getInitialThreadUpdate() {
|
||||
return sThreadUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial {@link Client} flag for thread updates.
|
||||
* <p/>This change takes effect right away, for newly created {@link Client} objects.
|
||||
*/
|
||||
public static void setInitialThreadUpdate(boolean state) {
|
||||
sThreadUpdate = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the initial {@link Client} flag for heap updates.
|
||||
* @see #setInitialHeapUpdate(boolean)
|
||||
*/
|
||||
public static boolean getInitialHeapUpdate() {
|
||||
return sInitialHeapUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial {@link Client} flag for heap updates.
|
||||
* <p/>If <code>true</code>, the {@link ClientData} will automatically be updated with
|
||||
* the VM heap information whenever a GC happens.
|
||||
* <p/>This change takes effect right away, for newly created {@link Client} objects.
|
||||
*/
|
||||
public static void setInitialHeapUpdate(boolean state) {
|
||||
sInitialHeapUpdate = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the debug port used by the selected {@link Client}.
|
||||
*/
|
||||
public static int getSelectedDebugPort() {
|
||||
return sSelectedDebugPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the debug port used by the selected {@link Client}.
|
||||
* <p/>This change takes effect right away.
|
||||
* @param port the new port to use.
|
||||
*/
|
||||
public static void setSelectedDebugPort(int port) {
|
||||
sSelectedDebugPort = port;
|
||||
|
||||
MonitorThread monitorThread = MonitorThread.getInstance();
|
||||
if (monitorThread != null) {
|
||||
monitorThread.setDebugSelectedPort(port);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the debug port used by the first {@link Client}. Following clients, will use the
|
||||
* next port.
|
||||
*/
|
||||
public static int getDebugPortBase() {
|
||||
return sDebugPortBase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the debug port used by the first {@link Client}.
|
||||
* <p/>Once a port is used, the next Client will use port + 1. Quitting applications will
|
||||
* release their debug port, and new clients will be able to reuse them.
|
||||
* <p/>This must be called before {@link AndroidDebugBridge#init(boolean)}.
|
||||
*/
|
||||
public static void setDebugPortBase(int port) {
|
||||
sDebugPortBase = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum {@link LogLevel} being displayed.
|
||||
*/
|
||||
public static LogLevel getLogLevel() {
|
||||
return sLogLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum {@link LogLevel} to display.
|
||||
* <p/>This change takes effect right away.
|
||||
*/
|
||||
public static void setLogLevel(String value) {
|
||||
sLogLevel = LogLevel.getByString(value);
|
||||
|
||||
Log.setLevel(sLogLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Non accessible constructor.
|
||||
*/
|
||||
private DdmPreferences() {
|
||||
// pass, only static methods in the class.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import com.android.ddmlib.Device;
|
||||
|
||||
/**
|
||||
* Centralized point to provide a {@link IDebugPortProvider} to ddmlib.
|
||||
*
|
||||
* <p/>When {@link Client} objects are created, they start listening for debuggers on a specific
|
||||
* port. The default behavior is to start with {@link DdmPreferences#getDebugPortBase()} and
|
||||
* increment this value for each new <code>Client</code>.
|
||||
*
|
||||
* <p/>This {@link DebugPortManager} allows applications using ddmlib to provide a custom
|
||||
* port provider on a per-<code>Client</code> basis, depending on the device/emulator they are
|
||||
* running on, and/or their names.
|
||||
*/
|
||||
public class DebugPortManager {
|
||||
|
||||
/**
|
||||
* Classes which implement this interface provide a method that provides a non random
|
||||
* debugger port for a newly created {@link Client}.
|
||||
*/
|
||||
public interface IDebugPortProvider {
|
||||
|
||||
public static final int NO_STATIC_PORT = -1;
|
||||
|
||||
/**
|
||||
* Returns a non-random debugger port for the specified application running on the
|
||||
* specified {@link Device}.
|
||||
* @param device The device the application is running on.
|
||||
* @param appName The application name, as defined in the <code>AndroidManifest.xml</code>
|
||||
* <var>package</var> attribute of the <var>manifest</var> node.
|
||||
* @return The non-random debugger port or {@link #NO_STATIC_PORT} if the {@link Client}
|
||||
* should use the automatic debugger port provider.
|
||||
*/
|
||||
public int getPort(Device device, String appName);
|
||||
}
|
||||
|
||||
private static IDebugPortProvider sProvider = null;
|
||||
|
||||
/**
|
||||
* Sets the {@link IDebugPortProvider} that will be used when a new {@link Client} requests
|
||||
* a debugger port.
|
||||
* @param provider the <code>IDebugPortProvider</code> to use.
|
||||
*/
|
||||
public static void setProvider(IDebugPortProvider provider) {
|
||||
sProvider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the
|
||||
* @return
|
||||
*/
|
||||
static IDebugPortProvider getProvider() {
|
||||
return sProvider;
|
||||
}
|
||||
}
|
||||
351
tools/ddms/libs/ddmlib/src/com/android/ddmlib/Debugger.java
Normal file
351
tools/ddms/libs/ddmlib/src/com/android/ddmlib/Debugger.java
Normal file
@@ -0,0 +1,351 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
/**
|
||||
* This represents a pending or established connection with a JDWP debugger.
|
||||
*/
|
||||
class Debugger {
|
||||
|
||||
/*
|
||||
* Messages from the debugger should be pretty small; may not even
|
||||
* need an expanding-buffer implementation for this.
|
||||
*/
|
||||
private static final int INITIAL_BUF_SIZE = 1 * 1024;
|
||||
private static final int MAX_BUF_SIZE = 32 * 1024;
|
||||
private ByteBuffer mReadBuffer;
|
||||
|
||||
private static final int PRE_DATA_BUF_SIZE = 256;
|
||||
private ByteBuffer mPreDataBuffer;
|
||||
|
||||
/* connection state */
|
||||
private int mConnState;
|
||||
private static final int ST_NOT_CONNECTED = 1;
|
||||
private static final int ST_AWAIT_SHAKE = 2;
|
||||
private static final int ST_READY = 3;
|
||||
|
||||
/* peer */
|
||||
private Client mClient; // client we're forwarding to/from
|
||||
private int mListenPort; // listen to me
|
||||
private ServerSocketChannel mListenChannel;
|
||||
|
||||
/* this goes up and down; synchronize methods that access the field */
|
||||
private SocketChannel mChannel;
|
||||
|
||||
/**
|
||||
* Create a new Debugger object, configured to listen for connections
|
||||
* on a specific port.
|
||||
*/
|
||||
Debugger(Client client, int listenPort) throws IOException {
|
||||
|
||||
mClient = client;
|
||||
mListenPort = listenPort;
|
||||
|
||||
mListenChannel = ServerSocketChannel.open();
|
||||
mListenChannel.configureBlocking(false); // required for Selector
|
||||
|
||||
InetSocketAddress addr = new InetSocketAddress(
|
||||
InetAddress.getByName("localhost"), // $NON-NLS-1$
|
||||
listenPort);
|
||||
mListenChannel.socket().setReuseAddress(true); // enable SO_REUSEADDR
|
||||
mListenChannel.socket().bind(addr);
|
||||
|
||||
mReadBuffer = ByteBuffer.allocate(INITIAL_BUF_SIZE);
|
||||
mPreDataBuffer = ByteBuffer.allocate(PRE_DATA_BUF_SIZE);
|
||||
mConnState = ST_NOT_CONNECTED;
|
||||
|
||||
Log.i("ddms", "Created: " + this.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns "true" if a debugger is currently attached to us.
|
||||
*/
|
||||
boolean isDebuggerAttached() {
|
||||
return mChannel != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represent the Debugger as a string.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
// mChannel != null means we have connection, ST_READY means it's going
|
||||
return "[Debugger " + mListenPort + "-->" + mClient.getClientData().getPid()
|
||||
+ ((mConnState != ST_READY) ? " inactive]" : " active]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the debugger's listen socket with the Selector.
|
||||
*/
|
||||
void registerListener(Selector sel) throws IOException {
|
||||
mListenChannel.register(sel, SelectionKey.OP_ACCEPT, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Client being debugged.
|
||||
*/
|
||||
Client getClient() {
|
||||
return mClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept a new connection, but only if we don't already have one.
|
||||
*
|
||||
* Must be synchronized with other uses of mChannel and mPreBuffer.
|
||||
*
|
||||
* Returns "null" if we're already talking to somebody.
|
||||
*/
|
||||
synchronized SocketChannel accept() throws IOException {
|
||||
return accept(mListenChannel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept a new connection from the specified listen channel. This
|
||||
* is so we can listen on a dedicated port for the "current" client,
|
||||
* where "current" is constantly in flux.
|
||||
*
|
||||
* Must be synchronized with other uses of mChannel and mPreBuffer.
|
||||
*
|
||||
* Returns "null" if we're already talking to somebody.
|
||||
*/
|
||||
synchronized SocketChannel accept(ServerSocketChannel listenChan)
|
||||
throws IOException {
|
||||
|
||||
if (listenChan != null) {
|
||||
SocketChannel newChan;
|
||||
|
||||
newChan = listenChan.accept();
|
||||
if (mChannel != null) {
|
||||
Log.w("ddms", "debugger already talking to " + mClient
|
||||
+ " on " + mListenPort);
|
||||
newChan.close();
|
||||
return null;
|
||||
}
|
||||
mChannel = newChan;
|
||||
mChannel.configureBlocking(false); // required for Selector
|
||||
mConnState = ST_AWAIT_SHAKE;
|
||||
return mChannel;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the data connection only.
|
||||
*/
|
||||
synchronized void closeData() {
|
||||
try {
|
||||
if (mChannel != null) {
|
||||
mChannel.close();
|
||||
mChannel = null;
|
||||
mConnState = ST_NOT_CONNECTED;
|
||||
|
||||
ClientData cd = mClient.getClientData();
|
||||
cd.setDebuggerConnectionStatus(ClientData.DEBUGGER_DEFAULT);
|
||||
mClient.update(Client.CHANGE_DEBUGGER_INTEREST);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
Log.w("ddms", "Failed to close data " + this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the socket that's listening for new connections and (if
|
||||
* we're connected) the debugger data socket.
|
||||
*/
|
||||
synchronized void close() {
|
||||
try {
|
||||
if (mListenChannel != null) {
|
||||
mListenChannel.close();
|
||||
}
|
||||
mListenChannel = null;
|
||||
closeData();
|
||||
} catch (IOException ioe) {
|
||||
Log.w("ddms", "Failed to close listener " + this);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ?? add a finalizer that verifies the channel was closed
|
||||
|
||||
/**
|
||||
* Read data from our channel.
|
||||
*
|
||||
* This is called when data is known to be available, and we don't yet
|
||||
* have a full packet in the buffer. If the buffer is at capacity,
|
||||
* expand it.
|
||||
*/
|
||||
void read() throws IOException {
|
||||
int count;
|
||||
|
||||
if (mReadBuffer.position() == mReadBuffer.capacity()) {
|
||||
if (mReadBuffer.capacity() * 2 > MAX_BUF_SIZE) {
|
||||
throw new BufferOverflowException();
|
||||
}
|
||||
Log.d("ddms", "Expanding read buffer to "
|
||||
+ mReadBuffer.capacity() * 2);
|
||||
|
||||
ByteBuffer newBuffer =
|
||||
ByteBuffer.allocate(mReadBuffer.capacity() * 2);
|
||||
mReadBuffer.position(0);
|
||||
newBuffer.put(mReadBuffer); // leaves "position" at end
|
||||
|
||||
mReadBuffer = newBuffer;
|
||||
}
|
||||
|
||||
count = mChannel.read(mReadBuffer);
|
||||
Log.v("ddms", "Read " + count + " bytes from " + this);
|
||||
if (count < 0) throw new IOException("read failed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information for the first full JDWP packet in the buffer.
|
||||
*
|
||||
* If we don't yet have a full packet, return null.
|
||||
*
|
||||
* If we haven't yet received the JDWP handshake, we watch for it here
|
||||
* and consume it without admitting to have done so. We also send
|
||||
* the handshake response to the debugger, along with any pending
|
||||
* pre-connection data, which is why this can throw an IOException.
|
||||
*/
|
||||
JdwpPacket getJdwpPacket() throws IOException {
|
||||
/*
|
||||
* On entry, the data starts at offset 0 and ends at "position".
|
||||
* "limit" is set to the buffer capacity.
|
||||
*/
|
||||
if (mConnState == ST_AWAIT_SHAKE) {
|
||||
int result;
|
||||
|
||||
result = JdwpPacket.findHandshake(mReadBuffer);
|
||||
//Log.v("ddms", "findHand: " + result);
|
||||
switch (result) {
|
||||
case JdwpPacket.HANDSHAKE_GOOD:
|
||||
Log.i("ddms", "Good handshake from debugger");
|
||||
JdwpPacket.consumeHandshake(mReadBuffer);
|
||||
sendHandshake();
|
||||
mConnState = ST_READY;
|
||||
|
||||
ClientData cd = mClient.getClientData();
|
||||
cd.setDebuggerConnectionStatus(ClientData.DEBUGGER_ATTACHED);
|
||||
mClient.update(Client.CHANGE_DEBUGGER_INTEREST);
|
||||
|
||||
// see if we have another packet in the buffer
|
||||
return getJdwpPacket();
|
||||
case JdwpPacket.HANDSHAKE_BAD:
|
||||
// not a debugger, throw an exception so we drop the line
|
||||
Log.i("ddms", "Bad handshake from debugger");
|
||||
throw new IOException("bad handshake");
|
||||
case JdwpPacket.HANDSHAKE_NOTYET:
|
||||
break;
|
||||
default:
|
||||
Log.e("ddms", "Unknown packet while waiting for client handshake");
|
||||
}
|
||||
return null;
|
||||
} else if (mConnState == ST_READY) {
|
||||
if (mReadBuffer.position() != 0) {
|
||||
Log.v("ddms", "Checking " + mReadBuffer.position() + " bytes");
|
||||
}
|
||||
return JdwpPacket.findPacket(mReadBuffer);
|
||||
} else {
|
||||
Log.e("ddms", "Receiving data in state = " + mConnState);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward a packet to the client.
|
||||
*
|
||||
* "mClient" will never be null, though it's possible that the channel
|
||||
* in the client has closed and our send attempt will fail.
|
||||
*
|
||||
* Consumes the packet.
|
||||
*/
|
||||
void forwardPacketToClient(JdwpPacket packet) throws IOException {
|
||||
mClient.sendAndConsume(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the handshake to the debugger. We also send along any packets
|
||||
* we already received from the client (usually just a VM_START event,
|
||||
* if anything at all).
|
||||
*/
|
||||
private synchronized void sendHandshake() throws IOException {
|
||||
ByteBuffer tempBuffer = ByteBuffer.allocate(JdwpPacket.HANDSHAKE_LEN);
|
||||
JdwpPacket.putHandshake(tempBuffer);
|
||||
int expectedLength = tempBuffer.position();
|
||||
tempBuffer.flip();
|
||||
if (mChannel.write(tempBuffer) != expectedLength) {
|
||||
throw new IOException("partial handshake write");
|
||||
}
|
||||
|
||||
expectedLength = mPreDataBuffer.position();
|
||||
if (expectedLength > 0) {
|
||||
Log.d("ddms", "Sending " + mPreDataBuffer.position()
|
||||
+ " bytes of saved data");
|
||||
mPreDataBuffer.flip();
|
||||
if (mChannel.write(mPreDataBuffer) != expectedLength) {
|
||||
throw new IOException("partial pre-data write");
|
||||
}
|
||||
mPreDataBuffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a packet to the debugger.
|
||||
*
|
||||
* Ideally, we can do this with a single channel write. If that doesn't
|
||||
* happen, we have to prevent anybody else from writing to the channel
|
||||
* until this packet completes, so we synchronize on the channel.
|
||||
*
|
||||
* Another goal is to avoid unnecessary buffer copies, so we write
|
||||
* directly out of the JdwpPacket's ByteBuffer.
|
||||
*
|
||||
* We must synchronize on "mChannel" before writing to it. We want to
|
||||
* coordinate the buffered data with mChannel creation, so this whole
|
||||
* method is synchronized.
|
||||
*/
|
||||
synchronized void sendAndConsume(JdwpPacket packet)
|
||||
throws IOException {
|
||||
|
||||
if (mChannel == null) {
|
||||
/*
|
||||
* Buffer this up so we can send it to the debugger when it
|
||||
* finally does connect. This is essential because the VM_START
|
||||
* message might be telling the debugger that the VM is
|
||||
* suspended. The alternative approach would be for us to
|
||||
* capture and interpret VM_START and send it later if we
|
||||
* didn't choose to un-suspend the VM for our own purposes.
|
||||
*/
|
||||
Log.d("ddms", "Saving packet 0x"
|
||||
+ Integer.toHexString(packet.getId()));
|
||||
packet.movePacket(mPreDataBuffer);
|
||||
} else {
|
||||
packet.writeAndConsume(mChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
385
tools/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java
Normal file
385
tools/ddms/libs/ddmlib/src/com/android/ddmlib/Device.java
Normal file
@@ -0,0 +1,385 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import com.android.ddmlib.Client;
|
||||
import com.android.ddmlib.log.LogReceiver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* A Device. It can be a physical device or an emulator.
|
||||
*
|
||||
* TODO: make this class package-protected, and shift all callers to use IDevice
|
||||
*/
|
||||
public final class Device implements IDevice {
|
||||
/**
|
||||
* The state of a device.
|
||||
*/
|
||||
public static enum DeviceState {
|
||||
BOOTLOADER("bootloader"), //$NON-NLS-1$
|
||||
OFFLINE("offline"), //$NON-NLS-1$
|
||||
ONLINE("device"); //$NON-NLS-1$
|
||||
|
||||
private String mState;
|
||||
|
||||
DeviceState(String state) {
|
||||
mState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link DeviceState} from the string returned by <code>adb devices</code>.
|
||||
* @param state the device state.
|
||||
* @return a {@link DeviceState} object or <code>null</code> if the state is unknown.
|
||||
*/
|
||||
public static DeviceState getState(String state) {
|
||||
for (DeviceState deviceState : values()) {
|
||||
if (deviceState.mState.equals(state)) {
|
||||
return deviceState;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Emulator Serial Number regexp. */
|
||||
final static String RE_EMULATOR_SN = "emulator-(\\d+)"; //$NON-NLS-1$
|
||||
|
||||
/** Serial number of the device */
|
||||
String serialNumber = null;
|
||||
|
||||
/** Name of the AVD */
|
||||
String mAvdName = null;
|
||||
|
||||
/** State of the device. */
|
||||
DeviceState state = null;
|
||||
|
||||
/** Device properties. */
|
||||
private final Map<String, String> mProperties = new HashMap<String, String>();
|
||||
|
||||
private final ArrayList<Client> mClients = new ArrayList<Client>();
|
||||
private DeviceMonitor mMonitor;
|
||||
|
||||
/**
|
||||
* Socket for the connection monitoring client connection/disconnection.
|
||||
*/
|
||||
private SocketChannel mSocketChannel;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getSerialNumber()
|
||||
*/
|
||||
public String getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
public String getAvdName() {
|
||||
return mAvdName;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getState()
|
||||
*/
|
||||
public DeviceState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getProperties()
|
||||
*/
|
||||
public Map<String, String> getProperties() {
|
||||
return Collections.unmodifiableMap(mProperties);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getPropertyCount()
|
||||
*/
|
||||
public int getPropertyCount() {
|
||||
return mProperties.size();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getProperty(java.lang.String)
|
||||
*/
|
||||
public String getProperty(String name) {
|
||||
return mProperties.get(name);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#isOnline()
|
||||
*/
|
||||
public boolean isOnline() {
|
||||
return state == DeviceState.ONLINE;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#isEmulator()
|
||||
*/
|
||||
public boolean isEmulator() {
|
||||
return serialNumber.matches(RE_EMULATOR_SN);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#isOffline()
|
||||
*/
|
||||
public boolean isOffline() {
|
||||
return state == DeviceState.OFFLINE;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#isBootLoader()
|
||||
*/
|
||||
public boolean isBootLoader() {
|
||||
return state == DeviceState.BOOTLOADER;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#hasClients()
|
||||
*/
|
||||
public boolean hasClients() {
|
||||
return mClients.size() > 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getClients()
|
||||
*/
|
||||
public Client[] getClients() {
|
||||
synchronized (mClients) {
|
||||
return mClients.toArray(new Client[mClients.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getClient(java.lang.String)
|
||||
*/
|
||||
public Client getClient(String applicationName) {
|
||||
synchronized (mClients) {
|
||||
for (Client c : mClients) {
|
||||
if (applicationName.equals(c.getClientData().getClientDescription())) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getSyncService()
|
||||
*/
|
||||
public SyncService getSyncService() {
|
||||
SyncService syncService = new SyncService(AndroidDebugBridge.sSocketAddr, this);
|
||||
if (syncService.openSync()) {
|
||||
return syncService;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getFileListingService()
|
||||
*/
|
||||
public FileListingService getFileListingService() {
|
||||
return new FileListingService(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getScreenshot()
|
||||
*/
|
||||
public RawImage getScreenshot() throws IOException {
|
||||
return AdbHelper.getFrameBuffer(AndroidDebugBridge.sSocketAddr, this);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#executeShellCommand(java.lang.String, com.android.ddmlib.IShellOutputReceiver)
|
||||
*/
|
||||
public void executeShellCommand(String command, IShellOutputReceiver receiver)
|
||||
throws IOException {
|
||||
AdbHelper.executeRemoteCommand(AndroidDebugBridge.sSocketAddr, command, this,
|
||||
receiver);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#runEventLogService(com.android.ddmlib.log.LogReceiver)
|
||||
*/
|
||||
public void runEventLogService(LogReceiver receiver) throws IOException {
|
||||
AdbHelper.runEventLogService(AndroidDebugBridge.sSocketAddr, this, receiver);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#runLogService(com.android.ddmlib.log.LogReceiver)
|
||||
*/
|
||||
public void runLogService(String logname,
|
||||
LogReceiver receiver) throws IOException {
|
||||
AdbHelper.runLogService(AndroidDebugBridge.sSocketAddr, this, logname, receiver);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#createForward(int, int)
|
||||
*/
|
||||
public boolean createForward(int localPort, int remotePort) {
|
||||
try {
|
||||
return AdbHelper.createForward(AndroidDebugBridge.sSocketAddr, this,
|
||||
localPort, remotePort);
|
||||
} catch (IOException e) {
|
||||
Log.e("adb-forward", e); //$NON-NLS-1$
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#removeForward(int, int)
|
||||
*/
|
||||
public boolean removeForward(int localPort, int remotePort) {
|
||||
try {
|
||||
return AdbHelper.removeForward(AndroidDebugBridge.sSocketAddr, this,
|
||||
localPort, remotePort);
|
||||
} catch (IOException e) {
|
||||
Log.e("adb-remove-forward", e); //$NON-NLS-1$
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IDevice#getClientName(int)
|
||||
*/
|
||||
public String getClientName(int pid) {
|
||||
synchronized (mClients) {
|
||||
for (Client c : mClients) {
|
||||
if (c.getClientData().getPid() == pid) {
|
||||
return c.getClientData().getClientDescription();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Device(DeviceMonitor monitor) {
|
||||
mMonitor = monitor;
|
||||
}
|
||||
|
||||
DeviceMonitor getMonitor() {
|
||||
return mMonitor;
|
||||
}
|
||||
|
||||
void addClient(Client client) {
|
||||
synchronized (mClients) {
|
||||
mClients.add(client);
|
||||
}
|
||||
}
|
||||
|
||||
List<Client> getClientList() {
|
||||
return mClients;
|
||||
}
|
||||
|
||||
boolean hasClient(int pid) {
|
||||
synchronized (mClients) {
|
||||
for (Client client : mClients) {
|
||||
if (client.getClientData().getPid() == pid) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void clearClientList() {
|
||||
synchronized (mClients) {
|
||||
mClients.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client monitoring socket.
|
||||
* @param socketChannel the sockets
|
||||
*/
|
||||
void setClientMonitoringSocket(SocketChannel socketChannel) {
|
||||
mSocketChannel = socketChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client monitoring socket.
|
||||
*/
|
||||
SocketChannel getClientMonitoringSocket() {
|
||||
return mSocketChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a {@link Client} from the list.
|
||||
* @param client the client to remove.
|
||||
* @param notify Whether or not to notify the listeners of a change.
|
||||
*/
|
||||
void removeClient(Client client, boolean notify) {
|
||||
mMonitor.addPortToAvailableList(client.getDebuggerListenPort());
|
||||
synchronized (mClients) {
|
||||
mClients.remove(client);
|
||||
}
|
||||
if (notify) {
|
||||
mMonitor.getServer().deviceChanged(this, CHANGE_CLIENT_LIST);
|
||||
}
|
||||
}
|
||||
|
||||
void update(int changeMask) {
|
||||
mMonitor.getServer().deviceChanged(this, changeMask);
|
||||
}
|
||||
|
||||
void update(Client client, int changeMask) {
|
||||
mMonitor.getServer().clientChanged(client, changeMask);
|
||||
}
|
||||
|
||||
void addProperty(String label, String value) {
|
||||
mProperties.put(label, value);
|
||||
}
|
||||
}
|
||||
866
tools/ddms/libs/ddmlib/src/com/android/ddmlib/DeviceMonitor.java
Normal file
866
tools/ddms/libs/ddmlib/src/com/android/ddmlib/DeviceMonitor.java
Normal file
@@ -0,0 +1,866 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import com.android.ddmlib.AdbHelper.AdbResponse;
|
||||
import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
|
||||
import com.android.ddmlib.Device.DeviceState;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.AsynchronousCloseException;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A Device monitor. This connects to the Android Debug Bridge and get device and
|
||||
* debuggable process information from it.
|
||||
*/
|
||||
final class DeviceMonitor {
|
||||
private byte[] mLengthBuffer = new byte[4];
|
||||
private byte[] mLengthBuffer2 = new byte[4];
|
||||
|
||||
private boolean mQuit = false;
|
||||
|
||||
private AndroidDebugBridge mServer;
|
||||
|
||||
private SocketChannel mMainAdbConnection = null;
|
||||
private boolean mMonitoring = false;
|
||||
private int mConnectionAttempt = 0;
|
||||
private int mRestartAttemptCount = 0;
|
||||
private boolean mInitialDeviceListDone = false;
|
||||
|
||||
private Selector mSelector;
|
||||
|
||||
private final ArrayList<Device> mDevices = new ArrayList<Device>();
|
||||
|
||||
private final ArrayList<Integer> mDebuggerPorts = new ArrayList<Integer>();
|
||||
|
||||
private final HashMap<Client, Integer> mClientsToReopen = new HashMap<Client, Integer>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link DeviceMonitor} object and links it to the running
|
||||
* {@link AndroidDebugBridge} object.
|
||||
* @param server the running {@link AndroidDebugBridge}.
|
||||
*/
|
||||
DeviceMonitor(AndroidDebugBridge server) {
|
||||
mServer = server;
|
||||
|
||||
mDebuggerPorts.add(DdmPreferences.getDebugPortBase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the monitoring.
|
||||
*/
|
||||
void start() {
|
||||
new Thread("Device List Monitor") { //$NON-NLS-1$
|
||||
@Override
|
||||
public void run() {
|
||||
deviceMonitorLoop();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the monitoring.
|
||||
*/
|
||||
void stop() {
|
||||
mQuit = true;
|
||||
|
||||
// wakeup the main loop thread by closing the main connection to adb.
|
||||
try {
|
||||
if (mMainAdbConnection != null) {
|
||||
mMainAdbConnection.close();
|
||||
}
|
||||
} catch (IOException e1) {
|
||||
}
|
||||
|
||||
// wake up the secondary loop by closing the selector.
|
||||
if (mSelector != null) {
|
||||
mSelector.wakeup();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Returns if the monitor is currently connected to the debug bridge server.
|
||||
* @return
|
||||
*/
|
||||
boolean isMonitoring() {
|
||||
return mMonitoring;
|
||||
}
|
||||
|
||||
int getConnectionAttemptCount() {
|
||||
return mConnectionAttempt;
|
||||
}
|
||||
|
||||
int getRestartAttemptCount() {
|
||||
return mRestartAttemptCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the devices.
|
||||
*/
|
||||
Device[] getDevices() {
|
||||
synchronized (mDevices) {
|
||||
return mDevices.toArray(new Device[mDevices.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasInitialDeviceList() {
|
||||
return mInitialDeviceListDone;
|
||||
}
|
||||
|
||||
AndroidDebugBridge getServer() {
|
||||
return mServer;
|
||||
}
|
||||
|
||||
void addClientToDropAndReopen(Client client, int port) {
|
||||
synchronized (mClientsToReopen) {
|
||||
Log.d("DeviceMonitor",
|
||||
"Adding " + client + " to list of client to reopen (" + port +").");
|
||||
if (mClientsToReopen.get(client) == null) {
|
||||
mClientsToReopen.put(client, port);
|
||||
}
|
||||
}
|
||||
mSelector.wakeup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors the devices. This connects to the Debug Bridge
|
||||
*/
|
||||
private void deviceMonitorLoop() {
|
||||
do {
|
||||
try {
|
||||
if (mMainAdbConnection == null) {
|
||||
Log.d("DeviceMonitor", "Opening adb connection");
|
||||
mMainAdbConnection = openAdbConnection();
|
||||
if (mMainAdbConnection == null) {
|
||||
mConnectionAttempt++;
|
||||
Log.e("DeviceMonitor", "Connection attempts: " + mConnectionAttempt);
|
||||
if (mConnectionAttempt > 10) {
|
||||
if (mServer.startAdb() == false) {
|
||||
mRestartAttemptCount++;
|
||||
Log.e("DeviceMonitor",
|
||||
"adb restart attempts: " + mRestartAttemptCount);
|
||||
} else {
|
||||
mRestartAttemptCount = 0;
|
||||
}
|
||||
}
|
||||
waitABit();
|
||||
} else {
|
||||
Log.d("DeviceMonitor", "Connected to adb for device monitoring");
|
||||
mConnectionAttempt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (mMainAdbConnection != null && mMonitoring == false) {
|
||||
mMonitoring = sendDeviceListMonitoringRequest();
|
||||
}
|
||||
|
||||
if (mMonitoring) {
|
||||
// read the length of the incoming message
|
||||
int length = readLength(mMainAdbConnection, mLengthBuffer);
|
||||
|
||||
if (length >= 0) {
|
||||
// read the incoming message
|
||||
processIncomingDeviceData(length);
|
||||
|
||||
// flag the fact that we have build the list at least once.
|
||||
mInitialDeviceListDone = true;
|
||||
}
|
||||
}
|
||||
} catch (AsynchronousCloseException ace) {
|
||||
// this happens because of a call to Quit. We do nothing, and the loop will break.
|
||||
} catch (IOException ioe) {
|
||||
if (mQuit == false) {
|
||||
Log.e("DeviceMonitor", "Adb connection Error:" + ioe.getMessage());
|
||||
mMonitoring = false;
|
||||
if (mMainAdbConnection != null) {
|
||||
try {
|
||||
mMainAdbConnection.close();
|
||||
} catch (IOException ioe2) {
|
||||
// we can safely ignore that one.
|
||||
}
|
||||
mMainAdbConnection = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (mQuit == false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleeps for a little bit.
|
||||
*/
|
||||
private void waitABit() {
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e1) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to connect to the debug bridge server.
|
||||
* @return a connect socket if success, null otherwise
|
||||
*/
|
||||
private SocketChannel openAdbConnection() {
|
||||
Log.d("DeviceMonitor", "Connecting to adb for Device List Monitoring...");
|
||||
|
||||
SocketChannel adbChannel = null;
|
||||
try {
|
||||
adbChannel = SocketChannel.open(AndroidDebugBridge.sSocketAddr);
|
||||
adbChannel.socket().setTcpNoDelay(true);
|
||||
} catch (IOException e) {
|
||||
}
|
||||
|
||||
return adbChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private boolean sendDeviceListMonitoringRequest() throws IOException {
|
||||
byte[] request = AdbHelper.formAdbRequest("host:track-devices"); //$NON-NLS-1$
|
||||
|
||||
if (AdbHelper.write(mMainAdbConnection, request) == false) {
|
||||
Log.e("DeviceMonitor", "Sending Tracking request failed!");
|
||||
mMainAdbConnection.close();
|
||||
throw new IOException("Sending Tracking request failed!");
|
||||
}
|
||||
|
||||
AdbResponse resp = AdbHelper.readAdbResponse(mMainAdbConnection,
|
||||
false /* readDiagString */);
|
||||
|
||||
if (resp.ioSuccess == false) {
|
||||
Log.e("DeviceMonitor", "Failed to read the adb response!");
|
||||
mMainAdbConnection.close();
|
||||
throw new IOException("Failed to read the adb response!");
|
||||
}
|
||||
|
||||
if (resp.okay == false) {
|
||||
// request was refused by adb!
|
||||
Log.e("DeviceMonitor", "adb refused request: " + resp.message);
|
||||
}
|
||||
|
||||
return resp.okay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an incoming device message from the socket
|
||||
* @param socket
|
||||
* @param length
|
||||
* @throws IOException
|
||||
*/
|
||||
private void processIncomingDeviceData(int length) throws IOException {
|
||||
ArrayList<Device> list = new ArrayList<Device>();
|
||||
|
||||
if (length > 0) {
|
||||
byte[] buffer = new byte[length];
|
||||
String result = read(mMainAdbConnection, buffer);
|
||||
|
||||
String[] devices = result.split("\n"); // $NON-NLS-1$
|
||||
|
||||
for (String d : devices) {
|
||||
String[] param = d.split("\t"); // $NON-NLS-1$
|
||||
if (param.length == 2) {
|
||||
// new adb uses only serial numbers to identify devices
|
||||
Device device = new Device(this);
|
||||
device.serialNumber = param[0];
|
||||
device.state = DeviceState.getState(param[1]);
|
||||
|
||||
//add the device to the list
|
||||
list.add(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now merge the new devices with the old ones.
|
||||
updateDevices(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the device list with the new items received from the monitoring service.
|
||||
*/
|
||||
private void updateDevices(ArrayList<Device> newList) {
|
||||
// because we are going to call mServer.deviceDisconnected which will acquire this lock
|
||||
// we lock it first, so that the AndroidDebugBridge lock is always locked first.
|
||||
synchronized (AndroidDebugBridge.getLock()) {
|
||||
synchronized (mDevices) {
|
||||
// For each device in the current list, we look for a matching the new list.
|
||||
// * if we find it, we update the current object with whatever new information
|
||||
// there is
|
||||
// (mostly state change, if the device becomes ready, we query for build info).
|
||||
// We also remove the device from the new list to mark it as "processed"
|
||||
// * if we do not find it, we remove it from the current list.
|
||||
// Once this is done, the new list contains device we aren't monitoring yet, so we
|
||||
// add them to the list, and start monitoring them.
|
||||
|
||||
for (int d = 0 ; d < mDevices.size() ;) {
|
||||
Device device = mDevices.get(d);
|
||||
|
||||
// look for a similar device in the new list.
|
||||
int count = newList.size();
|
||||
boolean foundMatch = false;
|
||||
for (int dd = 0 ; dd < count ; dd++) {
|
||||
Device newDevice = newList.get(dd);
|
||||
// see if it matches in id and serial number.
|
||||
if (newDevice.serialNumber.equals(device.serialNumber)) {
|
||||
foundMatch = true;
|
||||
|
||||
// update the state if needed.
|
||||
if (device.state != newDevice.state) {
|
||||
device.state = newDevice.state;
|
||||
device.update(Device.CHANGE_STATE);
|
||||
|
||||
// if the device just got ready/online, we need to start
|
||||
// monitoring it.
|
||||
if (device.isOnline()) {
|
||||
if (AndroidDebugBridge.getClientSupport() == true) {
|
||||
if (startMonitoringDevice(device) == false) {
|
||||
Log.e("DeviceMonitor",
|
||||
"Failed to start monitoring "
|
||||
+ device.serialNumber);
|
||||
}
|
||||
}
|
||||
|
||||
if (device.getPropertyCount() == 0) {
|
||||
queryNewDeviceForInfo(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove the new device from the list since it's been used
|
||||
newList.remove(dd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundMatch == false) {
|
||||
// the device is gone, we need to remove it, and keep current index
|
||||
// to process the next one.
|
||||
removeDevice(device);
|
||||
mServer.deviceDisconnected(device);
|
||||
} else {
|
||||
// process the next one
|
||||
d++;
|
||||
}
|
||||
}
|
||||
|
||||
// at this point we should still have some new devices in newList, so we
|
||||
// process them.
|
||||
for (Device newDevice : newList) {
|
||||
// add them to the list
|
||||
mDevices.add(newDevice);
|
||||
mServer.deviceConnected(newDevice);
|
||||
|
||||
// start monitoring them.
|
||||
if (AndroidDebugBridge.getClientSupport() == true) {
|
||||
if (newDevice.isOnline()) {
|
||||
startMonitoringDevice(newDevice);
|
||||
}
|
||||
}
|
||||
|
||||
// look for their build info.
|
||||
if (newDevice.isOnline()) {
|
||||
queryNewDeviceForInfo(newDevice);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
newList.clear();
|
||||
}
|
||||
|
||||
private void removeDevice(Device device) {
|
||||
device.clearClientList();
|
||||
mDevices.remove(device);
|
||||
|
||||
SocketChannel channel = device.getClientMonitoringSocket();
|
||||
if (channel != null) {
|
||||
try {
|
||||
channel.close();
|
||||
} catch (IOException e) {
|
||||
// doesn't really matter if the close fails.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries a device for its build info.
|
||||
* @param device the device to query.
|
||||
*/
|
||||
private void queryNewDeviceForInfo(Device device) {
|
||||
// TODO: do this in a separate thread.
|
||||
try {
|
||||
// first get the list of properties.
|
||||
device.executeShellCommand(GetPropReceiver.GETPROP_COMMAND,
|
||||
new GetPropReceiver(device));
|
||||
|
||||
// now get the emulator Virtual Device name (if applicable).
|
||||
if (device.isEmulator()) {
|
||||
EmulatorConsole console = EmulatorConsole.getConsole(device);
|
||||
if (console != null) {
|
||||
device.mAvdName = console.getAvdName();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// if we can't get the build info, it doesn't matter too much
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a monitoring service for a device.
|
||||
* @param device the device to monitor.
|
||||
* @return true if success.
|
||||
*/
|
||||
private boolean startMonitoringDevice(Device device) {
|
||||
SocketChannel socketChannel = openAdbConnection();
|
||||
|
||||
if (socketChannel != null) {
|
||||
try {
|
||||
boolean result = sendDeviceMonitoringRequest(socketChannel, device);
|
||||
if (result) {
|
||||
|
||||
if (mSelector == null) {
|
||||
startDeviceMonitorThread();
|
||||
}
|
||||
|
||||
device.setClientMonitoringSocket(socketChannel);
|
||||
|
||||
synchronized (mDevices) {
|
||||
// always wakeup before doing the register. The synchronized block
|
||||
// ensure that the selector won't select() before the end of this block.
|
||||
// @see deviceClientMonitorLoop
|
||||
mSelector.wakeup();
|
||||
|
||||
socketChannel.configureBlocking(false);
|
||||
socketChannel.register(mSelector, SelectionKey.OP_READ, device);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
// attempt to close the socket if needed.
|
||||
socketChannel.close();
|
||||
} catch (IOException e1) {
|
||||
// we can ignore that one. It may already have been closed.
|
||||
}
|
||||
Log.d("DeviceMonitor",
|
||||
"Connection Failure when starting to monitor device '"
|
||||
+ device + "' : " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void startDeviceMonitorThread() throws IOException {
|
||||
mSelector = Selector.open();
|
||||
new Thread("Device Client Monitor") { //$NON-NLS-1$
|
||||
@Override
|
||||
public void run() {
|
||||
deviceClientMonitorLoop();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
private void deviceClientMonitorLoop() {
|
||||
do {
|
||||
try {
|
||||
// This synchronized block stops us from doing the select() if a new
|
||||
// Device is being added.
|
||||
// @see startMonitoringDevice()
|
||||
synchronized (mDevices) {
|
||||
}
|
||||
|
||||
int count = mSelector.select();
|
||||
|
||||
if (mQuit) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (mClientsToReopen) {
|
||||
if (mClientsToReopen.size() > 0) {
|
||||
Set<Client> clients = mClientsToReopen.keySet();
|
||||
MonitorThread monitorThread = MonitorThread.getInstance();
|
||||
|
||||
for (Client client : clients) {
|
||||
Device device = client.getDevice();
|
||||
int pid = client.getClientData().getPid();
|
||||
|
||||
monitorThread.dropClient(client, false /* notify */);
|
||||
|
||||
// This is kinda bad, but if we don't wait a bit, the client
|
||||
// will never answer the second handshake!
|
||||
waitABit();
|
||||
|
||||
int port = mClientsToReopen.get(client);
|
||||
|
||||
if (port == IDebugPortProvider.NO_STATIC_PORT) {
|
||||
port = getNextDebuggerPort();
|
||||
}
|
||||
Log.d("DeviceMonitor", "Reopening " + client);
|
||||
openClient(device, pid, port, monitorThread);
|
||||
device.update(Device.CHANGE_CLIENT_LIST);
|
||||
}
|
||||
|
||||
mClientsToReopen.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Set<SelectionKey> keys = mSelector.selectedKeys();
|
||||
Iterator<SelectionKey> iter = keys.iterator();
|
||||
|
||||
while (iter.hasNext()) {
|
||||
SelectionKey key = iter.next();
|
||||
iter.remove();
|
||||
|
||||
if (key.isValid() && key.isReadable()) {
|
||||
Object attachment = key.attachment();
|
||||
|
||||
if (attachment instanceof Device) {
|
||||
Device device = (Device)attachment;
|
||||
|
||||
SocketChannel socket = device.getClientMonitoringSocket();
|
||||
|
||||
if (socket != null) {
|
||||
try {
|
||||
int length = readLength(socket, mLengthBuffer2);
|
||||
|
||||
processIncomingJdwpData(device, socket, length);
|
||||
} catch (IOException ioe) {
|
||||
Log.d("DeviceMonitor",
|
||||
"Error reading jdwp list: " + ioe.getMessage());
|
||||
socket.close();
|
||||
|
||||
// restart the monitoring of that device
|
||||
synchronized (mDevices) {
|
||||
if (mDevices.contains(device)) {
|
||||
Log.d("DeviceMonitor",
|
||||
"Restarting monitoring service for " + device);
|
||||
startMonitoringDevice(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (mQuit == false) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} while (mQuit == false);
|
||||
}
|
||||
|
||||
private boolean sendDeviceMonitoringRequest(SocketChannel socket, Device device)
|
||||
throws IOException {
|
||||
|
||||
AdbHelper.setDevice(socket, device);
|
||||
|
||||
byte[] request = AdbHelper.formAdbRequest("track-jdwp"); //$NON-NLS-1$
|
||||
|
||||
if (AdbHelper.write(socket, request) == false) {
|
||||
Log.e("DeviceMonitor", "Sending jdwp tracking request failed!");
|
||||
socket.close();
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
AdbResponse resp = AdbHelper.readAdbResponse(socket, false /* readDiagString */);
|
||||
|
||||
if (resp.ioSuccess == false) {
|
||||
Log.e("DeviceMonitor", "Failed to read the adb response!");
|
||||
socket.close();
|
||||
throw new IOException();
|
||||
}
|
||||
|
||||
if (resp.okay == false) {
|
||||
// request was refused by adb!
|
||||
Log.e("DeviceMonitor", "adb refused request: " + resp.message);
|
||||
}
|
||||
|
||||
return resp.okay;
|
||||
}
|
||||
|
||||
private void processIncomingJdwpData(Device device, SocketChannel monitorSocket, int length)
|
||||
throws IOException {
|
||||
if (length >= 0) {
|
||||
// array for the current pids.
|
||||
ArrayList<Integer> pidList = new ArrayList<Integer>();
|
||||
|
||||
// get the string data if there are any
|
||||
if (length > 0) {
|
||||
byte[] buffer = new byte[length];
|
||||
String result = read(monitorSocket, buffer);
|
||||
|
||||
// split each line in its own list and create an array of integer pid
|
||||
String[] pids = result.split("\n"); //$NON-NLS-1$
|
||||
|
||||
for (String pid : pids) {
|
||||
try {
|
||||
pidList.add(Integer.valueOf(pid));
|
||||
} catch (NumberFormatException nfe) {
|
||||
// looks like this pid is not really a number. Lets ignore it.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MonitorThread monitorThread = MonitorThread.getInstance();
|
||||
|
||||
// Now we merge the current list with the old one.
|
||||
// this is the same mechanism as the merging of the device list.
|
||||
|
||||
// For each client in the current list, we look for a matching the pid in the new list.
|
||||
// * if we find it, we do nothing, except removing the pid from its list,
|
||||
// to mark it as "processed"
|
||||
// * if we do not find any match, we remove the client from the current list.
|
||||
// Once this is done, the new list contains pids for which we don't have clients yet,
|
||||
// so we create clients for them, add them to the list, and start monitoring them.
|
||||
|
||||
List<Client> clients = device.getClientList();
|
||||
|
||||
boolean changed = false;
|
||||
|
||||
// because MonitorThread#dropClient acquires first the monitorThread lock and then the
|
||||
// Device client list lock (when removing the Client from the list), we have to make
|
||||
// sure we acquire the locks in the same order, since another thread (MonitorThread),
|
||||
// could call dropClient itself.
|
||||
synchronized (monitorThread) {
|
||||
synchronized (clients) {
|
||||
for (int c = 0 ; c < clients.size() ;) {
|
||||
Client client = clients.get(c);
|
||||
int pid = client.getClientData().getPid();
|
||||
|
||||
// look for a matching pid
|
||||
Integer match = null;
|
||||
for (Integer matchingPid : pidList) {
|
||||
if (pid == matchingPid.intValue()) {
|
||||
match = matchingPid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match != null) {
|
||||
pidList.remove(match);
|
||||
c++; // move on to the next client.
|
||||
} else {
|
||||
// we need to drop the client. the client will remove itself from the
|
||||
// list of its device which is 'clients', so there's no need to
|
||||
// increment c.
|
||||
// We ask the monitor thread to not send notification, as we'll do
|
||||
// it once at the end.
|
||||
monitorThread.dropClient(client, false /* notify */);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// at this point whatever pid is left in the list needs to be converted into Clients.
|
||||
for (int newPid : pidList) {
|
||||
openClient(device, newPid, getNextDebuggerPort(), monitorThread);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
mServer.deviceChanged(device, Device.CHANGE_CLIENT_LIST);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens and creates a new client.
|
||||
* @return
|
||||
*/
|
||||
private void openClient(Device device, int pid, int port, MonitorThread monitorThread) {
|
||||
|
||||
SocketChannel clientSocket;
|
||||
try {
|
||||
clientSocket = AdbHelper.createPassThroughConnection(
|
||||
AndroidDebugBridge.sSocketAddr, device, pid);
|
||||
|
||||
// required for Selector
|
||||
clientSocket.configureBlocking(false);
|
||||
} catch (UnknownHostException uhe) {
|
||||
Log.d("DeviceMonitor", "Unknown Jdwp pid: " + pid);
|
||||
return;
|
||||
} catch (IOException ioe) {
|
||||
Log.w("DeviceMonitor",
|
||||
"Failed to connect to client '" + pid + "': " + ioe.getMessage());
|
||||
return ;
|
||||
}
|
||||
|
||||
createClient(device, pid, clientSocket, port, monitorThread);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a client and register it to the monitor thread
|
||||
* @param device
|
||||
* @param pid
|
||||
* @param socket
|
||||
* @param debuggerPort the debugger port.
|
||||
* @param monitorThread the {@link MonitorThread} object.
|
||||
*/
|
||||
private void createClient(Device device, int pid, SocketChannel socket, int debuggerPort,
|
||||
MonitorThread monitorThread) {
|
||||
|
||||
/*
|
||||
* Successfully connected to something. Create a Client object, add
|
||||
* it to the list, and initiate the JDWP handshake.
|
||||
*/
|
||||
|
||||
Client client = new Client(device, socket, pid);
|
||||
|
||||
if (client.sendHandshake()) {
|
||||
try {
|
||||
if (AndroidDebugBridge.getClientSupport()) {
|
||||
client.listenForDebugger(debuggerPort);
|
||||
}
|
||||
client.requestAllocationStatus();
|
||||
} catch (IOException ioe) {
|
||||
client.getClientData().setDebuggerConnectionStatus(ClientData.DEBUGGER_ERROR);
|
||||
Log.e("ddms", "Can't bind to local " + debuggerPort + " for debugger");
|
||||
// oh well
|
||||
}
|
||||
} else {
|
||||
Log.e("ddms", "Handshake with " + client + " failed!");
|
||||
/*
|
||||
* The handshake send failed. We could remove it now, but if the
|
||||
* failure is "permanent" we'll just keep banging on it and
|
||||
* getting the same result. Keep it in the list with its "error"
|
||||
* state so we don't try to reopen it.
|
||||
*/
|
||||
}
|
||||
|
||||
if (client.isValid()) {
|
||||
device.addClient(client);
|
||||
monitorThread.addClient(client);
|
||||
} else {
|
||||
client = null;
|
||||
}
|
||||
}
|
||||
|
||||
private int getNextDebuggerPort() {
|
||||
// get the first port and remove it
|
||||
synchronized (mDebuggerPorts) {
|
||||
if (mDebuggerPorts.size() > 0) {
|
||||
int port = mDebuggerPorts.get(0);
|
||||
|
||||
// remove it.
|
||||
mDebuggerPorts.remove(0);
|
||||
|
||||
// if there's nothing left, add the next port to the list
|
||||
if (mDebuggerPorts.size() == 0) {
|
||||
mDebuggerPorts.add(port+1);
|
||||
}
|
||||
|
||||
return port;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void addPortToAvailableList(int port) {
|
||||
if (port > 0) {
|
||||
synchronized (mDebuggerPorts) {
|
||||
// because there could be case where clients are closed twice, we have to make
|
||||
// sure the port number is not already in the list.
|
||||
if (mDebuggerPorts.indexOf(port) == -1) {
|
||||
// add the port to the list while keeping it sorted. It's not like there's
|
||||
// going to be tons of objects so we do it linearly.
|
||||
int count = mDebuggerPorts.size();
|
||||
for (int i = 0 ; i < count ; i++) {
|
||||
if (port < mDebuggerPorts.get(i)) {
|
||||
mDebuggerPorts.add(i, port);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// TODO: check if we can compact the end of the list.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the length of the next message from a socket.
|
||||
* @param socket The {@link SocketChannel} to read from.
|
||||
* @return the length, or 0 (zero) if no data is available from the socket.
|
||||
* @throws IOException if the connection failed.
|
||||
*/
|
||||
private int readLength(SocketChannel socket, byte[] buffer) throws IOException {
|
||||
String msg = read(socket, buffer);
|
||||
|
||||
if (msg != null) {
|
||||
try {
|
||||
return Integer.parseInt(msg, 16);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// we'll throw an exception below.
|
||||
}
|
||||
}
|
||||
|
||||
// we receive something we can't read. It's better to reset the connection at this point.
|
||||
throw new IOException("Unable to read length");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills a buffer from a socket.
|
||||
* @param socket
|
||||
* @param buffer
|
||||
* @return the content of the buffer as a string, or null if it failed to convert the buffer.
|
||||
* @throws IOException
|
||||
*/
|
||||
private String read(SocketChannel socket, byte[] buffer) throws IOException {
|
||||
ByteBuffer buf = ByteBuffer.wrap(buffer, 0, buffer.length);
|
||||
|
||||
while (buf.position() != buf.limit()) {
|
||||
int count;
|
||||
|
||||
count = socket.read(buf);
|
||||
if (count < 0) {
|
||||
throw new IOException("EOF");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return new String(buffer, 0, buf.position(), AdbHelper.DEFAULT_ENCODING);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// we'll return null below.
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,751 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.security.InvalidParameterException;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Provides control over emulated hardware of the Android emulator.
|
||||
* <p/>This is basically a wrapper around the command line console normally used with telnet.
|
||||
*<p/>
|
||||
* Regarding line termination handling:<br>
|
||||
* One of the issues is that the telnet protocol <b>requires</b> usage of <code>\r\n</code>. Most
|
||||
* implementations don't enforce it (the dos one does). In this particular case, this is mostly
|
||||
* irrelevant since we don't use telnet in Java, but that means we want to make
|
||||
* sure we use the same line termination than what the console expects. The console
|
||||
* code removes <code>\r</code> and waits for <code>\n</code>.
|
||||
* <p/>However this means you <i>may</i> receive <code>\r\n</code> when reading from the console.
|
||||
* <p/>
|
||||
* <b>This API will change in the near future.</b>
|
||||
*/
|
||||
public final class EmulatorConsole {
|
||||
|
||||
private final static String DEFAULT_ENCODING = "ISO-8859-1"; //$NON-NLS-1$
|
||||
|
||||
private final static int WAIT_TIME = 5; // spin-wait sleep, in ms
|
||||
|
||||
private final static int STD_TIMEOUT = 5000; // standard delay, in ms
|
||||
|
||||
private final static String HOST = "127.0.0.1"; //$NON-NLS-1$
|
||||
|
||||
private final static String COMMAND_PING = "help\r\n"; //$NON-NLS-1$
|
||||
private final static String COMMAND_AVD_NAME = "avd name\r\n"; //$NON-NLS-1$
|
||||
private final static String COMMAND_KILL = "kill\r\n"; //$NON-NLS-1$
|
||||
private final static String COMMAND_GSM_STATUS = "gsm status\r\n"; //$NON-NLS-1$
|
||||
private final static String COMMAND_GSM_CALL = "gsm call %1$s\r\n"; //$NON-NLS-1$
|
||||
private final static String COMMAND_GSM_CANCEL_CALL = "gsm cancel %1$s\r\n"; //$NON-NLS-1$
|
||||
private final static String COMMAND_GSM_DATA = "gsm data %1$s\r\n"; //$NON-NLS-1$
|
||||
private final static String COMMAND_GSM_VOICE = "gsm voice %1$s\r\n"; //$NON-NLS-1$
|
||||
private final static String COMMAND_SMS_SEND = "sms send %1$s %2$s\r\n"; //$NON-NLS-1$
|
||||
private final static String COMMAND_NETWORK_STATUS = "network status\r\n"; //$NON-NLS-1$
|
||||
private final static String COMMAND_NETWORK_SPEED = "network speed %1$s\r\n"; //$NON-NLS-1$
|
||||
private final static String COMMAND_NETWORK_LATENCY = "network delay %1$s\r\n"; //$NON-NLS-1$
|
||||
private final static String COMMAND_GPS =
|
||||
"geo nmea $GPGGA,%1$02d%2$02d%3$02d.%4$03d," + //$NON-NLS-1$
|
||||
"%5$03d%6$09.6f,%7$c,%8$03d%9$09.6f,%10$c," + //$NON-NLS-1$
|
||||
"1,10,0.0,0.0,0,0.0,0,0.0,0000\r\n"; //$NON-NLS-1$
|
||||
|
||||
private final static Pattern RE_KO = Pattern.compile("KO:\\s+(.*)"); //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Array of delay values: no delay, gprs, edge/egprs, umts/3d
|
||||
*/
|
||||
public final static int[] MIN_LATENCIES = new int[] {
|
||||
0, // No delay
|
||||
150, // gprs
|
||||
80, // edge/egprs
|
||||
35 // umts/3g
|
||||
};
|
||||
|
||||
/**
|
||||
* Array of download speeds: full speed, gsm, hscsd, gprs, edge/egprs, umts/3g, hsdpa.
|
||||
*/
|
||||
public final int[] DOWNLOAD_SPEEDS = new int[] {
|
||||
0, // full speed
|
||||
14400, // gsm
|
||||
43200, // hscsd
|
||||
80000, // gprs
|
||||
236800, // edge/egprs
|
||||
1920000, // umts/3g
|
||||
14400000 // hsdpa
|
||||
};
|
||||
|
||||
/** Arrays of valid network speeds */
|
||||
public final static String[] NETWORK_SPEEDS = new String[] {
|
||||
"full", //$NON-NLS-1$
|
||||
"gsm", //$NON-NLS-1$
|
||||
"hscsd", //$NON-NLS-1$
|
||||
"gprs", //$NON-NLS-1$
|
||||
"edge", //$NON-NLS-1$
|
||||
"umts", //$NON-NLS-1$
|
||||
"hsdpa", //$NON-NLS-1$
|
||||
};
|
||||
|
||||
/** Arrays of valid network latencies */
|
||||
public final static String[] NETWORK_LATENCIES = new String[] {
|
||||
"none", //$NON-NLS-1$
|
||||
"gprs", //$NON-NLS-1$
|
||||
"edge", //$NON-NLS-1$
|
||||
"umts", //$NON-NLS-1$
|
||||
};
|
||||
|
||||
/** Gsm Mode enum. */
|
||||
public static enum GsmMode {
|
||||
UNKNOWN((String)null),
|
||||
UNREGISTERED(new String[] { "unregistered", "off" }),
|
||||
HOME(new String[] { "home", "on" }),
|
||||
ROAMING("roaming"),
|
||||
SEARCHING("searching"),
|
||||
DENIED("denied");
|
||||
|
||||
private final String[] tags;
|
||||
|
||||
GsmMode(String tag) {
|
||||
if (tag != null) {
|
||||
this.tags = new String[] { tag };
|
||||
} else {
|
||||
this.tags = new String[0];
|
||||
}
|
||||
}
|
||||
|
||||
GsmMode(String[] tags) {
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
public static GsmMode getEnum(String tag) {
|
||||
for (GsmMode mode : values()) {
|
||||
for (String t : mode.tags) {
|
||||
if (t.equals(tag)) {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first tag of the enum.
|
||||
*/
|
||||
public String getTag() {
|
||||
if (tags.length > 0) {
|
||||
return tags[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public final static String RESULT_OK = null;
|
||||
|
||||
private final static Pattern sEmulatorRegexp = Pattern.compile(Device.RE_EMULATOR_SN);
|
||||
private final static Pattern sVoiceStatusRegexp = Pattern.compile(
|
||||
"gsm\\s+voice\\s+state:\\s*([a-z]+)", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
|
||||
private final static Pattern sDataStatusRegexp = Pattern.compile(
|
||||
"gsm\\s+data\\s+state:\\s*([a-z]+)", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
|
||||
private final static Pattern sDownloadSpeedRegexp = Pattern.compile(
|
||||
"\\s+download\\s+speed:\\s+(\\d+)\\s+bits.*", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
|
||||
private final static Pattern sMinLatencyRegexp = Pattern.compile(
|
||||
"\\s+minimum\\s+latency:\\s+(\\d+)\\s+ms", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
|
||||
|
||||
private final static HashMap<Integer, EmulatorConsole> sEmulators =
|
||||
new HashMap<Integer, EmulatorConsole>();
|
||||
|
||||
/** Gsm Status class */
|
||||
public static class GsmStatus {
|
||||
/** Voice status. */
|
||||
public GsmMode voice = GsmMode.UNKNOWN;
|
||||
/** Data status. */
|
||||
public GsmMode data = GsmMode.UNKNOWN;
|
||||
}
|
||||
|
||||
/** Network Status class */
|
||||
public static class NetworkStatus {
|
||||
/** network speed status. This is an index in the {@link #DOWNLOAD_SPEEDS} array. */
|
||||
public int speed = -1;
|
||||
/** network latency status. This is an index in the {@link #MIN_LATENCIES} array. */
|
||||
public int latency = -1;
|
||||
}
|
||||
|
||||
private int mPort;
|
||||
|
||||
private SocketChannel mSocketChannel;
|
||||
|
||||
private byte[] mBuffer = new byte[1024];
|
||||
|
||||
/**
|
||||
* Returns an {@link EmulatorConsole} object for the given {@link Device}. This can
|
||||
* be an already existing console, or a new one if it hadn't been created yet.
|
||||
* @param d The device that the console links to.
|
||||
* @return an <code>EmulatorConsole</code> object or <code>null</code> if the connection failed.
|
||||
*/
|
||||
public static synchronized EmulatorConsole getConsole(Device d) {
|
||||
// we need to make sure that the device is an emulator
|
||||
Matcher m = sEmulatorRegexp.matcher(d.serialNumber);
|
||||
if (m.matches()) {
|
||||
// get the port number. This is the console port.
|
||||
int port;
|
||||
try {
|
||||
port = Integer.parseInt(m.group(1));
|
||||
if (port <= 0) {
|
||||
return null;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// looks like we failed to get the port number. This is a bit strange since
|
||||
// it's coming from a regexp that only accept digit, but we handle the case
|
||||
// and return null.
|
||||
return null;
|
||||
}
|
||||
|
||||
EmulatorConsole console = sEmulators.get(port);
|
||||
|
||||
if (console != null) {
|
||||
// if the console exist, we ping the emulator to check the connection.
|
||||
if (console.ping() == false) {
|
||||
RemoveConsole(console.mPort);
|
||||
console = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (console == null) {
|
||||
// no console object exists for this port so we create one, and start
|
||||
// the connection.
|
||||
console = new EmulatorConsole(port);
|
||||
if (console.start()) {
|
||||
sEmulators.put(port, console);
|
||||
} else {
|
||||
console = null;
|
||||
}
|
||||
}
|
||||
|
||||
return console;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the console object associated with a port from the map.
|
||||
* @param port The port of the console to remove.
|
||||
*/
|
||||
private static synchronized void RemoveConsole(int port) {
|
||||
sEmulators.remove(port);
|
||||
}
|
||||
|
||||
private EmulatorConsole(int port) {
|
||||
super();
|
||||
mPort = port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the connection of the console.
|
||||
* @return true if success.
|
||||
*/
|
||||
private boolean start() {
|
||||
|
||||
InetSocketAddress socketAddr;
|
||||
try {
|
||||
InetAddress hostAddr = InetAddress.getByName(HOST);
|
||||
socketAddr = new InetSocketAddress(hostAddr, mPort);
|
||||
} catch (UnknownHostException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
mSocketChannel = SocketChannel.open(socketAddr);
|
||||
} catch (IOException e1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// read some stuff from it
|
||||
readLines();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping the emulator to check if the connection is still alive.
|
||||
* @return true if the connection is alive.
|
||||
*/
|
||||
private synchronized boolean ping() {
|
||||
// it looks like we can send stuff, even when the emulator quit, but we can't read
|
||||
// from the socket. So we check the return of readLines()
|
||||
if (sendCommand(COMMAND_PING)) {
|
||||
return readLines() != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a KILL command to the emulator.
|
||||
*/
|
||||
public synchronized void kill() {
|
||||
if (sendCommand(COMMAND_KILL)) {
|
||||
RemoveConsole(mPort);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized String getAvdName() {
|
||||
if (sendCommand(COMMAND_AVD_NAME)) {
|
||||
String[] result = readLines();
|
||||
if (result != null && result.length == 2) { // this should be the name on first line,
|
||||
// and ok on 2nd line
|
||||
return result[0];
|
||||
} else {
|
||||
// try to see if there's a message after KO
|
||||
Matcher m = RE_KO.matcher(result[result.length-1]);
|
||||
if (m.matches()) {
|
||||
return m.group(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the network status of the emulator.
|
||||
* @return a {@link NetworkStatus} object containing the {@link GsmStatus}, or
|
||||
* <code>null</code> if the query failed.
|
||||
*/
|
||||
public synchronized NetworkStatus getNetworkStatus() {
|
||||
if (sendCommand(COMMAND_NETWORK_STATUS)) {
|
||||
/* Result is in the format
|
||||
Current network status:
|
||||
download speed: 14400 bits/s (1.8 KB/s)
|
||||
upload speed: 14400 bits/s (1.8 KB/s)
|
||||
minimum latency: 0 ms
|
||||
maximum latency: 0 ms
|
||||
*/
|
||||
String[] result = readLines();
|
||||
|
||||
if (isValid(result)) {
|
||||
// we only compare agains the min latency and the download speed
|
||||
// let's not rely on the order of the output, and simply loop through
|
||||
// the line testing the regexp.
|
||||
NetworkStatus status = new NetworkStatus();
|
||||
for (String line : result) {
|
||||
Matcher m = sDownloadSpeedRegexp.matcher(line);
|
||||
if (m.matches()) {
|
||||
// get the string value
|
||||
String value = m.group(1);
|
||||
|
||||
// get the index from the list
|
||||
status.speed = getSpeedIndex(value);
|
||||
|
||||
// move on to next line.
|
||||
continue;
|
||||
}
|
||||
|
||||
m = sMinLatencyRegexp.matcher(line);
|
||||
if (m.matches()) {
|
||||
// get the string value
|
||||
String value = m.group(1);
|
||||
|
||||
// get the index from the list
|
||||
status.latency = getLatencyIndex(value);
|
||||
|
||||
// move on to next line.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current gsm status of the emulator
|
||||
* @return a {@link GsmStatus} object containing the gms status, or <code>null</code>
|
||||
* if the query failed.
|
||||
*/
|
||||
public synchronized GsmStatus getGsmStatus() {
|
||||
if (sendCommand(COMMAND_GSM_STATUS)) {
|
||||
/*
|
||||
* result is in the format:
|
||||
* gsm status
|
||||
* gsm voice state: home
|
||||
* gsm data state: home
|
||||
*/
|
||||
|
||||
String[] result = readLines();
|
||||
if (isValid(result)) {
|
||||
|
||||
GsmStatus status = new GsmStatus();
|
||||
|
||||
// let's not rely on the order of the output, and simply loop through
|
||||
// the line testing the regexp.
|
||||
for (String line : result) {
|
||||
Matcher m = sVoiceStatusRegexp.matcher(line);
|
||||
if (m.matches()) {
|
||||
// get the string value
|
||||
String value = m.group(1);
|
||||
|
||||
// get the index from the list
|
||||
status.voice = GsmMode.getEnum(value.toLowerCase());
|
||||
|
||||
// move on to next line.
|
||||
continue;
|
||||
}
|
||||
|
||||
m = sDataStatusRegexp.matcher(line);
|
||||
if (m.matches()) {
|
||||
// get the string value
|
||||
String value = m.group(1);
|
||||
|
||||
// get the index from the list
|
||||
status.data = GsmMode.getEnum(value.toLowerCase());
|
||||
|
||||
// move on to next line.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the GSM voice mode.
|
||||
* @param mode the {@link GsmMode} value.
|
||||
* @return RESULT_OK if success, an error String otherwise.
|
||||
* @throws InvalidParameterException if mode is an invalid value.
|
||||
*/
|
||||
public synchronized String setGsmVoiceMode(GsmMode mode) throws InvalidParameterException {
|
||||
if (mode == GsmMode.UNKNOWN) {
|
||||
throw new InvalidParameterException();
|
||||
}
|
||||
|
||||
String command = String.format(COMMAND_GSM_VOICE, mode.getTag());
|
||||
return processCommand(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the GSM data mode.
|
||||
* @param mode the {@link GsmMode} value
|
||||
* @return {@link #RESULT_OK} if success, an error String otherwise.
|
||||
* @throws InvalidParameterException if mode is an invalid value.
|
||||
*/
|
||||
public synchronized String setGsmDataMode(GsmMode mode) throws InvalidParameterException {
|
||||
if (mode == GsmMode.UNKNOWN) {
|
||||
throw new InvalidParameterException();
|
||||
}
|
||||
|
||||
String command = String.format(COMMAND_GSM_DATA, mode.getTag());
|
||||
return processCommand(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate an incoming call on the emulator.
|
||||
* @param number a string representing the calling number.
|
||||
* @return {@link #RESULT_OK} if success, an error String otherwise.
|
||||
*/
|
||||
public synchronized String call(String number) {
|
||||
String command = String.format(COMMAND_GSM_CALL, number);
|
||||
return processCommand(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a current call.
|
||||
* @param number the number of the call to cancel
|
||||
* @return {@link #RESULT_OK} if success, an error String otherwise.
|
||||
*/
|
||||
public synchronized String cancelCall(String number) {
|
||||
String command = String.format(COMMAND_GSM_CANCEL_CALL, number);
|
||||
return processCommand(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an SMS to the emulator
|
||||
* @param number The sender phone number
|
||||
* @param message The SMS message. \ characters must be escaped. The carriage return is
|
||||
* the 2 character sequence {'\', 'n' }
|
||||
*
|
||||
* @return {@link #RESULT_OK} if success, an error String otherwise.
|
||||
*/
|
||||
public synchronized String sendSms(String number, String message) {
|
||||
String command = String.format(COMMAND_SMS_SEND, number, message);
|
||||
return processCommand(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the network speed.
|
||||
* @param selectionIndex The index in the {@link #NETWORK_SPEEDS} table.
|
||||
* @return {@link #RESULT_OK} if success, an error String otherwise.
|
||||
*/
|
||||
public synchronized String setNetworkSpeed(int selectionIndex) {
|
||||
String command = String.format(COMMAND_NETWORK_SPEED, NETWORK_SPEEDS[selectionIndex]);
|
||||
return processCommand(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the network latency.
|
||||
* @param selectionIndex The index in the {@link #NETWORK_LATENCIES} table.
|
||||
* @return {@link #RESULT_OK} if success, an error String otherwise.
|
||||
*/
|
||||
public synchronized String setNetworkLatency(int selectionIndex) {
|
||||
String command = String.format(COMMAND_NETWORK_LATENCY, NETWORK_LATENCIES[selectionIndex]);
|
||||
return processCommand(command);
|
||||
}
|
||||
|
||||
public synchronized String sendLocation(double longitude, double latitude, double elevation) {
|
||||
|
||||
Calendar c = Calendar.getInstance();
|
||||
|
||||
double absLong = Math.abs(longitude);
|
||||
int longDegree = (int)Math.floor(absLong);
|
||||
char longDirection = 'E';
|
||||
if (longitude < 0) {
|
||||
longDirection = 'W';
|
||||
}
|
||||
|
||||
double longMinute = (absLong - Math.floor(absLong)) * 60;
|
||||
|
||||
double absLat = Math.abs(latitude);
|
||||
int latDegree = (int)Math.floor(absLat);
|
||||
char latDirection = 'N';
|
||||
if (latitude < 0) {
|
||||
latDirection = 'S';
|
||||
}
|
||||
|
||||
double latMinute = (absLat - Math.floor(absLat)) * 60;
|
||||
|
||||
String command = String.format(COMMAND_GPS,
|
||||
c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE),
|
||||
c.get(Calendar.SECOND), c.get(Calendar.MILLISECOND),
|
||||
latDegree, latMinute, latDirection,
|
||||
longDegree, longMinute, longDirection);
|
||||
|
||||
return processCommand(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a command to the emulator console.
|
||||
* @param command The command string. <b>MUST BE TERMINATED BY \n</b>.
|
||||
* @return true if success
|
||||
*/
|
||||
private boolean sendCommand(String command) {
|
||||
boolean result = false;
|
||||
try {
|
||||
byte[] bCommand;
|
||||
try {
|
||||
bCommand = command.getBytes(DEFAULT_ENCODING);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// wrong encoding...
|
||||
return result;
|
||||
}
|
||||
|
||||
// write the command
|
||||
AdbHelper.write(mSocketChannel, bCommand, bCommand.length, AdbHelper.STD_TIMEOUT);
|
||||
|
||||
result = true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
} finally {
|
||||
if (result == false) {
|
||||
// FIXME connection failed somehow, we need to disconnect the console.
|
||||
RemoveConsole(mPort);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a command to the emulator and parses its answer.
|
||||
* @param command the command to send.
|
||||
* @return {@link #RESULT_OK} if success, an error message otherwise.
|
||||
*/
|
||||
private String processCommand(String command) {
|
||||
if (sendCommand(command)) {
|
||||
String[] result = readLines();
|
||||
|
||||
if (result != null && result.length > 0) {
|
||||
Matcher m = RE_KO.matcher(result[result.length-1]);
|
||||
if (m.matches()) {
|
||||
return m.group(1);
|
||||
}
|
||||
return RESULT_OK;
|
||||
}
|
||||
|
||||
return "Unable to communicate with the emulator";
|
||||
}
|
||||
|
||||
return "Unable to send command to the emulator";
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads line from the console socket. This call is blocking until we read the lines:
|
||||
* <ul>
|
||||
* <li>OK\r\n</li>
|
||||
* <li>KO<msg>\r\n</li>
|
||||
* </ul>
|
||||
* @return the array of strings read from the emulator.
|
||||
*/
|
||||
private String[] readLines() {
|
||||
try {
|
||||
ByteBuffer buf = ByteBuffer.wrap(mBuffer, 0, mBuffer.length);
|
||||
int numWaits = 0;
|
||||
boolean stop = false;
|
||||
|
||||
while (buf.position() != buf.limit() && stop == false) {
|
||||
int count;
|
||||
|
||||
count = mSocketChannel.read(buf);
|
||||
if (count < 0) {
|
||||
return null;
|
||||
} else if (count == 0) {
|
||||
if (numWaits * WAIT_TIME > STD_TIMEOUT) {
|
||||
return null;
|
||||
}
|
||||
// non-blocking spin
|
||||
try {
|
||||
Thread.sleep(WAIT_TIME);
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
numWaits++;
|
||||
} else {
|
||||
numWaits = 0;
|
||||
}
|
||||
|
||||
// check the last few char aren't OK. For a valid message to test
|
||||
// we need at least 4 bytes (OK/KO + \r\n)
|
||||
if (buf.position() >= 4) {
|
||||
int pos = buf.position();
|
||||
if (endsWithOK(pos) || lastLineIsKO(pos)) {
|
||||
stop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String msg = new String(mBuffer, 0, buf.position(), DEFAULT_ENCODING);
|
||||
return msg.split("\r\n"); //$NON-NLS-1$
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the 4 characters *before* the current position are "OK\r\n"
|
||||
* @param currentPosition The current position
|
||||
*/
|
||||
private boolean endsWithOK(int currentPosition) {
|
||||
if (mBuffer[currentPosition-1] == '\n' &&
|
||||
mBuffer[currentPosition-2] == '\r' &&
|
||||
mBuffer[currentPosition-3] == 'K' &&
|
||||
mBuffer[currentPosition-4] == 'O') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the last line starts with KO and is also terminated by \r\n
|
||||
* @param currentPosition the current position
|
||||
*/
|
||||
private boolean lastLineIsKO(int currentPosition) {
|
||||
// first check that the last 2 characters are CRLF
|
||||
if (mBuffer[currentPosition-1] != '\n' ||
|
||||
mBuffer[currentPosition-2] != '\r') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// now loop backward looking for the previous CRLF, or the beginning of the buffer
|
||||
int i = 0;
|
||||
for (i = currentPosition-3 ; i >= 0; i--) {
|
||||
if (mBuffer[i] == '\n') {
|
||||
// found \n!
|
||||
if (i > 0 && mBuffer[i-1] == '\r') {
|
||||
// found \r!
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// here it is either -1 if we reached the start of the buffer without finding
|
||||
// a CRLF, or the position of \n. So in both case we look at the characters at i+1 and i+2
|
||||
if (mBuffer[i+1] == 'K' && mBuffer[i+2] == 'O') {
|
||||
// found error!
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the last line of the result does not start with KO
|
||||
*/
|
||||
private boolean isValid(String[] result) {
|
||||
if (result != null && result.length > 0) {
|
||||
return !(RE_KO.matcher(result[result.length-1]).matches());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private int getLatencyIndex(String value) {
|
||||
try {
|
||||
// get the int value
|
||||
int latency = Integer.parseInt(value);
|
||||
|
||||
// check for the speed from the index
|
||||
for (int i = 0 ; i < MIN_LATENCIES.length; i++) {
|
||||
if (MIN_LATENCIES[i] == latency) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// Do nothing, we'll just return -1.
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int getSpeedIndex(String value) {
|
||||
try {
|
||||
// get the int value
|
||||
int speed = Integer.parseInt(value);
|
||||
|
||||
// check for the speed from the index
|
||||
for (int i = 0 ; i < DOWNLOAD_SPEEDS.length; i++) {
|
||||
if (DOWNLOAD_SPEEDS[i] == speed) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// Do nothing, we'll just return -1.
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,767 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Provides {@link Device} side file listing service.
|
||||
* <p/>To get an instance for a known {@link Device}, call {@link Device#getFileListingService()}.
|
||||
*/
|
||||
public final class FileListingService {
|
||||
|
||||
/** Pattern to find filenames that match "*.apk" */
|
||||
private final static Pattern sApkPattern =
|
||||
Pattern.compile(".*\\.apk", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$
|
||||
|
||||
private final static String PM_FULL_LISTING = "pm list packages -f"; //$NON-NLS-1$
|
||||
|
||||
/** Pattern to parse the output of the 'pm -lf' command.<br>
|
||||
* The output format looks like:<br>
|
||||
* /data/app/myapp.apk=com.mypackage.myapp */
|
||||
private final static Pattern sPmPattern = Pattern.compile("^package:(.+?)=(.+)$"); //$NON-NLS-1$
|
||||
|
||||
/** Top level data folder. */
|
||||
public final static String DIRECTORY_DATA = "data"; //$NON-NLS-1$
|
||||
/** Top level sdcard folder. */
|
||||
public final static String DIRECTORY_SDCARD = "sdcard"; //$NON-NLS-1$
|
||||
/** Top level system folder. */
|
||||
public final static String DIRECTORY_SYSTEM = "system"; //$NON-NLS-1$
|
||||
/** Top level temp folder. */
|
||||
public final static String DIRECTORY_TEMP = "tmp"; //$NON-NLS-1$
|
||||
/** Application folder. */
|
||||
public final static String DIRECTORY_APP = "app"; //$NON-NLS-1$
|
||||
|
||||
private final static String[] sRootLevelApprovedItems = {
|
||||
DIRECTORY_DATA,
|
||||
DIRECTORY_SDCARD,
|
||||
DIRECTORY_SYSTEM,
|
||||
DIRECTORY_TEMP
|
||||
};
|
||||
|
||||
public static final long REFRESH_RATE = 5000L;
|
||||
/**
|
||||
* Refresh test has to be slightly lower for precision issue.
|
||||
*/
|
||||
static final long REFRESH_TEST = (long)(REFRESH_RATE * .8);
|
||||
|
||||
/** Entry type: File */
|
||||
public static final int TYPE_FILE = 0;
|
||||
/** Entry type: Directory */
|
||||
public static final int TYPE_DIRECTORY = 1;
|
||||
/** Entry type: Directory Link */
|
||||
public static final int TYPE_DIRECTORY_LINK = 2;
|
||||
/** Entry type: Block */
|
||||
public static final int TYPE_BLOCK = 3;
|
||||
/** Entry type: Character */
|
||||
public static final int TYPE_CHARACTER = 4;
|
||||
/** Entry type: Link */
|
||||
public static final int TYPE_LINK = 5;
|
||||
/** Entry type: Socket */
|
||||
public static final int TYPE_SOCKET = 6;
|
||||
/** Entry type: FIFO */
|
||||
public static final int TYPE_FIFO = 7;
|
||||
/** Entry type: Other */
|
||||
public static final int TYPE_OTHER = 8;
|
||||
|
||||
/** Device side file separator. */
|
||||
public static final String FILE_SEPARATOR = "/"; //$NON-NLS-1$
|
||||
|
||||
private static final String FILE_ROOT = "/"; //$NON-NLS-1$
|
||||
|
||||
|
||||
/**
|
||||
* Regexp pattern to parse the result from ls.
|
||||
*/
|
||||
private static Pattern sLsPattern = Pattern.compile(
|
||||
"^([bcdlsp-][-r][-w][-xsS][-r][-w][-xsS][-r][-w][-xstST])\\s+(\\S+)\\s+(\\S+)\\s+([\\d\\s,]*)\\s+(\\d{4}-\\d\\d-\\d\\d)\\s+(\\d\\d:\\d\\d)\\s+(.*)$"); //$NON-NLS-1$
|
||||
|
||||
private Device mDevice;
|
||||
private FileEntry mRoot;
|
||||
|
||||
private ArrayList<Thread> mThreadList = new ArrayList<Thread>();
|
||||
|
||||
/**
|
||||
* Represents an entry in a directory. This can be a file or a directory.
|
||||
*/
|
||||
public final static class FileEntry {
|
||||
/** Pattern to escape filenames for shell command consumption. */
|
||||
private final static Pattern sEscapePattern = Pattern.compile(
|
||||
"([\\\\()*+?\"'#/\\s])"); //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Comparator object for FileEntry
|
||||
*/
|
||||
private static Comparator<FileEntry> sEntryComparator = new Comparator<FileEntry>() {
|
||||
public int compare(FileEntry o1, FileEntry o2) {
|
||||
if (o1 instanceof FileEntry && o2 instanceof FileEntry) {
|
||||
FileEntry fe1 = (FileEntry)o1;
|
||||
FileEntry fe2 = (FileEntry)o2;
|
||||
return fe1.name.compareTo(fe2.name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
FileEntry parent;
|
||||
String name;
|
||||
String info;
|
||||
String permissions;
|
||||
String size;
|
||||
String date;
|
||||
String time;
|
||||
String owner;
|
||||
String group;
|
||||
int type;
|
||||
boolean isAppPackage;
|
||||
|
||||
boolean isRoot;
|
||||
|
||||
/**
|
||||
* Indicates whether the entry content has been fetched yet, or not.
|
||||
*/
|
||||
long fetchTime = 0;
|
||||
|
||||
final ArrayList<FileEntry> mChildren = new ArrayList<FileEntry>();
|
||||
|
||||
/**
|
||||
* Creates a new file entry.
|
||||
* @param parent parent entry or null if entry is root
|
||||
* @param name name of the entry.
|
||||
* @param type entry type. Can be one of the following: {@link FileListingService#TYPE_FILE},
|
||||
* {@link FileListingService#TYPE_DIRECTORY}, {@link FileListingService#TYPE_OTHER}.
|
||||
*/
|
||||
private FileEntry(FileEntry parent, String name, int type, boolean isRoot) {
|
||||
this.parent = parent;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.isRoot = isRoot;
|
||||
|
||||
checkAppPackageStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the entry
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size string of the entry, as returned by <code>ls</code>.
|
||||
*/
|
||||
public String getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the entry.
|
||||
*/
|
||||
public int getSizeValue() {
|
||||
return Integer.parseInt(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the date string of the entry, as returned by <code>ls</code>.
|
||||
*/
|
||||
public String getDate() {
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time string of the entry, as returned by <code>ls</code>.
|
||||
*/
|
||||
public String getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the permission string of the entry, as returned by <code>ls</code>.
|
||||
*/
|
||||
public String getPermissions() {
|
||||
return permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extra info for the entry.
|
||||
* <p/>For a link, it will be a description of the link.
|
||||
* <p/>For an application apk file it will be the application package as returned
|
||||
* by the Package Manager.
|
||||
*/
|
||||
public String getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the full path of the entry.
|
||||
* @return a path string using {@link FileListingService#FILE_SEPARATOR} as separator.
|
||||
*/
|
||||
public String getFullPath() {
|
||||
if (isRoot) {
|
||||
return FILE_ROOT;
|
||||
}
|
||||
StringBuilder pathBuilder = new StringBuilder();
|
||||
fillPathBuilder(pathBuilder, false);
|
||||
|
||||
return pathBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fully escaped path of the entry. This path is safe to use in a
|
||||
* shell command line.
|
||||
* @return a path string using {@link FileListingService#FILE_SEPARATOR} as separator
|
||||
*/
|
||||
public String getFullEscapedPath() {
|
||||
StringBuilder pathBuilder = new StringBuilder();
|
||||
fillPathBuilder(pathBuilder, true);
|
||||
|
||||
return pathBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path as a list of segments.
|
||||
*/
|
||||
public String[] getPathSegments() {
|
||||
ArrayList<String> list = new ArrayList<String>();
|
||||
fillPathSegments(list);
|
||||
|
||||
return list.toArray(new String[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the entry is a directory, false otherwise;
|
||||
*/
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the entry is a folder or a link to a folder.
|
||||
*/
|
||||
public boolean isDirectory() {
|
||||
return type == TYPE_DIRECTORY || type == TYPE_DIRECTORY_LINK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent entry.
|
||||
*/
|
||||
public FileEntry getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cached children of the entry. This returns the cache created from calling
|
||||
* <code>FileListingService.getChildren()</code>.
|
||||
*/
|
||||
public FileEntry[] getCachedChildren() {
|
||||
return mChildren.toArray(new FileEntry[mChildren.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the child {@link FileEntry} matching the name.
|
||||
* This uses the cached children list.
|
||||
* @param name the name of the child to return.
|
||||
* @return the FileEntry matching the name or null.
|
||||
*/
|
||||
public FileEntry findChild(String name) {
|
||||
for (FileEntry entry : mChildren) {
|
||||
if (entry.name.equals(name)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the entry is the root.
|
||||
*/
|
||||
public boolean isRoot() {
|
||||
return isRoot;
|
||||
}
|
||||
|
||||
void addChild(FileEntry child) {
|
||||
mChildren.add(child);
|
||||
}
|
||||
|
||||
void setChildren(ArrayList<FileEntry> newChildren) {
|
||||
mChildren.clear();
|
||||
mChildren.addAll(newChildren);
|
||||
}
|
||||
|
||||
boolean needFetch() {
|
||||
if (fetchTime == 0) {
|
||||
return true;
|
||||
}
|
||||
long current = System.currentTimeMillis();
|
||||
if (current-fetchTime > REFRESH_TEST) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the entry is a valid application package.
|
||||
*/
|
||||
public boolean isApplicationPackage() {
|
||||
return isAppPackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the file name is an application package name.
|
||||
*/
|
||||
public boolean isAppFileName() {
|
||||
Matcher m = sApkPattern.matcher(name);
|
||||
return m.matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively fills the pathBuilder with the full path
|
||||
* @param pathBuilder a StringBuilder used to create the path.
|
||||
* @param escapePath Whether the path need to be escaped for consumption by
|
||||
* a shell command line.
|
||||
*/
|
||||
protected void fillPathBuilder(StringBuilder pathBuilder, boolean escapePath) {
|
||||
if (isRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parent != null) {
|
||||
parent.fillPathBuilder(pathBuilder, escapePath);
|
||||
}
|
||||
pathBuilder.append(FILE_SEPARATOR);
|
||||
pathBuilder.append(escapePath ? escape(name) : name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively fills the segment list with the full path.
|
||||
* @param list The list of segments to fill.
|
||||
*/
|
||||
protected void fillPathSegments(ArrayList<String> list) {
|
||||
if (isRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parent != null) {
|
||||
parent.fillPathSegments(list);
|
||||
}
|
||||
|
||||
list.add(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal app package status flag. This checks whether the entry is in an app
|
||||
* directory like /data/app or /system/app
|
||||
*/
|
||||
private void checkAppPackageStatus() {
|
||||
isAppPackage = false;
|
||||
|
||||
String[] segments = getPathSegments();
|
||||
if (type == TYPE_FILE && segments.length == 3 && isAppFileName()) {
|
||||
isAppPackage = DIRECTORY_APP.equals(segments[1]) &&
|
||||
(DIRECTORY_SYSTEM.equals(segments[0]) || DIRECTORY_DATA.equals(segments[0]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an escaped version of the entry name.
|
||||
* @param entryName
|
||||
*/
|
||||
private String escape(String entryName) {
|
||||
return sEscapePattern.matcher(entryName).replaceAll("\\\\$1"); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
|
||||
private class LsReceiver extends MultiLineReceiver {
|
||||
|
||||
private ArrayList<FileEntry> mEntryList;
|
||||
private ArrayList<String> mLinkList;
|
||||
private FileEntry[] mCurrentChildren;
|
||||
private FileEntry mParentEntry;
|
||||
|
||||
/**
|
||||
* Create an ls receiver/parser.
|
||||
* @param currentChildren The list of current children. To prevent
|
||||
* collapse during update, reusing the same FileEntry objects for
|
||||
* files that were already there is paramount.
|
||||
* @param entryList the list of new children to be filled by the
|
||||
* receiver.
|
||||
* @param linkList the list of link path to compute post ls, to figure
|
||||
* out if the link pointed to a file or to a directory.
|
||||
*/
|
||||
public LsReceiver(FileEntry parentEntry, ArrayList<FileEntry> entryList,
|
||||
ArrayList<String> linkList) {
|
||||
mParentEntry = parentEntry;
|
||||
mCurrentChildren = parentEntry.getCachedChildren();
|
||||
mEntryList = entryList;
|
||||
mLinkList = linkList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processNewLines(String[] lines) {
|
||||
for (String line : lines) {
|
||||
// no need to handle empty lines.
|
||||
if (line.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// run the line through the regexp
|
||||
Matcher m = sLsPattern.matcher(line);
|
||||
if (m.matches() == false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the name
|
||||
String name = m.group(7);
|
||||
|
||||
// if the parent is root, we only accept selected items
|
||||
if (mParentEntry.isRoot()) {
|
||||
boolean found = false;
|
||||
for (String approved : sRootLevelApprovedItems) {
|
||||
if (approved.equals(name)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if it's not in the approved list we skip this entry.
|
||||
if (found == false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// get the rest of the groups
|
||||
String permissions = m.group(1);
|
||||
String owner = m.group(2);
|
||||
String group = m.group(3);
|
||||
String size = m.group(4);
|
||||
String date = m.group(5);
|
||||
String time = m.group(6);
|
||||
String info = null;
|
||||
|
||||
// and the type
|
||||
int objectType = TYPE_OTHER;
|
||||
switch (permissions.charAt(0)) {
|
||||
case '-' :
|
||||
objectType = TYPE_FILE;
|
||||
break;
|
||||
case 'b' :
|
||||
objectType = TYPE_BLOCK;
|
||||
break;
|
||||
case 'c' :
|
||||
objectType = TYPE_CHARACTER;
|
||||
break;
|
||||
case 'd' :
|
||||
objectType = TYPE_DIRECTORY;
|
||||
break;
|
||||
case 'l' :
|
||||
objectType = TYPE_LINK;
|
||||
break;
|
||||
case 's' :
|
||||
objectType = TYPE_SOCKET;
|
||||
break;
|
||||
case 'p' :
|
||||
objectType = TYPE_FIFO;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// now check what we may be linking to
|
||||
if (objectType == TYPE_LINK) {
|
||||
String[] segments = name.split("\\s->\\s"); //$NON-NLS-1$
|
||||
|
||||
// we should have 2 segments
|
||||
if (segments.length == 2) {
|
||||
// update the entry name to not contain the link
|
||||
name = segments[0];
|
||||
|
||||
// and the link name
|
||||
info = segments[1];
|
||||
|
||||
// now get the path to the link
|
||||
String[] pathSegments = info.split(FILE_SEPARATOR);
|
||||
if (pathSegments.length == 1) {
|
||||
// the link is to something in the same directory,
|
||||
// unless the link is ..
|
||||
if ("..".equals(pathSegments[0])) { //$NON-NLS-1$
|
||||
// set the type and we're done.
|
||||
objectType = TYPE_DIRECTORY_LINK;
|
||||
} else {
|
||||
// either we found the object already
|
||||
// or we'll find it later.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add an arrow in front to specify it's a link.
|
||||
info = "-> " + info; //$NON-NLS-1$;
|
||||
}
|
||||
|
||||
// get the entry, either from an existing one, or a new one
|
||||
FileEntry entry = getExistingEntry(name);
|
||||
if (entry == null) {
|
||||
entry = new FileEntry(mParentEntry, name, objectType, false /* isRoot */);
|
||||
}
|
||||
|
||||
// add some misc info
|
||||
entry.permissions = permissions;
|
||||
entry.size = size;
|
||||
entry.date = date;
|
||||
entry.time = time;
|
||||
entry.owner = owner;
|
||||
entry.group = group;
|
||||
if (objectType == TYPE_LINK) {
|
||||
entry.info = info;
|
||||
}
|
||||
|
||||
mEntryList.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries for an already existing Entry per name
|
||||
* @param name the name of the entry
|
||||
* @return the existing FileEntry or null if no entry with a matching
|
||||
* name exists.
|
||||
*/
|
||||
private FileEntry getExistingEntry(String name) {
|
||||
for (int i = 0 ; i < mCurrentChildren.length; i++) {
|
||||
FileEntry e = mCurrentChildren[i];
|
||||
|
||||
// since we're going to "erase" the one we use, we need to
|
||||
// check that the item is not null.
|
||||
if (e != null) {
|
||||
// compare per name, case-sensitive.
|
||||
if (name.equals(e.name)) {
|
||||
// erase from the list
|
||||
mCurrentChildren[i] = null;
|
||||
|
||||
// and return the object
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// couldn't find any matching object, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isCancelled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void finishLinks() {
|
||||
// TODO Handle links in the listing service
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classes which implement this interface provide a method that deals with asynchronous
|
||||
* result from <code>ls</code> command on the device.
|
||||
*
|
||||
* @see FileListingService#getChildren(com.android.ddmlib.FileListingService.FileEntry, boolean, com.android.ddmlib.FileListingService.IListingReceiver)
|
||||
*/
|
||||
public interface IListingReceiver {
|
||||
public void setChildren(FileEntry entry, FileEntry[] children);
|
||||
|
||||
public void refreshEntry(FileEntry entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a File Listing Service for a specified {@link Device}.
|
||||
* @param device The Device the service is connected to.
|
||||
*/
|
||||
FileListingService(Device device) {
|
||||
mDevice = device;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root element.
|
||||
* @return the {@link FileEntry} object representing the root element or
|
||||
* <code>null</code> if the device is invalid.
|
||||
*/
|
||||
public FileEntry getRoot() {
|
||||
if (mDevice != null) {
|
||||
if (mRoot == null) {
|
||||
mRoot = new FileEntry(null /* parent */, "" /* name */, TYPE_DIRECTORY,
|
||||
true /* isRoot */);
|
||||
}
|
||||
|
||||
return mRoot;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the children of a {@link FileEntry}.
|
||||
* <p/>
|
||||
* This method supports a cache mechanism and synchronous and asynchronous modes.
|
||||
* <p/>
|
||||
* If <var>receiver</var> is <code>null</code>, the device side <code>ls</code>
|
||||
* command is done synchronously, and the method will return upon completion of the command.<br>
|
||||
* If <var>receiver</var> is non <code>null</code>, the command is launched is a separate
|
||||
* thread and upon completion, the receiver will be notified of the result.
|
||||
* <p/>
|
||||
* The result for each <code>ls</code> command is cached in the parent
|
||||
* <code>FileEntry</code>. <var>useCache</var> allows usage of this cache, but only if the
|
||||
* cache is valid. The cache is valid only for {@link FileListingService#REFRESH_RATE} ms.
|
||||
* After that a new <code>ls</code> command is always executed.
|
||||
* <p/>
|
||||
* If the cache is valid and <code>useCache == true</code>, the method will always simply
|
||||
* return the value of the cache, whether a {@link IListingReceiver} has been provided or not.
|
||||
*
|
||||
* @param entry The parent entry.
|
||||
* @param useCache A flag to use the cache or to force a new ls command.
|
||||
* @param receiver A receiver for asynchronous calls.
|
||||
* @return The list of children or <code>null</code> for asynchronous calls.
|
||||
*
|
||||
* @see FileEntry#getCachedChildren()
|
||||
*/
|
||||
public FileEntry[] getChildren(final FileEntry entry, boolean useCache,
|
||||
final IListingReceiver receiver) {
|
||||
// first thing we do is check the cache, and if we already have a recent
|
||||
// enough children list, we just return that.
|
||||
if (useCache && entry.needFetch() == false) {
|
||||
return entry.getCachedChildren();
|
||||
}
|
||||
|
||||
// if there's no receiver, then this is a synchronous call, and we
|
||||
// return the result of ls
|
||||
if (receiver == null) {
|
||||
doLs(entry);
|
||||
return entry.getCachedChildren();
|
||||
}
|
||||
|
||||
// this is a asynchronous call.
|
||||
// we launch a thread that will do ls and give the listing
|
||||
// to the receiver
|
||||
Thread t = new Thread("ls " + entry.getFullPath()) { //$NON-NLS-1$
|
||||
@Override
|
||||
public void run() {
|
||||
doLs(entry);
|
||||
|
||||
receiver.setChildren(entry, entry.getCachedChildren());
|
||||
|
||||
final FileEntry[] children = entry.getCachedChildren();
|
||||
if (children.length > 0 && children[0].isApplicationPackage()) {
|
||||
final HashMap<String, FileEntry> map = new HashMap<String, FileEntry>();
|
||||
|
||||
for (FileEntry child : children) {
|
||||
String path = child.getFullPath();
|
||||
map.put(path, child);
|
||||
}
|
||||
|
||||
// call pm.
|
||||
String command = PM_FULL_LISTING;
|
||||
try {
|
||||
mDevice.executeShellCommand(command, new MultiLineReceiver() {
|
||||
@Override
|
||||
public void processNewLines(String[] lines) {
|
||||
for (String line : lines) {
|
||||
if (line.length() > 0) {
|
||||
// get the filepath and package from the line
|
||||
Matcher m = sPmPattern.matcher(line);
|
||||
if (m.matches()) {
|
||||
// get the children with that path
|
||||
FileEntry entry = map.get(m.group(1));
|
||||
if (entry != null) {
|
||||
entry.info = m.group(2);
|
||||
receiver.refreshEntry(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public boolean isCancelled() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
// adb failed somehow, we do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if another thread is pending, launch it
|
||||
synchronized (mThreadList) {
|
||||
// first remove ourselves from the list
|
||||
mThreadList.remove(this);
|
||||
|
||||
// then launch the next one if applicable.
|
||||
if (mThreadList.size() > 0) {
|
||||
Thread t = mThreadList.get(0);
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// we don't want to run multiple ls on the device at the same time, so we
|
||||
// store the thread in a list and launch it only if there's no other thread running.
|
||||
// the thread will launch the next one once it's done.
|
||||
synchronized (mThreadList) {
|
||||
// add to the list
|
||||
mThreadList.add(t);
|
||||
|
||||
// if it's the only one, launch it.
|
||||
if (mThreadList.size() == 1) {
|
||||
t.start();
|
||||
}
|
||||
}
|
||||
|
||||
// and we return null.
|
||||
return null;
|
||||
}
|
||||
|
||||
private void doLs(FileEntry entry) {
|
||||
// create a list that will receive the list of the entries
|
||||
ArrayList<FileEntry> entryList = new ArrayList<FileEntry>();
|
||||
|
||||
// create a list that will receive the link to compute post ls;
|
||||
ArrayList<String> linkList = new ArrayList<String>();
|
||||
|
||||
try {
|
||||
// create the command
|
||||
String command = "ls -l " + entry.getFullPath(); //$NON-NLS-1$
|
||||
|
||||
// create the receiver object that will parse the result from ls
|
||||
LsReceiver receiver = new LsReceiver(entry, entryList, linkList);
|
||||
|
||||
// call ls.
|
||||
mDevice.executeShellCommand(command, receiver);
|
||||
|
||||
// finish the process of the receiver to handle links
|
||||
receiver.finishLinks();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
|
||||
|
||||
// at this point we need to refresh the viewer
|
||||
entry.fetchTime = System.currentTimeMillis();
|
||||
|
||||
// sort the children and set them as the new children
|
||||
Collections.sort(entryList, FileEntry.sEntryComparator);
|
||||
entry.setChildren(entryList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A receiver able to parse the result of the execution of
|
||||
* {@link #GETPROP_COMMAND} on a device.
|
||||
*/
|
||||
final class GetPropReceiver extends MultiLineReceiver {
|
||||
final static String GETPROP_COMMAND = "getprop"; //$NON-NLS-1$
|
||||
|
||||
private final static Pattern GETPROP_PATTERN = Pattern.compile("^\\[([^]]+)\\]\\:\\s*\\[(.*)\\]$"); //$NON-NLS-1$
|
||||
|
||||
/** indicates if we need to read the first */
|
||||
private Device mDevice = null;
|
||||
|
||||
/**
|
||||
* Creates the receiver with the device the receiver will modify.
|
||||
* @param device The device to modify
|
||||
*/
|
||||
public GetPropReceiver(Device device) {
|
||||
mDevice = device;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processNewLines(String[] lines) {
|
||||
// We receive an array of lines. We're expecting
|
||||
// to have the build info in the first line, and the build
|
||||
// date in the 2nd line. There seems to be an empty line
|
||||
// after all that.
|
||||
|
||||
for (String line : lines) {
|
||||
if (line.length() == 0 || line.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Matcher m = GETPROP_PATTERN.matcher(line);
|
||||
if (m.matches()) {
|
||||
String label = m.group(1);
|
||||
String value = m.group(2);
|
||||
|
||||
if (label.length() > 0) {
|
||||
mDevice.addProperty(label, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCancelled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void done() {
|
||||
mDevice.update(Device.CHANGE_BUILD_INFO);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Handle the "app name" chunk (APNM).
|
||||
*/
|
||||
final class HandleAppName extends ChunkHandler {
|
||||
|
||||
public static final int CHUNK_APNM = ChunkHandler.type("APNM");
|
||||
|
||||
private static final HandleAppName mInst = new HandleAppName();
|
||||
|
||||
|
||||
private HandleAppName() {}
|
||||
|
||||
/**
|
||||
* Register for the packets we expect to get from the client.
|
||||
*/
|
||||
public static void register(MonitorThread mt) {
|
||||
mt.registerChunkHandler(CHUNK_APNM, mInst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Client is ready.
|
||||
*/
|
||||
@Override
|
||||
public void clientReady(Client client) throws IOException {}
|
||||
|
||||
/**
|
||||
* Client went away.
|
||||
*/
|
||||
@Override
|
||||
public void clientDisconnected(Client client) {}
|
||||
|
||||
/**
|
||||
* Chunk handler entry point.
|
||||
*/
|
||||
@Override
|
||||
public void handleChunk(Client client, int type, ByteBuffer data,
|
||||
boolean isReply, int msgId) {
|
||||
|
||||
Log.d("ddm-appname", "handling " + ChunkHandler.name(type));
|
||||
|
||||
if (type == CHUNK_APNM) {
|
||||
assert !isReply;
|
||||
handleAPNM(client, data);
|
||||
} else {
|
||||
handleUnknownChunk(client, type, data, isReply, msgId);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a reply to our APNM message.
|
||||
*/
|
||||
private static void handleAPNM(Client client, ByteBuffer data) {
|
||||
int appNameLen;
|
||||
String appName;
|
||||
|
||||
appNameLen = data.getInt();
|
||||
appName = getString(data, appNameLen);
|
||||
|
||||
Log.i("ddm-appname", "APNM: app='" + appName + "'");
|
||||
|
||||
ClientData cd = client.getClientData();
|
||||
synchronized (cd) {
|
||||
cd.setClientDescription(appName);
|
||||
}
|
||||
|
||||
client = checkDebuggerPortForAppName(client, appName);
|
||||
|
||||
if (client != null) {
|
||||
client.update(Client.CHANGE_NAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Submit an exit request.
|
||||
*/
|
||||
final class HandleExit extends ChunkHandler {
|
||||
|
||||
public static final int CHUNK_EXIT = type("EXIT");
|
||||
|
||||
private static final HandleExit mInst = new HandleExit();
|
||||
|
||||
|
||||
private HandleExit() {}
|
||||
|
||||
/**
|
||||
* Register for the packets we expect to get from the client.
|
||||
*/
|
||||
public static void register(MonitorThread mt) {}
|
||||
|
||||
/**
|
||||
* Client is ready.
|
||||
*/
|
||||
@Override
|
||||
public void clientReady(Client client) throws IOException {}
|
||||
|
||||
/**
|
||||
* Client went away.
|
||||
*/
|
||||
@Override
|
||||
public void clientDisconnected(Client client) {}
|
||||
|
||||
/**
|
||||
* Chunk handler entry point.
|
||||
*/
|
||||
@Override
|
||||
public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
|
||||
handleUnknownChunk(client, type, data, isReply, msgId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an EXIT request to the client.
|
||||
*/
|
||||
public static void sendEXIT(Client client, int status)
|
||||
throws IOException
|
||||
{
|
||||
ByteBuffer rawBuf = allocBuffer(4);
|
||||
JdwpPacket packet = new JdwpPacket(rawBuf);
|
||||
ByteBuffer buf = getChunkDataBuf(rawBuf);
|
||||
|
||||
buf.putInt(status);
|
||||
|
||||
finishChunkPacket(packet, CHUNK_EXIT, buf.position());
|
||||
Log.d("ddm-exit", "Sending " + name(CHUNK_EXIT) + ": " + status);
|
||||
client.sendAndConsume(packet, mInst);
|
||||
}
|
||||
}
|
||||
|
||||
497
tools/ddms/libs/ddmlib/src/com/android/ddmlib/HandleHeap.java
Normal file
497
tools/ddms/libs/ddmlib/src/com/android/ddmlib/HandleHeap.java
Normal file
@@ -0,0 +1,497 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Handle heap status updates.
|
||||
*/
|
||||
final class HandleHeap extends ChunkHandler {
|
||||
|
||||
public static final int CHUNK_HPIF = type("HPIF");
|
||||
public static final int CHUNK_HPST = type("HPST");
|
||||
public static final int CHUNK_HPEN = type("HPEN");
|
||||
public static final int CHUNK_HPSG = type("HPSG");
|
||||
public static final int CHUNK_HPGC = type("HPGC");
|
||||
public static final int CHUNK_REAE = type("REAE");
|
||||
public static final int CHUNK_REAQ = type("REAQ");
|
||||
public static final int CHUNK_REAL = type("REAL");
|
||||
|
||||
// args to sendHPSG
|
||||
public static final int WHEN_DISABLE = 0;
|
||||
public static final int WHEN_GC = 1;
|
||||
public static final int WHAT_MERGE = 0; // merge adjacent objects
|
||||
public static final int WHAT_OBJ = 1; // keep objects distinct
|
||||
|
||||
// args to sendHPIF
|
||||
public static final int HPIF_WHEN_NEVER = 0;
|
||||
public static final int HPIF_WHEN_NOW = 1;
|
||||
public static final int HPIF_WHEN_NEXT_GC = 2;
|
||||
public static final int HPIF_WHEN_EVERY_GC = 3;
|
||||
|
||||
private static final HandleHeap mInst = new HandleHeap();
|
||||
|
||||
private HandleHeap() {}
|
||||
|
||||
/**
|
||||
* Register for the packets we expect to get from the client.
|
||||
*/
|
||||
public static void register(MonitorThread mt) {
|
||||
mt.registerChunkHandler(CHUNK_HPIF, mInst);
|
||||
mt.registerChunkHandler(CHUNK_HPST, mInst);
|
||||
mt.registerChunkHandler(CHUNK_HPEN, mInst);
|
||||
mt.registerChunkHandler(CHUNK_HPSG, mInst);
|
||||
mt.registerChunkHandler(CHUNK_REAQ, mInst);
|
||||
mt.registerChunkHandler(CHUNK_REAL, mInst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Client is ready.
|
||||
*/
|
||||
@Override
|
||||
public void clientReady(Client client) throws IOException {
|
||||
if (client.isHeapUpdateEnabled()) {
|
||||
//sendHPSG(client, WHEN_GC, WHAT_MERGE);
|
||||
sendHPIF(client, HPIF_WHEN_EVERY_GC);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Client went away.
|
||||
*/
|
||||
@Override
|
||||
public void clientDisconnected(Client client) {}
|
||||
|
||||
/**
|
||||
* Chunk handler entry point.
|
||||
*/
|
||||
@Override
|
||||
public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
|
||||
Log.d("ddm-heap", "handling " + ChunkHandler.name(type));
|
||||
|
||||
if (type == CHUNK_HPIF) {
|
||||
handleHPIF(client, data);
|
||||
client.update(Client.CHANGE_HEAP_DATA);
|
||||
} else if (type == CHUNK_HPST) {
|
||||
handleHPST(client, data);
|
||||
} else if (type == CHUNK_HPEN) {
|
||||
handleHPEN(client, data);
|
||||
client.update(Client.CHANGE_HEAP_DATA);
|
||||
} else if (type == CHUNK_HPSG) {
|
||||
handleHPSG(client, data);
|
||||
} else if (type == CHUNK_REAQ) {
|
||||
handleREAQ(client, data);
|
||||
client.update(Client.CHANGE_HEAP_ALLOCATION_STATUS);
|
||||
} else if (type == CHUNK_REAL) {
|
||||
handleREAL(client, data);
|
||||
client.update(Client.CHANGE_HEAP_ALLOCATIONS);
|
||||
} else {
|
||||
handleUnknownChunk(client, type, data, isReply, msgId);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a heap info message.
|
||||
*/
|
||||
private void handleHPIF(Client client, ByteBuffer data) {
|
||||
Log.d("ddm-heap", "HPIF!");
|
||||
try {
|
||||
int numHeaps = data.getInt();
|
||||
|
||||
for (int i = 0; i < numHeaps; i++) {
|
||||
int heapId = data.getInt();
|
||||
@SuppressWarnings("unused")
|
||||
long timeStamp = data.getLong();
|
||||
@SuppressWarnings("unused")
|
||||
byte reason = data.get();
|
||||
long maxHeapSize = (long)data.getInt() & 0x00ffffffff;
|
||||
long heapSize = (long)data.getInt() & 0x00ffffffff;
|
||||
long bytesAllocated = (long)data.getInt() & 0x00ffffffff;
|
||||
long objectsAllocated = (long)data.getInt() & 0x00ffffffff;
|
||||
|
||||
client.getClientData().setHeapInfo(heapId, maxHeapSize,
|
||||
heapSize, bytesAllocated, objectsAllocated);
|
||||
}
|
||||
} catch (BufferUnderflowException ex) {
|
||||
Log.w("ddm-heap", "malformed HPIF chunk from client");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an HPIF (HeaP InFo) request to the client.
|
||||
*/
|
||||
public static void sendHPIF(Client client, int when) throws IOException {
|
||||
ByteBuffer rawBuf = allocBuffer(1);
|
||||
JdwpPacket packet = new JdwpPacket(rawBuf);
|
||||
ByteBuffer buf = getChunkDataBuf(rawBuf);
|
||||
|
||||
buf.put((byte)when);
|
||||
|
||||
finishChunkPacket(packet, CHUNK_HPIF, buf.position());
|
||||
Log.d("ddm-heap", "Sending " + name(CHUNK_HPIF) + ": when=" + when);
|
||||
client.sendAndConsume(packet, mInst);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a heap segment series start message.
|
||||
*/
|
||||
private void handleHPST(Client client, ByteBuffer data) {
|
||||
/* Clear out any data that's sitting around to
|
||||
* get ready for the chunks that are about to come.
|
||||
*/
|
||||
//xxx todo: only clear data that belongs to the heap mentioned in <data>.
|
||||
client.getClientData().getVmHeapData().clearHeapData();
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a heap segment series end message.
|
||||
*/
|
||||
private void handleHPEN(Client client, ByteBuffer data) {
|
||||
/* Let the UI know that we've received all of the
|
||||
* data for this heap.
|
||||
*/
|
||||
//xxx todo: only seal data that belongs to the heap mentioned in <data>.
|
||||
client.getClientData().getVmHeapData().sealHeapData();
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a heap segment message.
|
||||
*/
|
||||
private void handleHPSG(Client client, ByteBuffer data) {
|
||||
byte dataCopy[] = new byte[data.limit()];
|
||||
data.rewind();
|
||||
data.get(dataCopy);
|
||||
data = ByteBuffer.wrap(dataCopy);
|
||||
client.getClientData().getVmHeapData().addHeapData(data);
|
||||
//xxx todo: add to the heap mentioned in <data>
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HPSG (HeaP SeGment) request to the client.
|
||||
*/
|
||||
public static void sendHPSG(Client client, int when, int what)
|
||||
throws IOException {
|
||||
|
||||
ByteBuffer rawBuf = allocBuffer(2);
|
||||
JdwpPacket packet = new JdwpPacket(rawBuf);
|
||||
ByteBuffer buf = getChunkDataBuf(rawBuf);
|
||||
|
||||
buf.put((byte)when);
|
||||
buf.put((byte)what);
|
||||
|
||||
finishChunkPacket(packet, CHUNK_HPSG, buf.position());
|
||||
Log.d("ddm-heap", "Sending " + name(CHUNK_HPSG) + ": when="
|
||||
+ when + ", what=" + what);
|
||||
client.sendAndConsume(packet, mInst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HPGC request to the client.
|
||||
*/
|
||||
public static void sendHPGC(Client client)
|
||||
throws IOException {
|
||||
ByteBuffer rawBuf = allocBuffer(0);
|
||||
JdwpPacket packet = new JdwpPacket(rawBuf);
|
||||
ByteBuffer buf = getChunkDataBuf(rawBuf);
|
||||
|
||||
// no data
|
||||
|
||||
finishChunkPacket(packet, CHUNK_HPGC, buf.position());
|
||||
Log.d("ddm-heap", "Sending " + name(CHUNK_HPGC));
|
||||
client.sendAndConsume(packet, mInst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a REAE (REcent Allocation Enable) request to the client.
|
||||
*/
|
||||
public static void sendREAE(Client client, boolean enable)
|
||||
throws IOException {
|
||||
ByteBuffer rawBuf = allocBuffer(1);
|
||||
JdwpPacket packet = new JdwpPacket(rawBuf);
|
||||
ByteBuffer buf = getChunkDataBuf(rawBuf);
|
||||
|
||||
buf.put((byte) (enable ? 1 : 0));
|
||||
|
||||
finishChunkPacket(packet, CHUNK_REAE, buf.position());
|
||||
Log.d("ddm-heap", "Sending " + name(CHUNK_REAE) + ": " + enable);
|
||||
client.sendAndConsume(packet, mInst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a REAQ (REcent Allocation Query) request to the client.
|
||||
*/
|
||||
public static void sendREAQ(Client client)
|
||||
throws IOException {
|
||||
ByteBuffer rawBuf = allocBuffer(0);
|
||||
JdwpPacket packet = new JdwpPacket(rawBuf);
|
||||
ByteBuffer buf = getChunkDataBuf(rawBuf);
|
||||
|
||||
// no data
|
||||
|
||||
finishChunkPacket(packet, CHUNK_REAQ, buf.position());
|
||||
Log.d("ddm-heap", "Sending " + name(CHUNK_REAQ));
|
||||
client.sendAndConsume(packet, mInst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a REAL (REcent ALlocation) request to the client.
|
||||
*/
|
||||
public static void sendREAL(Client client)
|
||||
throws IOException {
|
||||
ByteBuffer rawBuf = allocBuffer(0);
|
||||
JdwpPacket packet = new JdwpPacket(rawBuf);
|
||||
ByteBuffer buf = getChunkDataBuf(rawBuf);
|
||||
|
||||
// no data
|
||||
|
||||
finishChunkPacket(packet, CHUNK_REAL, buf.position());
|
||||
Log.d("ddm-heap", "Sending " + name(CHUNK_REAL));
|
||||
client.sendAndConsume(packet, mInst);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle the response from our REcent Allocation Query message.
|
||||
*/
|
||||
private void handleREAQ(Client client, ByteBuffer data) {
|
||||
boolean enabled;
|
||||
|
||||
enabled = (data.get() != 0);
|
||||
Log.d("ddm-heap", "REAQ says: enabled=" + enabled);
|
||||
|
||||
client.getClientData().setAllocationStatus(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a VM class descriptor string ("Landroid/os/Debug;") to
|
||||
* a dot-notation class name ("android.os.Debug").
|
||||
*/
|
||||
private String descriptorToDot(String str) {
|
||||
// count the number of arrays.
|
||||
int array = 0;
|
||||
while (str.startsWith("[")) {
|
||||
str = str.substring(1);
|
||||
array++;
|
||||
}
|
||||
|
||||
int len = str.length();
|
||||
|
||||
/* strip off leading 'L' and trailing ';' if appropriate */
|
||||
if (len >= 2 && str.charAt(0) == 'L' && str.charAt(len - 1) == ';') {
|
||||
str = str.substring(1, len-1);
|
||||
str = str.replace('/', '.');
|
||||
} else {
|
||||
// convert the basic types
|
||||
if ("C".equals(str)) {
|
||||
str = "char";
|
||||
} else if ("B".equals(str)) {
|
||||
str = "byte";
|
||||
} else if ("Z".equals(str)) {
|
||||
str = "boolean";
|
||||
} else if ("S".equals(str)) {
|
||||
str = "short";
|
||||
} else if ("I".equals(str)) {
|
||||
str = "int";
|
||||
} else if ("J".equals(str)) {
|
||||
str = "long";
|
||||
} else if ("F".equals(str)) {
|
||||
str = "float";
|
||||
} else if ("D".equals(str)) {
|
||||
str = "double";
|
||||
}
|
||||
}
|
||||
|
||||
// now add the array part
|
||||
for (int a = 0 ; a < array; a++) {
|
||||
str = str + "[]";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a string table out of "data".
|
||||
*
|
||||
* This is just a serial collection of strings, each of which is a
|
||||
* four-byte length followed by UTF-16 data.
|
||||
*/
|
||||
private void readStringTable(ByteBuffer data, String[] strings) {
|
||||
int count = strings.length;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
int nameLen = data.getInt();
|
||||
String descriptor = getString(data, nameLen);
|
||||
strings[i] = descriptorToDot(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a REcent ALlocation response.
|
||||
*
|
||||
* Message header (all values big-endian):
|
||||
* (1b) message header len (to allow future expansion); includes itself
|
||||
* (1b) entry header len
|
||||
* (1b) stack frame len
|
||||
* (2b) number of entries
|
||||
* (4b) offset to string table from start of message
|
||||
* (2b) number of class name strings
|
||||
* (2b) number of method name strings
|
||||
* (2b) number of source file name strings
|
||||
* For each entry:
|
||||
* (4b) total allocation size
|
||||
* (2b) threadId
|
||||
* (2b) allocated object's class name index
|
||||
* (1b) stack depth
|
||||
* For each stack frame:
|
||||
* (2b) method's class name
|
||||
* (2b) method name
|
||||
* (2b) method source file
|
||||
* (2b) line number, clipped to 32767; -2 if native; -1 if no source
|
||||
* (xb) class name strings
|
||||
* (xb) method name strings
|
||||
* (xb) source file strings
|
||||
*
|
||||
* As with other DDM traffic, strings are sent as a 4-byte length
|
||||
* followed by UTF-16 data.
|
||||
*/
|
||||
private void handleREAL(Client client, ByteBuffer data) {
|
||||
Log.e("ddm-heap", "*** Received " + name(CHUNK_REAL));
|
||||
int messageHdrLen, entryHdrLen, stackFrameLen;
|
||||
int numEntries, offsetToStrings;
|
||||
int numClassNames, numMethodNames, numFileNames;
|
||||
|
||||
/*
|
||||
* Read the header.
|
||||
*/
|
||||
messageHdrLen = (data.get() & 0xff);
|
||||
entryHdrLen = (data.get() & 0xff);
|
||||
stackFrameLen = (data.get() & 0xff);
|
||||
numEntries = (data.getShort() & 0xffff);
|
||||
offsetToStrings = data.getInt();
|
||||
numClassNames = (data.getShort() & 0xffff);
|
||||
numMethodNames = (data.getShort() & 0xffff);
|
||||
numFileNames = (data.getShort() & 0xffff);
|
||||
|
||||
|
||||
/*
|
||||
* Skip forward to the strings and read them.
|
||||
*/
|
||||
data.position(offsetToStrings);
|
||||
|
||||
String[] classNames = new String[numClassNames];
|
||||
String[] methodNames = new String[numMethodNames];
|
||||
String[] fileNames = new String[numFileNames];
|
||||
|
||||
readStringTable(data, classNames);
|
||||
readStringTable(data, methodNames);
|
||||
//System.out.println("METHODS: "
|
||||
// + java.util.Arrays.deepToString(methodNames));
|
||||
readStringTable(data, fileNames);
|
||||
|
||||
/*
|
||||
* Skip back to a point just past the header and start reading
|
||||
* entries.
|
||||
*/
|
||||
data.position(messageHdrLen);
|
||||
|
||||
ArrayList<AllocationInfo> list = new ArrayList<AllocationInfo>(numEntries);
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
int totalSize;
|
||||
int threadId, classNameIndex, stackDepth;
|
||||
|
||||
totalSize = data.getInt();
|
||||
threadId = (data.getShort() & 0xffff);
|
||||
classNameIndex = (data.getShort() & 0xffff);
|
||||
stackDepth = (data.get() & 0xff);
|
||||
/* we've consumed 9 bytes; gobble up any extra */
|
||||
for (int skip = 9; skip < entryHdrLen; skip++)
|
||||
data.get();
|
||||
|
||||
StackTraceElement[] steArray = new StackTraceElement[stackDepth];
|
||||
|
||||
/*
|
||||
* Pull out the stack trace.
|
||||
*/
|
||||
for (int sti = 0; sti < stackDepth; sti++) {
|
||||
int methodClassNameIndex, methodNameIndex;
|
||||
int methodSourceFileIndex;
|
||||
short lineNumber;
|
||||
String methodClassName, methodName, methodSourceFile;
|
||||
|
||||
methodClassNameIndex = (data.getShort() & 0xffff);
|
||||
methodNameIndex = (data.getShort() & 0xffff);
|
||||
methodSourceFileIndex = (data.getShort() & 0xffff);
|
||||
lineNumber = data.getShort();
|
||||
|
||||
methodClassName = classNames[methodClassNameIndex];
|
||||
methodName = methodNames[methodNameIndex];
|
||||
methodSourceFile = fileNames[methodSourceFileIndex];
|
||||
|
||||
steArray[sti] = new StackTraceElement(methodClassName,
|
||||
methodName, methodSourceFile, lineNumber);
|
||||
|
||||
/* we've consumed 8 bytes; gobble up any extra */
|
||||
for (int skip = 9; skip < stackFrameLen; skip++)
|
||||
data.get();
|
||||
}
|
||||
|
||||
list.add(new AllocationInfo(classNames[classNameIndex],
|
||||
totalSize, (short) threadId, steArray));
|
||||
}
|
||||
|
||||
// sort biggest allocations first.
|
||||
Collections.sort(list);
|
||||
|
||||
client.getClientData().setAllocations(list.toArray(new AllocationInfo[numEntries]));
|
||||
}
|
||||
|
||||
/*
|
||||
* For debugging: dump the contents of an AllocRecord array.
|
||||
*
|
||||
* The array starts with the oldest known allocation and ends with
|
||||
* the most recent allocation.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private static void dumpRecords(AllocationInfo[] records) {
|
||||
System.out.println("Found " + records.length + " records:");
|
||||
|
||||
for (AllocationInfo rec: records) {
|
||||
System.out.println("tid=" + rec.getThreadId() + " "
|
||||
+ rec.getAllocatedClass() + " (" + rec.getSize() + " bytes)");
|
||||
|
||||
for (StackTraceElement ste: rec.getStackTrace()) {
|
||||
if (ste.isNativeMethod()) {
|
||||
System.out.println(" " + ste.getClassName()
|
||||
+ "." + ste.getMethodName()
|
||||
+ " (Native method)");
|
||||
} else {
|
||||
System.out.println(" " + ste.getClassName()
|
||||
+ "." + ste.getMethodName()
|
||||
+ " (" + ste.getFileName()
|
||||
+ ":" + ste.getLineNumber() + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
130
tools/ddms/libs/ddmlib/src/com/android/ddmlib/HandleHello.java
Normal file
130
tools/ddms/libs/ddmlib/src/com/android/ddmlib/HandleHello.java
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Handle the "hello" chunk (HELO).
|
||||
*/
|
||||
final class HandleHello extends ChunkHandler {
|
||||
|
||||
public static final int CHUNK_HELO = ChunkHandler.type("HELO");
|
||||
|
||||
private static final HandleHello mInst = new HandleHello();
|
||||
|
||||
|
||||
private HandleHello() {}
|
||||
|
||||
/**
|
||||
* Register for the packets we expect to get from the client.
|
||||
*/
|
||||
public static void register(MonitorThread mt) {
|
||||
mt.registerChunkHandler(CHUNK_HELO, mInst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Client is ready.
|
||||
*/
|
||||
@Override
|
||||
public void clientReady(Client client) throws IOException {
|
||||
Log.d("ddm-hello", "Now ready: " + client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Client went away.
|
||||
*/
|
||||
@Override
|
||||
public void clientDisconnected(Client client) {
|
||||
Log.d("ddm-hello", "Now disconnected: " + client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chunk handler entry point.
|
||||
*/
|
||||
@Override
|
||||
public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
|
||||
|
||||
Log.d("ddm-hello", "handling " + ChunkHandler.name(type));
|
||||
|
||||
if (type == CHUNK_HELO) {
|
||||
assert isReply;
|
||||
handleHELO(client, data);
|
||||
} else {
|
||||
handleUnknownChunk(client, type, data, isReply, msgId);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a reply to our HELO message.
|
||||
*/
|
||||
private static void handleHELO(Client client, ByteBuffer data) {
|
||||
int version, pid, vmIdentLen, appNameLen;
|
||||
String vmIdent, appName;
|
||||
|
||||
version = data.getInt();
|
||||
pid = data.getInt();
|
||||
vmIdentLen = data.getInt();
|
||||
appNameLen = data.getInt();
|
||||
|
||||
vmIdent = getString(data, vmIdentLen);
|
||||
appName = getString(data, appNameLen);
|
||||
|
||||
Log.d("ddm-hello", "HELO: v=" + version + ", pid=" + pid
|
||||
+ ", vm='" + vmIdent + "', app='" + appName + "'");
|
||||
|
||||
ClientData cd = client.getClientData();
|
||||
|
||||
synchronized (cd) {
|
||||
if (cd.getPid() == pid) {
|
||||
cd.setVmIdentifier(vmIdent);
|
||||
cd.setClientDescription(appName);
|
||||
cd.isDdmAware(true);
|
||||
} else {
|
||||
Log.e("ddm-hello", "Received pid (" + pid + ") does not match client pid ("
|
||||
+ cd.getPid() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
client = checkDebuggerPortForAppName(client, appName);
|
||||
|
||||
if (client != null) {
|
||||
client.update(Client.CHANGE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send a HELO request to the client.
|
||||
*/
|
||||
public static void sendHELO(Client client, int serverProtocolVersion)
|
||||
throws IOException
|
||||
{
|
||||
ByteBuffer rawBuf = allocBuffer(4);
|
||||
JdwpPacket packet = new JdwpPacket(rawBuf);
|
||||
ByteBuffer buf = getChunkDataBuf(rawBuf);
|
||||
|
||||
buf.putInt(serverProtocolVersion);
|
||||
|
||||
finishChunkPacket(packet, CHUNK_HELO, buf.position());
|
||||
Log.d("ddm-hello", "Sending " + name(CHUNK_HELO)
|
||||
+ " ID=0x" + Integer.toHexString(packet.getId()));
|
||||
client.sendAndConsume(packet, mInst);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Handle thread status updates.
|
||||
*/
|
||||
final class HandleNativeHeap extends ChunkHandler {
|
||||
|
||||
public static final int CHUNK_NHGT = type("NHGT"); // $NON-NLS-1$
|
||||
public static final int CHUNK_NHSG = type("NHSG"); // $NON-NLS-1$
|
||||
public static final int CHUNK_NHST = type("NHST"); // $NON-NLS-1$
|
||||
public static final int CHUNK_NHEN = type("NHEN"); // $NON-NLS-1$
|
||||
|
||||
private static final HandleNativeHeap mInst = new HandleNativeHeap();
|
||||
|
||||
private HandleNativeHeap() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register for the packets we expect to get from the client.
|
||||
*/
|
||||
public static void register(MonitorThread mt) {
|
||||
mt.registerChunkHandler(CHUNK_NHGT, mInst);
|
||||
mt.registerChunkHandler(CHUNK_NHSG, mInst);
|
||||
mt.registerChunkHandler(CHUNK_NHST, mInst);
|
||||
mt.registerChunkHandler(CHUNK_NHEN, mInst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Client is ready.
|
||||
*/
|
||||
@Override
|
||||
public void clientReady(Client client) throws IOException {}
|
||||
|
||||
/**
|
||||
* Client went away.
|
||||
*/
|
||||
@Override
|
||||
public void clientDisconnected(Client client) {}
|
||||
|
||||
/**
|
||||
* Chunk handler entry point.
|
||||
*/
|
||||
@Override
|
||||
public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
|
||||
|
||||
Log.d("ddm-nativeheap", "handling " + ChunkHandler.name(type));
|
||||
|
||||
if (type == CHUNK_NHGT) {
|
||||
handleNHGT(client, data);
|
||||
} else if (type == CHUNK_NHST) {
|
||||
// start chunk before any NHSG chunk(s)
|
||||
client.getClientData().getNativeHeapData().clearHeapData();
|
||||
} else if (type == CHUNK_NHEN) {
|
||||
// end chunk after NHSG chunk(s)
|
||||
client.getClientData().getNativeHeapData().sealHeapData();
|
||||
} else if (type == CHUNK_NHSG) {
|
||||
handleNHSG(client, data);
|
||||
} else {
|
||||
handleUnknownChunk(client, type, data, isReply, msgId);
|
||||
}
|
||||
|
||||
client.update(Client.CHANGE_NATIVE_HEAP_DATA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an NHGT (Native Thread GeT) request to the client.
|
||||
*/
|
||||
public static void sendNHGT(Client client) throws IOException {
|
||||
|
||||
ByteBuffer rawBuf = allocBuffer(0);
|
||||
JdwpPacket packet = new JdwpPacket(rawBuf);
|
||||
ByteBuffer buf = getChunkDataBuf(rawBuf);
|
||||
|
||||
// no data in request message
|
||||
|
||||
finishChunkPacket(packet, CHUNK_NHGT, buf.position());
|
||||
Log.d("ddm-nativeheap", "Sending " + name(CHUNK_NHGT));
|
||||
client.sendAndConsume(packet, mInst);
|
||||
|
||||
rawBuf = allocBuffer(2);
|
||||
packet = new JdwpPacket(rawBuf);
|
||||
buf = getChunkDataBuf(rawBuf);
|
||||
|
||||
buf.put((byte)HandleHeap.WHEN_GC);
|
||||
buf.put((byte)HandleHeap.WHAT_OBJ);
|
||||
|
||||
finishChunkPacket(packet, CHUNK_NHSG, buf.position());
|
||||
Log.d("ddm-nativeheap", "Sending " + name(CHUNK_NHSG));
|
||||
client.sendAndConsume(packet, mInst);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle our native heap data.
|
||||
*/
|
||||
private void handleNHGT(Client client, ByteBuffer data) {
|
||||
ClientData cd = client.getClientData();
|
||||
|
||||
Log.d("ddm-nativeheap", "NHGT: " + data.limit() + " bytes");
|
||||
|
||||
// TODO - process incoming data and save in "cd"
|
||||
byte[] copy = new byte[data.limit()];
|
||||
data.get(copy);
|
||||
|
||||
// clear the previous run
|
||||
cd.clearNativeAllocationInfo();
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(copy);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
// read the header
|
||||
// typedef struct Header {
|
||||
// uint32_t mapSize;
|
||||
// uint32_t allocSize;
|
||||
// uint32_t allocInfoSize;
|
||||
// uint32_t totalMemory;
|
||||
// uint32_t backtraceSize;
|
||||
// };
|
||||
|
||||
int mapSize = buffer.getInt();
|
||||
int allocSize = buffer.getInt();
|
||||
int allocInfoSize = buffer.getInt();
|
||||
int totalMemory = buffer.getInt();
|
||||
int backtraceSize = buffer.getInt();
|
||||
|
||||
Log.d("ddms", "mapSize: " + mapSize);
|
||||
Log.d("ddms", "allocSize: " + allocSize);
|
||||
Log.d("ddms", "allocInfoSize: " + allocInfoSize);
|
||||
Log.d("ddms", "totalMemory: " + totalMemory);
|
||||
|
||||
cd.setTotalNativeMemory(totalMemory);
|
||||
|
||||
// this means that updates aren't turned on.
|
||||
if (allocInfoSize == 0)
|
||||
return;
|
||||
|
||||
if (mapSize > 0) {
|
||||
byte[] maps = new byte[mapSize];
|
||||
buffer.get(maps, 0, mapSize);
|
||||
parseMaps(cd, maps);
|
||||
}
|
||||
|
||||
int iterations = allocSize / allocInfoSize;
|
||||
|
||||
for (int i = 0 ; i < iterations ; i++) {
|
||||
NativeAllocationInfo info = new NativeAllocationInfo(
|
||||
buffer.getInt() /* size */,
|
||||
buffer.getInt() /* allocations */);
|
||||
|
||||
for (int j = 0 ; j < backtraceSize ; j++) {
|
||||
long addr = ((long)buffer.getInt()) & 0x00000000ffffffffL;
|
||||
|
||||
info.addStackCallAddress(addr);;
|
||||
}
|
||||
|
||||
cd.addNativeAllocation(info);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleNHSG(Client client, ByteBuffer data) {
|
||||
byte dataCopy[] = new byte[data.limit()];
|
||||
data.rewind();
|
||||
data.get(dataCopy);
|
||||
data = ByteBuffer.wrap(dataCopy);
|
||||
client.getClientData().getNativeHeapData().addHeapData(data);
|
||||
|
||||
if (true) {
|
||||
return;
|
||||
}
|
||||
|
||||
// WORK IN PROGRESS
|
||||
|
||||
// Log.e("ddm-nativeheap", "NHSG: ----------------------------------");
|
||||
// Log.e("ddm-nativeheap", "NHSG: " + data.limit() + " bytes");
|
||||
|
||||
byte[] copy = new byte[data.limit()];
|
||||
data.get(copy);
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.wrap(copy);
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
int id = buffer.getInt();
|
||||
int unitsize = (int) buffer.get();
|
||||
long startAddress = (long) buffer.getInt() & 0x00000000ffffffffL;
|
||||
int offset = buffer.getInt();
|
||||
int allocationUnitCount = buffer.getInt();
|
||||
|
||||
// Log.e("ddm-nativeheap", "id: " + id);
|
||||
// Log.e("ddm-nativeheap", "unitsize: " + unitsize);
|
||||
// Log.e("ddm-nativeheap", "startAddress: 0x" + Long.toHexString(startAddress));
|
||||
// Log.e("ddm-nativeheap", "offset: " + offset);
|
||||
// Log.e("ddm-nativeheap", "allocationUnitCount: " + allocationUnitCount);
|
||||
// Log.e("ddm-nativeheap", "end: 0x" +
|
||||
// Long.toHexString(startAddress + unitsize * allocationUnitCount));
|
||||
|
||||
// read the usage
|
||||
while (buffer.position() < buffer.limit()) {
|
||||
int eState = (int)buffer.get() & 0x000000ff;
|
||||
int eLen = ((int)buffer.get() & 0x000000ff) + 1;
|
||||
//Log.e("ddm-nativeheap", "solidity: " + (eState & 0x7) + " - kind: "
|
||||
// + ((eState >> 3) & 0x7) + " - len: " + eLen);
|
||||
}
|
||||
|
||||
|
||||
// count += unitsize * allocationUnitCount;
|
||||
// Log.e("ddm-nativeheap", "count = " + count);
|
||||
|
||||
}
|
||||
|
||||
private void parseMaps(ClientData cd, byte[] maps) {
|
||||
InputStreamReader input = new InputStreamReader(new ByteArrayInputStream(maps));
|
||||
BufferedReader reader = new BufferedReader(input);
|
||||
|
||||
String line;
|
||||
|
||||
try {
|
||||
|
||||
// most libraries are defined on several lines, so we need to make sure we parse
|
||||
// all the library lines and only add the library at the end
|
||||
long startAddr = 0;
|
||||
long endAddr = 0;
|
||||
String library = null;
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
Log.d("ddms", "line: " + line);
|
||||
if (line.length() < 16) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
long tmpStart = Long.parseLong(line.substring(0, 8), 16);
|
||||
long tmpEnd = Long.parseLong(line.substring(9, 17), 16);
|
||||
|
||||
/*
|
||||
* only check for library addresses as defined in
|
||||
* //device/config/prelink-linux-arm.map
|
||||
*/
|
||||
if (tmpStart >= 0x0000000080000000L && tmpStart <= 0x00000000BFFFFFFFL) {
|
||||
|
||||
int index = line.indexOf('/');
|
||||
|
||||
if (index == -1)
|
||||
continue;
|
||||
|
||||
String tmpLib = line.substring(index);
|
||||
|
||||
if (library == null ||
|
||||
(library != null && tmpLib.equals(library) == false)) {
|
||||
|
||||
if (library != null) {
|
||||
cd.addNativeLibraryMapInfo(startAddr, endAddr, library);
|
||||
Log.d("ddms", library + "(" + Long.toHexString(startAddr) +
|
||||
" - " + Long.toHexString(endAddr) + ")");
|
||||
}
|
||||
|
||||
// now init the new library
|
||||
library = tmpLib;
|
||||
startAddr = tmpStart;
|
||||
endAddr = tmpEnd;
|
||||
} else {
|
||||
// add the new end
|
||||
endAddr = tmpEnd;
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (library != null) {
|
||||
cd.addNativeLibraryMapInfo(startAddr, endAddr, library);
|
||||
Log.d("ddms", library + "(" + Long.toHexString(startAddr) +
|
||||
" - " + Long.toHexString(endAddr) + ")");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import com.android.ddmlib.Log.LogLevel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Handle thread status updates.
|
||||
*/
|
||||
final class HandleTest extends ChunkHandler {
|
||||
|
||||
public static final int CHUNK_TEST = type("TEST");
|
||||
|
||||
private static final HandleTest mInst = new HandleTest();
|
||||
|
||||
|
||||
private HandleTest() {}
|
||||
|
||||
/**
|
||||
* Register for the packets we expect to get from the client.
|
||||
*/
|
||||
public static void register(MonitorThread mt) {
|
||||
mt.registerChunkHandler(CHUNK_TEST, mInst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Client is ready.
|
||||
*/
|
||||
@Override
|
||||
public void clientReady(Client client) throws IOException {}
|
||||
|
||||
/**
|
||||
* Client went away.
|
||||
*/
|
||||
@Override
|
||||
public void clientDisconnected(Client client) {}
|
||||
|
||||
/**
|
||||
* Chunk handler entry point.
|
||||
*/
|
||||
@Override
|
||||
public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
|
||||
|
||||
Log.d("ddm-test", "handling " + ChunkHandler.name(type));
|
||||
|
||||
if (type == CHUNK_TEST) {
|
||||
handleTEST(client, data);
|
||||
} else {
|
||||
handleUnknownChunk(client, type, data, isReply, msgId);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a thread creation message.
|
||||
*/
|
||||
private void handleTEST(Client client, ByteBuffer data)
|
||||
{
|
||||
/*
|
||||
* Can't call data.array() on a read-only ByteBuffer, so we make
|
||||
* a copy.
|
||||
*/
|
||||
byte[] copy = new byte[data.limit()];
|
||||
data.get(copy);
|
||||
|
||||
Log.d("ddm-test", "Received:");
|
||||
Log.hexDump("ddm-test", LogLevel.DEBUG, copy, 0, copy.length);
|
||||
}
|
||||
}
|
||||
|
||||
379
tools/ddms/libs/ddmlib/src/com/android/ddmlib/HandleThread.java
Normal file
379
tools/ddms/libs/ddmlib/src/com/android/ddmlib/HandleThread.java
Normal file
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Handle thread status updates.
|
||||
*/
|
||||
final class HandleThread extends ChunkHandler {
|
||||
|
||||
public static final int CHUNK_THEN = type("THEN");
|
||||
public static final int CHUNK_THCR = type("THCR");
|
||||
public static final int CHUNK_THDE = type("THDE");
|
||||
public static final int CHUNK_THST = type("THST");
|
||||
public static final int CHUNK_THNM = type("THNM");
|
||||
public static final int CHUNK_STKL = type("STKL");
|
||||
|
||||
private static final HandleThread mInst = new HandleThread();
|
||||
|
||||
// only read/written by requestThreadUpdates()
|
||||
private static volatile boolean mThreadStatusReqRunning = false;
|
||||
private static volatile boolean mThreadStackTraceReqRunning = false;
|
||||
|
||||
private HandleThread() {}
|
||||
|
||||
|
||||
/**
|
||||
* Register for the packets we expect to get from the client.
|
||||
*/
|
||||
public static void register(MonitorThread mt) {
|
||||
mt.registerChunkHandler(CHUNK_THCR, mInst);
|
||||
mt.registerChunkHandler(CHUNK_THDE, mInst);
|
||||
mt.registerChunkHandler(CHUNK_THST, mInst);
|
||||
mt.registerChunkHandler(CHUNK_THNM, mInst);
|
||||
mt.registerChunkHandler(CHUNK_STKL, mInst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Client is ready.
|
||||
*/
|
||||
@Override
|
||||
public void clientReady(Client client) throws IOException {
|
||||
Log.d("ddm-thread", "Now ready: " + client);
|
||||
if (client.isThreadUpdateEnabled())
|
||||
sendTHEN(client, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Client went away.
|
||||
*/
|
||||
@Override
|
||||
public void clientDisconnected(Client client) {}
|
||||
|
||||
/**
|
||||
* Chunk handler entry point.
|
||||
*/
|
||||
@Override
|
||||
public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
|
||||
|
||||
Log.d("ddm-thread", "handling " + ChunkHandler.name(type));
|
||||
|
||||
if (type == CHUNK_THCR) {
|
||||
handleTHCR(client, data);
|
||||
} else if (type == CHUNK_THDE) {
|
||||
handleTHDE(client, data);
|
||||
} else if (type == CHUNK_THST) {
|
||||
handleTHST(client, data);
|
||||
} else if (type == CHUNK_THNM) {
|
||||
handleTHNM(client, data);
|
||||
} else if (type == CHUNK_STKL) {
|
||||
handleSTKL(client, data);
|
||||
} else {
|
||||
handleUnknownChunk(client, type, data, isReply, msgId);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a thread creation message.
|
||||
*
|
||||
* We should be tolerant of receiving a duplicate create message. (It
|
||||
* shouldn't happen with the current implementation.)
|
||||
*/
|
||||
private void handleTHCR(Client client, ByteBuffer data) {
|
||||
int threadId, nameLen;
|
||||
String name;
|
||||
|
||||
threadId = data.getInt();
|
||||
nameLen = data.getInt();
|
||||
name = getString(data, nameLen);
|
||||
|
||||
Log.v("ddm-thread", "THCR: " + threadId + " '" + name + "'");
|
||||
|
||||
client.getClientData().addThread(threadId, name);
|
||||
client.update(Client.CHANGE_THREAD_DATA);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a thread death message.
|
||||
*/
|
||||
private void handleTHDE(Client client, ByteBuffer data) {
|
||||
int threadId;
|
||||
|
||||
threadId = data.getInt();
|
||||
Log.v("ddm-thread", "THDE: " + threadId);
|
||||
|
||||
client.getClientData().removeThread(threadId);
|
||||
client.update(Client.CHANGE_THREAD_DATA);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a thread status update message.
|
||||
*
|
||||
* Response has:
|
||||
* (1b) header len
|
||||
* (1b) bytes per entry
|
||||
* (2b) thread count
|
||||
* Then, for each thread:
|
||||
* (4b) threadId (matches value from THCR)
|
||||
* (1b) thread status
|
||||
* (4b) tid
|
||||
* (4b) utime
|
||||
* (4b) stime
|
||||
*/
|
||||
private void handleTHST(Client client, ByteBuffer data) {
|
||||
int headerLen, bytesPerEntry, extraPerEntry;
|
||||
int threadCount;
|
||||
|
||||
headerLen = (data.get() & 0xff);
|
||||
bytesPerEntry = (data.get() & 0xff);
|
||||
threadCount = data.getShort();
|
||||
|
||||
headerLen -= 4; // we've read 4 bytes
|
||||
while (headerLen-- > 0)
|
||||
data.get();
|
||||
|
||||
extraPerEntry = bytesPerEntry - 18; // we want 18 bytes
|
||||
|
||||
Log.v("ddm-thread", "THST: threadCount=" + threadCount);
|
||||
|
||||
/*
|
||||
* For each thread, extract the data, find the appropriate
|
||||
* client, and add it to the ClientData.
|
||||
*/
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
int threadId, status, tid, utime, stime;
|
||||
boolean isDaemon = false;
|
||||
|
||||
threadId = data.getInt();
|
||||
status = data.get();
|
||||
tid = data.getInt();
|
||||
utime = data.getInt();
|
||||
stime = data.getInt();
|
||||
if (bytesPerEntry >= 18)
|
||||
isDaemon = (data.get() != 0);
|
||||
|
||||
Log.v("ddm-thread", " id=" + threadId
|
||||
+ ", status=" + status + ", tid=" + tid
|
||||
+ ", utime=" + utime + ", stime=" + stime);
|
||||
|
||||
ClientData cd = client.getClientData();
|
||||
ThreadInfo threadInfo = cd.getThread(threadId);
|
||||
if (threadInfo != null)
|
||||
threadInfo.updateThread(status, tid, utime, stime, isDaemon);
|
||||
else
|
||||
Log.i("ddms", "Thread with id=" + threadId + " not found");
|
||||
|
||||
// slurp up any extra
|
||||
for (int slurp = extraPerEntry; slurp > 0; slurp--)
|
||||
data.get();
|
||||
}
|
||||
|
||||
client.update(Client.CHANGE_THREAD_DATA);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a THNM (THread NaMe) message. We get one of these after
|
||||
* somebody calls Thread.setName() on a running thread.
|
||||
*/
|
||||
private void handleTHNM(Client client, ByteBuffer data) {
|
||||
int threadId, nameLen;
|
||||
String name;
|
||||
|
||||
threadId = data.getInt();
|
||||
nameLen = data.getInt();
|
||||
name = getString(data, nameLen);
|
||||
|
||||
Log.v("ddm-thread", "THNM: " + threadId + " '" + name + "'");
|
||||
|
||||
ThreadInfo threadInfo = client.getClientData().getThread(threadId);
|
||||
if (threadInfo != null) {
|
||||
threadInfo.setThreadName(name);
|
||||
client.update(Client.CHANGE_THREAD_DATA);
|
||||
} else {
|
||||
Log.i("ddms", "Thread with id=" + threadId + " not found");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parse an incoming STKL.
|
||||
*/
|
||||
private void handleSTKL(Client client, ByteBuffer data) {
|
||||
StackTraceElement[] trace;
|
||||
int i, threadId, stackDepth;
|
||||
@SuppressWarnings("unused")
|
||||
int future;
|
||||
|
||||
future = data.getInt();
|
||||
threadId = data.getInt();
|
||||
|
||||
Log.v("ddms", "STKL: " + threadId);
|
||||
|
||||
/* un-serialize the StackTraceElement[] */
|
||||
stackDepth = data.getInt();
|
||||
trace = new StackTraceElement[stackDepth];
|
||||
for (i = 0; i < stackDepth; i++) {
|
||||
String className, methodName, fileName;
|
||||
int len, lineNumber;
|
||||
|
||||
len = data.getInt();
|
||||
className = getString(data, len);
|
||||
len = data.getInt();
|
||||
methodName = getString(data, len);
|
||||
len = data.getInt();
|
||||
if (len == 0) {
|
||||
fileName = null;
|
||||
} else {
|
||||
fileName = getString(data, len);
|
||||
}
|
||||
lineNumber = data.getInt();
|
||||
|
||||
trace[i] = new StackTraceElement(className, methodName, fileName,
|
||||
lineNumber);
|
||||
}
|
||||
|
||||
ThreadInfo threadInfo = client.getClientData().getThread(threadId);
|
||||
if (threadInfo != null) {
|
||||
threadInfo.setStackCall(trace);
|
||||
client.update(Client.CHANGE_THREAD_STACKTRACE);
|
||||
} else {
|
||||
Log.d("STKL", String.format(
|
||||
"Got stackcall for thread %1$d, which does not exists (anymore?).", //$NON-NLS-1$
|
||||
threadId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send a THEN (THread notification ENable) request to the client.
|
||||
*/
|
||||
public static void sendTHEN(Client client, boolean enable)
|
||||
throws IOException {
|
||||
|
||||
ByteBuffer rawBuf = allocBuffer(1);
|
||||
JdwpPacket packet = new JdwpPacket(rawBuf);
|
||||
ByteBuffer buf = getChunkDataBuf(rawBuf);
|
||||
|
||||
if (enable)
|
||||
buf.put((byte)1);
|
||||
else
|
||||
buf.put((byte)0);
|
||||
|
||||
finishChunkPacket(packet, CHUNK_THEN, buf.position());
|
||||
Log.d("ddm-thread", "Sending " + name(CHUNK_THEN) + ": " + enable);
|
||||
client.sendAndConsume(packet, mInst);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Send a STKL (STacK List) request to the client. The VM will suspend
|
||||
* the target thread, obtain its stack, and return it. If the thread
|
||||
* is no longer running, a failure result will be returned.
|
||||
*/
|
||||
public static void sendSTKL(Client client, int threadId)
|
||||
throws IOException {
|
||||
|
||||
if (false) {
|
||||
Log.i("ddm-thread", "would send STKL " + threadId);
|
||||
return;
|
||||
}
|
||||
|
||||
ByteBuffer rawBuf = allocBuffer(4);
|
||||
JdwpPacket packet = new JdwpPacket(rawBuf);
|
||||
ByteBuffer buf = getChunkDataBuf(rawBuf);
|
||||
|
||||
buf.putInt(threadId);
|
||||
|
||||
finishChunkPacket(packet, CHUNK_STKL, buf.position());
|
||||
Log.d("ddm-thread", "Sending " + name(CHUNK_STKL) + ": " + threadId);
|
||||
client.sendAndConsume(packet, mInst);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is called periodically from the UI thread. To avoid locking
|
||||
* the UI while we request the updates, we create a new thread.
|
||||
*
|
||||
*/
|
||||
static void requestThreadUpdate(final Client client) {
|
||||
if (client.isDdmAware() && client.isThreadUpdateEnabled()) {
|
||||
if (mThreadStatusReqRunning) {
|
||||
Log.w("ddms", "Waiting for previous thread update req to finish");
|
||||
return;
|
||||
}
|
||||
|
||||
new Thread("Thread Status Req") {
|
||||
@Override
|
||||
public void run() {
|
||||
mThreadStatusReqRunning = true;
|
||||
try {
|
||||
sendTHST(client);
|
||||
} catch (IOException ioe) {
|
||||
Log.i("ddms", "Unable to request thread updates from "
|
||||
+ client + ": " + ioe.getMessage());
|
||||
} finally {
|
||||
mThreadStatusReqRunning = false;
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
}
|
||||
|
||||
static void requestThreadStackCallRefresh(final Client client, final int threadId) {
|
||||
if (client.isDdmAware() && client.isThreadUpdateEnabled()) {
|
||||
if (mThreadStackTraceReqRunning ) {
|
||||
Log.w("ddms", "Waiting for previous thread stack call req to finish");
|
||||
return;
|
||||
}
|
||||
|
||||
new Thread("Thread Status Req") {
|
||||
@Override
|
||||
public void run() {
|
||||
mThreadStackTraceReqRunning = true;
|
||||
try {
|
||||
sendSTKL(client, threadId);
|
||||
} catch (IOException ioe) {
|
||||
Log.i("ddms", "Unable to request thread stack call updates from "
|
||||
+ client + ": " + ioe.getMessage());
|
||||
} finally {
|
||||
mThreadStackTraceReqRunning = false;
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a THST request to the specified client.
|
||||
*/
|
||||
private static void sendTHST(Client client) throws IOException {
|
||||
ByteBuffer rawBuf = allocBuffer(0);
|
||||
JdwpPacket packet = new JdwpPacket(rawBuf);
|
||||
ByteBuffer buf = getChunkDataBuf(rawBuf);
|
||||
|
||||
// nothing much to say
|
||||
|
||||
finishChunkPacket(packet, CHUNK_THST, buf.position());
|
||||
Log.d("ddm-thread", "Sending " + name(CHUNK_THST));
|
||||
client.sendAndConsume(packet, mInst);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Handle the "wait" chunk (WAIT). These are sent up when the client is
|
||||
* waiting for something, e.g. for a debugger to attach.
|
||||
*/
|
||||
final class HandleWait extends ChunkHandler {
|
||||
|
||||
public static final int CHUNK_WAIT = ChunkHandler.type("WAIT");
|
||||
|
||||
private static final HandleWait mInst = new HandleWait();
|
||||
|
||||
|
||||
private HandleWait() {}
|
||||
|
||||
/**
|
||||
* Register for the packets we expect to get from the client.
|
||||
*/
|
||||
public static void register(MonitorThread mt) {
|
||||
mt.registerChunkHandler(CHUNK_WAIT, mInst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Client is ready.
|
||||
*/
|
||||
@Override
|
||||
public void clientReady(Client client) throws IOException {}
|
||||
|
||||
/**
|
||||
* Client went away.
|
||||
*/
|
||||
@Override
|
||||
public void clientDisconnected(Client client) {}
|
||||
|
||||
/**
|
||||
* Chunk handler entry point.
|
||||
*/
|
||||
@Override
|
||||
public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {
|
||||
|
||||
Log.d("ddm-wait", "handling " + ChunkHandler.name(type));
|
||||
|
||||
if (type == CHUNK_WAIT) {
|
||||
assert !isReply;
|
||||
handleWAIT(client, data);
|
||||
} else {
|
||||
handleUnknownChunk(client, type, data, isReply, msgId);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a reply to our WAIT message.
|
||||
*/
|
||||
private static void handleWAIT(Client client, ByteBuffer data) {
|
||||
byte reason;
|
||||
|
||||
reason = data.get();
|
||||
|
||||
Log.i("ddm-wait", "WAIT: reason=" + reason);
|
||||
|
||||
|
||||
ClientData cd = client.getClientData();
|
||||
synchronized (cd) {
|
||||
cd.setDebuggerConnectionStatus(ClientData.DEBUGGER_WAITING);
|
||||
}
|
||||
|
||||
client.update(Client.CHANGE_DEBUGGER_INTEREST);
|
||||
}
|
||||
}
|
||||
|
||||
446
tools/ddms/libs/ddmlib/src/com/android/ddmlib/HeapSegment.java
Normal file
446
tools/ddms/libs/ddmlib/src/com/android/ddmlib/HeapSegment.java
Normal file
@@ -0,0 +1,446 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.nio.BufferUnderflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.text.ParseException;
|
||||
|
||||
/**
|
||||
* Describes the types and locations of objects in a segment of a heap.
|
||||
*/
|
||||
public final class HeapSegment implements Comparable<HeapSegment> {
|
||||
|
||||
/**
|
||||
* Describes an object/region encoded in the HPSG data.
|
||||
*/
|
||||
public static class HeapSegmentElement implements Comparable<HeapSegmentElement> {
|
||||
|
||||
/*
|
||||
* Solidity values, which must match the values in
|
||||
* the HPSG data.
|
||||
*/
|
||||
|
||||
/** The element describes a free block. */
|
||||
public static int SOLIDITY_FREE = 0;
|
||||
|
||||
/** The element is strongly-reachable. */
|
||||
public static int SOLIDITY_HARD = 1;
|
||||
|
||||
/** The element is softly-reachable. */
|
||||
public static int SOLIDITY_SOFT = 2;
|
||||
|
||||
/** The element is weakly-reachable. */
|
||||
public static int SOLIDITY_WEAK = 3;
|
||||
|
||||
/** The element is phantom-reachable. */
|
||||
public static int SOLIDITY_PHANTOM = 4;
|
||||
|
||||
/** The element is pending finalization. */
|
||||
public static int SOLIDITY_FINALIZABLE = 5;
|
||||
|
||||
/** The element is not reachable, and is about to be swept/freed. */
|
||||
public static int SOLIDITY_SWEEP = 6;
|
||||
|
||||
/** The reachability of the object is unknown. */
|
||||
public static int SOLIDITY_INVALID = -1;
|
||||
|
||||
|
||||
/*
|
||||
* Kind values, which must match the values in
|
||||
* the HPSG data.
|
||||
*/
|
||||
|
||||
/** The element describes a data object. */
|
||||
public static int KIND_OBJECT = 0;
|
||||
|
||||
/** The element describes a class object. */
|
||||
public static int KIND_CLASS_OBJECT = 1;
|
||||
|
||||
/** The element describes an array of 1-byte elements. */
|
||||
public static int KIND_ARRAY_1 = 2;
|
||||
|
||||
/** The element describes an array of 2-byte elements. */
|
||||
public static int KIND_ARRAY_2 = 3;
|
||||
|
||||
/** The element describes an array of 4-byte elements. */
|
||||
public static int KIND_ARRAY_4 = 4;
|
||||
|
||||
/** The element describes an array of 8-byte elements. */
|
||||
public static int KIND_ARRAY_8 = 5;
|
||||
|
||||
/** The element describes an unknown type of object. */
|
||||
public static int KIND_UNKNOWN = 6;
|
||||
|
||||
/** The element describes a native object. */
|
||||
public static int KIND_NATIVE = 7;
|
||||
|
||||
/** The object kind is unknown or unspecified. */
|
||||
public static int KIND_INVALID = -1;
|
||||
|
||||
|
||||
/**
|
||||
* A bit in the HPSG data that indicates that an element should
|
||||
* be combined with the element that follows, typically because
|
||||
* an element is too large to be described by a single element.
|
||||
*/
|
||||
private static int PARTIAL_MASK = 1 << 7;
|
||||
|
||||
|
||||
/**
|
||||
* Describes the reachability/solidity of the element. Must
|
||||
* be set to one of the SOLIDITY_* values.
|
||||
*/
|
||||
private int mSolidity;
|
||||
|
||||
/**
|
||||
* Describes the type/kind of the element. Must be set to one
|
||||
* of the KIND_* values.
|
||||
*/
|
||||
private int mKind;
|
||||
|
||||
/**
|
||||
* Describes the length of the element, in bytes.
|
||||
*/
|
||||
private int mLength;
|
||||
|
||||
|
||||
/**
|
||||
* Creates an uninitialized element.
|
||||
*/
|
||||
public HeapSegmentElement() {
|
||||
setSolidity(SOLIDITY_INVALID);
|
||||
setKind(KIND_INVALID);
|
||||
setLength(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an element describing the entry at the current
|
||||
* position of hpsgData.
|
||||
*
|
||||
* @param hs The heap segment to pull the entry from.
|
||||
* @throws BufferUnderflowException if there is not a whole entry
|
||||
* following the current position
|
||||
* of hpsgData.
|
||||
* @throws ParseException if the provided data is malformed.
|
||||
*/
|
||||
public HeapSegmentElement(HeapSegment hs)
|
||||
throws BufferUnderflowException, ParseException {
|
||||
set(hs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the element with the entry at the current position of
|
||||
* hpsgData.
|
||||
*
|
||||
* @param hs The heap segment to pull the entry from.
|
||||
* @return this object.
|
||||
* @throws BufferUnderflowException if there is not a whole entry
|
||||
* following the current position of
|
||||
* hpsgData.
|
||||
* @throws ParseException if the provided data is malformed.
|
||||
*/
|
||||
public HeapSegmentElement set(HeapSegment hs)
|
||||
throws BufferUnderflowException, ParseException {
|
||||
|
||||
/* TODO: Maybe keep track of the virtual address of each element
|
||||
* so that they can be examined independently.
|
||||
*/
|
||||
ByteBuffer data = hs.mUsageData;
|
||||
int eState = (int)data.get() & 0x000000ff;
|
||||
int eLen = ((int)data.get() & 0x000000ff) + 1;
|
||||
|
||||
while ((eState & PARTIAL_MASK) != 0) {
|
||||
|
||||
/* If the partial bit was set, the next byte should describe
|
||||
* the same object as the current one.
|
||||
*/
|
||||
int nextState = (int)data.get() & 0x000000ff;
|
||||
if ((nextState & ~PARTIAL_MASK) != (eState & ~PARTIAL_MASK)) {
|
||||
throw new ParseException("State mismatch", data.position());
|
||||
}
|
||||
eState = nextState;
|
||||
eLen += ((int)data.get() & 0x000000ff) + 1;
|
||||
}
|
||||
|
||||
setSolidity(eState & 0x7);
|
||||
setKind((eState >> 3) & 0x7);
|
||||
setLength(eLen * hs.mAllocationUnitSize);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getSolidity() {
|
||||
return mSolidity;
|
||||
}
|
||||
|
||||
public void setSolidity(int solidity) {
|
||||
this.mSolidity = solidity;
|
||||
}
|
||||
|
||||
public int getKind() {
|
||||
return mKind;
|
||||
}
|
||||
|
||||
public void setKind(int kind) {
|
||||
this.mKind = kind;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return mLength;
|
||||
}
|
||||
|
||||
public void setLength(int length) {
|
||||
this.mLength = length;
|
||||
}
|
||||
|
||||
public int compareTo(HeapSegmentElement other) {
|
||||
if (mLength != other.mLength) {
|
||||
return mLength < other.mLength ? -1 : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//* The ID of the heap that this segment belongs to.
|
||||
protected int mHeapId;
|
||||
|
||||
//* The size of an allocation unit, in bytes. (e.g., 8 bytes)
|
||||
protected int mAllocationUnitSize;
|
||||
|
||||
//* The virtual address of the start of this segment.
|
||||
protected long mStartAddress;
|
||||
|
||||
//* The offset of this pices from mStartAddress, in bytes.
|
||||
protected int mOffset;
|
||||
|
||||
//* The number of allocation units described in this segment.
|
||||
protected int mAllocationUnitCount;
|
||||
|
||||
//* The raw data that describes the contents of this segment.
|
||||
protected ByteBuffer mUsageData;
|
||||
|
||||
//* mStartAddress is set to this value when the segment becomes invalid.
|
||||
private final static long INVALID_START_ADDRESS = -1;
|
||||
|
||||
/**
|
||||
* Create a new HeapSegment based on the raw contents
|
||||
* of an HPSG chunk.
|
||||
*
|
||||
* @param hpsgData The raw data from an HPSG chunk.
|
||||
* @throws BufferUnderflowException if hpsgData is too small
|
||||
* to hold the HPSG chunk header data.
|
||||
*/
|
||||
public HeapSegment(ByteBuffer hpsgData) throws BufferUnderflowException {
|
||||
/* Read the HPSG chunk header.
|
||||
* These get*() calls may throw a BufferUnderflowException
|
||||
* if the underlying data isn't big enough.
|
||||
*/
|
||||
hpsgData.order(ByteOrder.BIG_ENDIAN);
|
||||
mHeapId = hpsgData.getInt();
|
||||
mAllocationUnitSize = (int) hpsgData.get();
|
||||
mStartAddress = (long) hpsgData.getInt() & 0x00000000ffffffffL;
|
||||
mOffset = hpsgData.getInt();
|
||||
mAllocationUnitCount = hpsgData.getInt();
|
||||
|
||||
// Hold onto the remainder of the data.
|
||||
mUsageData = hpsgData.slice();
|
||||
mUsageData.order(ByteOrder.BIG_ENDIAN); // doesn't actually matter
|
||||
|
||||
// Validate the data.
|
||||
//xxx do it
|
||||
//xxx make sure the number of elements matches mAllocationUnitCount.
|
||||
//xxx make sure the last element doesn't have P set
|
||||
}
|
||||
|
||||
/**
|
||||
* See if this segment still contains data, and has not been
|
||||
* appended to another segment.
|
||||
*
|
||||
* @return true if this segment has not been appended to
|
||||
* another segment.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return mStartAddress != INVALID_START_ADDRESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* See if <code>other</code> comes immediately after this segment.
|
||||
*
|
||||
* @param other The HeapSegment to check.
|
||||
* @return true if <code>other</code> comes immediately after this
|
||||
* segment.
|
||||
*/
|
||||
public boolean canAppend(HeapSegment other) {
|
||||
return isValid() && other.isValid() && mHeapId == other.mHeapId &&
|
||||
mAllocationUnitSize == other.mAllocationUnitSize &&
|
||||
getEndAddress() == other.getStartAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the contents of <code>other</code> to this segment
|
||||
* if it describes the segment immediately after this one.
|
||||
*
|
||||
* @param other The segment to append to this segment, if possible.
|
||||
* If appended, <code>other</code> will be invalid
|
||||
* when this method returns.
|
||||
* @return true if <code>other</code> was successfully appended to
|
||||
* this segment.
|
||||
*/
|
||||
public boolean append(HeapSegment other) {
|
||||
if (canAppend(other)) {
|
||||
/* Preserve the position. The mark is not preserved,
|
||||
* but we don't use it anyway.
|
||||
*/
|
||||
int pos = mUsageData.position();
|
||||
|
||||
// Guarantee that we have enough room for the new data.
|
||||
if (mUsageData.capacity() - mUsageData.limit() <
|
||||
other.mUsageData.limit()) {
|
||||
/* Grow more than necessary in case another append()
|
||||
* is about to happen.
|
||||
*/
|
||||
int newSize = mUsageData.limit() + other.mUsageData.limit();
|
||||
ByteBuffer newData = ByteBuffer.allocate(newSize * 2);
|
||||
|
||||
mUsageData.rewind();
|
||||
newData.put(mUsageData);
|
||||
mUsageData = newData;
|
||||
}
|
||||
|
||||
// Copy the data from the other segment and restore the position.
|
||||
other.mUsageData.rewind();
|
||||
mUsageData.put(other.mUsageData);
|
||||
mUsageData.position(pos);
|
||||
|
||||
// Fix this segment's header to cover the new data.
|
||||
mAllocationUnitCount += other.mAllocationUnitCount;
|
||||
|
||||
// Mark the other segment as invalid.
|
||||
other.mStartAddress = INVALID_START_ADDRESS;
|
||||
other.mUsageData = null;
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public long getStartAddress() {
|
||||
return mStartAddress + mOffset;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return mAllocationUnitSize * mAllocationUnitCount;
|
||||
}
|
||||
|
||||
public long getEndAddress() {
|
||||
return getStartAddress() + getLength();
|
||||
}
|
||||
|
||||
public void rewindElements() {
|
||||
if (mUsageData != null) {
|
||||
mUsageData.rewind();
|
||||
}
|
||||
}
|
||||
|
||||
public HeapSegmentElement getNextElement(HeapSegmentElement reuse) {
|
||||
try {
|
||||
if (reuse != null) {
|
||||
return reuse.set(this);
|
||||
} else {
|
||||
return new HeapSegmentElement(this);
|
||||
}
|
||||
} catch (BufferUnderflowException ex) {
|
||||
/* Normal "end of buffer" situation.
|
||||
*/
|
||||
} catch (ParseException ex) {
|
||||
/* Malformed data.
|
||||
*/
|
||||
//TODO: we should catch this in the constructor
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Method overrides for Comparable
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof HeapSegment) {
|
||||
return compareTo((HeapSegment) o) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mHeapId * 31 +
|
||||
mAllocationUnitSize * 31 +
|
||||
(int) mStartAddress * 31 +
|
||||
mOffset * 31 +
|
||||
mAllocationUnitCount * 31 +
|
||||
mUsageData.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder str = new StringBuilder();
|
||||
|
||||
str.append("HeapSegment { heap ").append(mHeapId)
|
||||
.append(", start 0x")
|
||||
.append(Integer.toHexString((int) getStartAddress()))
|
||||
.append(", length ").append(getLength())
|
||||
.append(" }");
|
||||
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
public int compareTo(HeapSegment other) {
|
||||
if (mHeapId != other.mHeapId) {
|
||||
return mHeapId < other.mHeapId ? -1 : 1;
|
||||
}
|
||||
if (getStartAddress() != other.getStartAddress()) {
|
||||
return getStartAddress() < other.getStartAddress() ? -1 : 1;
|
||||
}
|
||||
|
||||
/* If two segments have the same start address, the rest of
|
||||
* the fields should be equal. Go through the motions, though.
|
||||
* Note that we re-check the components of getStartAddress()
|
||||
* (mStartAddress and mOffset) to make sure that all fields in
|
||||
* an equal segment are equal.
|
||||
*/
|
||||
|
||||
if (mAllocationUnitSize != other.mAllocationUnitSize) {
|
||||
return mAllocationUnitSize < other.mAllocationUnitSize ? -1 : 1;
|
||||
}
|
||||
if (mStartAddress != other.mStartAddress) {
|
||||
return mStartAddress < other.mStartAddress ? -1 : 1;
|
||||
}
|
||||
if (mOffset != other.mOffset) {
|
||||
return mOffset < other.mOffset ? -1 : 1;
|
||||
}
|
||||
if (mAllocationUnitCount != other.mAllocationUnitCount) {
|
||||
return mAllocationUnitCount < other.mAllocationUnitCount ? -1 : 1;
|
||||
}
|
||||
if (mUsageData != other.mUsageData) {
|
||||
return mUsageData.compareTo(other.mUsageData);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
184
tools/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java
Executable file
184
tools/ddms/libs/ddmlib/src/com/android/ddmlib/IDevice.java
Executable file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import com.android.ddmlib.Device.DeviceState;
|
||||
import com.android.ddmlib.log.LogReceiver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* A Device. It can be a physical device or an emulator.
|
||||
*/
|
||||
public interface IDevice {
|
||||
|
||||
public final static String PROP_BUILD_VERSION = "ro.build.version.release";
|
||||
public final static String PROP_BUILD_VERSION_NUMBER = "ro.build.version.sdk";
|
||||
public final static String PROP_DEBUGGABLE = "ro.debuggable";
|
||||
/** Serial number of the first connected emulator. */
|
||||
public final static String FIRST_EMULATOR_SN = "emulator-5554"; //$NON-NLS-1$
|
||||
/** Device change bit mask: {@link DeviceState} change. */
|
||||
public static final int CHANGE_STATE = 0x0001;
|
||||
/** Device change bit mask: {@link Client} list change. */
|
||||
public static final int CHANGE_CLIENT_LIST = 0x0002;
|
||||
/** Device change bit mask: build info change. */
|
||||
public static final int CHANGE_BUILD_INFO = 0x0004;
|
||||
|
||||
/**
|
||||
* Returns the serial number of the device.
|
||||
*/
|
||||
public String getSerialNumber();
|
||||
|
||||
/**
|
||||
* Returns the name of the AVD the emulator is running.
|
||||
* <p/>This is only valid if {@link #isEmulator()} returns true.
|
||||
* <p/>If the emulator is not running any AVD (for instance it's running from an Android source
|
||||
* tree build), this method will return "<code><build></code>".
|
||||
* @return the name of the AVD or <code>null</code> if there isn't any.
|
||||
*/
|
||||
public String getAvdName();
|
||||
|
||||
/**
|
||||
* Returns the state of the device.
|
||||
*/
|
||||
public DeviceState getState();
|
||||
|
||||
/**
|
||||
* Returns the device properties. It contains the whole output of 'getprop'
|
||||
*/
|
||||
public Map<String, String> getProperties();
|
||||
|
||||
/**
|
||||
* Returns the number of property for this device.
|
||||
*/
|
||||
public int getPropertyCount();
|
||||
|
||||
/**
|
||||
* Returns a property value.
|
||||
* @param name the name of the value to return.
|
||||
* @return the value or <code>null</code> if the property does not exist.
|
||||
*/
|
||||
public String getProperty(String name);
|
||||
|
||||
/**
|
||||
* Returns if the device is ready.
|
||||
* @return <code>true</code> if {@link #getState()} returns {@link DeviceState#ONLINE}.
|
||||
*/
|
||||
public boolean isOnline();
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if the device is an emulator.
|
||||
*/
|
||||
public boolean isEmulator();
|
||||
|
||||
/**
|
||||
* Returns if the device is offline.
|
||||
* @return <code>true</code> if {@link #getState()} returns {@link DeviceState#OFFLINE}.
|
||||
*/
|
||||
public boolean isOffline();
|
||||
|
||||
/**
|
||||
* Returns if the device is in bootloader mode.
|
||||
* @return <code>true</code> if {@link #getState()} returns {@link DeviceState#BOOTLOADER}.
|
||||
*/
|
||||
public boolean isBootLoader();
|
||||
|
||||
/**
|
||||
* Returns whether the {@link Device} has {@link Client}s.
|
||||
*/
|
||||
public boolean hasClients();
|
||||
|
||||
/**
|
||||
* Returns the array of clients.
|
||||
*/
|
||||
public Client[] getClients();
|
||||
|
||||
/**
|
||||
* Returns a {@link Client} by its application name.
|
||||
* @param applicationName the name of the application
|
||||
* @return the <code>Client</code> object or <code>null</code> if no match was found.
|
||||
*/
|
||||
public Client getClient(String applicationName);
|
||||
|
||||
/**
|
||||
* Returns a {@link SyncService} object to push / pull files to and from the device.
|
||||
* @return <code>null</code> if the SyncService couldn't be created.
|
||||
*/
|
||||
public SyncService getSyncService();
|
||||
|
||||
/**
|
||||
* Returns a {@link FileListingService} for this device.
|
||||
*/
|
||||
public FileListingService getFileListingService();
|
||||
|
||||
/**
|
||||
* Takes a screen shot of the device and returns it as a {@link RawImage}.
|
||||
* @return the screenshot as a <code>RawImage</code> or <code>null</code> if
|
||||
* something went wrong.
|
||||
* @throws IOException
|
||||
*/
|
||||
public RawImage getScreenshot() throws IOException;
|
||||
|
||||
/**
|
||||
* Executes a shell command on the device, and sends the result to a receiver.
|
||||
* @param command The command to execute
|
||||
* @param receiver The receiver object getting the result from the command.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void executeShellCommand(String command,
|
||||
IShellOutputReceiver receiver) throws IOException;
|
||||
|
||||
/**
|
||||
* Runs the event log service and outputs the event log to the {@link LogReceiver}.
|
||||
* @param receiver the receiver to receive the event log entries.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void runEventLogService(LogReceiver receiver) throws IOException;
|
||||
|
||||
/**
|
||||
* Runs the log service for the given log and outputs the log to the {@link LogReceiver}.
|
||||
* @param logname the logname of the log to read from.
|
||||
* @param receiver the receiver to receive the event log entries.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void runLogService(String logname, LogReceiver receiver) throws IOException;
|
||||
|
||||
/**
|
||||
* Creates a port forwarding between a local and a remote port.
|
||||
* @param localPort the local port to forward
|
||||
* @param remotePort the remote port.
|
||||
* @return <code>true</code> if success.
|
||||
*/
|
||||
public boolean createForward(int localPort, int remotePort);
|
||||
|
||||
/**
|
||||
* Removes a port forwarding between a local and a remote port.
|
||||
* @param localPort the local port to forward
|
||||
* @param remotePort the remote port.
|
||||
* @return <code>true</code> if success.
|
||||
*/
|
||||
public boolean removeForward(int localPort, int remotePort);
|
||||
|
||||
/**
|
||||
* Returns the name of the client by pid or <code>null</code> if pid is unknown
|
||||
* @param pid the pid of the client.
|
||||
*/
|
||||
public String getClientName(int pid);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
/**
|
||||
* Classes which implement this interface provide methods that deal with out from a remote shell
|
||||
* command on a device/emulator.
|
||||
*/
|
||||
public interface IShellOutputReceiver {
|
||||
/**
|
||||
* Called every time some new data is available.
|
||||
* @param data The new data.
|
||||
* @param offset The offset at which the new data starts.
|
||||
* @param length The length of the new data.
|
||||
*/
|
||||
public void addOutput(byte[] data, int offset, int length);
|
||||
|
||||
/**
|
||||
* Called at the end of the process execution (unless the process was
|
||||
* canceled). This allows the receiver to terminate and flush whatever
|
||||
* data was not yet processed.
|
||||
*/
|
||||
public void flush();
|
||||
|
||||
/**
|
||||
* Cancel method to stop the execution of the remote shell command.
|
||||
* @return true to cancel the execution of the command.
|
||||
*/
|
||||
public boolean isCancelled();
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
/**
|
||||
* Classes which implement this interface provide a method that returns a stack trace.
|
||||
*/
|
||||
public interface IStackTraceInfo {
|
||||
|
||||
/**
|
||||
* Returns the stack trace. This can be <code>null</code>.
|
||||
*/
|
||||
public StackTraceElement[] getStackTrace();
|
||||
|
||||
}
|
||||
371
tools/ddms/libs/ddmlib/src/com/android/ddmlib/JdwpPacket.java
Normal file
371
tools/ddms/libs/ddmlib/src/com/android/ddmlib/JdwpPacket.java
Normal file
@@ -0,0 +1,371 @@
|
||||
/* //device/tools/ddms/libs/ddmlib/src/com/android/ddmlib/JdwpPacket.java
|
||||
**
|
||||
** Copyright 2007, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
/**
|
||||
* A JDWP packet, sitting at the start of a ByteBuffer somewhere.
|
||||
*
|
||||
* This allows us to wrap a "pointer" to the data with the results of
|
||||
* decoding the packet.
|
||||
*
|
||||
* None of the operations here are synchronized. If multiple threads will
|
||||
* be accessing the same ByteBuffers, external sync will be required.
|
||||
*
|
||||
* Use the constructor to create an empty packet, or "findPacket()" to
|
||||
* wrap a JdwpPacket around existing data.
|
||||
*/
|
||||
final class JdwpPacket {
|
||||
// header len
|
||||
public static final int JDWP_HEADER_LEN = 11;
|
||||
|
||||
// results from findHandshake
|
||||
public static final int HANDSHAKE_GOOD = 1;
|
||||
public static final int HANDSHAKE_NOTYET = 2;
|
||||
public static final int HANDSHAKE_BAD = 3;
|
||||
|
||||
// our cmdSet/cmd
|
||||
private static final int DDMS_CMD_SET = 0xc7; // 'G' + 128
|
||||
private static final int DDMS_CMD = 0x01;
|
||||
|
||||
// "flags" field
|
||||
private static final int REPLY_PACKET = 0x80;
|
||||
|
||||
// this is sent and expected at the start of a JDWP connection
|
||||
private static final byte[] mHandshake = {
|
||||
'J', 'D', 'W', 'P', '-', 'H', 'a', 'n', 'd', 's', 'h', 'a', 'k', 'e'
|
||||
};
|
||||
|
||||
public static final int HANDSHAKE_LEN = mHandshake.length;
|
||||
|
||||
private ByteBuffer mBuffer;
|
||||
private int mLength, mId, mFlags, mCmdSet, mCmd, mErrCode;
|
||||
private boolean mIsNew;
|
||||
|
||||
private static int mSerialId = 0x40000000;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new, empty packet, in "buf".
|
||||
*/
|
||||
JdwpPacket(ByteBuffer buf) {
|
||||
mBuffer = buf;
|
||||
mIsNew = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish a packet created with newPacket().
|
||||
*
|
||||
* This always creates a command packet, with the next serial number
|
||||
* in sequence.
|
||||
*
|
||||
* We have to take "payloadLength" as an argument because we can't
|
||||
* see the position in the "slice" returned by getPayload(). We could
|
||||
* fish it out of the chunk header, but it's legal for there to be
|
||||
* more than one chunk in a JDWP packet.
|
||||
*
|
||||
* On exit, "position" points to the end of the data.
|
||||
*/
|
||||
void finishPacket(int payloadLength) {
|
||||
assert mIsNew;
|
||||
|
||||
ByteOrder oldOrder = mBuffer.order();
|
||||
mBuffer.order(ChunkHandler.CHUNK_ORDER);
|
||||
|
||||
mLength = JDWP_HEADER_LEN + payloadLength;
|
||||
mId = getNextSerial();
|
||||
mFlags = 0;
|
||||
mCmdSet = DDMS_CMD_SET;
|
||||
mCmd = DDMS_CMD;
|
||||
|
||||
mBuffer.putInt(0x00, mLength);
|
||||
mBuffer.putInt(0x04, mId);
|
||||
mBuffer.put(0x08, (byte) mFlags);
|
||||
mBuffer.put(0x09, (byte) mCmdSet);
|
||||
mBuffer.put(0x0a, (byte) mCmd);
|
||||
|
||||
mBuffer.order(oldOrder);
|
||||
mBuffer.position(mLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next serial number. This creates a unique serial number
|
||||
* across all connections, not just for the current connection. This
|
||||
* is a useful property when debugging, but isn't necessary.
|
||||
*
|
||||
* We can't synchronize on an int, so we use a sync method.
|
||||
*/
|
||||
private static synchronized int getNextSerial() {
|
||||
return mSerialId++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a slice of the byte buffer, positioned past the JDWP header
|
||||
* to the start of the chunk header. The buffer's limit will be set
|
||||
* to the size of the payload if the size is known; if this is a
|
||||
* packet under construction the limit will be set to the end of the
|
||||
* buffer.
|
||||
*
|
||||
* Doesn't examine the packet at all -- works on empty buffers.
|
||||
*/
|
||||
ByteBuffer getPayload() {
|
||||
ByteBuffer buf;
|
||||
int oldPosn = mBuffer.position();
|
||||
|
||||
mBuffer.position(JDWP_HEADER_LEN);
|
||||
buf = mBuffer.slice(); // goes from position to limit
|
||||
mBuffer.position(oldPosn);
|
||||
|
||||
if (mLength > 0)
|
||||
buf.limit(mLength - JDWP_HEADER_LEN);
|
||||
else
|
||||
assert mIsNew;
|
||||
buf.order(ChunkHandler.CHUNK_ORDER);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns "true" if this JDWP packet has a JDWP command type.
|
||||
*
|
||||
* This never returns "true" for reply packets.
|
||||
*/
|
||||
boolean isDdmPacket() {
|
||||
return (mFlags & REPLY_PACKET) == 0 &&
|
||||
mCmdSet == DDMS_CMD_SET &&
|
||||
mCmd == DDMS_CMD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns "true" if this JDWP packet is tagged as a reply.
|
||||
*/
|
||||
boolean isReply() {
|
||||
return (mFlags & REPLY_PACKET) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns "true" if this JDWP packet is a reply with a nonzero
|
||||
* error code.
|
||||
*/
|
||||
boolean isError() {
|
||||
return isReply() && mErrCode != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns "true" if this JDWP packet has no data.
|
||||
*/
|
||||
boolean isEmpty() {
|
||||
return (mLength == JDWP_HEADER_LEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the packet's ID. For a reply packet, this allows us to
|
||||
* match the reply with the original request.
|
||||
*/
|
||||
int getId() {
|
||||
return mId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the length of a packet. This includes the header, so an
|
||||
* empty packet is 11 bytes long.
|
||||
*/
|
||||
int getLength() {
|
||||
return mLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write our packet to "chan". Consumes the packet as part of the
|
||||
* write.
|
||||
*
|
||||
* The JDWP packet starts at offset 0 and ends at mBuffer.position().
|
||||
*/
|
||||
void writeAndConsume(SocketChannel chan) throws IOException {
|
||||
int oldLimit;
|
||||
|
||||
//Log.i("ddms", "writeAndConsume: pos=" + mBuffer.position()
|
||||
// + ", limit=" + mBuffer.limit());
|
||||
|
||||
assert mLength > 0;
|
||||
|
||||
mBuffer.flip(); // limit<-posn, posn<-0
|
||||
oldLimit = mBuffer.limit();
|
||||
mBuffer.limit(mLength);
|
||||
while (mBuffer.position() != mBuffer.limit()) {
|
||||
chan.write(mBuffer);
|
||||
}
|
||||
// position should now be at end of packet
|
||||
assert mBuffer.position() == mLength;
|
||||
|
||||
mBuffer.limit(oldLimit);
|
||||
mBuffer.compact(); // shift posn...limit, posn<-pending data
|
||||
|
||||
//Log.i("ddms", " : pos=" + mBuffer.position()
|
||||
// + ", limit=" + mBuffer.limit());
|
||||
}
|
||||
|
||||
/**
|
||||
* "Move" the packet data out of the buffer we're sitting on and into
|
||||
* buf at the current position.
|
||||
*/
|
||||
void movePacket(ByteBuffer buf) {
|
||||
Log.v("ddms", "moving " + mLength + " bytes");
|
||||
int oldPosn = mBuffer.position();
|
||||
|
||||
mBuffer.position(0);
|
||||
mBuffer.limit(mLength);
|
||||
buf.put(mBuffer);
|
||||
mBuffer.position(mLength);
|
||||
mBuffer.limit(oldPosn);
|
||||
mBuffer.compact(); // shift posn...limit, posn<-pending data
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume the JDWP packet.
|
||||
*
|
||||
* On entry and exit, "position" is the #of bytes in the buffer.
|
||||
*/
|
||||
void consume()
|
||||
{
|
||||
//Log.d("ddms", "consuming " + mLength + " bytes");
|
||||
//Log.d("ddms", " posn=" + mBuffer.position()
|
||||
// + ", limit=" + mBuffer.limit());
|
||||
|
||||
/*
|
||||
* The "flip" call sets "limit" equal to the position (usually the
|
||||
* end of data) and "position" equal to zero.
|
||||
*
|
||||
* compact() copies everything from "position" and "limit" to the
|
||||
* start of the buffer, sets "position" to the end of data, and
|
||||
* sets "limit" to the capacity.
|
||||
*
|
||||
* On entry, "position" is set to the amount of data in the buffer
|
||||
* and "limit" is set to the capacity. We want to call flip()
|
||||
* so that position..limit spans our data, advance "position" past
|
||||
* the current packet, then compact.
|
||||
*/
|
||||
mBuffer.flip(); // limit<-posn, posn<-0
|
||||
mBuffer.position(mLength);
|
||||
mBuffer.compact(); // shift posn...limit, posn<-pending data
|
||||
mLength = 0;
|
||||
//Log.d("ddms", " after compact, posn=" + mBuffer.position()
|
||||
// + ", limit=" + mBuffer.limit());
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the JDWP packet at the start of "buf". The start is known,
|
||||
* but the length has to be parsed out.
|
||||
*
|
||||
* On entry, the packet data in "buf" must start at offset 0 and end
|
||||
* at "position". "limit" should be set to the buffer capacity. This
|
||||
* method does not alter "buf"s attributes.
|
||||
*
|
||||
* Returns a new JdwpPacket if a full one is found in the buffer. If
|
||||
* not, returns null. Throws an exception if the data doesn't look like
|
||||
* a valid JDWP packet.
|
||||
*/
|
||||
static JdwpPacket findPacket(ByteBuffer buf) {
|
||||
int count = buf.position();
|
||||
int length, id, flags, cmdSet, cmd;
|
||||
|
||||
if (count < JDWP_HEADER_LEN)
|
||||
return null;
|
||||
|
||||
ByteOrder oldOrder = buf.order();
|
||||
buf.order(ChunkHandler.CHUNK_ORDER);
|
||||
|
||||
length = buf.getInt(0x00);
|
||||
id = buf.getInt(0x04);
|
||||
flags = buf.get(0x08) & 0xff;
|
||||
cmdSet = buf.get(0x09) & 0xff;
|
||||
cmd = buf.get(0x0a) & 0xff;
|
||||
|
||||
buf.order(oldOrder);
|
||||
|
||||
if (length < JDWP_HEADER_LEN)
|
||||
throw new BadPacketException();
|
||||
if (count < length)
|
||||
return null;
|
||||
|
||||
JdwpPacket pkt = new JdwpPacket(buf);
|
||||
//pkt.mBuffer = buf;
|
||||
pkt.mLength = length;
|
||||
pkt.mId = id;
|
||||
pkt.mFlags = flags;
|
||||
|
||||
if ((flags & REPLY_PACKET) == 0) {
|
||||
pkt.mCmdSet = cmdSet;
|
||||
pkt.mCmd = cmd;
|
||||
pkt.mErrCode = -1;
|
||||
} else {
|
||||
pkt.mCmdSet = -1;
|
||||
pkt.mCmd = -1;
|
||||
pkt.mErrCode = cmdSet | (cmd << 8);
|
||||
}
|
||||
|
||||
return pkt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like findPacket(), but when we're expecting the JDWP handshake.
|
||||
*
|
||||
* Returns one of:
|
||||
* HANDSHAKE_GOOD - found handshake, looks good
|
||||
* HANDSHAKE_BAD - found enough data, but it's wrong
|
||||
* HANDSHAKE_NOTYET - not enough data has been read yet
|
||||
*/
|
||||
static int findHandshake(ByteBuffer buf) {
|
||||
int count = buf.position();
|
||||
int i;
|
||||
|
||||
if (count < mHandshake.length)
|
||||
return HANDSHAKE_NOTYET;
|
||||
|
||||
for (i = mHandshake.length -1; i >= 0; --i) {
|
||||
if (buf.get(i) != mHandshake[i])
|
||||
return HANDSHAKE_BAD;
|
||||
}
|
||||
|
||||
return HANDSHAKE_GOOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the handshake string from the buffer.
|
||||
*
|
||||
* On entry and exit, "position" is the #of bytes in the buffer.
|
||||
*/
|
||||
static void consumeHandshake(ByteBuffer buf) {
|
||||
// in theory, nothing else can have arrived, so this is overkill
|
||||
buf.flip(); // limit<-posn, posn<-0
|
||||
buf.position(mHandshake.length);
|
||||
buf.compact(); // shift posn...limit, posn<-pending data
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the handshake string into the output buffer.
|
||||
*
|
||||
* On exit, "buf"s position will be advanced.
|
||||
*/
|
||||
static void putHandshake(ByteBuffer buf) {
|
||||
buf.put(mHandshake);
|
||||
}
|
||||
}
|
||||
|
||||
351
tools/ddms/libs/ddmlib/src/com/android/ddmlib/Log.java
Normal file
351
tools/ddms/libs/ddmlib/src/com/android/ddmlib/Log.java
Normal file
@@ -0,0 +1,351 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
/**
|
||||
* Log class that mirrors the API in main Android sources.
|
||||
* <p/>Default behavior outputs the log to {@link System#out}. Use
|
||||
* {@link #setLogOutput(com.android.ddmlib.Log.ILogOutput)} to redirect the log somewhere else.
|
||||
*/
|
||||
public final class Log {
|
||||
|
||||
/**
|
||||
* Log Level enum.
|
||||
*/
|
||||
public enum LogLevel {
|
||||
VERBOSE(2, "verbose", 'V'), //$NON-NLS-1$
|
||||
DEBUG(3, "debug", 'D'), //$NON-NLS-1$
|
||||
INFO(4, "info", 'I'), //$NON-NLS-1$
|
||||
WARN(5, "warn", 'W'), //$NON-NLS-1$
|
||||
ERROR(6, "error", 'E'), //$NON-NLS-1$
|
||||
ASSERT(7, "assert", 'A'); //$NON-NLS-1$
|
||||
|
||||
private int mPriorityLevel;
|
||||
private String mStringValue;
|
||||
private char mPriorityLetter;
|
||||
|
||||
LogLevel(int intPriority, String stringValue, char priorityChar) {
|
||||
mPriorityLevel = intPriority;
|
||||
mStringValue = stringValue;
|
||||
mPriorityLetter = priorityChar;
|
||||
}
|
||||
|
||||
public static LogLevel getByString(String value) {
|
||||
for (LogLevel mode : values()) {
|
||||
if (mode.mStringValue.equals(value)) {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link LogLevel} enum matching the specified letter.
|
||||
* @param letter the letter matching a <code>LogLevel</code> enum
|
||||
* @return a <code>LogLevel</code> object or <code>null</code> if no match were found.
|
||||
*/
|
||||
public static LogLevel getByLetter(char letter) {
|
||||
for (LogLevel mode : values()) {
|
||||
if (mode.mPriorityLetter == letter) {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link LogLevel} enum matching the specified letter.
|
||||
* <p/>
|
||||
* The letter is passed as a {@link String} argument, but only the first character
|
||||
* is used.
|
||||
* @param letter the letter matching a <code>LogLevel</code> enum
|
||||
* @return a <code>LogLevel</code> object or <code>null</code> if no match were found.
|
||||
*/
|
||||
public static LogLevel getByLetterString(String letter) {
|
||||
if (letter.length() > 0) {
|
||||
return getByLetter(letter.charAt(0));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the letter identifying the priority of the {@link LogLevel}.
|
||||
*/
|
||||
public char getPriorityLetter() {
|
||||
return mPriorityLetter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the numerical value of the priority.
|
||||
*/
|
||||
public int getPriority() {
|
||||
return mPriorityLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a non translated string representing the LogLevel.
|
||||
*/
|
||||
public String getStringValue() {
|
||||
return mStringValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classes which implement this interface provides methods that deal with outputting log
|
||||
* messages.
|
||||
*/
|
||||
public interface ILogOutput {
|
||||
/**
|
||||
* Sent when a log message needs to be printed.
|
||||
* @param logLevel The {@link LogLevel} enum representing the priority of the message.
|
||||
* @param tag The tag associated with the message.
|
||||
* @param message The message to display.
|
||||
*/
|
||||
public void printLog(LogLevel logLevel, String tag, String message);
|
||||
|
||||
/**
|
||||
* Sent when a log message needs to be printed, and, if possible, displayed to the user
|
||||
* in a dialog box.
|
||||
* @param logLevel The {@link LogLevel} enum representing the priority of the message.
|
||||
* @param tag The tag associated with the message.
|
||||
* @param message The message to display.
|
||||
*/
|
||||
public void printAndPromptLog(LogLevel logLevel, String tag, String message);
|
||||
}
|
||||
|
||||
private static LogLevel mLevel = DdmPreferences.getLogLevel();
|
||||
|
||||
private static ILogOutput sLogOutput;
|
||||
|
||||
private static final char[] mSpaceLine = new char[72];
|
||||
private static final char[] mHexDigit = new char[]
|
||||
{ '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' };
|
||||
static {
|
||||
/* prep for hex dump */
|
||||
int i = mSpaceLine.length-1;
|
||||
while (i >= 0)
|
||||
mSpaceLine[i--] = ' ';
|
||||
mSpaceLine[0] = mSpaceLine[1] = mSpaceLine[2] = mSpaceLine[3] = '0';
|
||||
mSpaceLine[4] = '-';
|
||||
}
|
||||
|
||||
static final class Config {
|
||||
static final boolean LOGV = true;
|
||||
static final boolean LOGD = true;
|
||||
};
|
||||
|
||||
private Log() {}
|
||||
|
||||
/**
|
||||
* Outputs a {@link LogLevel#VERBOSE} level message.
|
||||
* @param tag The tag associated with the message.
|
||||
* @param message The message to output.
|
||||
*/
|
||||
public static void v(String tag, String message) {
|
||||
println(LogLevel.VERBOSE, tag, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a {@link LogLevel#DEBUG} level message.
|
||||
* @param tag The tag associated with the message.
|
||||
* @param message The message to output.
|
||||
*/
|
||||
public static void d(String tag, String message) {
|
||||
println(LogLevel.DEBUG, tag, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a {@link LogLevel#INFO} level message.
|
||||
* @param tag The tag associated with the message.
|
||||
* @param message The message to output.
|
||||
*/
|
||||
public static void i(String tag, String message) {
|
||||
println(LogLevel.INFO, tag, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a {@link LogLevel#WARN} level message.
|
||||
* @param tag The tag associated with the message.
|
||||
* @param message The message to output.
|
||||
*/
|
||||
public static void w(String tag, String message) {
|
||||
println(LogLevel.WARN, tag, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a {@link LogLevel#ERROR} level message.
|
||||
* @param tag The tag associated with the message.
|
||||
* @param message The message to output.
|
||||
*/
|
||||
public static void e(String tag, String message) {
|
||||
println(LogLevel.ERROR, tag, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a log message and attempts to display it in a dialog.
|
||||
* @param tag The tag associated with the message.
|
||||
* @param message The message to output.
|
||||
*/
|
||||
public static void logAndDisplay(LogLevel logLevel, String tag, String message) {
|
||||
if (sLogOutput != null) {
|
||||
sLogOutput.printAndPromptLog(logLevel, tag, message);
|
||||
} else {
|
||||
println(logLevel, tag, message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a {@link LogLevel#ERROR} level {@link Throwable} information.
|
||||
* @param tag The tag associated with the message.
|
||||
* @param throwable The {@link Throwable} to output.
|
||||
*/
|
||||
public static void e(String tag, Throwable throwable) {
|
||||
if (throwable != null) {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
|
||||
throwable.printStackTrace(pw);
|
||||
println(LogLevel.ERROR, tag, throwable.getMessage() + '\n' + sw.toString());
|
||||
}
|
||||
}
|
||||
|
||||
static void setLevel(LogLevel logLevel) {
|
||||
mLevel = logLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ILogOutput} to use to print the logs. If not set, {@link System#out}
|
||||
* will be used.
|
||||
* @param logOutput The {@link ILogOutput} to use to print the log.
|
||||
*/
|
||||
public static void setLogOutput(ILogOutput logOutput) {
|
||||
sLogOutput = logOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show hex dump.
|
||||
* <p/>
|
||||
* Local addition. Output looks like:
|
||||
* 1230- 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff 0123456789abcdef
|
||||
* <p/>
|
||||
* Uses no string concatenation; creates one String object per line.
|
||||
*/
|
||||
static void hexDump(String tag, LogLevel level, byte[] data, int offset, int length) {
|
||||
|
||||
int kHexOffset = 6;
|
||||
int kAscOffset = 55;
|
||||
char[] line = new char[mSpaceLine.length];
|
||||
int addr, baseAddr, count;
|
||||
int i, ch;
|
||||
boolean needErase = true;
|
||||
|
||||
//Log.w(tag, "HEX DUMP: off=" + offset + ", length=" + length);
|
||||
|
||||
baseAddr = 0;
|
||||
while (length != 0) {
|
||||
if (length > 16) {
|
||||
// full line
|
||||
count = 16;
|
||||
} else {
|
||||
// partial line; re-copy blanks to clear end
|
||||
count = length;
|
||||
needErase = true;
|
||||
}
|
||||
|
||||
if (needErase) {
|
||||
System.arraycopy(mSpaceLine, 0, line, 0, mSpaceLine.length);
|
||||
needErase = false;
|
||||
}
|
||||
|
||||
// output the address (currently limited to 4 hex digits)
|
||||
addr = baseAddr;
|
||||
addr &= 0xffff;
|
||||
ch = 3;
|
||||
while (addr != 0) {
|
||||
line[ch] = mHexDigit[addr & 0x0f];
|
||||
ch--;
|
||||
addr >>>= 4;
|
||||
}
|
||||
|
||||
// output hex digits and ASCII chars
|
||||
ch = kHexOffset;
|
||||
for (i = 0; i < count; i++) {
|
||||
byte val = data[offset + i];
|
||||
|
||||
line[ch++] = mHexDigit[(val >>> 4) & 0x0f];
|
||||
line[ch++] = mHexDigit[val & 0x0f];
|
||||
ch++;
|
||||
|
||||
if (val >= 0x20 && val < 0x7f)
|
||||
line[kAscOffset + i] = (char) val;
|
||||
else
|
||||
line[kAscOffset + i] = '.';
|
||||
}
|
||||
|
||||
println(level, tag, new String(line));
|
||||
|
||||
// advance to next chunk of data
|
||||
length -= count;
|
||||
offset += count;
|
||||
baseAddr += count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the entire contents of a byte array with DEBUG priority.
|
||||
*/
|
||||
static void hexDump(byte[] data) {
|
||||
hexDump("ddms", LogLevel.DEBUG, data, 0, data.length);
|
||||
}
|
||||
|
||||
/* currently prints to stdout; could write to a log window */
|
||||
private static void println(LogLevel logLevel, String tag, String message) {
|
||||
if (logLevel.getPriority() >= mLevel.getPriority()) {
|
||||
if (sLogOutput != null) {
|
||||
sLogOutput.printLog(logLevel, tag, message);
|
||||
} else {
|
||||
printLog(logLevel, tag, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a log message.
|
||||
* @param logLevel
|
||||
* @param tag
|
||||
* @param message
|
||||
*/
|
||||
public static void printLog(LogLevel logLevel, String tag, String message) {
|
||||
long msec;
|
||||
|
||||
msec = System.currentTimeMillis();
|
||||
String outMessage = String.format("%02d:%02d %c/%s: %s\n",
|
||||
(msec / 60000) % 60, (msec / 1000) % 60,
|
||||
logLevel.getPriorityLetter(), tag, message);
|
||||
System.out.print(outMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
780
tools/ddms/libs/ddmlib/src/com/android/ddmlib/MonitorThread.java
Normal file
780
tools/ddms/libs/ddmlib/src/com/android/ddmlib/MonitorThread.java
Normal file
@@ -0,0 +1,780 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
|
||||
import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
|
||||
import com.android.ddmlib.Log.LogLevel;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.CancelledKeyException;
|
||||
import java.nio.channels.NotYetBoundException;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Monitor open connections.
|
||||
*/
|
||||
final class MonitorThread extends Thread {
|
||||
|
||||
// For broadcasts to message handlers
|
||||
//private static final int CLIENT_CONNECTED = 1;
|
||||
|
||||
private static final int CLIENT_READY = 2;
|
||||
|
||||
private static final int CLIENT_DISCONNECTED = 3;
|
||||
|
||||
private volatile boolean mQuit = false;
|
||||
|
||||
// List of clients we're paying attention to
|
||||
private ArrayList<Client> mClientList;
|
||||
|
||||
// The almighty mux
|
||||
private Selector mSelector;
|
||||
|
||||
// Map chunk types to handlers
|
||||
private HashMap<Integer, ChunkHandler> mHandlerMap;
|
||||
|
||||
// port for "debug selected"
|
||||
private ServerSocketChannel mDebugSelectedChan;
|
||||
|
||||
private int mNewDebugSelectedPort;
|
||||
|
||||
private int mDebugSelectedPort = -1;
|
||||
|
||||
/**
|
||||
* "Selected" client setup to answer debugging connection to the mNewDebugSelectedPort port.
|
||||
*/
|
||||
private Client mSelectedClient = null;
|
||||
|
||||
// singleton
|
||||
private static MonitorThread mInstance;
|
||||
|
||||
/**
|
||||
* Generic constructor.
|
||||
*/
|
||||
private MonitorThread() {
|
||||
super("Monitor");
|
||||
mClientList = new ArrayList<Client>();
|
||||
mHandlerMap = new HashMap<Integer, ChunkHandler>();
|
||||
|
||||
mNewDebugSelectedPort = DdmPreferences.getSelectedDebugPort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and return the singleton instance of the client monitor thread.
|
||||
*/
|
||||
static MonitorThread createInstance() {
|
||||
return mInstance = new MonitorThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get singleton instance of the client monitor thread.
|
||||
*/
|
||||
static MonitorThread getInstance() {
|
||||
return mInstance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets or changes the port number for "debug selected".
|
||||
*/
|
||||
synchronized void setDebugSelectedPort(int port) throws IllegalStateException {
|
||||
if (mInstance == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (AndroidDebugBridge.getClientSupport() == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mDebugSelectedChan != null) {
|
||||
Log.d("ddms", "Changing debug-selected port to " + port);
|
||||
mNewDebugSelectedPort = port;
|
||||
wakeup();
|
||||
} else {
|
||||
// we set mNewDebugSelectedPort instead of mDebugSelectedPort so that it's automatically
|
||||
// opened on the first run loop.
|
||||
mNewDebugSelectedPort = port;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the client to accept debugger connection on the custom "Selected debug port".
|
||||
* @param selectedClient the client. Can be null.
|
||||
*/
|
||||
synchronized void setSelectedClient(Client selectedClient) {
|
||||
if (mInstance == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSelectedClient != selectedClient) {
|
||||
Client oldClient = mSelectedClient;
|
||||
mSelectedClient = selectedClient;
|
||||
|
||||
if (oldClient != null) {
|
||||
oldClient.update(Client.CHANGE_PORT);
|
||||
}
|
||||
|
||||
if (mSelectedClient != null) {
|
||||
mSelectedClient.update(Client.CHANGE_PORT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client accepting debugger connection on the custom "Selected debug port".
|
||||
*/
|
||||
Client getSelectedClient() {
|
||||
return mSelectedClient;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns "true" if we want to retry connections to clients if we get a bad
|
||||
* JDWP handshake back, "false" if we want to just mark them as bad and
|
||||
* leave them alone.
|
||||
*/
|
||||
boolean getRetryOnBadHandshake() {
|
||||
return true; // TODO? make configurable
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of known clients.
|
||||
*/
|
||||
Client[] getClients() {
|
||||
synchronized (mClientList) {
|
||||
return mClientList.toArray(new Client[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register "handler" as the handler for type "type".
|
||||
*/
|
||||
synchronized void registerChunkHandler(int type, ChunkHandler handler) {
|
||||
if (mInstance == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (mHandlerMap) {
|
||||
if (mHandlerMap.get(type) == null) {
|
||||
mHandlerMap.put(type, handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch for activity from clients and debuggers.
|
||||
*/
|
||||
@Override
|
||||
public void run() {
|
||||
Log.d("ddms", "Monitor is up");
|
||||
|
||||
// create a selector
|
||||
try {
|
||||
mSelector = Selector.open();
|
||||
} catch (IOException ioe) {
|
||||
Log.logAndDisplay(LogLevel.ERROR, "ddms",
|
||||
"Failed to initialize Monitor Thread: " + ioe.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
while (!mQuit) {
|
||||
|
||||
try {
|
||||
/*
|
||||
* sync with new registrations: we wait until addClient is done before going through
|
||||
* and doing mSelector.select() again.
|
||||
* @see {@link #addClient(Client)}
|
||||
*/
|
||||
synchronized (mClientList) {
|
||||
}
|
||||
|
||||
// (re-)open the "debug selected" port, if it's not opened yet or
|
||||
// if the port changed.
|
||||
try {
|
||||
if (AndroidDebugBridge.getClientSupport()) {
|
||||
if ((mDebugSelectedChan == null ||
|
||||
mNewDebugSelectedPort != mDebugSelectedPort) &&
|
||||
mNewDebugSelectedPort != -1) {
|
||||
if (reopenDebugSelectedPort()) {
|
||||
mDebugSelectedPort = mNewDebugSelectedPort;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
Log.e("ddms",
|
||||
"Failed to reopen debug port for Selected Client to: " + mNewDebugSelectedPort);
|
||||
Log.e("ddms", ioe);
|
||||
mNewDebugSelectedPort = mDebugSelectedPort; // no retry
|
||||
}
|
||||
|
||||
int count;
|
||||
try {
|
||||
count = mSelector.select();
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
continue;
|
||||
} catch (CancelledKeyException cke) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
// somebody called wakeup() ?
|
||||
// Log.i("ddms", "selector looping");
|
||||
continue;
|
||||
}
|
||||
|
||||
Set<SelectionKey> keys = mSelector.selectedKeys();
|
||||
Iterator<SelectionKey> iter = keys.iterator();
|
||||
|
||||
while (iter.hasNext()) {
|
||||
SelectionKey key = iter.next();
|
||||
iter.remove();
|
||||
|
||||
try {
|
||||
if (key.attachment() instanceof Client) {
|
||||
processClientActivity(key);
|
||||
}
|
||||
else if (key.attachment() instanceof Debugger) {
|
||||
processDebuggerActivity(key);
|
||||
}
|
||||
else if (key.attachment() instanceof MonitorThread) {
|
||||
processDebugSelectedActivity(key);
|
||||
}
|
||||
else {
|
||||
Log.e("ddms", "unknown activity key");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// we don't want to have our thread be killed because of any uncaught
|
||||
// exception, so we intercept all here.
|
||||
Log.e("ddms", "Exception during activity from Selector.");
|
||||
Log.e("ddms", e);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// we don't want to have our thread be killed because of any uncaught
|
||||
// exception, so we intercept all here.
|
||||
Log.e("ddms", "Exception MonitorThread.run()");
|
||||
Log.e("ddms", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the port on which the selected client listen for debugger
|
||||
*/
|
||||
int getDebugSelectedPort() {
|
||||
return mDebugSelectedPort;
|
||||
}
|
||||
|
||||
/*
|
||||
* Something happened. Figure out what.
|
||||
*/
|
||||
private void processClientActivity(SelectionKey key) {
|
||||
Client client = (Client)key.attachment();
|
||||
|
||||
try {
|
||||
if (key.isReadable() == false || key.isValid() == false) {
|
||||
Log.d("ddms", "Invalid key from " + client + ". Dropping client.");
|
||||
dropClient(client, true /* notify */);
|
||||
return;
|
||||
}
|
||||
|
||||
client.read();
|
||||
|
||||
/*
|
||||
* See if we have a full packet in the buffer. It's possible we have
|
||||
* more than one packet, so we have to loop.
|
||||
*/
|
||||
JdwpPacket packet = client.getJdwpPacket();
|
||||
while (packet != null) {
|
||||
if (packet.isDdmPacket()) {
|
||||
// unsolicited DDM request - hand it off
|
||||
assert !packet.isReply();
|
||||
callHandler(client, packet, null);
|
||||
packet.consume();
|
||||
} else if (packet.isReply()
|
||||
&& client.isResponseToUs(packet.getId()) != null) {
|
||||
// reply to earlier DDM request
|
||||
ChunkHandler handler = client
|
||||
.isResponseToUs(packet.getId());
|
||||
if (packet.isError())
|
||||
client.packetFailed(packet);
|
||||
else if (packet.isEmpty())
|
||||
Log.d("ddms", "Got empty reply for 0x"
|
||||
+ Integer.toHexString(packet.getId())
|
||||
+ " from " + client);
|
||||
else
|
||||
callHandler(client, packet, handler);
|
||||
packet.consume();
|
||||
client.removeRequestId(packet.getId());
|
||||
} else {
|
||||
Log.v("ddms", "Forwarding client "
|
||||
+ (packet.isReply() ? "reply" : "event") + " 0x"
|
||||
+ Integer.toHexString(packet.getId()) + " to "
|
||||
+ client.getDebugger());
|
||||
client.forwardPacketToDebugger(packet);
|
||||
}
|
||||
|
||||
// find next
|
||||
packet = client.getJdwpPacket();
|
||||
}
|
||||
} catch (CancelledKeyException e) {
|
||||
// key was canceled probably due to a disconnected client before we could
|
||||
// read stuff coming from the client, so we drop it.
|
||||
dropClient(client, true /* notify */);
|
||||
} catch (IOException ex) {
|
||||
// something closed down, no need to print anything. The client is simply dropped.
|
||||
dropClient(client, true /* notify */);
|
||||
} catch (Exception ex) {
|
||||
Log.e("ddms", ex);
|
||||
|
||||
/* close the client; automatically un-registers from selector */
|
||||
dropClient(client, true /* notify */);
|
||||
|
||||
if (ex instanceof BufferOverflowException) {
|
||||
Log.w("ddms",
|
||||
"Client data packet exceeded maximum buffer size "
|
||||
+ client);
|
||||
} else {
|
||||
// don't know what this is, display it
|
||||
Log.e("ddms", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Process an incoming DDM packet. If this is a reply to an earlier request,
|
||||
* "handler" will be set to the handler responsible for the original
|
||||
* request. The spec allows a JDWP message to include multiple DDM chunks.
|
||||
*/
|
||||
private void callHandler(Client client, JdwpPacket packet,
|
||||
ChunkHandler handler) {
|
||||
|
||||
// on first DDM packet received, broadcast a "ready" message
|
||||
if (!client.ddmSeen())
|
||||
broadcast(CLIENT_READY, client);
|
||||
|
||||
ByteBuffer buf = packet.getPayload();
|
||||
int type, length;
|
||||
boolean reply = true;
|
||||
|
||||
type = buf.getInt();
|
||||
length = buf.getInt();
|
||||
|
||||
if (handler == null) {
|
||||
// not a reply, figure out who wants it
|
||||
synchronized (mHandlerMap) {
|
||||
handler = mHandlerMap.get(type);
|
||||
reply = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (handler == null) {
|
||||
Log.w("ddms", "Received unsupported chunk type "
|
||||
+ ChunkHandler.name(type) + " (len=" + length + ")");
|
||||
} else {
|
||||
Log.d("ddms", "Calling handler for " + ChunkHandler.name(type)
|
||||
+ " [" + handler + "] (len=" + length + ")");
|
||||
ByteBuffer ibuf = buf.slice();
|
||||
ByteBuffer roBuf = ibuf.asReadOnlyBuffer(); // enforce R/O
|
||||
roBuf.order(ChunkHandler.CHUNK_ORDER);
|
||||
// do the handling of the chunk synchronized on the client list
|
||||
// to be sure there's no concurrency issue when we look for HOME
|
||||
// in hasApp()
|
||||
synchronized (mClientList) {
|
||||
handler.handleChunk(client, type, roBuf, reply, packet.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops a client from the monitor.
|
||||
* <p/>This will lock the {@link Client} list of the {@link Device} running <var>client</var>.
|
||||
* @param client
|
||||
* @param notify
|
||||
*/
|
||||
synchronized void dropClient(Client client, boolean notify) {
|
||||
if (mInstance == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (mClientList) {
|
||||
if (mClientList.remove(client) == false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
client.close(notify);
|
||||
broadcast(CLIENT_DISCONNECTED, client);
|
||||
|
||||
/*
|
||||
* http://forum.java.sun.com/thread.jspa?threadID=726715&start=0
|
||||
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5073504
|
||||
*/
|
||||
wakeup();
|
||||
}
|
||||
|
||||
/*
|
||||
* Process activity from one of the debugger sockets. This could be a new
|
||||
* connection or a data packet.
|
||||
*/
|
||||
private void processDebuggerActivity(SelectionKey key) {
|
||||
Debugger dbg = (Debugger)key.attachment();
|
||||
|
||||
try {
|
||||
if (key.isAcceptable()) {
|
||||
try {
|
||||
acceptNewDebugger(dbg, null);
|
||||
} catch (IOException ioe) {
|
||||
Log.w("ddms", "debugger accept() failed");
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
} else if (key.isReadable()) {
|
||||
processDebuggerData(key);
|
||||
} else {
|
||||
Log.d("ddm-debugger", "key in unknown state");
|
||||
}
|
||||
} catch (CancelledKeyException cke) {
|
||||
// key has been cancelled we can ignore that.
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Accept a new connection from a debugger. If successful, register it with
|
||||
* the Selector.
|
||||
*/
|
||||
private void acceptNewDebugger(Debugger dbg, ServerSocketChannel acceptChan)
|
||||
throws IOException {
|
||||
|
||||
synchronized (mClientList) {
|
||||
SocketChannel chan;
|
||||
|
||||
if (acceptChan == null)
|
||||
chan = dbg.accept();
|
||||
else
|
||||
chan = dbg.accept(acceptChan);
|
||||
|
||||
if (chan != null) {
|
||||
chan.socket().setTcpNoDelay(true);
|
||||
|
||||
wakeup();
|
||||
|
||||
try {
|
||||
chan.register(mSelector, SelectionKey.OP_READ, dbg);
|
||||
} catch (IOException ioe) {
|
||||
// failed, drop the connection
|
||||
dbg.closeData();
|
||||
throw ioe;
|
||||
} catch (RuntimeException re) {
|
||||
// failed, drop the connection
|
||||
dbg.closeData();
|
||||
throw re;
|
||||
}
|
||||
} else {
|
||||
Log.i("ddms", "ignoring duplicate debugger");
|
||||
// new connection already closed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We have incoming data from the debugger. Forward it to the client.
|
||||
*/
|
||||
private void processDebuggerData(SelectionKey key) {
|
||||
Debugger dbg = (Debugger)key.attachment();
|
||||
|
||||
try {
|
||||
/*
|
||||
* Read pending data.
|
||||
*/
|
||||
dbg.read();
|
||||
|
||||
/*
|
||||
* See if we have a full packet in the buffer. It's possible we have
|
||||
* more than one packet, so we have to loop.
|
||||
*/
|
||||
JdwpPacket packet = dbg.getJdwpPacket();
|
||||
while (packet != null) {
|
||||
Log.v("ddms", "Forwarding dbg req 0x"
|
||||
+ Integer.toHexString(packet.getId()) + " to "
|
||||
+ dbg.getClient());
|
||||
|
||||
dbg.forwardPacketToClient(packet);
|
||||
|
||||
packet = dbg.getJdwpPacket();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
/*
|
||||
* Close data connection; automatically un-registers dbg from
|
||||
* selector. The failure could be caused by the debugger going away,
|
||||
* or by the client going away and failing to accept our data.
|
||||
* Either way, the debugger connection does not need to exist any
|
||||
* longer. We also need to recycle the connection to the client, so
|
||||
* that the VM sees the debugger disconnect. For a DDM-aware client
|
||||
* this won't be necessary, and we can just send a "debugger
|
||||
* disconnected" message.
|
||||
*/
|
||||
Log.i("ddms", "Closing connection to debugger " + dbg);
|
||||
dbg.closeData();
|
||||
Client client = dbg.getClient();
|
||||
if (client.isDdmAware()) {
|
||||
// TODO: soft-disconnect DDM-aware clients
|
||||
Log.i("ddms", " (recycling client connection as well)");
|
||||
|
||||
// we should drop the client, but also attempt to reopen it.
|
||||
// This is done by the DeviceMonitor.
|
||||
client.getDevice().getMonitor().addClientToDropAndReopen(client,
|
||||
IDebugPortProvider.NO_STATIC_PORT);
|
||||
} else {
|
||||
Log.i("ddms", " (recycling client connection as well)");
|
||||
// we should drop the client, but also attempt to reopen it.
|
||||
// This is done by the DeviceMonitor.
|
||||
client.getDevice().getMonitor().addClientToDropAndReopen(client,
|
||||
IDebugPortProvider.NO_STATIC_PORT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Tell the thread that something has changed.
|
||||
*/
|
||||
private void wakeup() {
|
||||
mSelector.wakeup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the thread to stop. Called from UI thread.
|
||||
*/
|
||||
synchronized void quit() {
|
||||
mQuit = true;
|
||||
wakeup();
|
||||
Log.d("ddms", "Waiting for Monitor thread");
|
||||
try {
|
||||
this.join();
|
||||
// since we're quitting, lets drop all the client and disconnect
|
||||
// the DebugSelectedPort
|
||||
synchronized (mClientList) {
|
||||
for (Client c : mClientList) {
|
||||
c.close(false /* notify */);
|
||||
broadcast(CLIENT_DISCONNECTED, c);
|
||||
}
|
||||
mClientList.clear();
|
||||
}
|
||||
|
||||
if (mDebugSelectedChan != null) {
|
||||
mDebugSelectedChan.close();
|
||||
mDebugSelectedChan.socket().close();
|
||||
mDebugSelectedChan = null;
|
||||
}
|
||||
mSelector.close();
|
||||
} catch (InterruptedException ie) {
|
||||
ie.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
mInstance = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new Client to the list of things we monitor. Also adds the client's
|
||||
* channel and the client's debugger listener to the selection list. This
|
||||
* should only be called from one thread (the VMWatcherThread) to avoid a
|
||||
* race between "alreadyOpen" and Client creation.
|
||||
*/
|
||||
synchronized void addClient(Client client) {
|
||||
if (mInstance == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("ddms", "Adding new client " + client);
|
||||
|
||||
synchronized (mClientList) {
|
||||
mClientList.add(client);
|
||||
|
||||
/*
|
||||
* Register the Client's socket channel with the selector. We attach
|
||||
* the Client to the SelectionKey. If you try to register a new
|
||||
* channel with the Selector while it is waiting for I/O, you will
|
||||
* block. The solution is to call wakeup() and then hold a lock to
|
||||
* ensure that the registration happens before the Selector goes
|
||||
* back to sleep.
|
||||
*/
|
||||
try {
|
||||
wakeup();
|
||||
|
||||
client.register(mSelector);
|
||||
|
||||
Debugger dbg = client.getDebugger();
|
||||
if (dbg != null) {
|
||||
dbg.registerListener(mSelector);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
// not really expecting this to happen
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Broadcast an event to all message handlers.
|
||||
*/
|
||||
private void broadcast(int event, Client client) {
|
||||
Log.d("ddms", "broadcast " + event + ": " + client);
|
||||
|
||||
/*
|
||||
* The handler objects appear once in mHandlerMap for each message they
|
||||
* handle. We want to notify them once each, so we convert the HashMap
|
||||
* to a HashSet before we iterate.
|
||||
*/
|
||||
HashSet<ChunkHandler> set;
|
||||
synchronized (mHandlerMap) {
|
||||
Collection<ChunkHandler> values = mHandlerMap.values();
|
||||
set = new HashSet<ChunkHandler>(values);
|
||||
}
|
||||
|
||||
Iterator<ChunkHandler> iter = set.iterator();
|
||||
while (iter.hasNext()) {
|
||||
ChunkHandler handler = iter.next();
|
||||
switch (event) {
|
||||
case CLIENT_READY:
|
||||
try {
|
||||
handler.clientReady(client);
|
||||
} catch (IOException ioe) {
|
||||
// Something failed with the client. It should
|
||||
// fall out of the list the next time we try to
|
||||
// do something with it, so we discard the
|
||||
// exception here and assume cleanup will happen
|
||||
// later. May need to propagate farther. The
|
||||
// trouble is that not all values for "event" may
|
||||
// actually throw an exception.
|
||||
Log.w("ddms",
|
||||
"Got exception while broadcasting 'ready'");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CLIENT_DISCONNECTED:
|
||||
handler.clientDisconnected(client);
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens (or reopens) the "debug selected" port and listen for connections.
|
||||
* @return true if the port was opened successfully.
|
||||
* @throws IOException
|
||||
*/
|
||||
private boolean reopenDebugSelectedPort() throws IOException {
|
||||
|
||||
Log.d("ddms", "reopen debug-selected port: " + mNewDebugSelectedPort);
|
||||
if (mDebugSelectedChan != null) {
|
||||
mDebugSelectedChan.close();
|
||||
}
|
||||
|
||||
mDebugSelectedChan = ServerSocketChannel.open();
|
||||
mDebugSelectedChan.configureBlocking(false); // required for Selector
|
||||
|
||||
InetSocketAddress addr = new InetSocketAddress(
|
||||
InetAddress.getByName("localhost"), //$NON-NLS-1$
|
||||
mNewDebugSelectedPort);
|
||||
mDebugSelectedChan.socket().setReuseAddress(true); // enable SO_REUSEADDR
|
||||
|
||||
try {
|
||||
mDebugSelectedChan.socket().bind(addr);
|
||||
if (mSelectedClient != null) {
|
||||
mSelectedClient.update(Client.CHANGE_PORT);
|
||||
}
|
||||
|
||||
mDebugSelectedChan.register(mSelector, SelectionKey.OP_ACCEPT, this);
|
||||
|
||||
return true;
|
||||
} catch (java.net.BindException e) {
|
||||
displayDebugSelectedBindError(mNewDebugSelectedPort);
|
||||
|
||||
// do not attempt to reopen it.
|
||||
mDebugSelectedChan = null;
|
||||
mNewDebugSelectedPort = -1;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We have some activity on the "debug selected" port. Handle it.
|
||||
*/
|
||||
private void processDebugSelectedActivity(SelectionKey key) {
|
||||
assert key.isAcceptable();
|
||||
|
||||
ServerSocketChannel acceptChan = (ServerSocketChannel)key.channel();
|
||||
|
||||
/*
|
||||
* Find the debugger associated with the currently-selected client.
|
||||
*/
|
||||
if (mSelectedClient != null) {
|
||||
Debugger dbg = mSelectedClient.getDebugger();
|
||||
|
||||
if (dbg != null) {
|
||||
Log.i("ddms", "Accepting connection on 'debug selected' port");
|
||||
try {
|
||||
acceptNewDebugger(dbg, acceptChan);
|
||||
} catch (IOException ioe) {
|
||||
// client should be gone, keep going
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Log.w("ddms",
|
||||
"Connection on 'debug selected' port, but none selected");
|
||||
try {
|
||||
SocketChannel chan = acceptChan.accept();
|
||||
chan.close();
|
||||
} catch (IOException ioe) {
|
||||
// not expected; client should be gone, keep going
|
||||
} catch (NotYetBoundException e) {
|
||||
displayDebugSelectedBindError(mDebugSelectedPort);
|
||||
}
|
||||
}
|
||||
|
||||
private void displayDebugSelectedBindError(int port) {
|
||||
String message = String.format(
|
||||
"Could not open Selected VM debug port (%1$d). Make sure you do not have another instance of DDMS or of the eclipse plugin running. If it's being used by something else, choose a new port number in the preferences.",
|
||||
port);
|
||||
|
||||
Log.logAndDisplay(LogLevel.ERROR, "ddms", message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Base implementation of {@link IShellOutputReceiver}, that takes the raw data coming from the
|
||||
* socket, and convert it into {@link String} objects.
|
||||
* <p/>Additionally, it splits the string by lines.
|
||||
* <p/>Classes extending it must implement {@link #processNewLines(String[])} which receives
|
||||
* new parsed lines as they become available.
|
||||
*/
|
||||
public abstract class MultiLineReceiver implements IShellOutputReceiver {
|
||||
|
||||
private boolean mTrimLines = true;
|
||||
|
||||
/** unfinished message line, stored for next packet */
|
||||
private String mUnfinishedLine = null;
|
||||
|
||||
private final ArrayList<String> mArray = new ArrayList<String>();
|
||||
|
||||
/**
|
||||
* Set the trim lines flag.
|
||||
* @param trim hether the lines are trimmed, or not.
|
||||
*/
|
||||
public void setTrimLine(boolean trim) {
|
||||
mTrimLines = trim;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.android.ddmlib.adb.IShellOutputReceiver#addOutput(
|
||||
* byte[], int, int)
|
||||
*/
|
||||
public final void addOutput(byte[] data, int offset, int length) {
|
||||
if (isCancelled() == false) {
|
||||
String s = null;
|
||||
try {
|
||||
s = new String(data, offset, length, "ISO-8859-1"); //$NON-NLS-1$
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// normal encoding didn't work, try the default one
|
||||
s = new String(data, offset,length);
|
||||
}
|
||||
|
||||
// ok we've got a string
|
||||
if (s != null) {
|
||||
// if we had an unfinished line we add it.
|
||||
if (mUnfinishedLine != null) {
|
||||
s = mUnfinishedLine + s;
|
||||
mUnfinishedLine = null;
|
||||
}
|
||||
|
||||
// now we split the lines
|
||||
mArray.clear();
|
||||
int start = 0;
|
||||
do {
|
||||
int index = s.indexOf("\r\n", start); //$NON-NLS-1$
|
||||
|
||||
// if \r\n was not found, this is an unfinished line
|
||||
// and we store it to be processed for the next packet
|
||||
if (index == -1) {
|
||||
mUnfinishedLine = s.substring(start);
|
||||
break;
|
||||
}
|
||||
|
||||
// so we found a \r\n;
|
||||
// extract the line
|
||||
String line = s.substring(start, index);
|
||||
if (mTrimLines) {
|
||||
line = line.trim();
|
||||
}
|
||||
mArray.add(line);
|
||||
|
||||
// move start to after the \r\n we found
|
||||
start = index + 2;
|
||||
} while (true);
|
||||
|
||||
if (mArray.size() > 0) {
|
||||
// at this point we've split all the lines.
|
||||
// make the array
|
||||
String[] lines = mArray.toArray(new String[mArray.size()]);
|
||||
|
||||
// send it for final processing
|
||||
processNewLines(lines);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.android.ddmlib.adb.IShellOutputReceiver#flush()
|
||||
*/
|
||||
public final void flush() {
|
||||
if (mUnfinishedLine != null) {
|
||||
processNewLines(new String[] { mUnfinishedLine });
|
||||
}
|
||||
|
||||
done();
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminates the process. This is called after the last lines have been through
|
||||
* {@link #processNewLines(String[])}.
|
||||
*/
|
||||
public void done() {
|
||||
// do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when new lines are being received by the remote process.
|
||||
* <p/>It is guaranteed that the lines are complete when they are given to this method.
|
||||
* @param lines The array containing the new lines.
|
||||
*/
|
||||
public abstract void processNewLines(String[] lines);
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Stores native allocation information.
|
||||
* <p/>Contains number of allocations, their size and the stack trace.
|
||||
* <p/>Note: the ddmlib does not resolve the stack trace automatically. While this class provides
|
||||
* storage for resolved stack trace, this is merely for convenience.
|
||||
*/
|
||||
public final class NativeAllocationInfo {
|
||||
/* constants for flag bits */
|
||||
private static final int FLAG_ZYGOTE_CHILD = (1<<31);
|
||||
private static final int FLAG_MASK = (FLAG_ZYGOTE_CHILD);
|
||||
|
||||
/**
|
||||
* list of alloc functions that are filtered out when attempting to display
|
||||
* a relevant method responsible for an allocation
|
||||
*/
|
||||
private static ArrayList<String> sAllocFunctionFilter;
|
||||
static {
|
||||
sAllocFunctionFilter = new ArrayList<String>();
|
||||
sAllocFunctionFilter.add("malloc"); //$NON-NLS-1$
|
||||
sAllocFunctionFilter.add("calloc"); //$NON-NLS-1$
|
||||
sAllocFunctionFilter.add("realloc"); //$NON-NLS-1$
|
||||
sAllocFunctionFilter.add("get_backtrace"); //$NON-NLS-1$
|
||||
sAllocFunctionFilter.add("get_hash"); //$NON-NLS-1$
|
||||
sAllocFunctionFilter.add("??"); //$NON-NLS-1$
|
||||
sAllocFunctionFilter.add("internal_free"); //$NON-NLS-1$
|
||||
sAllocFunctionFilter.add("operator new"); //$NON-NLS-1$
|
||||
sAllocFunctionFilter.add("leak_free"); //$NON-NLS-1$
|
||||
sAllocFunctionFilter.add("chk_free"); //$NON-NLS-1$
|
||||
sAllocFunctionFilter.add("chk_memalign"); //$NON-NLS-1$
|
||||
sAllocFunctionFilter.add("Malloc"); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
private final int mSize;
|
||||
|
||||
private final boolean mIsZygoteChild;
|
||||
|
||||
private final int mAllocations;
|
||||
|
||||
private final ArrayList<Long> mStackCallAddresses = new ArrayList<Long>();
|
||||
|
||||
private ArrayList<NativeStackCallInfo> mResolvedStackCall = null;
|
||||
|
||||
private boolean mIsStackCallResolved = false;
|
||||
|
||||
/**
|
||||
* Constructs a new {@link NativeAllocationInfo}.
|
||||
* @param size The size of the allocations.
|
||||
* @param allocations the allocation count
|
||||
*/
|
||||
NativeAllocationInfo(int size, int allocations) {
|
||||
this.mSize = size & ~FLAG_MASK;
|
||||
this.mIsZygoteChild = ((size & FLAG_ZYGOTE_CHILD) != 0);
|
||||
this.mAllocations = allocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a stack call address for this allocation.
|
||||
* @param address The address to add.
|
||||
*/
|
||||
void addStackCallAddress(long address) {
|
||||
mStackCallAddresses.add(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total size of this allocation.
|
||||
*/
|
||||
public int getSize() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the allocation happened in a child of the zygote
|
||||
* process.
|
||||
*/
|
||||
public boolean isZygoteChild() {
|
||||
return mIsZygoteChild;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the allocation count.
|
||||
*/
|
||||
public int getAllocationCount() {
|
||||
return mAllocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the stack call addresses have been resolved into
|
||||
* {@link NativeStackCallInfo} objects.
|
||||
*/
|
||||
public boolean isStackCallResolved() {
|
||||
return mIsStackCallResolved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stack call of this allocation as raw addresses.
|
||||
* @return the list of addresses where the allocation happened.
|
||||
*/
|
||||
public Long[] getStackCallAddresses() {
|
||||
return mStackCallAddresses.toArray(new Long[mStackCallAddresses.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the resolved stack call for this allocation.
|
||||
* <p/>
|
||||
* If <code>resolvedStackCall</code> is non <code>null</code> then
|
||||
* {@link #isStackCallResolved()} will return <code>true</code> after this call.
|
||||
* @param resolvedStackCall The list of {@link NativeStackCallInfo}.
|
||||
*/
|
||||
public synchronized void setResolvedStackCall(List<NativeStackCallInfo> resolvedStackCall) {
|
||||
if (mResolvedStackCall == null) {
|
||||
mResolvedStackCall = new ArrayList<NativeStackCallInfo>();
|
||||
} else {
|
||||
mResolvedStackCall.clear();
|
||||
}
|
||||
mResolvedStackCall.addAll(resolvedStackCall);
|
||||
mIsStackCallResolved = mResolvedStackCall.size() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resolved stack call.
|
||||
* @return An array of {@link NativeStackCallInfo} or <code>null</code> if the stack call
|
||||
* was not resolved.
|
||||
* @see #setResolvedStackCall(ArrayList)
|
||||
* @see #isStackCallResolved()
|
||||
*/
|
||||
public synchronized NativeStackCallInfo[] getResolvedStackCall() {
|
||||
if (mIsStackCallResolved) {
|
||||
return mResolvedStackCall.toArray(new NativeStackCallInfo[mResolvedStackCall.size()]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether some other object is "equal to" this one.
|
||||
* @param obj the reference object with which to compare.
|
||||
* @return <code>true</code> if this object is equal to the obj argument;
|
||||
* <code>false</code> otherwise.
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this)
|
||||
return true;
|
||||
if (obj instanceof NativeAllocationInfo) {
|
||||
NativeAllocationInfo mi = (NativeAllocationInfo)obj;
|
||||
// quick compare of size, alloc, and stackcall size
|
||||
if (mSize != mi.mSize || mAllocations != mi.mAllocations ||
|
||||
mStackCallAddresses.size() != mi.mStackCallAddresses.size()) {
|
||||
return false;
|
||||
}
|
||||
// compare the stack addresses
|
||||
int count = mStackCallAddresses.size();
|
||||
for (int i = 0 ; i < count ; i++) {
|
||||
long a = mStackCallAddresses.get(i);
|
||||
long b = mi.mStackCallAddresses.get(i);
|
||||
if (a != b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the object.
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
buffer.append("Allocations: ");
|
||||
buffer.append(mAllocations);
|
||||
buffer.append("\n"); //$NON-NLS-1$
|
||||
|
||||
buffer.append("Size: ");
|
||||
buffer.append(mSize);
|
||||
buffer.append("\n"); //$NON-NLS-1$
|
||||
|
||||
buffer.append("Total Size: ");
|
||||
buffer.append(mSize * mAllocations);
|
||||
buffer.append("\n"); //$NON-NLS-1$
|
||||
|
||||
Iterator<Long> addrIterator = mStackCallAddresses.iterator();
|
||||
Iterator<NativeStackCallInfo> sourceIterator = mResolvedStackCall.iterator();
|
||||
|
||||
while (sourceIterator.hasNext()) {
|
||||
long addr = addrIterator.next();
|
||||
NativeStackCallInfo source = sourceIterator.next();
|
||||
if (addr == 0)
|
||||
continue;
|
||||
|
||||
if (source.getLineNumber() != -1) {
|
||||
buffer.append(String.format("\t%1$08x\t%2$s --- %3$s --- %4$s:%5$d\n", addr,
|
||||
source.getLibraryName(), source.getMethodName(),
|
||||
source.getSourceFile(), source.getLineNumber()));
|
||||
} else {
|
||||
buffer.append(String.format("\t%1$08x\t%2$s --- %3$s --- %4$s\n", addr,
|
||||
source.getLibraryName(), source.getMethodName(), source.getSourceFile()));
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first {@link NativeStackCallInfo} that is relevant.
|
||||
* <p/>
|
||||
* A relevant <code>NativeStackCallInfo</code> is a stack call that is not deep in the
|
||||
* lower level of the libc, but the actual method that performed the allocation.
|
||||
* @return a <code>NativeStackCallInfo</code> or <code>null</code> if the stack call has not
|
||||
* been processed from the raw addresses.
|
||||
* @see #setResolvedStackCall(ArrayList)
|
||||
* @see #isStackCallResolved()
|
||||
*/
|
||||
public synchronized NativeStackCallInfo getRelevantStackCallInfo() {
|
||||
if (mIsStackCallResolved && mResolvedStackCall != null) {
|
||||
Iterator<NativeStackCallInfo> sourceIterator = mResolvedStackCall.iterator();
|
||||
Iterator<Long> addrIterator = mStackCallAddresses.iterator();
|
||||
|
||||
while (sourceIterator.hasNext() && addrIterator.hasNext()) {
|
||||
long addr = addrIterator.next();
|
||||
NativeStackCallInfo info = sourceIterator.next();
|
||||
if (addr != 0 && info != null) {
|
||||
if (isRelevant(info.getMethodName())) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// couldnt find a relevant one, so we'll return the first one if it
|
||||
// exists.
|
||||
if (mResolvedStackCall.size() > 0)
|
||||
return mResolvedStackCall.get(0);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the method name is relevant.
|
||||
* @param methodName the method name to test.
|
||||
*/
|
||||
private boolean isRelevant(String methodName) {
|
||||
for (String filter : sAllocFunctionFilter) {
|
||||
if (methodName.contains(filter)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
/**
|
||||
* Memory address to library mapping for native libraries.
|
||||
* <p/>
|
||||
* Each instance represents a single native library and its start and end memory addresses.
|
||||
*/
|
||||
public final class NativeLibraryMapInfo {
|
||||
private long mStartAddr;
|
||||
private long mEndAddr;
|
||||
|
||||
private String mLibrary;
|
||||
|
||||
/**
|
||||
* Constructs a new native library map info.
|
||||
* @param startAddr The start address of the library.
|
||||
* @param endAddr The end address of the library.
|
||||
* @param library The name of the library.
|
||||
*/
|
||||
NativeLibraryMapInfo(long startAddr, long endAddr, String library) {
|
||||
this.mStartAddr = startAddr;
|
||||
this.mEndAddr = endAddr;
|
||||
this.mLibrary = library;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the library.
|
||||
*/
|
||||
public String getLibraryName() {
|
||||
return mLibrary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start address of the library.
|
||||
*/
|
||||
public long getStartAddress() {
|
||||
return mStartAddr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the end address of the library.
|
||||
*/
|
||||
public long getEndAddress() {
|
||||
return mEndAddr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the specified address is inside the library.
|
||||
* @param address The address to test.
|
||||
* @return <code>true</code> if the address is between the start and end address of the library.
|
||||
* @see #getStartAddress()
|
||||
* @see #getEndAddress()
|
||||
*/
|
||||
public boolean isWithinLibrary(long address) {
|
||||
return address >= mStartAddr && address <= mEndAddr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Represents a stack call. This is used to return all of the call
|
||||
* information as one object.
|
||||
*/
|
||||
public final class NativeStackCallInfo {
|
||||
private final static Pattern SOURCE_NAME_PATTERN = Pattern.compile("^(.+):(\\d+)$");
|
||||
|
||||
/** name of the library */
|
||||
private String mLibrary;
|
||||
|
||||
/** name of the method */
|
||||
private String mMethod;
|
||||
|
||||
/**
|
||||
* name of the source file + line number in the format<br>
|
||||
* <sourcefile>:<linenumber>
|
||||
*/
|
||||
private String mSourceFile;
|
||||
|
||||
private int mLineNumber = -1;
|
||||
|
||||
/**
|
||||
* Basic constructor with library, method, and sourcefile information
|
||||
*
|
||||
* @param lib The name of the library
|
||||
* @param method the name of the method
|
||||
* @param sourceFile the name of the source file and the line number
|
||||
* as "[sourcefile]:[fileNumber]"
|
||||
*/
|
||||
public NativeStackCallInfo(String lib, String method, String sourceFile) {
|
||||
mLibrary = lib;
|
||||
mMethod = method;
|
||||
|
||||
Matcher m = SOURCE_NAME_PATTERN.matcher(sourceFile);
|
||||
if (m.matches()) {
|
||||
mSourceFile = m.group(1);
|
||||
try {
|
||||
mLineNumber = Integer.parseInt(m.group(2));
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing, the line number will stay at -1
|
||||
}
|
||||
} else {
|
||||
mSourceFile = sourceFile;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the library name.
|
||||
*/
|
||||
public String getLibraryName() {
|
||||
return mLibrary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the method.
|
||||
*/
|
||||
public String getMethodName() {
|
||||
return mMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the source file.
|
||||
*/
|
||||
public String getSourceFile() {
|
||||
return mSourceFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the line number, or -1 if unknown.
|
||||
*/
|
||||
public int getLineNumber() {
|
||||
return mLineNumber;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
/**
|
||||
* Implementation of {@link IShellOutputReceiver} that does nothing.
|
||||
* <p/>This can be used to execute a remote shell command when the output is not needed.
|
||||
*/
|
||||
public final class NullOutputReceiver implements IShellOutputReceiver {
|
||||
|
||||
private static NullOutputReceiver sReceiver = new NullOutputReceiver();
|
||||
|
||||
public static IShellOutputReceiver getReceiver() {
|
||||
return sReceiver;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.android.ddmlib.adb.IShellOutputReceiver#addOutput(byte[], int, int)
|
||||
*/
|
||||
public void addOutput(byte[] data, int offset, int length) {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.android.ddmlib.adb.IShellOutputReceiver#flush()
|
||||
*/
|
||||
public void flush() {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.android.ddmlib.adb.IShellOutputReceiver#isCancelled()
|
||||
*/
|
||||
public boolean isCancelled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
32
tools/ddms/libs/ddmlib/src/com/android/ddmlib/RawImage.java
Normal file
32
tools/ddms/libs/ddmlib/src/com/android/ddmlib/RawImage.java
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
/**
|
||||
* Data representing an image taken from a device frame buffer.
|
||||
*/
|
||||
public final class RawImage {
|
||||
/**
|
||||
* bit-per-pixel value.
|
||||
*/
|
||||
public int bpp;
|
||||
public int size;
|
||||
public int width;
|
||||
public int height;
|
||||
|
||||
public byte[] data;
|
||||
}
|
||||
949
tools/ddms/libs/ddmlib/src/com/android/ddmlib/SyncService.java
Normal file
949
tools/ddms/libs/ddmlib/src/com/android/ddmlib/SyncService.java
Normal file
@@ -0,0 +1,949 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
import com.android.ddmlib.AdbHelper.AdbResponse;
|
||||
import com.android.ddmlib.FileListingService.FileEntry;
|
||||
import com.android.ddmlib.utils.ArrayHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Sync service class to push/pull to/from devices/emulators, through the debug bridge.
|
||||
* <p/>
|
||||
* To get a {@link SyncService} object, use {@link Device#getSyncService()}.
|
||||
*/
|
||||
public final class SyncService {
|
||||
|
||||
private final static byte[] ID_OKAY = { 'O', 'K', 'A', 'Y' };
|
||||
private final static byte[] ID_FAIL = { 'F', 'A', 'I', 'L' };
|
||||
private final static byte[] ID_STAT = { 'S', 'T', 'A', 'T' };
|
||||
private final static byte[] ID_RECV = { 'R', 'E', 'C', 'V' };
|
||||
private final static byte[] ID_DATA = { 'D', 'A', 'T', 'A' };
|
||||
private final static byte[] ID_DONE = { 'D', 'O', 'N', 'E' };
|
||||
private final static byte[] ID_SEND = { 'S', 'E', 'N', 'D' };
|
||||
// private final static byte[] ID_LIST = { 'L', 'I', 'S', 'T' };
|
||||
// private final static byte[] ID_DENT = { 'D', 'E', 'N', 'T' };
|
||||
|
||||
private final static NullSyncProgresMonitor sNullSyncProgressMonitor =
|
||||
new NullSyncProgresMonitor();
|
||||
|
||||
private final static int S_ISOCK = 0xC000; // type: symbolic link
|
||||
private final static int S_IFLNK = 0xA000; // type: symbolic link
|
||||
private final static int S_IFREG = 0x8000; // type: regular file
|
||||
private final static int S_IFBLK = 0x6000; // type: block device
|
||||
private final static int S_IFDIR = 0x4000; // type: directory
|
||||
private final static int S_IFCHR = 0x2000; // type: character device
|
||||
private final static int S_IFIFO = 0x1000; // type: fifo
|
||||
/*
|
||||
private final static int S_ISUID = 0x0800; // set-uid bit
|
||||
private final static int S_ISGID = 0x0400; // set-gid bit
|
||||
private final static int S_ISVTX = 0x0200; // sticky bit
|
||||
private final static int S_IRWXU = 0x01C0; // user permissions
|
||||
private final static int S_IRUSR = 0x0100; // user: read
|
||||
private final static int S_IWUSR = 0x0080; // user: write
|
||||
private final static int S_IXUSR = 0x0040; // user: execute
|
||||
private final static int S_IRWXG = 0x0038; // group permissions
|
||||
private final static int S_IRGRP = 0x0020; // group: read
|
||||
private final static int S_IWGRP = 0x0010; // group: write
|
||||
private final static int S_IXGRP = 0x0008; // group: execute
|
||||
private final static int S_IRWXO = 0x0007; // other permissions
|
||||
private final static int S_IROTH = 0x0004; // other: read
|
||||
private final static int S_IWOTH = 0x0002; // other: write
|
||||
private final static int S_IXOTH = 0x0001; // other: execute
|
||||
*/
|
||||
|
||||
private final static int SYNC_DATA_MAX = 64*1024;
|
||||
private final static int REMOTE_PATH_MAX_LENGTH = 1024;
|
||||
|
||||
/** Result code for transfer success. */
|
||||
public static final int RESULT_OK = 0;
|
||||
/** Result code for canceled transfer */
|
||||
public static final int RESULT_CANCELED = 1;
|
||||
/** Result code for unknown error */
|
||||
public static final int RESULT_UNKNOWN_ERROR = 2;
|
||||
/** Result code for network connection error */
|
||||
public static final int RESULT_CONNECTION_ERROR = 3;
|
||||
/** Result code for unknown remote object during a pull */
|
||||
public static final int RESULT_NO_REMOTE_OBJECT = 4;
|
||||
/** Result code when attempting to pull multiple files into a file */
|
||||
public static final int RESULT_TARGET_IS_FILE = 5;
|
||||
/** Result code when attempting to pull multiple into a directory that does not exist. */
|
||||
public static final int RESULT_NO_DIR_TARGET = 6;
|
||||
/** Result code for wrong encoding on the remote path. */
|
||||
public static final int RESULT_REMOTE_PATH_ENCODING = 7;
|
||||
/** Result code for remote path that is too long. */
|
||||
public static final int RESULT_REMOTE_PATH_LENGTH = 8;
|
||||
/** Result code for error while writing local file. */
|
||||
public static final int RESULT_FILE_WRITE_ERROR = 9;
|
||||
/** Result code for error while reading local file. */
|
||||
public static final int RESULT_FILE_READ_ERROR = 10;
|
||||
/** Result code for attempting to push a file that does not exist. */
|
||||
public static final int RESULT_NO_LOCAL_FILE = 11;
|
||||
/** Result code for attempting to push a directory. */
|
||||
public static final int RESULT_LOCAL_IS_DIRECTORY = 12;
|
||||
/** Result code for when the target path of a multi file push is a file. */
|
||||
public static final int RESULT_REMOTE_IS_FILE = 13;
|
||||
/** Result code for receiving too much data from the remove device at once */
|
||||
public static final int RESULT_BUFFER_OVERRUN = 14;
|
||||
|
||||
/**
|
||||
* A file transfer result.
|
||||
* <p/>
|
||||
* This contains a code, and an optional string
|
||||
*/
|
||||
public static class SyncResult {
|
||||
private int mCode;
|
||||
private String mMessage;
|
||||
SyncResult(int code, String message) {
|
||||
mCode = code;
|
||||
mMessage = message;
|
||||
}
|
||||
|
||||
SyncResult(int code, Exception e) {
|
||||
this(code, e.getMessage());
|
||||
}
|
||||
|
||||
SyncResult(int code) {
|
||||
this(code, errorCodeToString(code));
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return mCode;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return mMessage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classes which implement this interface provide methods that deal
|
||||
* with displaying transfer progress.
|
||||
*/
|
||||
public interface ISyncProgressMonitor {
|
||||
/**
|
||||
* Sent when the transfer starts
|
||||
* @param totalWork the total amount of work.
|
||||
*/
|
||||
public void start(int totalWork);
|
||||
/**
|
||||
* Sent when the transfer is finished or interrupted.
|
||||
*/
|
||||
public void stop();
|
||||
/**
|
||||
* Sent to query for possible cancellation.
|
||||
* @return true if the transfer should be stopped.
|
||||
*/
|
||||
public boolean isCanceled();
|
||||
/**
|
||||
* Sent when a sub task is started.
|
||||
* @param name the name of the sub task.
|
||||
*/
|
||||
public void startSubTask(String name);
|
||||
/**
|
||||
* Sent when some progress have been made.
|
||||
* @param work the amount of work done.
|
||||
*/
|
||||
public void advance(int work);
|
||||
}
|
||||
|
||||
/**
|
||||
* A Sync progress monitor that does nothing
|
||||
*/
|
||||
private static class NullSyncProgresMonitor implements ISyncProgressMonitor {
|
||||
public void advance(int work) {
|
||||
}
|
||||
public boolean isCanceled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void start(int totalWork) {
|
||||
}
|
||||
public void startSubTask(String name) {
|
||||
}
|
||||
public void stop() {
|
||||
}
|
||||
}
|
||||
|
||||
private InetSocketAddress mAddress;
|
||||
private Device mDevice;
|
||||
private SocketChannel mChannel;
|
||||
|
||||
/**
|
||||
* Buffer used to send data. Allocated when needed and reused afterward.
|
||||
*/
|
||||
private byte[] mBuffer;
|
||||
|
||||
/**
|
||||
* Creates a Sync service object.
|
||||
* @param address The address to connect to
|
||||
* @param device the {@link Device} that the service connects to.
|
||||
*/
|
||||
SyncService(InetSocketAddress address, Device device) {
|
||||
mAddress = address;
|
||||
mDevice = device;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the sync connection. This must be called before any calls to push[File] / pull[File].
|
||||
* @return true if the connection opened, false otherwise.
|
||||
*/
|
||||
boolean openSync() {
|
||||
try {
|
||||
mChannel = SocketChannel.open(mAddress);
|
||||
mChannel.configureBlocking(false);
|
||||
|
||||
// target a specific device
|
||||
AdbHelper.setDevice(mChannel, mDevice);
|
||||
|
||||
byte[] request = AdbHelper.formAdbRequest("sync:"); // $NON-NLS-1$
|
||||
AdbHelper.write(mChannel, request, -1, AdbHelper.STD_TIMEOUT);
|
||||
|
||||
AdbResponse resp = AdbHelper.readAdbResponse(mChannel, false /* readDiagString */);
|
||||
|
||||
if (!resp.ioSuccess || !resp.okay) {
|
||||
Log.w("ddms",
|
||||
"Got timeout or unhappy response from ADB sync req: "
|
||||
+ resp.message);
|
||||
mChannel.close();
|
||||
mChannel = null;
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (mChannel != null) {
|
||||
try {
|
||||
mChannel.close();
|
||||
} catch (IOException e1) {
|
||||
// we do nothing, since we'll return false just below
|
||||
}
|
||||
mChannel = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connection.
|
||||
*/
|
||||
public void close() {
|
||||
if (mChannel != null) {
|
||||
try {
|
||||
mChannel.close();
|
||||
} catch (IOException e) {
|
||||
// nothing to be done really...
|
||||
}
|
||||
mChannel = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sync progress monitor that does nothing. This allows background tasks that don't
|
||||
* want/need to display ui, to pass a valid {@link ISyncProgressMonitor}.
|
||||
* <p/>This object can be reused multiple times and can be used by concurrent threads.
|
||||
*/
|
||||
public static ISyncProgressMonitor getNullProgressMonitor() {
|
||||
return sNullSyncProgressMonitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an error code into a non-localized string
|
||||
* @param code the error code;
|
||||
*/
|
||||
private static String errorCodeToString(int code) {
|
||||
switch (code) {
|
||||
case RESULT_OK:
|
||||
return "Success.";
|
||||
case RESULT_CANCELED:
|
||||
return "Tranfert canceled by the user.";
|
||||
case RESULT_UNKNOWN_ERROR:
|
||||
return "Unknown Error.";
|
||||
case RESULT_CONNECTION_ERROR:
|
||||
return "Adb Connection Error.";
|
||||
case RESULT_NO_REMOTE_OBJECT:
|
||||
return "Remote object doesn't exist!";
|
||||
case RESULT_TARGET_IS_FILE:
|
||||
return "Target object is a file.";
|
||||
case RESULT_NO_DIR_TARGET:
|
||||
return "Target directory doesn't exist.";
|
||||
case RESULT_REMOTE_PATH_ENCODING:
|
||||
return "Remote Path encoding is not supported.";
|
||||
case RESULT_REMOTE_PATH_LENGTH:
|
||||
return "Remove path is too long.";
|
||||
case RESULT_FILE_WRITE_ERROR:
|
||||
return "Writing local file failed!";
|
||||
case RESULT_FILE_READ_ERROR:
|
||||
return "Reading local file failed!";
|
||||
case RESULT_NO_LOCAL_FILE:
|
||||
return "Local file doesn't exist.";
|
||||
case RESULT_LOCAL_IS_DIRECTORY:
|
||||
return "Local path is a directory.";
|
||||
case RESULT_REMOTE_IS_FILE:
|
||||
return "Remote path is a file.";
|
||||
case RESULT_BUFFER_OVERRUN:
|
||||
return "Receiving too much data.";
|
||||
}
|
||||
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls file(s) or folder(s).
|
||||
* @param entries the remote item(s) to pull
|
||||
* @param localPath The local destination. If the entries count is > 1 or
|
||||
* if the unique entry is a folder, this should be a folder.
|
||||
* @param monitor The progress monitor. Cannot be null.
|
||||
* @return a {@link SyncResult} object with a code and an optional message.
|
||||
*
|
||||
* @see FileListingService.FileEntry
|
||||
* @see #getNullProgressMonitor()
|
||||
*/
|
||||
public SyncResult pull(FileEntry[] entries, String localPath, ISyncProgressMonitor monitor) {
|
||||
|
||||
// first we check the destination is a directory and exists
|
||||
File f = new File(localPath);
|
||||
if (f.exists() == false) {
|
||||
return new SyncResult(RESULT_NO_DIR_TARGET);
|
||||
}
|
||||
if (f.isDirectory() == false) {
|
||||
return new SyncResult(RESULT_TARGET_IS_FILE);
|
||||
}
|
||||
|
||||
// get a FileListingService object
|
||||
FileListingService fls = new FileListingService(mDevice);
|
||||
|
||||
// compute the number of file to move
|
||||
int total = getTotalRemoteFileSize(entries, fls);
|
||||
|
||||
// start the monitor
|
||||
monitor.start(total);
|
||||
|
||||
SyncResult result = doPull(entries, localPath, fls, monitor);
|
||||
|
||||
monitor.stop();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls a single file.
|
||||
* @param remote the remote file
|
||||
* @param localFilename The local destination.
|
||||
* @param monitor The progress monitor. Cannot be null.
|
||||
* @return a {@link SyncResult} object with a code and an optional message.
|
||||
*
|
||||
* @see FileListingService.FileEntry
|
||||
* @see #getNullProgressMonitor()
|
||||
*/
|
||||
public SyncResult pullFile(FileEntry remote, String localFilename,
|
||||
ISyncProgressMonitor monitor) {
|
||||
int total = remote.getSizeValue();
|
||||
monitor.start(total);
|
||||
|
||||
SyncResult result = doPullFile(remote.getFullPath(), localFilename, monitor);
|
||||
|
||||
monitor.stop();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push several files.
|
||||
* @param local An array of loca files to push
|
||||
* @param remote the remote {@link FileEntry} representing a directory.
|
||||
* @param monitor The progress monitor. Cannot be null.
|
||||
* @return a {@link SyncResult} object with a code and an optional message.
|
||||
*/
|
||||
public SyncResult push(String[] local, FileEntry remote, ISyncProgressMonitor monitor) {
|
||||
if (remote.isDirectory() == false) {
|
||||
return new SyncResult(RESULT_REMOTE_IS_FILE);
|
||||
}
|
||||
|
||||
// make a list of File from the list of String
|
||||
ArrayList<File> files = new ArrayList<File>();
|
||||
for (String path : local) {
|
||||
files.add(new File(path));
|
||||
}
|
||||
|
||||
// get the total count of the bytes to transfer
|
||||
File[] fileArray = files.toArray(new File[files.size()]);
|
||||
int total = getTotalLocalFileSize(fileArray);
|
||||
|
||||
monitor.start(total);
|
||||
|
||||
SyncResult result = doPush(fileArray, remote.getFullPath(), monitor);
|
||||
|
||||
monitor.stop();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a single file.
|
||||
* @param local the local filepath.
|
||||
* @param remote The remote filepath.
|
||||
* @param monitor The progress monitor. Cannot be null.
|
||||
* @return a {@link SyncResult} object with a code and an optional message.
|
||||
*/
|
||||
public SyncResult pushFile(String local, String remote, ISyncProgressMonitor monitor) {
|
||||
File f = new File(local);
|
||||
if (f.exists() == false) {
|
||||
return new SyncResult(RESULT_NO_LOCAL_FILE);
|
||||
}
|
||||
|
||||
if (f.isDirectory()) {
|
||||
return new SyncResult(RESULT_LOCAL_IS_DIRECTORY);
|
||||
}
|
||||
|
||||
monitor.start((int)f.length());
|
||||
|
||||
SyncResult result = doPushFile(local, remote, monitor);
|
||||
|
||||
monitor.stop();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* compute the recursive file size of all the files in the list. Folder
|
||||
* have a weight of 1.
|
||||
* @param entries
|
||||
* @param fls
|
||||
* @return
|
||||
*/
|
||||
private int getTotalRemoteFileSize(FileEntry[] entries, FileListingService fls) {
|
||||
int count = 0;
|
||||
for (FileEntry e : entries) {
|
||||
int type = e.getType();
|
||||
if (type == FileListingService.TYPE_DIRECTORY) {
|
||||
// get the children
|
||||
FileEntry[] children = fls.getChildren(e, false, null);
|
||||
count += getTotalRemoteFileSize(children, fls) + 1;
|
||||
} else if (type == FileListingService.TYPE_FILE) {
|
||||
count += e.getSizeValue();
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* compute the recursive file size of all the files in the list. Folder
|
||||
* have a weight of 1.
|
||||
* This does not check for circular links.
|
||||
* @param files
|
||||
* @return
|
||||
*/
|
||||
private int getTotalLocalFileSize(File[] files) {
|
||||
int count = 0;
|
||||
|
||||
for (File f : files) {
|
||||
if (f.exists()) {
|
||||
if (f.isDirectory()) {
|
||||
return getTotalLocalFileSize(f.listFiles()) + 1;
|
||||
} else if (f.isFile()) {
|
||||
count += f.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls multiple files/folders recursively.
|
||||
* @param entries The list of entry to pull
|
||||
* @param localPath the localpath to a directory
|
||||
* @param fileListingService a FileListingService object to browse through remote directories.
|
||||
* @param monitor the progress monitor. Must be started already.
|
||||
* @return a {@link SyncResult} object with a code and an optional message.
|
||||
*/
|
||||
private SyncResult doPull(FileEntry[] entries, String localPath,
|
||||
FileListingService fileListingService,
|
||||
ISyncProgressMonitor monitor) {
|
||||
|
||||
for (FileEntry e : entries) {
|
||||
// check if we're cancelled
|
||||
if (monitor.isCanceled() == true) {
|
||||
return new SyncResult(RESULT_CANCELED);
|
||||
}
|
||||
|
||||
// get type (we only pull directory and files for now)
|
||||
int type = e.getType();
|
||||
if (type == FileListingService.TYPE_DIRECTORY) {
|
||||
monitor.startSubTask(e.getFullPath());
|
||||
String dest = localPath + File.separator + e.getName();
|
||||
|
||||
// make the directory
|
||||
File d = new File(dest);
|
||||
d.mkdir();
|
||||
|
||||
// then recursively call the content. Since we did a ls command
|
||||
// to get the number of files, we can use the cache
|
||||
FileEntry[] children = fileListingService.getChildren(e, true, null);
|
||||
SyncResult result = doPull(children, dest, fileListingService, monitor);
|
||||
if (result.mCode != RESULT_OK) {
|
||||
return result;
|
||||
}
|
||||
monitor.advance(1);
|
||||
} else if (type == FileListingService.TYPE_FILE) {
|
||||
monitor.startSubTask(e.getFullPath());
|
||||
String dest = localPath + File.separator + e.getName();
|
||||
SyncResult result = doPullFile(e.getFullPath(), dest, monitor);
|
||||
if (result.mCode != RESULT_OK) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new SyncResult(RESULT_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls a remote file
|
||||
* @param remotePath the remote file (length max is 1024)
|
||||
* @param localPath the local destination
|
||||
* @param monitor the monitor. The monitor must be started already.
|
||||
* @return a {@link SyncResult} object with a code and an optional message.
|
||||
*/
|
||||
private SyncResult doPullFile(String remotePath, String localPath,
|
||||
ISyncProgressMonitor monitor) {
|
||||
byte[] msg = null;
|
||||
byte[] pullResult = new byte[8];
|
||||
try {
|
||||
byte[] remotePathContent = remotePath.getBytes(AdbHelper.DEFAULT_ENCODING);
|
||||
|
||||
if (remotePathContent.length > REMOTE_PATH_MAX_LENGTH) {
|
||||
return new SyncResult(RESULT_REMOTE_PATH_LENGTH);
|
||||
}
|
||||
|
||||
// create the full request message
|
||||
msg = createFileReq(ID_RECV, remotePathContent);
|
||||
|
||||
// and send it.
|
||||
AdbHelper.write(mChannel, msg, -1, AdbHelper.STD_TIMEOUT);
|
||||
|
||||
// read the result, in a byte array containing 2 ints
|
||||
// (id, size)
|
||||
AdbHelper.read(mChannel, pullResult, -1, AdbHelper.STD_TIMEOUT);
|
||||
|
||||
// check we have the proper data back
|
||||
if (checkResult(pullResult, ID_DATA) == false &&
|
||||
checkResult(pullResult, ID_DONE) == false) {
|
||||
return new SyncResult(RESULT_CONNECTION_ERROR);
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return new SyncResult(RESULT_REMOTE_PATH_ENCODING, e);
|
||||
} catch (IOException e) {
|
||||
return new SyncResult(RESULT_CONNECTION_ERROR, e);
|
||||
}
|
||||
|
||||
// access the destination file
|
||||
File f = new File(localPath);
|
||||
|
||||
// create the stream to write in the file. We use a new try/catch block to differentiate
|
||||
// between file and network io exceptions.
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(f);
|
||||
} catch (FileNotFoundException e) {
|
||||
return new SyncResult(RESULT_FILE_WRITE_ERROR, e);
|
||||
}
|
||||
|
||||
// the buffer to read the data
|
||||
byte[] data = new byte[SYNC_DATA_MAX];
|
||||
|
||||
// loop to get data until we're done.
|
||||
while (true) {
|
||||
// check if we're cancelled
|
||||
if (monitor.isCanceled() == true) {
|
||||
return new SyncResult(RESULT_CANCELED);
|
||||
}
|
||||
|
||||
// if we're done, we stop the loop
|
||||
if (checkResult(pullResult, ID_DONE)) {
|
||||
break;
|
||||
}
|
||||
if (checkResult(pullResult, ID_DATA) == false) {
|
||||
// hmm there's an error
|
||||
return new SyncResult(RESULT_CONNECTION_ERROR);
|
||||
}
|
||||
int length = ArrayHelper.swap32bitFromArray(pullResult, 4);
|
||||
if (length > SYNC_DATA_MAX) {
|
||||
// buffer overrun!
|
||||
// error and exit
|
||||
return new SyncResult(RESULT_BUFFER_OVERRUN);
|
||||
}
|
||||
|
||||
try {
|
||||
// now read the length we received
|
||||
AdbHelper.read(mChannel, data, length, AdbHelper.STD_TIMEOUT);
|
||||
|
||||
// get the header for the next packet.
|
||||
AdbHelper.read(mChannel, pullResult, -1, AdbHelper.STD_TIMEOUT);
|
||||
} catch (IOException e) {
|
||||
return new SyncResult(RESULT_CONNECTION_ERROR, e);
|
||||
}
|
||||
|
||||
// write the content in the file
|
||||
try {
|
||||
fos.write(data, 0, length);
|
||||
} catch (IOException e) {
|
||||
return new SyncResult(RESULT_FILE_WRITE_ERROR, e);
|
||||
}
|
||||
|
||||
monitor.advance(length);
|
||||
}
|
||||
|
||||
try {
|
||||
fos.flush();
|
||||
} catch (IOException e) {
|
||||
return new SyncResult(RESULT_FILE_WRITE_ERROR, e);
|
||||
}
|
||||
return new SyncResult(RESULT_OK);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Push multiple files
|
||||
* @param fileArray
|
||||
* @param remotePath
|
||||
* @param monitor
|
||||
* @return a {@link SyncResult} object with a code and an optional message.
|
||||
*/
|
||||
private SyncResult doPush(File[] fileArray, String remotePath, ISyncProgressMonitor monitor) {
|
||||
for (File f : fileArray) {
|
||||
// check if we're canceled
|
||||
if (monitor.isCanceled() == true) {
|
||||
return new SyncResult(RESULT_CANCELED);
|
||||
}
|
||||
if (f.exists()) {
|
||||
if (f.isDirectory()) {
|
||||
// append the name of the directory to the remote path
|
||||
String dest = remotePath + "/" + f.getName(); // $NON-NLS-1S
|
||||
monitor.startSubTask(dest);
|
||||
SyncResult result = doPush(f.listFiles(), dest, monitor);
|
||||
|
||||
if (result.mCode != RESULT_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
monitor.advance(1);
|
||||
} else if (f.isFile()) {
|
||||
// append the name of the file to the remote path
|
||||
String remoteFile = remotePath + "/" + f.getName(); // $NON-NLS-1S
|
||||
monitor.startSubTask(remoteFile);
|
||||
SyncResult result = doPushFile(f.getAbsolutePath(), remoteFile, monitor);
|
||||
if (result.mCode != RESULT_OK) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new SyncResult(RESULT_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a single file
|
||||
* @param localPath the local file to push
|
||||
* @param remotePath the remote file (length max is 1024)
|
||||
* @param monitor the monitor. The monitor must be started already.
|
||||
* @return a {@link SyncResult} object with a code and an optional message.
|
||||
*/
|
||||
private SyncResult doPushFile(String localPath, String remotePath,
|
||||
ISyncProgressMonitor monitor) {
|
||||
FileInputStream fis = null;
|
||||
byte[] msg;
|
||||
|
||||
try {
|
||||
byte[] remotePathContent = remotePath.getBytes(AdbHelper.DEFAULT_ENCODING);
|
||||
|
||||
if (remotePathContent.length > REMOTE_PATH_MAX_LENGTH) {
|
||||
return new SyncResult(RESULT_REMOTE_PATH_LENGTH);
|
||||
}
|
||||
|
||||
File f = new File(localPath);
|
||||
|
||||
// this shouldn't happen but still...
|
||||
if (f.exists() == false) {
|
||||
return new SyncResult(RESULT_NO_LOCAL_FILE);
|
||||
}
|
||||
|
||||
// create the stream to read the file
|
||||
fis = new FileInputStream(f);
|
||||
|
||||
// create the header for the action
|
||||
msg = createSendFileReq(ID_SEND, remotePathContent, 0644);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return new SyncResult(RESULT_REMOTE_PATH_ENCODING, e);
|
||||
} catch (FileNotFoundException e) {
|
||||
return new SyncResult(RESULT_FILE_READ_ERROR, e);
|
||||
}
|
||||
|
||||
// and send it. We use a custom try/catch block to make the difference between
|
||||
// file and network IO exceptions.
|
||||
try {
|
||||
AdbHelper.write(mChannel, msg, -1, AdbHelper.STD_TIMEOUT);
|
||||
} catch (IOException e) {
|
||||
return new SyncResult(RESULT_CONNECTION_ERROR, e);
|
||||
}
|
||||
|
||||
// create the buffer used to read.
|
||||
// we read max SYNC_DATA_MAX, but we need 2 4 bytes at the beginning.
|
||||
if (mBuffer == null) {
|
||||
mBuffer = new byte[SYNC_DATA_MAX + 8];
|
||||
}
|
||||
System.arraycopy(ID_DATA, 0, mBuffer, 0, ID_DATA.length);
|
||||
|
||||
// look while there is something to read
|
||||
while (true) {
|
||||
// check if we're canceled
|
||||
if (monitor.isCanceled() == true) {
|
||||
return new SyncResult(RESULT_CANCELED);
|
||||
}
|
||||
|
||||
// read up to SYNC_DATA_MAX
|
||||
int readCount = 0;
|
||||
try {
|
||||
readCount = fis.read(mBuffer, 8, SYNC_DATA_MAX);
|
||||
} catch (IOException e) {
|
||||
return new SyncResult(RESULT_FILE_READ_ERROR, e);
|
||||
}
|
||||
|
||||
if (readCount == -1) {
|
||||
// we reached the end of the file
|
||||
break;
|
||||
}
|
||||
|
||||
// now send the data to the device
|
||||
// first write the amount read
|
||||
ArrayHelper.swap32bitsToArray(readCount, mBuffer, 4);
|
||||
|
||||
// now write it
|
||||
try {
|
||||
AdbHelper.write(mChannel, mBuffer, readCount+8, AdbHelper.STD_TIMEOUT);
|
||||
} catch (IOException e) {
|
||||
return new SyncResult(RESULT_CONNECTION_ERROR, e);
|
||||
}
|
||||
|
||||
// and advance the monitor
|
||||
monitor.advance(readCount);
|
||||
}
|
||||
// close the local file
|
||||
try {
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
return new SyncResult(RESULT_FILE_READ_ERROR, e);
|
||||
}
|
||||
|
||||
try {
|
||||
// create the DONE message
|
||||
long time = System.currentTimeMillis() / 1000;
|
||||
msg = createReq(ID_DONE, (int)time);
|
||||
|
||||
// and send it.
|
||||
AdbHelper.write(mChannel, msg, -1, AdbHelper.STD_TIMEOUT);
|
||||
|
||||
// read the result, in a byte array containing 2 ints
|
||||
// (id, size)
|
||||
byte[] result = new byte[8];
|
||||
AdbHelper.read(mChannel, result, -1 /* full length */, AdbHelper.STD_TIMEOUT);
|
||||
|
||||
if (checkResult(result, ID_OKAY) == false) {
|
||||
if (checkResult(result, ID_FAIL)) {
|
||||
// read some error message...
|
||||
int len = ArrayHelper.swap32bitFromArray(result, 4);
|
||||
|
||||
AdbHelper.read(mChannel, mBuffer, len, AdbHelper.STD_TIMEOUT);
|
||||
|
||||
// output the result?
|
||||
String message = new String(mBuffer, 0, len);
|
||||
Log.e("ddms", "transfer error: " + message);
|
||||
return new SyncResult(RESULT_UNKNOWN_ERROR, message);
|
||||
}
|
||||
|
||||
return new SyncResult(RESULT_UNKNOWN_ERROR);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return new SyncResult(RESULT_CONNECTION_ERROR, e);
|
||||
}
|
||||
|
||||
return new SyncResult(RESULT_OK);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the mode of the remote file.
|
||||
* @param path the remote file
|
||||
* @return and Integer containing the mode if all went well or null
|
||||
* otherwise
|
||||
*/
|
||||
private Integer readMode(String path) {
|
||||
try {
|
||||
// create the stat request message.
|
||||
byte[] msg = createFileReq(ID_STAT, path);
|
||||
|
||||
AdbHelper.write(mChannel, msg, -1 /* full length */, AdbHelper.STD_TIMEOUT);
|
||||
|
||||
// read the result, in a byte array containing 4 ints
|
||||
// (id, mode, size, time)
|
||||
byte[] statResult = new byte[16];
|
||||
AdbHelper.read(mChannel, statResult, -1 /* full length */, AdbHelper.STD_TIMEOUT);
|
||||
|
||||
// check we have the proper data back
|
||||
if (checkResult(statResult, ID_STAT) == false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// we return the mode (2nd int in the array)
|
||||
return ArrayHelper.swap32bitFromArray(statResult, 4);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a command with a code and an int values
|
||||
* @param command
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
private static byte[] createReq(byte[] command, int value) {
|
||||
byte[] array = new byte[8];
|
||||
|
||||
System.arraycopy(command, 0, array, 0, 4);
|
||||
ArrayHelper.swap32bitsToArray(value, array, 4);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the data array for a stat request.
|
||||
* @param command the 4 byte command (ID_STAT, ID_RECV, ...)
|
||||
* @param path The path of the remote file on which to execute the command
|
||||
* @return the byte[] to send to the device through adb
|
||||
*/
|
||||
private static byte[] createFileReq(byte[] command, String path) {
|
||||
byte[] pathContent = null;
|
||||
try {
|
||||
pathContent = path.getBytes(AdbHelper.DEFAULT_ENCODING);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createFileReq(command, pathContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the data array for a file request. This creates an array with a 4 byte command + the
|
||||
* remote file name.
|
||||
* @param command the 4 byte command (ID_STAT, ID_RECV, ...).
|
||||
* @param path The path, as a byte array, of the remote file on which to
|
||||
* execute the command.
|
||||
* @return the byte[] to send to the device through adb
|
||||
*/
|
||||
private static byte[] createFileReq(byte[] command, byte[] path) {
|
||||
byte[] array = new byte[8 + path.length];
|
||||
|
||||
System.arraycopy(command, 0, array, 0, 4);
|
||||
ArrayHelper.swap32bitsToArray(path.length, array, 4);
|
||||
System.arraycopy(path, 0, array, 8, path.length);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
private static byte[] createSendFileReq(byte[] command, byte[] path, int mode) {
|
||||
// make the mode into a string
|
||||
String modeStr = "," + (mode & 0777); // $NON-NLS-1S
|
||||
byte[] modeContent = null;
|
||||
try {
|
||||
modeContent = modeStr.getBytes(AdbHelper.DEFAULT_ENCODING);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] array = new byte[8 + path.length + modeContent.length];
|
||||
|
||||
System.arraycopy(command, 0, array, 0, 4);
|
||||
ArrayHelper.swap32bitsToArray(path.length + modeContent.length, array, 4);
|
||||
System.arraycopy(path, 0, array, 8, path.length);
|
||||
System.arraycopy(modeContent, 0, array, 8 + path.length, modeContent.length);
|
||||
|
||||
return array;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the result array starts with the provided code
|
||||
* @param result The result array to check
|
||||
* @param code The 4 byte code.
|
||||
* @return true if the code matches.
|
||||
*/
|
||||
private static boolean checkResult(byte[] result, byte[] code) {
|
||||
if (result[0] != code[0] ||
|
||||
result[1] != code[1] ||
|
||||
result[2] != code[2] ||
|
||||
result[3] != code[3]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private static int getFileType(int mode) {
|
||||
if ((mode & S_ISOCK) == S_ISOCK) {
|
||||
return FileListingService.TYPE_SOCKET;
|
||||
}
|
||||
|
||||
if ((mode & S_IFLNK) == S_IFLNK) {
|
||||
return FileListingService.TYPE_LINK;
|
||||
}
|
||||
|
||||
if ((mode & S_IFREG) == S_IFREG) {
|
||||
return FileListingService.TYPE_FILE;
|
||||
}
|
||||
|
||||
if ((mode & S_IFBLK) == S_IFBLK) {
|
||||
return FileListingService.TYPE_BLOCK;
|
||||
}
|
||||
|
||||
if ((mode & S_IFDIR) == S_IFDIR) {
|
||||
return FileListingService.TYPE_DIRECTORY;
|
||||
}
|
||||
|
||||
if ((mode & S_IFCHR) == S_IFCHR) {
|
||||
return FileListingService.TYPE_CHARACTER;
|
||||
}
|
||||
|
||||
if ((mode & S_IFIFO) == S_IFIFO) {
|
||||
return FileListingService.TYPE_FIFO;
|
||||
}
|
||||
|
||||
return FileListingService.TYPE_OTHER;
|
||||
}
|
||||
}
|
||||
139
tools/ddms/libs/ddmlib/src/com/android/ddmlib/ThreadInfo.java
Normal file
139
tools/ddms/libs/ddmlib/src/com/android/ddmlib/ThreadInfo.java
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib;
|
||||
|
||||
/**
|
||||
* Holds a thread information.
|
||||
*/
|
||||
public final class ThreadInfo implements IStackTraceInfo {
|
||||
private int mThreadId;
|
||||
private String mThreadName;
|
||||
private int mStatus;
|
||||
private int mTid;
|
||||
private int mUtime;
|
||||
private int mStime;
|
||||
private boolean mIsDaemon;
|
||||
private StackTraceElement[] mTrace;
|
||||
private long mTraceTime;
|
||||
|
||||
// priority?
|
||||
// total CPU used?
|
||||
// method at top of stack?
|
||||
|
||||
/**
|
||||
* Construct with basic identification.
|
||||
*/
|
||||
ThreadInfo(int threadId, String threadName) {
|
||||
mThreadId = threadId;
|
||||
mThreadName = threadName;
|
||||
|
||||
mStatus = -1;
|
||||
//mTid = mUtime = mStime = 0;
|
||||
//mIsDaemon = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set with the values we get from a THST chunk.
|
||||
*/
|
||||
void updateThread(int status, int tid, int utime, int stime, boolean isDaemon) {
|
||||
|
||||
mStatus = status;
|
||||
mTid = tid;
|
||||
mUtime = utime;
|
||||
mStime = stime;
|
||||
mIsDaemon = isDaemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the stack call of the thread.
|
||||
* @param trace stackcall information.
|
||||
*/
|
||||
void setStackCall(StackTraceElement[] trace) {
|
||||
mTrace = trace;
|
||||
mTraceTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the thread's ID.
|
||||
*/
|
||||
public int getThreadId() {
|
||||
return mThreadId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the thread's name.
|
||||
*/
|
||||
public String getThreadName() {
|
||||
return mThreadName;
|
||||
}
|
||||
|
||||
void setThreadName(String name) {
|
||||
mThreadName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the system tid.
|
||||
*/
|
||||
public int getTid() {
|
||||
return mTid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the VM thread status.
|
||||
*/
|
||||
public int getStatus() {
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cumulative user time.
|
||||
*/
|
||||
public int getUtime() {
|
||||
return mUtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cumulative system time.
|
||||
*/
|
||||
public int getStime() {
|
||||
return mStime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this is a daemon thread.
|
||||
*/
|
||||
public boolean isDaemon() {
|
||||
return mIsDaemon;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.android.ddmlib.IStackTraceInfo#getStackTrace()
|
||||
*/
|
||||
public StackTraceElement[] getStackTrace() {
|
||||
return mTrace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the approximate time of the stacktrace data.
|
||||
* @see #getStackTrace()
|
||||
*/
|
||||
public long getStackCallTime() {
|
||||
return mTraceTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,461 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib.log;
|
||||
|
||||
import com.android.ddmlib.log.LogReceiver.LogEntry;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Represents an event and its data.
|
||||
*/
|
||||
public class EventContainer {
|
||||
|
||||
/**
|
||||
* Comparison method for {@link EventContainer#testValue(int, Object, com.android.ddmlib.log.EventContainer.CompareMethod)}
|
||||
*
|
||||
*/
|
||||
public enum CompareMethod {
|
||||
EQUAL_TO("equals", "=="),
|
||||
LESSER_THAN("less than or equals to", "<="),
|
||||
LESSER_THAN_STRICT("less than", "<"),
|
||||
GREATER_THAN("greater than or equals to", ">="),
|
||||
GREATER_THAN_STRICT("greater than", ">"),
|
||||
BIT_CHECK("bit check", "&");
|
||||
|
||||
private final String mName;
|
||||
private final String mTestString;
|
||||
|
||||
private CompareMethod(String name, String testString) {
|
||||
mName = name;
|
||||
mTestString = testString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display string.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a short string representing the comparison.
|
||||
*/
|
||||
public String testString() {
|
||||
return mTestString;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Type for event data.
|
||||
*/
|
||||
public static enum EventValueType {
|
||||
UNKNOWN(0),
|
||||
INT(1),
|
||||
LONG(2),
|
||||
STRING(3),
|
||||
LIST(4),
|
||||
TREE(5);
|
||||
|
||||
private final static Pattern STORAGE_PATTERN = Pattern.compile("^(\\d+)@(.*)$"); //$NON-NLS-1$
|
||||
|
||||
private int mValue;
|
||||
|
||||
/**
|
||||
* Returns a {@link EventValueType} from an integer value, or <code>null</code> if no match
|
||||
* was found.
|
||||
* @param value the integer value.
|
||||
*/
|
||||
static EventValueType getEventValueType(int value) {
|
||||
for (EventValueType type : values()) {
|
||||
if (type.mValue == value) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a storage string for an {@link Object} of type supported by
|
||||
* {@link EventValueType}.
|
||||
* <p/>
|
||||
* Strings created by this method can be reloaded with
|
||||
* {@link #getObjectFromStorageString(String)}.
|
||||
* <p/>
|
||||
* NOTE: for now, only {@link #STRING}, {@link #INT}, and {@link #LONG} are supported.
|
||||
* @param object the object to "convert" into a storage string.
|
||||
* @return a string storing the object and its type or null if the type was not recognized.
|
||||
*/
|
||||
public static String getStorageString(Object object) {
|
||||
if (object instanceof String) {
|
||||
return STRING.mValue + "@" + (String)object; //$NON-NLS-1$
|
||||
} else if (object instanceof Integer) {
|
||||
return INT.mValue + "@" + object.toString(); //$NON-NLS-1$
|
||||
} else if (object instanceof Long) {
|
||||
return LONG.mValue + "@" + object.toString(); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link Object} from a storage string created with
|
||||
* {@link #getStorageString(Object)}.
|
||||
* @param value the storage string
|
||||
* @return an {@link Object} or null if the string or type were not recognized.
|
||||
*/
|
||||
public static Object getObjectFromStorageString(String value) {
|
||||
Matcher m = STORAGE_PATTERN.matcher(value);
|
||||
if (m.matches()) {
|
||||
try {
|
||||
EventValueType type = getEventValueType(Integer.parseInt(m.group(1)));
|
||||
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case STRING:
|
||||
return m.group(2);
|
||||
case INT:
|
||||
return Integer.valueOf(m.group(2));
|
||||
case LONG:
|
||||
return Long.valueOf(m.group(2));
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the integer value of the enum.
|
||||
*/
|
||||
public int getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString().toLowerCase();
|
||||
}
|
||||
|
||||
private EventValueType(int value) {
|
||||
mValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int mTag;
|
||||
public int pid; /* generating process's pid */
|
||||
public int tid; /* generating process's tid */
|
||||
public int sec; /* seconds since Epoch */
|
||||
public int nsec; /* nanoseconds */
|
||||
|
||||
private Object mData;
|
||||
|
||||
/**
|
||||
* Creates an {@link EventContainer} from a {@link LogEntry}.
|
||||
* @param entry the LogEntry from which pid, tid, and time info is copied.
|
||||
* @param tag the event tag value
|
||||
* @param data the data of the EventContainer.
|
||||
*/
|
||||
EventContainer(LogEntry entry, int tag, Object data) {
|
||||
getType(data);
|
||||
mTag = tag;
|
||||
mData = data;
|
||||
|
||||
pid = entry.pid;
|
||||
tid = entry.tid;
|
||||
sec = entry.sec;
|
||||
nsec = entry.nsec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link EventContainer} with raw data
|
||||
*/
|
||||
EventContainer(int tag, int pid, int tid, int sec, int nsec, Object data) {
|
||||
getType(data);
|
||||
mTag = tag;
|
||||
mData = data;
|
||||
|
||||
this.pid = pid;
|
||||
this.tid = tid;
|
||||
this.sec = sec;
|
||||
this.nsec = nsec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data as an int.
|
||||
* @throws InvalidTypeException if the data type is not {@link EventValueType#INT}.
|
||||
* @see #getType()
|
||||
*/
|
||||
public final Integer getInt() throws InvalidTypeException {
|
||||
if (getType(mData) == EventValueType.INT) {
|
||||
return (Integer)mData;
|
||||
}
|
||||
|
||||
throw new InvalidTypeException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data as a long.
|
||||
* @throws InvalidTypeException if the data type is not {@link EventValueType#LONG}.
|
||||
* @see #getType()
|
||||
*/
|
||||
public final Long getLong() throws InvalidTypeException {
|
||||
if (getType(mData) == EventValueType.LONG) {
|
||||
return (Long)mData;
|
||||
}
|
||||
|
||||
throw new InvalidTypeException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data as a String.
|
||||
* @throws InvalidTypeException if the data type is not {@link EventValueType#STRING}.
|
||||
* @see #getType()
|
||||
*/
|
||||
public final String getString() throws InvalidTypeException {
|
||||
if (getType(mData) == EventValueType.STRING) {
|
||||
return (String)mData;
|
||||
}
|
||||
|
||||
throw new InvalidTypeException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value by index. The return type is defined by its type.
|
||||
* @param valueIndex the index of the value. If the data is not a list, this is ignored.
|
||||
*/
|
||||
public Object getValue(int valueIndex) {
|
||||
return getValue(mData, valueIndex, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value by index as a double.
|
||||
* @param valueIndex the index of the value. If the data is not a list, this is ignored.
|
||||
* @throws InvalidTypeException if the data type is not {@link EventValueType#INT},
|
||||
* {@link EventValueType#LONG}, {@link EventValueType#LIST}, or if the item in the
|
||||
* list at index <code>valueIndex</code> is not of type {@link EventValueType#INT} or
|
||||
* {@link EventValueType#LONG}.
|
||||
* @see #getType()
|
||||
*/
|
||||
public double getValueAsDouble(int valueIndex) throws InvalidTypeException {
|
||||
return getValueAsDouble(mData, valueIndex, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a value by index as a String.
|
||||
* @param valueIndex the index of the value. If the data is not a list, this is ignored.
|
||||
* @throws InvalidTypeException if the data type is not {@link EventValueType#INT},
|
||||
* {@link EventValueType#LONG}, {@link EventValueType#STRING}, {@link EventValueType#LIST},
|
||||
* or if the item in the list at index <code>valueIndex</code> is not of type
|
||||
* {@link EventValueType#INT}, {@link EventValueType#LONG}, or {@link EventValueType#STRING}
|
||||
* @see #getType()
|
||||
*/
|
||||
public String getValueAsString(int valueIndex) throws InvalidTypeException {
|
||||
return getValueAsString(mData, valueIndex, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the data.
|
||||
*/
|
||||
public EventValueType getType() {
|
||||
return getType(mData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of an object.
|
||||
*/
|
||||
public final EventValueType getType(Object data) {
|
||||
if (data instanceof Integer) {
|
||||
return EventValueType.INT;
|
||||
} else if (data instanceof Long) {
|
||||
return EventValueType.LONG;
|
||||
} else if (data instanceof String) {
|
||||
return EventValueType.STRING;
|
||||
} else if (data instanceof Object[]) {
|
||||
// loop through the list to see if we have another list
|
||||
Object[] objects = (Object[])data;
|
||||
for (Object obj : objects) {
|
||||
EventValueType type = getType(obj);
|
||||
if (type == EventValueType.LIST || type == EventValueType.TREE) {
|
||||
return EventValueType.TREE;
|
||||
}
|
||||
}
|
||||
return EventValueType.LIST;
|
||||
}
|
||||
|
||||
return EventValueType.UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the <code>index</code>-th value of this event against a provided value.
|
||||
* @param index the index of the value to test
|
||||
* @param value the value to test against
|
||||
* @param compareMethod the method of testing
|
||||
* @return true if the test passed.
|
||||
* @throws InvalidTypeException in case of type mismatch between the value to test and the value
|
||||
* to test against, or if the compare method is incompatible with the type of the values.
|
||||
* @see CompareMethod
|
||||
*/
|
||||
public boolean testValue(int index, Object value,
|
||||
CompareMethod compareMethod) throws InvalidTypeException {
|
||||
EventValueType type = getType(mData);
|
||||
if (index > 0 && type != EventValueType.LIST) {
|
||||
throw new InvalidTypeException();
|
||||
}
|
||||
|
||||
Object data = mData;
|
||||
if (type == EventValueType.LIST) {
|
||||
data = ((Object[])mData)[index];
|
||||
}
|
||||
|
||||
if (data.getClass().equals(data.getClass()) == false) {
|
||||
throw new InvalidTypeException();
|
||||
}
|
||||
|
||||
switch (compareMethod) {
|
||||
case EQUAL_TO:
|
||||
return data.equals(value);
|
||||
case LESSER_THAN:
|
||||
if (data instanceof Integer) {
|
||||
return (((Integer)data).compareTo((Integer)value) <= 0);
|
||||
} else if (data instanceof Long) {
|
||||
return (((Long)data).compareTo((Long)value) <= 0);
|
||||
}
|
||||
|
||||
// other types can't use this compare method.
|
||||
throw new InvalidTypeException();
|
||||
case LESSER_THAN_STRICT:
|
||||
if (data instanceof Integer) {
|
||||
return (((Integer)data).compareTo((Integer)value) < 0);
|
||||
} else if (data instanceof Long) {
|
||||
return (((Long)data).compareTo((Long)value) < 0);
|
||||
}
|
||||
|
||||
// other types can't use this compare method.
|
||||
throw new InvalidTypeException();
|
||||
case GREATER_THAN:
|
||||
if (data instanceof Integer) {
|
||||
return (((Integer)data).compareTo((Integer)value) >= 0);
|
||||
} else if (data instanceof Long) {
|
||||
return (((Long)data).compareTo((Long)value) >= 0);
|
||||
}
|
||||
|
||||
// other types can't use this compare method.
|
||||
throw new InvalidTypeException();
|
||||
case GREATER_THAN_STRICT:
|
||||
if (data instanceof Integer) {
|
||||
return (((Integer)data).compareTo((Integer)value) > 0);
|
||||
} else if (data instanceof Long) {
|
||||
return (((Long)data).compareTo((Long)value) > 0);
|
||||
}
|
||||
|
||||
// other types can't use this compare method.
|
||||
throw new InvalidTypeException();
|
||||
case BIT_CHECK:
|
||||
if (data instanceof Integer) {
|
||||
return (((Integer)data).intValue() & ((Integer)value).intValue()) != 0;
|
||||
} else if (data instanceof Long) {
|
||||
return (((Long)data).longValue() & ((Long)value).longValue()) != 0;
|
||||
}
|
||||
|
||||
// other types can't use this compare method.
|
||||
throw new InvalidTypeException();
|
||||
default :
|
||||
throw new InvalidTypeException();
|
||||
}
|
||||
}
|
||||
|
||||
private final Object getValue(Object data, int valueIndex, boolean recursive) {
|
||||
EventValueType type = getType(data);
|
||||
|
||||
switch (type) {
|
||||
case INT:
|
||||
case LONG:
|
||||
case STRING:
|
||||
return data;
|
||||
case LIST:
|
||||
if (recursive) {
|
||||
Object[] list = (Object[]) data;
|
||||
if (valueIndex >= 0 && valueIndex < list.length) {
|
||||
return getValue(list[valueIndex], valueIndex, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private final double getValueAsDouble(Object data, int valueIndex, boolean recursive)
|
||||
throws InvalidTypeException {
|
||||
EventValueType type = getType(data);
|
||||
|
||||
switch (type) {
|
||||
case INT:
|
||||
return ((Integer)data).doubleValue();
|
||||
case LONG:
|
||||
return ((Long)data).doubleValue();
|
||||
case STRING:
|
||||
throw new InvalidTypeException();
|
||||
case LIST:
|
||||
if (recursive) {
|
||||
Object[] list = (Object[]) data;
|
||||
if (valueIndex >= 0 && valueIndex < list.length) {
|
||||
return getValueAsDouble(list[valueIndex], valueIndex, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidTypeException();
|
||||
}
|
||||
|
||||
private final String getValueAsString(Object data, int valueIndex, boolean recursive)
|
||||
throws InvalidTypeException {
|
||||
EventValueType type = getType(data);
|
||||
|
||||
switch (type) {
|
||||
case INT:
|
||||
return ((Integer)data).toString();
|
||||
case LONG:
|
||||
return ((Long)data).toString();
|
||||
case STRING:
|
||||
return (String)data;
|
||||
case LIST:
|
||||
if (recursive) {
|
||||
Object[] list = (Object[]) data;
|
||||
if (valueIndex >= 0 && valueIndex < list.length) {
|
||||
return getValueAsString(list[valueIndex], valueIndex, false);
|
||||
}
|
||||
} else {
|
||||
throw new InvalidTypeException(
|
||||
"getValueAsString() doesn't support EventValueType.TREE");
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidTypeException(
|
||||
"getValueAsString() unsupported type:" + type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,577 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib.log;
|
||||
|
||||
import com.android.ddmlib.Device;
|
||||
import com.android.ddmlib.Log;
|
||||
import com.android.ddmlib.MultiLineReceiver;
|
||||
import com.android.ddmlib.log.EventContainer.EventValueType;
|
||||
import com.android.ddmlib.log.EventValueDescription.ValueType;
|
||||
import com.android.ddmlib.log.LogReceiver.LogEntry;
|
||||
import com.android.ddmlib.utils.ArrayHelper;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Parser for the "event" log.
|
||||
*/
|
||||
public final class EventLogParser {
|
||||
|
||||
/** Location of the tag map file on the device */
|
||||
private final static String EVENT_TAG_MAP_FILE = "/system/etc/event-log-tags"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Event log entry types. These must match up with the declarations in
|
||||
* java/android/android/util/EventLog.java.
|
||||
*/
|
||||
private final static int EVENT_TYPE_INT = 0;
|
||||
private final static int EVENT_TYPE_LONG = 1;
|
||||
private final static int EVENT_TYPE_STRING = 2;
|
||||
private final static int EVENT_TYPE_LIST = 3;
|
||||
|
||||
private final static Pattern PATTERN_SIMPLE_TAG = Pattern.compile(
|
||||
"^(\\d+)\\s+([A-Za-z0-9_]+)\\s*$"); //$NON-NLS-1$
|
||||
private final static Pattern PATTERN_TAG_WITH_DESC = Pattern.compile(
|
||||
"^(\\d+)\\s+([A-Za-z0-9_]+)\\s*(.*)\\s*$"); //$NON-NLS-1$
|
||||
private final static Pattern PATTERN_DESCRIPTION = Pattern.compile(
|
||||
"\\(([A-Za-z0-9_\\s]+)\\|(\\d+)(\\|\\d+){0,1}\\)"); //$NON-NLS-1$
|
||||
|
||||
private final static Pattern TEXT_LOG_LINE = Pattern.compile(
|
||||
"(\\d\\d)-(\\d\\d)\\s(\\d\\d):(\\d\\d):(\\d\\d).(\\d{3})\\s+I/([a-zA-Z0-9_]+)\\s*\\(\\s*(\\d+)\\):\\s+(.*)"); //$NON-NLS-1$
|
||||
|
||||
private final TreeMap<Integer, String> mTagMap = new TreeMap<Integer, String>();
|
||||
|
||||
private final TreeMap<Integer, EventValueDescription[]> mValueDescriptionMap =
|
||||
new TreeMap<Integer, EventValueDescription[]>();
|
||||
|
||||
public EventLogParser() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Inits the parser for a specific Device.
|
||||
* <p/>
|
||||
* This methods reads the event-log-tags located on the device to find out
|
||||
* what tags are being written to the event log and what their format is.
|
||||
* @param device The device.
|
||||
* @return <code>true</code> if success, <code>false</code> if failure or cancellation.
|
||||
*/
|
||||
public boolean init(Device device) {
|
||||
// read the event tag map file on the device.
|
||||
try {
|
||||
device.executeShellCommand("cat " + EVENT_TAG_MAP_FILE, //$NON-NLS-1$
|
||||
new MultiLineReceiver() {
|
||||
@Override
|
||||
public void processNewLines(String[] lines) {
|
||||
for (String line : lines) {
|
||||
processTagLine(line);
|
||||
}
|
||||
}
|
||||
public boolean isCancelled() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inits the parser with the content of a tag file.
|
||||
* @param tagFileContent the lines of a tag file.
|
||||
* @return <code>true</code> if success, <code>false</code> if failure.
|
||||
*/
|
||||
public boolean init(String[] tagFileContent) {
|
||||
for (String line : tagFileContent) {
|
||||
processTagLine(line);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inits the parser with a specified event-log-tags file.
|
||||
* @param filePath
|
||||
* @return <code>true</code> if success, <code>false</code> if failure.
|
||||
*/
|
||||
public boolean init(String filePath) {
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new FileReader(filePath));
|
||||
|
||||
String line = null;
|
||||
do {
|
||||
line = reader.readLine();
|
||||
if (line != null) {
|
||||
processTagLine(line);
|
||||
}
|
||||
} while (line != null);
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a line from the event-log-tags file.
|
||||
* @param line the line to process
|
||||
*/
|
||||
private void processTagLine(String line) {
|
||||
// ignore empty lines and comment lines
|
||||
if (line.length() > 0 && line.charAt(0) != '#') {
|
||||
Matcher m = PATTERN_TAG_WITH_DESC.matcher(line);
|
||||
if (m.matches()) {
|
||||
try {
|
||||
int value = Integer.parseInt(m.group(1));
|
||||
String name = m.group(2);
|
||||
if (name != null && mTagMap.get(value) == null) {
|
||||
mTagMap.put(value, name);
|
||||
}
|
||||
|
||||
// special case for the GC tag. We ignore what is in the file,
|
||||
// and take what the custom GcEventContainer class tells us.
|
||||
// This is due to the event encoding several values on 2 longs.
|
||||
// @see GcEventContainer
|
||||
if (value == GcEventContainer.GC_EVENT_TAG) {
|
||||
mValueDescriptionMap.put(value,
|
||||
GcEventContainer.getValueDescriptions());
|
||||
} else {
|
||||
|
||||
String description = m.group(3);
|
||||
if (description != null && description.length() > 0) {
|
||||
EventValueDescription[] desc =
|
||||
processDescription(description);
|
||||
|
||||
if (desc != null) {
|
||||
mValueDescriptionMap.put(value, desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// failed to convert the number into a string. just ignore it.
|
||||
}
|
||||
} else {
|
||||
m = PATTERN_SIMPLE_TAG.matcher(line);
|
||||
if (m.matches()) {
|
||||
int value = Integer.parseInt(m.group(1));
|
||||
String name = m.group(2);
|
||||
if (name != null && mTagMap.get(value) == null) {
|
||||
mTagMap.put(value, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private EventValueDescription[] processDescription(String description) {
|
||||
String[] descriptions = description.split("\\s*,\\s*"); //$NON-NLS-1$
|
||||
|
||||
ArrayList<EventValueDescription> list = new ArrayList<EventValueDescription>();
|
||||
|
||||
for (String desc : descriptions) {
|
||||
Matcher m = PATTERN_DESCRIPTION.matcher(desc);
|
||||
if (m.matches()) {
|
||||
try {
|
||||
String name = m.group(1);
|
||||
|
||||
String typeString = m.group(2);
|
||||
int typeValue = Integer.parseInt(typeString);
|
||||
EventValueType eventValueType = EventValueType.getEventValueType(typeValue);
|
||||
if (eventValueType == null) {
|
||||
// just ignore this description if the value is not recognized.
|
||||
// TODO: log the error.
|
||||
}
|
||||
|
||||
typeString = m.group(3);
|
||||
if (typeString != null && typeString.length() > 0) {
|
||||
//skip the |
|
||||
typeString = typeString.substring(1);
|
||||
|
||||
typeValue = Integer.parseInt(typeString);
|
||||
ValueType valueType = ValueType.getValueType(typeValue);
|
||||
|
||||
list.add(new EventValueDescription(name, eventValueType, valueType));
|
||||
} else {
|
||||
list.add(new EventValueDescription(name, eventValueType));
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
// just ignore this description if one number is malformed.
|
||||
// TODO: log the error.
|
||||
} catch (InvalidValueTypeException e) {
|
||||
// just ignore this description if data type and data unit don't match
|
||||
// TODO: log the error.
|
||||
}
|
||||
} else {
|
||||
Log.e("EventLogParser", //$NON-NLS-1$
|
||||
String.format("Can't parse %1$s", description)); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
|
||||
if (list.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return list.toArray(new EventValueDescription[list.size()]);
|
||||
|
||||
}
|
||||
|
||||
public EventContainer parse(LogEntry entry) {
|
||||
if (entry.len < 4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int inOffset = 0;
|
||||
|
||||
int tagValue = ArrayHelper.swap32bitFromArray(entry.data, inOffset);
|
||||
inOffset += 4;
|
||||
|
||||
String tag = mTagMap.get(tagValue);
|
||||
if (tag == null) {
|
||||
Log.e("EventLogParser", String.format("unknown tag number: %1$d", tagValue));
|
||||
}
|
||||
|
||||
ArrayList<Object> list = new ArrayList<Object>();
|
||||
if (parseBinaryEvent(entry.data, inOffset, list) == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object data;
|
||||
if (list.size() == 1) {
|
||||
data = list.get(0);
|
||||
} else{
|
||||
data = list.toArray();
|
||||
}
|
||||
|
||||
EventContainer event = null;
|
||||
if (tagValue == GcEventContainer.GC_EVENT_TAG) {
|
||||
event = new GcEventContainer(entry, tagValue, data);
|
||||
} else {
|
||||
event = new EventContainer(entry, tagValue, data);
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
public EventContainer parse(String textLogLine) {
|
||||
// line will look like
|
||||
// 04-29 23:16:16.691 I/dvm_gc_info( 427): <data>
|
||||
// where <data> is either
|
||||
// [value1,value2...]
|
||||
// or
|
||||
// value
|
||||
if (textLogLine.length() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// parse the header first
|
||||
Matcher m = TEXT_LOG_LINE.matcher(textLogLine);
|
||||
if (m.matches()) {
|
||||
try {
|
||||
int month = Integer.parseInt(m.group(1));
|
||||
int day = Integer.parseInt(m.group(2));
|
||||
int hours = Integer.parseInt(m.group(3));
|
||||
int minutes = Integer.parseInt(m.group(4));
|
||||
int seconds = Integer.parseInt(m.group(5));
|
||||
int milliseconds = Integer.parseInt(m.group(6));
|
||||
|
||||
// convert into seconds since epoch and nano-seconds.
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.set(cal.get(Calendar.YEAR), month-1, day, hours, minutes, seconds);
|
||||
int sec = (int)Math.floor(cal.getTimeInMillis()/1000);
|
||||
int nsec = milliseconds * 1000000;
|
||||
|
||||
String tag = m.group(7);
|
||||
|
||||
// get the numerical tag value
|
||||
int tagValue = -1;
|
||||
Set<Entry<Integer, String>> tagSet = mTagMap.entrySet();
|
||||
for (Entry<Integer, String> entry : tagSet) {
|
||||
if (tag.equals(entry.getValue())) {
|
||||
tagValue = entry.getKey();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tagValue == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int pid = Integer.parseInt(m.group(8));
|
||||
|
||||
Object data = parseTextData(m.group(9), tagValue);
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// now we can allocate and return the EventContainer
|
||||
EventContainer event = null;
|
||||
if (tagValue == GcEventContainer.GC_EVENT_TAG) {
|
||||
event = new GcEventContainer(tagValue, pid, -1 /* tid */, sec, nsec, data);
|
||||
} else {
|
||||
event = new EventContainer(tagValue, pid, -1 /* tid */, sec, nsec, data);
|
||||
}
|
||||
|
||||
return event;
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<Integer, String> getTagMap() {
|
||||
return mTagMap;
|
||||
}
|
||||
|
||||
public Map<Integer, EventValueDescription[]> getEventInfoMap() {
|
||||
return mValueDescriptionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively convert binary log data to printable form.
|
||||
*
|
||||
* This needs to be recursive because you can have lists of lists.
|
||||
*
|
||||
* If we run out of room, we stop processing immediately. It's important
|
||||
* for us to check for space on every output element to avoid producing
|
||||
* garbled output.
|
||||
*
|
||||
* Returns the amount read on success, -1 on failure.
|
||||
*/
|
||||
private static int parseBinaryEvent(byte[] eventData, int dataOffset, ArrayList<Object> list) {
|
||||
|
||||
if (eventData.length - dataOffset < 1)
|
||||
return -1;
|
||||
|
||||
int offset = dataOffset;
|
||||
|
||||
int type = eventData[offset++];
|
||||
|
||||
//fprintf(stderr, "--- type=%d (rem len=%d)\n", type, eventDataLen);
|
||||
|
||||
switch (type) {
|
||||
case EVENT_TYPE_INT: { /* 32-bit signed int */
|
||||
int ival;
|
||||
|
||||
if (eventData.length - offset < 4)
|
||||
return -1;
|
||||
ival = ArrayHelper.swap32bitFromArray(eventData, offset);
|
||||
offset += 4;
|
||||
|
||||
list.add(new Integer(ival));
|
||||
}
|
||||
break;
|
||||
case EVENT_TYPE_LONG: { /* 64-bit signed long */
|
||||
long lval;
|
||||
|
||||
if (eventData.length - offset < 8)
|
||||
return -1;
|
||||
lval = ArrayHelper.swap64bitFromArray(eventData, offset);
|
||||
offset += 8;
|
||||
|
||||
list.add(new Long(lval));
|
||||
}
|
||||
break;
|
||||
case EVENT_TYPE_STRING: { /* UTF-8 chars, not NULL-terminated */
|
||||
int strLen;
|
||||
|
||||
if (eventData.length - offset < 4)
|
||||
return -1;
|
||||
strLen = ArrayHelper.swap32bitFromArray(eventData, offset);
|
||||
offset += 4;
|
||||
|
||||
if (eventData.length - offset < strLen)
|
||||
return -1;
|
||||
|
||||
// get the string
|
||||
try {
|
||||
String str = new String(eventData, offset, strLen, "UTF-8"); //$NON-NLS-1$
|
||||
list.add(str);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
}
|
||||
offset += strLen;
|
||||
break;
|
||||
}
|
||||
case EVENT_TYPE_LIST: { /* N items, all different types */
|
||||
|
||||
if (eventData.length - offset < 1)
|
||||
return -1;
|
||||
|
||||
int count = eventData[offset++];
|
||||
|
||||
// make a new temp list
|
||||
ArrayList<Object> subList = new ArrayList<Object>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
int result = parseBinaryEvent(eventData, offset, subList);
|
||||
if (result == -1) {
|
||||
return result;
|
||||
}
|
||||
|
||||
offset += result;
|
||||
}
|
||||
|
||||
list.add(subList.toArray());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log.e("EventLogParser", //$NON-NLS-1$
|
||||
String.format("Unknown binary event type %1$d", type)); //$NON-NLS-1$
|
||||
return -1;
|
||||
}
|
||||
|
||||
return offset - dataOffset;
|
||||
}
|
||||
|
||||
private Object parseTextData(String data, int tagValue) {
|
||||
// first, get the description of what we're supposed to parse
|
||||
EventValueDescription[] desc = mValueDescriptionMap.get(tagValue);
|
||||
|
||||
if (desc == null) {
|
||||
// TODO parse and create string values.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (desc.length == 1) {
|
||||
return getObjectFromString(data, desc[0].getEventValueType());
|
||||
} else if (data.startsWith("[") && data.endsWith("]")) {
|
||||
data = data.substring(1, data.length() - 1);
|
||||
|
||||
// get each individual values as String
|
||||
String[] values = data.split(",");
|
||||
|
||||
if (tagValue == GcEventContainer.GC_EVENT_TAG) {
|
||||
// special case for the GC event!
|
||||
Object[] objects = new Object[2];
|
||||
|
||||
objects[0] = getObjectFromString(values[0], EventValueType.LONG);
|
||||
objects[1] = getObjectFromString(values[1], EventValueType.LONG);
|
||||
|
||||
return objects;
|
||||
} else {
|
||||
// must be the same number as the number of descriptors.
|
||||
if (values.length != desc.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Object[] objects = new Object[values.length];
|
||||
|
||||
for (int i = 0 ; i < desc.length ; i++) {
|
||||
Object obj = getObjectFromString(values[i], desc[i].getEventValueType());
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
objects[i] = obj;
|
||||
}
|
||||
|
||||
return objects;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private Object getObjectFromString(String value, EventValueType type) {
|
||||
try {
|
||||
switch (type) {
|
||||
case INT:
|
||||
return Integer.valueOf(value);
|
||||
case LONG:
|
||||
return Long.valueOf(value);
|
||||
case STRING:
|
||||
return value;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
// do nothing, we'll return null.
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recreates the event-log-tags at the specified file path.
|
||||
* @param filePath the file path to write the file.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void saveTags(String filePath) throws IOException {
|
||||
File destFile = new File(filePath);
|
||||
destFile.createNewFile();
|
||||
FileOutputStream fos = null;
|
||||
|
||||
try {
|
||||
|
||||
fos = new FileOutputStream(destFile);
|
||||
|
||||
for (Integer key : mTagMap.keySet()) {
|
||||
// get the tag name
|
||||
String tagName = mTagMap.get(key);
|
||||
|
||||
// get the value descriptions
|
||||
EventValueDescription[] descriptors = mValueDescriptionMap.get(key);
|
||||
|
||||
String line = null;
|
||||
if (descriptors != null) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(String.format("%1$d %2$s", key, tagName)); //$NON-NLS-1$
|
||||
boolean first = true;
|
||||
for (EventValueDescription evd : descriptors) {
|
||||
if (first) {
|
||||
sb.append(" ("); //$NON-NLS-1$
|
||||
first = false;
|
||||
} else {
|
||||
sb.append(",("); //$NON-NLS-1$
|
||||
}
|
||||
sb.append(evd.getName());
|
||||
sb.append("|"); //$NON-NLS-1$
|
||||
sb.append(evd.getEventValueType().getValue());
|
||||
sb.append("|"); //$NON-NLS-1$
|
||||
sb.append(evd.getValueType().getValue());
|
||||
sb.append("|)"); //$NON-NLS-1$
|
||||
}
|
||||
sb.append("\n"); //$NON-NLS-1$
|
||||
|
||||
line = sb.toString();
|
||||
} else {
|
||||
line = String.format("%1$d %2$s\n", key, tagName); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
byte[] buffer = line.getBytes();
|
||||
fos.write(buffer);
|
||||
}
|
||||
} finally {
|
||||
if (fos != null) {
|
||||
fos.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib.log;
|
||||
|
||||
import com.android.ddmlib.log.EventContainer.EventValueType;
|
||||
|
||||
|
||||
/**
|
||||
* Describes an {@link EventContainer} value.
|
||||
* <p/>
|
||||
* This is a stand-alone object, not linked to a particular Event. It describes the value, by
|
||||
* name, type ({@link EventValueType}), and (if needed) value unit ({@link ValueType}).
|
||||
* <p/>
|
||||
* The index of the value is not contained within this class, and is instead dependent on the
|
||||
* index of this particular object in the array of {@link EventValueDescription} returned by
|
||||
* {@link EventLogParser#getEventInfoMap()} when queried for a particular event tag.
|
||||
*
|
||||
*/
|
||||
public final class EventValueDescription {
|
||||
|
||||
/**
|
||||
* Represents the type of a numerical value. This is used to display values of vastly different
|
||||
* type/range in graphs.
|
||||
*/
|
||||
public static enum ValueType {
|
||||
NOT_APPLICABLE(0),
|
||||
OBJECTS(1),
|
||||
BYTES(2),
|
||||
MILLISECONDS(3),
|
||||
ALLOCATIONS(4),
|
||||
ID(5),
|
||||
PERCENT(6);
|
||||
|
||||
private int mValue;
|
||||
|
||||
/**
|
||||
* Checks that the {@link EventValueType} is compatible with the {@link ValueType}.
|
||||
* @param type the {@link EventValueType} to check.
|
||||
* @throws InvalidValueTypeException if the types are not compatible.
|
||||
*/
|
||||
public void checkType(EventValueType type) throws InvalidValueTypeException {
|
||||
if ((type != EventValueType.INT && type != EventValueType.LONG)
|
||||
&& this != NOT_APPLICABLE) {
|
||||
throw new InvalidValueTypeException(
|
||||
String.format("%1$s doesn't support type %2$s", type, this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ValueType} from an integer value, or <code>null</code> if no match
|
||||
* were found.
|
||||
* @param value the integer value.
|
||||
*/
|
||||
public static ValueType getValueType(int value) {
|
||||
for (ValueType type : values()) {
|
||||
if (type.mValue == value) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the integer value of the enum.
|
||||
*/
|
||||
public int getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString().toLowerCase();
|
||||
}
|
||||
|
||||
private ValueType(int value) {
|
||||
mValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
private String mName;
|
||||
private EventValueType mEventValueType;
|
||||
private ValueType mValueType;
|
||||
|
||||
/**
|
||||
* Builds a {@link EventValueDescription} with a name and a type.
|
||||
* <p/>
|
||||
* If the type is {@link EventValueType#INT} or {@link EventValueType#LONG}, the
|
||||
* {@link #mValueType} is set to {@link ValueType#BYTES} by default. It set to
|
||||
* {@link ValueType#NOT_APPLICABLE} for all other {@link EventValueType} values.
|
||||
* @param name
|
||||
* @param type
|
||||
*/
|
||||
EventValueDescription(String name, EventValueType type) {
|
||||
mName = name;
|
||||
mEventValueType = type;
|
||||
if (mEventValueType == EventValueType.INT || mEventValueType == EventValueType.LONG) {
|
||||
mValueType = ValueType.BYTES;
|
||||
} else {
|
||||
mValueType = ValueType.NOT_APPLICABLE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link EventValueDescription} with a name and a type, and a {@link ValueType}.
|
||||
* <p/>
|
||||
* @param name
|
||||
* @param type
|
||||
* @param valueType
|
||||
* @throws InvalidValueTypeException if type and valuetype are not compatible.
|
||||
*
|
||||
*/
|
||||
EventValueDescription(String name, EventValueType type, ValueType valueType)
|
||||
throws InvalidValueTypeException {
|
||||
mName = name;
|
||||
mEventValueType = type;
|
||||
mValueType = valueType;
|
||||
mValueType.checkType(mEventValueType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Name.
|
||||
*/
|
||||
public String getName() {
|
||||
return mName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link EventValueType}.
|
||||
*/
|
||||
public EventValueType getEventValueType() {
|
||||
return mEventValueType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link ValueType}.
|
||||
*/
|
||||
public ValueType getValueType() {
|
||||
return mValueType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (mValueType != ValueType.NOT_APPLICABLE) {
|
||||
return String.format("%1$s (%2$s, %3$s)", mName, mEventValueType.toString(),
|
||||
mValueType.toString());
|
||||
}
|
||||
|
||||
return String.format("%1$s (%2$s)", mName, mEventValueType.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value is of the proper type for this receiver.
|
||||
* @param value the value to check.
|
||||
* @return true if the value is of the proper type for this receiver.
|
||||
*/
|
||||
public boolean checkForType(Object value) {
|
||||
switch (mEventValueType) {
|
||||
case INT:
|
||||
return value instanceof Integer;
|
||||
case LONG:
|
||||
return value instanceof Long;
|
||||
case STRING:
|
||||
return value instanceof String;
|
||||
case LIST:
|
||||
return value instanceof Object[];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object of a valid type (based on the value returned by
|
||||
* {@link #getEventValueType()}) from a String value.
|
||||
* <p/>
|
||||
* IMPORTANT {@link EventValueType#LIST} and {@link EventValueType#TREE} are not
|
||||
* supported.
|
||||
* @param value the value of the object expressed as a string.
|
||||
* @return an object or null if the conversion could not be done.
|
||||
*/
|
||||
public Object getObjectFromString(String value) {
|
||||
switch (mEventValueType) {
|
||||
case INT:
|
||||
try {
|
||||
return Integer.valueOf(value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
case LONG:
|
||||
try {
|
||||
return Long.valueOf(value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
case STRING:
|
||||
return value;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib.log;
|
||||
|
||||
import com.android.ddmlib.log.EventValueDescription.ValueType;
|
||||
import com.android.ddmlib.log.LogReceiver.LogEntry;
|
||||
|
||||
/**
|
||||
* Custom Event Container for the Gc event since this event doesn't simply output data in
|
||||
* int or long format, but encodes several values on 4 longs.
|
||||
* <p/>
|
||||
* The array of {@link EventValueDescription}s parsed from the "event-log-tags" file must
|
||||
* be ignored, and instead, the array returned from {@link #getValueDescriptions()} must be used.
|
||||
*/
|
||||
final class GcEventContainer extends EventContainer {
|
||||
|
||||
public final static int GC_EVENT_TAG = 20001;
|
||||
|
||||
private String processId;
|
||||
private long gcTime;
|
||||
private long bytesFreed;
|
||||
private long objectsFreed;
|
||||
private long actualSize;
|
||||
private long allowedSize;
|
||||
private long softLimit;
|
||||
private long objectsAllocated;
|
||||
private long bytesAllocated;
|
||||
private long zActualSize;
|
||||
private long zAllowedSize;
|
||||
private long zObjectsAllocated;
|
||||
private long zBytesAllocated;
|
||||
private long dlmallocFootprint;
|
||||
private long mallinfoTotalAllocatedSpace;
|
||||
private long externalLimit;
|
||||
private long externalBytesAllocated;
|
||||
|
||||
GcEventContainer(LogEntry entry, int tag, Object data) {
|
||||
super(entry, tag, data);
|
||||
init(data);
|
||||
}
|
||||
|
||||
GcEventContainer(int tag, int pid, int tid, int sec, int nsec, Object data) {
|
||||
super(tag, pid, tid, sec, nsec, data);
|
||||
init(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data
|
||||
*/
|
||||
private void init(Object data) {
|
||||
if (data instanceof Object[]) {
|
||||
Object[] values = (Object[])data;
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
if (values[i] instanceof Long) {
|
||||
parseDvmHeapInfo((Long)values[i], i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventValueType getType() {
|
||||
return EventValueType.LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean testValue(int index, Object value, CompareMethod compareMethod)
|
||||
throws InvalidTypeException {
|
||||
// do a quick easy check on the type.
|
||||
if (index == 0) {
|
||||
if ((value instanceof String) == false) {
|
||||
throw new InvalidTypeException();
|
||||
}
|
||||
} else if ((value instanceof Long) == false) {
|
||||
throw new InvalidTypeException();
|
||||
}
|
||||
|
||||
switch (compareMethod) {
|
||||
case EQUAL_TO:
|
||||
if (index == 0) {
|
||||
return processId.equals(value);
|
||||
} else {
|
||||
return getValueAsLong(index) == ((Long)value).longValue();
|
||||
}
|
||||
case LESSER_THAN:
|
||||
return getValueAsLong(index) <= ((Long)value).longValue();
|
||||
case LESSER_THAN_STRICT:
|
||||
return getValueAsLong(index) < ((Long)value).longValue();
|
||||
case GREATER_THAN:
|
||||
return getValueAsLong(index) >= ((Long)value).longValue();
|
||||
case GREATER_THAN_STRICT:
|
||||
return getValueAsLong(index) > ((Long)value).longValue();
|
||||
case BIT_CHECK:
|
||||
return (getValueAsLong(index) & ((Long)value).longValue()) != 0;
|
||||
}
|
||||
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(int valueIndex) {
|
||||
if (valueIndex == 0) {
|
||||
return processId;
|
||||
}
|
||||
|
||||
try {
|
||||
return new Long(getValueAsLong(valueIndex));
|
||||
} catch (InvalidTypeException e) {
|
||||
// this would only happened if valueIndex was 0, which we test above.
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getValueAsDouble(int valueIndex) throws InvalidTypeException {
|
||||
return (double)getValueAsLong(valueIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValueAsString(int valueIndex) {
|
||||
switch (valueIndex) {
|
||||
case 0:
|
||||
return processId;
|
||||
default:
|
||||
try {
|
||||
return Long.toString(getValueAsLong(valueIndex));
|
||||
} catch (InvalidTypeException e) {
|
||||
// we shouldn't stop there since we test, in this method first.
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a custom array of {@link EventValueDescription} since the actual content of this
|
||||
* event (list of (long, long) does not match the values encoded into those longs.
|
||||
*/
|
||||
static EventValueDescription[] getValueDescriptions() {
|
||||
try {
|
||||
return new EventValueDescription[] {
|
||||
new EventValueDescription("Process Name", EventValueType.STRING),
|
||||
new EventValueDescription("GC Time", EventValueType.LONG,
|
||||
ValueType.MILLISECONDS),
|
||||
new EventValueDescription("Freed Objects", EventValueType.LONG,
|
||||
ValueType.OBJECTS),
|
||||
new EventValueDescription("Freed Bytes", EventValueType.LONG, ValueType.BYTES),
|
||||
new EventValueDescription("Soft Limit", EventValueType.LONG, ValueType.BYTES),
|
||||
new EventValueDescription("Actual Size (aggregate)", EventValueType.LONG,
|
||||
ValueType.BYTES),
|
||||
new EventValueDescription("Allowed Size (aggregate)", EventValueType.LONG,
|
||||
ValueType.BYTES),
|
||||
new EventValueDescription("Allocated Objects (aggregate)",
|
||||
EventValueType.LONG, ValueType.OBJECTS),
|
||||
new EventValueDescription("Allocated Bytes (aggregate)", EventValueType.LONG,
|
||||
ValueType.BYTES),
|
||||
new EventValueDescription("Actual Size", EventValueType.LONG, ValueType.BYTES),
|
||||
new EventValueDescription("Allowed Size", EventValueType.LONG, ValueType.BYTES),
|
||||
new EventValueDescription("Allocated Objects", EventValueType.LONG,
|
||||
ValueType.OBJECTS),
|
||||
new EventValueDescription("Allocated Bytes", EventValueType.LONG,
|
||||
ValueType.BYTES),
|
||||
new EventValueDescription("Actual Size (zygote)", EventValueType.LONG,
|
||||
ValueType.BYTES),
|
||||
new EventValueDescription("Allowed Size (zygote)", EventValueType.LONG,
|
||||
ValueType.BYTES),
|
||||
new EventValueDescription("Allocated Objects (zygote)", EventValueType.LONG,
|
||||
ValueType.OBJECTS),
|
||||
new EventValueDescription("Allocated Bytes (zygote)", EventValueType.LONG,
|
||||
ValueType.BYTES),
|
||||
new EventValueDescription("External Allocation Limit", EventValueType.LONG,
|
||||
ValueType.BYTES),
|
||||
new EventValueDescription("External Bytes Allocated", EventValueType.LONG,
|
||||
ValueType.BYTES),
|
||||
new EventValueDescription("dlmalloc Footprint", EventValueType.LONG,
|
||||
ValueType.BYTES),
|
||||
new EventValueDescription("Malloc Info: Total Allocated Space",
|
||||
EventValueType.LONG, ValueType.BYTES),
|
||||
};
|
||||
} catch (InvalidValueTypeException e) {
|
||||
// this shouldn't happen since we control manual the EventValueType and the ValueType
|
||||
// values. For development purpose, we assert if this happens.
|
||||
assert false;
|
||||
}
|
||||
|
||||
// this shouldn't happen, but the compiler complains otherwise.
|
||||
return null;
|
||||
}
|
||||
|
||||
private void parseDvmHeapInfo(long data, int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
// [63 ] Must be zero
|
||||
// [62-24] ASCII process identifier
|
||||
// [23-12] GC time in ms
|
||||
// [11- 0] Bytes freed
|
||||
|
||||
gcTime = float12ToInt((int)((data >> 12) & 0xFFFL));
|
||||
bytesFreed = float12ToInt((int)(data & 0xFFFL));
|
||||
|
||||
// convert the long into an array, in the proper order so that we can convert the
|
||||
// first 5 char into a string.
|
||||
byte[] dataArray = new byte[8];
|
||||
put64bitsToArray(data, dataArray, 0);
|
||||
|
||||
// get the name from the string
|
||||
processId = new String(dataArray, 0, 5);
|
||||
break;
|
||||
case 1:
|
||||
// [63-62] 10
|
||||
// [61-60] Reserved; must be zero
|
||||
// [59-48] Objects freed
|
||||
// [47-36] Actual size (current footprint)
|
||||
// [35-24] Allowed size (current hard max)
|
||||
// [23-12] Objects allocated
|
||||
// [11- 0] Bytes allocated
|
||||
objectsFreed = float12ToInt((int)((data >> 48) & 0xFFFL));
|
||||
actualSize = float12ToInt((int)((data >> 36) & 0xFFFL));
|
||||
allowedSize = float12ToInt((int)((data >> 24) & 0xFFFL));
|
||||
objectsAllocated = float12ToInt((int)((data >> 12) & 0xFFFL));
|
||||
bytesAllocated = float12ToInt((int)(data & 0xFFFL));
|
||||
break;
|
||||
case 2:
|
||||
// [63-62] 11
|
||||
// [61-60] Reserved; must be zero
|
||||
// [59-48] Soft limit (current soft max)
|
||||
// [47-36] Actual size (current footprint)
|
||||
// [35-24] Allowed size (current hard max)
|
||||
// [23-12] Objects allocated
|
||||
// [11- 0] Bytes allocated
|
||||
softLimit = float12ToInt((int)((data >> 48) & 0xFFFL));
|
||||
zActualSize = float12ToInt((int)((data >> 36) & 0xFFFL));
|
||||
zAllowedSize = float12ToInt((int)((data >> 24) & 0xFFFL));
|
||||
zObjectsAllocated = float12ToInt((int)((data >> 12) & 0xFFFL));
|
||||
zBytesAllocated = float12ToInt((int)(data & 0xFFFL));
|
||||
break;
|
||||
case 3:
|
||||
// [63-48] Reserved; must be zero
|
||||
// [47-36] dlmallocFootprint
|
||||
// [35-24] mallinfo: total allocated space
|
||||
// [23-12] External byte limit
|
||||
// [11- 0] External bytes allocated
|
||||
dlmallocFootprint = float12ToInt((int)((data >> 36) & 0xFFFL));
|
||||
mallinfoTotalAllocatedSpace = float12ToInt((int)((data >> 24) & 0xFFFL));
|
||||
externalLimit = float12ToInt((int)((data >> 12) & 0xFFFL));
|
||||
externalBytesAllocated = float12ToInt((int)(data & 0xFFFL));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a 12 bit float representation into an unsigned int (returned as a long)
|
||||
* @param f12
|
||||
*/
|
||||
private static long float12ToInt(int f12) {
|
||||
return (f12 & 0x1FF) << ((f12 >>> 9) * 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* puts an unsigned value in an array.
|
||||
* @param value The value to put.
|
||||
* @param dest the destination array
|
||||
* @param offset the offset in the array where to put the value.
|
||||
* Array length must be at least offset + 8
|
||||
*/
|
||||
private static void put64bitsToArray(long value, byte[] dest, int offset) {
|
||||
dest[offset + 7] = (byte)(value & 0x00000000000000FFL);
|
||||
dest[offset + 6] = (byte)((value & 0x000000000000FF00L) >> 8);
|
||||
dest[offset + 5] = (byte)((value & 0x0000000000FF0000L) >> 16);
|
||||
dest[offset + 4] = (byte)((value & 0x00000000FF000000L) >> 24);
|
||||
dest[offset + 3] = (byte)((value & 0x000000FF00000000L) >> 32);
|
||||
dest[offset + 2] = (byte)((value & 0x0000FF0000000000L) >> 40);
|
||||
dest[offset + 1] = (byte)((value & 0x00FF000000000000L) >> 48);
|
||||
dest[offset + 0] = (byte)((value & 0xFF00000000000000L) >> 56);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the long value of the <code>valueIndex</code>-th value.
|
||||
* @param valueIndex the index of the value.
|
||||
* @throws InvalidTypeException if index is 0 as it is a string value.
|
||||
*/
|
||||
private final long getValueAsLong(int valueIndex) throws InvalidTypeException {
|
||||
switch (valueIndex) {
|
||||
case 0:
|
||||
throw new InvalidTypeException();
|
||||
case 1:
|
||||
return gcTime;
|
||||
case 2:
|
||||
return objectsFreed;
|
||||
case 3:
|
||||
return bytesFreed;
|
||||
case 4:
|
||||
return softLimit;
|
||||
case 5:
|
||||
return actualSize;
|
||||
case 6:
|
||||
return allowedSize;
|
||||
case 7:
|
||||
return objectsAllocated;
|
||||
case 8:
|
||||
return bytesAllocated;
|
||||
case 9:
|
||||
return actualSize - zActualSize;
|
||||
case 10:
|
||||
return allowedSize - zAllowedSize;
|
||||
case 11:
|
||||
return objectsAllocated - zObjectsAllocated;
|
||||
case 12:
|
||||
return bytesAllocated - zBytesAllocated;
|
||||
case 13:
|
||||
return zActualSize;
|
||||
case 14:
|
||||
return zAllowedSize;
|
||||
case 15:
|
||||
return zObjectsAllocated;
|
||||
case 16:
|
||||
return zBytesAllocated;
|
||||
case 17:
|
||||
return externalLimit;
|
||||
case 18:
|
||||
return externalBytesAllocated;
|
||||
case 19:
|
||||
return dlmallocFootprint;
|
||||
case 20:
|
||||
return mallinfoTotalAllocatedSpace;
|
||||
}
|
||||
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib.log;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Exception thrown when accessing an {@link EventContainer} value with the wrong type.
|
||||
*/
|
||||
public final class InvalidTypeException extends Exception {
|
||||
|
||||
/**
|
||||
* Needed by {@link Serializable}.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the default detail message.
|
||||
* @see java.lang.Exception
|
||||
*/
|
||||
public InvalidTypeException() {
|
||||
super("Invalid Type");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message.
|
||||
* @param message the detail message. The detail message is saved for later retrieval
|
||||
* by the {@link Throwable#getMessage()} method.
|
||||
* @see java.lang.Exception
|
||||
*/
|
||||
public InvalidTypeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified cause and a detail message of
|
||||
* <code>(cause==null ? null : cause.toString())</code> (which typically contains
|
||||
* the class and detail message of cause).
|
||||
* @param cause the cause (which is saved for later retrieval by the
|
||||
* {@link Throwable#getCause()} method). (A <code>null</code> value is permitted,
|
||||
* and indicates that the cause is nonexistent or unknown.)
|
||||
* @see java.lang.Exception
|
||||
*/
|
||||
public InvalidTypeException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message and cause.
|
||||
* @param message the detail message. The detail message is saved for later retrieval
|
||||
* by the {@link Throwable#getMessage()} method.
|
||||
* @param cause the cause (which is saved for later retrieval by the
|
||||
* {@link Throwable#getCause()} method). (A <code>null</code> value is permitted,
|
||||
* and indicates that the cause is nonexistent or unknown.)
|
||||
* @see java.lang.Exception
|
||||
*/
|
||||
public InvalidTypeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib.log;
|
||||
|
||||
import com.android.ddmlib.log.EventContainer.EventValueType;
|
||||
import com.android.ddmlib.log.EventValueDescription.ValueType;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Exception thrown when associating an {@link EventValueType} with an incompatible
|
||||
* {@link ValueType}.
|
||||
*/
|
||||
public final class InvalidValueTypeException extends Exception {
|
||||
|
||||
/**
|
||||
* Needed by {@link Serializable}.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the default detail message.
|
||||
* @see java.lang.Exception
|
||||
*/
|
||||
public InvalidValueTypeException() {
|
||||
super("Invalid Type");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message.
|
||||
* @param message the detail message. The detail message is saved for later retrieval
|
||||
* by the {@link Throwable#getMessage()} method.
|
||||
* @see java.lang.Exception
|
||||
*/
|
||||
public InvalidValueTypeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified cause and a detail message of
|
||||
* <code>(cause==null ? null : cause.toString())</code> (which typically contains
|
||||
* the class and detail message of cause).
|
||||
* @param cause the cause (which is saved for later retrieval by the
|
||||
* {@link Throwable#getCause()} method). (A <code>null</code> value is permitted,
|
||||
* and indicates that the cause is nonexistent or unknown.)
|
||||
* @see java.lang.Exception
|
||||
*/
|
||||
public InvalidValueTypeException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new exception with the specified detail message and cause.
|
||||
* @param message the detail message. The detail message is saved for later retrieval
|
||||
* by the {@link Throwable#getMessage()} method.
|
||||
* @param cause the cause (which is saved for later retrieval by the
|
||||
* {@link Throwable#getCause()} method). (A <code>null</code> value is permitted,
|
||||
* and indicates that the cause is nonexistent or unknown.)
|
||||
* @see java.lang.Exception
|
||||
*/
|
||||
public InvalidValueTypeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib.log;
|
||||
|
||||
|
||||
import com.android.ddmlib.utils.ArrayHelper;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
|
||||
/**
|
||||
* Receiver able to provide low level parsing for device-side log services.
|
||||
*/
|
||||
public final class LogReceiver {
|
||||
|
||||
private final static int ENTRY_HEADER_SIZE = 20; // 2*2 + 4*4; see LogEntry.
|
||||
|
||||
/**
|
||||
* Represents a log entry and its raw data.
|
||||
*/
|
||||
public final static class LogEntry {
|
||||
/*
|
||||
* See //device/include/utils/logger.h
|
||||
*/
|
||||
/** 16bit unsigned: length of the payload. */
|
||||
public int len; /* This is normally followed by a 16 bit padding */
|
||||
/** pid of the process that generated this {@link LogEntry} */
|
||||
public int pid;
|
||||
/** tid of the process that generated this {@link LogEntry} */
|
||||
public int tid;
|
||||
/** Seconds since epoch. */
|
||||
public int sec;
|
||||
/** nanoseconds. */
|
||||
public int nsec;
|
||||
/** The entry's raw data. */
|
||||
public byte[] data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Classes which implement this interface provide a method that deals
|
||||
* with {@link LogEntry} objects coming from log service through a {@link LogReceiver}.
|
||||
* <p/>This interface provides two methods.
|
||||
* <ul>
|
||||
* <li>{@link #newEntry(com.android.ddmlib.log.LogReceiver.LogEntry)} provides a
|
||||
* first level of parsing, extracting {@link LogEntry} objects out of the log service output.</li>
|
||||
* <li>{@link #newData(byte[], int, int)} provides a way to receive the raw information
|
||||
* coming directly from the log service.</li>
|
||||
* </ul>
|
||||
*/
|
||||
public interface ILogListener {
|
||||
/**
|
||||
* Sent when a new {@link LogEntry} has been parsed by the {@link LogReceiver}.
|
||||
* @param entry the new log entry.
|
||||
*/
|
||||
public void newEntry(LogEntry entry);
|
||||
|
||||
/**
|
||||
* Sent when new raw data is coming from the log service.
|
||||
* @param data the raw data buffer.
|
||||
* @param offset the offset into the buffer signaling the beginning of the new data.
|
||||
* @param length the length of the new data.
|
||||
*/
|
||||
public void newData(byte[] data, int offset, int length);
|
||||
}
|
||||
|
||||
/** Current {@link LogEntry} being read, before sending it to the listener. */
|
||||
private LogEntry mCurrentEntry;
|
||||
|
||||
/** Temp buffer to store partial entry headers. */
|
||||
private byte[] mEntryHeaderBuffer = new byte[ENTRY_HEADER_SIZE];
|
||||
/** Offset in the partial header buffer */
|
||||
private int mEntryHeaderOffset = 0;
|
||||
/** Offset in the partial entry data */
|
||||
private int mEntryDataOffset = 0;
|
||||
|
||||
/** Listener waiting for receive fully read {@link LogEntry} objects */
|
||||
private ILogListener mListener;
|
||||
|
||||
private boolean mIsCancelled = false;
|
||||
|
||||
/**
|
||||
* Creates a {@link LogReceiver} with an {@link ILogListener}.
|
||||
* <p/>
|
||||
* The {@link ILogListener} will receive new log entries as they are parsed, in the form
|
||||
* of {@link LogEntry} objects.
|
||||
* @param listener the listener to receive new log entries.
|
||||
*/
|
||||
public LogReceiver(ILogListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses new data coming from the log service.
|
||||
* @param data the data buffer
|
||||
* @param offset the offset into the buffer signaling the beginning of the new data.
|
||||
* @param length the length of the new data.
|
||||
*/
|
||||
public void parseNewData(byte[] data, int offset, int length) {
|
||||
// notify the listener of new raw data
|
||||
if (mListener != null) {
|
||||
mListener.newData(data, offset, length);
|
||||
}
|
||||
|
||||
// loop while there is still data to be read and the receiver has not be cancelled.
|
||||
while (length > 0 && mIsCancelled == false) {
|
||||
// first check if we have no current entry.
|
||||
if (mCurrentEntry == null) {
|
||||
if (mEntryHeaderOffset + length < ENTRY_HEADER_SIZE) {
|
||||
// if we don't have enough data to finish the header, save
|
||||
// the data we have and return
|
||||
System.arraycopy(data, offset, mEntryHeaderBuffer, mEntryHeaderOffset, length);
|
||||
mEntryHeaderOffset += length;
|
||||
return;
|
||||
} else {
|
||||
// we have enough to fill the header, let's do it.
|
||||
// did we store some part at the beginning of the header?
|
||||
if (mEntryHeaderOffset != 0) {
|
||||
// copy the rest of the entry header into the header buffer
|
||||
int size = ENTRY_HEADER_SIZE - mEntryHeaderOffset;
|
||||
System.arraycopy(data, offset, mEntryHeaderBuffer, mEntryHeaderOffset,
|
||||
size);
|
||||
|
||||
// create the entry from the header buffer
|
||||
mCurrentEntry = createEntry(mEntryHeaderBuffer, 0);
|
||||
|
||||
// since we used the whole entry header buffer, we reset the offset
|
||||
mEntryHeaderOffset = 0;
|
||||
|
||||
// adjust current offset and remaining length to the beginning
|
||||
// of the entry data
|
||||
offset += size;
|
||||
length -= size;
|
||||
} else {
|
||||
// create the entry directly from the data array
|
||||
mCurrentEntry = createEntry(data, offset);
|
||||
|
||||
// adjust current offset and remaining length to the beginning
|
||||
// of the entry data
|
||||
offset += ENTRY_HEADER_SIZE;
|
||||
length -= ENTRY_HEADER_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// at this point, we have an entry, and offset/length have been updated to skip
|
||||
// the entry header.
|
||||
|
||||
// if we have enough data for this entry or more, we'll need to end this entry
|
||||
if (length >= mCurrentEntry.len - mEntryDataOffset) {
|
||||
// compute and save the size of the data that we have to read for this entry,
|
||||
// based on how much we may already have read.
|
||||
int dataSize = mCurrentEntry.len - mEntryDataOffset;
|
||||
|
||||
// we only read what we need, and put it in the entry buffer.
|
||||
System.arraycopy(data, offset, mCurrentEntry.data, mEntryDataOffset, dataSize);
|
||||
|
||||
// notify the listener of a new entry
|
||||
if (mListener != null) {
|
||||
mListener.newEntry(mCurrentEntry);
|
||||
}
|
||||
|
||||
// reset some flags: we have read 0 data of the current entry.
|
||||
// and we have no current entry being read.
|
||||
mEntryDataOffset = 0;
|
||||
mCurrentEntry = null;
|
||||
|
||||
// and update the data buffer info to the end of the current entry / start
|
||||
// of the next one.
|
||||
offset += dataSize;
|
||||
length -= dataSize;
|
||||
} else {
|
||||
// we don't have enough data to fill this entry, so we store what we have
|
||||
// in the entry itself.
|
||||
System.arraycopy(data, offset, mCurrentEntry.data, mEntryDataOffset, length);
|
||||
|
||||
// save the amount read for the data.
|
||||
mEntryDataOffset += length;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this receiver is canceling the remote service.
|
||||
*/
|
||||
public boolean isCancelled() {
|
||||
return mIsCancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the current remote service.
|
||||
*/
|
||||
public void cancel() {
|
||||
mIsCancelled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link LogEntry} from the array of bytes. This expects the data buffer size
|
||||
* to be at least <code>offset + {@link #ENTRY_HEADER_SIZE}</code>.
|
||||
* @param data the data buffer the entry is read from.
|
||||
* @param offset the offset of the first byte from the buffer representing the entry.
|
||||
* @return a new {@link LogEntry} or <code>null</code> if some error happened.
|
||||
*/
|
||||
private LogEntry createEntry(byte[] data, int offset) {
|
||||
if (data.length < offset + ENTRY_HEADER_SIZE) {
|
||||
throw new InvalidParameterException(
|
||||
"Buffer not big enough to hold full LoggerEntry header");
|
||||
}
|
||||
|
||||
// create the new entry and fill it.
|
||||
LogEntry entry = new LogEntry();
|
||||
entry.len = ArrayHelper.swapU16bitFromArray(data, offset);
|
||||
|
||||
// we've read only 16 bits, but since there's also a 16 bit padding,
|
||||
// we can skip right over both.
|
||||
offset += 4;
|
||||
|
||||
entry.pid = ArrayHelper.swap32bitFromArray(data, offset);
|
||||
offset += 4;
|
||||
entry.tid = ArrayHelper.swap32bitFromArray(data, offset);
|
||||
offset += 4;
|
||||
entry.sec = ArrayHelper.swap32bitFromArray(data, offset);
|
||||
offset += 4;
|
||||
entry.nsec = ArrayHelper.swap32bitFromArray(data, offset);
|
||||
offset += 4;
|
||||
|
||||
// allocate the data
|
||||
entry.data = new byte[entry.len];
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib.testrunner;
|
||||
|
||||
/**
|
||||
* Receives event notifications during instrumentation test runs.
|
||||
* Patterned after {@link junit.runner.TestRunListener}.
|
||||
*/
|
||||
public interface ITestRunListener {
|
||||
|
||||
/**
|
||||
* Types of test failures.
|
||||
*/
|
||||
enum TestFailure {
|
||||
/** Test failed due to unanticipated uncaught exception. */
|
||||
ERROR,
|
||||
/** Test failed due to a false assertion. */
|
||||
FAILURE
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the start of a test run.
|
||||
*
|
||||
* @param testCount total number of tests in test run
|
||||
*/
|
||||
public void testRunStarted(int testCount);
|
||||
|
||||
/**
|
||||
* Reports end of test run.
|
||||
*
|
||||
* @param elapsedTime device reported elapsed time, in milliseconds
|
||||
*/
|
||||
public void testRunEnded(long elapsedTime);
|
||||
|
||||
/**
|
||||
* Reports test run stopped before completion.
|
||||
*
|
||||
* @param elapsedTime device reported elapsed time, in milliseconds
|
||||
*/
|
||||
public void testRunStopped(long elapsedTime);
|
||||
|
||||
/**
|
||||
* Reports the start of an individual test case.
|
||||
*
|
||||
* @param test identifies the test
|
||||
*/
|
||||
public void testStarted(TestIdentifier test);
|
||||
|
||||
/**
|
||||
* Reports the execution end of an individual test case.
|
||||
* If {@link #testFailed} was not invoked, this test passed.
|
||||
*
|
||||
* @param test identifies the test
|
||||
*/
|
||||
public void testEnded(TestIdentifier test);
|
||||
|
||||
/**
|
||||
* Reports the failure of a individual test case.
|
||||
* Will be called between testStarted and testEnded.
|
||||
*
|
||||
* @param status failure type
|
||||
* @param test identifies the test
|
||||
* @param trace stack trace of failure
|
||||
*/
|
||||
public void testFailed(TestFailure status, TestIdentifier test, String trace);
|
||||
|
||||
/**
|
||||
* Reports test run failed to execute due to a fatal error.
|
||||
*/
|
||||
public void testRunFailed(String errorMessage);
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib.testrunner;
|
||||
|
||||
import com.android.ddmlib.IShellOutputReceiver;
|
||||
import com.android.ddmlib.Log;
|
||||
import com.android.ddmlib.MultiLineReceiver;
|
||||
|
||||
/**
|
||||
* Parses the 'raw output mode' results of an instrumentation test run from shell and informs a
|
||||
* ITestRunListener of the results.
|
||||
*
|
||||
* <p>Expects the following output:
|
||||
*
|
||||
* <p>If fatal error occurred when attempted to run the tests:
|
||||
* <pre> INSTRUMENTATION_FAILED: </pre>
|
||||
*
|
||||
* <p>Otherwise, expect a series of test results, each one containing a set of status key/value
|
||||
* pairs, delimited by a start(1)/pass(0)/fail(-2)/error(-1) status code result. At end of test
|
||||
* run, expects that the elapsed test time in seconds will be displayed
|
||||
*
|
||||
* <p>For example:
|
||||
* <pre>
|
||||
* INSTRUMENTATION_STATUS_CODE: 1
|
||||
* INSTRUMENTATION_STATUS: class=com.foo.FooTest
|
||||
* INSTRUMENTATION_STATUS: test=testFoo
|
||||
* INSTRUMENTATION_STATUS: numtests=2
|
||||
* INSTRUMENTATION_STATUS: stack=com.foo.FooTest#testFoo:312
|
||||
* com.foo.X
|
||||
* INSTRUMENTATION_STATUS_CODE: -2
|
||||
* ...
|
||||
*
|
||||
* Time: X
|
||||
* </pre>
|
||||
* <p>Note that the "value" portion of the key-value pair may wrap over several text lines
|
||||
*/
|
||||
public class InstrumentationResultParser extends MultiLineReceiver {
|
||||
|
||||
/** Relevant test status keys. */
|
||||
private static class StatusKeys {
|
||||
private static final String TEST = "test";
|
||||
private static final String CLASS = "class";
|
||||
private static final String STACK = "stack";
|
||||
private static final String NUMTESTS = "numtests";
|
||||
}
|
||||
|
||||
/** Test result status codes. */
|
||||
private static class StatusCodes {
|
||||
private static final int FAILURE = -2;
|
||||
private static final int START = 1;
|
||||
private static final int ERROR = -1;
|
||||
private static final int OK = 0;
|
||||
}
|
||||
|
||||
/** Prefixes used to identify output. */
|
||||
private static class Prefixes {
|
||||
private static final String STATUS = "INSTRUMENTATION_STATUS: ";
|
||||
private static final String STATUS_CODE = "INSTRUMENTATION_STATUS_CODE: ";
|
||||
private static final String STATUS_FAILED = "INSTRUMENTATION_FAILED: ";
|
||||
private static final String TIME_REPORT = "Time: ";
|
||||
}
|
||||
|
||||
private final ITestRunListener mTestListener;
|
||||
|
||||
/**
|
||||
* Test result data
|
||||
*/
|
||||
private static class TestResult {
|
||||
private Integer mCode = null;
|
||||
private String mTestName = null;
|
||||
private String mTestClass = null;
|
||||
private String mStackTrace = null;
|
||||
private Integer mNumTests = null;
|
||||
|
||||
/** Returns true if all expected values have been parsed */
|
||||
boolean isComplete() {
|
||||
return mCode != null && mTestName != null && mTestClass != null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Stores the status values for the test result currently being parsed */
|
||||
private TestResult mCurrentTestResult = null;
|
||||
|
||||
/** Stores the current "key" portion of the status key-value being parsed. */
|
||||
private String mCurrentKey = null;
|
||||
|
||||
/** Stores the current "value" portion of the status key-value being parsed. */
|
||||
private StringBuilder mCurrentValue = null;
|
||||
|
||||
/** True if start of test has already been reported to listener. */
|
||||
private boolean mTestStartReported = false;
|
||||
|
||||
/** The elapsed time of the test run, in milliseconds. */
|
||||
private long mTestTime = 0;
|
||||
|
||||
/** True if current test run has been canceled by user. */
|
||||
private boolean mIsCancelled = false;
|
||||
|
||||
private static final String LOG_TAG = "InstrumentationResultParser";
|
||||
|
||||
/**
|
||||
* Creates the InstrumentationResultParser.
|
||||
*
|
||||
* @param listener informed of test results as the tests are executing
|
||||
*/
|
||||
public InstrumentationResultParser(ITestRunListener listener) {
|
||||
mTestListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the instrumentation test output from shell.
|
||||
*
|
||||
* @see MultiLineReceiver#processNewLines
|
||||
*/
|
||||
@Override
|
||||
public void processNewLines(String[] lines) {
|
||||
for (String line : lines) {
|
||||
parse(line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an individual output line. Expects a line that is one of:
|
||||
* <ul>
|
||||
* <li>
|
||||
* The start of a new status line (starts with Prefixes.STATUS or Prefixes.STATUS_CODE),
|
||||
* and thus there is a new key=value pair to parse, and the previous key-value pair is
|
||||
* finished.
|
||||
* </li>
|
||||
* <li>
|
||||
* A continuation of the previous status (the "value" portion of the key has wrapped
|
||||
* to the next line).
|
||||
* </li>
|
||||
* <li> A line reporting a fatal error in the test run (Prefixes.STATUS_FAILED) </li>
|
||||
* <li> A line reporting the total elapsed time of the test run. (Prefixes.TIME_REPORT) </li>
|
||||
* </ul>
|
||||
*
|
||||
* @param line Text output line
|
||||
*/
|
||||
private void parse(String line) {
|
||||
if (line.startsWith(Prefixes.STATUS_CODE)) {
|
||||
// Previous status key-value has been collected. Store it.
|
||||
submitCurrentKeyValue();
|
||||
parseStatusCode(line);
|
||||
} else if (line.startsWith(Prefixes.STATUS)) {
|
||||
// Previous status key-value has been collected. Store it.
|
||||
submitCurrentKeyValue();
|
||||
parseKey(line, Prefixes.STATUS.length());
|
||||
} else if (line.startsWith(Prefixes.STATUS_FAILED)) {
|
||||
Log.e(LOG_TAG, "test run failed " + line);
|
||||
mTestListener.testRunFailed(line);
|
||||
} else if (line.startsWith(Prefixes.TIME_REPORT)) {
|
||||
parseTime(line, Prefixes.TIME_REPORT.length());
|
||||
} else {
|
||||
if (mCurrentValue != null) {
|
||||
// this is a value that has wrapped to next line.
|
||||
mCurrentValue.append("\r\n");
|
||||
mCurrentValue.append(line);
|
||||
} else {
|
||||
Log.w(LOG_TAG, "unrecognized line " + line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the currently parsed key-value pair into mCurrentTestInfo.
|
||||
*/
|
||||
private void submitCurrentKeyValue() {
|
||||
if (mCurrentKey != null && mCurrentValue != null) {
|
||||
TestResult testInfo = getCurrentTestInfo();
|
||||
String statusValue = mCurrentValue.toString();
|
||||
|
||||
if (mCurrentKey.equals(StatusKeys.CLASS)) {
|
||||
testInfo.mTestClass = statusValue.trim();
|
||||
}
|
||||
else if (mCurrentKey.equals(StatusKeys.TEST)) {
|
||||
testInfo.mTestName = statusValue.trim();
|
||||
}
|
||||
else if (mCurrentKey.equals(StatusKeys.NUMTESTS)) {
|
||||
try {
|
||||
testInfo.mNumTests = Integer.parseInt(statusValue);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
Log.e(LOG_TAG, "Unexpected integer number of tests, received " + statusValue);
|
||||
}
|
||||
}
|
||||
else if (mCurrentKey.equals(StatusKeys.STACK)) {
|
||||
testInfo.mStackTrace = statusValue;
|
||||
}
|
||||
|
||||
mCurrentKey = null;
|
||||
mCurrentValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
private TestResult getCurrentTestInfo() {
|
||||
if (mCurrentTestResult == null) {
|
||||
mCurrentTestResult = new TestResult();
|
||||
}
|
||||
return mCurrentTestResult;
|
||||
}
|
||||
|
||||
private void clearCurrentTestInfo() {
|
||||
mCurrentTestResult = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the key from the current line.
|
||||
* Expects format of "key=value".
|
||||
*
|
||||
* @param line full line of text to parse
|
||||
* @param keyStartPos the starting position of the key in the given line
|
||||
*/
|
||||
private void parseKey(String line, int keyStartPos) {
|
||||
int endKeyPos = line.indexOf('=', keyStartPos);
|
||||
if (endKeyPos != -1) {
|
||||
mCurrentKey = line.substring(keyStartPos, endKeyPos).trim();
|
||||
parseValue(line, endKeyPos+1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the start of a key=value pair.
|
||||
*
|
||||
* @param line - full line of text to parse
|
||||
* @param valueStartPos - the starting position of the value in the given line
|
||||
*/
|
||||
private void parseValue(String line, int valueStartPos) {
|
||||
mCurrentValue = new StringBuilder();
|
||||
mCurrentValue.append(line.substring(valueStartPos));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses out a status code result.
|
||||
*/
|
||||
private void parseStatusCode(String line) {
|
||||
String value = line.substring(Prefixes.STATUS_CODE.length()).trim();
|
||||
TestResult testInfo = getCurrentTestInfo();
|
||||
try {
|
||||
testInfo.mCode = Integer.parseInt(value);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
Log.e(LOG_TAG, "Expected integer status code, received: " + value);
|
||||
}
|
||||
|
||||
// this means we're done with current test result bundle
|
||||
reportResult(testInfo);
|
||||
clearCurrentTestInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if test run canceled.
|
||||
*
|
||||
* @see IShellOutputReceiver#isCancelled()
|
||||
*/
|
||||
public boolean isCancelled() {
|
||||
return mIsCancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests cancellation of test run.
|
||||
*/
|
||||
public void cancel() {
|
||||
mIsCancelled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports a test result to the test run listener. Must be called when a individual test
|
||||
* result has been fully parsed.
|
||||
*
|
||||
* @param statusMap key-value status pairs of test result
|
||||
*/
|
||||
private void reportResult(TestResult testInfo) {
|
||||
if (!testInfo.isComplete()) {
|
||||
Log.e(LOG_TAG, "invalid instrumentation status bundle " + testInfo.toString());
|
||||
return;
|
||||
}
|
||||
reportTestRunStarted(testInfo);
|
||||
TestIdentifier testId = new TestIdentifier(testInfo.mTestClass, testInfo.mTestName);
|
||||
|
||||
switch (testInfo.mCode) {
|
||||
case StatusCodes.START:
|
||||
mTestListener.testStarted(testId);
|
||||
break;
|
||||
case StatusCodes.FAILURE:
|
||||
mTestListener.testFailed(ITestRunListener.TestFailure.FAILURE, testId,
|
||||
getTrace(testInfo));
|
||||
mTestListener.testEnded(testId);
|
||||
break;
|
||||
case StatusCodes.ERROR:
|
||||
mTestListener.testFailed(ITestRunListener.TestFailure.ERROR, testId,
|
||||
getTrace(testInfo));
|
||||
mTestListener.testEnded(testId);
|
||||
break;
|
||||
case StatusCodes.OK:
|
||||
mTestListener.testEnded(testId);
|
||||
break;
|
||||
default:
|
||||
Log.e(LOG_TAG, "Unknown status code received: " + testInfo.mCode);
|
||||
mTestListener.testEnded(testId);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the start of a test run, and the total test count, if it has not been previously
|
||||
* reported.
|
||||
*
|
||||
* @param testInfo current test status values
|
||||
*/
|
||||
private void reportTestRunStarted(TestResult testInfo) {
|
||||
// if start test run not reported yet
|
||||
if (!mTestStartReported && testInfo.mNumTests != null) {
|
||||
mTestListener.testRunStarted(testInfo.mNumTests);
|
||||
mTestStartReported = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stack trace of the current failed test, from the provided testInfo.
|
||||
*/
|
||||
private String getTrace(TestResult testInfo) {
|
||||
if (testInfo.mStackTrace != null) {
|
||||
return testInfo.mStackTrace;
|
||||
}
|
||||
else {
|
||||
Log.e(LOG_TAG, "Could not find stack trace for failed test ");
|
||||
return new Throwable("Unknown failure").toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses out and store the elapsed time.
|
||||
*/
|
||||
private void parseTime(String line, int startPos) {
|
||||
String timeString = line.substring(startPos);
|
||||
try {
|
||||
float timeSeconds = Float.parseFloat(timeString);
|
||||
mTestTime = (long)(timeSeconds * 1000);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
Log.e(LOG_TAG, "Unexpected time format " + timeString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by parent when adb session is complete.
|
||||
*/
|
||||
@Override
|
||||
public void done() {
|
||||
super.done();
|
||||
mTestListener.testRunEnded(mTestTime);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib.testrunner;
|
||||
|
||||
|
||||
import com.android.ddmlib.IDevice;
|
||||
import com.android.ddmlib.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Runs a Android test command remotely and reports results.
|
||||
*/
|
||||
public class RemoteAndroidTestRunner {
|
||||
|
||||
private static final char CLASS_SEPARATOR = ',';
|
||||
private static final char METHOD_SEPARATOR = '#';
|
||||
private static final char RUNNER_SEPARATOR = '/';
|
||||
private String mClassArg;
|
||||
private final String mPackageName;
|
||||
private final String mRunnerName;
|
||||
private String mExtraArgs;
|
||||
private boolean mLogOnlyMode;
|
||||
private IDevice mRemoteDevice;
|
||||
private InstrumentationResultParser mParser;
|
||||
|
||||
private static final String LOG_TAG = "RemoteAndroidTest";
|
||||
private static final String DEFAULT_RUNNER_NAME =
|
||||
"android.test.InstrumentationTestRunner";
|
||||
|
||||
/**
|
||||
* Creates a remote Android test runner.
|
||||
*
|
||||
* @param packageName the Android application package that contains the tests to run
|
||||
* @param runnerName the instrumentation test runner to execute. If null, will use default
|
||||
* runner
|
||||
* @param remoteDevice the Android device to execute tests on
|
||||
*/
|
||||
public RemoteAndroidTestRunner(String packageName,
|
||||
String runnerName,
|
||||
IDevice remoteDevice) {
|
||||
|
||||
mPackageName = packageName;
|
||||
mRunnerName = runnerName;
|
||||
mRemoteDevice = remoteDevice;
|
||||
mClassArg = null;
|
||||
mExtraArgs = "";
|
||||
mLogOnlyMode = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternate constructor. Uses default instrumentation runner.
|
||||
*
|
||||
* @param packageName the Android application package that contains the tests to run
|
||||
* @param remoteDevice the Android device to execute tests on
|
||||
*/
|
||||
public RemoteAndroidTestRunner(String packageName,
|
||||
IDevice remoteDevice) {
|
||||
this(packageName, null, remoteDevice);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the application package name.
|
||||
*/
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the runnerName.
|
||||
*/
|
||||
public String getRunnerName() {
|
||||
if (mRunnerName == null) {
|
||||
return DEFAULT_RUNNER_NAME;
|
||||
}
|
||||
return mRunnerName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the complete instrumentation component path.
|
||||
*/
|
||||
private String getRunnerPath() {
|
||||
return getPackageName() + RUNNER_SEPARATOR + getRunnerName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to run only tests in this class
|
||||
* Must be called before 'run'.
|
||||
*
|
||||
* @param className fully qualified class name (eg x.y.z)
|
||||
*/
|
||||
public void setClassName(String className) {
|
||||
mClassArg = className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to run only tests in the provided classes
|
||||
* Must be called before 'run'.
|
||||
* <p>
|
||||
* If providing more than one class, requires a InstrumentationTestRunner that supports
|
||||
* the multiple class argument syntax.
|
||||
*
|
||||
* @param classNames array of fully qualified class names (eg x.y.z)
|
||||
*/
|
||||
public void setClassNames(String[] classNames) {
|
||||
StringBuilder classArgBuilder = new StringBuilder();
|
||||
|
||||
for (int i=0; i < classNames.length; i++) {
|
||||
if (i != 0) {
|
||||
classArgBuilder.append(CLASS_SEPARATOR);
|
||||
}
|
||||
classArgBuilder.append(classNames[i]);
|
||||
}
|
||||
mClassArg = classArgBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets to run only specified test method
|
||||
* Must be called before 'run'.
|
||||
*
|
||||
* @param className fully qualified class name (eg x.y.z)
|
||||
* @param testName method name
|
||||
*/
|
||||
public void setMethodName(String className, String testName) {
|
||||
mClassArg = className + METHOD_SEPARATOR + testName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets extra arguments to include in instrumentation command.
|
||||
* Must be called before 'run'.
|
||||
*
|
||||
* @param instrumentationArgs must not be null
|
||||
*/
|
||||
public void setExtraArgs(String instrumentationArgs) {
|
||||
if (instrumentationArgs == null) {
|
||||
throw new IllegalArgumentException("instrumentationArgs cannot be null");
|
||||
}
|
||||
mExtraArgs = instrumentationArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extra instrumentation arguments.
|
||||
*/
|
||||
public String getExtraArgs() {
|
||||
return mExtraArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this test run to log only mode - skips test execution.
|
||||
*/
|
||||
public void setLogOnly(boolean logOnly) {
|
||||
mLogOnlyMode = logOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute this test run.
|
||||
*
|
||||
* @param listener listens for test results
|
||||
*/
|
||||
public void run(ITestRunListener listener) {
|
||||
final String runCaseCommandStr = "am instrument -w -r "
|
||||
+ getClassCmd() + " " + getLogCmd() + " " + getExtraArgs() + " " + getRunnerPath();
|
||||
Log.d(LOG_TAG, runCaseCommandStr);
|
||||
mParser = new InstrumentationResultParser(listener);
|
||||
|
||||
try {
|
||||
mRemoteDevice.executeShellCommand(runCaseCommandStr, mParser);
|
||||
} catch (IOException e) {
|
||||
Log.e(LOG_TAG, e);
|
||||
listener.testRunFailed(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests cancellation of this test run.
|
||||
*/
|
||||
public void cancel() {
|
||||
if (mParser != null) {
|
||||
mParser.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the test class argument.
|
||||
*/
|
||||
private String getClassArg() {
|
||||
return mClassArg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full instrumentation command which specifies the test classes to execute.
|
||||
* Returns an empty string if no classes were specified.
|
||||
*/
|
||||
private String getClassCmd() {
|
||||
String classArg = getClassArg();
|
||||
if (classArg != null) {
|
||||
return "-e class " + classArg;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full command to enable log only mode - if specified. Otherwise returns an
|
||||
* empty string.
|
||||
*/
|
||||
private String getLogCmd() {
|
||||
if (mLogOnlyMode) {
|
||||
return "-e log true";
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib.testrunner;
|
||||
|
||||
/**
|
||||
* Identifies a parsed instrumentation test
|
||||
*/
|
||||
public class TestIdentifier {
|
||||
|
||||
private final String mClassName;
|
||||
private final String mTestName;
|
||||
|
||||
/**
|
||||
* Creates a test identifier
|
||||
*
|
||||
* @param className fully qualified class name of the test. Cannot be null.
|
||||
* @param testName name of the test. Cannot be null.
|
||||
*/
|
||||
public TestIdentifier(String className, String testName) {
|
||||
if (className == null || testName == null) {
|
||||
throw new IllegalArgumentException("className and testName must " +
|
||||
"be non-null");
|
||||
}
|
||||
mClassName = className;
|
||||
mTestName = testName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fully qualified class name of the test
|
||||
*/
|
||||
public String getClassName() {
|
||||
return mClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the test
|
||||
*/
|
||||
public String getTestName() {
|
||||
return mTestName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests equality by comparing class and method name
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof TestIdentifier)) {
|
||||
return false;
|
||||
}
|
||||
TestIdentifier otherTest = (TestIdentifier)other;
|
||||
return getClassName().equals(otherTest.getClassName()) &&
|
||||
getTestName().equals(otherTest.getTestName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates hashCode based on class and method name.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getClassName().hashCode() * 31 + getTestName().hashCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.ddmlib.utils;
|
||||
|
||||
/**
|
||||
* Utility class providing array to int/long conversion for data received from devices through adb.
|
||||
*/
|
||||
public final class ArrayHelper {
|
||||
|
||||
/**
|
||||
* Swaps an unsigned value around, and puts the result in an array that can be sent to a device.
|
||||
* @param value The value to swap.
|
||||
* @param dest the destination array
|
||||
* @param offset the offset in the array where to put the swapped value.
|
||||
* Array length must be at least offset + 4
|
||||
*/
|
||||
public static void swap32bitsToArray(int value, byte[] dest, int offset) {
|
||||
dest[offset] = (byte)(value & 0x000000FF);
|
||||
dest[offset + 1] = (byte)((value & 0x0000FF00) >> 8);
|
||||
dest[offset + 2] = (byte)((value & 0x00FF0000) >> 16);
|
||||
dest[offset + 3] = (byte)((value & 0xFF000000) >> 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a signed 32 bit integer from an array coming from a device.
|
||||
* @param value the array containing the int
|
||||
* @param offset the offset in the array at which the int starts
|
||||
* @return the integer read from the array
|
||||
*/
|
||||
public static int swap32bitFromArray(byte[] value, int offset) {
|
||||
int v = 0;
|
||||
v |= ((int)value[offset]) & 0x000000FF;
|
||||
v |= (((int)value[offset + 1]) & 0x000000FF) << 8;
|
||||
v |= (((int)value[offset + 2]) & 0x000000FF) << 16;
|
||||
v |= (((int)value[offset + 3]) & 0x000000FF) << 24;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an unsigned 16 bit integer from an array coming from a device,
|
||||
* and returns it as an 'int'
|
||||
* @param value the array containing the 16 bit int (2 byte).
|
||||
* @param offset the offset in the array at which the int starts
|
||||
* Array length must be at least offset + 2
|
||||
* @return the integer read from the array.
|
||||
*/
|
||||
public static int swapU16bitFromArray(byte[] value, int offset) {
|
||||
int v = 0;
|
||||
v |= ((int)value[offset]) & 0x000000FF;
|
||||
v |= (((int)value[offset + 1]) & 0x000000FF) << 8;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a signed 64 bit integer from an array coming from a device.
|
||||
* @param value the array containing the int
|
||||
* @param offset the offset in the array at which the int starts
|
||||
* Array length must be at least offset + 8
|
||||
* @return the integer read from the array
|
||||
*/
|
||||
public static long swap64bitFromArray(byte[] value, int offset) {
|
||||
long v = 0;
|
||||
v |= ((long)value[offset]) & 0x00000000000000FFL;
|
||||
v |= (((long)value[offset + 1]) & 0x00000000000000FFL) << 8;
|
||||
v |= (((long)value[offset + 2]) & 0x00000000000000FFL) << 16;
|
||||
v |= (((long)value[offset + 3]) & 0x00000000000000FFL) << 24;
|
||||
v |= (((long)value[offset + 4]) & 0x00000000000000FFL) << 32;
|
||||
v |= (((long)value[offset + 5]) & 0x00000000000000FFL) << 40;
|
||||
v |= (((long)value[offset + 6]) & 0x00000000000000FFL) << 48;
|
||||
v |= (((long)value[offset + 7]) & 0x00000000000000FFL) << 56;
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user