diff --git a/android/todo.txt b/android/todo.txt index 68a2d4d08..0730b372c 100644 --- a/android/todo.txt +++ b/android/todo.txt @@ -1,5 +1,18 @@ 0193 android +o verify that processing-core.jar has downloaded properly +o http://code.google.com/p/processing/issues/detail?id=421 +o this is the download page: +o http://code.google.com/p/processing/downloads/detail?name=processing-android-core-0191.zip +o so look for "SHA1 Checksum: 1ed63f9316441fe46949ef5a92a28f58cc7ee226" +_ try using the internal javac on windows and see if exceptions come through.. +_ actually i think that might have been worse... + +_ just use generic android apis as requirement, not the google apis +_ though it does give us the emulator skin.. maybe another option + +_ instructions on installing the usb driver for windows +_ http://developer.android.com/sdk/win-usb.html _ error in 'create avd' with "Emulator already exists" when it needs an upgrade _ or cannot be used with the current setup @@ -25,9 +38,6 @@ _ to create the new sdk object _ need to do this for utf8: "overridable Ant javac properties: java.encoding" _ new for sdk tools r8, it's using ascii as the default, we're utf-8 -_ just use generic android apis as requirement, not the google apis -_ though it does give us the emulator skin.. maybe another option - _ need to do something to make it easier to do new screen sizes. _ when returning to android application, sometimes screen stays black @@ -44,12 +54,6 @@ _ Error 336 Can't run any sketch. _ http://code.google.com/p/processing/issues/detail?id=393 _ javac.exe not included with the download -_ verify that processing-core.jar has downloaded properly -_ http://code.google.com/p/processing/issues/detail?id=421 -_ this is the download page: -_ http://code.google.com/p/processing/downloads/detail?name=processing-android-core-0191.zip -_ so look for "SHA1 Checksum: 1ed63f9316441fe46949ef5a92a28f58cc7ee226" - _ gui stuff: http://hg.postspectacular.com/cp5magic/wiki/Home _ "failed to get signature key" problem diff --git a/app/src/processing/mode/android/AVD.java b/app/src/processing/mode/android/AVD.java index df62bd22f..90761231b 100644 --- a/app/src/processing/mode/android/AVD.java +++ b/app/src/processing/mode/android/AVD.java @@ -33,7 +33,8 @@ public class AVD { */ public static final AVD defaultAVD = new AVD("Processing-Android-" + AndroidBuild.sdkVersion, - "Google Inc.:Google APIs:" + AndroidBuild.sdkVersion); + AndroidBuild.sdkTarget); + //"Google Inc.:Google APIs:" + AndroidBuild.sdkVersion); public static boolean ensureEclairAVD(final AndroidSDK sdk) { diff --git a/app/src/processing/mode/android/AndroidBuild.java b/app/src/processing/mode/android/AndroidBuild.java index c9e968a82..b71a85959 100644 --- a/app/src/processing/mode/android/AndroidBuild.java +++ b/app/src/processing/mode/android/AndroidBuild.java @@ -35,25 +35,56 @@ import processing.mode.java.JavaBuild; class AndroidBuild extends JavaBuild { static final String basePackage = "processing.android.test"; static final String sdkVersion = "7"; + static final String sdkTarget = "android-" + sdkVersion; // private final Editor editor; private final AndroidSDK sdk; + private final File coreZipFile; + + private String target; + private Manifest manifest; - Manifest manifest; -// String className; /** temporary folder safely inside a 8.3-friendly folder */ - File tmpFolder; + private File tmpFolder; + /** build.xml file for this project */ - File buildFile; + private File buildFile; - public AndroidBuild(final Sketch sketch, final AndroidSDK sdk) { + public AndroidBuild(final Sketch sketch, final AndroidMode mode) { super(sketch); - this.sdk = sdk; + + sdk = mode.getSDK(); + coreZipFile = mode.getCoreZipLocation(); + + AVD.ensureEclairAVD(sdk); } - public File createProject(String target, File coreZipFile) throws IOException, SketchException { + /** + * Build into temporary folders (needed for the Windows 8.3 bugs in the Android SDK). + * @param target + * @throws SketchException + * @throws IOException + */ + public File build(String target) throws IOException, SketchException { + this.target = target; + File folder = createProject(); + if (folder != null) { + if (!antBuild()) { + return null; + } + } + return folder; + } + + + /** + * Create an Android project folder, and run the preprocessor on the sketch. + * Populates the 'src' folder with Java code, and 'libs' folder with the + * libraries and code folder contents. Also copies data folder to 'assets'. + */ + public File createProject() throws IOException, SketchException { tmpFolder = createTempBuildFolder(sketch); // Create the 'src' folder with the preprocessed code. @@ -193,11 +224,37 @@ class AndroidBuild extends JavaBuild { return androidFolder; } + + public boolean exportProject() throws IOException, SketchException { + File projectFolder = build("debug"); + if (projectFolder == null) { + return false; + } + + File exportFolder = createExportFolder(); + Base.copyDir(projectFolder, exportFolder); + return true; + } + + + public boolean exportPackage() throws IOException, SketchException { + File projectFolder = build("release"); + if (projectFolder == null) { + return false; + } + + // TODO all the signing magic needs to happen here + + File exportFolder = createExportFolder(); + Base.copyDir(projectFolder, exportFolder); + return true; + } + /** * @param target "debug" or "release" */ - protected boolean antBuild(final String target) throws SketchException { + protected boolean antBuild() throws SketchException { final Project p = new Project(); String path = buildFile.getAbsolutePath().replace('\\', '/'); p.setUserProperty("ant.file", path); @@ -293,7 +350,7 @@ class AndroidBuild extends JavaBuild { // } - String getPathForAPK(final String target) { + String getPathForAPK() { String suffix = target.equals("release") ? "unsigned" : "debug"; String apkName = "bin/" + sketch.getName() + "-" + suffix + ".apk"; final File apkFile = new File(tmpFolder, apkName); @@ -341,7 +398,7 @@ class AndroidBuild extends JavaBuild { private void writeDefaultProps(final File file) { final PrintWriter writer = PApplet.createWriter(file); //writer.println("target=Google Inc.:Google APIs:" + sdkVersion); - writer.println("target=android-" + sdkVersion); + writer.println("target=" + sdkTarget); writer.flush(); writer.close(); } diff --git a/app/src/processing/mode/android/AndroidEditor.java b/app/src/processing/mode/android/AndroidEditor.java index 5300bf0c6..d53e83a29 100644 --- a/app/src/processing/mode/android/AndroidEditor.java +++ b/app/src/processing/mode/android/AndroidEditor.java @@ -25,14 +25,6 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.IOException; -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.JMenu; import javax.swing.JMenuItem; @@ -40,11 +32,9 @@ import javax.swing.JMenuItem; import processing.app.Base; import processing.app.EditorToolbar; import processing.app.Mode; -import processing.app.RunnerListener; import processing.app.SketchException; import processing.core.PApplet; import processing.mode.java.JavaEditor; -import processing.mode.java.runner.Runner; // http://dl.google.com/android/repository/repository.xml // http://dl.google.com/android/android-sdk_r3-mac.zip @@ -57,8 +47,8 @@ import processing.mode.java.runner.Runner; // may as well do the auto-download thing. -public class AndroidEditor extends JavaEditor implements DeviceListener { - private AndroidBuild build; +public class AndroidEditor extends JavaEditor { +// private AndroidBuild build; private AndroidMode amode; // private static final String ANDROID_CORE_FILENAME = @@ -203,7 +193,7 @@ public class AndroidEditor extends JavaEditor implements DeviceListener { item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // editor.statusNotice("Resetting the Android Debug Bridge server."); - Environment.killAdbServer(); + Devices.killAdbServer(); } }); menu.add(item); @@ -311,233 +301,6 @@ public class AndroidEditor extends JavaEditor implements 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 boolean startSketch(final Device device) { - final String packageName = build.getPackageName(); - final String className = build.getSketchClassName(); - try { - if (device.launchApp(packageName, className)) { - return true; - } - } catch (final Exception e) { - e.printStackTrace(System.err); - } - return false; - } - - - private Device waitForDevice(Future deviceFuture, RunnerListener listener) throws MonitorCanceled { -// final IndeterminateProgressMonitor monitor) - for (int i = 0; i < 120; i++) { -// if (monitor.isCanceled()) { - if (listener.isHalted()) { - deviceFuture.cancel(true); - throw new MonitorCanceled(); - } - try { - return deviceFuture.get(1, TimeUnit.SECONDS); - } catch (final InterruptedException e) { - statusError("Interrupted."); - return null; - } catch (final ExecutionException e) { - statusError(e); - return null; - } catch (final TimeoutException expected) { - } - } - statusError("No, on second thought, I'm giving up " + - "on waiting for that device to show up."); - return null; - } - - - private volatile Device lastRunDevice = null; - - /** - * @param target "debug" or "release" - */ - private void runSketchOnDevice(final Future deviceFuture, - final String target, - RunnerListener listener) throws MonitorCanceled { -// final IndeterminateProgressMonitor monitor = -// new IndeterminateProgressMonitor(this, -// "Building and launching...", -// "Creating project..."); - - listener.startIndeterminate(); - listener.statusNotice("Creating project..."); - - build = new AndroidBuild(sketch, amode.getSDK()); - try { - try { - if (build.createProject(target, amode.getCoreZipLocation()) == null) { - return; - } - } catch (SketchException se) { - statusError(se); - } catch (IOException e) { - statusError(e); - } - try { -// if (monitor.isCanceled()) { -// throw new MonitorCanceled(); -// } -// monitor.setNote("Building..."); - listener.statusNotice("Building..."); - try { - if (!build.antBuild(target)) { - return; - } - } catch (SketchException se) { - statusError(se); - } - -// if (monitor.isCanceled()) { -// throw new MonitorCanceled(); -// } -// monitor.setNote("Waiting for device to become available..."); - listener.statusNotice("Waiting for device to become available..."); -// final Device device = waitForDevice(deviceFuture, monitor); - final Device device = waitForDevice(deviceFuture, listener); - if (device == null || !device.isAlive()) { - statusError("Device killed or disconnected."); - return; - } - - device.addListener(this); - - if (listener.isHalted()) { -// if (monitor.isCanceled()) { - throw new MonitorCanceled(); - } -// monitor.setNote("Installing sketch on " + device.getId()); - statusNotice("Installing sketch on " + device.getId()); - if (!device.installApp(build.getPathForAPK(target), this)) { - statusError("Device killed or disconnected."); - return; - } - -// if (monitor.isCanceled()) { -// throw new MonitorCanceled(); -// } -// monitor.setNote("Starting sketch on " + device.getId()); - listener.statusNotice("Starting sketch on " + device.getId()); - if (startSketch(device)) { - statusNotice("Sketch launched on the " - + (device.isEmulator() ? "emulator" : "phone") + "."); - } else { - statusError("Could not start the sketch."); - } - - lastRunDevice = device; - } finally { - build.cleanup(); - } - } finally { -// monitor.close(); - listener.stopIndeterminate(); - } - } - - - private void buildReleaseForExport(String target) throws MonitorCanceled { - final IndeterminateProgressMonitor monitor = - new IndeterminateProgressMonitor(this, - "Building and exporting...", - "Creating project..."); - try { - File tempFolder = null; - try { - tempFolder = build.createProject(target, amode.getCoreZipLocation()); - if (tempFolder == null) { - return; - } - } catch (IOException e) { - e.printStackTrace(); - } catch (SketchException se) { - se.printStackTrace(); - } - try { - if (monitor.isCanceled()) { - throw new MonitorCanceled(); - } - monitor.setNote("Building release version..."); -// if (!build.antBuild("release")) { -// return; -// } - - if (monitor.isCanceled()) { - throw new MonitorCanceled(); - } - - // If things built successfully, copy the contents to the export folder - File exportFolder = build.createExportFolder(); - if (exportFolder != null) { - Base.copyDir(tempFolder, exportFolder); - statusNotice("Done with export."); - Base.openFolder(exportFolder); - } else { - statusError("Could not copy files to export folder."); - } - } catch (IOException e) { - statusError(e); - - } finally { - build.cleanup(); - } - } finally { - monitor.close(); - } - } - - - private static final Pattern LOCATION = - Pattern.compile("\\(([^:]+):(\\d+)\\)"); - private static final Pattern EXCEPTION_PARSER = - Pattern.compile("^\\s*([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); - statusError("Unknown exception"); - return; - } - final String exceptionClass = m.group(1); - if (Runner.handleCommonErrors(exceptionClass, exceptionLine, this)) { - 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)) - 1; - final SketchException rex = - build.placeException(exceptionLine, filename, lineNumber); - statusError(rex == null ? new SketchException(exceptionLine, false) : rex); - return; - } - } - } - } - - public void sketchStopped() { deactivateRun(); statusEmpty(); @@ -551,12 +314,12 @@ public class AndroidEditor extends JavaEditor implements DeviceListener { new Thread() { public void run() { prepareRun(); - AVD.ensureEclairAVD(amode.getSDK()); try { - runSketchOnDevice(Environment.getInstance().getEmulator(), "debug", AndroidEditor.this); - } catch (final MonitorCanceled ok) { - sketchStopped(); - statusNotice("Canceled."); + amode.handleRunEmulator(sketch, AndroidEditor.this); + } catch (SketchException e) { + statusError(e); + } catch (IOException e) { + statusError(e); } } }.start(); @@ -567,19 +330,23 @@ public class AndroidEditor extends JavaEditor implements DeviceListener { * Build the sketch and run it on a device with the debugger connected. */ public void handleRunDevice() { - try { - runSketchOnDevice(Environment.getInstance().getHardware(), "debug", this); - } catch (final MonitorCanceled ok) { - sketchStopped(); - statusNotice("Canceled."); - } + new Thread() { + public void run() { + prepareRun(); + try { + amode.handleRunDevice(sketch, AndroidEditor.this); + } catch (SketchException e) { + statusError(e); + } catch (IOException e) { + statusError(e); + } + } + }.start(); } public void handleStop() { - if (lastRunDevice != null) { - lastRunDevice.bringLauncherToFront(); - } + amode.handleStop(); } @@ -588,13 +355,24 @@ public class AndroidEditor extends JavaEditor implements DeviceListener { * If users want a debug build, they can do that from the command line. */ public void handleExportProject() { + AndroidBuild build = new AndroidBuild(sketch, amode); try { - buildReleaseForExport("debug"); - } catch (final MonitorCanceled ok) { - statusNotice("Canceled."); - } finally { - deactivateExport(); + if (build.exportProject()) { + statusNotice("Done with export."); + } + } catch (IOException e) { + statusError(e); + } catch (SketchException e) { + statusError(e); } + +// try { +// buildReleaseForExport("debug"); +// } catch (final MonitorCanceled ok) { +// statusNotice("Canceled."); +// } finally { +// deactivateExport(); +// } } @@ -628,9 +406,4 @@ public class AndroidEditor extends JavaEditor implements DeviceListener { // editor.deactivateExport(); // } } - - - @SuppressWarnings("serial") - private static class MonitorCanceled extends Exception { - } } \ No newline at end of file diff --git a/app/src/processing/mode/android/AndroidMode.java b/app/src/processing/mode/android/AndroidMode.java index 73362cd41..eeab381f6 100644 --- a/app/src/processing/mode/android/AndroidMode.java +++ b/app/src/processing/mode/android/AndroidMode.java @@ -26,17 +26,25 @@ import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; -import processing.app.*; +import processing.app.Base; +import processing.app.Editor; +import processing.app.RunnerListener; +import processing.app.Sketch; +import processing.app.SketchException; import processing.mode.java.JavaMode; public class AndroidMode extends JavaMode { - static private AndroidSDK sdk; - static private File coreZipLocation; + private AndroidSDK sdk; + private File coreZipLocation; + private AndroidRunner runner; + - public AndroidMode(Base base, File folder) { super(base, folder); + + // keytool -list -v -storepass android -keystore debug.keystore + // Valid from: Mon Nov 02 15:38:52 EST 2009 until: Tue Nov 02 16:38:52 EDT 2010 } @@ -108,4 +116,129 @@ public class AndroidMode extends JavaMode { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + +// public void handleRun(Sketch sketch, RunnerListener listener) throws SketchException { +// JavaBuild build = new JavaBuild(sketch); +// String appletClassName = build.build(); +// if (appletClassName != null) { +// runtime = new Runner(build, listener); +// runtime.launch(false); +// } +// } + public void handleRunEmulator(Sketch sketch, RunnerListener listener) throws SketchException, IOException { + listener.startIndeterminate(); + listener.statusNotice("Starting build..."); + AndroidBuild build = new AndroidBuild(sketch, this); + + listener.statusNotice("Building Android project..."); + build.build("debug"); + + listener.statusNotice("Running sketch on emulator..."); + runner = new AndroidRunner(build, listener); + runner.launch(Devices.getInstance().getEmulator()); + } + + + public void handleRunDevice(Sketch sketch, RunnerListener listener) throws SketchException, IOException { +// JavaBuild build = new JavaBuild(sketch); +// String appletClassName = build.build(); +// if (appletClassName != null) { +// runtime = new Runner(build, listener); +// runtime.launch(true); +// } + +// try { +// runSketchOnDevice(Environment.getInstance().getHardware(), "debug", this); +// } catch (final MonitorCanceled ok) { +// sketchStopped(); +// statusNotice("Canceled."); +// } + listener.startIndeterminate(); + listener.statusNotice("Starting build..."); + AndroidBuild build = new AndroidBuild(sketch, this); + + listener.statusNotice("Building Android project..."); + build.build("debug"); + + listener.statusNotice("Running sketch on device..."); + runner = new AndroidRunner(build, listener); + runner.launch(Devices.getInstance().getHardware()); + } + + + public void handleStop(RunnerListener listener) { + listener.statusNotice(""); + listener.stopIndeterminate(); + +// if (runtime != null) { +// runtime.close(); // kills the window +// runtime = null; // will this help? +// } + if (runner != null) { + runner.close(); + runner = null; + } + } + + +// public void handleExport(Sketch sketch, ) + + + /* + protected void buildReleaseForExport(Sketch sketch, String target) throws MonitorCanceled { +// final IndeterminateProgressMonitor monitor = +// new IndeterminateProgressMonitor(this, +// "Building and exporting...", +// "Creating project..."); + try { + AndroidBuild build = new AndroidBuild(sketch, sdk); + File tempFolder = null; + try { + tempFolder = build.createProject(target, getCoreZipLocation()); + if (tempFolder == null) { + return; + } + } catch (IOException e) { + e.printStackTrace(); + } catch (SketchException se) { + se.printStackTrace(); + } + try { + if (monitor.isCanceled()) { + throw new MonitorCanceled(); + } + monitor.setNote("Building release version..."); +// if (!build.antBuild("release")) { +// return; +// } + + if (monitor.isCanceled()) { + throw new MonitorCanceled(); + } + + // If things built successfully, copy the contents to the export folder + File exportFolder = build.createExportFolder(); + if (exportFolder != null) { + Base.copyDir(tempFolder, exportFolder); + listener.statusNotice("Done with export."); + Base.openFolder(exportFolder); + } else { + listener.statusError("Could not copy files to export folder."); + } + } catch (IOException e) { + listener.statusError(e); + + } finally { + build.cleanup(); + } + } finally { + monitor.close(); + } + } + + + @SuppressWarnings("serial") + private static class MonitorCanceled extends Exception { + } + */ } \ No newline at end of file diff --git a/app/src/processing/mode/android/AndroidRunner.java b/app/src/processing/mode/android/AndroidRunner.java new file mode 100644 index 000000000..3c7a3ffac --- /dev/null +++ b/app/src/processing/mode/android/AndroidRunner.java @@ -0,0 +1,289 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2011 Ben Fry and Casey Reas + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.android; + +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import processing.app.RunnerListener; +import processing.app.SketchException; +import processing.mode.java.runner.Runner; + + +public class AndroidRunner implements DeviceListener { + AndroidBuild build; + RunnerListener listener; + + + public AndroidRunner(AndroidBuild build, RunnerListener listener) { + this.build = build; + this.listener = listener; + } + + + public void launch(Future deviceFuture) { +// try { +// runSketchOnDevice(Devices.getInstance().getEmulator(), "debug", AndroidEditor.this); +// } catch (final MonitorCanceled ok) { +// sketchStopped(); +// statusNotice("Canceled."); +// } + + listener.statusNotice("Waiting for device to become available..."); +// final Device device = waitForDevice(deviceFuture, monitor); + final Device device = waitForDevice(deviceFuture, listener); + if (device == null || !device.isAlive()) { + listener.statusError("Device killed or disconnected."); + return; + } + + device.addListener(this); + +// if (listener.isHalted()) { +//// if (monitor.isCanceled()) { +// throw new MonitorCanceled(); +// } + +// monitor.setNote("Installing sketch on " + device.getId()); + listener.statusNotice("Installing sketch on " + device.getId()); + if (!device.installApp(build.getPathForAPK(), listener)) { + listener.statusError("Device killed or disconnected."); + return; + } + +// if (monitor.isCanceled()) { +// throw new MonitorCanceled(); +// } +// monitor.setNote("Starting sketch on " + device.getId()); + listener.statusNotice("Starting sketch on " + device.getId()); + if (startSketch(build, device)) { + listener.statusNotice("Sketch launched on the " + + (device.isEmulator() ? "emulator" : "phone") + "."); + } else { + listener.statusError("Could not start the sketch."); + } + + lastRunDevice = device; +//} finally { +// build.cleanup(); +//} +//} finally { +////monitor.close(); +//listener.stopIndeterminate(); +//} + } + + + private volatile Device lastRunDevice = null; + + /** + * @param target "debug" or "release" + */ + /* + private void runSketchOnDevice(Sketch sketch, + Future deviceFuture, + String target, + RunnerListener listener) { +// final IndeterminateProgressMonitor monitor = +// new IndeterminateProgressMonitor(this, +// "Building and launching...", +// "Creating project..."); + + + AndroidBuild build = new AndroidBuild(sketch, listener); + try { + try { + if (build.createProject(target) == null) { + return; + } + } catch (SketchException se) { + listener.statusError(se); + } catch (IOException e) { + listener.statusError(e); + } + try { +// if (monitor.isCanceled()) { +// throw new MonitorCanceled(); +// } +// monitor.setNote("Building..."); + listener.statusNotice("Building..."); + try { + if (!build.antBuild(target)) { + return; + } + } catch (SketchException se) { + listener.statusError(se); + } + +// if (monitor.isCanceled()) { +// throw new MonitorCanceled(); +// } +// monitor.setNote("Waiting for device to become available..."); + listener.statusNotice("Waiting for device to become available..."); +// final Device device = waitForDevice(deviceFuture, monitor); + final Device device = waitForDevice(deviceFuture, listener); + if (device == null || !device.isAlive()) { + listener.statusError("Device killed or disconnected."); + return; + } + + device.addListener(this); + +// if (listener.isHalted()) { +//// if (monitor.isCanceled()) { +// throw new MonitorCanceled(); +// } + +// monitor.setNote("Installing sketch on " + device.getId()); + listener.statusNotice("Installing sketch on " + device.getId()); + if (!device.installApp(build.getPathForAPK(target), listener)) { + listener.statusError("Device killed or disconnected."); + return; + } + +// if (monitor.isCanceled()) { +// throw new MonitorCanceled(); +// } +// monitor.setNote("Starting sketch on " + device.getId()); + listener.statusNotice("Starting sketch on " + device.getId()); + if (startSketch(build, device)) { + listener.statusNotice("Sketch launched on the " + + (device.isEmulator() ? "emulator" : "phone") + "."); + } else { + listener.statusError("Could not start the sketch."); + } + + lastRunDevice = device; + } finally { + build.cleanup(); + } + } finally { +// monitor.close(); + listener.stopIndeterminate(); + } + } + */ + + + // 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 boolean startSketch(AndroidBuild build, final Device device) { + final String packageName = build.getPackageName(); + final String className = build.getSketchClassName(); + try { + if (device.launchApp(packageName, className)) { + return true; + } + } catch (final Exception e) { + e.printStackTrace(System.err); + } + return false; + } + + + private Device waitForDevice(Future deviceFuture, RunnerListener listener) { + for (int i = 0; i < 120; i++) { +// if (monitor.isCanceled()) { + if (listener.isHalted()) { + deviceFuture.cancel(true); +// throw new MonitorCanceled(); + return null; + } + try { + return deviceFuture.get(1, TimeUnit.SECONDS); + } catch (final InterruptedException e) { + listener.statusError("Interrupted."); + return null; + } catch (final ExecutionException e) { + listener.statusError(e); + return null; + } catch (final TimeoutException expected) { + } + } + listener.statusError("No, on second thought, I'm giving up " + + "on waiting for that device to show up."); + return null; + } + + + private static final Pattern LOCATION = + Pattern.compile("\\(([^:]+):(\\d+)\\)"); + private static final Pattern EXCEPTION_PARSER = + Pattern.compile("^\\s*([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); + listener.statusError("Unknown exception"); + return; + } + final String exceptionClass = m.group(1); + if (Runner.handleCommonErrors(exceptionClass, exceptionLine, listener)) { + 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)) - 1; + final SketchException rex = + build.placeException(exceptionLine, filename, lineNumber); + listener.statusError(rex == null ? new SketchException(exceptionLine, false) : rex); + return; + } + } + } + } + + + // called by AndroidMode.handleStop()... + public void close() { + if (lastRunDevice != null) { + lastRunDevice.bringLauncherToFront(); + } + } + + + // sketch stopped on the device + public void sketchStopped() { + listener.statusHalt(); + } +} diff --git a/app/src/processing/mode/android/Device.java b/app/src/processing/mode/android/Device.java index 28e849a44..f8364eac9 100644 --- a/app/src/processing/mode/android/Device.java +++ b/app/src/processing/mode/android/Device.java @@ -19,7 +19,7 @@ import processing.mode.android.LogEntry.Severity; class Device implements DeviceProperties { - private final Environment env; + private final Devices env; private final String id; private final Set activeProcesses = new HashSet(); private final Set listeners = @@ -28,7 +28,7 @@ class Device implements DeviceProperties { // mutable state private Process logcat; - public Device(final Environment env, final String id) { + public Device(final Devices env, final String id) { this.env = env; this.id = id; } @@ -231,7 +231,7 @@ class Device implements DeviceProperties { return id; } - public Environment getEnv() { + public Devices getEnv() { return env; } diff --git a/app/src/processing/mode/android/Environment.java b/app/src/processing/mode/android/Devices.java similarity index 98% rename from app/src/processing/mode/android/Environment.java rename to app/src/processing/mode/android/Devices.java index ee560b41d..0b14cf73c 100644 --- a/app/src/processing/mode/android/Environment.java +++ b/app/src/processing/mode/android/Devices.java @@ -23,14 +23,14 @@ import processing.mode.android.EmulatorController.State; * @author Jonathan Feinberg <jdf@pobox.com> * */ -class Environment { +class Devices { private static final String ADB_DEVICES_ERROR = "Received unfamiliar output from “adb devices”.\n" + "The device list may have errors."; - private static final Environment INSTANCE = new Environment(); + private static final Devices INSTANCE = new Devices(); - public static Environment getInstance() { + public static Devices getInstance() { return INSTANCE; } @@ -54,7 +54,7 @@ class Environment { } - private Environment() { + private Devices() { if (processing.app.Base.DEBUG) { System.out.println("Starting up AndroidEnvironment"); } diff --git a/app/src/processing/mode/android/EmulatorController.java b/app/src/processing/mode/android/EmulatorController.java index 0a91d422f..e22f0b730 100644 --- a/app/src/processing/mode/android/EmulatorController.java +++ b/app/src/processing/mode/android/EmulatorController.java @@ -112,7 +112,7 @@ class EmulatorController { } Thread.sleep(2000); // System.out.println("done sleeping"); - for (final String device : Environment.listDevices()) { + for (final String device : Devices.listDevices()) { if (device.contains("emulator")) { // System.err.println("EmulatorController: Emulator booted."); setState(State.RUNNING); diff --git a/app/src/processing/mode/android/IndeterminateProgressMonitor.java b/app/src/processing/mode/android/IndeterminateProgressMonitor.java deleted file mode 100644 index 85adaff8e..000000000 --- a/app/src/processing/mode/android/IndeterminateProgressMonitor.java +++ /dev/null @@ -1,70 +0,0 @@ -package processing.mode.android; - -import java.awt.Component; -import java.awt.Dimension; -import java.awt.HeadlessException; -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JProgressBar; -import javax.swing.UIManager; - -class IndeterminateProgressMonitor { - private JDialog dialog; - private JOptionPane pane; - private JProgressBar progressBar; - private final JLabel noteLabel; - private Object[] cancelOption = null; - - IndeterminateProgressMonitor(final Component parentComponent, - final Object message, final String note) { - cancelOption = - new Object[] { UIManager.getString("OptionPane.cancelButtonText") }; - progressBar = new JProgressBar(); - progressBar.setIndeterminate(true); - noteLabel = new JLabel(note); - pane = new IndefiniteProgressOptionPane(message, noteLabel, progressBar); - dialog = pane.createDialog(parentComponent, "Progress"); - dialog.setVisible(true); - } - - @SuppressWarnings("serial") - private class IndefiniteProgressOptionPane extends JOptionPane { - IndefiniteProgressOptionPane(final Object... messageList) { - super(messageList, JOptionPane.INFORMATION_MESSAGE, - JOptionPane.DEFAULT_OPTION, null, - IndeterminateProgressMonitor.this.cancelOption, null); - setPreferredSize(new Dimension(getPreferredSize().width + 120, - getPreferredSize().height)); - } - - @Override - public JDialog createDialog(final Component parentComponent, - final String title) throws HeadlessException { - final JDialog d = super.createDialog(parentComponent, title); - d.setModal(false); - return d; - } - } - - public void close() { - if (dialog != null) { - dialog.setVisible(false); - dialog.dispose(); - dialog = null; - pane = null; - progressBar = null; - } - } - - public boolean isCanceled() { - final Object v = pane.getValue(); - return ((v != null) && (cancelOption.length == 1) && (v - .equals(cancelOption[0]))); - } - - public void setNote(final String note) { - noteLabel.setText(note); - } - -} diff --git a/app/src/processing/mode/android/Manifest.java b/app/src/processing/mode/android/Manifest.java index 47378be4e..8158ef1c5 100644 --- a/app/src/processing/mode/android/Manifest.java +++ b/app/src/processing/mode/android/Manifest.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2010 Ben Fry and Casey Reas + Copyright (c) 2010-11 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 diff --git a/app/src/processing/mode/java/JavaMode.java b/app/src/processing/mode/java/JavaMode.java index 480d74d67..441d9ceb1 100644 --- a/app/src/processing/mode/java/JavaMode.java +++ b/app/src/processing/mode/java/JavaMode.java @@ -162,10 +162,6 @@ public class JavaMode extends Mode { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - /** - * Implements Sketch → Run. - * @throws SketchException - */ public void handleRun(Sketch sketch, RunnerListener listener) throws SketchException { JavaBuild build = new JavaBuild(sketch); String appletClassName = build.build();