moving android fully to its own subdirectory

This commit is contained in:
Ben Fry
2013-04-19 11:52:45 -04:00
parent 73bebaf2a0
commit 6fbef971a0
18 changed files with 0 additions and 0 deletions
-196
View File
@@ -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);
}
}
-302
View File
@@ -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 &lt;jdf@pobox.com&gt;
*
*/
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
*/
-44
View File
@@ -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;
}
}
}