Return-Path: X-Original-To: apmail-incubator-callback-commits-archive@minotaur.apache.org Delivered-To: apmail-incubator-callback-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 561C2927D for ; Wed, 8 Feb 2012 20:14:55 +0000 (UTC) Received: (qmail 43706 invoked by uid 500); 8 Feb 2012 20:14:55 -0000 Delivered-To: apmail-incubator-callback-commits-archive@incubator.apache.org Received: (qmail 43683 invoked by uid 500); 8 Feb 2012 20:14:55 -0000 Mailing-List: contact callback-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: callback-dev@incubator.apache.org Delivered-To: mailing list callback-commits@incubator.apache.org Received: (qmail 43676 invoked by uid 99); 8 Feb 2012 20:14:55 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 08 Feb 2012 20:14:55 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.114] (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 08 Feb 2012 20:14:51 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 12D6A31D5E8; Wed, 8 Feb 2012 20:14:02 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: deedubbu@apache.org To: callback-commits@incubator.apache.org X-Mailer: ASF-Git Admin Mailer Subject: [10/19] CB-226 Rename to Cordova. Message-Id: <20120208201402.12D6A31D5E8@tyr.zones.apache.org> Date: Wed, 8 Feb 2012 20:14:02 +0000 (UTC) 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 cordova.PluginManager. + * + * PluginManager provides a function, exec, that can be invoked + * from the script environment: cordova.PluginManager.exec(...). + * 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: + * + * cordova.pluginManager.exec - Loads the appropriate + * Plugin and invokes the specified action. + * + * cordova.pluginManager.destroy - Invokes the onDestroy + * 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 cordova.pluginManager.exec(...) from JavaScript will + * result in this class' invoke() 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; + } +}