/* * 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"); } } }