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
     }
 }