mirror of
https://github.com/processing/processing4.git
synced 2026-06-16 04:26:26 +02:00
Changes to make the detection of bad emulator behavior more robust. For example, if you kill the emulator while the sketch is launching on it, it seems to react appropriately now. There's a lot of noise coming out on stderr now; I'll clean that up once I've satisfied myself that it's reliable.
This commit is contained in:
@@ -49,6 +49,9 @@ class AndroidDevice implements AndroidDeviceProperties {
|
||||
|
||||
// safe to just always include the -r (reinstall) flag
|
||||
public boolean installApp(final String apkPath, final RunnerListener status) {
|
||||
if (!isAlive()) {
|
||||
return false;
|
||||
}
|
||||
bringLauncherToFront();
|
||||
try {
|
||||
final ProcessResult installResult = adb("install", "-r", apkPath);
|
||||
@@ -80,6 +83,9 @@ class AndroidDevice implements AndroidDeviceProperties {
|
||||
// http://asantoso.wordpress.com/2009/09/26/using-jdb-with-adb-to-debugging-of-android-app-on-a-real-device/
|
||||
public boolean launchApp(final String packageName, final String className)
|
||||
throws IOException, InterruptedException {
|
||||
if (!isAlive()) {
|
||||
return false;
|
||||
}
|
||||
return adb("shell", "am", "start", "-e", "debug", "true", "-a",
|
||||
"android.intent.action.MAIN", "-c", "android.intent.category.LAUNCHER",
|
||||
"-n", packageName + "/." + className).succeeded();
|
||||
@@ -173,16 +179,21 @@ class AndroidDevice implements AndroidDeviceProperties {
|
||||
System.err.println("Receiving log entries from " + id);
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
synchronized void shutdown() {
|
||||
System.err.println(id + " is shutting down.");
|
||||
if (logcat != null) {
|
||||
logcat.destroy();
|
||||
logcat = null;
|
||||
ProcessRegistry.unwatch(logcat);
|
||||
}
|
||||
env.deviceRemoved(this);
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
synchronized boolean isAlive() {
|
||||
return logcat != null;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
@@ -215,7 +226,7 @@ class AndroidDevice implements AndroidDeviceProperties {
|
||||
private ProcessResult adb(final String... cmd) throws InterruptedException,
|
||||
IOException {
|
||||
final String[] adbCmd = generateAdbCommand(cmd);
|
||||
return new ProcessHelper(adbCmd).execute();
|
||||
return AndroidSDK.runADB(adbCmd);
|
||||
}
|
||||
|
||||
private String[] generateAdbCommand(final String... cmd) {
|
||||
|
||||
@@ -43,7 +43,7 @@ class AndroidEnvironment {
|
||||
System.err.print("Shutting down any existing adb server...");
|
||||
System.err.flush();
|
||||
try {
|
||||
new ProcessHelper("adb", "kill-server").execute();
|
||||
AndroidSDK.runADB("kill-server");
|
||||
System.err.println("OK.");
|
||||
} catch (final Exception e) {
|
||||
System.err.println("failed.");
|
||||
@@ -55,16 +55,6 @@ class AndroidEnvironment {
|
||||
private AndroidEnvironment() {
|
||||
System.err.println("Starting up AndroidEnvironment");
|
||||
killAdbServer();
|
||||
System.err.print("Starting up fresh adb server...");
|
||||
System.err.flush();
|
||||
try {
|
||||
new ProcessHelper("adb", "start-server").execute();
|
||||
System.err.println("OK.");
|
||||
} catch (final Exception e) {
|
||||
System.err.println("failed.");
|
||||
System.err.println();
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
new Thread("AndroidEnvironment Shutdown") {
|
||||
@Override
|
||||
@@ -103,29 +93,39 @@ class AndroidEnvironment {
|
||||
|
||||
final EmulatorController emuController = EmulatorController.getInstance();
|
||||
final State currentState = emuController.getState();
|
||||
if (currentState == State.Idle) {
|
||||
if (currentState == State.NOT_RUNNING) {
|
||||
try {
|
||||
emuController.launch();
|
||||
emuController.launch(); // this blocks until emulator boots
|
||||
} catch (final IOException e) {
|
||||
e.printStackTrace(System.err);
|
||||
return null;
|
||||
}
|
||||
} else if (currentState == State.Launched) {
|
||||
System.err.println("Emulator has already been launched. I shall wait.");
|
||||
} else if (currentState == State.Running) {
|
||||
} else if (currentState == State.WAITING_FOR_BOOT) {
|
||||
System.err.println("Emulator has already been launched. I'll wait.");
|
||||
} else if (currentState == State.RUNNING) {
|
||||
System.err
|
||||
.println("That's weird. The emulator process seems to be running, but I don't know about it.");
|
||||
}
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (final InterruptedException e) {
|
||||
System.err.println("AndroidEnvironment: looking for emulator in loop.");
|
||||
System.err.println("AndroidEnvironment: emulatorcontroller state is "
|
||||
+ emuController.getState());
|
||||
if (emuController.getState() == State.NOT_RUNNING) {
|
||||
System.err.println("Ouch. Emulator got killed, I think.");
|
||||
return null;
|
||||
}
|
||||
emu = find(true);
|
||||
if (emu != null) {
|
||||
System.err.println("AndroidEnvironment: returning " + emu.getId()
|
||||
+ " from loop.");
|
||||
return emu;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (final InterruptedException e) {
|
||||
System.err.println("AndroidEnvironment: interrupted in loop.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -233,10 +233,10 @@ class AndroidEnvironment {
|
||||
* @return list of device identifiers
|
||||
* @throws IOException
|
||||
*/
|
||||
private static List<String> listDevices() {
|
||||
public static List<String> listDevices() {
|
||||
ProcessResult result;
|
||||
try {
|
||||
result = new ProcessHelper("adb", "devices").execute();
|
||||
result = AndroidSDK.runADB("devices");
|
||||
} catch (final InterruptedException e) {
|
||||
return Collections.emptyList();
|
||||
} catch (final IOException e) {
|
||||
@@ -261,7 +261,10 @@ class AndroidEnvironment {
|
||||
if (!line.contains("\t")) {
|
||||
continue;
|
||||
}
|
||||
devices.add(line.split("\t")[0]);
|
||||
final String[] fields = line.split("\t");
|
||||
if (fields[1].equals("device")) {
|
||||
devices.add(fields[0]);
|
||||
}
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
@@ -153,6 +153,37 @@ class AndroidSDK {
|
||||
}
|
||||
}
|
||||
|
||||
private static final String ADB_DAEMON_MSG_1 = "daemon not running";
|
||||
private static final String ADB_DAEMON_MSG_2 = "daemon started successfully";
|
||||
|
||||
public static ProcessResult runADB(final String... cmd)
|
||||
throws InterruptedException, IOException {
|
||||
final String[] adbCmd;
|
||||
if (cmd[0] != "adb") {
|
||||
adbCmd = new String[cmd.length + 1];
|
||||
adbCmd[0] = "adb";
|
||||
System.arraycopy(cmd, 0, adbCmd, 1, cmd.length);
|
||||
} else {
|
||||
adbCmd = cmd;
|
||||
}
|
||||
final ProcessResult adbResult = new ProcessHelper(adbCmd).execute();
|
||||
/*
|
||||
* Ignore messages about starting up an adb daemon
|
||||
*/
|
||||
final String out = adbResult.getStdout();
|
||||
if (out.contains(ADB_DAEMON_MSG_1) && out.contains(ADB_DAEMON_MSG_2)) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (final String line : out.split("\n")) {
|
||||
if (!(out.contains(ADB_DAEMON_MSG_1) || out.contains(ADB_DAEMON_MSG_2))) {
|
||||
sb.append(line).append("\n");
|
||||
}
|
||||
}
|
||||
return new ProcessResult(adbResult.getCmd(), adbResult.getResult(), sb
|
||||
.toString(), adbResult.getStderr(), adbResult.getTime());
|
||||
}
|
||||
return adbResult;
|
||||
}
|
||||
|
||||
private static final String ANDROID_SDK_PRIMARY = "Is the Android SDK installed?";
|
||||
|
||||
private static final String ANDROID_SDK_SECONDARY = "The Android SDK does not appear to be installed, <br>"
|
||||
|
||||
@@ -141,19 +141,17 @@ public class AndroidTool implements Tool, DeviceListener {
|
||||
// if user asks for 480x320, 320x480, 854x480 etc, then launch like that
|
||||
// though would need to query the emulator to see if it can do that
|
||||
|
||||
private void startSketch(final AndroidDevice device) {
|
||||
private boolean startSketch(final AndroidDevice device) {
|
||||
final String packageName = build.getPackageName();
|
||||
final String className = build.getClassName();
|
||||
try {
|
||||
if (device.launchApp(packageName, className)) {
|
||||
editor.statusNotice("Sketch launched on the "
|
||||
+ (device.isEmulator() ? "emulator" : "phone") + ".");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
editor.statusError("Could not start the sketch.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private AndroidDevice waitForDevice(final Future<AndroidDevice> deviceFuture,
|
||||
@@ -207,7 +205,8 @@ public class AndroidTool implements Tool, DeviceListener {
|
||||
}
|
||||
monitor.setNote("Waiting for device to become available...");
|
||||
final AndroidDevice device = waitForDevice(deviceFuture, monitor);
|
||||
if (device == null) {
|
||||
if (device == null || !device.isAlive()) {
|
||||
editor.statusError("Device killed or disconnected.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -218,6 +217,7 @@ public class AndroidTool implements Tool, DeviceListener {
|
||||
}
|
||||
monitor.setNote("Installing sketch on " + device.getId());
|
||||
if (!device.installApp(build.getPathForAPK("debug"), editor)) {
|
||||
editor.statusError("Device killed or disconnected.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -225,7 +225,12 @@ public class AndroidTool implements Tool, DeviceListener {
|
||||
throw new Cancelled();
|
||||
}
|
||||
monitor.setNote("Starting sketch on " + device.getId());
|
||||
startSketch(device);
|
||||
if (startSketch(device)) {
|
||||
editor.statusNotice("Sketch launched on the "
|
||||
+ (device.isEmulator() ? "emulator" : "phone") + ".");
|
||||
} else {
|
||||
editor.statusError("Could not start the sketch.");
|
||||
}
|
||||
|
||||
lastRunDevice = device;
|
||||
} finally {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package processing.app.tools.android;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import processing.app.Preferences;
|
||||
|
||||
class EmulatorController {
|
||||
public static enum State {
|
||||
Idle, Launched, Running
|
||||
NOT_RUNNING, WAITING_FOR_BOOT, RUNNING
|
||||
}
|
||||
|
||||
public static EmulatorController getInstance() {
|
||||
@@ -14,7 +15,7 @@ class EmulatorController {
|
||||
|
||||
private static final EmulatorController INSTANCE = new EmulatorController();
|
||||
|
||||
private volatile State state = State.Idle;
|
||||
private volatile State state = State.NOT_RUNNING;
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
@@ -25,8 +26,12 @@ class EmulatorController {
|
||||
System.err.println("Emulator state: " + state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks until emulator is running, or some catastrophe happens.
|
||||
* @throws IOException
|
||||
*/
|
||||
synchronized public void launch() throws IOException {
|
||||
if (state != State.Idle) {
|
||||
if (state != State.NOT_RUNNING) {
|
||||
throw new IllegalStateException(
|
||||
"You can't launch an emulator whose state is "
|
||||
+ state);
|
||||
@@ -38,34 +43,45 @@ class EmulatorController {
|
||||
Preferences.set("android.emulator.port", portString);
|
||||
}
|
||||
|
||||
System.err.println("Launching emulator");
|
||||
System.err.println("EmulatorController: Launching emulator");
|
||||
|
||||
// See http://developer.android.com/guide/developing/tools/emulator.html
|
||||
final Process p = Runtime.getRuntime().exec(
|
||||
new String[] {
|
||||
"emulator", "-avd", AVD.ECLAIR.name, "-port", portString,
|
||||
"-no-boot-anim" });
|
||||
ProcessRegistry.watch(p);
|
||||
|
||||
// if we've gotten this far, then we've at least succeeded in finding and
|
||||
// beginning execution of the emulator, so we are now officially "Launching"
|
||||
setState(State.Launched);
|
||||
// beginning execution of the emulator, so we are now officially "Launched"
|
||||
setState(State.WAITING_FOR_BOOT);
|
||||
|
||||
ProcessRegistry.watch(p);
|
||||
// "emulator: ERROR: the user data image is used by another emulator. aborting"
|
||||
// make sure that the streams are drained properly
|
||||
new StreamPump(p.getInputStream()).addTarget(System.out).start();
|
||||
new StreamPump(p.getErrorStream()).addTarget(System.err).start();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
if (AndroidEnvironment.getInstance().getEmulator().get() != null) {
|
||||
setState(State.Running);
|
||||
System.err.println("EmulatorController: Waiting for boot.");
|
||||
while (state == State.WAITING_FOR_BOOT) {
|
||||
Thread.sleep(2000);
|
||||
for (final String device : AndroidEnvironment.listDevices()) {
|
||||
if (device.contains("emulator")) {
|
||||
System.err.println("EmulatorController: Emulator booted.");
|
||||
setState(State.RUNNING);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
System.err.println("EmulatorController: Emulator never booted.");
|
||||
} catch (final Exception e) {
|
||||
System.err.println("While waiting for emulator to launch " + e);
|
||||
p.destroy();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}, "EmulatorController: Wait for emulator to boot").start();
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
@@ -82,9 +98,14 @@ class EmulatorController {
|
||||
ProcessRegistry.unwatch(p);
|
||||
}
|
||||
} finally {
|
||||
setState(State.Idle);
|
||||
setState(State.NOT_RUNNING);
|
||||
}
|
||||
}
|
||||
}, "Emulator Babysitter").start();
|
||||
}, "EmulatorController: Process manager").start();
|
||||
try {
|
||||
latch.await();
|
||||
} catch (final InterruptedException drop) {
|
||||
System.err.println("Interrupted while waiting for emulator to launch.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,8 @@ class ProcessHelper {
|
||||
final long startTime = System.currentTimeMillis();
|
||||
|
||||
final String prettyCommand = getCommand();
|
||||
// System.err.println("ProcessHelper: begin " + prettyCommand);
|
||||
System.err.println("ProcessHelper: >>>>> " + Thread.currentThread().getId()
|
||||
+ " " + cmd[0]);
|
||||
final Process process = Runtime.getRuntime().exec(cmd);
|
||||
ProcessRegistry.watch(process);
|
||||
try {
|
||||
@@ -67,7 +68,9 @@ class ProcessHelper {
|
||||
try {
|
||||
final int result = process.waitFor();
|
||||
final long time = System.currentTimeMillis() - startTime;
|
||||
// System.err.println("ProcessHelper: " + time + "ms: " + prettyCommand);
|
||||
System.err.println("ProcessHelper: <<<<< "
|
||||
+ Thread.currentThread().getId() + " " + cmd[0] + " (" + time
|
||||
+ "ms)");
|
||||
return new ProcessResult(prettyCommand, result, outWriter.toString(),
|
||||
errWriter.toString(), time);
|
||||
} catch (final InterruptedException e) {
|
||||
|
||||
@@ -23,6 +23,14 @@ public class ProcessResult implements Iterable<String> {
|
||||
return Arrays.asList(output.split("\r?\n")).iterator();
|
||||
}
|
||||
|
||||
public String getCmd() {
|
||||
return cmd;
|
||||
}
|
||||
|
||||
public int getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean succeeded() {
|
||||
return result == 0;
|
||||
}
|
||||
@@ -35,6 +43,10 @@ public class ProcessResult implements Iterable<String> {
|
||||
return output;
|
||||
}
|
||||
|
||||
public long getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder(cmd).append("\n");
|
||||
@@ -44,4 +56,5 @@ public class ProcessResult implements Iterable<String> {
|
||||
sb.append(" stderr:\n").append(error);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user