You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ag...@apache.org on 2013/10/22 22:43:26 UTC
[7/8] Move java files back into api/ directory.
http://git-wip-us.apache.org/repos/asf/cordova-android/blob/1dfbebf9/framework/src/org/apache/cordova/api/PluginManager.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/api/PluginManager.java b/framework/src/org/apache/cordova/api/PluginManager.java
index d7fddfe..d9eb41e 100755
--- a/framework/src/org/apache/cordova/api/PluginManager.java
+++ b/framework/src/org/apache/cordova/api/PluginManager.java
@@ -18,10 +18,419 @@
*/
package org.apache.cordova.api;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.cordova.CordovaArgs;
import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.api.CallbackContext;
+import org.apache.cordova.api.CordovaInterface;
+import org.apache.cordova.api.CordovaPlugin;
+import org.apache.cordova.api.PluginEntry;
+import org.apache.cordova.api.PluginResult;
+import org.json.JSONException;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Intent;
+import android.content.res.XmlResourceParser;
+
+import android.net.Uri;
+import android.util.Log;
+
+/**
+ * PluginManager is exposed to JavaScript in the Cordova WebView.
+ *
+ * Calling native plugin code can be done by calling PluginManager.exec(...)
+ * from JavaScript.
+ */
+public class PluginManager {
+ private static String TAG = "PluginManager";
+
+ // List of service entries
+ private final HashMap<String, PluginEntry> entries = new HashMap<String, PluginEntry>();
+
+ private final CordovaInterface ctx;
+ private final CordovaWebView app;
-public class PluginManager extends org.apache.cordova.PluginManager {
+ // Flag to track first time through
+ private boolean firstRun;
+
+ // Map URL schemes like foo: to plugins that want to handle those schemes
+ // This would allow how all URLs are handled to be offloaded to a plugin
+ protected HashMap<String, String> urlMap = new HashMap<String, String>();
+
+ private AtomicInteger numPendingUiExecs;
+
+ /**
+ * Constructor.
+ *
+ * @param app
+ * @param ctx
+ */
public PluginManager(CordovaWebView app, CordovaInterface ctx) {
- super(app, ctx);
+ this.ctx = ctx;
+ this.app = app;
+ this.firstRun = true;
+ this.numPendingUiExecs = new AtomicInteger(0);
+ }
+
+ /**
+ * Init when loading a new HTML page into webview.
+ */
+ public void init() {
+ LOG.d(TAG, "init()");
+
+ // If first time, then load plugins from config.xml file
+ if (this.firstRun) {
+ this.loadPlugins();
+ this.firstRun = false;
+ }
+
+ // Stop plugins on current HTML page and discard plugin objects
+ else {
+ this.onPause(false);
+ this.onDestroy();
+ this.clearPluginObjects();
+ }
+
+ // Insert PluginManager service
+ this.addService(new PluginEntry("PluginManager", new PluginManagerService()));
+
+ // Start up all plugins that have onload specified
+ this.startupPlugins();
+ }
+
+ /**
+ * Load plugins from res/xml/config.xml
+ */
+ public void loadPlugins() {
+ int id = this.ctx.getActivity().getResources().getIdentifier("config", "xml", this.ctx.getActivity().getClass().getPackage().getName());
+ if (id == 0) {
+ this.pluginConfigurationMissing();
+ //We have the error, we need to exit without crashing!
+ return;
+ }
+ XmlResourceParser xml = this.ctx.getActivity().getResources().getXml(id);
+ int eventType = -1;
+ String service = "", pluginClass = "", paramType = "";
+ boolean onload = false;
+ boolean insideFeature = false;
+ while (eventType != XmlResourceParser.END_DOCUMENT) {
+ if (eventType == XmlResourceParser.START_TAG) {
+ String strNode = xml.getName();
+ //This is for the old scheme
+ if (strNode.equals("plugin")) {
+ service = xml.getAttributeValue(null, "name");
+ pluginClass = xml.getAttributeValue(null, "value");
+ Log.d(TAG, "<plugin> tags are deprecated, please use <features> instead. <plugin> will no longer work as of Cordova 3.0");
+ onload = "true".equals(xml.getAttributeValue(null, "onload"));
+ }
+ //What is this?
+ else if (strNode.equals("url-filter")) {
+ this.urlMap.put(xml.getAttributeValue(null, "value"), service);
+ }
+ else if (strNode.equals("feature")) {
+ //Check for supported feature sets aka. plugins (Accelerometer, Geolocation, etc)
+ //Set the bit for reading params
+ insideFeature = true;
+ service = xml.getAttributeValue(null, "name");
+ }
+ else if (insideFeature && strNode.equals("param")) {
+ paramType = xml.getAttributeValue(null, "name");
+ if (paramType.equals("service")) // check if it is using the older service param
+ service = xml.getAttributeValue(null, "value");
+ else if (paramType.equals("package") || paramType.equals("android-package"))
+ pluginClass = xml.getAttributeValue(null,"value");
+ else if (paramType.equals("onload"))
+ onload = "true".equals(xml.getAttributeValue(null, "value"));
+ }
+ }
+ else if (eventType == XmlResourceParser.END_TAG)
+ {
+ String strNode = xml.getName();
+ if (strNode.equals("feature") || strNode.equals("plugin"))
+ {
+ PluginEntry entry = new PluginEntry(service, pluginClass, onload);
+ this.addService(entry);
+
+ //Empty the strings to prevent plugin loading bugs
+ service = "";
+ pluginClass = "";
+ insideFeature = false;
+ }
+ }
+ try {
+ eventType = xml.next();
+ } catch (XmlPullParserException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Delete all plugin objects.
+ */
+ public void clearPluginObjects() {
+ for (PluginEntry entry : this.entries.values()) {
+ entry.plugin = null;
+ }
+ }
+
+ /**
+ * Create plugins objects that have onload set.
+ */
+ public void startupPlugins() {
+ for (PluginEntry entry : this.entries.values()) {
+ if (entry.onload) {
+ entry.createPlugin(this.app, this.ctx);
+ }
+ }
+ }
+
+ /**
+ * Receives a request for execution and fulfills it by finding the appropriate
+ * Java class and calling it's execute method.
+ *
+ * PluginManager.exec can be used either synchronously or async. In either case, a JSON encoded
+ * string is returned that will indicate if any errors have occurred when trying to find
+ * or execute the class denoted by the clazz argument.
+ *
+ * @param service String containing the service to run
+ * @param action String containing the action that the class 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 execute in JavaScript if
+ * this is an async plugin call.
+ * @param rawArgs An Array literal string containing any arguments needed in the
+ * plugin execute method.
+ */
+ public void exec(final String service, final String action, final String callbackId, final String rawArgs) {
+ if (numPendingUiExecs.get() > 0) {
+ numPendingUiExecs.getAndIncrement();
+ this.ctx.getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ execHelper(service, action, callbackId, rawArgs);
+ numPendingUiExecs.getAndDecrement();
+ }
+ });
+ } else {
+ execHelper(service, action, callbackId, rawArgs);
+ }
+ }
+
+ private void execHelper(final String service, final String action, final String callbackId, final String rawArgs) {
+ CordovaPlugin plugin = getPlugin(service);
+ if (plugin == null) {
+ Log.d(TAG, "exec() call to unknown plugin: " + service);
+ PluginResult cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION);
+ app.sendPluginResult(cr, callbackId);
+ return;
+ }
+ try {
+ CallbackContext callbackContext = new CallbackContext(callbackId, app);
+ boolean wasValidAction = plugin.execute(action, rawArgs, callbackContext);
+ if (!wasValidAction) {
+ PluginResult cr = new PluginResult(PluginResult.Status.INVALID_ACTION);
+ app.sendPluginResult(cr, callbackId);
+ }
+ } catch (JSONException e) {
+ PluginResult cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
+ app.sendPluginResult(cr, callbackId);
+ }
+ }
+
+ @Deprecated
+ public void exec(String service, String action, String callbackId, String jsonArgs, boolean async) {
+ exec(service, action, callbackId, jsonArgs);
+ }
+
+ /**
+ * Get the plugin object that implements the service.
+ * If the plugin object does not already exist, then create it.
+ * If the service doesn't exist, then return null.
+ *
+ * @param service The name of the service.
+ * @return CordovaPlugin or null
+ */
+ public CordovaPlugin getPlugin(String service) {
+ PluginEntry entry = this.entries.get(service);
+ if (entry == null) {
+ return null;
+ }
+ CordovaPlugin plugin = entry.plugin;
+ if (plugin == null) {
+ plugin = entry.createPlugin(this.app, this.ctx);
+ }
+ return plugin;
+ }
+
+ /**
+ * Add a plugin class that implements a service to the service entry table.
+ * This does not create the plugin object instance.
+ *
+ * @param service The service name
+ * @param className The plugin class name
+ */
+ public void addService(String service, String className) {
+ PluginEntry entry = new PluginEntry(service, className, false);
+ this.addService(entry);
+ }
+
+ /**
+ * Add a plugin class that implements a service to the service entry table.
+ * This does not create the plugin object instance.
+ *
+ * @param entry The plugin entry
+ */
+ public void addService(PluginEntry entry) {
+ this.entries.put(entry.service, entry);
+ }
+
+ /**
+ * Called when the system is about to start resuming a previous activity.
+ *
+ * @param multitasking Flag indicating if multitasking is turned on for app
+ */
+ public void onPause(boolean multitasking) {
+ for (PluginEntry entry : this.entries.values()) {
+ if (entry.plugin != null) {
+ entry.plugin.onPause(multitasking);
+ }
+ }
+ }
+
+ /**
+ * Called when the activity will start interacting with the user.
+ *
+ * @param multitasking Flag indicating if multitasking is turned on for app
+ */
+ public void onResume(boolean multitasking) {
+ for (PluginEntry entry : this.entries.values()) {
+ if (entry.plugin != null) {
+ entry.plugin.onResume(multitasking);
+ }
+ }
+ }
+
+ /**
+ * The final call you receive before your activity is destroyed.
+ */
+ public void onDestroy() {
+ for (PluginEntry entry : this.entries.values()) {
+ if (entry.plugin != null) {
+ entry.plugin.onDestroy();
+ }
+ }
+ }
+
+ /**
+ * Send a message to all plugins.
+ *
+ * @param id The message id
+ * @param data The message data
+ * @return
+ */
+ public Object postMessage(String id, Object data) {
+ Object obj = this.ctx.onMessage(id, data);
+ if (obj != null) {
+ return obj;
+ }
+ for (PluginEntry entry : this.entries.values()) {
+ if (entry.plugin != null) {
+ obj = entry.plugin.onMessage(id, data);
+ if (obj != null) {
+ return obj;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Called when the activity receives a new intent.
+ */
+ public void onNewIntent(Intent intent) {
+ for (PluginEntry entry : this.entries.values()) {
+ if (entry.plugin != null) {
+ entry.plugin.onNewIntent(intent);
+ }
+ }
+ }
+
+ /**
+ * Called when the URL of the webview changes.
+ *
+ * @param url The URL that is being changed to.
+ * @return Return false to allow the URL to load, return true to prevent the URL from loading.
+ */
+ public boolean onOverrideUrlLoading(String url) {
+ Iterator<Entry<String, String>> it = this.urlMap.entrySet().iterator();
+ while (it.hasNext()) {
+ HashMap.Entry<String, String> pairs = it.next();
+ if (url.startsWith(pairs.getKey())) {
+ return this.getPlugin(pairs.getValue()).onOverrideUrlLoading(url);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called when the app navigates or refreshes.
+ */
+ public void onReset() {
+ Iterator<PluginEntry> it = this.entries.values().iterator();
+ while (it.hasNext()) {
+ CordovaPlugin plugin = it.next().plugin;
+ if (plugin != null) {
+ plugin.onReset();
+ }
+ }
+ }
+
+
+ private void pluginConfigurationMissing() {
+ LOG.e(TAG, "=====================================================================================");
+ LOG.e(TAG, "ERROR: config.xml is missing. Add res/xml/config.xml to your project.");
+ LOG.e(TAG, "https://git-wip-us.apache.org/repos/asf?p=incubator-cordova-android.git;a=blob;f=framework/res/xml/plugins.xml");
+ LOG.e(TAG, "=====================================================================================");
+ }
+
+ public Uri remapUri(Uri uri) {
+ for (PluginEntry entry : this.entries.values()) {
+ if (entry.plugin != null) {
+ Uri ret = entry.plugin.remapUri(uri);
+ if (ret != null) {
+ return ret;
+ }
+ }
+ }
+ return null;
+ }
+
+ private class PluginManagerService extends CordovaPlugin {
+ @Override
+ public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException {
+ if ("startup".equals(action)) {
+ // The onPageStarted event of CordovaWebViewClient resets the queue of messages to be returned to javascript in response
+ // to exec calls. Since this event occurs on the UI thread and exec calls happen on the WebCore thread it is possible
+ // that onPageStarted occurs after exec calls have started happening on a new page, which can cause the message queue
+ // to be reset between the queuing of a new message and its retrieval by javascript. To avoid this from happening,
+ // javascript always sends a "startup" exec upon loading a new page which causes all future exec calls to happen on the UI
+ // thread (and hence after onPageStarted) until there are no more pending exec calls remaining.
+ numPendingUiExecs.getAndIncrement();
+ ctx.getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ numPendingUiExecs.getAndDecrement();
+ }
+ });
+ return true;
+ }
+ return false;
+ }
}
}
http://git-wip-us.apache.org/repos/asf/cordova-android/blob/1dfbebf9/framework/src/org/apache/cordova/api/PluginResult.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/api/PluginResult.java b/framework/src/org/apache/cordova/api/PluginResult.java
index 39d3983..a642200 100755
--- a/framework/src/org/apache/cordova/api/PluginResult.java
+++ b/framework/src/org/apache/cordova/api/PluginResult.java
@@ -21,40 +21,159 @@ package org.apache.cordova.api;
import org.json.JSONArray;
import org.json.JSONObject;
-public class PluginResult extends org.apache.cordova.PluginResult {
+import android.util.Base64;
+
+public class PluginResult {
+ private final int status;
+ private final int messageType;
+ private boolean keepCallback = false;
+ private String strMessage;
+ private String encodedMessage;
+
public PluginResult(Status status) {
- super(status);
+ this(status, PluginResult.StatusMessages[status.ordinal()]);
}
public PluginResult(Status status, String message) {
- super(status, message);
+ this.status = status.ordinal();
+ this.messageType = message == null ? MESSAGE_TYPE_NULL : MESSAGE_TYPE_STRING;
+ this.strMessage = message;
}
public PluginResult(Status status, JSONArray message) {
- super(status, message);
+ this.status = status.ordinal();
+ this.messageType = MESSAGE_TYPE_JSON;
+ encodedMessage = message.toString();
}
public PluginResult(Status status, JSONObject message) {
- super(status, message);
+ this.status = status.ordinal();
+ this.messageType = MESSAGE_TYPE_JSON;
+ encodedMessage = message.toString();
}
public PluginResult(Status status, int i) {
- super(status, i);
+ this.status = status.ordinal();
+ this.messageType = MESSAGE_TYPE_NUMBER;
+ this.encodedMessage = ""+i;
}
public PluginResult(Status status, float f) {
- super(status, f);
+ this.status = status.ordinal();
+ this.messageType = MESSAGE_TYPE_NUMBER;
+ this.encodedMessage = ""+f;
}
public PluginResult(Status status, boolean b) {
- super(status, b);
+ this.status = status.ordinal();
+ this.messageType = MESSAGE_TYPE_BOOLEAN;
+ this.encodedMessage = Boolean.toString(b);
}
public PluginResult(Status status, byte[] data) {
- super(status, data);
+ this(status, data, false);
}
public PluginResult(Status status, byte[] data, boolean binaryString) {
- super(status, data, binaryString);
+ this.status = status.ordinal();
+ this.messageType = binaryString ? MESSAGE_TYPE_BINARYSTRING : MESSAGE_TYPE_ARRAYBUFFER;
+ this.encodedMessage = Base64.encodeToString(data, Base64.NO_WRAP);
+ }
+
+ public void setKeepCallback(boolean b) {
+ this.keepCallback = b;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+
+ public int getMessageType() {
+ return messageType;
+ }
+
+ public String getMessage() {
+ if (encodedMessage == null) {
+ encodedMessage = JSONObject.quote(strMessage);
+ }
+ return encodedMessage;
+ }
+
+ /**
+ * If messageType == MESSAGE_TYPE_STRING, then returns the message string.
+ * Otherwise, returns null.
+ */
+ public String getStrMessage() {
+ return strMessage;
+ }
+
+ public boolean getKeepCallback() {
+ return this.keepCallback;
+ }
+
+ @Deprecated // Use sendPluginResult instead of sendJavascript.
+ public String getJSONString() {
+ return "{\"status\":" + this.status + ",\"message\":" + this.getMessage() + ",\"keepCallback\":" + this.keepCallback + "}";
+ }
+
+ @Deprecated // Use sendPluginResult instead of sendJavascript.
+ public String toCallbackString(String callbackId) {
+ // If no result to be sent and keeping callback, then no need to sent back to JavaScript
+ if ((status == PluginResult.Status.NO_RESULT.ordinal()) && keepCallback) {
+ return null;
+ }
+
+ // Check the success (OK, NO_RESULT & !KEEP_CALLBACK)
+ if ((status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal())) {
+ return toSuccessCallbackString(callbackId);
+ }
+
+ return toErrorCallbackString(callbackId);
+ }
+
+ @Deprecated // Use sendPluginResult instead of sendJavascript.
+ public String toSuccessCallbackString(String callbackId) {
+ return "cordova.callbackSuccess('"+callbackId+"',"+this.getJSONString()+");";
+ }
+
+ @Deprecated // Use sendPluginResult instead of sendJavascript.
+ public String toErrorCallbackString(String callbackId) {
+ return "cordova.callbackError('"+callbackId+"', " + this.getJSONString()+ ");";
+ }
+
+ public static final int MESSAGE_TYPE_STRING = 1;
+ public static final int MESSAGE_TYPE_JSON = 2;
+ public static final int MESSAGE_TYPE_NUMBER = 3;
+ public static final int MESSAGE_TYPE_BOOLEAN = 4;
+ public static final int MESSAGE_TYPE_NULL = 5;
+ public static final int MESSAGE_TYPE_ARRAYBUFFER = 6;
+ // Use BINARYSTRING when your string may contain null characters.
+ // This is required to work around a bug in the platform :(.
+ public static final int MESSAGE_TYPE_BINARYSTRING = 7;
+
+ public static String[] StatusMessages = new String[] {
+ "No result",
+ "OK",
+ "Class not found",
+ "Illegal access",
+ "Instantiation error",
+ "Malformed url",
+ "IO error",
+ "Invalid action",
+ "JSON error",
+ "Error"
+ };
+
+ public enum Status {
+ NO_RESULT,
+ OK,
+ CLASS_NOT_FOUND_EXCEPTION,
+ ILLEGAL_ACCESS_EXCEPTION,
+ INSTANTIATION_EXCEPTION,
+ MALFORMED_URL_EXCEPTION,
+ IO_EXCEPTION,
+ INVALID_ACTION,
+ JSON_EXCEPTION,
+ ERROR
}
}