You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by de...@apache.org on 2012/02/08 21:14:02 UTC

[10/19] CB-226 Rename to Cordova.

http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/api/IPlugin.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/api/IPlugin.java b/framework/ext/src/org/apache/cordova/api/IPlugin.java
new file mode 100644
index 0000000..7e09d31
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/api/IPlugin.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.api;
+
+import org.apache.cordova.CordovaExtension;
+import org.apache.cordova.json4j.JSONArray;
+
+/**
+ * Plugin interface must be implemented by any plugin classes.
+ *
+ * The execute method is called by the PluginManager.
+ */
+public interface IPlugin {
+
+	/**
+	 * Executes the request and returns PluginResult.
+	 *
+	 * @param action 		The action to execute.
+	 * @param args 			JSONArry of arguments for the plugin.
+	 * @param callbackId	The callback id used when calling back into JavaScript.
+	 * @return 				A PluginResult object with a status and message.
+	 */
+	PluginResult execute(String action, JSONArray args, String callbackId);
+
+	/**
+	 * Identifies if action to be executed returns a value and should be run synchronously.
+	 *
+	 * @param action	The action to execute
+	 * @return			T=returns value
+	 */
+	public boolean isSynch(String action);
+
+	/**
+	 * Sets the context of the Plugin. This can then be used to do things like
+	 * get file paths associated with the Activity.
+	 *
+	 * @param ctx The main application class.
+	 */
+	void setContext(CordovaExtension ctx);
+
+    /**
+     * Called when the system is about to start resuming a previous activity.
+     */
+    void onPause();
+
+    /**
+     * Called when the activity will start interacting with the user.
+     */
+    void onResume();
+
+    /**
+     * The final call you receive before your activity is destroyed.
+     */
+    void onDestroy();
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/api/Plugin.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/api/Plugin.java b/framework/ext/src/org/apache/cordova/api/Plugin.java
new file mode 100644
index 0000000..6f70b85
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/api/Plugin.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.api;
+
+import org.apache.cordova.CordovaExtension;
+import org.apache.cordova.json4j.JSONArray;
+
+/**
+ * Plugin interface must be implemented by any plugin classes.
+ *
+ * The execute method is called by the PluginManager.
+ */
+public abstract class Plugin implements IPlugin {
+
+    public CordovaExtension ctx;					// Main application object
+
+	/**
+	 * Executes the request and returns PluginResult.
+	 *
+	 * @param action 		The action to execute.
+	 * @param args 			JSONArry of arguments for the plugin.
+	 * @param callbackId	The callback id used when calling back into JavaScript.
+	 * @return 				A PluginResult object with a status and message.
+	 */
+	public abstract PluginResult execute(String action, JSONArray args, String callbackId);
+
+	/**
+	 * Identifies if action to be executed returns a value and should be run synchronously.
+	 *
+	 * @param action	The action to execute
+	 * @return			T=returns value
+	 */
+	public boolean isSynch(String action) {
+		return false;
+	}
+
+	/**
+	 * Sets the context of the Plugin. This can then be used to do things like
+	 * get file paths associated with the Activity.
+	 *
+	 * @param ctx The context of the main Activity.
+	 */
+	public void setContext(CordovaExtension ctx) {
+		this.ctx = ctx;
+	}
+
+    /**
+     * Called when Plugin is paused.
+     */
+    public void onPause() {
+    }
+
+    /**
+     * Called when Plugin is resumed.
+     */
+    public void onResume() {
+    }
+
+    /**
+     * Called when Plugin is destroyed.
+     */
+    public void onDestroy() {
+    }
+
+    /**
+     * Send generic JavaScript statement back to JavaScript.
+     * success(...) and error(...) should be used instead where possible.
+     *
+     * @param statement
+     */
+    public void invokeScript(String statement) {
+        CordovaExtension.invokeScript(statement);
+    }
+
+    /**
+     * Call the JavaScript success callback for this plugin.
+     *
+     * This can be used if the execute code for the plugin is asynchronous meaning
+     * that execute should return null and the callback from the async operation can
+     * call success(...) or error(...)
+     *
+     * @param pluginResult		The result to return.
+	 * @param callbackId		The callback id used when calling back into JavaScript.
+     */
+    public static void success(PluginResult pluginResult, String callbackId) {
+        CordovaExtension.invokeSuccessCallback(callbackId, pluginResult);
+    }
+
+    /**
+     * Call the JavaScript error callback for this plugin.
+     *
+     * @param pluginResult		The result to return.
+	 * @param callbackId		The callback id used when calling back into JavaScript.
+     */
+    public static void error(PluginResult pluginResult, String callbackId) {
+        CordovaExtension.invokeErrorCallback(callbackId, pluginResult);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/api/PluginManager.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/api/PluginManager.java b/framework/ext/src/org/apache/cordova/api/PluginManager.java
new file mode 100644
index 0000000..e1e8d26
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/api/PluginManager.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.api;
+
+import java.util.Hashtable;
+
+import org.apache.cordova.CordovaExtension;
+import org.apache.cordova.file.FileUtils;
+import org.apache.cordova.util.Logger;
+
+import net.rim.device.api.script.Scriptable;
+import net.rim.device.api.script.ScriptableFunction;
+
+/**
+ * PluginManager represents an object in the script engine. It can be accessed
+ * from the script environment using <code>cordova.PluginManager</code>.
+ *
+ * PluginManager provides a function, <code>exec</code>, that can be invoked
+ * from the script environment: <code>cordova.PluginManager.exec(...)</code>.
+ * Invoking this function causes the script engine to load the appropriate
+ * Cordova Plugin and perform the specified action.
+ */
+public final class PluginManager extends Scriptable {
+
+    /**
+     * Field used to invoke Plugin actions.
+     */
+    public static String FIELD_EXEC = "exec";
+
+    /**
+     * Field used to cleanup Plugins.
+     */
+    public static String FIELD_DESTROY = "destroy";
+
+    /**
+     * Field used to indicate application has been brought to foreground.
+     */
+    public static String FIELD_RESUME = "resume";
+
+    /**
+     * Field used to indicate application has been sent to background
+     */
+    public static String FIELD_PAUSE = "pause";
+
+    /**
+     * Field used to register a Plugin.
+     */
+    public static String FIELD_ADD_PLUGIN = "addPlugin";
+
+    /**
+     * Loads the appropriate Cordova Plugins and invokes their actions.
+     */
+    private final PluginManagerFunction pluginManagerFunction;
+
+    /**
+     * Maps available services to Java class names.
+     */
+    private Hashtable services = new Hashtable();
+
+    /**
+     * Constructor.  Adds available Cordova services.
+     * @param ext   The Cordova JavaScript Extension
+     */
+    public PluginManager(CordovaExtension ext) {
+        this.pluginManagerFunction = new PluginManagerFunction(ext, this);
+    }
+
+    /**
+     * The following fields are supported from the script environment:
+     *
+     *  <code>cordova.pluginManager.exec</code> - Loads the appropriate
+     *  Plugin and invokes the specified action.
+     *
+     *  <code>cordova.pluginManager.destroy</code> - Invokes the <code>onDestroy</code>
+     *  method on all Plugins to give them a chance to cleanup before exit.
+     */
+    public Object getField(String name) throws Exception {
+        if (name.equals(FIELD_EXEC)) {
+            return this.pluginManagerFunction;
+        }
+        else if (name.equals(FIELD_DESTROY)) {
+            return new ScriptableFunction() {
+                public Object invoke(Object obj, Object[] oargs) throws Exception {
+                    destroy();
+                    return null;
+                }
+            };
+        }
+        else if (name.equals(FIELD_RESUME)) {
+            final PluginManagerFunction plugin_mgr = this.pluginManagerFunction;
+            return new ScriptableFunction() {
+                public Object invoke(Object obj, Object[] oargs) throws Exception {
+                    plugin_mgr.onResume();
+                    return null;
+                }
+            };
+        }
+        else if (name.equals(FIELD_PAUSE)) {
+            final PluginManagerFunction plugin_mgr = this.pluginManagerFunction;
+            return new ScriptableFunction() {
+                public Object invoke(Object obj, Object[] oargs) throws Exception {
+                    plugin_mgr.onPause();
+                    return null;
+                }
+            };
+        }
+        else if (name.equals(FIELD_ADD_PLUGIN)) {
+            Logger.log("Plugins are now added through the plugins.xml in the application root.");
+        }
+        return super.getField(name);
+    }
+
+    /**
+     * Add a class that implements a service.
+     *
+     * @param serviceName   The service name.
+     * @param className     The Java class name that implements the service.
+     */
+    public void addService(String serviceName, String className) {
+        this.services.put(serviceName, className);
+    }
+
+    /**
+     * Cleanup the plugin resources and delete temporary directory that may have
+     * been created.
+     */
+    public void destroy() {
+        // allow plugins to clean up
+        pluginManagerFunction.onDestroy();
+
+        // delete temporary application directory
+        // NOTE: doing this on a background thread doesn't work because the app
+        // is closing and the thread is killed before it completes.
+        try {
+            FileUtils.deleteApplicationTempDirectory();
+        } catch (Exception e) {
+            Logger.log(this.getClass().getName()
+                    + ": error deleting application temp directory: "
+                    + e.getMessage());
+        }
+    }
+
+    /**
+     * Get the class that implements a service.
+     *
+     * @param serviceName   The service name.
+     * @return The Java class name that implements the service.
+     */
+    public String getClassForService(String serviceName) {
+        return (String)this.services.get(serviceName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/api/PluginManagerFunction.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/api/PluginManagerFunction.java b/framework/ext/src/org/apache/cordova/api/PluginManagerFunction.java
new file mode 100644
index 0000000..13a0e77
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/api/PluginManagerFunction.java
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.api;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import net.rim.device.api.script.ScriptableFunction;
+
+import org.apache.cordova.CordovaExtension;
+import org.apache.cordova.json4j.JSONArray;
+import org.apache.cordova.json4j.JSONException;
+import org.apache.cordova.util.Logger;
+
+/**
+ * PluginManagerFunction represents a function that can be invoked from the
+ * script environment of the widget framework.  It manages the plugins for
+ * the Cordova JavaScript Extension.
+ *
+ * Calling <code>cordova.pluginManager.exec(...)</code> from JavaScript will
+ * result in this class' <code>invoke()</code> method being called.
+ */
+public class PluginManagerFunction extends ScriptableFunction {
+
+	private final static int ARG_SERVICE = 0;
+	private final static int ARG_ACTION = 1;
+	private final static int ARG_CALLBACK_ID = 2;
+	private final static int ARG_ARGS = 3;
+	private final static int ARG_ASYNC = 4;
+
+	private Hashtable plugins = new Hashtable();
+
+	private final CordovaExtension ext;
+	private final PluginManager pluginManager;
+
+	/**
+	 * Constructor.
+	 * @param ext              The Cordova JavaScript Extension
+	 * @param pluginManager    The PluginManager that exposes the scriptable object.
+	 */
+	public PluginManagerFunction(CordovaExtension ext, PluginManager pluginManager) {
+		this.ext = ext;
+		this.pluginManager = pluginManager;
+	}
+
+	/**
+	 * The invoke method is called when cordova.pluginManager.exec(...) is
+	 * used from the script environment.  It instantiates the appropriate plugin
+	 * and invokes the specified action.  JavaScript arguments are passed in
+	 * as an array of objects.
+	 *
+	 * @param service 		String containing the service to run
+	 * @param action 		String containing the action that the service is supposed to perform. This is
+	 * 						passed to the plugin execute method and it is up to the plugin developer
+	 * 						how to deal with it.
+	 * @param callbackId 	String containing the id of the callback that is executed in JavaScript if
+	 * 						this is an async plugin call.
+	 * @param args 			An Array literal string containing any arguments needed in the
+	 * 						plugin execute method.
+	 * @param async 		Boolean indicating whether the calling JavaScript code is expecting an
+	 * 						immediate return value. If true, either CordovaExtension.callbackSuccess(...) or
+	 * 						CordovaExtension.callbackError(...) is called once the plugin code has executed.
+	 *
+	 * @return 				JSON encoded string with a response message and status.
+	 *
+	 * @see net.rim.device.api.script.ScriptableFunction#invoke(java.lang.Object, java.lang.Object[])
+	 */
+	public Object invoke(Object obj, Object[] oargs) throws Exception {
+		final String service = (String)oargs[ARG_SERVICE];
+		final String action = (String)oargs[ARG_ACTION];
+		final String callbackId = (String)oargs[ARG_CALLBACK_ID];
+		boolean async = (oargs[ARG_ASYNC].toString().equals("true") ? true : false);
+		PluginResult pr = null;
+
+		try {
+			// action arguments
+			final JSONArray args = new JSONArray((String)oargs[ARG_ARGS]);
+
+			// get the class for the specified service
+			String clazz = this.pluginManager.getClassForService(service);
+			Class c = null;
+			if (clazz != null) {
+				c = getClassByName(clazz);
+			}
+
+			if (isCordovaPlugin(c)) {
+				// Create a new instance of the plugin and set the context
+				final Plugin plugin = this.loadPlugin(clazz, c);
+				async = async && !plugin.isSynch(action);
+				if (async) {
+					// Run this async on a background thread so that JavaScript can continue on
+					Thread thread = new Thread(new Runnable() {
+						public void run() {
+							// Call execute on the plugin so that it can do it's thing
+						    final PluginResult result = plugin.execute(action, args, callbackId);
+
+						    if (result != null) {
+						        int status = result.getStatus();
+
+						        // If plugin status is OK,
+						        // or plugin is not going to send an immediate result (NO_RESULT)
+						        if (status == PluginResult.Status.OK.ordinal() ||
+						            status == PluginResult.Status.NO_RESULT.ordinal()) {
+						            CordovaExtension.invokeSuccessCallback(callbackId, result);
+						        }
+						        // error
+						        else {
+						            CordovaExtension.invokeErrorCallback(callbackId, result);
+						        }
+						    }
+						}
+					});
+					thread.start();
+					return "";
+				} else {
+					// Call execute on the plugin so that it can do it's thing
+					pr = plugin.execute(action, args, callbackId);
+				}
+			}
+		} catch (ClassNotFoundException e) {
+		    Logger.log(this.getClass().getName() + ": " + e);
+			pr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION, "ClassNotFoundException: " + e.getMessage());
+		} catch (IllegalAccessException e) {
+            Logger.log(this.getClass().getName() + ": " + e);
+			pr = new PluginResult(PluginResult.Status.ILLEGAL_ACCESS_EXCEPTION, "IllegalAccessException:" + e.getMessage());
+		} catch (InstantiationException e) {
+            Logger.log(this.getClass().getName() + ": " + e);
+			pr = new PluginResult(PluginResult.Status.INSTANTIATION_EXCEPTION, "InstantiationException: " + e.getMessage());
+		} catch (JSONException e) {
+            Logger.log(this.getClass().getName() + ": " + e);
+			pr = new PluginResult(PluginResult.Status.JSON_EXCEPTION, "JSONException: " + e.getMessage());
+		}
+		// if async we have already returned at this point unless there was an error...
+		if (async) {
+			CordovaExtension.invokeErrorCallback(callbackId, pr);
+		}
+		return ( pr != null ? pr.getJSONString() : "{ status: 0, message: 'all good' }" );
+	}
+
+	/**
+	 * Get the class.
+	 *
+	 * @param clazz
+	 * @return
+	 * @throws ClassNotFoundException
+	 */
+	private Class getClassByName(final String clazz) throws ClassNotFoundException {
+		return Class.forName(clazz);
+	}
+
+	/**
+	 * Determines if the class implements org.apache.cordova.api.Plugin interface.
+	 *
+	 * @param c The class to check.
+	 * @return Boolean indicating if the class implements org.apache.cordova.api.Plugin
+	 */
+	private boolean isCordovaPlugin(Class c) {
+		if (c != null) {
+			return org.apache.cordova.api.Plugin.class.isAssignableFrom(c) || org.apache.cordova.api.IPlugin.class.isAssignableFrom(c);
+		}
+		return false;
+	}
+
+    /**
+     * Add plugin to be loaded and cached.
+     * If plugin is already created, then just return it.
+     *
+     * @param className				The class to load
+     * @return						The plugin
+     */
+	public Plugin loadPlugin(String className, Class clazz) throws IllegalAccessException, InstantiationException {
+	    if (this.plugins.containsKey(className)) {
+                return this.getPlugin(className);
+	    }
+        Logger.log(this.getClass().getName() + ": Loading plugin " + clazz);
+        Plugin plugin = (Plugin)clazz.newInstance();
+        this.plugins.put(className, plugin);
+        plugin.setContext(this.ext);
+        return plugin;
+    }
+
+    /**
+     * Get the loaded plugin.
+     *
+     * @param className				The class of the loaded plugin.
+     * @return
+     */
+    public Plugin getPlugin(String className) {
+        return (Plugin)this.plugins.get(className);
+    }
+
+    /**
+     * Called when application is paused.
+     */
+    public void onPause() {
+        Enumeration e = this.plugins.elements();
+        while (e.hasMoreElements()) {
+            Plugin plugin = (Plugin)e.nextElement();
+            plugin.onPause();
+        }
+    }
+
+    /**
+     * Called when application is resumed.
+     */
+    public void onResume() {
+        Enumeration e = this.plugins.elements();
+        while (e.hasMoreElements()) {
+            Plugin plugin = (Plugin)e.nextElement();
+            plugin.onResume();
+        }
+    }
+
+    /**
+     * Called when application is destroyed.
+     */
+    public void onDestroy() {
+        Enumeration e = this.plugins.elements();
+        while (e.hasMoreElements()) {
+            Plugin plugin = (Plugin)e.nextElement();
+            plugin.onDestroy();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/api/PluginResult.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/api/PluginResult.java b/framework/ext/src/org/apache/cordova/api/PluginResult.java
new file mode 100644
index 0000000..2cca7ee
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/api/PluginResult.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.api;
+
+import org.apache.cordova.json4j.JSONObject;
+
+/**
+ * This class defines the standard object that should be returned as the
+ * result of any Cordova plugin invocation.
+ */
+public class PluginResult {
+
+    private final int status;
+    private final String message;
+    private boolean keepCallback = false;
+
+    public PluginResult(Status status) {
+        this.status = status.ordinal();
+        this.message = JSONObject.quote(status.getMessage());
+    }
+
+    public PluginResult(Status status, String message) {
+        this.status = status.ordinal();
+        this.message = JSONObject.quote(message);
+    }
+
+    public PluginResult(Status status, JSONObject message) {
+        this.status = status.ordinal();
+        this.message = (message != null) ? message.toString(): "null";
+    }
+
+    public PluginResult(Status status, int i) {
+        this.status = status.ordinal();
+        this.message = ""+i;
+    }
+
+    public PluginResult(Status status, float f) {
+        this.status = status.ordinal();
+        this.message = ""+f;
+    }
+
+    public PluginResult(Status status, boolean b) {
+        this.status = status.ordinal();
+        this.message = ""+b;
+    }
+
+    public PluginResult(Status status, long l) {
+        this.status = status.ordinal();
+        this.message = ""+l;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setKeepCallback(boolean b) {
+        this.keepCallback = b;
+    }
+
+    public boolean getKeepCallback() {
+        return this.keepCallback;
+    }
+
+    public String getJSONString() {
+        return "{status:" + this.status + ",message:" + this.message + ",keepCallback:" + this.keepCallback + "}";
+    }
+
+    /**
+     * Returns the JavaScript string that executes the success callback for the
+     * appropriate Cordova plugin.  The string is intended to be passed to the
+     * JavaScript engine.
+     * @param callbackId Unique id of the callback that is associated with the invoked plugin
+     * @return JavaScript string that invokes the appropriate plugin success callback
+     */
+    public String toSuccessCallbackString(String callbackId) {
+        return "try { Cordova.callbackSuccess('"+callbackId+"', " + this.getJSONString() + "); } catch(e) { alert('error in callbackSuccess:' + e.message); }";
+    }
+
+    /**
+     * Returns the JavaScript string that executes the error callback for the
+     * appropriate Cordova plugin.  The string is intended to be passed to the
+     * JavaScript engine.
+     * @param callbackId Unique id of the callback that is associated with the invoked plugin
+     * @return JavaScript string that invokes the appropriate plugin error callback
+     */
+    public String toErrorCallbackString(String callbackId) {
+        return "try { Cordova.callbackError('"+callbackId+"', " + this.getJSONString() + "); } catch(e) { alert('error in callbackError:' + e.message); }";
+    }
+
+    public String toErrorString() {
+        return "alert('general error');";
+    }
+
+    /**
+     * Enumerates PluginResult status.
+     */
+    public static class Status
+    {
+        private int val;
+        private String message;
+
+        protected Status(int val, String message) {
+            this.val = val;
+            this.message = message;
+        }
+
+        public int ordinal() {
+            return this.val;
+        }
+
+        public String getMessage() {
+            return this.message;
+        }
+
+        public static final Status NO_RESULT = new Status(0, "No result");
+        public static final Status OK = new Status(1, "OK");
+        public static final Status CLASS_NOT_FOUND_EXCEPTION = new Status(2, "Class not found");
+        public static final Status ILLEGAL_ACCESS_EXCEPTION = new Status(3, "Illegal access");
+        public static final Status INSTANTIATION_EXCEPTION = new Status(4, "Instantiation error");
+        public static final Status MALFORMED_URL_EXCEPTION = new Status(5, "Malformed URL");
+        public static final Status IO_EXCEPTION = new Status(6, "IO error");
+        public static final Status INVALID_ACTION = new Status(7, "Invalid action");
+        public static final Status JSON_EXCEPTION = new Status(8, "JSON error");
+        public static final Status ERROR = new Status(9, "Error");
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/app/App.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/app/App.java b/framework/ext/src/org/apache/cordova/app/App.java
new file mode 100644
index 0000000..38ae205
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/app/App.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.app;
+
+import org.apache.cordova.CordovaExtension;
+import org.apache.cordova.api.Plugin;
+import org.apache.cordova.api.PluginResult;
+import org.apache.cordova.json4j.JSONArray;
+
+import net.rim.device.api.browser.field2.BrowserFieldHistory;
+import net.rim.device.api.system.Application;
+import net.rim.device.api.system.SystemListener2;
+
+/**
+ * The App plug-in. This class provides access to application specific
+ * management. The following actions are supported:
+ *
+ *      clearHistory    - Clear the browser history.
+ *      backHistory     - Navigate back in the browser history.
+ *      detectBacklight - Start a system listener for backlight changes.
+ *      ignoreBacklight - Stop the system listener for backlight changes.
+ */
+public class App extends Plugin {
+
+    private final static String ACTION_CLEAR_HISTORY = "clearHistory";
+    private final static String ACTION_BACK_HISTORY = "backHistory";
+    private final static String ACTION_DETECT_BACKLIGHT = "detectBacklight";
+    private final static String ACTION_IGNORE_BACKLIGHT = "ignoreBacklight";
+
+    private SystemListener2 listener = null;
+    private String callbackId = null;
+
+    /**
+     * Executes the requested action and returns a PluginResult.
+     *
+     * @param action
+     *            The action to execute.
+     * @param callbackId
+     *            The callback ID to be invoked upon action completion
+     * @param args
+     *            JSONArry of arguments for the action.
+     * @return A PluginResult object with a status and message.
+     */
+    public PluginResult execute(String action, JSONArray args,
+            final String callbackId) {
+        PluginResult result = null;
+
+        if (ACTION_CLEAR_HISTORY.equals(action)) {
+            BrowserFieldHistory history = CordovaExtension.getBrowserField()
+                    .getHistory();
+            if (history != null) {
+                history.clearHistory();
+            }
+            result = new PluginResult(PluginResult.Status.OK);
+        } else if (ACTION_BACK_HISTORY.equals(action)) {
+            CordovaExtension.getBrowserField().back();
+            result = new PluginResult(PluginResult.Status.OK);
+        } else if (ACTION_DETECT_BACKLIGHT.equals(action)) {
+            addListener(callbackId);
+            result = new PluginResult(PluginResult.Status.NO_RESULT);
+            result.setKeepCallback(true);
+        } else if (ACTION_IGNORE_BACKLIGHT.equals(action)) {
+            removeListener();
+            result = new PluginResult(PluginResult.Status.OK);
+        } else {
+            result = new PluginResult(PluginResult.Status.INVALID_ACTION,
+                    "App: Invalid action: " + action);
+        }
+
+        return result;
+    }
+
+    /**
+     * Called when Plugin is destroyed.
+     */
+    public void onDestroy() {
+        removeListener();
+    }
+
+    /**
+     * Register a system listener for backlight changes if one has not already
+     * been registered.
+     *
+     * @param callbackId
+     *            the callback ID associated with the system listener
+     */
+    private synchronized void addListener(final String callbackId) {
+        if (listener == null) {
+            listener = new SystemListener2() {
+                public void batteryGood() {}
+                public void batteryLow() {}
+                public void batteryStatusChange(int status) {}
+                public void powerOff() {}
+                public void powerUp() {}
+
+                public void backlightStateChange(boolean on) {
+                    PluginResult result = new PluginResult(
+                            PluginResult.Status.OK, on);
+
+                    // Must keep the call back active for future events.
+                    result.setKeepCallback(true);
+                    success(result, callbackId);
+                }
+
+                public void cradleMismatch(boolean mismatch) {}
+                public void fastReset() {}
+                public void powerOffRequested(int reason) {}
+                public void usbConnectionStateChange(int state) {}
+            };
+
+            this.callbackId = callbackId;
+            Application.getApplication().addSystemListener(listener);
+        }
+    }
+
+    /**
+     * Remove the system listener if it is registered and close out the
+     * callback handler.
+     */
+    private synchronized void removeListener() {
+        if (listener != null) {
+            Application.getApplication().removeSystemListener(listener);
+            listener = null;
+
+            if (callbackId != null) {
+                success(new PluginResult(PluginResult.Status.NO_RESULT),
+                        callbackId);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/battery/Battery.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/battery/Battery.java b/framework/ext/src/org/apache/cordova/battery/Battery.java
new file mode 100644
index 0000000..55356a5
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/battery/Battery.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.battery;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import org.apache.cordova.api.Plugin;
+import org.apache.cordova.api.PluginResult;
+import org.apache.cordova.json4j.JSONArray;
+import org.apache.cordova.json4j.JSONException;
+import org.apache.cordova.json4j.JSONObject;
+import org.apache.cordova.util.Logger;
+
+import net.rim.device.api.system.Application;
+import net.rim.device.api.system.DeviceInfo;
+import net.rim.device.api.system.SystemListener;
+
+/**
+ * The Battery plug-in. This class provides information about the state of the
+ * battery on the phone. The following actions are supported:
+ *
+ *      start - Start listening for changes in battery level (%) and batter
+ *              charging state.
+ *      stop  - Stop listening for changes in battery level and state.
+ */
+public class Battery extends Plugin {
+
+    /** Actions to start and stop listening for battery changes. */
+    private final static String ACTION_START = "start";
+    private final static String ACTION_STOP = "stop";
+
+    /** The percentage of battery remaining. */
+    private final static String LEVEL = "level";
+
+    /** Whether the battery is currently charging or not. */
+    private final static String CHARGING = "isPlugged";
+
+    // The set of call back IDs to send results to. Using Hashtable because
+    // BlackBerry does not support Collections. There should only ever be one
+    // call back ID, but this allows multiple.
+    private Hashtable callbackIds = new Hashtable();
+
+    private SystemListener batteryListener = null;
+
+    /**
+     * Executes the requested action and returns a PluginResult.
+     *
+     * @param action
+     *            The action to execute.
+     * @param callbackId
+     *            The callback ID to be invoked upon action completion
+     * @param args
+     *            JSONArry of arguments for the action.
+     * @return A PluginResult object with a status and message.
+     */
+    public PluginResult execute(String action, JSONArray args, String callbackId) {
+        PluginResult result = null;
+
+        if (ACTION_START.equals(action)) {
+            // Register a listener to detect battery changes.
+            addListener(callbackId);
+
+            // Don't return any result now, since battery status results are
+            // sent when listener is notified.
+            result = new PluginResult(PluginResult.Status.NO_RESULT);
+
+            // Must keep the call back active for future events.
+            result.setKeepCallback(true);
+        } else if (ACTION_STOP.equals(action)) {
+            // Remove the battery listener and cleanup call back IDs.
+            removeListener();
+            result = new PluginResult(PluginResult.Status.OK);
+        } else {
+            result = new PluginResult(PluginResult.Status.INVALID_ACTION,
+                    "Battery: Invalid action: " + action);
+        }
+
+        return result;
+    }
+
+    /**
+     * Remove the listener when the application is destroyed. Note that onPause
+     * is not overridden, so the listener will continue if the application is
+     * simply paused instead of destroyed.
+     */
+    public void onDestroy() {
+        removeListener();
+    }
+
+    /**
+     * Adds a SystemListener to listen for changes to the battery state. The
+     * listener is only registered if one has not already been added. If a
+     * listener has already been registered the call back id is simply saved so
+     * that it can be notified upon next battery state change.
+     *
+     * @param callbackId
+     *            The reference point to call back when a listener event occurs.
+     */
+    private synchronized void addListener(String callbackId) {
+        callbackIds.put(callbackId, callbackId);
+
+        // Only register a listener if one has not been registered.
+        if (batteryListener == null) {
+            batteryListener = new SystemListener() {
+                // Initialize the charging state and battery level.
+                private boolean prevChargeState = (DeviceInfo
+                        .getBatteryStatus() & DeviceInfo.BSTAT_CHARGING) != 0;
+                private int prevLevel = DeviceInfo.getBatteryLevel();
+
+                public void batteryGood() { }
+                public void batteryLow() { }
+
+                public void batteryStatusChange(int status) {
+                    // The status bits passed into this method are unreliable
+                    // in determining when the battery level has changed.
+                    // Instead, when any state change occurs, get the current
+                    // battery level and report the change if it is different
+                    // then previous value.
+                    int newLevel = DeviceInfo.getBatteryLevel();
+                    boolean newChargeState = (DeviceInfo.BSTAT_CHARGING & status) != 0;
+
+                    // Report change if level or charge state is different then
+                    // previous values.
+                    if (newLevel != prevLevel || newChargeState != prevChargeState) {
+                        prevChargeState = newChargeState;
+                        prevLevel = newLevel;
+
+                        // Store the retrieved properties in a JSON object.
+                        JSONObject connectionInfo = new JSONObject();
+                        try {
+                            connectionInfo.put(LEVEL, newLevel);
+                            connectionInfo.put(CHARGING, newChargeState);
+                        } catch (JSONException e) {
+                            Logger.error("JSONException: " + e.getMessage());
+                            return;
+                        }
+
+                        PluginResult result = new PluginResult(
+                                PluginResult.Status.OK, connectionInfo);
+
+                        sendSuccessResult(result, true);
+                    }
+                }
+
+                public void powerOff() { }
+                public void powerUp() { }
+            };
+            Application.getApplication().addSystemListener(batteryListener);
+        }
+    }
+
+    /**
+     * Remove the registered battery status listener and cleanup the call back
+     * IDs.
+     */
+    private synchronized void removeListener() {
+        if (batteryListener != null) {
+
+            // Remove the battery listener.
+            Application.getApplication().removeSystemListener(batteryListener);
+            batteryListener = null;
+
+            // Close out the call back IDs.
+            sendSuccessResult(new PluginResult(PluginResult.Status.OK), false);
+            callbackIds.clear();
+        }
+    }
+
+    /**
+     * Helper function to send the PluginResult to the saved call back IDs.
+     *
+     * @param result
+     *            the PluginResult to return
+     * @param keepCallback
+     *            Boolean value indicating whether to keep the call back id
+     *            active.
+     */
+    private void sendSuccessResult(PluginResult result, boolean keepCallback) {
+
+        if (result != null) {
+            // Must keep the call back active for future events.
+            result.setKeepCallback(keepCallback);
+
+            // Iterate through the saved call back IDs. Really should only ever
+            // be one.
+            for (Enumeration callbacks = this.callbackIds.elements(); callbacks
+                    .hasMoreElements();) {
+                success(result, (String) callbacks.nextElement());
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/camera/Camera.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/camera/Camera.java b/framework/ext/src/org/apache/cordova/camera/Camera.java
new file mode 100644
index 0000000..c033abe
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/camera/Camera.java
@@ -0,0 +1,430 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.camera;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Date;
+
+import javax.microedition.io.Connector;
+import javax.microedition.io.file.FileConnection;
+
+import org.apache.cordova.api.Plugin;
+import org.apache.cordova.api.PluginResult;
+import org.apache.cordova.json4j.JSONArray;
+import org.apache.cordova.json4j.JSONException;
+import org.apache.cordova.util.Logger;
+
+import net.rim.blackberry.api.invoke.CameraArguments;
+import net.rim.blackberry.api.invoke.Invoke;
+import net.rim.device.api.io.Base64OutputStream;
+import net.rim.device.api.io.IOUtilities;
+import net.rim.device.api.system.ApplicationDescriptor;
+import net.rim.device.api.system.Bitmap;
+import net.rim.device.api.system.Characters;
+import net.rim.device.api.system.ControlledAccessException;
+import net.rim.device.api.system.EncodedImage;
+import net.rim.device.api.system.EventInjector;
+import net.rim.device.api.system.JPEGEncodedImage;
+import net.rim.device.api.system.PNGEncodedImage;
+import net.rim.device.api.ui.UiApplication;
+
+/**
+ * The Camera plugin interface.
+ *
+ * The Camera class can invoke the following actions:
+ *
+ *   - takePicture: takes photo and returns base64 encoded image or image file URI
+ *
+ *   future?
+ *   - captureVideo...
+ *
+ */
+public class Camera extends Plugin
+{
+    /**
+     * Possible actions.
+     */
+    public static final String ACTION_TAKE_PICTURE = "takePicture";
+
+    /**
+     * Maximum image encoding size (in bytes) to allow.  (Obtained unofficially
+     * through trial and error). Anything larger will cause stability issues
+     * when sending back to the browser.
+     */
+    private static final long MAX_ENCODING_SIZE = 1500000L;
+
+    /**
+     * Executes the requested action and returns a PluginResult.
+     *
+     * @param action The action to execute.
+     * @param callbackId The callback ID to be invoked upon action completion
+     * @param args   JSONArry of arguments for the action.
+     * @return A PluginResult object with a status and message.
+     */
+    public PluginResult execute(String action, JSONArray args, String callbackId)
+    {
+        PluginResult result = null;
+
+        // take a picture
+        if (action != null && action.equals(ACTION_TAKE_PICTURE))
+        {
+            // Parse the options specified for the take picture action.
+            CameraOptions options;
+            try {
+                options = CameraOptions.fromJSONArray(args);
+            } catch (NumberFormatException e) {
+                return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "One of the camera options is not a valid number.");
+            } catch (JSONException e) {
+                return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "One of the camera options is not valid JSON.");
+            }
+
+            // launch native camera application
+            launchCamera(new PhotoListener(options, callbackId));
+
+            // The native camera application runs in a separate process, so we
+            // must now wait for the listener to retrieve the photo taken.
+            // Return NO_RESULT status so plugin manager does not invoke a callback,
+            // but keep the callback so the listener can invoke it later.
+            result = new PluginResult(PluginResult.Status.NO_RESULT);
+            result.setKeepCallback(true);
+            return result;
+        }
+        else
+        {
+            result = new PluginResult(PluginResult.Status.INVALID_ACTION, "Camera: Invalid action:" + action);
+        }
+
+        return result;
+    }
+
+    /**
+     * Launches the native camera application.
+     */
+    private static void launchCamera(PhotoListener listener)
+    {
+        // MMAPI interface doesn't use the native Camera application or interface
+        // (we would have to replicate it).  So, we invoke the native Camera application,
+        // which doesn't allow us to set any options.
+        synchronized(UiApplication.getEventLock()) {
+            UiApplication.getUiApplication().addFileSystemJournalListener(listener);
+            Invoke.invokeApplication(Invoke.APP_TYPE_CAMERA, new CameraArguments());
+        }
+    }
+
+    /**
+     * Closes the native camera application.
+     */
+    public static void closeCamera()
+    {
+        // simulate two escape characters to exit native camera application
+        // no, there is no other way to do this
+        UiApplication.getUiApplication().invokeLater(new Runnable() {
+            public void run() {
+                try
+                {
+                    EventInjector.KeyEvent inject = new EventInjector.KeyEvent(
+                            EventInjector.KeyEvent.KEY_DOWN, Characters.ESCAPE, 0);
+                    inject.post();
+                    inject.post();
+                }
+                catch (ControlledAccessException e)
+                {
+                    // the application doesn't have key injection permissions
+                    Logger.log(Camera.class.getName() + ": Unable to close camera.  " +
+                            ApplicationDescriptor.currentApplicationDescriptor().getName() +
+                            " does not have key injection permissions.");
+                }
+            }
+        });
+    }
+
+    /**
+     * Returns the image file URI or the Base64-encoded image.
+     * @param filePath The full path of the image file
+     * @param options Specifies the format of the image and the result
+     * @param callbackId The id of the callback to receive the result
+     */
+    public static void processImage(String filePath, CameraOptions options,
+            String callbackId) {
+        PluginResult result = null;
+        try
+        {
+            // wait for the file to be fully written to the file system
+            // to avoid premature access to it (yes, this has happened)
+            waitForImageFile(filePath);
+
+            // Reformat the image if the specified options require it,
+            // otherwise, get encoded string if base 64 string is output format.
+            String imageURIorData = filePath;
+            if (options.reformat) {
+                imageURIorData = reformatImage(filePath, options);
+            } else if (options.destinationType == CameraOptions.DESTINATION_DATA_URL) {
+                imageURIorData = encodeImage(filePath);
+            }
+
+            // we have to check the size to avoid memory errors in the browser
+            if (imageURIorData.length() > MAX_ENCODING_SIZE)
+            {
+                // it's a big one.  this is for your own good.
+                String msg = "Encoded image is too large.  Try reducing camera image size.";
+                Logger.log(Camera.class.getName() + ": " + msg);
+                result =  new PluginResult(PluginResult.Status.ERROR, msg);
+            }
+            else
+            {
+                result = new PluginResult(PluginResult.Status.OK, imageURIorData);
+            }
+        }
+        catch (Exception e)
+        {
+            result = new PluginResult(PluginResult.Status.IO_EXCEPTION, e.toString());
+        }
+
+        // send result back to JavaScript
+        sendResult(result, callbackId);
+    }
+
+    /**
+     * Waits for the image file to be fully written to the file system.
+     * @param filePath     Full path of the image file
+     * @throws IOException
+     */
+    private static void waitForImageFile(String filePath) throws IOException
+    {
+        long start = (new Date()).getTime();
+        FileConnection fconn = null;
+        try
+        {
+            fconn = (FileConnection)Connector.open(filePath, Connector.READ);
+            if (fconn.exists())
+            {
+                long fileSize = fconn.fileSize();
+                long size = 0;
+                while (true)
+                {
+                    try { Thread.sleep(100); } catch (InterruptedException e) {}
+                    size = fconn.fileSize();
+                    if (size == fileSize) {
+                        break;
+                    }
+                    fileSize = size;
+                }
+                Logger.log(Camera.class.getName() + ": " + filePath +
+                    " size=" + Long.toString(fileSize) + " bytes");
+            }
+        }
+        finally
+        {
+            if (fconn != null) fconn.close();
+        }
+        long end = (new Date()).getTime();
+        Logger.log(Camera.class.getName() + ": wait time=" + Long.toString(end-start) + " ms");
+    }
+
+    /**
+     * Opens the specified image file and converts its contents to a Base64-encoded string.
+     * @param filePath     Full path of the image file
+     * @return file contents as a Base64-encoded String
+     */
+    private static String encodeImage(String filePath) throws IOException
+    {
+        String imageData = null;
+
+        // open the image file
+        FileConnection fconn = null;
+        InputStream in = null;
+        ByteArrayOutputStream byteArrayOS = null;
+        try
+        {
+            fconn = (FileConnection)Connector.open(filePath);
+            if (fconn.exists())
+            {
+                // encode file contents using BASE64 encoding
+                in = fconn.openInputStream();
+                byteArrayOS = new ByteArrayOutputStream();
+                Base64OutputStream base64OS = new Base64OutputStream(byteArrayOS);
+                base64OS.write(IOUtilities.streamToBytes(in, 96*1024));
+                base64OS.flush();
+                base64OS.close();
+                imageData = byteArrayOS.toString();
+
+                Logger.log(Camera.class.getName() + ": Base64 encoding size=" +
+                        Integer.toString(imageData.length()));
+            }
+        }
+        finally
+        {
+            if (in != null) in.close();
+            if (fconn != null) fconn.close();
+            if (byteArrayOS != null) byteArrayOS.close();
+        }
+
+        return imageData;
+    }
+
+    /**
+     * Reformats the image taken with the camera based on the options specified.
+     *
+     * Unfortunately, reformatting the image will cause EXIF data in the photo
+     * to be lost.  Most importantly the orientation data is lost so the
+     * picture is not auto rotated by software that recognizes EXIF data.
+     *
+     * @param filePath
+     *            The full path of the image file
+     * @param options
+     *            Specifies the format of the image and the result
+     * @return the reformatted image file URI or Base64-encoded image
+     * @throws IOException
+     */
+    private static String reformatImage(String filePath, CameraOptions options)
+            throws IOException {
+        long start = (new Date()).getTime();
+
+        // Open the original image created by the camera application and read
+        // it into an EncodedImage object.
+        FileConnection fconn = null;
+        InputStream in = null;
+        Bitmap originalImage = null;
+        try {
+            fconn = (FileConnection) Connector.open(filePath);
+            in = fconn.openInputStream();
+            originalImage = Bitmap.createBitmapFromBytes(IOUtilities.streamToBytes(in, 96*1024), 0, -1, 1);
+        } finally {
+            if (in != null)
+                in.close();
+            if (fconn != null)
+                fconn.close();
+        }
+
+        int newWidth = options.targetWidth;
+        int newHeight = options.targetHeight;
+        int origWidth = originalImage.getWidth();
+        int origHeight = originalImage.getHeight();
+
+        // If only width or only height was specified, the missing dimension is
+        // set based on the current aspect ratio of the image.
+        if (newWidth > 0 && newHeight <= 0) {
+            newHeight = (newWidth * origHeight) / origWidth;
+        } else if (newWidth <= 0 && newHeight > 0) {
+            newWidth = (newHeight * origWidth) / origHeight;
+        } else if (newWidth <= 0 && newHeight <= 0) {
+            newWidth = origWidth;
+            newHeight = origHeight;
+        } else {
+            // If the user specified both a positive width and height
+            // (potentially different aspect ratio) then the width or height is
+            // scaled so that the image fits while maintaining aspect ratio.
+            // Alternatively, the specified width and height could have been
+            // kept and Bitmap.SCALE_TO_FIT specified when scaling, but this
+            // would result in whitespace in the new image.
+            double newRatio = newWidth / (double)newHeight;
+            double origRatio = origWidth / (double)origHeight;
+
+            if (origRatio > newRatio) {
+                newHeight = (newWidth * origHeight) / origWidth;
+            } else if (origRatio < newRatio) {
+                newWidth = (newHeight * origWidth) / origHeight;
+            }
+        }
+
+        Bitmap newImage = new Bitmap(newWidth, newHeight);
+        originalImage.scaleInto(newImage, options.imageFilter, Bitmap.SCALE_TO_FILL);
+
+        // Convert the image to the appropriate encoding.  PNG does not allow
+        // quality to be specified so the only affect that the quality option
+        // has for a PNG is on the seelction of the image filter.
+        EncodedImage encodedImage;
+        if (options.encoding == CameraOptions.ENCODING_PNG) {
+            encodedImage = PNGEncodedImage.encode(newImage);
+        } else {
+            encodedImage = JPEGEncodedImage.encode(newImage, options.quality);
+        }
+
+        // Rewrite the modified image back out to the same file.  This is done
+        // to ensure that for every picture taken, only one shows up in the
+        // gallery.  If the encoding changed the file extension will differ
+        // from the original.
+        OutputStream out = null;
+        int dirIndex = filePath.lastIndexOf('/');
+        String filename = filePath.substring(dirIndex + 1, filePath.lastIndexOf('.'))
+                + options.fileExtension;
+        try {
+            fconn = (FileConnection) Connector.open(filePath);
+            fconn.truncate(0);
+            out = fconn.openOutputStream();
+            out.write(encodedImage.getData());
+            fconn.rename(filename);
+        } finally {
+            if (out != null)
+                out.close();
+            if (fconn != null)
+                fconn.close();
+        }
+
+        // Return either the Base64-encoded string or the image URI for the
+        // new image.
+        String imageURIorData;
+        if (options.destinationType == CameraOptions.DESTINATION_DATA_URL) {
+            ByteArrayOutputStream byteArrayOS = null;
+
+            try {
+                byteArrayOS = new ByteArrayOutputStream();
+                Base64OutputStream base64OS = new Base64OutputStream(
+                        byteArrayOS);
+                base64OS.write(encodedImage.getData());
+                base64OS.flush();
+                base64OS.close();
+                imageURIorData = byteArrayOS.toString();
+                Logger.log(Camera.class.getName() + ": Base64 encoding size="
+                        + Integer.toString(imageURIorData.length()));
+            } finally {
+                if (byteArrayOS != null) {
+                    byteArrayOS.close();
+                }
+            }
+        } else {
+            imageURIorData = filePath.substring(0, dirIndex + 1) + filename;
+        }
+
+        long end = (new Date()).getTime();
+        Logger.log(Camera.class.getName() + ": reformat time=" + Long.toString(end-start) + " ms");
+
+        return imageURIorData;
+    }
+
+    /**
+     * Sends result back to JavaScript.
+     * @param result PluginResult
+     */
+    private static void sendResult(PluginResult result, String callbackId)
+    {
+        // invoke the appropriate callback
+        if (result.getStatus() == PluginResult.Status.OK.ordinal())
+        {
+            success(result, callbackId);
+        }
+        else
+        {
+            error(result, callbackId);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/camera/CameraOptions.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/camera/CameraOptions.java b/framework/ext/src/org/apache/cordova/camera/CameraOptions.java
new file mode 100644
index 0000000..97a4176
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/camera/CameraOptions.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.camera;
+
+import org.apache.cordova.json4j.JSONArray;
+import org.apache.cordova.json4j.JSONException;
+
+import net.rim.device.api.system.Bitmap;
+
+/**
+ * A helper class to hold all the options specified when using the camera api.
+ */
+public class CameraOptions {
+
+    /** Return the result as a Base-64 encoded string. */
+    public static final int DESTINATION_DATA_URL = 0;
+
+    /** Return the result as a file URI. */
+    public static final int DESTINATION_FILE_URI = 1;
+
+    /** JPEG image encoding. */
+    public static final int ENCODING_JPEG = 0;
+
+    /** PNG image encoding. */
+    public static final int ENCODING_PNG = 1;
+
+    /** Select image from picture library. */
+    public static final int SOURCE_PHOTOLIBRARY = 0;
+
+    /** Take picture from camera. */
+    public static final int SOURCE_CAMERA = 1;
+
+    /** Select image from picture library. */
+    public static final int SOURCE_SAVEDPHOTOALBUM = 2;
+
+    // Class members with defaults set.
+    public int quality = 80;
+    public int destinationType = DESTINATION_DATA_URL;
+    public int sourceType = SOURCE_CAMERA;
+    public int targetWidth = -1;
+    public int targetHeight = -1;
+    public int encoding = ENCODING_JPEG;
+    public String fileExtension = ".jpg";
+    public int imageFilter = Bitmap.FILTER_LANCZOS;
+    public boolean reformat = false;
+
+    /**
+     * Defines the order of args in the JSONArray
+     *
+     * [ 80,                                   // quality
+     *   Camera.DestinationType.DATA_URL,      // destinationType
+     *   Camera.PictureSourceType.PHOTOLIBRARY // sourceType (ignored)
+     *   400,                                  // targetWidth
+     *   600,                                  // targetHeight
+     *   Camera.EncodingType.JPEG]             // encoding
+     */
+    private static final int ARG_QUALITY = 0;
+    private static final int ARG_DESTINATION_TYPE = 1;
+    private static final int ARG_SOURCE_TYPE = 2;
+    private static final int ARG_TARGET_WIDTH = 3;
+    private static final int ARG_TARGET_HEIGHT = 4;
+    private static final int ARG_ENCODING = 5;
+
+
+    /**
+     * Parse the JSONArray and populate the class members with the values.
+     *
+     * @param args
+     *            a JSON Array of camera options.
+     * @return a new CameraOptions object with values set.
+     * @throws NumberFormatException
+     * @throws JSONException
+     */
+    public static CameraOptions fromJSONArray(JSONArray args)
+            throws NumberFormatException, JSONException {
+        CameraOptions options = new CameraOptions();
+
+        if (args != null && args.length() > 0) {
+            // Use the quality value to determine what image filter to use
+            // if a reformat is necessary.  The possible values in order from
+            // fastest (poorest quality) to slowest (best quality) are:
+            //
+            //     FILTER_BOX -> FILTER_BILINEAR -> FILTER_LANCZOS
+            if (!args.isNull(ARG_QUALITY)) {
+                int quality = Integer.parseInt(args.getString(ARG_QUALITY));
+                if (quality > 0) {
+                    options.quality = quality > 100 ? 100 : quality;
+                    if (options.quality < 30) {
+                        options.imageFilter = Bitmap.FILTER_BOX;
+                    } else if (options.quality < 60) {
+                        options.imageFilter = Bitmap.FILTER_BILINEAR;
+                    }
+                }
+            }
+
+            if (!args.isNull(ARG_DESTINATION_TYPE)) {
+                int destType = Integer.parseInt(args
+                        .getString(ARG_DESTINATION_TYPE));
+                if (destType == DESTINATION_FILE_URI) {
+                    options.destinationType = DESTINATION_FILE_URI;
+                }
+            }
+
+            if (!args.isNull(ARG_SOURCE_TYPE)) {
+                options.sourceType = Integer.parseInt(args
+                        .getString(ARG_SOURCE_TYPE));
+            }
+
+            if (!args.isNull(ARG_TARGET_WIDTH)) {
+                options.targetWidth = Integer.parseInt(args
+                        .getString(ARG_TARGET_WIDTH));
+            }
+
+            if (!args.isNull(ARG_TARGET_HEIGHT)) {
+                options.targetHeight = Integer.parseInt(args
+                        .getString(ARG_TARGET_HEIGHT));
+            }
+
+            if (!args.isNull(ARG_ENCODING)) {
+                int encoding = Integer.parseInt(args.getString(ARG_ENCODING));
+                if (encoding == ENCODING_PNG) {
+                    options.encoding = ENCODING_PNG;
+                    options.fileExtension = ".png";
+                }
+            }
+
+            // A reformat of the picture taken from the camera is only performed
+            // if a custom width or height was specified or the user wants
+            // the output in an encoded form which is not JPEG.
+            if (options.targetWidth > 0 || options.targetHeight > 0
+                    || options.encoding != ENCODING_JPEG) {
+                options.reformat = true;
+            }
+        }
+
+        return options;
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        StringBuffer str = new StringBuffer();
+        str.append("Destination: " + destinationType + "\n");
+        str.append("Source: " + sourceType + "\n");
+        str.append("Quality: " + quality + "\n");
+        str.append("Width:  " + targetWidth + "\n");
+        str.append("Height: " + targetHeight + "\n");
+        str.append("Encoding:    " + encoding + "\n");
+        str.append("Filter: " + imageFilter + "\n");
+        str.append("Reformat: " + reformat);
+        return str.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/camera/PhotoListener.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/camera/PhotoListener.java b/framework/ext/src/org/apache/cordova/camera/PhotoListener.java
new file mode 100644
index 0000000..8571788
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/camera/PhotoListener.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.camera;
+
+import net.rim.device.api.io.file.FileSystemJournal;
+import net.rim.device.api.io.file.FileSystemJournalEntry;
+import net.rim.device.api.io.file.FileSystemJournalListener;
+import net.rim.device.api.ui.UiApplication;
+
+/**
+ * Listens for photo added to file system and invokes the specified callback
+ * with the result formatted according the specified destination type.
+ */
+public class PhotoListener implements FileSystemJournalListener {
+
+    /**
+     * Image format options specified by the caller.
+     */
+    private CameraOptions options;
+
+    /**
+     * Callback to be invoked with the result.
+     */
+    private String callbackId;
+
+    /**
+     * Used to track file system changes.
+     */
+    private long lastUSN = 0;
+
+    /**
+     * Constructor.
+     * @param options         Specifies the format of the image and result
+     * @param callbackId      The id of the callback to receive the result
+     */
+    public PhotoListener(CameraOptions options, String callbackId)
+    {
+        this.options = options;
+        this.callbackId = callbackId;
+    }
+
+    /**
+     * Listens for file system changes.  When a JPEG file is added, we process
+     * it and send it back.
+     */
+    public void fileJournalChanged()
+    {
+        // next sequence number file system will use
+        long USN = FileSystemJournal.getNextUSN();
+
+        for (long i = USN - 1; i >= lastUSN && i < USN; --i)
+        {
+            FileSystemJournalEntry entry = FileSystemJournal.getEntry(i);
+            if (entry == null)
+            {
+                break;
+            }
+
+            if (entry.getEvent() == FileSystemJournalEntry.FILE_ADDED)
+            {
+                String path = entry.getPath();
+                if (path != null && path.indexOf(".jpg") != -1)
+                {
+                    // we found a new JPEG file
+                    // first, stop listening to avoid processing the file more than once
+                    synchronized(UiApplication.getEventLock()) {
+                        UiApplication.getUiApplication().removeFileSystemJournalListener(this);
+                    }
+
+                    // process the image on a background thread to avoid clogging the event queue
+                    final String filePath = "file://" + path;
+                    Thread thread = new Thread(new Runnable() {
+                        public void run() {
+                            Camera.processImage(filePath, options, callbackId);
+                        }
+                    });
+                    thread.start();
+
+                    // clean up
+                    Camera.closeCamera();
+
+                    break;
+                }
+            }
+        }
+
+        // remember the file journal change number,
+        // so we don't search the same events again and again
+        lastUSN = USN;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/device/Device.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/device/Device.java b/framework/ext/src/org/apache/cordova/device/Device.java
new file mode 100644
index 0000000..ea421ef
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/device/Device.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ * Copyright (c) 2011, Research In Motion Limited.
+ */
+package org.apache.cordova.device;
+
+import org.apache.cordova.api.Plugin;
+import org.apache.cordova.api.PluginResult;
+import org.apache.cordova.json4j.JSONArray;
+import org.apache.cordova.json4j.JSONException;
+import org.apache.cordova.json4j.JSONObject;
+
+import net.rim.device.api.system.DeviceInfo;
+
+/**
+ * Provides device information, including:
+ *
+ * - Device platform version (e.g. 2.13.0.95). Not to be confused with BlackBerry OS version.
+ * - Unique device identifier (UUID).
+ * - Cordova software version.
+ */
+public final class Device extends Plugin {
+
+	public static final String FIELD_PLATFORM 	= "platform";
+	public static final String FIELD_UUID     	= "uuid";
+	public static final String FIELD_CORDOVA	= "cordova";
+	public static final String FIELD_NAME 		= "name";
+	public static final String FIELD_VERSION 	= "version";
+
+	public static final String ACTION_GET_DEVICE_INFO = "getDeviceInfo";
+
+	public PluginResult execute(String action, JSONArray args, String callbackId) {
+		PluginResult result = new PluginResult(PluginResult.Status.INVALID_ACTION, "Device: Invalid action:" + action);
+
+		if(action.equals(ACTION_GET_DEVICE_INFO)){
+			try {
+				JSONObject device = new JSONObject();
+				device.put( FIELD_PLATFORM, new String(DeviceInfo.getPlatformVersion() ) );
+				device.put( FIELD_UUID, new Integer( DeviceInfo.getDeviceId()) );
+				device.put( FIELD_CORDOVA, "1.4.1" );
+				device.put( FIELD_NAME, new String(DeviceInfo.getDeviceName()) );
+				device.put( FIELD_VERSION, new String(DeviceInfo.getSoftwareVersion()) );
+				result = new PluginResult(PluginResult.Status.OK, device);
+			} catch (JSONException e) {
+				result = new PluginResult(PluginResult.Status.JSON_EXCEPTION, e.getMessage());
+			}
+		}
+
+		return result;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/file/Entry.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/file/Entry.java b/framework/ext/src/org/apache/cordova/file/Entry.java
new file mode 100644
index 0000000..66fb59b
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/file/Entry.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.file;
+
+import org.apache.cordova.json4j.JSONException;
+import org.apache.cordova.json4j.JSONObject;
+
+public class Entry {
+
+    private boolean isDirectory = false;
+    private String name = null;
+    private String fullPath = null;
+
+    public boolean isDirectory() {
+        return isDirectory;
+    }
+
+    public void setDirectory(boolean isDirectory) {
+        this.isDirectory = isDirectory;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getFullPath() {
+        return fullPath;
+    }
+
+    public void setFullPath(String fullPath) {
+        this.fullPath = fullPath;
+    }
+
+    public JSONObject toJSONObject() {
+        JSONObject o = new JSONObject();
+        try {
+            o.put("isDirectory", isDirectory);
+            o.put("isFile", !isDirectory);
+            o.put("name", name);
+            o.put("fullPath", fullPath);
+        }
+        catch (JSONException ignored) {
+        }
+        return o;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-cordova-blackberry-webworks/blob/05319d3c/framework/ext/src/org/apache/cordova/file/File.java
----------------------------------------------------------------------
diff --git a/framework/ext/src/org/apache/cordova/file/File.java b/framework/ext/src/org/apache/cordova/file/File.java
new file mode 100644
index 0000000..3d04041
--- /dev/null
+++ b/framework/ext/src/org/apache/cordova/file/File.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cordova.file;
+
+import org.apache.cordova.json4j.JSONException;
+import org.apache.cordova.json4j.JSONObject;
+
+public class File {
+    private String name = null;
+    private String fullPath = null;
+    private String type = null;
+    private long lastModifiedDate;
+    private long size = 0;
+
+    public File(String filePath) {
+        this.fullPath = filePath;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public long getLastModifiedDate() {
+        return lastModifiedDate;
+    }
+
+    public void setLastModifiedDate(long lastModifiedDate) {
+        this.lastModifiedDate = lastModifiedDate;
+    }
+
+    public long getSize() {
+        return size;
+    }
+
+    public void setSize(long size) {
+        this.size = size;
+    }
+
+    public String getFullPath() {
+        return fullPath;
+    }
+
+    public JSONObject toJSONObject() {
+        JSONObject o = new JSONObject();
+        try {
+            o.put("fullPath", fullPath);
+            o.put("type", type);
+            o.put("name", name);
+            o.put("lastModifiedDate", lastModifiedDate);
+            o.put("size", size);
+        }
+        catch (JSONException ignored) {
+        }
+        return o;
+    }
+}