/* * 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.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.lang.Thread.State; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * A connection to the host-side android debug bridge (adb) *
This is the central point to communicate with any devices, emulators, or the applications * running on them. * {@link #init(boolean)} must be called before anything is done. */ public final class AndroidDebugBridge { /* * Minimum and maximum version of adb supported. This correspond to * ADB_SERVER_VERSION found in //device/tools/adb/adb.h */ private final static int ADB_VERSION_MICRO_MIN = 20; private final static int ADB_VERSION_MICRO_MAX = -1; private final static Pattern sAdbVersion = Pattern.compile( "^.*(\\d+)\\.(\\d+)\\.(\\d+)$"); //$NON-NLS-1$ private final static String ADB = "adb"; //$NON-NLS-1$ private final static String DDMS = "ddms"; //$NON-NLS-1$ // Where to find the ADB bridge. final static String ADB_HOST = "127.0.0.1"; //$NON-NLS-1$ final static int ADB_PORT = 5037; static InetAddress sHostAddr; static InetSocketAddress sSocketAddr; static { // built-in local address/port for ADB. try { sHostAddr = InetAddress.getByName(ADB_HOST); sSocketAddr = new InetSocketAddress(sHostAddr, ADB_PORT); } catch (UnknownHostException e) { } } private static AndroidDebugBridge sThis; private static boolean sClientSupport; /** Full path to adb. */ private String mAdbOsLocation = null; private boolean mVersionCheck; private boolean mStarted = false; private DeviceMonitor mDeviceMonitor; private final static ArrayListddm library.
* This must be called once before any call to
* {@link #createBridge(String, boolean)}.
* The library can be initialized in 2 ways: *
true.false.ddmlib to connect a debugger to them.ddmlib which acts as a proxy between the debuggers and
* the applications to debug. See {@link Client#getDebuggerListenPort()}.
* The preferences of ddmlib should also be initialized with whatever default
* values were changed from the default values.
* When the application quits, {@link #terminate()} should be called.
* @param clientSupport Indicates whether the library should enable the monitoring and
* interaction with applications running on the devices.
* @see AndroidDebugBridge#createBridge(String, boolean)
* @see DdmPreferences
*/
public static void init(boolean clientSupport) {
sClientSupport = clientSupport;
MonitorThread monitorThread = MonitorThread.createInstance();
monitorThread.start();
HandleHello.register(monitorThread);
HandleAppName.register(monitorThread);
HandleTest.register(monitorThread);
HandleThread.register(monitorThread);
HandleHeap.register(monitorThread);
HandleWait.register(monitorThread);
HandleProfiling.register(monitorThread);
}
/**
* Terminates the ddm library. This must be called upon application termination.
*/
public static void terminate() {
// kill the monitoring services
if (sThis != null && sThis.mDeviceMonitor != null) {
sThis.mDeviceMonitor.stop();
sThis.mDeviceMonitor = null;
}
MonitorThread monitorThread = MonitorThread.getInstance();
if (monitorThread != null) {
monitorThread.quit();
}
}
/**
* Returns whether the ddmlib is setup to support monitoring and interacting with
* {@link Client}s running on the {@link IDevice}s.
*/
static boolean getClientSupport() {
return sClientSupport;
}
/**
* Creates a {@link AndroidDebugBridge} that is not linked to any particular executable.
* This bridge will expect adb to be running. It will not be able to start/stop/restart
* adb.
* If a bridge has already been started, it is directly returned with no changes (similar
* to calling {@link #getBridge()}).
* @return a connected bridge.
*/
public static AndroidDebugBridge createBridge() {
synchronized (sLock) {
if (sThis != null) {
return sThis;
}
try {
sThis = new AndroidDebugBridge();
sThis.start();
} catch (InvalidParameterException e) {
sThis = null;
}
// because the listeners could remove themselves from the list while processing
// their event callback, we make a copy of the list and iterate on it instead of
// the main list.
// This mostly happens when the application quits.
IDebugBridgeChangeListener[] listenersCopy = sBridgeListeners.toArray(
new IDebugBridgeChangeListener[sBridgeListeners.size()]);
// notify the listeners of the change
for (IDebugBridgeChangeListener listener : listenersCopy) {
// we attempt to catch any exception so that a bad listener doesn't kill our
// thread
try {
listener.bridgeChanged(sThis);
} catch (Exception e) {
Log.e(DDMS, e);
}
}
return sThis;
}
}
/**
* Creates a new debug bridge from the location of the command line tool.
*
* Any existing server will be disconnected, unless the location is the same and
* forceNewBridge is set to false.
* @param osLocation the location of the command line tool 'adb'
* @param forceNewBridge force creation of a new bridge even if one with the same location
* already exists.
* @return a connected bridge.
*/
public static AndroidDebugBridge createBridge(String osLocation, boolean forceNewBridge) {
synchronized (sLock) {
if (sThis != null) {
if (sThis.mAdbOsLocation != null && sThis.mAdbOsLocation.equals(osLocation) &&
forceNewBridge == false) {
return sThis;
} else {
// stop the current server
sThis.stop();
}
}
try {
sThis = new AndroidDebugBridge(osLocation);
sThis.start();
} catch (InvalidParameterException e) {
sThis = null;
}
// because the listeners could remove themselves from the list while processing
// their event callback, we make a copy of the list and iterate on it instead of
// the main list.
// This mostly happens when the application quits.
IDebugBridgeChangeListener[] listenersCopy = sBridgeListeners.toArray(
new IDebugBridgeChangeListener[sBridgeListeners.size()]);
// notify the listeners of the change
for (IDebugBridgeChangeListener listener : listenersCopy) {
// we attempt to catch any exception so that a bad listener doesn't kill our
// thread
try {
listener.bridgeChanged(sThis);
} catch (Exception e) {
Log.e(DDMS, e);
}
}
return sThis;
}
}
/**
* Returns the current debug bridge. Can be null if none were created.
*/
public static AndroidDebugBridge getBridge() {
return sThis;
}
/**
* Disconnects the current debug bridge, and destroy the object.
* This also stops the current adb host server.
*
* A new object will have to be created with {@link #createBridge(String, boolean)}.
*/
public static void disconnectBridge() {
synchronized (sLock) {
if (sThis != null) {
sThis.stop();
sThis = null;
// because the listeners could remove themselves from the list while processing
// their event callback, we make a copy of the list and iterate on it instead of
// the main list.
// This mostly happens when the application quits.
IDebugBridgeChangeListener[] listenersCopy = sBridgeListeners.toArray(
new IDebugBridgeChangeListener[sBridgeListeners.size()]);
// notify the listeners.
for (IDebugBridgeChangeListener listener : listenersCopy) {
// we attempt to catch any exception so that a bad listener doesn't kill our
// thread
try {
listener.bridgeChanged(sThis);
} catch (Exception e) {
Log.e(DDMS, e);
}
}
}
}
}
/**
* Adds the listener to the collection of listeners who will be notified when a new
* {@link AndroidDebugBridge} is connected, by sending it one of the messages defined
* in the {@link IDebugBridgeChangeListener} interface.
* @param listener The listener which should be notified.
*/
public static void addDebugBridgeChangeListener(IDebugBridgeChangeListener listener) {
synchronized (sLock) {
if (sBridgeListeners.contains(listener) == false) {
sBridgeListeners.add(listener);
if (sThis != null) {
// we attempt to catch any exception so that a bad listener doesn't kill our
// thread
try {
listener.bridgeChanged(sThis);
} catch (Exception e) {
Log.e(DDMS, e);
}
}
}
}
}
/**
* Removes the listener from the collection of listeners who will be notified when a new
* {@link AndroidDebugBridge} is started.
* @param listener The listener which should no longer be notified.
*/
public static void removeDebugBridgeChangeListener(IDebugBridgeChangeListener listener) {
synchronized (sLock) {
sBridgeListeners.remove(listener);
}
}
/**
* Adds the listener to the collection of listeners who will be notified when a {@link IDevice}
* is connected, disconnected, or when its properties or its {@link Client} list changed,
* by sending it one of the messages defined in the {@link IDeviceChangeListener} interface.
* @param listener The listener which should be notified.
*/
public static void addDeviceChangeListener(IDeviceChangeListener listener) {
synchronized (sLock) {
if (sDeviceListeners.contains(listener) == false) {
sDeviceListeners.add(listener);
}
}
}
/**
* Removes the listener from the collection of listeners who will be notified when a
* {@link IDevice} is connected, disconnected, or when its properties or its {@link Client}
* list changed.
* @param listener The listener which should no longer be notified.
*/
public static void removeDeviceChangeListener(IDeviceChangeListener listener) {
synchronized (sLock) {
sDeviceListeners.remove(listener);
}
}
/**
* Adds the listener to the collection of listeners who will be notified when a {@link Client}
* property changed, by sending it one of the messages defined in the
* {@link IClientChangeListener} interface.
* @param listener The listener which should be notified.
*/
public static void addClientChangeListener(IClientChangeListener listener) {
synchronized (sLock) {
if (sClientListeners.contains(listener) == false) {
sClientListeners.add(listener);
}
}
}
/**
* Removes the listener from the collection of listeners who will be notified when a
* {@link Client} property changed.
* @param listener The listener which should no longer be notified.
*/
public static void removeClientChangeListener(IClientChangeListener listener) {
synchronized (sLock) {
sClientListeners.remove(listener);
}
}
/**
* Returns the devices.
* @see #hasInitialDeviceList()
*/
public IDevice[] getDevices() {
synchronized (sLock) {
if (mDeviceMonitor != null) {
return mDeviceMonitor.getDevices();
}
}
return new IDevice[0];
}
/**
* Returns whether the bridge has acquired the initial list from adb after being created.
* Calling {@link #getDevices()} right after {@link #createBridge(String, boolean)} will
* generally result in an empty list. This is due to the internal asynchronous communication
* mechanism with adb that does not guarantee that the {@link IDevice} list has been
* built before the call to {@link #getDevices()}.
* The recommended way to get the list of {@link IDevice} objects is to create a
* {@link IDeviceChangeListener} object.
*/
public boolean hasInitialDeviceList() {
if (mDeviceMonitor != null) {
return mDeviceMonitor.hasInitialDeviceList();
}
return false;
}
/**
* Sets the client to accept debugger connection on the custom "Selected debug port".
* @param selectedClient the client. Can be null.
*/
public void setSelectedClient(Client selectedClient) {
MonitorThread monitorThread = MonitorThread.getInstance();
if (monitorThread != null) {
monitorThread.setSelectedClient(selectedClient);
}
}
/**
* Returns whether the {@link AndroidDebugBridge} object is still connected to the adb daemon.
*/
public boolean isConnected() {
MonitorThread monitorThread = MonitorThread.getInstance();
if (mDeviceMonitor != null && monitorThread != null) {
return mDeviceMonitor.isMonitoring() && monitorThread.getState() != State.TERMINATED;
}
return false;
}
/**
* Returns the number of times the {@link AndroidDebugBridge} object attempted to connect
* to the adb daemon.
*/
public int getConnectionAttemptCount() {
if (mDeviceMonitor != null) {
return mDeviceMonitor.getConnectionAttemptCount();
}
return -1;
}
/**
* Returns the number of times the {@link AndroidDebugBridge} object attempted to restart
* the adb daemon.
*/
public int getRestartAttemptCount() {
if (mDeviceMonitor != null) {
return mDeviceMonitor.getRestartAttemptCount();
}
return -1;
}
/**
* Creates a new bridge.
* @param osLocation the location of the command line tool
* @throws InvalidParameterException
*/
private AndroidDebugBridge(String osLocation) throws InvalidParameterException {
if (osLocation == null || osLocation.length() == 0) {
throw new InvalidParameterException();
}
mAdbOsLocation = osLocation;
checkAdbVersion();
}
/**
* Creates a new bridge not linked to any particular adb executable.
*/
private AndroidDebugBridge() {
}
/**
* Queries adb for its version number and checks it against {@link #MIN_VERSION_NUMBER} and
* {@link #MAX_VERSION_NUMBER}
*/
private void checkAdbVersion() {
// default is bad check
mVersionCheck = false;
if (mAdbOsLocation == null) {
return;
}
try {
String[] command = new String[2];
command[0] = mAdbOsLocation;
command[1] = "version"; //$NON-NLS-1$
Log.d(DDMS, String.format("Checking '%1$s version'", mAdbOsLocation)); //$NON-NLS-1$
Process process = Runtime.getRuntime().exec(command);
ArrayListIDevice.
* @see #getLock()
*/
void deviceConnected(IDevice device) {
// because the listeners could remove themselves from the list while processing
// their event callback, we make a copy of the list and iterate on it instead of
// the main list.
// This mostly happens when the application quits.
IDeviceChangeListener[] listenersCopy = null;
synchronized (sLock) {
listenersCopy = sDeviceListeners.toArray(
new IDeviceChangeListener[sDeviceListeners.size()]);
}
// Notify the listeners
for (IDeviceChangeListener listener : listenersCopy) {
// we attempt to catch any exception so that a bad listener doesn't kill our
// thread
try {
listener.deviceConnected(device);
} catch (Exception e) {
Log.e(DDMS, e);
}
}
}
/**
* Notify the listener of a disconnected {@link IDevice}.
*
* The notification of the listeners is done in a synchronized block. It is important to
* expect the listeners to potentially access various methods of {@link IDevice} as well as
* {@link #getDevices()} which use internal locks.
*
* For this reason, any call to this method from a method of {@link DeviceMonitor},
* {@link IDevice} which is also inside a synchronized block, should first synchronize on
* the {@link AndroidDebugBridge} lock. Access to this lock is done through {@link #getLock()}.
* @param device the disconnected IDevice.
* @see #getLock()
*/
void deviceDisconnected(IDevice device) {
// because the listeners could remove themselves from the list while processing
// their event callback, we make a copy of the list and iterate on it instead of
// the main list.
// This mostly happens when the application quits.
IDeviceChangeListener[] listenersCopy = null;
synchronized (sLock) {
listenersCopy = sDeviceListeners.toArray(
new IDeviceChangeListener[sDeviceListeners.size()]);
}
// Notify the listeners
for (IDeviceChangeListener listener : listenersCopy) {
// we attempt to catch any exception so that a bad listener doesn't kill our
// thread
try {
listener.deviceDisconnected(device);
} catch (Exception e) {
Log.e(DDMS, e);
}
}
}
/**
* Notify the listener of a modified {@link IDevice}.
*
* The notification of the listeners is done in a synchronized block. It is important to
* expect the listeners to potentially access various methods of {@link IDevice} as well as
* {@link #getDevices()} which use internal locks.
*
* For this reason, any call to this method from a method of {@link DeviceMonitor},
* {@link IDevice} which is also inside a synchronized block, should first synchronize on
* the {@link AndroidDebugBridge} lock. Access to this lock is done through {@link #getLock()}.
* @param device the modified IDevice.
* @see #getLock()
*/
void deviceChanged(IDevice device, int changeMask) {
// because the listeners could remove themselves from the list while processing
// their event callback, we make a copy of the list and iterate on it instead of
// the main list.
// This mostly happens when the application quits.
IDeviceChangeListener[] listenersCopy = null;
synchronized (sLock) {
listenersCopy = sDeviceListeners.toArray(
new IDeviceChangeListener[sDeviceListeners.size()]);
}
// Notify the listeners
for (IDeviceChangeListener listener : listenersCopy) {
// we attempt to catch any exception so that a bad listener doesn't kill our
// thread
try {
listener.deviceChanged(device, changeMask);
} catch (Exception e) {
Log.e(DDMS, e);
}
}
}
/**
* Notify the listener of a modified {@link Client}.
*
* The notification of the listeners is done in a synchronized block. It is important to
* expect the listeners to potentially access various methods of {@link IDevice} as well as
* {@link #getDevices()} which use internal locks.
*
* For this reason, any call to this method from a method of {@link DeviceMonitor},
* {@link IDevice} which is also inside a synchronized block, should first synchronize on
* the {@link AndroidDebugBridge} lock. Access to this lock is done through {@link #getLock()}.
* @param device the modified Client.
* @param changeMask the mask indicating what changed in the Client
* @see #getLock()
*/
void clientChanged(Client client, int changeMask) {
// because the listeners could remove themselves from the list while processing
// their event callback, we make a copy of the list and iterate on it instead of
// the main list.
// This mostly happens when the application quits.
IClientChangeListener[] listenersCopy = null;
synchronized (sLock) {
listenersCopy = sClientListeners.toArray(
new IClientChangeListener[sClientListeners.size()]);
}
// Notify the listeners
for (IClientChangeListener listener : listenersCopy) {
// we attempt to catch any exception so that a bad listener doesn't kill our
// thread
try {
listener.clientChanged(client, changeMask);
} catch (Exception e) {
Log.e(DDMS, e);
}
}
}
/**
* Returns the {@link DeviceMonitor} object.
*/
DeviceMonitor getDeviceMonitor() {
return mDeviceMonitor;
}
/**
* Starts the adb host side server.
* @return true if success
*/
synchronized boolean startAdb() {
if (mAdbOsLocation == null) {
Log.e(ADB,
"Cannot start adb when AndroidDebugBridge is created without the location of adb."); //$NON-NLS-1$
return false;
}
Process proc;
int status = -1;
try {
String[] command = new String[2];
command[0] = mAdbOsLocation;
command[1] = "start-server"; //$NON-NLS-1$
Log.d(DDMS,
String.format("Launching '%1$s %2$s' to ensure ADB is running.", //$NON-NLS-1$
mAdbOsLocation, command[1]));
proc = Runtime.getRuntime().exec(command);
ArrayList