diff --git a/android/tool/src/processing/app/tools/android/AndroidDevice.java b/android/tool/src/processing/app/tools/android/AndroidDevice.java index 471fdc236..e812b05a3 100644 --- a/android/tool/src/processing/app/tools/android/AndroidDevice.java +++ b/android/tool/src/processing/app/tools/android/AndroidDevice.java @@ -1,7 +1,10 @@ package processing.app.tools.android; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -12,6 +15,8 @@ class AndroidDevice implements AndroidDeviceProperties { private final AndroidEnvironment env; private final String id; private final Set activeProcesses = new HashSet(); + private final Set listeners = Collections + .synchronizedSet(new HashSet()); // mutable state private Process logcat; @@ -88,40 +93,65 @@ class AndroidDevice implements AndroidDeviceProperties { private static final Pattern SIG = Pattern .compile("PID:\\s+(\\d+)\\s+SIG:\\s+(\\d+)"); + private final List stackTrace = new ArrayList(); + private class LogLineProcessor implements LineProcessor { public void processLine(final String line) { final LogEntry entry = new LogEntry(line); - final String src = entry.source; - final String msg = entry.message; - final Severity sev = entry.severity; - if (msg.startsWith("PROCESSING")) { - if (msg.contains("onStart")) { - startProc(src, entry.sourcePid); - } else if (msg.contains("onStop")) { - endProc(entry.sourcePid); + if (entry.message.startsWith("PROCESSING")) { + if (entry.message.contains("onStart")) { + startProc(entry.source, entry.pid); + } else if (entry.message.contains("onStop")) { + endProc(entry.pid); } - } else if (src.equals("Process")) { - final Matcher m = SIG.matcher(msg); + } else if (entry.source.equals("Process")) { + final Matcher m = SIG.matcher(entry.message); if (m.find()) { final int pid = Integer.parseInt(m.group(1)); final int signal = Integer.parseInt(m.group(2)); if (signal == 9) { endProc(pid); + } else if (signal == 3) { + reportStackTrace(entry); } } - } else if (activeProcesses.contains(entry.sourcePid) - && ((src.equals("AndroidRuntime") && sev == Severity.Error) - || src.equals("System.out") || src.equals("System.err"))) { - if (sev.useErrorStream) { - System.err.println(msg); - } else { - System.out.println(msg); - } + } else if (activeProcesses.contains(entry.pid)) { + handleConsole(entry); } - //System.err.println(entry.source + "/" + entry.message); } } + private void handleConsole(final LogEntry entry) { + final boolean isStackTrace = entry.source.equals("AndroidRuntime") + && entry.severity == Severity.Error; + if (isStackTrace) { + if (!entry.message.startsWith("Uncaught handler")) { + stackTrace.add(entry.message); + System.err.println(entry.message); + } + } else if (entry.source.equals("System.out") + || entry.source.equals("System.err")) { + if (entry.severity.useErrorStream) { + System.err.println(entry.message); + } else { + System.out.println(entry.message); + } + } + } + + private void reportStackTrace(final LogEntry entry) { + if (stackTrace.isEmpty()) { + System.err.println("That's weird. Proc " + entry.pid + + " got signal 3, but there's no stack trace."); + } + final List stackCopy = Collections + .unmodifiableList(new ArrayList(stackTrace)); + for (final DeviceListener listener : listeners) { + listener.stacktrace(stackCopy); + } + stackTrace.clear(); + } + void initialize() throws IOException, InterruptedException { adb("logcat", "-c"); logcat = Runtime.getRuntime().exec(generateAdbCommand("logcat")); @@ -135,6 +165,7 @@ class AndroidDevice implements AndroidDeviceProperties { logcat.destroy(); } env.deviceRemoved(this); + listeners.clear(); } public String getId() { @@ -155,6 +186,14 @@ class AndroidDevice implements AndroidDeviceProperties { activeProcesses.remove(pid); } + public void addListener(final DeviceListener listener) { + listeners.add(listener); + } + + public void removeListener(final DeviceListener listener) { + listeners.remove(listener); + } + private ProcessResult adb(final String... cmd) throws InterruptedException, IOException { final String[] adbCmd = generateAdbCommand(cmd); diff --git a/android/tool/src/processing/app/tools/android/AndroidSDK.java b/android/tool/src/processing/app/tools/android/AndroidSDK.java index c4cbefe6f..30a859a58 100644 --- a/android/tool/src/processing/app/tools/android/AndroidSDK.java +++ b/android/tool/src/processing/app/tools/android/AndroidSDK.java @@ -1,10 +1,10 @@ package processing.app.tools.android; +import java.awt.Frame; import java.io.File; import java.io.IOException; import javax.swing.JOptionPane; import processing.app.Base; -import processing.app.Editor; import processing.app.Platform; import processing.app.Preferences; @@ -76,7 +76,7 @@ class AndroidSDK { * @throws BadSDKException * @throws IOException */ - public static AndroidSDK find(final Editor editor) throws BadSDKException, + public static AndroidSDK find(final Frame window) throws BadSDKException, IOException { final Platform platform = Base.getPlatform(); @@ -110,7 +110,7 @@ class AndroidSDK { } } - final int result = Base.showYesNoQuestion(editor, "Android SDK", + final int result = Base.showYesNoQuestion(window, "Android SDK", ANDROID_SDK_PRIMARY, ANDROID_SDK_SECONDARY); if (result == JOptionPane.CANCEL_OPTION) { throw new BadSDKException("User cancelled attempt to find SDK."); @@ -122,7 +122,7 @@ class AndroidSDK { } while (true) { final File folder = Base.selectFolder(SELECT_ANDROID_SDK_FOLDER, null, - editor); + window); if (folder == null) { throw new BadSDKException("User cancelled attempt to find SDK."); } @@ -133,7 +133,7 @@ class AndroidSDK { Preferences.set("android.sdk.path", selectedPath); return androidSDK; } catch (final BadSDKException nope) { - JOptionPane.showMessageDialog(editor, NOT_ANDROID_SDK); + JOptionPane.showMessageDialog(window, NOT_ANDROID_SDK); } } } diff --git a/android/tool/src/processing/app/tools/android/AndroidTool.java b/android/tool/src/processing/app/tools/android/AndroidTool.java index 791cb3821..cecdfa177 100644 --- a/android/tool/src/processing/app/tools/android/AndroidTool.java +++ b/android/tool/src/processing/app/tools/android/AndroidTool.java @@ -21,16 +21,23 @@ package processing.app.tools.android; +import java.awt.Frame; import java.io.File; import java.net.URL; +import java.util.Iterator; +import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.swing.ProgressMonitor; import processing.app.Base; import processing.app.Editor; import processing.app.Sketch; +import processing.app.debug.Runner; +import processing.app.debug.RunnerException; import processing.app.tools.Tool; import processing.core.PApplet; @@ -38,7 +45,7 @@ import processing.core.PApplet; // http://dl.google.com/android/android-sdk_r3-mac.zip // http://dl.google.com/android/repository/tools_r03-macosx.zip -public class AndroidTool implements Tool { +public class AndroidTool implements Tool, DeviceListener { private AndroidSDK sdk; private Editor editor; private Build build; @@ -61,7 +68,7 @@ public class AndroidTool implements Tool { editor.statusNotice("Loading Android tools."); try { - sdk = AndroidSDK.find(editor); + sdk = AndroidSDK.find((editor instanceof Frame) ? (Frame) editor : null); } catch (final Exception e) { Base.showWarning("Android Tools Error", e.getMessage(), null); editor.statusNotice("Android mode canceled."); @@ -208,6 +215,8 @@ public class AndroidTool implements Tool { return; } + device.addListener(this); + if (monitor.isCanceled()) { throw new Cancelled(); } @@ -230,6 +239,51 @@ public class AndroidTool implements Tool { } } + private static final Pattern LOCATION = Pattern + .compile("\\(([^:]+):(\\d+)\\)"); + private static final Pattern EXCEPTION_PARSER = Pattern.compile( + "^([a-z]+(?:\\.[a-z]+)+)(?:: (.+))?$", Pattern.CASE_INSENSITIVE); + + /** + * Currently figures out the first relevant stack trace line + * by looking for the telltale presence of "processing.android" + * in the package. If the packaging for droid sketches changes, + * this method will have to change too. + */ + public void stacktrace(final List trace) { + final Iterator frames = trace.iterator(); + final String exceptionLine = frames.next(); + + final Matcher m = EXCEPTION_PARSER.matcher(exceptionLine); + if (!m.matches()) { + System.err.println("Can't parse this exception line:"); + System.err.println(exceptionLine); + editor.statusError("Unknown exception"); + return; + } + final String exceptionClass = m.group(1); + final String message = m.group(2); + if (Runner.handleCommonErrors(exceptionClass, message, editor)) { + return; + } + + while (frames.hasNext()) { + final String line = frames.next(); + if (line.contains("processing.android")) { + final Matcher lm = LOCATION.matcher(line); + if (lm.find()) { + final String filename = lm.group(1); + final int lineNumber = Integer.parseInt(lm.group(2)); + final RunnerException rex = editor.getSketch().placeException( + message, filename, lineNumber); + editor.statusError(rex == null ? new RunnerException(message, false) + : rex); + } + } + + } + } + /** * Build the sketch and run it inside an emulator with the debugger. */ diff --git a/android/tool/src/processing/app/tools/android/DeviceListener.java b/android/tool/src/processing/app/tools/android/DeviceListener.java new file mode 100644 index 000000000..d6335769b --- /dev/null +++ b/android/tool/src/processing/app/tools/android/DeviceListener.java @@ -0,0 +1,7 @@ +package processing.app.tools.android; + +import java.util.List; + +public interface DeviceListener { + void stacktrace(final List trace); +} diff --git a/android/tool/src/processing/app/tools/android/LogEntry.java b/android/tool/src/processing/app/tools/android/LogEntry.java index a61a94373..932746035 100644 --- a/android/tool/src/processing/app/tools/android/LogEntry.java +++ b/android/tool/src/processing/app/tools/android/LogEntry.java @@ -35,7 +35,7 @@ class LogEntry { public final Severity severity; public final String source; - public final int sourcePid; + public final int pid; public final String message; private static final Pattern PARSER = Pattern @@ -48,12 +48,12 @@ class LogEntry { } this.severity = Severity.fromChar(m.group(1).charAt(0)); this.source = m.group(2); - this.sourcePid = Integer.parseInt(m.group(3)); + this.pid = Integer.parseInt(m.group(3)); this.message = m.group(4); } @Override public String toString() { - return severity + "/" + source + "(" + sourcePid + "): " + message; + return severity + "/" + source + "(" + pid + "): " + message; } }