| /* |
| * 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.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.Map; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * 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 int ADB_VERSION_MICRO_MIN = 20; |
| private static final int ADB_VERSION_MICRO_MAX = -1; |
| |
| private static final Pattern sAdbVersion = Pattern.compile( |
| "^.*(\\d+)\\.(\\d+)\\.(\\d+)$"); //$NON-NLS-1$ |
| |
| 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 ADB_HOST = "127.0.0.1"; //$NON-NLS-1$ |
| static final int ADB_PORT = 5037; |
| |
| 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. |
| */ |
| public 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. |
| */ |
| public 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. |
| */ |
| public 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} |
| */ |
| public 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} |
| */ |
| public 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() |
| */ |
| 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; |
| |
| 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; |
| } |
| |
| String[] command = new String[2]; |
| command[0] = mAdbOsLocation; |
| command[1] = "version"; //$NON-NLS-1$ |
| Log.d(DDMS, String.format("Checking '%1$s version'", mAdbOsLocation)); |
| Process process = null; |
| try { |
| process = Runtime.getRuntime().exec(command); |
| } catch (IOException e) { |
| boolean exists = new File(mAdbOsLocation).exists(); |
| String msg; |
| if (exists) { |
| msg = String.format( |
| "Unexpected exception '%1$s' while attempting to get adb version from '%2$s'", |
| e.getMessage(), mAdbOsLocation); |
| } else { |
| msg = "Unable to locate adb.\n" + |
| "Please use SDK Manager and check if Android SDK platform-tools are installed."; |
| } |
| Log.logAndDisplay(LogLevel.ERROR, ADB, msg); |
| return; |
| } |
| |
| ArrayList<String> errorOutput = new ArrayList<String>(); |
| ArrayList<String> stdOutput = new ArrayList<String>(); |
| int status; |
| try { |
| status = grabProcessOutput(process, errorOutput, stdOutput, |
| true /* waitForReaders */); |
| } catch (InterruptedException e) { |
| return; |
| } |
| |
| if (status != 0) { |
| StringBuilder builder = new StringBuilder("'adb version' failed!"); //$NON-NLS-1$ |
| for (String error : errorOutput) { |
| builder.append('\n'); |
| builder.append(error); |
| } |
| Log.logAndDisplay(LogLevel.ERROR, ADB, builder.toString()); |
| } |
| |
| // check both stdout and stderr |
| boolean versionFound = false; |
| for (String line : stdOutput) { |
| versionFound = scanVersionLine(line); |
| if (versionFound) { |
| break; |
| } |
| } |
| if (!versionFound) { |
| for (String line : errorOutput) { |
| versionFound = scanVersionLine(line); |
| if (versionFound) { |
| break; |
| } |
| } |
| } |
| |
| if (!versionFound) { |
| // if we get here, we failed to parse the output. |
| StringBuilder builder = new StringBuilder( |
| "Failed to parse the output of 'adb version':\n"); //$NON-NLS-1$ |
| builder.append("Standard Output was:\n"); //$NON-NLS-1$ |
| for (String line : stdOutput) { |
| builder.append(line); |
| builder.append('\n'); |
| } |
| builder.append("\nError Output was:\n"); //$NON-NLS-1$ |
| for (String line : errorOutput) { |
| builder.append(line); |
| builder.append('\n'); |
| } |
| Log.logAndDisplay(LogLevel.ERROR, ADB, builder.toString()); |
| } |
| } |
| |
| /** |
| * Scans a line resulting from 'adb version' for a potential version number. |
| * <p/> |
| * If a version number is found, it checks the version number against what is expected |
| * by this version of ddms. |
| * <p/> |
| * Returns true when a version number has been found so that we can stop scanning, |
| * whether the version number is in the acceptable range or not. |
| * |
| * @param line The line to scan. |
| * @return True if a version number was found (whether it is acceptable or not). |
| */ |
| @SuppressWarnings("all") // With Eclipse 3.6, replace by @SuppressWarnings("unused") |
| private boolean scanVersionLine(String line) { |
| if (line != null) { |
| Matcher matcher = sAdbVersion.matcher(line); |
| if (matcher.matches()) { |
| int majorVersion = Integer.parseInt(matcher.group(1)); |
| int minorVersion = Integer.parseInt(matcher.group(2)); |
| int microVersion = Integer.parseInt(matcher.group(3)); |
| |
| // check only the micro version for now. |
| if (microVersion < ADB_VERSION_MICRO_MIN) { |
| String message = String.format( |
| "Required minimum version of adb: %1$d.%2$d.%3$d." //$NON-NLS-1$ |
| + "Current version is %1$d.%2$d.%4$d", //$NON-NLS-1$ |
| majorVersion, minorVersion, ADB_VERSION_MICRO_MIN, |
| microVersion); |
| Log.logAndDisplay(LogLevel.ERROR, ADB, message); |
| } else if (ADB_VERSION_MICRO_MAX != -1 && |
| microVersion > ADB_VERSION_MICRO_MAX) { |
| String message = String.format( |
| "Required maximum version of adb: %1$d.%2$d.%3$d." //$NON-NLS-1$ |
| + "Current version is %1$d.%2$d.%4$d", //$NON-NLS-1$ |
| majorVersion, minorVersion, ADB_VERSION_MICRO_MAX, |
| microVersion); |
| Log.logAndDisplay(LogLevel.ERROR, ADB, message); |
| } else { |
| mVersionCheck = true; |
| } |
| |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Starts the debug bridge. |
| * @return true if success. |
| */ |
| boolean start() { |
| if (mAdbOsLocation != null && (!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 (!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 device 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; |
| } |
| |
| 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])); |
| 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.d(DDMS, "Unable to run 'adb': " + ioe.getMessage()); //$NON-NLS-1$ |
| // we'll return false; |
| } catch (InterruptedException ie) { |
| Log.d(DDMS, "Unable to run 'adb': " + ie.getMessage()); //$NON-NLS-1$ |
| // we'll return false; |
| } |
| |
| if (status != 0) { |
| Log.w(DDMS, |
| "'adb start-server' failed -- run manually if necessary"); //$NON-NLS-1$ |
| return false; |
| } |
| |
| Log.d(DDMS, "'adb start-server' succeeded"); //$NON-NLS-1$ |
| |
| return true; |
| } |
| |
| /** |
| * 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."); //$NON-NLS-1$ |
| return false; |
| } |
| |
| Process proc; |
| int status = -1; |
| |
| try { |
| String[] command = new String[2]; |
| command[0] = mAdbOsLocation; |
| command[1] = "kill-server"; //$NON-NLS-1$ |
| proc = Runtime.getRuntime().exec(command); |
| status = proc.waitFor(); |
| } |
| catch (IOException ioe) { |
| // we'll return false; |
| } |
| catch (InterruptedException ie) { |
| // we'll return false; |
| } |
| |
| if (status != 0) { |
| Log.w(DDMS, |
| "'adb kill-server' failed -- run manually if necessary"); //$NON-NLS-1$ |
| return false; |
| } |
| |
| Log.d(DDMS, "'adb kill-server' succeeded"); //$NON-NLS-1$ |
| 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. |
| */ |
| static Object getLock() { |
| return sLock; |
| } |
| |
| /** |
| * Instantiates sSocketAddr with the address of the host's adb process. |
| */ |
| private static void initAdbSocketAddr() { |
| try { |
| int adb_port = determineAndValidateAdbPort(); |
| sHostAddr = InetAddress.getByName(ADB_HOST); |
| sSocketAddr = new InetSocketAddress(sHostAddr, adb_port); |
| } catch (UnknownHostException e) { |
| // localhost should always be known. |
| } |
| } |
| |
| /** |
| * Determines port where ADB is expected by looking at an env variable. |
| * <p/> |
| * The value for the environment variable ANDROID_ADB_SERVER_PORT is validated, |
| * IllegalArgumentException is thrown on illegal values. |
| * <p/> |
| * @return The port number where the host's adb should be expected or started. |
| * @throws IllegalArgumentException if ANDROID_ADB_SERVER_PORT has a non-numeric value. |
| */ |
| private static int determineAndValidateAdbPort() { |
| String adb_env_var; |
| int result = ADB_PORT; |
| try { |
| adb_env_var = System.getenv(SERVER_PORT_ENV_VAR); |
| |
| if (adb_env_var != null) { |
| adb_env_var = adb_env_var.trim(); |
| } |
| |
| if (adb_env_var != null && !adb_env_var.isEmpty()) { |
| // C tools (adb, emulator) accept hex and octal port numbers, so need to accept |
| // them too. |
| result = Integer.decode(adb_env_var); |
| |
| if (result <= 0) { |
| String errMsg = "env var " + SERVER_PORT_ENV_VAR //$NON-NLS-1$ |
| + ": must be >=0, got " //$NON-NLS-1$ |
| + System.getenv(SERVER_PORT_ENV_VAR); |
| throw new IllegalArgumentException(errMsg); |
| } |
| } |
| } catch (NumberFormatException nfEx) { |
| String errMsg = "env var " + SERVER_PORT_ENV_VAR //$NON-NLS-1$ |
| + ": illegal value '" //$NON-NLS-1$ |
| + System.getenv(SERVER_PORT_ENV_VAR) + "'"; //$NON-NLS-1$ |
| throw new IllegalArgumentException(errMsg); |
| } catch (SecurityException secEx) { |
| // 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. " //$NON-NLS-1$ |
| + "If you've set ANDROID_ADB_SERVER_PORT: it's being ignored."); //$NON-NLS-1$ |
| } |
| return result; |
| } |
| |
| } |