blob: 13dec700f607f21729f11a580820e5e227dc5245 [file] [log] [blame]
/*
* 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.annotations.NonNull;
import com.android.ddmlib.Log.LogLevel;
import com.google.common.base.Joiner;
import com.google.common.base.Throwables;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import java.io.BufferedReader;
import java.io.File;
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.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* A connection to the host-side android debug bridge (adb)
* <p/>This is the central point to communicate with any devices, emulators, or the applications
* running on them.
* <p/><b>{@link #init(boolean)} must be called before anything is done.</b>
*/
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 static final AdbVersion MIN_ADB_VERSION = AdbVersion.parseFrom("1.0.20");
private static final String ADB = "adb"; //$NON-NLS-1$
private static final String DDMS = "ddms"; //$NON-NLS-1$
private static final String SERVER_PORT_ENV_VAR = "ANDROID_ADB_SERVER_PORT"; //$NON-NLS-1$
// Where to find the ADB bridge.
static final String DEFAULT_ADB_HOST = "127.0.0.1"; //$NON-NLS-1$
static final int DEFAULT_ADB_PORT = 5037;
/** Port where adb server will be started **/
private static int sAdbServerPort = 0;
private static InetAddress sHostAddr;
private static InetSocketAddress sSocketAddr;
private static AndroidDebugBridge sThis;
private static boolean sInitialized = false;
private static boolean sClientSupport;
/** Full path to adb. */
private String mAdbOsLocation = null;
private boolean mVersionCheck;
private boolean mStarted = false;
private DeviceMonitor mDeviceMonitor;
private static final ArrayList<IDebugBridgeChangeListener> sBridgeListeners =
new ArrayList<IDebugBridgeChangeListener>();
private static final ArrayList<IDeviceChangeListener> sDeviceListeners =
new ArrayList<IDeviceChangeListener>();
private static final ArrayList<IClientChangeListener> sClientListeners =
new ArrayList<IClientChangeListener>();
// lock object for synchronization
private static final Object sLock = sBridgeListeners;
/**
* Classes which implement this interface provide a method that deals
* with {@link AndroidDebugBridge} changes.
*/
public interface IDebugBridgeChangeListener {
/**
* Sent when a new {@link AndroidDebugBridge} is connected.
* <p/>
* This is sent from a non UI thread.
* @param bridge the new {@link AndroidDebugBridge} object.
*/
void bridgeChanged(AndroidDebugBridge bridge);
}
/**
* Classes which implement this interface provide methods that deal
* with {@link IDevice} addition, deletion, and changes.
*/
public interface IDeviceChangeListener {
/**
* Sent when the a device is connected to the {@link AndroidDebugBridge}.
* <p/>
* This is sent from a non UI thread.
* @param device the new device.
*/
void deviceConnected(IDevice device);
/**
* Sent when the a device is connected to the {@link AndroidDebugBridge}.
* <p/>
* This is sent from a non UI thread.
* @param device the new device.
*/
void deviceDisconnected(IDevice device);
/**
* Sent when a device data changed, or when clients are started/terminated on the device.
* <p/>
* This is sent from a non UI thread.
* @param device the device that was updated.
* @param changeMask the mask describing what changed. It can contain any of the following
* values: {@link IDevice#CHANGE_BUILD_INFO}, {@link IDevice#CHANGE_STATE},
* {@link IDevice#CHANGE_CLIENT_LIST}
*/
void deviceChanged(IDevice device, int changeMask);
}
/**
* Classes which implement this interface provide methods that deal
* with {@link Client} changes.
*/
public interface IClientChangeListener {
/**
* Sent when an existing client information changed.
* <p/>
* This is sent from a non UI thread.
* @param client the updated client.
* @param changeMask the bit mask describing the changed properties. It can contain
* any of the following values: {@link Client#CHANGE_INFO},
* {@link Client#CHANGE_DEBUGGER_STATUS}, {@link Client#CHANGE_THREAD_MODE},
* {@link Client#CHANGE_THREAD_DATA}, {@link Client#CHANGE_HEAP_MODE},
* {@link Client#CHANGE_HEAP_DATA}, {@link Client#CHANGE_NATIVE_HEAP_DATA}
*/
void clientChanged(Client client, int changeMask);
}
/**
* Initialized the library only if needed.
*
* @param clientSupport Indicates whether the library should enable the monitoring and
* interaction with applications running on the devices.
*
* @see #init(boolean)
*/
public static synchronized void initIfNeeded(boolean clientSupport) {
if (sInitialized) {
return;
}
init(clientSupport);
}
/**
* Initializes the <code>ddm</code> library.
* <p/>This must be called once <b>before</b> any call to
* {@link #createBridge(String, boolean)}.
* <p>The library can be initialized in 2 ways:
* <ul>
* <li>Mode 1: <var>clientSupport</var> == <code>true</code>.<br>The library monitors the
* devices and the applications running on them. It will connect to each application, as a
* debugger of sort, to be able to interact with them through JDWP packets.</li>
* <li>Mode 2: <var>clientSupport</var> == <code>false</code>.<br>The library only monitors
* devices. The applications are left untouched, letting other tools built on
* <code>ddmlib</code> to connect a debugger to them.</li>
* </ul>
* <p/><b>Only one tool can run in mode 1 at the same time.</b>
* <p/>Note that mode 1 does not prevent debugging of applications running on devices. Mode 1
* lets debuggers connect to <code>ddmlib</code> which acts as a proxy between the debuggers and
* the applications to debug. See {@link Client#getDebuggerListenPort()}.
* <p/>The preferences of <code>ddmlib</code> should also be initialized with whatever default
* values were changed from the default values.
* <p/>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 synchronized void init(boolean clientSupport) {
if (sInitialized) {
throw new IllegalStateException("AndroidDebugBridge.init() has already been called.");
}
sInitialized = true;
sClientSupport = clientSupport;
// Determine port and instantiate socket address.
initAdbSocketAddr();
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);
HandleNativeHeap.register(monitorThread);
HandleViewDebug.register(monitorThread);
}
/**
* Terminates the ddm library. This must be called upon application termination.
*/
public static synchronized 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();
}
sInitialized = false;
}
/**
* 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;
}
/**
* Returns the socket address of the ADB server on the host.
*/
public static InetSocketAddress getSocketAddress() {
return sSocketAddr;
}
/**
* Creates a {@link AndroidDebugBridge} that is not linked to any particular executable.
* <p/>This bridge will expect adb to be running. It will not be able to start/stop/restart
* adb.
* <p/>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.
* <p/>
* Any existing server will be disconnected, unless the location is the same and
* <code>forceNewBridge</code> 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) {
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 <code>null</code> if none were created.
*/
public static AndroidDebugBridge getBridge() {
return sThis;
}
/**
* Disconnects the current debug bridge, and destroy the object.
* <p/>This also stops the current adb host server.
* <p/>
* 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)) {
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)) {
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)) {
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()
*/
@NonNull
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.
* <p/>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 <code>adb</code> that does not guarantee that the {@link IDevice} list has been
* built before the call to {@link #getDevices()}.
* <p/>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.isEmpty()) {
throw new InvalidParameterException();
}
mAdbOsLocation = osLocation;
try {
checkAdbVersion();
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Creates a new bridge not linked to any particular adb executable.
*/
private AndroidDebugBridge() {
}
/**
* Queries adb for its version number and checks that it is atleast {@link #MIN_ADB_VERSION}.
*/
private void checkAdbVersion() throws IOException {
// default is bad check
mVersionCheck = false;
if (mAdbOsLocation == null) {
return;
}
File adb = new File(mAdbOsLocation);
ListenableFuture<AdbVersion> future = getAdbVersion(adb);
AdbVersion version;
try {
version = future.get(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return;
} catch (java.util.concurrent.TimeoutException e) {
String msg = "Unable to obtain result of 'adb version'";
Log.logAndDisplay(LogLevel.ERROR, ADB, msg);
return;
} catch (ExecutionException e) {
Log.logAndDisplay(LogLevel.ERROR, ADB, e.getCause().getMessage());
Throwables.propagateIfInstanceOf(e.getCause(), IOException.class);
return;
}
if (version.compareTo(MIN_ADB_VERSION) > 0) {
mVersionCheck = true;
} else {
String message = String.format(
"Required minimum version of adb: %1$s."
+ "Current version is %2$s", MIN_ADB_VERSION, version);
Log.logAndDisplay(LogLevel.ERROR, ADB, message);
}
}
public static ListenableFuture<AdbVersion> getAdbVersion(@NonNull final File adb) {
final SettableFuture<AdbVersion> future = SettableFuture.create();
new Thread(new Runnable() {
@Override
public void run() {
ProcessBuilder pb = new ProcessBuilder(adb.getPath(), "version");
pb.redirectErrorStream(true);
Process p = null;
try {
p = pb.start();
} catch (IOException e) {
future.setException(e);
return;
}
StringBuilder sb = new StringBuilder();
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
try {
String line;
while ((line = br.readLine()) != null) {
AdbVersion version = AdbVersion.parseFrom(line);
if (version != AdbVersion.UNKNOWN) {
future.set(version);
return;
}
sb.append(line);
sb.append('\n');
}
} catch (IOException e) {
future.setException(e);
return;
} finally {
try {
br.close();
} catch (IOException e) {
future.setException(e);
}
}
future.setException(new RuntimeException(
"Unable to detect adb version, adb output: " + sb.toString()));
}
}, "Obtaining adb version").start();
return future;
}
/**
* Starts the debug bridge.
*
* @return true if success.
*/
boolean start() {
if (mAdbOsLocation != null && sAdbServerPort != 0 && (!mVersionCheck || !startAdb())) {
return false;
}
mStarted = true;
// now that the bridge is connected, we start the underlying services.
mDeviceMonitor = new DeviceMonitor(this);
mDeviceMonitor.start();
return true;
}
/**
* Kills the debug bridge, and the adb host server.
* @return true if success
*/
boolean stop() {
// if we haven't started we return false;
if (!mStarted) {
return false;
}
// kill the monitoring services
if (mDeviceMonitor != null) {
mDeviceMonitor.stop();
mDeviceMonitor = null;
}
if (!stopAdb()) {
return false;
}
mStarted = false;
return true;
}
/**
* Restarts adb, but not the services around it.
* @return true if success.
*/
public boolean restart() {
if (mAdbOsLocation == null) {
Log.e(ADB,
"Cannot restart adb when AndroidDebugBridge is created without the location of adb."); //$NON-NLS-1$
return false;
}
if (sAdbServerPort == 0) {
Log.e(ADB, "ADB server port for restarting AndroidDebugBridge is not set."); //$NON-NLS-1$
return false;
}
if (!mVersionCheck) {
Log.logAndDisplay(LogLevel.ERROR, ADB,
"Attempting to restart adb, but version check failed!"); //$NON-NLS-1$
return false;
}
synchronized (this) {
stopAdb();
boolean restart = startAdb();
if (restart && mDeviceMonitor == null) {
mDeviceMonitor = new DeviceMonitor(this);
mDeviceMonitor.start();
}
return restart;
}
}
/**
* Notify the listener of a new {@link IDevice}.
* <p/>
* 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.
* <p/>
* 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 new <code>IDevice</code>.
* @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}.
* <p/>
* 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.
* <p/>
* 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 <code>IDevice</code>.
* @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}.
* <p/>
* 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.
* <p/>
* 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 <code>IDevice</code>.
* @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}.
* <p/>
* 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.
* <p/>
* 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 client the modified <code>Client</code>.
* @param changeMask the mask indicating what changed in the <code>Client</code>
* @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;
}
if (sAdbServerPort == 0) {
Log.w(ADB, "ADB server port for starting AndroidDebugBridge is not set."); //$NON-NLS-1$
return false;
}
Process proc;
int status = -1;
String[] command = getAdbLaunchCommand("start-server");
String commandString = Joiner.on(',').join(command);
try {
Log.d(DDMS, String.format("Launching '%1$s' to ensure ADB is running.", commandString));
ProcessBuilder processBuilder = new ProcessBuilder(command);
if (DdmPreferences.getUseAdbHost()) {
String adbHostValue = DdmPreferences.getAdbHostValue();
if (adbHostValue != null && !adbHostValue.isEmpty()) {
//TODO : check that the String is a valid IP address
Map<String, String> env = processBuilder.environment();
env.put("ADBHOST", adbHostValue);
}
}
proc = processBuilder.start();
ArrayList<String> errorOutput = new ArrayList<String>();
ArrayList<String> stdOutput = new ArrayList<String>();
status = grabProcessOutput(proc, errorOutput, stdOutput, false /* waitForReaders */);
} catch (IOException ioe) {
Log.e(DDMS, "Unable to run 'adb': " + ioe.getMessage()); //$NON-NLS-1$
// we'll return false;
} catch (InterruptedException ie) {
Log.e(DDMS, "Unable to run 'adb': " + ie.getMessage()); //$NON-NLS-1$
// we'll return false;
}
if (status != 0) {
Log.e(DDMS,
String.format("'%1$s' failed -- run manually if necessary", commandString)); //$NON-NLS-1$
return false;
} else {
Log.d(DDMS, String.format("'%1$s' succeeded", commandString)); //$NON-NLS-1$
return true;
}
}
private String[] getAdbLaunchCommand(String option) {
List<String> command = new ArrayList<String>(4);
command.add(mAdbOsLocation);
if (sAdbServerPort != DEFAULT_ADB_PORT) {
command.add("-P"); //$NON-NLS-1$
command.add(Integer.toString(sAdbServerPort));
}
command.add(option);
return command.toArray(new String[command.size()]);
}
/**
* Stops the adb host side server.
*
* @return true if success
*/
private synchronized boolean stopAdb() {
if (mAdbOsLocation == null) {
Log.e(ADB,
"Cannot stop adb when AndroidDebugBridge is created without the location of adb.");
return false;
}
if (sAdbServerPort == 0) {
Log.e(ADB, "ADB server port for restarting AndroidDebugBridge is not set");
return false;
}
Process proc;
int status = -1;
String[] command = getAdbLaunchCommand("kill-server"); //$NON-NLS-1$
try {
proc = Runtime.getRuntime().exec(command);
status = proc.waitFor();
}
catch (IOException ioe) {
// we'll return false;
}
catch (InterruptedException ie) {
// we'll return false;
}
String commandString = Joiner.on(',').join(command);
if (status != 0) {
Log.w(DDMS, String.format("'%1$s' failed -- run manually if necessary", commandString));
return false;
} else {
Log.d(DDMS, String.format("'%1$s' succeeded", commandString));
return true;
}
}
/**
* Get the stderr/stdout outputs of a process and return when the process is done.
* Both <b>must</b> be read or the process will block on windows.
* @param process The process to get the output from
* @param errorOutput The array to store the stderr output. cannot be null.
* @param stdOutput The array to store the stdout output. cannot be null.
* @param waitForReaders if true, this will wait for the reader threads.
* @return the process return code.
* @throws InterruptedException
*/
private int grabProcessOutput(final Process process, final ArrayList<String> errorOutput,
final ArrayList<String> stdOutput, boolean waitForReaders)
throws InterruptedException {
assert errorOutput != null;
assert stdOutput != null;
// read the lines as they come. if null is returned, it's
// because the process finished
Thread t1 = new Thread("") { //$NON-NLS-1$
@Override
public void run() {
// create a buffer to read the stderr output
InputStreamReader is = new InputStreamReader(process.getErrorStream());
BufferedReader errReader = new BufferedReader(is);
try {
while (true) {
String line = errReader.readLine();
if (line != null) {
Log.e(ADB, line);
errorOutput.add(line);
} else {
break;
}
}
} catch (IOException e) {
// do nothing.
}
}
};
Thread t2 = new Thread("") { //$NON-NLS-1$
@Override
public void run() {
InputStreamReader is = new InputStreamReader(process.getInputStream());
BufferedReader outReader = new BufferedReader(is);
try {
while (true) {
String line = outReader.readLine();
if (line != null) {
Log.d(ADB, line);
stdOutput.add(line);
} else {
break;
}
}
} catch (IOException e) {
// do nothing.
}
}
};
t1.start();
t2.start();
// it looks like on windows process#waitFor() can return
// before the thread have filled the arrays, so we wait for both threads and the
// process itself.
if (waitForReaders) {
try {
t1.join();
} catch (InterruptedException e) {
}
try {
t2.join();
} catch (InterruptedException e) {
}
}
// get the return code from the process
return process.waitFor();
}
/**
* Returns the singleton lock used by this class to protect any access to the listener.
* <p/>
* This includes adding/removing listeners, but also notifying listeners of new bridges,
* devices, and clients.
*/
private static Object getLock() {
return sLock;
}
/**
* Instantiates sSocketAddr with the address of the host's adb process.
*/
private static void initAdbSocketAddr() {
try {
sAdbServerPort = getAdbServerPort();
sHostAddr = InetAddress.getByName(DEFAULT_ADB_HOST);
sSocketAddr = new InetSocketAddress(sHostAddr, sAdbServerPort);
} catch (UnknownHostException e) {
// localhost should always be known.
}
}
/**
* Returns the port where adb server should be launched. This looks at:
* <ol>
* <li>The system property ANDROID_ADB_SERVER_PORT</li>
* <li>The environment variable ANDROID_ADB_SERVER_PORT</li>
* <li>Defaults to {@link #DEFAULT_ADB_PORT} if neither the system property nor the env var
* are set.</li>
* </ol>
*
* @return The port number where the host's adb should be expected or started.
*/
private static int getAdbServerPort() {
// check system property
Integer prop = Integer.getInteger(SERVER_PORT_ENV_VAR);
if (prop != null) {
try {
return validateAdbServerPort(prop.toString());
} catch (IllegalArgumentException e) {
String msg = String.format(
"Invalid value (%1$s) for ANDROID_ADB_SERVER_PORT system property.",
prop);
Log.w(DDMS, msg);
}
}
// when system property is not set or is invalid, parse environment property
try {
String env = System.getenv(SERVER_PORT_ENV_VAR);
if (env != null) {
return validateAdbServerPort(env);
}
} catch (SecurityException ex) {
// A security manager has been installed that doesn't allow access to env vars.
// So an environment variable might have been set, but we can't tell.
// Let's log a warning and continue with ADB's default port.
// The issue is that adb would be started (by the forked process having access
// to the env vars) on the desired port, but within this process, we can't figure out
// what that port is. However, a security manager not granting access to env vars
// but allowing to fork is a rare and interesting configuration, so the right
// thing seems to be to continue using the default port, as forking is likely to
// fail later on in the scenario of the security manager.
Log.w(DDMS,
"No access to env variables allowed by current security manager. "
+ "If you've set ANDROID_ADB_SERVER_PORT: it's being ignored.");
} catch (IllegalArgumentException e) {
String msg = String.format(
"Invalid value (%1$s) for ANDROID_ADB_SERVER_PORT environment variable (%2$s).",
prop, e.getMessage());
Log.w(DDMS, msg);
}
// use default port if neither are set
return DEFAULT_ADB_PORT;
}
/**
* Returns the integer port value if it is a valid value for adb server port
* @param adbServerPort adb server port to validate
* @return {@code adbServerPort} as a parsed integer
* @throws IllegalArgumentException when {@code adbServerPort} is not bigger than 0 or it is
* not a number at all
*/
private static int validateAdbServerPort(@NonNull String adbServerPort)
throws IllegalArgumentException {
try {
// C tools (adb, emulator) accept hex and octal port numbers, so need to accept them too
int port = Integer.decode(adbServerPort);
if (port <= 0 || port >= 65535) {
throw new IllegalArgumentException("Should be > 0 and < 65535");
}
return port;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Not a valid port number");
}
}
}