| Index: dart/editor/tools/plugins/com.google.dart.tools.core/src/com/google/dart/tools/core/mobile/AndroidDebugBridge.java
|
| ===================================================================
|
| --- dart/editor/tools/plugins/com.google.dart.tools.core/src/com/google/dart/tools/core/mobile/AndroidDebugBridge.java (revision 37301)
|
| +++ dart/editor/tools/plugins/com.google.dart.tools.core/src/com/google/dart/tools/core/mobile/AndroidDebugBridge.java (working copy)
|
| @@ -13,14 +13,22 @@
|
| */
|
| package com.google.dart.tools.core.mobile;
|
|
|
| +import com.google.dart.engine.utilities.io.PrintStringWriter;
|
| import com.google.dart.tools.core.DartCore;
|
| import com.google.dart.tools.core.dart2js.ProcessRunner;
|
|
|
| +import org.eclipse.core.runtime.IStatus;
|
| +import org.eclipse.core.runtime.Status;
|
| +
|
| import java.io.File;
|
| import java.io.IOException;
|
| +import java.io.InputStream;
|
| +import java.io.InputStreamReader;
|
| import java.io.LineNumberReader;
|
| import java.io.StringReader;
|
| +import java.io.UnsupportedEncodingException;
|
| import java.util.ArrayList;
|
| +import java.util.HashMap;
|
| import java.util.HashSet;
|
| import java.util.List;
|
|
|
| @@ -30,14 +38,96 @@
|
| */
|
| public class AndroidDebugBridge {
|
|
|
| - private File adb;
|
| + /**
|
| + * Specialized process runner for ADB logcat
|
| + */
|
| + class LogcatRunner extends ProcessRunner {
|
|
|
| - private static String[] INSTALL_CMD = new String[] {"install"};
|
| - private static String[] DEVICES_CMD = new String[] {"devices"};
|
| + private final String msgPrefix;
|
| + private final PrintStringWriter output = new PrintStringWriter();
|
| + private final Object lock = new Object();
|
| + private IStatus result = null;
|
|
|
| + public LogcatRunner(ProcessBuilder processBuilder, String msgPrefix) {
|
| + super(processBuilder);
|
| + this.msgPrefix = msgPrefix;
|
| + }
|
| +
|
| + public String getOutput() {
|
| + return output.toString();
|
| + }
|
| +
|
| + /**
|
| + * Block until a result is available or the specified timeout expires.
|
| + *
|
| + * @return the status or {@code null} if the wait timed out
|
| + */
|
| + public IStatus waitForResult(long timeout) {
|
| + synchronized (lock) {
|
| + long endTime = System.currentTimeMillis() + timeout;
|
| + while (result == null) {
|
| + long delta = endTime - System.currentTimeMillis();
|
| + if (delta <= 0) {
|
| + break;
|
| + }
|
| + try {
|
| + lock.wait(delta);
|
| + } catch (InterruptedException e) {
|
| + //$FALL-THROUGH$
|
| + }
|
| + }
|
| + return result;
|
| + }
|
| + }
|
| +
|
| + @Override
|
| + protected void pipeStdout(InputStream in, StringBuilder builder) {
|
| + try {
|
| + LineNumberReader reader = new LineNumberReader(new InputStreamReader(in, "UTF-8"));
|
| + while (true) {
|
| + String line = reader.readLine();
|
| + if (line == null) {
|
| + break;
|
| + }
|
| + int index = line.indexOf(msgPrefix);
|
| + if (index >= 0) {
|
| + String msg = line.substring(index + msgPrefix.length() + 1).trim();
|
| + output.println(msg);
|
| + if (msg.equals("Success")) {
|
| + setResult(Status.OK_STATUS);
|
| + } else if (msg.startsWith("Error:")) {
|
| + setResult(new Status(IStatus.ERROR, DartCore.PLUGIN_ID, msg.substring(6).trim()));
|
| + }
|
| + }
|
| + }
|
| + } catch (UnsupportedEncodingException e) {
|
| + DartCore.logError(e);
|
| + } catch (IOException e) {
|
| + // This exception is expected.
|
| + }
|
| + }
|
| +
|
| + private void setResult(IStatus status) {
|
| + synchronized (lock) {
|
| + result = status;
|
| + lock.notifyAll();
|
| + }
|
| + }
|
| + }
|
| +
|
| + private static final String CONTENT_SHELL_APK_ID = "org.chromium.content_shell_apk";
|
| + private static final String CONNECTION_TEST_APK_ID = "com.google.dart.editor.mobile.connection.service";
|
| +
|
| + private static final String[] LIST_PACKAGES_CMD = new String[] {
|
| + "shell", "pm", "list", "packages", "-3"};
|
| + private static final String[] INSTALL_CMD = new String[] {"install", "-r"};
|
| + private static final String[] DEVICES_CMD = new String[] {"devices"};
|
| +
|
| private static final String DEVICE_CONNECTED_SUFFIX = "\tdevice";
|
| private static final String UNAUTHORIZED_SUFFIX = "\tunauthorized";
|
|
|
| + private static final String[] START_SERVICE = new String[] {"shell", "am", "startservice"};
|
| +
|
| private static String[] LAUNCH_URL_IN_CC_CMD = new String[] {
|
| "shell", "am", "start", "-n", "org.chromium.content_shell_apk/.ContentShellActivity", "-d"};
|
|
|
| @@ -45,25 +135,19 @@
|
| "shell", "am", "start", "-n", "com.android.chrome/com.google.android.apps.chrome.Main", "-d"};
|
|
|
| private static String[] STOP_APP_CMD = new String[] {
|
| - "shell", "am", "force-stop", "org.chromium.content_shell_apk"};
|
| + "shell", "am", "force-stop", CONTENT_SHELL_APK_ID};
|
|
|
| private static final String CONTENT_SHELL_DEBUG_PORT = "localabstract:content_shell_devtools_remote";
|
| private static final String CHROME_DEBUG_PORT = "localabstract:chrome_devtools_remote";
|
|
|
| private static String[] PORT_FORWARD_CMD = new String[] {"forward"};
|
|
|
| - private static String[] UNINSTALL_CMD = new String[] {
|
| - "shell", "pm", "uninstall", "-k", "org.chromium.content_shell_apk"};
|
| + private static String[] UNINSTALL_CMD = new String[] {"shell", "pm", "uninstall", "-k"};
|
|
|
| private static String[] START_SERVER_CMD = new String[] {"start-server"};
|
|
|
| - private ProcessRunner runner;
|
| + private static final String[] LOGCAT_CMD = new String[] {"logcat"};
|
|
|
| - /**
|
| - * The identifiers of devices on which content shell has been installed during this session.
|
| - */
|
| - private static HashSet<String> contentShellInstallSet = new HashSet<String>();
|
| -
|
| private static AndroidDebugBridge androidDebugBridge = new AndroidDebugBridge(
|
| AndroidSdkManager.getManager().getAdbExecutable());
|
|
|
| @@ -71,8 +155,12 @@
|
| return androidDebugBridge;
|
| }
|
|
|
| - AndroidDebugBridge(File adbExecutable) {
|
| - this.adb = adbExecutable;
|
| + private File adbExecutable;
|
| + private ProcessRunner adbRunner;
|
| + private final HashMap<String, HashSet<String>> installSet = new HashMap<String, HashSet<String>>();
|
| +
|
| + private AndroidDebugBridge(File adbExecutable) {
|
| + this.adbExecutable = adbExecutable;
|
| }
|
|
|
| /**
|
| @@ -87,7 +175,7 @@
|
| //04f5385f95d80610 device
|
| //T062873654 unauthorized
|
| String unauthorized = null;
|
| - LineNumberReader reader = new LineNumberReader(new StringReader(runner.getStdOut()));
|
| + LineNumberReader reader = new LineNumberReader(new StringReader(adbRunner.getStdOut()));
|
| try {
|
| while (true) {
|
| String line = reader.readLine();
|
| @@ -119,31 +207,15 @@
|
| * adb install path/to/apk
|
| * </p>
|
| *
|
| - * @param deviceId the identifier of the device on which to install the content shell
|
| + * @param device the device on which to install the content shell
|
| * @return true if install was successful
|
| */
|
| - public boolean installContentShellApk(String deviceId) {
|
| - if (contentShellInstallSet.contains(deviceId)) {
|
| - return true;
|
| - }
|
| - // TODO(keertip): process error to check if apk is already installed
|
| - List<String> args = buildAdbCommand(INSTALL_CMD);
|
| - if (deviceId != null) {
|
| - args.add(1, "-s");
|
| - args.add(2, deviceId);
|
| - }
|
| - args.add(AndroidSdkManager.getManager().getContentShellApkLocation());
|
| - if (runAdb(args, "ADB: install dart content shell browser", "This could take up to 30 seconds")) {
|
| - String message = runner.getStdOut();
|
| - // if the apk is present, message is Failure [INSTALL_FAILED_ALREADY_EXISTS]
|
| - if (message.toLowerCase().contains("already_exists")) {
|
| - // TODO(keertip): check version and reinstall
|
| - // DartCore.getConsole().println(message);
|
| - }
|
| - contentShellInstallSet.add(deviceId);
|
| - return true;
|
| - }
|
| - return false;
|
| + public boolean installContentShellApk(AndroidDevice device) {
|
| + return install(
|
| + device,
|
| + "dart content shell browser",
|
| + CONTENT_SHELL_APK_ID,
|
| + AndroidSdkManager.getManager().getContentShellApkLocation());
|
| }
|
|
|
| /**
|
| @@ -155,6 +227,59 @@
|
| }
|
|
|
| /**
|
| + * Determine if the given URL is accessible from the mobile device.
|
| + */
|
| + public IStatus isHtmlPageAccessible(AndroidDevice device, String pageUrl) {
|
| + if (!installConnectionTestApk(device)) {
|
| + return new Status(IStatus.ERROR, DartCore.PLUGIN_ID, "Failed to install connection test");
|
| + }
|
| +
|
| + String msgPrefix = "Connection test (" + System.currentTimeMillis() + ")";
|
| + LogcatRunner logcatRunner = new LogcatRunner(
|
| + new ProcessBuilder(buildAdbCommand(LOGCAT_CMD)),
|
| + msgPrefix);
|
| + try {
|
| + logcatRunner.runAsync();
|
| + } catch (IOException e) {
|
| + DartCore.logError(e);
|
| + DartCore.getConsole().println("Failed to launch ADB logcat");
|
| + return new Status(IStatus.ERROR, DartCore.PLUGIN_ID, "Failed to launch ADB logcat", e);
|
| + }
|
| +
|
| + // Launch the service that tests the connection from mobile device to developer machine
|
| + List<String> args = buildAdbCommand(START_SERVICE);
|
| + args.add("-n");
|
| + args.add("com.google.dart.editor.mobile.connection.service/.ConnectionService");
|
| + args.add("-d");
|
| + args.add(pageUrl);
|
| + args.add("-e");
|
| + args.add("prefix");
|
| + args.add(msgPrefix);
|
| + try {
|
| + if (!runAdb(args, "ADB: check port forwarding")) {
|
| + return new Status(
|
| + IStatus.ERROR,
|
| + DartCore.PLUGIN_ID,
|
| + "Failed to launch port forwarding detection");
|
| + }
|
| + IStatus result = logcatRunner.waitForResult(3500);
|
| + if (result != null) {
|
| + if (!result.isOK()) {
|
| + //DartCore.getConsole().println(logcatRunner.getOutput());
|
| + DartCore.getConsole().println(result.getMessage());
|
| + }
|
| + return result;
|
| + }
|
| + return new Status(
|
| + IStatus.ERROR,
|
| + DartCore.PLUGIN_ID,
|
| + "Timeout waiting for port forwarding detection");
|
| + } finally {
|
| + logcatRunner.dispose();
|
| + }
|
| + }
|
| +
|
| + /**
|
| * Open the url in the chrome browser on the device
|
| * <p>
|
| * adb shell am start com.android.chrome/com.google.android.apps.chrome.Main -d url
|
| @@ -208,7 +333,7 @@
|
| */
|
| public void startAdbServer() {
|
| List<String> args = buildAdbCommand(START_SERVER_CMD);
|
| - runAdb(args, "");
|
| + runAdb(args);
|
| }
|
|
|
| /**
|
| @@ -224,17 +349,22 @@
|
| return runAdb(args, "ADB: stop application");
|
| }
|
|
|
| + public void uninstallConnectionTestApk(AndroidDevice device) {
|
| + if (!uninstall(device, CONNECTION_TEST_APK_ID)) {
|
| + DartCore.logError("Failed to uninstall " + CONNECTION_TEST_APK_ID);
|
| + }
|
| + }
|
| +
|
| /**
|
| - * Uninstall the content shell apk from the device
|
| + * Uninstall the content shell
|
| */
|
| - void uninstallContentShellApk() {
|
| - List<String> args = buildAdbCommand(UNINSTALL_CMD);
|
| - runAdb(args, "ADB: uninstall browser");
|
| + public void uninstallContentShellApk(AndroidDevice device) {
|
| + uninstall(device, CONTENT_SHELL_APK_ID);
|
| }
|
|
|
| - private List<String> buildAdbCommand(String[] cmds) {
|
| + private List<String> buildAdbCommand(String... cmds) {
|
| List<String> args = new ArrayList<String>();
|
| - args.add(adb.getAbsolutePath());
|
| + args.add(adbExecutable.getAbsolutePath());
|
| if (cmds != null) {
|
| for (String string : cmds) {
|
| args.add(string);
|
| @@ -243,11 +373,95 @@
|
| return args;
|
| }
|
|
|
| + /**
|
| + * Install the specified APK onto the mobile device if this is the first installation during this
|
| + * sessions --or-- if the APK has been removed since the beginning of this session.
|
| + *
|
| + * @param device the device onto which the APK will be installed
|
| + * @param apkName the human readable name of the APK
|
| + * @param apkId TODO
|
| + * @param apkLocation the APK file to be installed
|
| + * @return {@code true} if the APK was installed or already resides on the mobile device
|
| + */
|
| + private boolean install(AndroidDevice device, String apkName, String apkId, String apkLocation) {
|
| +
|
| + // Check if the APK has been installed and still resides on the mobile device
|
| + if (device != null) {
|
| + HashSet<String> apkIds = installSet.get(device.getDeviceId());
|
| + if (apkIds == null) {
|
| + apkIds = new HashSet<String>();
|
| + installSet.put(device.getDeviceId(), apkIds);
|
| + }
|
| + if (apkIds.contains(apkId) && isInstalled(device, apkId)) {
|
| + return true;
|
| + }
|
| + apkIds.add(apkId);
|
| + }
|
| +
|
| + // Uninstall the old APK if it exists
|
| + //uninstall(device, apkId);
|
| +
|
| + // Install the APK
|
| + // adb install -r <path> to replace previously installed APK if present
|
| + List<String> args = buildAdbCommand(INSTALL_CMD);
|
| + if (device != null) {
|
| + args.add(1, "-s");
|
| + args.add(2, device.getDeviceId());
|
| + }
|
| + args.add(apkLocation);
|
| + return runAdb(args, "ADB: install " + apkName, "This could take up to 30 seconds");
|
| + }
|
| +
|
| + private boolean installConnectionTestApk(AndroidDevice device) {
|
| + return install(
|
| + device,
|
| + "connection test",
|
| + CONNECTION_TEST_APK_ID,
|
| + AndroidSdkManager.getManager().getConnectionTestApkLocation());
|
| + }
|
| +
|
| + /**
|
| + * Query the mobile device to determine if the specified APK is installed.
|
| + *
|
| + * @param device the device
|
| + * @param apkId the APK identifier
|
| + * @return {@code true} if installed, else {@code false}
|
| + */
|
| + private boolean isInstalled(AndroidDevice device, String apkId) {
|
| + List<String> args = buildAdbCommand(LIST_PACKAGES_CMD);
|
| + if (device != null) {
|
| + args.add(1, "-s");
|
| + args.add(2, device.getDeviceId());
|
| + }
|
| + if (runAdb(args)) {
|
| + // Output is list of apk identifiers prefixed by "package:"
|
| + //package:com.google.dart.editor.mobile.connection.service
|
| + //package:org.chromium.content_shell_apk
|
| + String target = "package:" + apkId;
|
| + LineNumberReader reader = new LineNumberReader(new StringReader(adbRunner.getStdOut()));
|
| + try {
|
| + while (true) {
|
| + String line = reader.readLine();
|
| + if (line == null) {
|
| + break;
|
| + }
|
| + line = line.trim();
|
| + if (line.equals(target)) {
|
| + return true;
|
| + }
|
| + }
|
| + } catch (IOException e) {
|
| + //$FALL-THROUGH$
|
| + }
|
| + }
|
| + return false;
|
| + }
|
| +
|
| private boolean runAdb(List<String> args, String... message) {
|
| int exitCode = 1;
|
| ProcessBuilder builder = new ProcessBuilder();
|
| builder.command(args);
|
| - runner = new ProcessRunner(builder);
|
| + adbRunner = new ProcessRunner(builder);
|
| for (int index = 0; index < message.length; index++) {
|
| if (index == 0) {
|
| DartCore.getConsole().printSeparator(message[index]);
|
| @@ -256,9 +470,9 @@
|
| }
|
| }
|
| try {
|
| - exitCode = runner.runSync(null);
|
| + exitCode = adbRunner.runSync(null);
|
| if (exitCode != 0) {
|
| - DartCore.getConsole().println(runner.getStdErr());
|
| + DartCore.getConsole().println(adbRunner.getStdErr());
|
| }
|
|
|
| } catch (IOException e) {
|
| @@ -266,4 +480,14 @@
|
| }
|
| return exitCode == 0 ? true : false;
|
| }
|
| +
|
| + private boolean uninstall(AndroidDevice device, String apkId) {
|
| + List<String> args = buildAdbCommand(UNINSTALL_CMD);
|
| + if (device != null) {
|
| + args.add(1, "-s");
|
| + args.add(2, device.getDeviceId());
|
| + }
|
| + args.add(apkId);
|
| + return runAdb(args, "ADB: uninstall " + apkId);
|
| + }
|
| }
|
|
|