| /* |
| 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.io.IOException; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map.Entry; |
| |
| import org.apache.cordova.CordovaWebView; |
| import org.json.JSONException; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import android.content.Intent; |
| import android.content.res.XmlResourceParser; |
| |
| import android.util.Log; |
| import android.webkit.WebResourceResponse; |
| |
| /** |
| * 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; |
| |
| // 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 int MAX_REPITIONS = 1000; |
| |
| /** |
| * Constructor. |
| * |
| * @param app |
| * @param ctx |
| */ |
| public PluginManager(CordovaWebView app, CordovaInterface ctx) { |
| this.ctx = ctx; |
| this.app = app; |
| this.firstRun = true; |
| } |
| |
| /** |
| * Init when loading a new HTML page into webview. |
| */ |
| public void init() { |
| LOG.d(TAG, "init()"); |
| |
| // If first time, then load plugins from plugins.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(); |
| } |
| |
| // Start up all plugins that have onload specified |
| this.startupPlugins(); |
| } |
| |
| /** |
| * Load plugins from res/xml/plugins.xml |
| */ |
| public void loadPlugins() { |
| int id = this.ctx.getActivity().getResources().getIdentifier("config", "xml", this.ctx.getActivity().getPackageName()); |
| if(id == 0) |
| { |
| id = this.ctx.getActivity().getResources().getIdentifier("plugins", "xml", this.ctx.getActivity().getPackageName()); |
| LOG.i(TAG, "Using plugins.xml instead of config.xml. plugins.xml will eventually be deprecated"); |
| } |
| 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. |
| * @return Whether the task completed synchronously. |
| */ |
| public boolean exec(String service, String action, String callbackId, String rawArgs) { |
| CordovaPlugin plugin = this.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 true; |
| } |
| 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); |
| return true; |
| } |
| return callbackContext.isFinished(); |
| } catch (JSONException e) { |
| PluginResult cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION); |
| app.sendPluginResult(cr, callbackId); |
| return true; |
| } |
| } |
| |
| @Deprecated |
| public boolean exec(String service, String action, String callbackId, String jsonArgs, boolean async) { |
| return 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 WebView is loading any resource, top-level or not. |
| * |
| * Uses the same url-filter tag as onOverrideUrlLoading. |
| * |
| * @param url The URL of the resource to be loaded. |
| * @return Return a WebResourceResponse with the resource, or null if the WebView should handle it. |
| */ |
| public WebResourceResponse shouldInterceptRequest(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()).shouldInterceptRequest(url); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * 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: plugin.xml is missing. Add res/xml/plugins.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, "====================================================================================="); |
| } |
| |
| /** |
| * Called when the any resource is going to be loaded - either from the webview, files or any other resource |
| * |
| * |
| * @param dataResource The resource request to be loaded. |
| * @param dataResourceContext The context of the dataResource request |
| * @return Return the resource request that will be loaded. The returned request may be modified or unchanged. |
| */ |
| public DataResource handleDataResourceRequestWithPlugins(DataResource dataResource, DataResourceContext dataResourceContext){ |
| int repetitions = 0; |
| boolean requestModified = true; |
| while(requestModified && repetitions < MAX_REPITIONS) { |
| requestModified = false; |
| repetitions ++; |
| for (PluginEntry entry : this.entries.values()) { |
| if (entry.plugin != null) { |
| DataResource ret = entry.plugin.handleDataResourceRequest(dataResource, dataResourceContext); |
| if(ret != null) { |
| dataResource = ret; |
| requestModified = true; |
| break; |
| } |
| } |
| } |
| } |
| return dataResource; |
| } |
| } |