diff --git a/build.gradle b/build.gradle index 58411da4..6c844bac 100755 --- a/build.gradle +++ b/build.gradle @@ -37,8 +37,8 @@ dependencies { compile "com.android.tools.ddms:ddmlib:26.2.0" shadow "com.android.tools.build:aapt2-proto:0.4.0" - shadow "com.google.auto.value:auto-value:1.5.2" - annotationProcessor "com.google.auto.value:auto-value:1.5.2" + shadow "com.google.auto.value:auto-value-annotations:1.6.2" + annotationProcessor "com.google.auto.value:auto-value:1.6.2" shadow "com.google.errorprone:error_prone_annotations:2.3.1" shadow "com.google.guava:guava:27.0.1-jre" shadow "com.google.protobuf:protobuf-java:3.4.0" @@ -49,8 +49,8 @@ dependencies { compileLinux "com.android.tools.build:aapt2:3.5.0-alpha03-5252756:linux" testCompile "com.android.tools.build:aapt2-proto:0.4.0" - testCompile "com.google.auto.value:auto-value-annotations:1.5.2" - testAnnotationProcessor "com.google.auto.value:auto-value:1.5.2" + testCompile "com.google.auto.value:auto-value-annotations:1.6.2" + testAnnotationProcessor "com.google.auto.value:auto-value:1.6.2" testCompile "com.google.errorprone:error_prone_annotations:2.3.1" testCompile "com.google.guava:guava:27.0.1-jre" testCompile "com.google.truth.extensions:truth-java8-extension:0.45" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 32a96c59..8f1bee26 100755 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStorePath=wrapper/dists diff --git a/src/main/java/com/android/ddmlib/AndroidDebugBridge.java b/src/main/java/com/android/ddmlib/AndroidDebugBridge.java new file mode 100644 index 00000000..deeb006b --- /dev/null +++ b/src/main/java/com/android/ddmlib/AndroidDebugBridge.java @@ -0,0 +1,1205 @@ +/* + * 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.annotations.Nullable; +import com.android.annotations.VisibleForTesting; +import com.android.annotations.concurrency.GuardedBy; +import com.android.ddmlib.Log.LogLevel; +import com.google.common.base.Charsets; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Sets; +import com.google.common.io.Closeables; +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.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +/** + * A connection to the host-side android debug bridge (adb) + * + *
This is the central point to communicate with any devices, emulators, or the applications + * running on them. + * + *
{@link #init(boolean)} must be called before anything is done.
+ */
+public 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 int DEFAULT_ADB_PORT = 5037;
+
+ // Only set when in unit testing mode. This is a hack until we move to devicelib.
+ // http://b.android.com/221925
+ private static boolean sUnitTestMode;
+
+ /** 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;
+ private static boolean sUseLibusb;
+ private static Map
+ * This is sent from a non UI thread.
+ * @param bridge the new {@link AndroidDebugBridge} object, null if there were errors while
+ * initializing the bridge
+ */
+ void bridgeChanged(@Nullable AndroidDebugBridge bridge);
+
+ /**
+ * Sent before trigger a restart.
+ *
+ * Note: Callback is inside a synchronized block so handler should be fast.
+ */
+ default void restartInitiated() {}
+
+ /**
+ * Sent when a restarted is finished.
+ *
+ * Note: Callback is inside a synchronized block so handler should be fast.
+ *
+ * @param isSuccessful if the bridge is successfully restarted.
+ */
+ default void restartCompleted(boolean isSuccessful) {};
+ }
+
+ /**
+ * 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}.
+ *
+ * This is sent from a non UI thread.
+ * @param device the new device.
+ */
+ void deviceConnected(@NonNull IDevice device);
+
+ /**
+ * Sent when the a device is connected to the {@link AndroidDebugBridge}.
+ *
+ * This is sent from a non UI thread.
+ * @param device the new device.
+ */
+ void deviceDisconnected(@NonNull IDevice device);
+
+ /**
+ * Sent when a device data changed, or when clients are started/terminated on the device.
+ *
+ * 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(@NonNull 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.
+ *
+ * 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(@NonNull 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 This must be called once before any call to
+ * {@link #createBridge(String, boolean)}.
+ * The library can be initialized in 2 ways:
+ * Only one tool can run in mode 1 at the same time.
+ * Note that mode 1 does not prevent debugging of applications running on devices. Mode 1
+ * lets debuggers connect to The preferences of 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) {
+ init(clientSupport, false, ImmutableMap.of());
+ }
+
+ public static synchronized void init(
+ boolean clientSupport, boolean useLibusb, @NonNull Map This bridge will expect adb to be running. It will not be able to start/stop/restart
+ * adb.
+ * If a bridge has already been started, it is directly returned with no changes (similar
+ * to calling {@link #getBridge()}).
+ * @return a connected bridge.
+ */
+ public static AndroidDebugBridge createBridge() {
+ synchronized (sLock) {
+ if (sThis != null) {
+ return sThis;
+ }
+
+ try {
+ sThis = new AndroidDebugBridge();
+ sThis.start();
+ } catch (InvalidParameterException e) {
+ sThis = null;
+ }
+
+ // notify the listeners of the change
+ for (IDebugBridgeChangeListener listener : sBridgeListeners) {
+ // we attempt to catch any exception so that a bad listener doesn't kill our thread
+ try {
+ listener.bridgeChanged(sThis);
+ } catch (Exception e) {
+ Log.e(DDMS, e);
+ }
+ }
+
+ return sThis;
+ }
+ }
+
+
+ /**
+ * Creates a new debug bridge from the location of the command line tool.
+ * Any existing server will be disconnected, unless the location is the same and
+ * This also stops the current adb host server.
+ *
+ * A new object will have to be created with {@link #createBridge(String, boolean)}.
+ */
+ public static void disconnectBridge() {
+ synchronized (sLock) {
+ if (sThis != null) {
+ sThis.stop();
+ sThis = null;
+
+ // notify the listeners.
+ for (IDebugBridgeChangeListener listener : sBridgeListeners) {
+ // 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(@NonNull IDebugBridgeChangeListener listener) {
+ synchronized (sLock) {
+ 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(@NonNull IDeviceChangeListener 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) {
+ 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) {
+ sClientListeners.add(listener);
+ }
+
+ /**
+ * Removes the listener from the collection of listeners who will be notified when a {@link
+ * Client} property changes.
+ *
+ * @param listener The listener which should no longer be notified.
+ */
+ public static void removeClientChangeListener(IClientChangeListener listener) {
+ 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.
+ * 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 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
+ * The notification of the listeners is done in a synchronized block. It is important to
+ * expect the listeners to potentially access various methods of {@link IDevice} as well as
+ * {@link #getDevices()} which use internal locks.
+ *
+ * For this reason, any call to this method from a method of {@link DeviceMonitor},
+ * {@link IDevice} which is also inside a synchronized block, should first synchronize on
+ * the {@link AndroidDebugBridge} lock. Access to this lock is done through {@link #getLock()}.
+ * @param device the new
+ * The notification of the listeners is done in a synchronized block. It is important to
+ * expect the listeners to potentially access various methods of {@link IDevice} as well as
+ * {@link #getDevices()} which use internal locks.
+ *
+ * For this reason, any call to this method from a method of {@link DeviceMonitor},
+ * {@link IDevice} which is also inside a synchronized block, should first synchronize on
+ * the {@link AndroidDebugBridge} lock. Access to this lock is done through {@link #getLock()}.
+ * @param device the disconnected
+ * The notification of the listeners is done in a synchronized block. It is important to
+ * expect the listeners to potentially access various methods of {@link IDevice} as well as
+ * {@link #getDevices()} which use internal locks.
+ *
+ * For this reason, any call to this method from a method of {@link DeviceMonitor},
+ * {@link IDevice} which is also inside a synchronized block, should first synchronize on
+ * the {@link AndroidDebugBridge} lock. Access to this lock is done through {@link #getLock()}.
+ * @param device the modified
+ * The notification of the listeners is done in a synchronized block. It is important to
+ * expect the listeners to potentially access various methods of {@link IDevice} as well as
+ * {@link #getDevices()} which use internal locks.
+ *
+ * For this reason, any call to this method from a method of {@link DeviceMonitor},
+ * {@link IDevice} which is also inside a synchronized block, should first synchronize on
+ * the {@link AndroidDebugBridge} lock. Access to this lock is done through {@link #getLock()}.
+ * @param client the modified
+ * 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() {
+ String adbHost = DdmPreferences.getUseAdbHost() ? DdmPreferences.getAdbHostValue() : DdmPreferences.DEFAULT_ADBHOST_VALUE;
+ try {
+ // If we're in unit test mode, we already manually set sAdbServerPort.
+ if (!sUnitTestMode) {
+ sAdbServerPort = getAdbServerPort();
+ }
+ sHostAddr = InetAddress.getByName(adbHost);
+ sSocketAddr = new InetSocketAddress(sHostAddr, sAdbServerPort);
+ } catch (UnknownHostException e) {
+ // localhost should always be known, but if it is not we would
+ // like to know.
+ Log.e(DDMS, "Unable to resolve: " + adbHost + ", due to:" + e);
+ }
+ }
+
+ /**
+ * Returns the port where adb server should be launched. This looks at:
+ * ddm library.
+ *
+ *
+ * true.
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.false.
The library only monitors
+ * devices. The applications are left untouched, letting other tools built on
+ * ddmlib to connect a debugger to them.ddmlib which acts as a proxy between the debuggers and
+ * the applications to debug. See {@link Client#getDebuggerListenPort()}.
+ * ddmlib should also be initialized with whatever default
+ * values were changed from the default values.
+ * forceNewBridge is set to false.
+ * @param osLocation the location of the command line tool 'adb'
+ * @param forceNewBridge force creation of a new bridge even if one with the same location
+ * already exists.
+ * @return a connected bridge, or null if there were errors while creating or connecting
+ * to the bridge
+ */
+ @Nullable
+ public static AndroidDebugBridge createBridge(@NonNull String osLocation,
+ boolean forceNewBridge) {
+ synchronized (sLock) {
+ if (!sUnitTestMode) {
+ 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);
+ if (!sThis.start()) {
+ return null;
+ }
+ } catch (InvalidParameterException e) {
+ sThis = null;
+ }
+
+ // notify the listeners of the change
+ for (IDebugBridgeChangeListener listener : sBridgeListeners) {
+ // we attempt to catch any exception so that a bad listener doesn't kill our thread
+ try {
+ listener.bridgeChanged(sThis);
+ } catch (Exception e) {
+ Log.e(DDMS, e);
+ }
+ }
+
+ return sThis;
+ }
+ }
+
+ /**
+ * Returns the current debug bridge. Can be null if none were created.
+ */
+ @Nullable
+ public static AndroidDebugBridge getBridge() {
+ return sThis;
+ }
+
+ /**
+ * Disconnects the current debug bridge, and destroy the object.
+ * adb that does not guarantee that the {@link IDevice} list has been
+ * built before the call to {@link #getDevices()}.
+ * IDevice.
+ * @see #getLock()
+ */
+ static void deviceConnected(@NonNull IDevice device) {
+ for (IDeviceChangeListener listener : sDeviceListeners) {
+ // 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}.
+ * IDevice.
+ * @see #getLock()
+ */
+ static void deviceDisconnected(@NonNull IDevice device) {
+ for (IDeviceChangeListener listener : sDeviceListeners) {
+ // 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}.
+ * IDevice.
+ * @see #getLock()
+ */
+ static void deviceChanged(@NonNull IDevice device, int changeMask) {
+ // Notify the listeners
+ for (IDeviceChangeListener listener : sDeviceListeners) {
+ // 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}.
+ * Client.
+ * @param changeMask the mask indicating what changed in the Client
+ * @see #getLock()
+ */
+ static void clientChanged(@NonNull Client client, int changeMask) {
+ // Notify the listeners
+ for (IClientChangeListener listener : sClientListeners) {
+ // 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 (sUnitTestMode) {
+ // in this case, we assume the FakeAdbServer was already setup by the test code
+ return true;
+ }
+
+ 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);
+ Map
+ *
+ *
+ * @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");
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/build/bundletool/BundleToolMain.java b/src/main/java/com/android/tools/build/bundletool/BundleToolMain.java
index 550756d7..2657ce68 100755
--- a/src/main/java/com/android/tools/build/bundletool/BundleToolMain.java
+++ b/src/main/java/com/android/tools/build/bundletool/BundleToolMain.java
@@ -15,6 +15,7 @@
*/
package com.android.tools.build.bundletool;
+import com.android.ddmlib.DdmPreferences;
import com.android.tools.build.bundletool.commands.BuildApksCommand;
import com.android.tools.build.bundletool.commands.BuildBundleCommand;
import com.android.tools.build.bundletool.commands.CommandHelp;
@@ -64,6 +65,13 @@ static void main(String[] args, Runtime runtime) {
return;
}
+ String adbhost = System.getenv("ADBHOST");
+ if (adbhost != null) {
+ System.out.println("Using ADBHOST=" + adbhost);
+ DdmPreferences.setAdbHostValue(adbhost);
+ DdmPreferences.setUseAdbHost(true);
+ }
+
try {
switch (command.get()) {
case BuildBundleCommand.COMMAND_NAME: