mirror of
https://github.com/processing/processing4.git
synced 2026-06-16 04:26:26 +02:00
moving android fully to its own subdirectory
This commit is contained in:
@@ -1,196 +0,0 @@
|
||||
package processing.mode.android;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import processing.app.Base;
|
||||
import processing.app.exec.ProcessHelper;
|
||||
import processing.app.exec.ProcessResult;
|
||||
import processing.core.PApplet;
|
||||
|
||||
|
||||
public class AVD {
|
||||
static private final String AVD_CREATE_PRIMARY =
|
||||
"An error occurred while running “android create avd”";
|
||||
|
||||
static private final String AVD_CREATE_SECONDARY =
|
||||
"The default Android emulator could not be set up. Make sure<br>" +
|
||||
"that the Android SDK is installed properly, and that the<br>" +
|
||||
"Android and Google APIs are installed for level " + AndroidBuild.sdkVersion + ".<br>" +
|
||||
"(Between you and me, occasionally, this error is a red herring,<br>" +
|
||||
"and your sketch may be launching shortly.)";
|
||||
|
||||
static private final String AVD_LOAD_PRIMARY =
|
||||
"There is an error with the Processing AVD.";
|
||||
static private final String AVD_LOAD_SECONDARY =
|
||||
"This could mean that the Android tools need to be updated,<br>" +
|
||||
"or that the Processing AVD should be deleted (it will<br>" +
|
||||
"automatically re-created the next time you run Processing).<br>" +
|
||||
"Open the Android SDK Manager (underneath the Android menu)<br>" +
|
||||
"to check for any errors.";
|
||||
|
||||
static private final String AVD_TARGET_PRIMARY =
|
||||
"The Google APIs are not installed properly";
|
||||
static private final String AVD_TARGET_SECONDARY =
|
||||
"Please re-read the installation instructions for Processing<br>" +
|
||||
"found at http://android.processing.org and try again.";
|
||||
|
||||
static final String DEFAULT_SKIN = "WVGA800";
|
||||
static final String DEFAULT_SDCARD_SIZE = "64M";
|
||||
|
||||
/** Name of this avd. */
|
||||
protected String name;
|
||||
|
||||
/** "android-7" or "Google Inc.:Google APIs:7" */
|
||||
protected String target;
|
||||
|
||||
/** Default virtual device used by Processing. */
|
||||
static public final AVD defaultAVD =
|
||||
new AVD("Processing-0" + Base.REVISION,
|
||||
"android-" + AndroidBuild.sdkVersion);
|
||||
// "Google Inc.:Google APIs:" + AndroidBuild.sdkVersion);
|
||||
|
||||
static ArrayList<String> avdList;
|
||||
static ArrayList<String> badList;
|
||||
// static ArrayList<String> skinList;
|
||||
|
||||
|
||||
public AVD(final String name, final String target) {
|
||||
this.name = name;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
|
||||
static protected void list(final AndroidSDK sdk) throws IOException {
|
||||
try {
|
||||
avdList = new ArrayList<String>();
|
||||
badList = new ArrayList<String>();
|
||||
ProcessResult listResult =
|
||||
new ProcessHelper(sdk.getAndroidToolPath(), "list", "avds").execute();
|
||||
if (listResult.succeeded()) {
|
||||
boolean badness = false;
|
||||
for (String line : listResult) {
|
||||
String[] m = PApplet.match(line, "\\s+Name\\:\\s+(\\S+)");
|
||||
if (m != null) {
|
||||
if (!badness) {
|
||||
// System.out.println("good: " + m[1]);
|
||||
avdList.add(m[1]);
|
||||
} else {
|
||||
// System.out.println("bad: " + m[1]);
|
||||
badList.add(m[1]);
|
||||
}
|
||||
// } else {
|
||||
// System.out.println("nope: " + line);
|
||||
}
|
||||
// "The following Android Virtual Devices could not be loaded:"
|
||||
if (line.contains("could not be loaded:")) {
|
||||
// System.out.println("starting the bad list");
|
||||
// System.err.println("Could not list AVDs:");
|
||||
// System.err.println(listResult);
|
||||
badness = true;
|
||||
// break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
System.err.println("Unhappy inside exists()");
|
||||
System.err.println(listResult);
|
||||
}
|
||||
} catch (final InterruptedException ie) { }
|
||||
}
|
||||
|
||||
|
||||
protected boolean exists(final AndroidSDK sdk) throws IOException {
|
||||
if (avdList == null) {
|
||||
list(sdk);
|
||||
}
|
||||
for (String avd : avdList) {
|
||||
if (Base.DEBUG) {
|
||||
System.out.println("AVD.exists() checking for " + name + " against " + avd);
|
||||
}
|
||||
if (avd.equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if a member of the renowned and prestigious
|
||||
* "The following Android Virtual Devices could not be loaded:" club.
|
||||
* (Prestigious may also not be the right word.)
|
||||
*/
|
||||
protected boolean badness() {
|
||||
for (String avd : badList) {
|
||||
if (avd.equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
protected boolean create(final AndroidSDK sdk) throws IOException {
|
||||
final String[] params = {
|
||||
sdk.getAndroidToolPath(),
|
||||
"create", "avd",
|
||||
"-n", name,
|
||||
"-t", target,
|
||||
"-c", DEFAULT_SDCARD_SIZE,
|
||||
"-s", DEFAULT_SKIN
|
||||
};
|
||||
|
||||
// Set the list to null so that exists() will check again
|
||||
avdList = null;
|
||||
|
||||
final ProcessHelper p = new ProcessHelper(params);
|
||||
try {
|
||||
// Passes 'no' to "Do you wish to create a custom hardware profile [no]"
|
||||
// System.out.println("CREATE AVD STARTING");
|
||||
final ProcessResult createAvdResult = p.execute("no");
|
||||
// System.out.println("CREATE AVD HAS COMPLETED");
|
||||
if (createAvdResult.succeeded()) {
|
||||
return true;
|
||||
}
|
||||
if (createAvdResult.toString().contains("Target id is not valid")) {
|
||||
// They didn't install the Google APIs
|
||||
Base.showWarningTiered("Android Error", AVD_TARGET_PRIMARY, AVD_TARGET_SECONDARY, null);
|
||||
// throw new IOException("Missing required SDK components");
|
||||
} else {
|
||||
// Just generally not working
|
||||
// Base.showWarning("Android Error", AVD_CREATE_ERROR, null);
|
||||
Base.showWarningTiered("Android Error", AVD_CREATE_PRIMARY, AVD_CREATE_SECONDARY, null);
|
||||
System.out.println(createAvdResult);
|
||||
// throw new IOException("Error creating the AVD");
|
||||
}
|
||||
//System.err.println(createAvdResult);
|
||||
} catch (final InterruptedException ie) { }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static public boolean ensureProperAVD(final AndroidSDK sdk) {
|
||||
try {
|
||||
if (defaultAVD.exists(sdk)) {
|
||||
// System.out.println("the avd exists");
|
||||
return true;
|
||||
}
|
||||
// if (badList.contains(defaultAVD)) {
|
||||
if (defaultAVD.badness()) {
|
||||
// Base.showWarning("Android Error", AVD_CANNOT_LOAD, null);
|
||||
Base.showWarningTiered("Android Error", AVD_LOAD_PRIMARY, AVD_LOAD_SECONDARY, null);
|
||||
return false;
|
||||
}
|
||||
if (defaultAVD.create(sdk)) {
|
||||
// System.out.println("the avd was created");
|
||||
return true;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// Base.showWarning("Android Error", AVD_CREATE_ERROR, e);
|
||||
Base.showWarningTiered("Android Error", AVD_CREATE_PRIMARY, AVD_CREATE_SECONDARY, null);
|
||||
}
|
||||
System.out.println("at bottom of ensure proper");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,804 +0,0 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2009-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
|
||||
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.io.*;
|
||||
|
||||
import org.apache.tools.ant.*;
|
||||
|
||||
import processing.app.*;
|
||||
import processing.app.exec.*;
|
||||
import processing.core.PApplet;
|
||||
import processing.mode.java.JavaBuild;
|
||||
|
||||
|
||||
class AndroidBuild extends JavaBuild {
|
||||
// static final String basePackage = "changethispackage.beforesubmitting.tothemarket";
|
||||
static final String basePackage = "processing.test";
|
||||
static final String sdkName = "2.3.3";
|
||||
static final String sdkVersion = "10"; // Android 2.3.3 (Gingerbread)
|
||||
static final String sdkTarget = "android-" + sdkVersion;
|
||||
|
||||
private final AndroidSDK sdk;
|
||||
private final File coreZipFile;
|
||||
|
||||
/** whether this is a "debug" or "release" build */
|
||||
private String target;
|
||||
private Manifest manifest;
|
||||
|
||||
/** temporary folder safely inside a 8.3-friendly folder */
|
||||
private File tmpFolder;
|
||||
|
||||
/** build.xml file for this project */
|
||||
private File buildFile;
|
||||
|
||||
|
||||
public AndroidBuild(final Sketch sketch, final AndroidMode mode) {
|
||||
super(sketch);
|
||||
|
||||
sdk = mode.getSDK();
|
||||
coreZipFile = mode.getCoreZipLocation();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build into temporary folders (needed for the Windows 8.3 bugs in the Android SDK).
|
||||
* @param target "debug" or "release"
|
||||
* @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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tell the PDE to not complain about android.* packages and others that are
|
||||
* part of the OS library set as if they're missing.
|
||||
*/
|
||||
protected boolean ignorableImport(String pkg) {
|
||||
if (pkg.startsWith("android.")) return true;
|
||||
if (pkg.startsWith("java.")) return true;
|
||||
if (pkg.startsWith("javax.")) return true;
|
||||
if (pkg.startsWith("org.apache.http.")) return true;
|
||||
if (pkg.startsWith("org.json.")) return true;
|
||||
if (pkg.startsWith("org.w3c.dom.")) return true;
|
||||
if (pkg.startsWith("org.xml.sax.")) return true;
|
||||
|
||||
if (pkg.startsWith("processing.core.")) return true;
|
||||
if (pkg.startsWith("processing.data.")) return true;
|
||||
if (pkg.startsWith("processing.event.")) return true;
|
||||
if (pkg.startsWith("processing.opengl.")) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
// final File srcFolder = new File(tmpFolder, "src");
|
||||
srcFolder = new File(tmpFolder, "src");
|
||||
// this folder isn't actually used, but it's used by the java preproc to
|
||||
// figure out the classpath, so we have to set it to something
|
||||
// binFolder = new File(tmpFolder, "bin");
|
||||
// use the src folder, since 'bin' might be used by the ant build
|
||||
binFolder = srcFolder;
|
||||
if (processing.app.Base.DEBUG) {
|
||||
Base.openFolder(tmpFolder);
|
||||
}
|
||||
|
||||
manifest = new Manifest(sketch);
|
||||
// grab code from current editing window (GUI only)
|
||||
// prepareExport(null);
|
||||
|
||||
// build the preproc and get to work
|
||||
AndroidPreprocessor preproc = new AndroidPreprocessor(sketch, getPackageName());
|
||||
// if (!preproc.parseSketchSize()) {
|
||||
// String[] sizeInfo = PdePreprocessor.parseSketchSize(sketch.getMainProgram());
|
||||
// if (sizeInfo == null) {
|
||||
// throw new SketchException("Could not parse the size() command.");
|
||||
// }
|
||||
// On Android, this init will throw a SketchException if there's a problem with size()
|
||||
preproc.initSketchSize(sketch.getMainProgram());
|
||||
sketchClassName = preprocess(srcFolder, manifest.getPackageName(), preproc, false);
|
||||
if (sketchClassName != null) {
|
||||
File tempManifest = new File(tmpFolder, "AndroidManifest.xml");
|
||||
manifest.writeBuild(tempManifest, sketchClassName, target.equals("debug"));
|
||||
|
||||
writeAntProps(new File(tmpFolder, "ant.properties"));
|
||||
buildFile = new File(tmpFolder, "build.xml");
|
||||
writeBuildXML(buildFile, sketch.getName());
|
||||
writeProjectProps(new File(tmpFolder, "project.properties"));
|
||||
writeLocalProps(new File(tmpFolder, "local.properties"));
|
||||
|
||||
final File resFolder = new File(tmpFolder, "res");
|
||||
writeRes(resFolder, sketchClassName);
|
||||
|
||||
// new location for SDK Tools 17: /opt/android/tools/proguard/proguard-android.txt
|
||||
// File proguardSrc = new File(sdk.getSdkFolder(), "tools/lib/proguard.cfg");
|
||||
// File proguardDst = new File(tmpFolder, "proguard.cfg");
|
||||
// Base.copyFile(proguardSrc, proguardDst);
|
||||
|
||||
final File libsFolder = mkdirs(tmpFolder, "libs");
|
||||
final File assetsFolder = mkdirs(tmpFolder, "assets");
|
||||
|
||||
// InputStream input = PApplet.createInput(getCoreZipLocation());
|
||||
// PApplet.saveStream(new File(libsFolder, "processing-core.jar"), input);
|
||||
Base.copyFile(coreZipFile, new File(libsFolder, "processing-core.jar"));
|
||||
|
||||
// Copy any imported libraries (their libs and assets),
|
||||
// and anything in the code folder contents to the project.
|
||||
copyLibraries(libsFolder, assetsFolder);
|
||||
copyCodeFolder(libsFolder);
|
||||
|
||||
// Copy the data folder (if one exists) to the project's 'assets' folder
|
||||
final File sketchDataFolder = sketch.getDataFolder();
|
||||
if (sketchDataFolder.exists()) {
|
||||
Base.copyDir(sketchDataFolder, assetsFolder);
|
||||
}
|
||||
|
||||
// Do the same for the 'res' folder.
|
||||
// http://code.google.com/p/processing/issues/detail?id=767
|
||||
final File sketchResFolder = new File(sketch.getFolder(), "res");
|
||||
if (sketchResFolder.exists()) {
|
||||
Base.copyDir(sketchResFolder, resFolder);
|
||||
}
|
||||
}
|
||||
return tmpFolder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The Android dex util pukes on paths containing spaces, which will happen
|
||||
* most of the time on Windows, since Processing sketches wind up in
|
||||
* "My Documents". Therefore, build android in a temp file.
|
||||
* http://code.google.com/p/android/issues/detail?id=4567
|
||||
*
|
||||
* TODO: better would be to retrieve the 8.3 name for the sketch folder!
|
||||
*
|
||||
* @param sketch
|
||||
* @return A folder in which to build the android sketch
|
||||
* @throws IOException
|
||||
*/
|
||||
private File createTempBuildFolder(final Sketch sketch) throws IOException {
|
||||
final File tmp = File.createTempFile("android", "sketch");
|
||||
if (!(tmp.delete() && tmp.mkdir())) {
|
||||
throw new IOException("Cannot create temp dir " + tmp + " to build android sketch");
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
|
||||
protected File createExportFolder() throws IOException {
|
||||
// Sketch sketch = editor.getSketch();
|
||||
// Create the 'android' build folder, and move any existing version out.
|
||||
File androidFolder = new File(sketch.getFolder(), "android");
|
||||
if (androidFolder.exists()) {
|
||||
// Date mod = new Date(androidFolder.lastModified());
|
||||
String stamp = AndroidMode.getDateStamp(androidFolder.lastModified());
|
||||
File dest = new File(sketch.getFolder(), "android." + stamp);
|
||||
boolean result = androidFolder.renameTo(dest);
|
||||
if (!result) {
|
||||
ProcessHelper mv;
|
||||
ProcessResult pr;
|
||||
try {
|
||||
System.err.println("createProject renameTo() failed, resorting to mv/move instead.");
|
||||
mv = new ProcessHelper("mv", androidFolder.getAbsolutePath(), dest.getAbsolutePath());
|
||||
pr = mv.execute();
|
||||
|
||||
// } catch (IOException e) {
|
||||
// editor.statusError(e);
|
||||
// return null;
|
||||
//
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
if (!pr.succeeded()) {
|
||||
System.err.println(pr.getStderr());
|
||||
Base.showWarning("Failed to rename",
|
||||
"Could not rename the old “android” build folder.\n" +
|
||||
"Please delete, close, or rename the folder\n" +
|
||||
androidFolder.getAbsolutePath() + "\n" +
|
||||
"and try again." , null);
|
||||
Base.openFolder(sketch.getFolder());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
boolean result = androidFolder.mkdirs();
|
||||
if (!result) {
|
||||
Base.showWarning("Folders, folders, folders",
|
||||
"Could not create the necessary folders to build.\n" +
|
||||
"Perhaps you have some file permissions to sort out?", null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return androidFolder;
|
||||
}
|
||||
|
||||
|
||||
public File exportProject() throws IOException, SketchException {
|
||||
// File projectFolder = build("debug");
|
||||
// if (projectFolder == null) {
|
||||
// return null;
|
||||
// }
|
||||
// this will set debuggable to true in the .xml file
|
||||
target = "debug";
|
||||
File projectFolder = createProject();
|
||||
if (projectFolder != null) {
|
||||
File exportFolder = createExportFolder();
|
||||
Base.copyDir(projectFolder, exportFolder);
|
||||
return exportFolder;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
// SDK tools 17 have a problem where 'dex' won't pick up the libs folder
|
||||
// (which contains our friend processing-core.jar) unless your current
|
||||
// working directory is the same as the build file. So this is an unpleasant
|
||||
// workaround, at least until things are fixed or we hear of a better way.
|
||||
// This was fixed in SDK 19 (and Processing revision 0205) so we've now
|
||||
// disabled this portion of the code.
|
||||
protected boolean antBuild_dexworkaround() throws SketchException {
|
||||
try {
|
||||
// ProcessHelper helper = new ProcessHelper(tmpFolder, new String[] { "ant", target });
|
||||
// Windows doesn't include full paths, so make 'em happen.
|
||||
String cp = System.getProperty("java.class.path");
|
||||
String[] cpp = PApplet.split(cp, File.pathSeparatorChar);
|
||||
for (int i = 0; i < cpp.length; i++) {
|
||||
cpp[i] = new File(cpp[i]).getAbsolutePath();
|
||||
}
|
||||
cp = PApplet.join(cpp, File.pathSeparator);
|
||||
|
||||
// Since Ant may or may not be installed, call it from the .jar file,
|
||||
// though hopefully 'java' is in the classpath.. Given what we do in
|
||||
// processing.mode.java.runner (and it that it works), should be ok.
|
||||
String[] cmd = new String[] {
|
||||
"java",
|
||||
"-cp", cp, //System.getProperty("java.class.path"),
|
||||
"org.apache.tools.ant.Main", target
|
||||
// "ant", target
|
||||
};
|
||||
ProcessHelper helper = new ProcessHelper(tmpFolder, cmd);
|
||||
ProcessResult pr = helper.execute();
|
||||
if (pr.getResult() != 0) {
|
||||
// System.err.println("mo builds, mo problems");
|
||||
System.err.println(pr.getStderr());
|
||||
System.out.println(pr.getStdout());
|
||||
// the actual javac errors and whatnot go to stdout
|
||||
antBuildProblems(pr.getStdout(), pr.getStderr());
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
return false;
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
public class HopefullyTemporaryWorkaround extends org.apache.tools.ant.Main {
|
||||
|
||||
protected void exit(int exitCode) {
|
||||
// I want to exit, but let's not System.exit()
|
||||
System.out.println("gonna exit");
|
||||
System.out.flush();
|
||||
System.err.flush();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected boolean antBuild() throws SketchException {
|
||||
String[] cmd = new String[] {
|
||||
"-main", "processing.mode.android.HopefullyTemporaryWorkaround",
|
||||
"-Duser.dir=" + tmpFolder.getAbsolutePath(),
|
||||
"-logfile", "/Users/fry/Desktop/ant-log.txt",
|
||||
"-verbose",
|
||||
"-help",
|
||||
// "debug"
|
||||
};
|
||||
HopefullyTemporaryWorkaround.main(cmd);
|
||||
return true;
|
||||
// ProcessResult listResult =
|
||||
// new ProcessHelper("ant", "debug", tmpFolder).execute();
|
||||
// if (listResult.succeeded()) {
|
||||
// boolean badness = false;
|
||||
// for (String line : listResult) {
|
||||
// }
|
||||
// }
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
protected boolean antBuild() throws SketchException {
|
||||
// System.setProperty("user.dir", tmpFolder.getAbsolutePath()); // oh why not { because it doesn't help }
|
||||
final Project p = new Project();
|
||||
// p.setBaseDir(tmpFolder); // doesn't seem to do anything
|
||||
|
||||
// System.out.println(tmpFolder.getAbsolutePath());
|
||||
// p.setUserProperty("user.dir", tmpFolder.getAbsolutePath());
|
||||
String path = buildFile.getAbsolutePath().replace('\\', '/');
|
||||
p.setUserProperty("ant.file", path);
|
||||
|
||||
// deals with a problem where javac error messages weren't coming through
|
||||
p.setUserProperty("build.compiler", "extJavac");
|
||||
// p.setUserProperty("build.compiler.emacs", "true"); // does nothing
|
||||
|
||||
// try to spew something useful to the console
|
||||
final DefaultLogger consoleLogger = new DefaultLogger();
|
||||
consoleLogger.setErrorPrintStream(System.err);
|
||||
consoleLogger.setOutputPrintStream(System.out); // ? uncommented before
|
||||
// WARN, INFO, VERBOSE, DEBUG
|
||||
// consoleLogger.setMessageOutputLevel(Project.MSG_ERR);
|
||||
consoleLogger.setMessageOutputLevel(Project.MSG_INFO);
|
||||
// consoleLogger.setMessageOutputLevel(Project.MSG_DEBUG);
|
||||
p.addBuildListener(consoleLogger);
|
||||
|
||||
// This logger is used to pick up javac errors to be parsed into
|
||||
// SketchException objects. Note that most errors seem to show up on stdout
|
||||
// since that's where the [javac] prefixed lines are coming through.
|
||||
final DefaultLogger errorLogger = new DefaultLogger();
|
||||
final ByteArrayOutputStream errb = new ByteArrayOutputStream();
|
||||
final PrintStream errp = new PrintStream(errb);
|
||||
errorLogger.setErrorPrintStream(errp);
|
||||
final ByteArrayOutputStream outb = new ByteArrayOutputStream();
|
||||
final PrintStream outp = new PrintStream(outb);
|
||||
errorLogger.setOutputPrintStream(outp);
|
||||
errorLogger.setMessageOutputLevel(Project.MSG_INFO);
|
||||
// errorLogger.setMessageOutputLevel(Project.MSG_DEBUG);
|
||||
p.addBuildListener(errorLogger);
|
||||
|
||||
try {
|
||||
// editor.statusNotice("Building sketch for Android...");
|
||||
p.fireBuildStarted();
|
||||
p.init();
|
||||
final ProjectHelper helper = ProjectHelper.getProjectHelper();
|
||||
p.addReference("ant.projectHelper", helper);
|
||||
helper.parse(p, buildFile);
|
||||
// p.executeTarget(p.getDefaultTarget());
|
||||
p.executeTarget(target);
|
||||
// editor.statusNotice("Finished building sketch.");
|
||||
return true;
|
||||
|
||||
} catch (final BuildException e) {
|
||||
// Send a "build finished" event to the build listeners for this project.
|
||||
p.fireBuildFinished(e);
|
||||
|
||||
// PApplet.println(new String(errb.toByteArray()));
|
||||
// PApplet.println(new String(outb.toByteArray()));
|
||||
|
||||
// String errorOutput = new String(errb.toByteArray());
|
||||
// String[] errorLines =
|
||||
// errorOutput.split(System.getProperty("line.separator"));
|
||||
// PApplet.println(errorLines);
|
||||
|
||||
//final String outPile = new String(outb.toByteArray());
|
||||
//antBuildProblems(new String(outb.toByteArray())
|
||||
antBuildProblems(new String(outb.toByteArray()),
|
||||
new String(errb.toByteArray()));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void antBuildProblems(String outPile, String errPile) throws SketchException {
|
||||
final String[] outLines =
|
||||
outPile.split(System.getProperty("line.separator"));
|
||||
final String[] errLines =
|
||||
errPile.split(System.getProperty("line.separator"));
|
||||
|
||||
for (final String line : outLines) {
|
||||
final String javacPrefix = "[javac]";
|
||||
final int javacIndex = line.indexOf(javacPrefix);
|
||||
if (javacIndex != -1) {
|
||||
// System.out.println("checking: " + line);
|
||||
// final Sketch sketch = editor.getSketch();
|
||||
// String sketchPath = sketch.getFolder().getAbsolutePath();
|
||||
int offset = javacIndex + javacPrefix.length() + 1;
|
||||
String[] pieces =
|
||||
PApplet.match(line.substring(offset), "^(.+):([0-9]+):\\s+(.+)$");
|
||||
if (pieces != null) {
|
||||
// PApplet.println(pieces);
|
||||
String fileName = pieces[1];
|
||||
// remove the path from the front of the filename
|
||||
//fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
|
||||
fileName = fileName.substring(fileName.lastIndexOf(File.separatorChar) + 1);
|
||||
final int lineNumber = PApplet.parseInt(pieces[2]) - 1;
|
||||
// PApplet.println("looking for " + fileName + " line " + lineNumber);
|
||||
SketchException rex = placeException(pieces[3], fileName, lineNumber);
|
||||
if (rex != null) {
|
||||
// System.out.println("found a rex");
|
||||
// rex.hideStackTrace();
|
||||
// editor.statusError(rex);
|
||||
// return false; // get outta here
|
||||
throw rex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Couldn't parse the exception, so send something generic
|
||||
SketchException skex =
|
||||
new SketchException("Error from inside the Android tools, " +
|
||||
"check the console.");
|
||||
|
||||
// Try to parse anything else we might know about
|
||||
for (final String line : errLines) {
|
||||
if (line.contains("Unable to resolve target '" + sdkTarget + "'")) {
|
||||
System.err.println("Use the Android SDK Manager (under the Android");
|
||||
System.err.println("menu) to install the SDK platform and ");
|
||||
System.err.println("Google APIs for Android " + sdkName +
|
||||
" (API " + sdkVersion + ")");
|
||||
skex = new SketchException("Please install the SDK platform and " +
|
||||
"Google APIs for API " + sdkVersion);
|
||||
}
|
||||
}
|
||||
// Stack trace is not relevant, just the message.
|
||||
skex.hideStackTrace();
|
||||
throw skex;
|
||||
}
|
||||
|
||||
|
||||
String getPathForAPK() {
|
||||
String suffix = target.equals("release") ? "unsigned" : "debug";
|
||||
String apkName = "bin/" + sketch.getName() + "-" + suffix + ".apk";
|
||||
final File apkFile = new File(tmpFolder, apkName);
|
||||
if (!apkFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
return apkFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
|
||||
private void writeAntProps(final File file) {
|
||||
final PrintWriter writer = PApplet.createWriter(file);
|
||||
writer.println("application-package=" + getPackageName());
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
|
||||
private void writeBuildXML(final File file, final String projectName) {
|
||||
final PrintWriter writer = PApplet.createWriter(file);
|
||||
writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||
|
||||
writer.println("<project name=\"" + projectName + "\" default=\"help\">");
|
||||
|
||||
writer.println(" <property file=\"local.properties\" />");
|
||||
writer.println(" <property file=\"ant.properties\" />");
|
||||
|
||||
writer.println(" <property environment=\"env\" />");
|
||||
writer.println(" <condition property=\"sdk.dir\" value=\"${env.ANDROID_HOME}\">");
|
||||
writer.println(" <isset property=\"env.ANDROID_HOME\" />");
|
||||
writer.println(" </condition>");
|
||||
|
||||
writer.println(" <loadproperties srcFile=\"project.properties\" />");
|
||||
|
||||
writer.println(" <fail message=\"sdk.dir is missing. Make sure to generate local.properties using 'android update project'\" unless=\"sdk.dir\" />");
|
||||
|
||||
writer.println(" <import file=\"custom_rules.xml\" optional=\"true\" />");
|
||||
|
||||
writer.println(" <!-- version-tag: 1 -->"); // should this be 'custom' instead of 1?
|
||||
writer.println(" <import file=\"${sdk.dir}/tools/ant/build.xml\" />");
|
||||
|
||||
writer.println("</project>");
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
|
||||
private void writeProjectProps(final File file) {
|
||||
final PrintWriter writer = PApplet.createWriter(file);
|
||||
writer.println("target=" + sdkTarget);
|
||||
writer.println();
|
||||
// http://stackoverflow.com/questions/4821043/includeantruntime-was-not-set-for-android-ant-script
|
||||
writer.println("# Suppress the javac task warnings about \"includeAntRuntime\"");
|
||||
writer.println("build.sysclasspath=last");
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
|
||||
private void writeLocalProps(final File file) {
|
||||
final PrintWriter writer = PApplet.createWriter(file);
|
||||
final String sdkPath = sdk.getSdkFolder().getAbsolutePath();
|
||||
if (Base.isWindows()) {
|
||||
// Windows needs backslashes escaped, or it will also accept forward
|
||||
// slashes in the build file. We're using the forward slashes since this
|
||||
// path gets concatenated with a lot of others that use forwards anyway.
|
||||
writer.println("sdk.dir=" + sdkPath.replace('\\', '/'));
|
||||
} else {
|
||||
writer.println("sdk.dir=" + sdkPath);
|
||||
}
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
|
||||
static final String ICON_72 = "icon-72.png";
|
||||
static final String ICON_48 = "icon-48.png";
|
||||
static final String ICON_36 = "icon-36.png";
|
||||
|
||||
private void writeRes(File resFolder,
|
||||
String className) throws SketchException {
|
||||
File layoutFolder = mkdirs(resFolder, "layout");
|
||||
File layoutFile = new File(layoutFolder, "main.xml");
|
||||
writeResLayoutMain(layoutFile);
|
||||
|
||||
// write the icon files
|
||||
File sketchFolder = sketch.getFolder();
|
||||
File localIcon36 = new File(sketchFolder, ICON_36);
|
||||
File localIcon48 = new File(sketchFolder, ICON_48);
|
||||
File localIcon72 = new File(sketchFolder, ICON_72);
|
||||
|
||||
// File drawableFolder = new File(resFolder, "drawable");
|
||||
// drawableFolder.mkdirs()
|
||||
File buildIcon48 = new File(resFolder, "drawable/icon.png");
|
||||
File buildIcon36 = new File(resFolder, "drawable-ldpi/icon.png");
|
||||
File buildIcon72 = new File(resFolder, "drawable-hdpi/icon.png");
|
||||
|
||||
if (!localIcon36.exists() &&
|
||||
!localIcon48.exists() &&
|
||||
!localIcon72.exists()) {
|
||||
try {
|
||||
// if no icons are in the sketch folder, then copy all the defaults
|
||||
if (buildIcon36.getParentFile().mkdirs()) {
|
||||
Base.copyFile(mode.getContentFile("icons/" + ICON_36), buildIcon36);
|
||||
} else {
|
||||
System.err.println("Could not create \"drawable-ldpi\" folder.");
|
||||
}
|
||||
if (buildIcon48.getParentFile().mkdirs()) {
|
||||
Base.copyFile(mode.getContentFile("icons/" + ICON_48), buildIcon48);
|
||||
} else {
|
||||
System.err.println("Could not create \"drawable\" folder.");
|
||||
}
|
||||
if (buildIcon72.getParentFile().mkdirs()) {
|
||||
Base.copyFile(mode.getContentFile("icons/" + ICON_72), buildIcon72);
|
||||
} else {
|
||||
System.err.println("Could not create \"drawable-hdpi\" folder.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
//throw new SketchException("Could not get Android icons");
|
||||
}
|
||||
} else {
|
||||
// if at least one of the icons already exists, then use that across the board
|
||||
try {
|
||||
if (localIcon36.exists()) {
|
||||
if (new File(resFolder, "drawable-ldpi").mkdirs()) {
|
||||
Base.copyFile(localIcon36, buildIcon36);
|
||||
}
|
||||
}
|
||||
if (localIcon48.exists()) {
|
||||
if (new File(resFolder, "drawable").mkdirs()) {
|
||||
Base.copyFile(localIcon48, buildIcon48);
|
||||
}
|
||||
}
|
||||
if (localIcon72.exists()) {
|
||||
if (new File(resFolder, "drawable-hdpi").mkdirs()) {
|
||||
Base.copyFile(localIcon72, buildIcon72);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("Problem while copying icons.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// final File valuesFolder = mkdirs(resFolder, "values");
|
||||
// final File stringsFile = new File(valuesFolder, "strings.xml");
|
||||
// writeResValuesStrings(stringsFile, className);
|
||||
}
|
||||
|
||||
|
||||
private File mkdirs(final File parent, final String name) throws SketchException {
|
||||
final File result = new File(parent, name);
|
||||
if (!(result.exists() || result.mkdirs())) {
|
||||
throw new SketchException("Could not create " + result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private void writeResLayoutMain(final File file) {
|
||||
final PrintWriter writer = PApplet.createWriter(file);
|
||||
writer.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
|
||||
writer.println("<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"");
|
||||
writer.println(" android:orientation=\"vertical\"");
|
||||
writer.println(" android:layout_width=\"fill_parent\"");
|
||||
writer.println(" android:layout_height=\"fill_parent\">");
|
||||
writer.println("</LinearLayout>");
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
|
||||
// This recommended to be a string resource so that it can be localized.
|
||||
// nah.. we're gonna be messing with it in the GUI anyway...
|
||||
// people can edit themselves if they need to
|
||||
// private static void writeResValuesStrings(final File file,
|
||||
// final String className) {
|
||||
// final PrintWriter writer = PApplet.createWriter(file);
|
||||
// writer.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
|
||||
// writer.println("<resources>");
|
||||
// writer.println(" <string name=\"app_name\">" + className + "</string>");
|
||||
// writer.println("</resources>");
|
||||
// writer.flush();
|
||||
// writer.close();
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* For each library, copy .jar and .zip files to the 'libs' folder,
|
||||
* and copy anything else to the 'assets' folder.
|
||||
*/
|
||||
private void copyLibraries(final File libsFolder,
|
||||
final File assetsFolder) throws IOException {
|
||||
for (Library library : getImportedLibraries()) {
|
||||
// add each item from the library folder / export list to the output
|
||||
for (File exportFile : library.getAndroidExports()) {
|
||||
String exportName = exportFile.getName();
|
||||
if (!exportFile.exists()) {
|
||||
System.err.println(exportFile.getName() +
|
||||
" is mentioned in export.txt, but it's " +
|
||||
"a big fat lie and does not exist.");
|
||||
} else if (exportFile.isDirectory()) {
|
||||
// Copy native library folders to the correct location
|
||||
if (exportName.equals("armeabi") ||
|
||||
exportName.equals("armeabi-v7a") ||
|
||||
exportName.equals("x86")) {
|
||||
Base.copyDir(exportFile, new File(libsFolder, exportName));
|
||||
} else {
|
||||
// Copy any other directory to the assets folder
|
||||
Base.copyDir(exportFile, new File(assetsFolder, exportName));
|
||||
}
|
||||
} else if (exportName.toLowerCase().endsWith(".zip")) {
|
||||
// As of r4 of the Android SDK, it looks like .zip files
|
||||
// are ignored in the libs folder, so rename to .jar
|
||||
System.err.println(".zip files are not allowed in Android libraries.");
|
||||
System.err.println("Please rename " + exportFile.getName() + " to be a .jar file.");
|
||||
String jarName = exportName.substring(0, exportName.length() - 4) + ".jar";
|
||||
Base.copyFile(exportFile, new File(libsFolder, jarName));
|
||||
|
||||
} else if (exportName.toLowerCase().endsWith(".jar")) {
|
||||
Base.copyFile(exportFile, new File(libsFolder, exportName));
|
||||
|
||||
} else {
|
||||
Base.copyFile(exportFile, new File(assetsFolder, exportName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// private void copyLibraries(final File libsFolder,
|
||||
// final File assetsFolder) throws IOException {
|
||||
// // Copy any libraries to the 'libs' folder
|
||||
// for (Library library : getImportedLibraries()) {
|
||||
// File libraryFolder = new File(library.getPath());
|
||||
// // in the list is a File object that points the
|
||||
// // library sketch's "library" folder
|
||||
// final File exportSettings = new File(libraryFolder, "export.txt");
|
||||
// final HashMap<String, String> exportTable =
|
||||
// Base.readSettings(exportSettings);
|
||||
// final String androidList = exportTable.get("android");
|
||||
// String exportList[] = null;
|
||||
// if (androidList != null) {
|
||||
// exportList = PApplet.splitTokens(androidList, ", ");
|
||||
// } else {
|
||||
// exportList = libraryFolder.list();
|
||||
// }
|
||||
// for (int i = 0; i < exportList.length; i++) {
|
||||
// exportList[i] = PApplet.trim(exportList[i]);
|
||||
// if (exportList[i].equals("") || exportList[i].equals(".")
|
||||
// || exportList[i].equals("..")) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// final File exportFile = new File(libraryFolder, exportList[i]);
|
||||
// if (!exportFile.exists()) {
|
||||
// System.err.println("File " + exportList[i] + " does not exist");
|
||||
// } else if (exportFile.isDirectory()) {
|
||||
// System.err.println("Ignoring sub-folder \"" + exportList[i] + "\"");
|
||||
// } else {
|
||||
// final String name = exportFile.getName();
|
||||
// final String lcname = name.toLowerCase();
|
||||
// if (lcname.endsWith(".zip") || lcname.endsWith(".jar")) {
|
||||
// // As of r4 of the Android SDK, it looks like .zip files
|
||||
// // are ignored in the libs folder, so rename to .jar
|
||||
// final String jarName =
|
||||
// name.substring(0, name.length() - 4) + ".jar";
|
||||
// Base.copyFile(exportFile, new File(libsFolder, jarName));
|
||||
// } else {
|
||||
// // just copy other files over directly
|
||||
// Base.copyFile(exportFile, new File(assetsFolder, name));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
private void copyCodeFolder(final File libsFolder) throws IOException {
|
||||
// Copy files from the 'code' directory into the 'libs' folder
|
||||
final File codeFolder = sketch.getCodeFolder();
|
||||
if (codeFolder != null && codeFolder.exists()) {
|
||||
for (final File item : codeFolder.listFiles()) {
|
||||
if (!item.isDirectory()) {
|
||||
final String name = item.getName();
|
||||
final String lcname = name.toLowerCase();
|
||||
if (lcname.endsWith(".jar") || lcname.endsWith(".zip")) {
|
||||
String jarName = name.substring(0, name.length() - 4) + ".jar";
|
||||
Base.copyFile(item, new File(libsFolder, jarName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected String getPackageName() {
|
||||
return manifest.getPackageName();
|
||||
}
|
||||
|
||||
|
||||
public void cleanup() {
|
||||
// don't want to be responsible for this
|
||||
//rm(tempBuildFolder);
|
||||
tmpFolder.deleteOnExit();
|
||||
}
|
||||
}
|
||||
@@ -1,398 +0,0 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2009-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
|
||||
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.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuItem;
|
||||
|
||||
import processing.app.*;
|
||||
import processing.mode.java.JavaEditor;
|
||||
|
||||
import processing.core.PApplet;
|
||||
|
||||
|
||||
public class AndroidEditor extends JavaEditor {
|
||||
private AndroidMode androidMode;
|
||||
|
||||
|
||||
protected AndroidEditor(Base base, String path, EditorState state, Mode mode) throws Exception {
|
||||
super(base, path, state, mode);
|
||||
androidMode = (AndroidMode) mode;
|
||||
androidMode.checkSDK(this);
|
||||
}
|
||||
|
||||
|
||||
public EditorToolbar createToolbar() {
|
||||
return new AndroidToolbar(this, base);
|
||||
}
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
|
||||
public JMenu buildFileMenu() {
|
||||
String exportPkgTitle = AndroidToolbar.getTitle(AndroidToolbar.EXPORT, false);
|
||||
JMenuItem exportPackage = Toolkit.newJMenuItem(exportPkgTitle, 'E');
|
||||
exportPackage.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
handleExportPackage();
|
||||
}
|
||||
});
|
||||
|
||||
String exportProjectTitle = AndroidToolbar.getTitle(AndroidToolbar.EXPORT, true);
|
||||
JMenuItem exportProject = Toolkit.newJMenuItemShift(exportProjectTitle, 'E');
|
||||
exportProject.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
handleExportProject();
|
||||
}
|
||||
});
|
||||
|
||||
return buildFileMenu(new JMenuItem[] { exportPackage, exportProject});
|
||||
}
|
||||
|
||||
|
||||
public JMenu buildSketchMenu() {
|
||||
JMenuItem runItem = Toolkit.newJMenuItem(AndroidToolbar.getTitle(AndroidToolbar.RUN, false), 'R');
|
||||
runItem.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
handleRunDevice();
|
||||
}
|
||||
});
|
||||
|
||||
JMenuItem presentItem = Toolkit.newJMenuItemShift(AndroidToolbar.getTitle(AndroidToolbar.RUN, true), 'R');
|
||||
presentItem.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
handleRunEmulator();
|
||||
}
|
||||
});
|
||||
|
||||
JMenuItem stopItem = new JMenuItem(AndroidToolbar.getTitle(AndroidToolbar.STOP, false));
|
||||
stopItem.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
handleStop();
|
||||
}
|
||||
});
|
||||
return buildSketchMenu(new JMenuItem[] { runItem, presentItem, stopItem });
|
||||
}
|
||||
|
||||
|
||||
public JMenu buildModeMenu() {
|
||||
JMenu menu = new JMenu("Android");
|
||||
JMenuItem item;
|
||||
|
||||
item = new JMenuItem("Sketch Permissions");
|
||||
item.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
new Permissions(sketch);
|
||||
}
|
||||
});
|
||||
menu.add(item);
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
item = new JMenuItem("Signing Key Setup");
|
||||
item.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
new Keys(AndroidEditor.this);
|
||||
}
|
||||
});
|
||||
item.setEnabled(false);
|
||||
menu.add(item);
|
||||
|
||||
item = new JMenuItem("Android SDK Manager");
|
||||
item.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
File file = androidMode.getSDK().getAndroidTool();
|
||||
PApplet.exec(new String[] { file.getAbsolutePath(), "sdk" });
|
||||
}
|
||||
});
|
||||
menu.add(item);
|
||||
|
||||
item = new JMenuItem("Android AVD Manager");
|
||||
item.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
File file = androidMode.getSDK().getAndroidTool();
|
||||
PApplet.exec(new String[] { file.getAbsolutePath(), "avd" });
|
||||
}
|
||||
});
|
||||
menu.add(item);
|
||||
|
||||
item = new JMenuItem("Reset Connections");
|
||||
item.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
// editor.statusNotice("Resetting the Android Debug Bridge server.");
|
||||
Devices.killAdbServer();
|
||||
}
|
||||
});
|
||||
menu.add(item);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uses the main help menu, and adds a few extra options. If/when there's
|
||||
* Android-specific documentation, we'll switch to that.
|
||||
*/
|
||||
public JMenu buildHelpMenu() {
|
||||
JMenu menu = super.buildHelpMenu();
|
||||
JMenuItem item;
|
||||
|
||||
menu.addSeparator();
|
||||
|
||||
item = new JMenuItem("Processing for Android Wiki");
|
||||
item.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Base.openURL("http://wiki.processing.org/w/Android");
|
||||
}
|
||||
});
|
||||
menu.add(item);
|
||||
|
||||
|
||||
item = new JMenuItem("Android Developers Site");
|
||||
item.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Base.openURL("http://developer.android.com/index.html");
|
||||
}
|
||||
});
|
||||
menu.add(item);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
|
||||
/** override the standard grab reference to just show the java reference */
|
||||
public void showReference(String filename) {
|
||||
File javaReferenceFolder = Base.getContentFile("modes/java/reference");
|
||||
File file = new File(javaReferenceFolder, filename);
|
||||
Base.openURL("file://" + file.getAbsolutePath());
|
||||
}
|
||||
|
||||
|
||||
|
||||
// protected void updateMode() {
|
||||
// // When the selection is made, the menu will update itself
|
||||
// boolean active = toggleItem.isSelected();
|
||||
// if (active) {
|
||||
// boolean rolling = true;
|
||||
// if (sdk == null) {
|
||||
// rolling = loadAndroid();
|
||||
// }
|
||||
// if (rolling) {
|
||||
// editor.setHandlers(new RunHandler(), new PresentHandler(),
|
||||
// new StopHandler(),
|
||||
// new ExportHandler(), new ExportAppHandler());
|
||||
// build = new AndroidBuild(editor, sdk);
|
||||
// editor.statusNotice("Android mode enabled for this editor window.");
|
||||
// }
|
||||
// } else {
|
||||
// editor.resetHandlers();
|
||||
// editor.statusNotice("Android mode disabled.");
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// protected boolean loadAndroid() {
|
||||
// statusNotice("Loading Android tools.");
|
||||
//
|
||||
// try {
|
||||
// sdk = AndroidSDK.find(this);
|
||||
// } catch (final Exception e) {
|
||||
// Base.showWarning("Android Tools Error", e.getMessage(), null);
|
||||
// statusNotice("Android mode canceled.");
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // Make sure that the processing.android.core.* classes are available
|
||||
// if (!checkCore()) {
|
||||
// statusNotice("Android mode canceled.");
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// statusNotice("Done loading Android tools.");
|
||||
// return true;
|
||||
// }
|
||||
|
||||
|
||||
// static protected File getCoreZipLocation() {
|
||||
// if (coreZipLocation == null) {
|
||||
// coreZipLocation = checkCoreZipLocation();
|
||||
// }
|
||||
// return coreZipLocation;
|
||||
// }
|
||||
|
||||
|
||||
// private boolean checkCore() {
|
||||
// final File target = getCoreZipLocation();
|
||||
// if (!target.exists()) {
|
||||
// try {
|
||||
// final URL url = new URL(ANDROID_CORE_URL);
|
||||
// PApplet.saveStream(target, url.openStream());
|
||||
// } catch (final Exception e) {
|
||||
// Base.showWarning("Download Error",
|
||||
// "Could not download Android core.zip", e);
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
|
||||
|
||||
public void statusError(String what) {
|
||||
super.statusError(what);
|
||||
// new Exception("deactivating RUN").printStackTrace();
|
||||
toolbar.deactivate(AndroidToolbar.RUN);
|
||||
}
|
||||
|
||||
|
||||
public void sketchStopped() {
|
||||
deactivateRun();
|
||||
statusEmpty();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build the sketch and run it inside an emulator with the debugger.
|
||||
*/
|
||||
public void handleRunEmulator() {
|
||||
new Thread() {
|
||||
public void run() {
|
||||
toolbar.activate(AndroidToolbar.RUN);
|
||||
startIndeterminate();
|
||||
prepareRun();
|
||||
try {
|
||||
androidMode.handleRunEmulator(sketch, AndroidEditor.this);
|
||||
} catch (SketchException e) {
|
||||
statusError(e);
|
||||
} catch (IOException e) {
|
||||
statusError(e);
|
||||
}
|
||||
stopIndeterminate();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Build the sketch and run it on a device with the debugger connected.
|
||||
*/
|
||||
public void handleRunDevice() {
|
||||
new Thread() {
|
||||
public void run() {
|
||||
toolbar.activate(AndroidToolbar.RUN);
|
||||
startIndeterminate();
|
||||
prepareRun();
|
||||
try {
|
||||
androidMode.handleRunDevice(sketch, AndroidEditor.this);
|
||||
} catch (SketchException e) {
|
||||
statusError(e);
|
||||
} catch (IOException e) {
|
||||
statusError(e);
|
||||
}
|
||||
stopIndeterminate();
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
|
||||
public void handleStop() {
|
||||
toolbar.deactivate(AndroidToolbar.RUN);
|
||||
stopIndeterminate();
|
||||
androidMode.handleStop(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a release build of the sketch and have its apk files ready.
|
||||
* If users want a debug build, they can do that from the command line.
|
||||
*/
|
||||
public void handleExportProject() {
|
||||
if (handleExportCheckModified()) {
|
||||
new Thread() {
|
||||
public void run() {
|
||||
toolbar.activate(AndroidToolbar.EXPORT);
|
||||
startIndeterminate();
|
||||
statusNotice("Exporting a debug version of the sketch...");
|
||||
AndroidBuild build = new AndroidBuild(sketch, androidMode);
|
||||
try {
|
||||
File exportFolder = build.exportProject();
|
||||
if (exportFolder != null) {
|
||||
Base.openFolder(exportFolder);
|
||||
statusNotice("Done with export.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
statusError(e);
|
||||
} catch (SketchException e) {
|
||||
statusError(e);
|
||||
}
|
||||
stopIndeterminate();
|
||||
toolbar.deactivate(AndroidToolbar.EXPORT);
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
// try {
|
||||
// buildReleaseForExport("debug");
|
||||
// } catch (final MonitorCanceled ok) {
|
||||
// statusNotice("Canceled.");
|
||||
// } finally {
|
||||
// deactivateExport();
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a release build of the sketch and install its apk files on the
|
||||
* attached device.
|
||||
*/
|
||||
public void handleExportPackage() {
|
||||
// Need to implement an entire signing setup first
|
||||
// http://dev.processing.org/bugs/show_bug.cgi?id=1430
|
||||
statusError("Exporting signed packages is not yet implemented.");
|
||||
deactivateExport();
|
||||
|
||||
// make a release build
|
||||
// try {
|
||||
// buildReleaseForExport("release");
|
||||
// } catch (final MonitorCanceled ok) {
|
||||
// statusNotice("Canceled.");
|
||||
// } finally {
|
||||
// deactivateExport();
|
||||
// }
|
||||
|
||||
// TODO now sign it... lots of fun signing code mess to go here. yay!
|
||||
|
||||
// maybe even send it to the device? mmm?
|
||||
// try {
|
||||
// runSketchOnDevice(AndroidEnvironment.getInstance().getHardware(), "release");
|
||||
// } catch (final MonitorCanceled ok) {
|
||||
// editor.statusNotice("Canceled.");
|
||||
// } finally {
|
||||
// editor.deactivateExport();
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2011-12 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.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import processing.app.Base;
|
||||
import processing.app.Editor;
|
||||
import processing.app.EditorState;
|
||||
import processing.app.Library;
|
||||
import processing.app.RunnerListener;
|
||||
import processing.app.Sketch;
|
||||
import processing.app.SketchException;
|
||||
import processing.mode.java.JavaMode;
|
||||
|
||||
|
||||
public class AndroidMode extends JavaMode {
|
||||
private AndroidSDK sdk;
|
||||
private File coreZipLocation;
|
||||
private AndroidRunner runner;
|
||||
|
||||
|
||||
public AndroidMode(Base base, File folder) {
|
||||
super(base, folder);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Editor createEditor(Base base, String path, EditorState state) {
|
||||
try {
|
||||
return new AndroidEditor(base, path, state, this);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return "Android";
|
||||
}
|
||||
|
||||
|
||||
public File[] getKeywordFiles() {
|
||||
return new File[] {
|
||||
Base.getContentFile("modes/java/keywords.txt")
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public File[] getExampleCategoryFolders() {
|
||||
return new File[] {
|
||||
new File(examplesFolder, "Basics"),
|
||||
new File(examplesFolder, "Topics"),
|
||||
new File(examplesFolder, "Demos"),
|
||||
new File(examplesFolder, "Sensors")
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
|
||||
/** @return null so that it doesn't try to pass along the desktop version of core.jar */
|
||||
public Library getCoreLibrary() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
protected File getCoreZipLocation() {
|
||||
if (coreZipLocation == null) {
|
||||
// for debugging only, check to see if this is an svn checkout
|
||||
File debugFile = new File("../../../android/core.zip");
|
||||
if (!debugFile.exists() && Base.isMacOS()) {
|
||||
// current path might be inside Processing.app, so need to go much higher
|
||||
debugFile = new File("../../../../../../../android/core.zip");
|
||||
}
|
||||
if (debugFile.exists()) {
|
||||
System.out.println("Using version of core.zip from local SVN checkout.");
|
||||
// return debugFile;
|
||||
coreZipLocation = debugFile;
|
||||
}
|
||||
|
||||
// otherwise do the usual
|
||||
// return new File(base.getSketchbookFolder(), ANDROID_CORE_FILENAME);
|
||||
coreZipLocation = getContentFile("android-core.zip");
|
||||
}
|
||||
return coreZipLocation;
|
||||
}
|
||||
|
||||
|
||||
// public AndroidSDK loadSDK() throws BadSDKException, IOException {
|
||||
// if (sdk == null) {
|
||||
// sdk = AndroidSDK.load();
|
||||
// }
|
||||
// return sdk;
|
||||
// }
|
||||
|
||||
|
||||
public void checkSDK(Editor parent) {
|
||||
if (sdk == null) {
|
||||
try {
|
||||
sdk = AndroidSDK.load();
|
||||
if (sdk == null) {
|
||||
sdk = AndroidSDK.locate(parent);
|
||||
}
|
||||
} catch (BadSDKException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (sdk == null) {
|
||||
Base.showWarning("It's gonna be a bad day",
|
||||
"The Android SDK could not be loaded.\n" +
|
||||
"Use of Android mode will be all but disabled.",
|
||||
null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public AndroidSDK getSDK() {
|
||||
return sdk;
|
||||
}
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
|
||||
static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMdd.HHmm");
|
||||
|
||||
|
||||
static public String getDateStamp() {
|
||||
return dateFormat.format(new Date());
|
||||
}
|
||||
|
||||
|
||||
static public String getDateStamp(long stamp) {
|
||||
return dateFormat.format(new Date(stamp));
|
||||
}
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
|
||||
// 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");
|
||||
|
||||
boolean avd = AVD.ensureProperAVD(sdk);
|
||||
if (!avd) {
|
||||
SketchException se =
|
||||
new SketchException("Could not create a virtual device for the emulator.");
|
||||
se.hideStackTrace();
|
||||
throw se;
|
||||
}
|
||||
|
||||
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 {
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2009-10 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.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.List;
|
||||
|
||||
import processing.app.*;
|
||||
import processing.mode.java.preproc.PdePreprocessor;
|
||||
import processing.mode.java.preproc.PreprocessorResult;
|
||||
import antlr.RecognitionException;
|
||||
import antlr.TokenStreamException;
|
||||
|
||||
|
||||
public class AndroidPreprocessor extends PdePreprocessor {
|
||||
Sketch sketch;
|
||||
String packageName;
|
||||
|
||||
|
||||
public AndroidPreprocessor(final Sketch sketch,
|
||||
final String packageName) throws IOException {
|
||||
super(sketch.getName());
|
||||
this.sketch = sketch;
|
||||
this.packageName = packageName;
|
||||
}
|
||||
|
||||
|
||||
public String[] initSketchSize(String code) throws SketchException {
|
||||
String[] info = parseSketchSize(code, true);
|
||||
if (info == null) {
|
||||
System.err.println("More about the size() command on Android can be");
|
||||
System.err.println("found here: http://wiki.processing.org/w/Android");
|
||||
throw new SketchException("Could not parse the size() command.");
|
||||
}
|
||||
sizeStatement = info[0];
|
||||
sketchWidth = info[1];
|
||||
sketchHeight = info[2];
|
||||
sketchRenderer = info[3];
|
||||
return info;
|
||||
}
|
||||
|
||||
/*
|
||||
protected boolean parseSketchSize() {
|
||||
// This matches against any uses of the size() function, whether numbers
|
||||
// or variables or whatever. This way, no warning is shown if size() isn't
|
||||
// actually used in the applet, which is the case especially for anyone
|
||||
// who is cutting/pasting from the reference.
|
||||
|
||||
String scrubbed = processing.mode.java.JavaBuild.scrubComments(sketch.getCode(0).getProgram());
|
||||
String[] matches = PApplet.match(scrubbed, processing.mode.java.JavaBuild.SIZE_REGEX);
|
||||
// PApplet.println("matches: " + Sketch.SIZE_REGEX);
|
||||
// PApplet.println(matches);
|
||||
|
||||
if (matches != null) {
|
||||
boolean badSize = false;
|
||||
|
||||
if (matches[1].equals("screenWidth") ||
|
||||
matches[1].equals("screenHeight") ||
|
||||
matches[2].equals("screenWidth") ||
|
||||
matches[2].equals("screenHeight")) {
|
||||
final String message =
|
||||
"The screenWidth and screenHeight variables are named\n" +
|
||||
"displayWidth and displayHeight in this release of Processing.";
|
||||
Base.showWarning("Time for a quick update", message, null);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!matches[1].equals("displayWidth") &&
|
||||
!matches[1].equals("displayHeight") &&
|
||||
PApplet.parseInt(matches[1], -1) == -1) {
|
||||
badSize = true;
|
||||
}
|
||||
if (!matches[2].equals("displayWidth") &&
|
||||
!matches[2].equals("displayHeight") &&
|
||||
PApplet.parseInt(matches[2], -1) == -1) {
|
||||
badSize = true;
|
||||
}
|
||||
|
||||
if (badSize) {
|
||||
// found a reference to size, but it didn't seem to contain numbers
|
||||
final String message =
|
||||
"The size of this applet could not automatically be determined\n" +
|
||||
"from your code. Use only numeric values (not variables) for the\n" +
|
||||
"size() command. See the size() reference for more information.";
|
||||
Base.showWarning("Could not find sketch size", message, null);
|
||||
System.out.println("More about the size() command on Android can be");
|
||||
System.out.println("found here: http://wiki.processing.org/w/Android");
|
||||
return false;
|
||||
}
|
||||
|
||||
// PApplet.println(matches);
|
||||
sizeStatement = matches[0]; // the full method to be removed from the source
|
||||
sketchWidth = matches[1];
|
||||
sketchHeight = matches[2];
|
||||
sketchRenderer = matches[3].trim();
|
||||
if (sketchRenderer.length() == 0) {
|
||||
sketchRenderer = null;
|
||||
}
|
||||
} else {
|
||||
sizeStatement = null;
|
||||
sketchWidth = null;
|
||||
sketchHeight = null;
|
||||
sketchRenderer = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
public PreprocessorResult write(Writer out, String program, String[] codeFolderPackages)
|
||||
throws SketchException, RecognitionException, TokenStreamException {
|
||||
if (sizeStatement != null) {
|
||||
int start = program.indexOf(sizeStatement);
|
||||
program = program.substring(0, start) +
|
||||
program.substring(start + sizeStatement.length());
|
||||
}
|
||||
// the OpenGL package is back in 2.0a5
|
||||
//program = program.replaceAll("import\\s+processing\\.opengl\\.\\S+;", "");
|
||||
return super.write(out, program, codeFolderPackages);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected int writeImports(final PrintWriter out,
|
||||
final List<String> programImports,
|
||||
final List<String> codeFolderImports) {
|
||||
out.println("package " + packageName + ";");
|
||||
out.println();
|
||||
// add two lines for the package above
|
||||
return 2 + super.writeImports(out, programImports, codeFolderImports);
|
||||
}
|
||||
|
||||
|
||||
protected void writeFooter(PrintWriter out, String className) {
|
||||
if (mode == Mode.STATIC) {
|
||||
// close off draw() definition
|
||||
out.println("noLoop();");
|
||||
out.println(indent + "}");
|
||||
}
|
||||
|
||||
if ((mode == Mode.STATIC) || (mode == Mode.ACTIVE)) {
|
||||
out.println();
|
||||
|
||||
if (sketchWidth != null) {
|
||||
out.println(indent + "public int sketchWidth() { return " + sketchWidth + "; }");
|
||||
}
|
||||
if (sketchHeight != null) {
|
||||
out.println(indent + "public int sketchHeight() { return " + sketchHeight + "; }");
|
||||
}
|
||||
if (sketchRenderer != null) {
|
||||
out.println(indent + "public String sketchRenderer() { return " + sketchRenderer + "; }");
|
||||
}
|
||||
|
||||
// close off the class definition
|
||||
out.println("}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// As of revision 0215 (2.0b7-ish), the default imports are now identical
|
||||
// between desktop and Android (to avoid unintended incompatibilities).
|
||||
/*
|
||||
@Override
|
||||
public String[] getCoreImports() {
|
||||
return new String[] {
|
||||
"processing.core.*",
|
||||
"processing.data.*",
|
||||
"processing.event.*",
|
||||
"processing.opengl.*"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String[] getDefaultImports() {
|
||||
final String prefsLine = Preferences.get("android.preproc.imports");
|
||||
if (prefsLine != null) {
|
||||
return PApplet.splitTokens(prefsLine, ", ");
|
||||
}
|
||||
|
||||
// The initial values are stored in here for the day when Android
|
||||
// is broken out as a separate mode.
|
||||
|
||||
// In the future, this may include standard classes for phone or
|
||||
// accelerometer access within the Android APIs. This is currently living
|
||||
// in code rather than preferences.txt because Android mode needs to
|
||||
// maintain its independence from the rest of processing.app.
|
||||
final String[] androidImports = new String[] {
|
||||
// "android.view.MotionEvent", "android.view.KeyEvent",
|
||||
// "android.graphics.Bitmap", //"java.awt.Image",
|
||||
"java.io.*", // for BufferedReader, InputStream, etc
|
||||
//"java.net.*", "java.text.*", // leaving otu for now
|
||||
"java.util.*" // for ArrayList and friends
|
||||
//"java.util.zip.*", "java.util.regex.*" // not necessary w/ newer i/o
|
||||
};
|
||||
|
||||
Preferences.set("android.preproc.imports",
|
||||
PApplet.join(androidImports, ","));
|
||||
|
||||
return androidImports;
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
/* -*- 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<Device> 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("Lost connection with device while launching. Try again.");
|
||||
// Reset the server, in case that's the problem. Sometimes when
|
||||
// launching the emulator times out, the device list refuses to update.
|
||||
Devices.killAdbServer();
|
||||
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());
|
||||
// this stopped working with Android SDK tools revision 17
|
||||
if (!device.installApp(build.getPathForAPK(), listener)) {
|
||||
listener.statusError("Lost connection with device while installing. Try again.");
|
||||
Devices.killAdbServer(); // see above
|
||||
return;
|
||||
}
|
||||
// if (!build.antInstall()) {
|
||||
// }
|
||||
|
||||
// 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" : "device") + ".");
|
||||
} else {
|
||||
listener.statusError("Could not start the sketch.");
|
||||
}
|
||||
listener.stopIndeterminate();
|
||||
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<Device> 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" : "device") + ".");
|
||||
} 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<Device> 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<String> trace) {
|
||||
final Iterator<String> 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.stopIndeterminate();
|
||||
listener.statusHalt();
|
||||
}
|
||||
}
|
||||
@@ -1,341 +0,0 @@
|
||||
package processing.mode.android;
|
||||
|
||||
import java.awt.FileDialog;
|
||||
import java.awt.Frame;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
import processing.app.Base;
|
||||
import processing.app.Platform;
|
||||
import processing.app.Preferences;
|
||||
import processing.app.exec.ProcessHelper;
|
||||
import processing.app.exec.ProcessResult;
|
||||
import processing.core.PApplet;
|
||||
|
||||
class AndroidSDK {
|
||||
private final File folder;
|
||||
private final File tools;
|
||||
private final File platformTools;
|
||||
private final File androidTool;
|
||||
|
||||
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>" +
|
||||
"because the ANDROID_SDK variable is not set. <br>" +
|
||||
"If it is installed, click “Yes” to select the <br>" +
|
||||
"location of the SDK, or “No” to visit the SDK<br>" +
|
||||
"download site at http://developer.android.com/sdk.";
|
||||
|
||||
private static final String SELECT_ANDROID_SDK_FOLDER =
|
||||
"Choose the location of the Android SDK";
|
||||
|
||||
private static final String NOT_ANDROID_SDK =
|
||||
"The selected folder does not appear to contain an Android SDK,\n" +
|
||||
"or the SDK needs to be updated to the latest version.";
|
||||
|
||||
private static final String ANDROID_SDK_URL =
|
||||
"http://developer.android.com/sdk/";
|
||||
|
||||
|
||||
public AndroidSDK(File folder) throws BadSDKException, IOException {
|
||||
this.folder = folder;
|
||||
if (!folder.exists()) {
|
||||
throw new BadSDKException(folder + " does not exist");
|
||||
}
|
||||
|
||||
tools = new File(folder, "tools");
|
||||
if (!tools.exists()) {
|
||||
throw new BadSDKException("There is no tools folder in " + folder);
|
||||
}
|
||||
|
||||
platformTools = new File(folder, "platform-tools");
|
||||
if (!platformTools.exists()) {
|
||||
throw new BadSDKException("There is no platform-tools folder in " + folder);
|
||||
}
|
||||
|
||||
androidTool = findAndroidTool(tools);
|
||||
|
||||
final Platform p = Base.getPlatform();
|
||||
|
||||
String path = p.getenv("PATH");
|
||||
|
||||
p.setenv("ANDROID_SDK", folder.getCanonicalPath());
|
||||
path = platformTools.getCanonicalPath() + File.pathSeparator +
|
||||
tools.getCanonicalPath() + File.pathSeparator + path;
|
||||
|
||||
String javaHomeProp = System.getProperty("java.home");
|
||||
File javaHome = new File(javaHomeProp).getCanonicalFile();
|
||||
p.setenv("JAVA_HOME", javaHome.getCanonicalPath());
|
||||
|
||||
path = new File(javaHome, "bin").getCanonicalPath() + File.pathSeparator + path;
|
||||
|
||||
p.setenv("PATH", path);
|
||||
|
||||
checkDebugCertificate();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If a debug certificate exists, check its expiration date. If it's expired,
|
||||
* remove it so that it doesn't cause problems during the build.
|
||||
*/
|
||||
protected void checkDebugCertificate() {
|
||||
File dotAndroidFolder = new File(System.getProperty("user.home"), ".android");
|
||||
File keystoreFile = new File(dotAndroidFolder, "debug.keystore");
|
||||
if (keystoreFile.exists()) {
|
||||
// keytool -list -v -storepass android -keystore debug.keystore
|
||||
ProcessHelper ph = new ProcessHelper(new String[] {
|
||||
"keytool", "-list", "-v",
|
||||
"-storepass", "android",
|
||||
"-keystore", keystoreFile.getAbsolutePath()
|
||||
});
|
||||
try {
|
||||
ProcessResult result = ph.execute();
|
||||
if (result.succeeded()) {
|
||||
// Valid from: Mon Nov 02 15:38:52 EST 2009 until: Tue Nov 02 16:38:52 EDT 2010
|
||||
String[] lines = PApplet.split(result.getStdout(), '\n');
|
||||
for (String line : lines) {
|
||||
String[] m = PApplet.match(line, "Valid from: .* until: (.*)");
|
||||
if (m != null) {
|
||||
String timestamp = m[1].trim();
|
||||
// "Sun Jan 22 11:09:08 EST 2012"
|
||||
// Hilariously, this is the format of Date.toString(), however
|
||||
// it isn't the default for SimpleDateFormat or others. Yay!
|
||||
DateFormat df = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy");
|
||||
try {
|
||||
Date date = df.parse(timestamp);
|
||||
long expireMillis = date.getTime();
|
||||
if (expireMillis < System.currentTimeMillis()) {
|
||||
System.out.println("Removing expired debug.keystore file.");
|
||||
String hidingName = "debug.keystore." + AndroidMode.getDateStamp(expireMillis);
|
||||
File hidingFile = new File(keystoreFile.getParent(), hidingName);
|
||||
if (!keystoreFile.renameTo(hidingFile)) {
|
||||
System.err.println("Could not remove the expired debug.keystore file.");
|
||||
System.err.println("Please remove the file " + keystoreFile.getAbsolutePath());
|
||||
}
|
||||
// } else {
|
||||
// System.out.println("Nah, that won't expire until " + date); //timestamp);
|
||||
}
|
||||
} catch (ParseException pe) {
|
||||
System.err.println("The date “" + timestamp + "” could not be parsed.");
|
||||
System.err.println("Please report this as a bug so we can fix it.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public File getAndroidTool() {
|
||||
return androidTool;
|
||||
}
|
||||
|
||||
|
||||
public String getAndroidToolPath() {
|
||||
return androidTool.getAbsolutePath();
|
||||
}
|
||||
|
||||
|
||||
public File getSdkFolder() {
|
||||
return folder;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
public File getToolsFolder() {
|
||||
return tools;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
public File getPlatformToolsFolder() {
|
||||
return platformTools;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks a path to see if there's a tools/android file inside, a rough check
|
||||
* for the SDK installation. Also figures out the name of android/android.bat
|
||||
* so that it can be called explicitly.
|
||||
*/
|
||||
private static File findAndroidTool(final File tools) throws BadSDKException {
|
||||
if (new File(tools, "android.exe").exists()) {
|
||||
return new File(tools, "android.exe");
|
||||
}
|
||||
if (new File(tools, "android.bat").exists()) {
|
||||
return new File(tools, "android.bat");
|
||||
}
|
||||
if (new File(tools, "android").exists()) {
|
||||
return new File(tools, "android");
|
||||
}
|
||||
throw new BadSDKException("Cannot find the android tool in " + tools);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check for the ANDROID_SDK environment variable. If the variable is set,
|
||||
* and refers to a legitimate Android SDK, then use that and save the pref.
|
||||
*
|
||||
* Check for a previously set android.sdk.path preference. If the pref
|
||||
* is set, and refers to a legitimate Android SDK, then use that.
|
||||
*
|
||||
* Prompt the user to select an Android SDK. If the user selects a
|
||||
* legitimate Android SDK, then use that, and save the preference.
|
||||
*
|
||||
* @return an AndroidSDK
|
||||
* @throws BadSDKException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static AndroidSDK load() throws BadSDKException, IOException {
|
||||
final Platform platform = Base.getPlatform();
|
||||
|
||||
// The environment variable is king. The preferences.txt entry is a page.
|
||||
final String sdkEnvPath = platform.getenv("ANDROID_SDK");
|
||||
if (sdkEnvPath != null) {
|
||||
try {
|
||||
final AndroidSDK androidSDK = new AndroidSDK(new File(sdkEnvPath));
|
||||
// Set this value in preferences.txt, in case ANDROID_SDK
|
||||
// gets knocked out later. For instance, by that pesky Eclipse,
|
||||
// which nukes all env variables when launching from the IDE.
|
||||
Preferences.set("android.sdk.path", sdkEnvPath);
|
||||
return androidSDK;
|
||||
} catch (final BadSDKException drop) { }
|
||||
}
|
||||
|
||||
// If android.sdk.path exists as a preference, make sure that the folder
|
||||
// is not bogus, otherwise the SDK may have been removed or deleted.
|
||||
final String sdkPrefsPath = Preferences.get("android.sdk.path");
|
||||
if (sdkPrefsPath != null) {
|
||||
try {
|
||||
final AndroidSDK androidSDK = new AndroidSDK(new File(sdkPrefsPath));
|
||||
// Set this value in preferences.txt, in case ANDROID_SDK
|
||||
// gets knocked out later. For instance, by that pesky Eclipse,
|
||||
// which nukes all env variables when launching from the IDE.
|
||||
Preferences.set("android.sdk.path", sdkPrefsPath);
|
||||
return androidSDK;
|
||||
} catch (final BadSDKException wellThatsThat) {
|
||||
Preferences.unset("android.sdk.path");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
static public AndroidSDK locate(final Frame window)
|
||||
throws BadSDKException, IOException {
|
||||
final int result = Base.showYesNoQuestion(window, "Android SDK",
|
||||
ANDROID_SDK_PRIMARY, ANDROID_SDK_SECONDARY);
|
||||
if (result == JOptionPane.CANCEL_OPTION) {
|
||||
throw new BadSDKException("User canceled attempt to find SDK.");
|
||||
}
|
||||
if (result == JOptionPane.NO_OPTION) {
|
||||
// user admitted they don't have the SDK installed, and need help.
|
||||
Base.openURL(ANDROID_SDK_URL);
|
||||
throw new BadSDKException("No SDK installed.");
|
||||
}
|
||||
while (true) {
|
||||
// TODO this is really a yucky way to do this stuff. fix it.
|
||||
File folder = selectFolder(SELECT_ANDROID_SDK_FOLDER, null, window);
|
||||
if (folder == null) {
|
||||
throw new BadSDKException("User canceled attempt to find SDK.");
|
||||
}
|
||||
try {
|
||||
final AndroidSDK androidSDK = new AndroidSDK(folder);
|
||||
Preferences.set("android.sdk.path", folder.getAbsolutePath());
|
||||
return androidSDK;
|
||||
|
||||
} catch (final BadSDKException nope) {
|
||||
JOptionPane.showMessageDialog(window, NOT_ANDROID_SDK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// this was banished from Base because it encourages bad practice.
|
||||
// TODO figure out a better way to handle the above.
|
||||
static public File selectFolder(String prompt, File folder, Frame frame) {
|
||||
if (Base.isMacOS()) {
|
||||
if (frame == null) frame = new Frame(); //.pack();
|
||||
FileDialog fd = new FileDialog(frame, prompt, FileDialog.LOAD);
|
||||
if (folder != null) {
|
||||
fd.setDirectory(folder.getParent());
|
||||
//fd.setFile(folder.getName());
|
||||
}
|
||||
System.setProperty("apple.awt.fileDialogForDirectories", "true");
|
||||
fd.setVisible(true);
|
||||
System.setProperty("apple.awt.fileDialogForDirectories", "false");
|
||||
if (fd.getFile() == null) {
|
||||
return null;
|
||||
}
|
||||
return new File(fd.getDirectory(), fd.getFile());
|
||||
|
||||
} else {
|
||||
JFileChooser fc = new JFileChooser();
|
||||
fc.setDialogTitle(prompt);
|
||||
if (folder != null) {
|
||||
fc.setSelectedFile(folder);
|
||||
}
|
||||
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
|
||||
|
||||
int returned = fc.showOpenDialog(frame);
|
||||
if (returned == JFileChooser.APPROVE_OPTION) {
|
||||
return fc.getSelectedFile();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
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].equals("adb")) {
|
||||
adbCmd = PApplet.splice(cmd, "adb", 0);
|
||||
} else {
|
||||
adbCmd = cmd;
|
||||
}
|
||||
// printing this here to see if anyone else is killing the adb server
|
||||
if (processing.app.Base.DEBUG) {
|
||||
PApplet.println(adbCmd);
|
||||
}
|
||||
// try {
|
||||
ProcessResult adbResult = new ProcessHelper(adbCmd).execute();
|
||||
// Ignore messages about starting up an adb daemon
|
||||
String out = adbResult.getStdout();
|
||||
if (out.contains(ADB_DAEMON_MSG_1) && out.contains(ADB_DAEMON_MSG_2)) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (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;
|
||||
// } catch (IOException ioe) {
|
||||
// ioe.printStackTrace();
|
||||
// throw ioe;
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2011-12 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.awt.Image;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import processing.app.Base;
|
||||
import processing.app.Editor;
|
||||
import processing.app.EditorToolbar;
|
||||
|
||||
|
||||
public class AndroidToolbar extends EditorToolbar {
|
||||
static protected final int RUN = 0;
|
||||
static protected final int STOP = 1;
|
||||
|
||||
static protected final int NEW = 2;
|
||||
static protected final int OPEN = 3;
|
||||
static protected final int SAVE = 4;
|
||||
static protected final int EXPORT = 5;
|
||||
|
||||
|
||||
public AndroidToolbar(Editor editor, Base base) {
|
||||
super(editor, base);
|
||||
}
|
||||
|
||||
|
||||
public void init() {
|
||||
Image[][] images = loadImages();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
addButton(getTitle(i, false), getTitle(i, true), images[i], i == NEW);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static public String getTitle(int index, boolean shift) {
|
||||
switch (index) {
|
||||
case RUN: return !shift ? "Run on Device" : "Run in Emulator";
|
||||
case STOP: return "Stop";
|
||||
case NEW: return "New";
|
||||
case OPEN: return "Open";
|
||||
case SAVE: return "Save";
|
||||
case EXPORT: return !shift ? "Export Signed Package" : "Export Android Project";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public void handlePressed(MouseEvent e, int sel) {
|
||||
boolean shift = e.isShiftDown();
|
||||
AndroidEditor aeditor = (AndroidEditor) editor;
|
||||
|
||||
switch (sel) {
|
||||
case RUN:
|
||||
if (!shift) {
|
||||
aeditor.handleRunDevice();
|
||||
} else {
|
||||
aeditor.handleRunEmulator();
|
||||
}
|
||||
break;
|
||||
|
||||
case STOP:
|
||||
aeditor.handleStop();
|
||||
break;
|
||||
|
||||
case OPEN:
|
||||
// TODO I think we need a longer chain of accessors here.
|
||||
JPopupMenu popup = editor.getMode().getToolbarMenu().getPopupMenu();
|
||||
popup.show(this, e.getX(), e.getY());
|
||||
break;
|
||||
|
||||
case NEW:
|
||||
// if (shift) {
|
||||
base.handleNew();
|
||||
// } else {
|
||||
// base.handleNewReplace();
|
||||
// }
|
||||
break;
|
||||
|
||||
case SAVE:
|
||||
aeditor.handleSave(false);
|
||||
break;
|
||||
|
||||
case EXPORT:
|
||||
if (!shift) {
|
||||
aeditor.handleExportPackage();
|
||||
} else {
|
||||
aeditor.handleExportProject();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package processing.mode.android;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class BadSDKException extends Exception {
|
||||
public BadSDKException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
package processing.mode.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;
|
||||
|
||||
import processing.app.Base;
|
||||
import processing.app.RunnerListener;
|
||||
import processing.app.exec.LineProcessor;
|
||||
import processing.app.exec.ProcessRegistry;
|
||||
import processing.app.exec.ProcessResult;
|
||||
import processing.app.exec.StreamPump;
|
||||
import processing.core.PApplet;
|
||||
import processing.mode.android.LogEntry.Severity;
|
||||
|
||||
|
||||
class Device {
|
||||
private final Devices env;
|
||||
private final String id;
|
||||
private final Set<Integer> activeProcesses = new HashSet<Integer>();
|
||||
private final Set<DeviceListener> listeners =
|
||||
Collections.synchronizedSet(new HashSet<DeviceListener>());
|
||||
|
||||
// public static final String APP_STARTED = "android.device.app.started";
|
||||
// public static final String APP_ENDED = "android.device.app.ended";
|
||||
|
||||
// mutable state
|
||||
private Process logcat;
|
||||
|
||||
public Device(final Devices env, final String id) {
|
||||
this.env = env;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void bringLauncherToFront() {
|
||||
try {
|
||||
adb("shell", "am", "start",
|
||||
"-a", "android.intent.action.MAIN",
|
||||
"-c", "android.intent.category.HOME");
|
||||
} catch (final Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
// adb -s emulator-5556 install helloWorld.apk
|
||||
|
||||
// : adb -s HT91MLC00031 install bin/Brightness-debug.apk
|
||||
// 532 KB/s (190588 bytes in 0.349s)
|
||||
// pkg: /data/local/tmp/Brightness-debug.apk
|
||||
// Failure [INSTALL_FAILED_ALREADY_EXISTS]
|
||||
|
||||
// : adb -s HT91MLC00031 install -r bin/Brightness-debug.apk
|
||||
// 1151 KB/s (190588 bytes in 0.161s)
|
||||
// pkg: /data/local/tmp/Brightness-debug.apk
|
||||
// Success
|
||||
|
||||
// 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);
|
||||
if (!installResult.succeeded()) {
|
||||
status.statusError("Could not install the sketch.");
|
||||
System.err.println(installResult);
|
||||
return false;
|
||||
}
|
||||
String errorMsg = null;
|
||||
for (final String line : installResult) {
|
||||
if (line.startsWith("Failure")) {
|
||||
errorMsg = line.substring(8);
|
||||
System.err.println(line);
|
||||
}
|
||||
}
|
||||
if (errorMsg == null) {
|
||||
status.statusNotice("Done installing.");
|
||||
return true;
|
||||
}
|
||||
status.statusError("Error while installing " + errorMsg);
|
||||
} catch (final IOException e) {
|
||||
status.statusError(e);
|
||||
} catch (final InterruptedException e) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// different version that actually runs through JDI:
|
||||
// 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;
|
||||
}
|
||||
String[] cmd = {
|
||||
"shell", "am", "start",
|
||||
"-e", "debug", "true",
|
||||
"-a", "android.intent.action.MAIN",
|
||||
"-c", "android.intent.category.LAUNCHER",
|
||||
"-n", packageName + "/." + className
|
||||
};
|
||||
// PApplet.println(cmd);
|
||||
ProcessResult pr = adb(cmd);
|
||||
if (Base.DEBUG) {
|
||||
System.out.println(pr.toString());
|
||||
}
|
||||
// Sometimes this shows up on stdout, even though it returns 'success'
|
||||
// Error type 2
|
||||
// android.util.AndroidException: Can't connect to activity manager; is the system running?
|
||||
if (pr.getStdout().contains("android.util.AndroidException")) {
|
||||
System.err.println(pr.getStdout());
|
||||
return false;
|
||||
}
|
||||
return pr.succeeded();
|
||||
}
|
||||
|
||||
public boolean isEmulator() {
|
||||
return id.startsWith("emulator");
|
||||
}
|
||||
|
||||
// I/Process ( 9213): Sending signal. PID: 9213 SIG: 9
|
||||
private static final Pattern SIG = Pattern
|
||||
.compile("PID:\\s+(\\d+)\\s+SIG:\\s+(\\d+)");
|
||||
|
||||
private final List<String> stackTrace = new ArrayList<String>();
|
||||
|
||||
private class LogLineProcessor implements LineProcessor {
|
||||
public void processLine(final String line) {
|
||||
final LogEntry entry = new LogEntry(line);
|
||||
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 (entry.source.equals("Process")) {
|
||||
handleCrash(entry);
|
||||
} else if (activeProcesses.contains(entry.pid)) {
|
||||
handleConsole(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCrash(final LogEntry entry) {
|
||||
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 (activeProcesses.contains(pid)) { // only report crashes of *our* sketches, por favor
|
||||
/*
|
||||
* A crashed sketch first gets a signal 3, which causes the
|
||||
* "you've crashed" dialog to appear on the device. After
|
||||
* the user dismisses the dialog, a sig 9 is sent.
|
||||
* TODO: is it possible to forcibly dismiss the crash dialog?
|
||||
*/
|
||||
if (signal == 3) {
|
||||
endProc(pid);
|
||||
reportStackTrace(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<String> stackCopy = Collections
|
||||
.unmodifiableList(new ArrayList<String>(stackTrace));
|
||||
for (final DeviceListener listener : listeners) {
|
||||
listener.stackTrace(stackCopy);
|
||||
}
|
||||
stackTrace.clear();
|
||||
}
|
||||
|
||||
void initialize() throws IOException, InterruptedException {
|
||||
adb("logcat", "-c");
|
||||
final String[] cmd = generateAdbCommand("logcat");
|
||||
final String title = PApplet.join(cmd, ' ');
|
||||
logcat = Runtime.getRuntime().exec(cmd);
|
||||
ProcessRegistry.watch(logcat);
|
||||
new StreamPump(logcat.getInputStream(), "log: " + title).addTarget(
|
||||
new LogLineProcessor()).start();
|
||||
new StreamPump(logcat.getErrorStream(), "err: " + title).addTarget(
|
||||
System.err).start();
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
logcat.waitFor();
|
||||
// final int result = logcat.waitFor();
|
||||
// System.err.println("AndroidDevice: " + getId() + " logcat exited "
|
||||
// + (result == 0 ? "normally" : "with status " + result));
|
||||
} catch (final InterruptedException e) {
|
||||
System.err
|
||||
.println("AndroidDevice: logcat process monitor interrupted");
|
||||
} finally {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
}, "AndroidDevice: logcat process monitor").start();
|
||||
// System.err.println("Receiving log entries from " + id);
|
||||
}
|
||||
|
||||
synchronized void shutdown() {
|
||||
if (!isAlive()) {
|
||||
return;
|
||||
}
|
||||
// System.err.println(id + " is shutting down.");
|
||||
if (logcat != null) {
|
||||
logcat.destroy();
|
||||
logcat = null;
|
||||
ProcessRegistry.unwatch(logcat);
|
||||
}
|
||||
env.deviceRemoved(this);
|
||||
if (activeProcesses.size() > 0) {
|
||||
for (final DeviceListener listener : listeners) {
|
||||
listener.sketchStopped();
|
||||
}
|
||||
}
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
synchronized boolean isAlive() {
|
||||
return logcat != null;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Devices getEnv() {
|
||||
return env;
|
||||
}
|
||||
|
||||
private void startProc(final String name, final int pid) {
|
||||
// System.err.println("Process " + name + " started at pid " + pid);
|
||||
activeProcesses.add(pid);
|
||||
}
|
||||
|
||||
private void endProc(final int pid) {
|
||||
// System.err.println("Process " + pid + " stopped.");
|
||||
activeProcesses.remove(pid);
|
||||
for (final DeviceListener listener : listeners) {
|
||||
listener.sketchStopped();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
return AndroidSDK.runADB(adbCmd);
|
||||
}
|
||||
|
||||
private String[] generateAdbCommand(final String... cmd) {
|
||||
// final String[] adbCmd = new String[3 + cmd.length];
|
||||
// adbCmd[0] = "adb";
|
||||
// adbCmd[1] = "-s";
|
||||
// adbCmd[2] = getId();
|
||||
// System.arraycopy(cmd, 0, adbCmd, 3, cmd.length);
|
||||
// return adbCmd;
|
||||
return PApplet.concat(new String[] { "adb", "-s", getId() }, cmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[AndroidDevice " + getId() + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package processing.mode.android;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface DeviceListener {
|
||||
void stackTrace(final List<String> trace);
|
||||
|
||||
void sketchStopped();
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
package processing.mode.android;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
//import processing.app.EditorConsole;
|
||||
import processing.app.exec.ProcessResult;
|
||||
import processing.mode.android.EmulatorController.State;
|
||||
|
||||
/**
|
||||
* <pre> AndroidEnvironment env = AndroidEnvironment.getInstance();
|
||||
* AndroidDevice n1 = env.getHardware();
|
||||
* AndroidDevice emu = env.getEmulator();</pre>
|
||||
* @author Jonathan Feinberg <jdf@pobox.com>
|
||||
*
|
||||
*/
|
||||
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 Devices INSTANCE = new Devices();
|
||||
|
||||
public static Devices getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private final Map<String, Device> devices =
|
||||
new ConcurrentHashMap<String, Device>();
|
||||
private final ExecutorService deviceLaunchThread =
|
||||
Executors.newSingleThreadExecutor();
|
||||
|
||||
|
||||
public static void killAdbServer() {
|
||||
System.out.println("Shutting down any existing adb server...");
|
||||
System.out.flush();
|
||||
try {
|
||||
AndroidSDK.runADB("kill-server");
|
||||
} catch (final Exception e) {
|
||||
System.err.println("Devices.killAdbServer() failed.");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Devices() {
|
||||
if (processing.app.Base.DEBUG) {
|
||||
System.out.println("Starting up Devices");
|
||||
}
|
||||
// killAdbServer();
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
new Thread("processing.mode.android.Devices Shutdown") {
|
||||
public void run() {
|
||||
//System.out.println("Shutting down Devices");
|
||||
//System.out.flush();
|
||||
for (Device device : new ArrayList<Device>(devices.values())) {
|
||||
device.shutdown();
|
||||
}
|
||||
// Don't do this, it'll just make Eclipse and others freak out.
|
||||
//killAdbServer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public Future<Device> getEmulator() {
|
||||
final Callable<Device> androidFinder = new Callable<Device>() {
|
||||
public Device call() throws Exception {
|
||||
return blockingGetEmulator();
|
||||
}
|
||||
};
|
||||
final FutureTask<Device> task =
|
||||
new FutureTask<Device>(androidFinder);
|
||||
deviceLaunchThread.execute(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
|
||||
private final Device blockingGetEmulator() {
|
||||
// System.out.println("going looking for emulator");
|
||||
Device emu = find(true);
|
||||
if (emu != null) {
|
||||
// System.out.println("found emu " + emu);
|
||||
return emu;
|
||||
}
|
||||
// System.out.println("no emu found");
|
||||
|
||||
EmulatorController emuController = EmulatorController.getInstance();
|
||||
// System.out.println("checking emulator state");
|
||||
if (emuController.getState() == State.NOT_RUNNING) {
|
||||
try {
|
||||
// System.out.println("not running, gonna launch");
|
||||
emuController.launch(); // this blocks until emulator boots
|
||||
// System.out.println("not just gonna, we've done the launch");
|
||||
} catch (final IOException e) {
|
||||
System.err.println("Problem while launching emulator.");
|
||||
e.printStackTrace(System.err);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
System.out.println("Emulator is " + emuController.getState() +
|
||||
", which is not expected.");
|
||||
}
|
||||
// System.out.println("and now we're out");
|
||||
|
||||
// System.out.println("Devices.blockingGet thread is " + Thread.currentThread());
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
// 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("Error while starting the emulator. (" +
|
||||
emuController.getState() + ")");
|
||||
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("Devices: interrupted in loop.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private Device find(final boolean wantEmulator) {
|
||||
refresh();
|
||||
synchronized (devices) {
|
||||
for (final Device device : devices.values()) {
|
||||
final boolean isEmulator = device.getId().contains("emulator");
|
||||
if ((isEmulator && wantEmulator) || (!isEmulator && !wantEmulator)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the first Android hardware device known to be running, or null if there are none.
|
||||
*/
|
||||
public Future<Device> getHardware() {
|
||||
final Callable<Device> androidFinder = new Callable<Device>() {
|
||||
public Device call() throws Exception {
|
||||
return blockingGetHardware();
|
||||
}
|
||||
};
|
||||
final FutureTask<Device> task =
|
||||
new FutureTask<Device>(androidFinder);
|
||||
deviceLaunchThread.execute(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
|
||||
private final Device blockingGetHardware() {
|
||||
Device hardware = find(false);
|
||||
if (hardware != null) {
|
||||
return hardware;
|
||||
}
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (final InterruptedException e) {
|
||||
return null;
|
||||
}
|
||||
hardware = find(false);
|
||||
if (hardware != null) {
|
||||
return hardware;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private void refresh() {
|
||||
final List<String> activeDevices = list();
|
||||
for (final String deviceId : activeDevices) {
|
||||
if (!devices.containsKey(deviceId)) {
|
||||
addDevice(new Device(this, deviceId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void addDevice(final Device device) {
|
||||
// System.err.println("AndroidEnvironment: adding " + device.getId());
|
||||
try {
|
||||
device.initialize();
|
||||
if (devices.put(device.getId(), device) != null) {
|
||||
throw new IllegalStateException("Adding " + device
|
||||
+ ", which already exists!");
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
System.err.println("While initializing " + device.getId() + ": " + e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void deviceRemoved(final Device device) {
|
||||
// System.err.println("AndroidEnvironment: removing " + device.getId());
|
||||
if (devices.remove(device.getId()) == null) {
|
||||
throw new IllegalStateException("I didn't know about device "
|
||||
+ device.getId() + "!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>First line starts "List of devices"
|
||||
*
|
||||
* <p>When an emulator is started with a debug port, then it shows up
|
||||
* in the list of devices.
|
||||
*
|
||||
* <p>List of devices attached
|
||||
* <br>HT91MLC00031 device
|
||||
* <br>emulator-5554 offline
|
||||
*
|
||||
* <p>List of devices attached
|
||||
* <br>HT91MLC00031 device
|
||||
* <br>emulator-5554 device
|
||||
*
|
||||
* @return list of device identifiers
|
||||
* @throws IOException
|
||||
*/
|
||||
public static List<String> list() {
|
||||
ProcessResult result;
|
||||
try {
|
||||
// System.out.println("listing devices 00");
|
||||
result = AndroidSDK.runADB("devices");
|
||||
// System.out.println("listing devices 05");
|
||||
} catch (InterruptedException e) {
|
||||
return Collections.emptyList();
|
||||
} catch (IOException e) {
|
||||
System.err.println("Problem inside Devices.list()");
|
||||
e.printStackTrace();
|
||||
// System.err.println(e);
|
||||
// System.err.println("checking devices");
|
||||
// e.printStackTrace(EditorConsole.systemErr);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// System.out.println("listing devices 10");
|
||||
if (!result.succeeded()) {
|
||||
if (result.getStderr().contains("protocol fault (no status)")) {
|
||||
System.err.println("bleh: " + result); // THIS IS WORKING
|
||||
} else {
|
||||
System.err.println("nope: " + result);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// System.out.println("listing devices 20");
|
||||
|
||||
// might read "List of devices attached"
|
||||
final String stdout = result.getStdout();
|
||||
if (!(stdout.startsWith("List of devices") || stdout.trim().length() == 0)) {
|
||||
System.err.println(ADB_DEVICES_ERROR);
|
||||
System.err.println("Output was “" + stdout + "”");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// System.out.println("listing devices 30");
|
||||
final List<String> devices = new ArrayList<String>();
|
||||
for (final String line : result) {
|
||||
if (line.contains("\t")) {
|
||||
final String[] fields = line.split("\t");
|
||||
if (fields[1].equals("device")) {
|
||||
devices.add(fields[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
package processing.mode.android;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import processing.app.Base;
|
||||
import processing.app.Preferences;
|
||||
import processing.app.exec.*;
|
||||
|
||||
import processing.core.PApplet;
|
||||
|
||||
|
||||
class EmulatorController {
|
||||
public static enum State {
|
||||
NOT_RUNNING, WAITING_FOR_BOOT, RUNNING
|
||||
}
|
||||
|
||||
private volatile State state = State.NOT_RUNNING;
|
||||
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
private void setState(final State state) {
|
||||
if (processing.app.Base.DEBUG) {
|
||||
//System.out.println("Emulator state: " + state);
|
||||
new Exception("setState(" + state + ") called").printStackTrace(System.out);
|
||||
}
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Blocks until emulator is running, or some catastrophe happens.
|
||||
* @throws IOException
|
||||
*/
|
||||
synchronized public void launch() throws IOException {
|
||||
if (state != State.NOT_RUNNING) {
|
||||
String illegal = "You can't launch an emulator whose state is " + state;
|
||||
throw new IllegalStateException(illegal);
|
||||
}
|
||||
|
||||
String portString = Preferences.get("android.emulator.port");
|
||||
if (portString == null) {
|
||||
portString = "5566";
|
||||
Preferences.set("android.emulator.port", portString);
|
||||
}
|
||||
|
||||
// See http://developer.android.com/guide/developing/tools/emulator.html
|
||||
final String[] cmd = new String[] {
|
||||
"emulator",
|
||||
"-avd", AVD.defaultAVD.name,
|
||||
"-port", portString,
|
||||
// "-no-boot-anim", // does this do anything?
|
||||
// http://code.google.com/p/processing/issues/detail?id=1059
|
||||
// "-gpu", "on" // enable OpenGL
|
||||
};
|
||||
//System.err.println("EmulatorController: Launching emulator");
|
||||
if (Base.DEBUG) {
|
||||
System.out.println(processing.core.PApplet.join(cmd, " "));
|
||||
}
|
||||
//ProcessResult adbResult = new ProcessHelper(adbCmd).execute();
|
||||
final Process p = Runtime.getRuntime().exec(cmd);
|
||||
ProcessRegistry.watch(p);
|
||||
// new StreamPump(p.getInputStream(), "emulator: ").addTarget(System.out).start();
|
||||
|
||||
// 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 "Launched"
|
||||
setState(State.WAITING_FOR_BOOT);
|
||||
|
||||
final String title = PApplet.join(cmd, ' ');
|
||||
|
||||
// when this shows up on stdout:
|
||||
// emulator: ERROR: the cache image is used by another emulator. aborting
|
||||
// need to reset adb and try again, since it's running but adb is hosed
|
||||
StreamPump outie = new StreamPump(p.getInputStream(), "out: " + title);
|
||||
outie.addTarget(new LineProcessor() {
|
||||
public void processLine(String line) {
|
||||
if (line.contains("the cache image is used by another emulator")) {
|
||||
|
||||
} else {
|
||||
// System.out.println(line);
|
||||
System.out.println(title + ": " + line);
|
||||
}
|
||||
}
|
||||
});
|
||||
//new StreamPump(p.getInputStream(), "out: " + title).addTarget(System.out).start();
|
||||
|
||||
// suppress this warning on OS X, otherwise we're gonna get a lot of reports:
|
||||
// 2010-04-13 15:26:56.380 emulator[91699:903] Warning once: This
|
||||
// application, or a library it uses, is using NSQuickDrawView, which has
|
||||
// been deprecated. Apps should cease use of QuickDraw and move to Quartz.
|
||||
StreamPump errie = new StreamPump(p.getErrorStream(), "err: " + title);
|
||||
errie.addTarget(new LineProcessor() {
|
||||
public void processLine(String line) {
|
||||
if (line.contains("This application, or a library it uses, is using NSQuickDrawView")) {
|
||||
// i don't really care
|
||||
} else {
|
||||
// System.err.println(line);
|
||||
System.err.println(title + ": " + line);
|
||||
}
|
||||
}
|
||||
});
|
||||
//new StreamPump(p.getErrorStream(), "err: " + title).addTarget(System.err).start();
|
||||
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
//System.err.println("EmulatorController: Waiting for boot.");
|
||||
while (state == State.WAITING_FOR_BOOT) {
|
||||
if (processing.app.Base.DEBUG) {
|
||||
System.out.println("sleeping for 2 seconds " + new java.util.Date().toString());
|
||||
}
|
||||
Thread.sleep(2000);
|
||||
//System.out.println("done sleeping");
|
||||
for (final String device : Devices.list()) {
|
||||
if (device.contains("emulator")) {
|
||||
//System.err.println("EmulatorController: Emulator booted.");
|
||||
setState(State.RUNNING);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
System.err.println("EmulatorController: Emulator never booted. " + state);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Exception while waiting for emulator to boot:");
|
||||
e.printStackTrace();
|
||||
p.destroy();
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
}, "EmulatorController: Wait for emulator to boot").start();
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
final int result = p.waitFor();
|
||||
// On Windows (as of SDK tools 15), emulator.exe process will terminate
|
||||
// immediately, even though the emulator itself is launching correctly.
|
||||
// However on OS X and Linux the process will stay open.
|
||||
if (result != 0) {
|
||||
System.err.println("Emulator process exited with status " + result + ".");
|
||||
setState(State.NOT_RUNNING);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
System.err.println("Emulator was interrupted.");
|
||||
setState(State.NOT_RUNNING);
|
||||
} finally {
|
||||
p.destroy();
|
||||
ProcessRegistry.unwatch(p);
|
||||
}
|
||||
}
|
||||
}, "EmulatorController: emulator process waitFor()").start();
|
||||
try {
|
||||
latch.await();
|
||||
} catch (final InterruptedException drop) {
|
||||
System.err.println("Interrupted while waiting for emulator to launch.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
|
||||
|
||||
|
||||
// whoever called them "design patterns" certainly wasn't a f*king designer.
|
||||
|
||||
public static EmulatorController getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private static final EmulatorController INSTANCE = new EmulatorController();
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package processing.mode.android;
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2009-10 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
|
||||
*/
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2010 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.awt.*;
|
||||
//import java.awt.event.*;
|
||||
//import java.util.ArrayList;
|
||||
//import java.util.HashMap;
|
||||
|
||||
import javax.swing.*;
|
||||
//import javax.swing.border.*;
|
||||
//import javax.swing.event.*;
|
||||
|
||||
import processing.app.*;
|
||||
|
||||
|
||||
public class Keys extends JFrame {
|
||||
Editor editor;
|
||||
|
||||
|
||||
public Keys(Editor editor) {
|
||||
this.editor = editor;
|
||||
setVisible(true);
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package processing.mode.android;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
class LogEntry {
|
||||
public static enum Severity {
|
||||
Verbose(false), Debug(false), Info(false), Warning(true), Error(true), Fatal(
|
||||
true);
|
||||
public final boolean useErrorStream;
|
||||
|
||||
private Severity(final boolean useErrorStream) {
|
||||
this.useErrorStream = useErrorStream;
|
||||
}
|
||||
|
||||
private static Severity fromChar(final char c) {
|
||||
if (c == 'V') {
|
||||
return Verbose;
|
||||
} else if (c == 'D') {
|
||||
return Debug;
|
||||
} else if (c == 'I') {
|
||||
return Info;
|
||||
} else if (c == 'W') {
|
||||
return Warning;
|
||||
} else if (c == 'E') {
|
||||
return Error;
|
||||
} else if (c == 'F') {
|
||||
return Fatal;
|
||||
} else {
|
||||
throw new IllegalArgumentException("I don't know how to interpret '"
|
||||
+ c + "' as a log severity");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final Severity severity;
|
||||
public final String source;
|
||||
public final int pid;
|
||||
public final String message;
|
||||
|
||||
private static final Pattern PARSER = Pattern
|
||||
.compile("^([VDIWEF])/([^\\(\\s]+)\\s*\\(\\s*(\\d+)\\): (.+)$");
|
||||
|
||||
public LogEntry(final String line) {
|
||||
final Matcher m = PARSER.matcher(line);
|
||||
if (!m.matches()) {
|
||||
throw new RuntimeException("I can't understand log entry\n" + line);
|
||||
}
|
||||
this.severity = Severity.fromChar(m.group(1).charAt(0));
|
||||
this.source = m.group(2);
|
||||
this.pid = Integer.parseInt(m.group(3));
|
||||
this.message = m.group(4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return severity + "/" + source + "(" + pid + "): " + message;
|
||||
}
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
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
|
||||
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.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import processing.app.*;
|
||||
import processing.core.PApplet;
|
||||
import processing.data.XML;
|
||||
|
||||
|
||||
public class Manifest {
|
||||
static final String MANIFEST_XML = "AndroidManifest.xml";
|
||||
|
||||
static final String WORLD_OF_HURT_COMING =
|
||||
"Errors occurred while reading or writing " + MANIFEST_XML + ",\n" +
|
||||
"which means lots of things are likely to stop working properly.\n" +
|
||||
"To prevent losing any data, it's recommended that you use “Save As”\n" +
|
||||
"to save a separate copy of your sketch, and the restart Processing.";
|
||||
static final String MULTIPLE_ACTIVITIES =
|
||||
"Processing only supports a single Activity in the AndroidManifest.xml\n" +
|
||||
"file. Only the first activity entry will be updated, and you better \n" +
|
||||
"hope that's the right one, smartypants.";
|
||||
|
||||
// private Editor editor;
|
||||
private Sketch sketch;
|
||||
|
||||
// entries we care about from the manifest file
|
||||
// private String packageName;
|
||||
|
||||
/** the manifest data read from the file */
|
||||
private XML xml;
|
||||
|
||||
|
||||
// public Manifest(Editor editor) {
|
||||
// this.editor = editor;
|
||||
// this.sketch = editor.getSketch();
|
||||
// load();
|
||||
// }
|
||||
public Manifest(Sketch sketch) {
|
||||
this.sketch = sketch;
|
||||
load();
|
||||
}
|
||||
|
||||
|
||||
private String defaultPackageName() {
|
||||
// Sketch sketch = editor.getSketch();
|
||||
return AndroidBuild.basePackage + "." + sketch.getName().toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
// called by other classes who want an actual package name
|
||||
// internally, we'll figure this out ourselves whether it's filled or not
|
||||
public String getPackageName() {
|
||||
String pkg = xml.getString("package");
|
||||
return pkg.length() == 0 ? defaultPackageName() : pkg;
|
||||
}
|
||||
|
||||
|
||||
public void setPackageName(String packageName) {
|
||||
// this.packageName = packageName;
|
||||
// this is the package attribute in the root <manifest> object
|
||||
xml.setString("package", packageName);
|
||||
save();
|
||||
}
|
||||
|
||||
|
||||
//writer.println(" <uses-permission android:name=\"android.permission.INTERNET\" />");
|
||||
//writer.println(" <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />");
|
||||
static final String PERMISSION_PREFIX = "android.permission.";
|
||||
|
||||
public String[] getPermissions() {
|
||||
XML[] elements = xml.getChildren("uses-permission");
|
||||
int count = elements.length;
|
||||
String[] names = new String[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
names[i] = elements[i].getString("android:name").substring(PERMISSION_PREFIX.length());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
|
||||
public void setPermissions(String[] names) {
|
||||
// just remove all the old ones
|
||||
for (XML kid : xml.getChildren("uses-permission")) {
|
||||
xml.removeChild(kid);
|
||||
}
|
||||
// ...and add the new kids back
|
||||
for (String name : names) {
|
||||
// PNode newbie = new PNodeXML("uses-permission");
|
||||
// newbie.setString("android:name", PERMISSION_PREFIX + name);
|
||||
// xml.addChild(newbie);
|
||||
XML newbie = xml.addChild("uses-permission");
|
||||
newbie.setString("android:name", PERMISSION_PREFIX + name);
|
||||
}
|
||||
save();
|
||||
}
|
||||
|
||||
|
||||
public void setClassName(String className) {
|
||||
XML[] kids = xml.getChildren("application/activity");
|
||||
if (kids.length != 1) {
|
||||
Base.showWarning("Don't touch that", MULTIPLE_ACTIVITIES, null);
|
||||
}
|
||||
XML activity = kids[0];
|
||||
String currentName = activity.getString("android:name");
|
||||
// only update if there are changes
|
||||
if (currentName == null || !currentName.equals(className)) {
|
||||
activity.setString("android:name", "." + className);
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void writeBlankManifest(final File file) {
|
||||
final PrintWriter writer = PApplet.createWriter(file);
|
||||
writer.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
|
||||
writer.println("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" ");
|
||||
// writer.println(" package=\"" + defaultPackageName() + "\" ");
|
||||
writer.println(" package=\"\" ");
|
||||
|
||||
// Tempting to use 'preferExternal' here, but might annoy some users.
|
||||
// 'auto' at least enables it to be moved back and forth
|
||||
// http://developer.android.com/guide/appendix/install-location.html
|
||||
// writer.println(" android:installLocation=\"auto\" ");
|
||||
// Disabling this for now (0190), requires default.properties to use API 8
|
||||
|
||||
// This is just a number (like the Processing 'revision'). It should
|
||||
// increment with each release. Perhaps P5 should do this automatically
|
||||
// with each build or read/write of the manifest file?
|
||||
writer.println(" android:versionCode=\"1\" ");
|
||||
// This is the version number/name seen by users
|
||||
writer.println(" android:versionName=\"1.0\">");
|
||||
|
||||
// for now including this... we're wiring to a particular SDK version anyway...
|
||||
writer.println(" <uses-sdk android:minSdkVersion=\"" + AndroidBuild.sdkVersion + "\" />");
|
||||
// writer.println(" <uses-sdk android:minSdkVersion=\"\" />"); // insert sdk version
|
||||
// writer.println(" <application android:label=\"@string/app_name\"");
|
||||
writer.println(" <application android:label=\"\""); // insert pretty name
|
||||
writer.println(" android:icon=\"@drawable/icon\"");
|
||||
writer.println(" android:debuggable=\"true\">");
|
||||
|
||||
// turns out label is not required for the activity, so nixing it
|
||||
// writer.println(" <activity android:name=\"\""); // insert class name prefixed w/ dot
|
||||
//// writer.println(" android:label=\"@string/app_name\">"); // pretty name
|
||||
// writer.println(" android:label=\"\">");
|
||||
|
||||
// activity/android:name should be the full name (package + class name) of
|
||||
// the actual activity class. or the package can be replaced by a single
|
||||
// dot as a prefix as an easier shorthand.
|
||||
writer.println(" <activity android:name=\"\">");
|
||||
|
||||
writer.println(" <intent-filter>");
|
||||
writer.println(" <action android:name=\"android.intent.action.MAIN\" />");
|
||||
writer.println(" <category android:name=\"android.intent.category.LAUNCHER\" />");
|
||||
writer.println(" </intent-filter>");
|
||||
writer.println(" </activity>");
|
||||
writer.println(" </application>");
|
||||
writer.println("</manifest>");
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save a new version of the manifest info to the build location.
|
||||
* Also fill in any missing attributes that aren't yet set properly.
|
||||
*/
|
||||
protected void writeBuild(File file, String className,
|
||||
boolean debug) throws IOException {
|
||||
// write a copy to the build location
|
||||
save(file);
|
||||
|
||||
// load the copy from the build location and start messing with it
|
||||
XML mf = null;
|
||||
try {
|
||||
mf = new XML(file);
|
||||
|
||||
// package name, or default
|
||||
String p = mf.getString("package").trim();
|
||||
if (p.length() == 0) {
|
||||
mf.setString("package", defaultPackageName());
|
||||
}
|
||||
|
||||
// app name and label, or the class name
|
||||
XML app = mf.getChild("application");
|
||||
String label = app.getString("android:label");
|
||||
if (label.length() == 0) {
|
||||
app.setString("android:label", className);
|
||||
}
|
||||
app.setString("android:debuggable", debug ? "true" : "false");
|
||||
|
||||
XML activity = app.getChild("activity");
|
||||
// the '.' prefix is just an alias for the full package name
|
||||
// http://developer.android.com/guide/topics/manifest/activity-element.html#name
|
||||
activity.setString("android:name", "." + className); // this has to be right
|
||||
|
||||
PrintWriter writer = PApplet.createWriter(file);
|
||||
writer.print(mf.toString());
|
||||
writer.flush();
|
||||
// mf.write(writer);
|
||||
writer.close();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void load() {
|
||||
// Sketch sketch = editor.getSketch();
|
||||
// File manifestFile = new File(sketch.getFolder(), MANIFEST_XML);
|
||||
// XMLElement xml = null;
|
||||
File manifestFile = getManifestFile();
|
||||
if (manifestFile.exists()) {
|
||||
try {
|
||||
xml = new XML(manifestFile);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.err.println("Problem reading AndroidManifest.xml, creating a new version");
|
||||
|
||||
// remove the old manifest file, rename it with date stamp
|
||||
long lastModified = manifestFile.lastModified();
|
||||
String stamp = AndroidMode.getDateStamp(lastModified);
|
||||
File dest = new File(sketch.getFolder(), MANIFEST_XML + "." + stamp);
|
||||
boolean moved = manifestFile.renameTo(dest);
|
||||
if (!moved) {
|
||||
System.err.println("Could not move/rename " + manifestFile.getAbsolutePath());
|
||||
System.err.println("You'll have to move or remove it before continuing.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (xml == null) {
|
||||
writeBlankManifest(manifestFile);
|
||||
try {
|
||||
xml = new XML(manifestFile);
|
||||
} catch (FileNotFoundException e) {
|
||||
System.err.println("Could not read " + manifestFile.getAbsolutePath());
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (ParserConfigurationException e) {
|
||||
e.printStackTrace();
|
||||
} catch (SAXException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (xml == null) {
|
||||
Base.showWarning("Error handling " + MANIFEST_XML, WORLD_OF_HURT_COMING, null);
|
||||
}
|
||||
// return xml;
|
||||
}
|
||||
|
||||
|
||||
protected void save() {
|
||||
save(getManifestFile());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save to the sketch folder, so that it can be copied in later.
|
||||
*/
|
||||
protected void save(File file) {
|
||||
PrintWriter writer = PApplet.createWriter(file);
|
||||
// xml.write(writer);
|
||||
writer.print(xml.toString());
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
|
||||
private File getManifestFile() {
|
||||
return new File(sketch.getFolder(), MANIFEST_XML);
|
||||
}
|
||||
}
|
||||
@@ -1,554 +0,0 @@
|
||||
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||
|
||||
/*
|
||||
Part of the Processing project - http://processing.org
|
||||
|
||||
Copyright (c) 2010 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.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.event.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.*;
|
||||
import javax.swing.event.*;
|
||||
|
||||
import processing.app.Base;
|
||||
import processing.app.Preferences;
|
||||
import processing.app.Sketch;
|
||||
import processing.app.Toolkit;
|
||||
|
||||
|
||||
public class Permissions extends JFrame {
|
||||
static final String GUIDE_URL =
|
||||
"http://developer.android.com/guide/topics/security/security.html#permissions";
|
||||
|
||||
static final int BORDER_HORIZ = 5;
|
||||
static final int BORDER_VERT = 3;
|
||||
|
||||
JScrollPane permissionScroller;
|
||||
JList permissionList;
|
||||
JLabel descriptionLabel;
|
||||
// JTextArea descriptionLabel;
|
||||
|
||||
// Editor editor;
|
||||
Sketch sketch;
|
||||
|
||||
|
||||
public Permissions(Sketch sketch) {
|
||||
//public Permissions(Editor editor) {
|
||||
super("Android Permissions Selector");
|
||||
this.sketch = sketch;
|
||||
// this.editor = editor;
|
||||
|
||||
// XMLElement xml =
|
||||
|
||||
permissionList = new CheckBoxList();
|
||||
// permissionList.addMouseListener(new MouseAdapter() {
|
||||
// public void mousePressed(MouseEvent e) {
|
||||
// if (isEnabled()) {
|
||||
// int index = permissionList.locationToIndex(e.getPoint());
|
||||
// if (index == -1) {
|
||||
// descriptionLabel.setText("");
|
||||
// } else {
|
||||
//// descriptionLabel.setText("<html>" + description[index] + "</html>");
|
||||
// descriptionLabel.setText(description[index]);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// ListSelectionModel lsm = permissionList.getSelectionModel();
|
||||
// lsm.addListSelectionListener(new ListSelectionListener() {
|
||||
// public void valueChanged(ListSelectionEvent e) {
|
||||
//// ListSelectionModel lsm = (ListSelectionModel) e.getSource();
|
||||
// int index = e.getFirstIndex();
|
||||
// if (index == -1) {
|
||||
// descriptionLabel.setText("");
|
||||
// } else {
|
||||
// descriptionLabel.setText("<html>" + description[index] + "</html>");
|
||||
//// descriptionLabel.setText(description[index]);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
permissionList.addListSelectionListener(new ListSelectionListener() {
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
if (e.getValueIsAdjusting() == false) {
|
||||
int index = permissionList.getSelectedIndex();
|
||||
if (index == -1) {
|
||||
descriptionLabel.setText("");
|
||||
} else {
|
||||
descriptionLabel.setText("<html>" + description[index] + "</html>");
|
||||
//descriptionLabel.setText(description[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// permissionList.setLayoutOrientation(JList.HORIZONTAL_WRAP);
|
||||
// permissionList.setFixedCellWidth(300);
|
||||
// int h = permissionList.getFixedCellHeight();
|
||||
// permissionList.setFixedCellHeight(h + 8);
|
||||
permissionList.setFixedCellHeight(20);
|
||||
permissionList.setBorder(new EmptyBorder(BORDER_VERT, BORDER_HORIZ,
|
||||
BORDER_VERT, BORDER_HORIZ));
|
||||
|
||||
DefaultListModel model = new DefaultListModel();
|
||||
permissionList.setModel(model);
|
||||
for (String item : title) {
|
||||
model.addElement(new JCheckBox(item));
|
||||
}
|
||||
|
||||
permissionScroller =
|
||||
new JScrollPane(permissionList,
|
||||
ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
|
||||
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
// permissionList.setVisibleRowCount(20);
|
||||
permissionList.setVisibleRowCount(12);
|
||||
// permissionList.setPreferredSize(new Dimension(400, 300));
|
||||
// permissionsScroller.setPreferredSize(new Dimension(400, 300));
|
||||
permissionList.addKeyListener(new KeyAdapter() {
|
||||
public void keyTyped(KeyEvent e) {
|
||||
if (e.getKeyChar() == ' ') {
|
||||
int index = permissionList.getSelectedIndex();
|
||||
JCheckBox checkbox =
|
||||
(JCheckBox) permissionList.getModel().getElementAt(index);
|
||||
checkbox.setSelected(!checkbox.isSelected());
|
||||
permissionList.repaint();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Container outer = getContentPane();
|
||||
// outer.setLayout(new BorderLayout());
|
||||
|
||||
// JPanel pain = new JPanel();
|
||||
Box pain = Box.createVerticalBox();
|
||||
pain.setBorder(new EmptyBorder(13, 13, 13, 13));
|
||||
// outer.add(pain, BorderLayout.CENTER);
|
||||
outer.add(pain);
|
||||
// pain.setLayout(new BoxLayout(pain, BoxLayout.Y_AXIS));
|
||||
|
||||
String labelText =
|
||||
"<html>" +
|
||||
"Android applications must specifically ask for permission\n" +
|
||||
"to do things like connect to the internet, write a file,\n" +
|
||||
"or make phone calls. When installing your application,\n" +
|
||||
"users will be asked whether they want to allow such access.\n" +
|
||||
"More about permissions can be found " +
|
||||
"<a href=\"" + GUIDE_URL + "\">here</a>.</body></html>";
|
||||
// "<html>" +
|
||||
// "Android applications must specifically ask for permission\n" +
|
||||
// "to do things like connect to the internet, write a file,\n" +
|
||||
// "or make phone calls. When installing your application,\n" +
|
||||
// "users will be asked whether they want to allow such access.\n" +
|
||||
// "More about permissions can be found " +
|
||||
// "<a href=\"" + GUIDE_URL + "\">here</a>.</body></html>";
|
||||
// JTextArea textarea = new JTextArea(labelText);
|
||||
// JTextArea textarea = new JTextArea(5, 40);
|
||||
// textarea.setText(labelText);
|
||||
JLabel textarea = new JLabel(labelText);
|
||||
// JLabel textarea = new JLabel(labelText) {
|
||||
// public Dimension getPreferredSize() {
|
||||
// return new Dimension(400, 100);
|
||||
// }
|
||||
// public Dimension getMinimumSize() {
|
||||
// return getPreferredSize();
|
||||
// }
|
||||
// public Dimension getMaximumSize() {
|
||||
// return getPreferredSize();
|
||||
// }
|
||||
// };
|
||||
textarea.setPreferredSize(new Dimension(400, 100));
|
||||
textarea.addMouseListener(new MouseAdapter() {
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
Base.openURL(GUIDE_URL);
|
||||
}
|
||||
});
|
||||
//textarea.setHorizontalAlignment(SwingConstants.LEFT);
|
||||
textarea.setAlignmentX(LEFT_ALIGNMENT);
|
||||
|
||||
// textarea.setBorder(new EmptyBorder(13, 8, 13, 8));
|
||||
|
||||
// textarea.setBackground(null);
|
||||
// textarea.setBackground(Color.RED);
|
||||
// textarea.setEditable(false);
|
||||
// textarea.setHighlighter(null);
|
||||
// textarea.setFont(new Font("Dialog", Font.PLAIN, 12));
|
||||
pain.add(textarea);
|
||||
// textarea.setForeground(Color.RED);
|
||||
// pain.setBackground(Color.GREEN);
|
||||
|
||||
// permissionList.setEnabled(false);
|
||||
|
||||
permissionScroller.setAlignmentX(LEFT_ALIGNMENT);
|
||||
pain.add(permissionScroller);
|
||||
// pain.add(permissionList);
|
||||
pain.add(Box.createVerticalStrut(8));
|
||||
|
||||
// descriptionLabel = new JTextArea(4, 10);
|
||||
descriptionLabel = new JLabel();
|
||||
// descriptionLabel = new JLabel() {
|
||||
// public Dimension getPreferredSize() {
|
||||
// return new Dimension(400, 100);
|
||||
// }
|
||||
// public Dimension getMinimumSize() {
|
||||
// return new Dimension(400, 100);
|
||||
// }
|
||||
// public Dimension getMaximumSize() {
|
||||
// return new Dimension(400, 100);
|
||||
// }
|
||||
// };
|
||||
descriptionLabel.setPreferredSize(new Dimension(400, 50));
|
||||
descriptionLabel.setVerticalAlignment(SwingConstants.TOP);
|
||||
descriptionLabel.setAlignmentX(LEFT_ALIGNMENT);
|
||||
pain.add(descriptionLabel);
|
||||
pain.add(Box.createVerticalStrut(8));
|
||||
|
||||
JPanel buttons = new JPanel();
|
||||
// buttons.setPreferredSize(new Dimension(400, 35));
|
||||
// JPanel buttons = new JPanel() {
|
||||
// public Dimension getPreferredSize() {
|
||||
// return new Dimension(400, 35);
|
||||
// }
|
||||
// public Dimension getMinimumSize() {
|
||||
// return new Dimension(400, 35);
|
||||
// }
|
||||
// public Dimension getMaximumSize() {
|
||||
// return new Dimension(400, 35);
|
||||
// }
|
||||
// };
|
||||
|
||||
// Box buttons = Box.createHorizontalBox();
|
||||
buttons.setAlignmentX(LEFT_ALIGNMENT);
|
||||
JButton okButton = new JButton("OK");
|
||||
Dimension dim = new Dimension(Preferences.BUTTON_WIDTH,
|
||||
okButton.getPreferredSize().height);
|
||||
okButton.setPreferredSize(dim);
|
||||
okButton.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
//PApplet.println(getSelections());
|
||||
saveSelections();
|
||||
setVisible(false);
|
||||
}
|
||||
});
|
||||
okButton.setEnabled(true);
|
||||
|
||||
JButton cancelButton = new JButton("Cancel");
|
||||
cancelButton.setPreferredSize(dim);
|
||||
cancelButton.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
setVisible(false);
|
||||
}
|
||||
});
|
||||
cancelButton.setEnabled(true);
|
||||
|
||||
// think different, biznatchios!
|
||||
if (Base.isMacOS()) {
|
||||
buttons.add(cancelButton);
|
||||
// buttons.add(Box.createHorizontalStrut(8));
|
||||
buttons.add(okButton);
|
||||
} else {
|
||||
buttons.add(okButton);
|
||||
// buttons.add(Box.createHorizontalStrut(8));
|
||||
buttons.add(cancelButton);
|
||||
}
|
||||
// buttons.setMaximumSize(new Dimension(300, buttons.getPreferredSize().height));
|
||||
pain.add(buttons);
|
||||
|
||||
JRootPane root = getRootPane();
|
||||
root.setDefaultButton(okButton);
|
||||
ActionListener disposer = new ActionListener() {
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
setVisible(false);
|
||||
}
|
||||
};
|
||||
Toolkit.registerWindowCloseKeys(root, disposer);
|
||||
Toolkit.setIcon(this);
|
||||
|
||||
pack();
|
||||
|
||||
Dimension screen = Toolkit.getScreenSize();
|
||||
Dimension windowSize = getSize();
|
||||
|
||||
setLocation((screen.width - windowSize.width) / 2,
|
||||
(screen.height - windowSize.height) / 2);
|
||||
|
||||
Manifest mf = new Manifest(sketch);
|
||||
setSelections(mf.getPermissions());
|
||||
|
||||
// show the window and get to work
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
|
||||
protected void setSelections(String[] sel) {
|
||||
// processing.core.PApplet.println("permissions are:");
|
||||
// processing.core.PApplet.println(sel);
|
||||
HashMap<String,Object> map = new HashMap<String, Object>();
|
||||
for (String s : sel) {
|
||||
map.put(s, new Object());
|
||||
}
|
||||
DefaultListModel model = (DefaultListModel) permissionList.getModel();
|
||||
for (int i = 0; i < count; i++) {
|
||||
JCheckBox box = (JCheckBox) model.get(i);
|
||||
// System.out.println(map.containsKey(box.getText()) + " " + box.getText());
|
||||
box.setSelected(map.containsKey(box.getText()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected String[] getSelections() {
|
||||
ArrayList<String> sel = new ArrayList<String>();
|
||||
DefaultListModel model = (DefaultListModel) permissionList.getModel();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (((JCheckBox) model.get(i)).isSelected()) {
|
||||
sel.add(title[i]);
|
||||
}
|
||||
}
|
||||
return sel.toArray(new String[0]);
|
||||
}
|
||||
|
||||
|
||||
protected void saveSelections() {
|
||||
String[] sel = getSelections();
|
||||
Manifest mf = new Manifest(sketch);
|
||||
mf.setPermissions(sel);
|
||||
}
|
||||
|
||||
|
||||
public String getMenuTitle() {
|
||||
return "Android Permissions";
|
||||
}
|
||||
|
||||
|
||||
// public void init(Editor editor) {
|
||||
// this.editor = editor;
|
||||
// }
|
||||
|
||||
|
||||
// public void run() {
|
||||
// // parse the manifest file here and figure out what permissions are set
|
||||
// Manifest mf = new Manifest(editor);
|
||||
// setSelections(mf.getPermissions());
|
||||
//
|
||||
// // show the window and get to work
|
||||
// setVisible(true);
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* Created by inserting the HTML doc into OpenOffice, then copy and pasting
|
||||
* the table into a plain text document, then adding the quotes via search
|
||||
* and replace. If there's a way to auto-create from aapt, that'd be better,
|
||||
* but I haven't found anything yet.
|
||||
*/
|
||||
static final String[] listing = {
|
||||
"ACCESS_CHECKIN_PROPERTIES", "Allows read/write access to the \"properties\" table in the checkin database, to change values that get uploaded.",
|
||||
"ACCESS_COARSE_LOCATION", "Allows an application to access coarse (e.g., Cell-ID, WiFi) location",
|
||||
"ACCESS_FINE_LOCATION", "Allows an application to access fine (e.g., GPS) location",
|
||||
"ACCESS_LOCATION_EXTRA_COMMANDS", "Allows an application to access extra location provider commands",
|
||||
"ACCESS_MOCK_LOCATION", "Allows an application to create mock location providers for testing",
|
||||
"ACCESS_NETWORK_STATE", "Allows applications to access information about networks",
|
||||
"ACCESS_SURFACE_FLINGER", "Allows an application to use SurfaceFlinger's low level features",
|
||||
"ACCESS_WIFI_STATE", "Allows applications to access information about Wi-Fi networks",
|
||||
"ACCOUNT_MANAGER", "Allows applications to call into AccountAuthenticators.",
|
||||
"AUTHENTICATE_ACCOUNTS", "Allows an application to act as an AccountAuthenticator for the AccountManager",
|
||||
"BATTERY_STATS", "Allows an application to collect battery statistics",
|
||||
"BIND_APPWIDGET", "Allows an application to tell the AppWidget service which application can access AppWidget's data.",
|
||||
"BIND_DEVICE_ADMIN", "Must be required by device administration receiver, to ensure that only the system can interact with it.",
|
||||
"BIND_INPUT_METHOD", "Must be required by an InputMethodService, to ensure that only the system can bind to it.",
|
||||
"BIND_WALLPAPER", "Must be required by a WallpaperService, to ensure that only the system can bind to it.",
|
||||
"BLUETOOTH", "Allows applications to connect to paired bluetooth devices",
|
||||
"BLUETOOTH_ADMIN", "Allows applications to discover and pair bluetooth devices",
|
||||
"BRICK", "Required to be able to disable the device (very dangerous!).",
|
||||
"BROADCAST_PACKAGE_REMOVED", "Allows an application to broadcast a notification that an application package has been removed.",
|
||||
"BROADCAST_SMS", "Allows an application to broadcast an SMS receipt notification",
|
||||
"BROADCAST_STICKY", "Allows an application to broadcast sticky intents.",
|
||||
"BROADCAST_WAP_PUSH", "Allows an application to broadcast a WAP PUSH receipt notification",
|
||||
"CALL_PHONE", "Allows an application to initiate a phone call without going through the Dialer user interface for the user to confirm the call being placed.",
|
||||
"CALL_PRIVILEGED", "Allows an application to call any phone number, including emergency numbers, without going through the Dialer user interface for the user to confirm the call being placed.",
|
||||
"CAMERA", "Required to be able to access the camera device.",
|
||||
"CHANGE_COMPONENT_ENABLED_STATE", "Allows an application to change whether an application component (other than its own) is enabled or not.",
|
||||
"CHANGE_CONFIGURATION", "Allows an application to modify the current configuration, such as locale.",
|
||||
"CHANGE_NETWORK_STATE", "Allows applications to change network connectivity state",
|
||||
"CHANGE_WIFI_MULTICAST_STATE", "Allows applications to enter Wi-Fi Multicast mode",
|
||||
"CHANGE_WIFI_STATE", "Allows applications to change Wi-Fi connectivity state",
|
||||
"CLEAR_APP_CACHE", "Allows an application to clear the caches of all installed applications on the device.",
|
||||
"CLEAR_APP_USER_DATA", "Allows an application to clear user data",
|
||||
"CONTROL_LOCATION_UPDATES", "Allows enabling/disabling location update notifications from the radio.",
|
||||
"DELETE_CACHE_FILES", "Allows an application to delete cache files.",
|
||||
"DELETE_PACKAGES", "Allows an application to delete packages.",
|
||||
"DEVICE_POWER", "Allows low-level access to power management",
|
||||
"DIAGNOSTIC", "Allows applications to RW to diagnostic resources.",
|
||||
"DISABLE_KEYGUARD", "Allows applications to disable the keyguard",
|
||||
"DUMP", "Allows an application to retrieve state dump information from system services.",
|
||||
"EXPAND_STATUS_BAR", "Allows an application to expand or collapse the status bar.",
|
||||
"FACTORY_TEST", "Run as a manufacturer test application, running as the root user.",
|
||||
"FLASHLIGHT", "Allows access to the flashlight",
|
||||
"FORCE_BACK", "Allows an application to force a BACK operation on whatever is the top activity.",
|
||||
"GET_ACCOUNTS", "Allows access to the list of accounts in the Accounts Service",
|
||||
"GET_PACKAGE_SIZE", "Allows an application to find out the space used by any package.",
|
||||
"GET_TASKS", "Allows an application to get information about the currently or recently running tasks: a thumbnail representation of the tasks, what activities are running in it, etc.",
|
||||
"GLOBAL_SEARCH", "This permission can be used on content providers to allow the global search system to access their data.",
|
||||
"HARDWARE_TEST", "Allows access to hardware peripherals.",
|
||||
"INJECT_EVENTS", "Allows an application to inject user events (keys, touch, trackball) into the event stream and deliver them to ANY window.",
|
||||
"INSTALL_LOCATION_PROVIDER", "Allows an application to install a location provider into the Location Manager",
|
||||
"INSTALL_PACKAGES", "Allows an application to install packages.",
|
||||
"INTERNAL_SYSTEM_WINDOW", "Allows an application to open windows that are for use by parts of the system user interface.",
|
||||
"INTERNET", "Allows applications to open network sockets.",
|
||||
"KILL_BACKGROUND_PROCESSES", "Allows an application to call killBackgroundProcesses(String).",
|
||||
"MANAGE_ACCOUNTS", "Allows an application to manage the list of accounts in the AccountManager",
|
||||
"MANAGE_APP_TOKENS", "Allows an application to manage (create, destroy, Z-order) application tokens in the window manager.",
|
||||
"MASTER_CLEAR", "",
|
||||
"MODIFY_AUDIO_SETTINGS", "Allows an application to modify global audio settings",
|
||||
"MODIFY_PHONE_STATE", "Allows modification of the telephony state - power on, mmi, etc.",
|
||||
"MOUNT_FORMAT_FILESYSTEMS", "Allows formatting file systems for removable storage.",
|
||||
"MOUNT_UNMOUNT_FILESYSTEMS", "Allows mounting and unmounting file systems for removable storage.",
|
||||
"PERSISTENT_ACTIVITY", "Allow an application to make its activities persistent.",
|
||||
"PROCESS_OUTGOING_CALLS", "Allows an application to monitor, modify, or abort outgoing calls.",
|
||||
"READ_CALENDAR", "Allows an application to read the user's calendar data.",
|
||||
"READ_CONTACTS", "Allows an application to read the user's contacts data.",
|
||||
"READ_FRAME_BUFFER", "Allows an application to take screen shots and more generally get access to the frame buffer data",
|
||||
"READ_HISTORY_BOOKMARKS", "Allows an application to read (but not write) the user's browsing history and bookmarks.",
|
||||
"READ_INPUT_STATE", "Allows an application to retrieve the current state of keys and switches.",
|
||||
"READ_LOGS", "Allows an application to read the low-level system log files.",
|
||||
"READ_OWNER_DATA", "Allows an application to read the owner's data.",
|
||||
"READ_PHONE_STATE", "Allows read only access to phone state.",
|
||||
"READ_SMS", "Allows an application to read SMS messages.",
|
||||
"READ_SYNC_SETTINGS", "Allows applications to read the sync settings",
|
||||
"READ_SYNC_STATS", "Allows applications to read the sync stats",
|
||||
"REBOOT", "Required to be able to reboot the device.",
|
||||
"RECEIVE_BOOT_COMPLETED", "Allows an application to receive the ACTION_BOOT_COMPLETED that is broadcast after the system finishes booting.",
|
||||
"RECEIVE_MMS", "Allows an application to monitor incoming MMS messages, to record or perform processing on them.",
|
||||
"RECEIVE_SMS", "Allows an application to monitor incoming SMS messages, to record or perform processing on them.",
|
||||
"RECEIVE_WAP_PUSH", "Allows an application to monitor incoming WAP push messages.",
|
||||
"RECORD_AUDIO", "Allows an application to record audio",
|
||||
"REORDER_TASKS", "Allows an application to change the Z-order of tasks",
|
||||
"RESTART_PACKAGES", "This constant is deprecated. The restartPackage(String) API is no longer supported. ",
|
||||
"SEND_SMS", "Allows an application to send SMS messages.",
|
||||
"SET_ACTIVITY_WATCHER", "Allows an application to watch and control how activities are started globally in the system.",
|
||||
"SET_ALWAYS_FINISH", "Allows an application to control whether activities are immediately finished when put in the background.",
|
||||
"SET_ANIMATION_SCALE", "Modify the global animation scaling factor.",
|
||||
"SET_DEBUG_APP", "Configure an application for debugging.",
|
||||
"SET_ORIENTATION", "Allows low-level access to setting the orientation (actually rotation) of the screen.",
|
||||
"SET_PREFERRED_APPLICATIONS", "This constant is deprecated. No longer useful, see addPackageToPreferred(String) for details. ",
|
||||
"SET_PROCESS_LIMIT", "Allows an application to set the maximum number of (not needed) application processes that can be running.",
|
||||
"SET_TIME", "Allows applications to set the system time",
|
||||
"SET_TIME_ZONE", "Allows applications to set the system time zone",
|
||||
"SET_WALLPAPER", "Allows applications to set the wallpaper",
|
||||
"SET_WALLPAPER_HINTS", "Allows applications to set the wallpaper hints",
|
||||
"SIGNAL_PERSISTENT_PROCESSES", "Allow an application to request that a signal be sent to all persistent processes",
|
||||
"STATUS_BAR", "Allows an application to open, close, or disable the status bar and its icons.",
|
||||
"SUBSCRIBED_FEEDS_READ", "Allows an application to allow access the subscribed feeds ContentProvider.",
|
||||
"SUBSCRIBED_FEEDS_WRITE", "",
|
||||
"SYSTEM_ALERT_WINDOW", "Allows an application to open windows using the type TYPE_SYSTEM_ALERT, shown on top of all other applications.",
|
||||
"UPDATE_DEVICE_STATS", "Allows an application to update device statistics.",
|
||||
"USE_CREDENTIALS", "Allows an application to request authtokens from the AccountManager",
|
||||
"VIBRATE", "Allows access to the vibrator",
|
||||
"WAKE_LOCK", "Allows using PowerManager WakeLocks to keep processor from sleeping or screen from dimming",
|
||||
"WRITE_APN_SETTINGS", "Allows applications to write the apn settings",
|
||||
"WRITE_CALENDAR", "Allows an application to write (but not read) the user's calendar data.",
|
||||
"WRITE_CONTACTS", "Allows an application to write (but not read) the user's contacts data.",
|
||||
"WRITE_EXTERNAL_STORAGE", "Allows an application to write to external storage",
|
||||
"WRITE_GSERVICES", "Allows an application to modify the Google service map.",
|
||||
"WRITE_HISTORY_BOOKMARKS", "Allows an application to write (but not read) the user's browsing history and bookmarks.",
|
||||
"WRITE_OWNER_DATA", "Allows an application to write (but not read) the owner's data.",
|
||||
"WRITE_SECURE_SETTINGS", "Allows an application to read or write the secure system settings.",
|
||||
"WRITE_SETTINGS", "Allows an application to read or write the system settings.",
|
||||
"WRITE_SMS", "Allows an application to write SMS messages.",
|
||||
"WRITE_SYNC_SETTINGS", "Allows applications to write the sync settings"
|
||||
};
|
||||
|
||||
static String[] title;
|
||||
static String[] description;
|
||||
static int count;
|
||||
static {
|
||||
count = listing.length / 2;
|
||||
title = new String[count];
|
||||
description = new String[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
title[i] = listing[i*2];
|
||||
description[i] = listing[i*2+1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Code for this CheckBoxList class found on the net, though I've lost the
|
||||
// link. If you run across the original version, please let me know so that
|
||||
// the original author can be credited properly. It was from a snippet
|
||||
// collection, but it seems to have been picked up so many places with others
|
||||
// placing their copyright on it, that I haven't been able to determine the
|
||||
// original author. [fry 20100216]
|
||||
class CheckBoxList extends JList {
|
||||
protected static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);
|
||||
int checkboxWidth;
|
||||
|
||||
public CheckBoxList() {
|
||||
setCellRenderer(new CellRenderer());
|
||||
|
||||
// get the width of a checkbox so we can figure out if the mouse is inside
|
||||
checkboxWidth = new JCheckBox().getPreferredSize().width;
|
||||
// add the amount for the inset
|
||||
checkboxWidth += Permissions.BORDER_HORIZ;
|
||||
|
||||
addMouseListener(new MouseAdapter() {
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (isEnabled()) {
|
||||
// System.out.println("cbw = " + checkboxWidth);
|
||||
int index = locationToIndex(e.getPoint());
|
||||
// descriptionLabel.setText(description[index]);
|
||||
if (index != -1) {
|
||||
JCheckBox checkbox = (JCheckBox) getModel().getElementAt(index);
|
||||
//System.out.println("mouse event in list: " + e);
|
||||
// System.out.println(checkbox.getSize() + " ... " + checkbox);
|
||||
// if (e.getX() < checkbox.getSize().height) {
|
||||
if (e.getX() < checkboxWidth) {
|
||||
checkbox.setSelected(!checkbox.isSelected());
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
}
|
||||
|
||||
|
||||
protected class CellRenderer implements ListCellRenderer {
|
||||
public Component getListCellRendererComponent(JList list, Object value,
|
||||
int index, boolean isSelected,
|
||||
boolean cellHasFocus) {
|
||||
JCheckBox checkbox = (JCheckBox) value;
|
||||
// checkbox.setBorder(new EmptyBorder(13, 5, 3, 5)); // trying again
|
||||
checkbox.setBackground(isSelected ? getSelectionBackground() : getBackground());
|
||||
checkbox.setForeground(isSelected ? getSelectionForeground() : getForeground());
|
||||
//checkbox.setEnabled(isEnabled());
|
||||
checkbox.setEnabled(list.isEnabled());
|
||||
checkbox.setFont(getFont());
|
||||
checkbox.setFocusPainted(false);
|
||||
checkbox.setBorderPainted(true);
|
||||
checkbox.setBorder(isSelected ? UIManager.getBorder("List.focusCellHighlightBorder") : noFocusBorder);
|
||||
return checkbox;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user