Return-Path: X-Original-To: apmail-cordova-commits-archive@www.apache.org Delivered-To: apmail-cordova-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 89474104EF for ; Tue, 22 Oct 2013 20:47:08 +0000 (UTC) Received: (qmail 94475 invoked by uid 500); 22 Oct 2013 20:43:39 -0000 Delivered-To: apmail-cordova-commits-archive@cordova.apache.org Received: (qmail 94380 invoked by uid 500); 22 Oct 2013 20:43:34 -0000 Mailing-List: contact commits-help@cordova.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cordova.apache.org Delivered-To: mailing list commits@cordova.apache.org Received: (qmail 94271 invoked by uid 99); 22 Oct 2013 20:43:21 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 22 Oct 2013 20:43:21 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 6320B543A1; Tue, 22 Oct 2013 20:43:20 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: agrieve@apache.org To: commits@cordova.apache.org Date: Tue, 22 Oct 2013 20:43:26 -0000 Message-Id: <719206eccd264feb8a0a8fae92b4aa0b@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [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 entries = new HashMap(); + + 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 urlMap = new HashMap(); + + 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, " tags are deprecated, please use instead. 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> it = this.urlMap.entrySet().iterator(); + while (it.hasNext()) { + HashMap.Entry 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 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 } }