Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(132)

Side by Side Diff: dart/editor/tools/plugins/com.google.dart.tools.core/src/com/google/dart/tools/core/mobile/AndroidDebugBridge.java

Issue 337623003: Version 1.5.0-dev.4.11 (Closed) Base URL: http://dart.googlecode.com/svn/trunk/
Patch Set: Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 /* 1 /*
2 * Copyright (c) 2014, the Dart project authors. 2 * Copyright (c) 2014, the Dart project authors.
3 * 3 *
4 * Licensed under the Eclipse Public License v1.0 (the "License"); you may not u se this file except 4 * Licensed under the Eclipse Public License v1.0 (the "License"); you may not u se this file except
5 * in compliance with the License. You may obtain a copy of the License at 5 * in compliance with the License. You may obtain a copy of the License at
6 * 6 *
7 * http://www.eclipse.org/legal/epl-v10.html 7 * http://www.eclipse.org/legal/epl-v10.html
8 * 8 *
9 * Unless required by applicable law or agreed to in writing, software distribut ed under the License 9 * Unless required by applicable law or agreed to in writing, software distribut ed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY K IND, either express 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY K IND, either express
11 * or implied. See the License for the specific language governing permissions a nd limitations under 11 * or implied. See the License for the specific language governing permissions a nd limitations under
12 * the License. 12 * the License.
13 */ 13 */
14 package com.google.dart.tools.core.mobile; 14 package com.google.dart.tools.core.mobile;
15 15
16 import com.google.dart.engine.utilities.io.PrintStringWriter;
16 import com.google.dart.tools.core.DartCore; 17 import com.google.dart.tools.core.DartCore;
17 import com.google.dart.tools.core.dart2js.ProcessRunner; 18 import com.google.dart.tools.core.dart2js.ProcessRunner;
18 19
20 import org.eclipse.core.runtime.IStatus;
21 import org.eclipse.core.runtime.Status;
22
19 import java.io.File; 23 import java.io.File;
20 import java.io.IOException; 24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
21 import java.io.LineNumberReader; 27 import java.io.LineNumberReader;
22 import java.io.StringReader; 28 import java.io.StringReader;
29 import java.io.UnsupportedEncodingException;
23 import java.util.ArrayList; 30 import java.util.ArrayList;
31 import java.util.HashMap;
24 import java.util.HashSet; 32 import java.util.HashSet;
25 import java.util.List; 33 import java.util.List;
26 34
27 /** 35 /**
28 * Instance of class {@code AndroidDevBridge} represents the AndroidDevBridge (a db) in the Android 36 * Instance of class {@code AndroidDevBridge} represents the AndroidDevBridge (a db) in the Android
29 * SDK 37 * SDK
30 */ 38 */
31 public class AndroidDebugBridge { 39 public class AndroidDebugBridge {
32 40
33 private File adb; 41 /**
42 * Specialized process runner for ADB logcat
43 */
44 class LogcatRunner extends ProcessRunner {
34 45
35 private static String[] INSTALL_CMD = new String[] {"install"}; 46 private final String msgPrefix;
36 private static String[] DEVICES_CMD = new String[] {"devices"}; 47 private final PrintStringWriter output = new PrintStringWriter();
48 private final Object lock = new Object();
49 private IStatus result = null;
50
51 public LogcatRunner(ProcessBuilder processBuilder, String msgPrefix) {
52 super(processBuilder);
53 this.msgPrefix = msgPrefix;
54 }
55
56 public String getOutput() {
57 return output.toString();
58 }
59
60 /**
61 * Block until a result is available or the specified timeout expires.
62 *
63 * @return the status or {@code null} if the wait timed out
64 */
65 public IStatus waitForResult(long timeout) {
66 synchronized (lock) {
67 long endTime = System.currentTimeMillis() + timeout;
68 while (result == null) {
69 long delta = endTime - System.currentTimeMillis();
70 if (delta <= 0) {
71 break;
72 }
73 try {
74 lock.wait(delta);
75 } catch (InterruptedException e) {
76 //$FALL-THROUGH$
77 }
78 }
79 return result;
80 }
81 }
82
83 @Override
84 protected void pipeStdout(InputStream in, StringBuilder builder) {
85 try {
86 LineNumberReader reader = new LineNumberReader(new InputStreamReader(in, "UTF-8"));
87 while (true) {
88 String line = reader.readLine();
89 if (line == null) {
90 break;
91 }
92 int index = line.indexOf(msgPrefix);
93 if (index >= 0) {
94 String msg = line.substring(index + msgPrefix.length() + 1).trim();
95 output.println(msg);
96 if (msg.equals("Success")) {
97 setResult(Status.OK_STATUS);
98 } else if (msg.startsWith("Error:")) {
99 setResult(new Status(IStatus.ERROR, DartCore.PLUGIN_ID, msg.substr ing(6).trim()));
100 }
101 }
102 }
103 } catch (UnsupportedEncodingException e) {
104 DartCore.logError(e);
105 } catch (IOException e) {
106 // This exception is expected.
107 }
108 }
109
110 private void setResult(IStatus status) {
111 synchronized (lock) {
112 result = status;
113 lock.notifyAll();
114 }
115 }
116 }
117
118 private static final String CONTENT_SHELL_APK_ID = "org.chromium.content_shell _apk";
119 private static final String CONNECTION_TEST_APK_ID = "com.google.dart.editor.m obile.connection.service";
120
121 private static final String[] LIST_PACKAGES_CMD = new String[] {
122 "shell", "pm", "list", "packages", "-3"};
123 private static final String[] INSTALL_CMD = new String[] {"install", "-r"};
124 private static final String[] DEVICES_CMD = new String[] {"devices"};
37 125
38 private static final String DEVICE_CONNECTED_SUFFIX = "\tdevice"; 126 private static final String DEVICE_CONNECTED_SUFFIX = "\tdevice";
39 private static final String UNAUTHORIZED_SUFFIX = "\tunauthorized"; 127 private static final String UNAUTHORIZED_SUFFIX = "\tunauthorized";
40 128
129 private static final String[] START_SERVICE = new String[] {"shell", "am", "st artservice"};
130
41 private static String[] LAUNCH_URL_IN_CC_CMD = new String[] { 131 private static String[] LAUNCH_URL_IN_CC_CMD = new String[] {
42 "shell", "am", "start", "-n", "org.chromium.content_shell_apk/.ContentShel lActivity", "-d"}; 132 "shell", "am", "start", "-n", "org.chromium.content_shell_apk/.ContentShel lActivity", "-d"};
43 133
44 private static String[] LAUNCH_URL_IN_BROWSER_CMD = new String[] { 134 private static String[] LAUNCH_URL_IN_BROWSER_CMD = new String[] {
45 "shell", "am", "start", "-n", "com.android.chrome/com.google.android.apps. chrome.Main", "-d"}; 135 "shell", "am", "start", "-n", "com.android.chrome/com.google.android.apps. chrome.Main", "-d"};
46 136
47 private static String[] STOP_APP_CMD = new String[] { 137 private static String[] STOP_APP_CMD = new String[] {
48 "shell", "am", "force-stop", "org.chromium.content_shell_apk"}; 138 "shell", "am", "force-stop", CONTENT_SHELL_APK_ID};
49 139
50 private static final String CONTENT_SHELL_DEBUG_PORT = "localabstract:content_ shell_devtools_remote"; 140 private static final String CONTENT_SHELL_DEBUG_PORT = "localabstract:content_ shell_devtools_remote";
51 private static final String CHROME_DEBUG_PORT = "localabstract:chrome_devtools _remote"; 141 private static final String CHROME_DEBUG_PORT = "localabstract:chrome_devtools _remote";
52 142
53 private static String[] PORT_FORWARD_CMD = new String[] {"forward"}; 143 private static String[] PORT_FORWARD_CMD = new String[] {"forward"};
54 144
55 private static String[] UNINSTALL_CMD = new String[] { 145 private static String[] UNINSTALL_CMD = new String[] {"shell", "pm", "uninstal l", "-k"};
56 "shell", "pm", "uninstall", "-k", "org.chromium.content_shell_apk"};
57 146
58 private static String[] START_SERVER_CMD = new String[] {"start-server"}; 147 private static String[] START_SERVER_CMD = new String[] {"start-server"};
59 148
60 private ProcessRunner runner; 149 private static final String[] LOGCAT_CMD = new String[] {"logcat"};
61
62 /**
63 * The identifiers of devices on which content shell has been installed during this session.
64 */
65 private static HashSet<String> contentShellInstallSet = new HashSet<String>();
66 150
67 private static AndroidDebugBridge androidDebugBridge = new AndroidDebugBridge( 151 private static AndroidDebugBridge androidDebugBridge = new AndroidDebugBridge(
68 AndroidSdkManager.getManager().getAdbExecutable()); 152 AndroidSdkManager.getManager().getAdbExecutable());
69 153
70 public static AndroidDebugBridge getAndroidDebugBridge() { 154 public static AndroidDebugBridge getAndroidDebugBridge() {
71 return androidDebugBridge; 155 return androidDebugBridge;
72 } 156 }
73 157
74 AndroidDebugBridge(File adbExecutable) { 158 private File adbExecutable;
75 this.adb = adbExecutable; 159 private ProcessRunner adbRunner;
160 private final HashMap<String, HashSet<String>> installSet = new HashMap<String , HashSet<String>>();
161
162 private AndroidDebugBridge(File adbExecutable) {
163 this.adbExecutable = adbExecutable;
76 } 164 }
77 165
78 /** 166 /**
79 * Gets the first device connected and detected by adb 167 * Gets the first device connected and detected by adb
80 * 168 *
81 * @return the device or {@code null} if no device detected 169 * @return the device or {@code null} if no device detected
82 */ 170 */
83 public AndroidDevice getConnectedDevice() { 171 public AndroidDevice getConnectedDevice() {
84 List<String> args = buildAdbCommand(DEVICES_CMD); 172 List<String> args = buildAdbCommand(DEVICES_CMD);
85 if (runAdb(args)) { 173 if (runAdb(args)) {
86 //List of devices attached 174 //List of devices attached
87 //04f5385f95d80610 device 175 //04f5385f95d80610 device
88 //T062873654 unauthorized 176 //T062873654 unauthorized
89 String unauthorized = null; 177 String unauthorized = null;
90 LineNumberReader reader = new LineNumberReader(new StringReader(runner.get StdOut())); 178 LineNumberReader reader = new LineNumberReader(new StringReader(adbRunner. getStdOut()));
91 try { 179 try {
92 while (true) { 180 while (true) {
93 String line = reader.readLine(); 181 String line = reader.readLine();
94 if (line == null) { 182 if (line == null) {
95 break; 183 break;
96 } 184 }
97 line = line.trim(); 185 line = line.trim();
98 if (line.endsWith(DEVICE_CONNECTED_SUFFIX)) { 186 if (line.endsWith(DEVICE_CONNECTED_SUFFIX)) {
99 String id = line.substring(0, line.length() - DEVICE_CONNECTED_SUFFI X.length()).trim(); 187 String id = line.substring(0, line.length() - DEVICE_CONNECTED_SUFFI X.length()).trim();
100 return new AndroidDevice(id, true); 188 return new AndroidDevice(id, true);
(...skipping 11 matching lines...) Expand all
112 } 200 }
113 return null; 201 return null;
114 } 202 }
115 203
116 /** 204 /**
117 * Install the apk for the content shell onto the connected phone 205 * Install the apk for the content shell onto the connected phone
118 * <p> 206 * <p>
119 * adb install path/to/apk 207 * adb install path/to/apk
120 * </p> 208 * </p>
121 * 209 *
122 * @param deviceId the identifier of the device on which to install the conten t shell 210 * @param device the device on which to install the content shell
123 * @return true if install was successful 211 * @return true if install was successful
124 */ 212 */
125 public boolean installContentShellApk(String deviceId) { 213 public boolean installContentShellApk(AndroidDevice device) {
126 if (contentShellInstallSet.contains(deviceId)) { 214 return install(
127 return true; 215 device,
128 } 216 "dart content shell browser",
129 // TODO(keertip): process error to check if apk is already installed 217 CONTENT_SHELL_APK_ID,
130 List<String> args = buildAdbCommand(INSTALL_CMD); 218 AndroidSdkManager.getManager().getContentShellApkLocation());
131 if (deviceId != null) {
132 args.add(1, "-s");
133 args.add(2, deviceId);
134 }
135 args.add(AndroidSdkManager.getManager().getContentShellApkLocation());
136 if (runAdb(args, "ADB: install dart content shell browser", "This could take up to 30 seconds")) {
137 String message = runner.getStdOut();
138 // if the apk is present, message is Failure [INSTALL_FAILED_ALREADY_EXIST S]
139 if (message.toLowerCase().contains("already_exists")) {
140 // TODO(keertip): check version and reinstall
141 // DartCore.getConsole().println(message);
142 }
143 contentShellInstallSet.add(deviceId);
144 return true;
145 }
146 return false;
147 } 219 }
148 220
149 /** 221 /**
150 * Determine if a mobile device is connected and authorized. 222 * Determine if a mobile device is connected and authorized.
151 */ 223 */
152 public boolean isDeviceConnectedAndAuthorized() { 224 public boolean isDeviceConnectedAndAuthorized() {
153 AndroidDevice device = getConnectedDevice(); 225 AndroidDevice device = getConnectedDevice();
154 return device != null && device.isAuthorized(); 226 return device != null && device.isAuthorized();
155 } 227 }
156 228
157 /** 229 /**
230 * Determine if the given URL is accessible from the mobile device.
231 */
232 public IStatus isHtmlPageAccessible(AndroidDevice device, String pageUrl) {
233 if (!installConnectionTestApk(device)) {
234 return new Status(IStatus.ERROR, DartCore.PLUGIN_ID, "Failed to install co nnection test");
235 }
236
237 String msgPrefix = "Connection test (" + System.currentTimeMillis() + ")";
238 LogcatRunner logcatRunner = new LogcatRunner(
239 new ProcessBuilder(buildAdbCommand(LOGCAT_CMD)),
240 msgPrefix);
241 try {
242 logcatRunner.runAsync();
243 } catch (IOException e) {
244 DartCore.logError(e);
245 DartCore.getConsole().println("Failed to launch ADB logcat");
246 return new Status(IStatus.ERROR, DartCore.PLUGIN_ID, "Failed to launch ADB logcat", e);
247 }
248
249 // Launch the service that tests the connection from mobile device to develo per machine
250 List<String> args = buildAdbCommand(START_SERVICE);
251 args.add("-n");
252 args.add("com.google.dart.editor.mobile.connection.service/.ConnectionServic e");
253 args.add("-d");
254 args.add(pageUrl);
255 args.add("-e");
256 args.add("prefix");
257 args.add(msgPrefix);
258 try {
259 if (!runAdb(args, "ADB: check port forwarding")) {
260 return new Status(
261 IStatus.ERROR,
262 DartCore.PLUGIN_ID,
263 "Failed to launch port forwarding detection");
264 }
265 IStatus result = logcatRunner.waitForResult(3500);
266 if (result != null) {
267 if (!result.isOK()) {
268 //DartCore.getConsole().println(logcatRunner.getOutput());
269 DartCore.getConsole().println(result.getMessage());
270 }
271 return result;
272 }
273 return new Status(
274 IStatus.ERROR,
275 DartCore.PLUGIN_ID,
276 "Timeout waiting for port forwarding detection");
277 } finally {
278 logcatRunner.dispose();
279 }
280 }
281
282 /**
158 * Open the url in the chrome browser on the device 283 * Open the url in the chrome browser on the device
159 * <p> 284 * <p>
160 * adb shell am start com.android.chrome/com.google.android.apps.chrome.Main - d url 285 * adb shell am start com.android.chrome/com.google.android.apps.chrome.Main - d url
161 * </p> 286 * </p>
162 */ 287 */
163 public boolean launchChromeBrowser(String url) { 288 public boolean launchChromeBrowser(String url) {
164 List<String> args = buildAdbCommand(LAUNCH_URL_IN_BROWSER_CMD); 289 List<String> args = buildAdbCommand(LAUNCH_URL_IN_BROWSER_CMD);
165 args.add(url); 290 args.add(url);
166 return runAdb(args, "ADB: launch browser"); 291 return runAdb(args, "ADB: launch browser");
167 } 292 }
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
201 args.add("tcp:" + port); 326 args.add("tcp:" + port);
202 args.add(CONTENT_SHELL_DEBUG_PORT); 327 args.add(CONTENT_SHELL_DEBUG_PORT);
203 return runAdb(args, ""); 328 return runAdb(args, "");
204 } 329 }
205 330
206 /** 331 /**
207 * Starts the adb server 332 * Starts the adb server
208 */ 333 */
209 public void startAdbServer() { 334 public void startAdbServer() {
210 List<String> args = buildAdbCommand(START_SERVER_CMD); 335 List<String> args = buildAdbCommand(START_SERVER_CMD);
211 runAdb(args, ""); 336 runAdb(args);
212 } 337 }
213 338
214 /** 339 /**
215 * Force stop (close) the content shell on the connected phone 340 * Force stop (close) the content shell on the connected phone
216 * <p> 341 * <p>
217 * adb shell am force-stop org.chromium.content_shell_apk 342 * adb shell am force-stop org.chromium.content_shell_apk
218 * </p> 343 * </p>
219 * 344 *
220 * @return true if stop was successful 345 * @return true if stop was successful
221 */ 346 */
222 public boolean stopApplication() { 347 public boolean stopApplication() {
223 List<String> args = buildAdbCommand(STOP_APP_CMD); 348 List<String> args = buildAdbCommand(STOP_APP_CMD);
224 return runAdb(args, "ADB: stop application"); 349 return runAdb(args, "ADB: stop application");
225 } 350 }
226 351
227 /** 352 public void uninstallConnectionTestApk(AndroidDevice device) {
228 * Uninstall the content shell apk from the device 353 if (!uninstall(device, CONNECTION_TEST_APK_ID)) {
229 */ 354 DartCore.logError("Failed to uninstall " + CONNECTION_TEST_APK_ID);
230 void uninstallContentShellApk() { 355 }
231 List<String> args = buildAdbCommand(UNINSTALL_CMD);
232 runAdb(args, "ADB: uninstall browser");
233 } 356 }
234 357
235 private List<String> buildAdbCommand(String[] cmds) { 358 /**
359 * Uninstall the content shell
360 */
361 public void uninstallContentShellApk(AndroidDevice device) {
362 uninstall(device, CONTENT_SHELL_APK_ID);
363 }
364
365 private List<String> buildAdbCommand(String... cmds) {
236 List<String> args = new ArrayList<String>(); 366 List<String> args = new ArrayList<String>();
237 args.add(adb.getAbsolutePath()); 367 args.add(adbExecutable.getAbsolutePath());
238 if (cmds != null) { 368 if (cmds != null) {
239 for (String string : cmds) { 369 for (String string : cmds) {
240 args.add(string); 370 args.add(string);
241 } 371 }
242 } 372 }
243 return args; 373 return args;
244 } 374 }
245 375
376 /**
377 * Install the specified APK onto the mobile device if this is the first insta llation during this
378 * sessions --or-- if the APK has been removed since the beginning of this ses sion.
379 *
380 * @param device the device onto which the APK will be installed
381 * @param apkName the human readable name of the APK
382 * @param apkId TODO
383 * @param apkLocation the APK file to be installed
384 * @return {@code true} if the APK was installed or already resides on the mob ile device
385 */
386 private boolean install(AndroidDevice device, String apkName, String apkId, St ring apkLocation) {
387
388 // Check if the APK has been installed and still resides on the mobile devic e
389 if (device != null) {
390 HashSet<String> apkIds = installSet.get(device.getDeviceId());
391 if (apkIds == null) {
392 apkIds = new HashSet<String>();
393 installSet.put(device.getDeviceId(), apkIds);
394 }
395 if (apkIds.contains(apkId) && isInstalled(device, apkId)) {
396 return true;
397 }
398 apkIds.add(apkId);
399 }
400
401 // Uninstall the old APK if it exists
402 //uninstall(device, apkId);
403
404 // Install the APK
405 // adb install -r <path> to replace previously installed APK if present
406 List<String> args = buildAdbCommand(INSTALL_CMD);
407 if (device != null) {
408 args.add(1, "-s");
409 args.add(2, device.getDeviceId());
410 }
411 args.add(apkLocation);
412 return runAdb(args, "ADB: install " + apkName, "This could take up to 30 sec onds");
413 }
414
415 private boolean installConnectionTestApk(AndroidDevice device) {
416 return install(
417 device,
418 "connection test",
419 CONNECTION_TEST_APK_ID,
420 AndroidSdkManager.getManager().getConnectionTestApkLocation());
421 }
422
423 /**
424 * Query the mobile device to determine if the specified APK is installed.
425 *
426 * @param device the device
427 * @param apkId the APK identifier
428 * @return {@code true} if installed, else {@code false}
429 */
430 private boolean isInstalled(AndroidDevice device, String apkId) {
431 List<String> args = buildAdbCommand(LIST_PACKAGES_CMD);
432 if (device != null) {
433 args.add(1, "-s");
434 args.add(2, device.getDeviceId());
435 }
436 if (runAdb(args)) {
437 // Output is list of apk identifiers prefixed by "package:"
438 //package:com.google.dart.editor.mobile.connection.service
439 //package:org.chromium.content_shell_apk
440 String target = "package:" + apkId;
441 LineNumberReader reader = new LineNumberReader(new StringReader(adbRunner. getStdOut()));
442 try {
443 while (true) {
444 String line = reader.readLine();
445 if (line == null) {
446 break;
447 }
448 line = line.trim();
449 if (line.equals(target)) {
450 return true;
451 }
452 }
453 } catch (IOException e) {
454 //$FALL-THROUGH$
455 }
456 }
457 return false;
458 }
459
246 private boolean runAdb(List<String> args, String... message) { 460 private boolean runAdb(List<String> args, String... message) {
247 int exitCode = 1; 461 int exitCode = 1;
248 ProcessBuilder builder = new ProcessBuilder(); 462 ProcessBuilder builder = new ProcessBuilder();
249 builder.command(args); 463 builder.command(args);
250 runner = new ProcessRunner(builder); 464 adbRunner = new ProcessRunner(builder);
251 for (int index = 0; index < message.length; index++) { 465 for (int index = 0; index < message.length; index++) {
252 if (index == 0) { 466 if (index == 0) {
253 DartCore.getConsole().printSeparator(message[index]); 467 DartCore.getConsole().printSeparator(message[index]);
254 } else { 468 } else {
255 DartCore.getConsole().println(message[index]); 469 DartCore.getConsole().println(message[index]);
256 } 470 }
257 } 471 }
258 try { 472 try {
259 exitCode = runner.runSync(null); 473 exitCode = adbRunner.runSync(null);
260 if (exitCode != 0) { 474 if (exitCode != 0) {
261 DartCore.getConsole().println(runner.getStdErr()); 475 DartCore.getConsole().println(adbRunner.getStdErr());
262 } 476 }
263 477
264 } catch (IOException e) { 478 } catch (IOException e) {
265 DartCore.logError(e); 479 DartCore.logError(e);
266 } 480 }
267 return exitCode == 0 ? true : false; 481 return exitCode == 0 ? true : false;
268 } 482 }
483
484 private boolean uninstall(AndroidDevice device, String apkId) {
485 List<String> args = buildAdbCommand(UNINSTALL_CMD);
486 if (device != null) {
487 args.add(1, "-s");
488 args.add(2, device.getDeviceId());
489 }
490 args.add(apkId);
491 return runAdb(args, "ADB: uninstall " + apkId);
492 }
269 } 493 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698