[CB-352] Support initializing DroidGap with existing WebView, WebViewClient and webViewChrome.

[CB-353] Create PluginEntry object to use by PluginManager.
This commit is contained in:
Bryce Curtis 2012-03-19 16:20:57 -05:00
parent 04b3e4d847
commit 7e70d76232
4 changed files with 425 additions and 286 deletions

View File

@ -64,7 +64,7 @@ public class CordovaWebViewClient extends WebViewClient {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// First give any plugins the chance to handle the url themselves
if (this.ctx.pluginManager.onOverrideUrlLoading(url)) {
if ((this.ctx.pluginManager != null) && this.ctx.pluginManager.onOverrideUrlLoading(url)) {
}
// If dialing phone (tel:5551212)

View File

@ -56,6 +56,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebSettings.LayoutAlgorithm;
import android.webkit.WebView;
@ -345,13 +346,24 @@ public class DroidGap extends Activity implements CordovaInterface {
}
/**
* Create and initialize web container.
* Create and initialize web container with default web view objects.
*/
public void init() {
this.init(new WebView(DroidGap.this), new CordovaWebViewClient(this), new CordovaChromeClient(DroidGap.this));
}
/**
* Initialize web container with web view objects.
*
* @param webView
* @param webViewClient
* @param webChromeClient
*/
public void init(WebView webView, WebViewClient webViewClient, WebChromeClient webChromeClient) {
LOG.d(TAG, "DroidGap.init()");
// Create web container
this.appView = new WebView(DroidGap.this);
// Set up web container
this.appView = webView;
this.appView.setId(100);
this.appView.setLayoutParams(new LinearLayout.LayoutParams(
@ -359,8 +371,8 @@ public class DroidGap extends Activity implements CordovaInterface {
ViewGroup.LayoutParams.FILL_PARENT,
1.0F));
this.appView.setWebChromeClient(new CordovaChromeClient(DroidGap.this));
this.setWebViewClient(this.appView, new CordovaWebViewClient(this));
this.appView.setWebChromeClient(webChromeClient);
this.setWebViewClient(this.appView, webViewClient);
this.appView.setInitialScale(0);
this.appView.setVerticalScrollBarEnabled(false);
@ -393,6 +405,9 @@ public class DroidGap extends Activity implements CordovaInterface {
// Clear cancel flag
this.cancelLoadUrl = false;
// Create plugin manager
this.pluginManager = new PluginManager(this.appView, this);
}
/**
@ -498,12 +513,7 @@ public class DroidGap extends Activity implements CordovaInterface {
else {
me.callbackServer.reinit(url);
}
if (me.pluginManager == null) {
me.pluginManager = new PluginManager(me.appView, me);
}
else {
me.pluginManager.reinit();
}
me.pluginManager.init();
// If loadingDialog property, then show the App loading dialog for first page of app
String loading = null;
@ -834,7 +844,9 @@ public class DroidGap extends Activity implements CordovaInterface {
this.appView.loadUrl("javascript:try{cordova.require('cordova/channel').onPause.fire();}catch(e){console.log('exception firing pause event from native');};");
// Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onPause(this.keepRunning);
}
// If app doesn't want to run in background
if (!this.keepRunning) {
@ -852,8 +864,10 @@ public class DroidGap extends Activity implements CordovaInterface {
super.onNewIntent(intent);
//Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onNewIntent(intent);
}
}
@Override
/**
@ -875,7 +889,9 @@ public class DroidGap extends Activity implements CordovaInterface {
this.appView.loadUrl("javascript:try{cordova.require('cordova/channel').onResume.fire();}catch(e){console.log('exception firing resume event from native');};");
// Forward to plugins
if (this.pluginManager != null) {
this.pluginManager.onResume(this.keepRunning || this.activityResultKeepRunning);
}
// If app doesn't want to run in background
if (!this.keepRunning || this.activityResultKeepRunning) {
@ -942,8 +958,10 @@ public class DroidGap extends Activity implements CordovaInterface {
*/
@Deprecated
public void addService(String serviceType, String className) {
if (this.pluginManager != null) {
this.pluginManager.addService(serviceType, className);
}
}
/**
* Send JavaScript statement back to JavaScript.

View File

@ -0,0 +1,119 @@
/*
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 android.webkit.WebView;
/**
* This class represents a service entry object.
*/
public class PluginEntry {
/**
* The name of the service that this plugin implements
*/
public String service = "";
/**
* The plugin class name that implements the service.
*/
public String pluginClass = "";
/**
* The plugin object.
* Plugin objects are only created when they are called from JavaScript. (see PluginManager.exec)
* The exception is if the onload flag is set, then they are created when PluginManager is initialized.
*/
public IPlugin plugin = null;
/**
* Flag that indicates the plugin object should be created when PluginManager is initialized.
*/
public boolean onload = false;
/**
* Constructor
*
* @param service The name of the service
* @param pluginClass The plugin class name
* @param onload Create plugin object when HTML page is loaded
*/
public PluginEntry(String service, String pluginClass, boolean onload) {
this.service = service;
this.pluginClass = pluginClass;
this.onload = onload;
}
/**
* Create plugin object.
* If plugin is already created, then just return it.
*
* @return The plugin object
*/
@SuppressWarnings("unchecked")
public IPlugin createPlugin(WebView webView, CordovaInterface ctx) {
if (this.plugin != null) {
return this.plugin;
}
try {
Class c = getClassByName(this.pluginClass);
if (isCordovaPlugin(c)) {
this.plugin = (IPlugin) c.newInstance();
this.plugin.setContext(ctx);
this.plugin.setView(webView);
return plugin;
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("Error adding plugin " + this.pluginClass + ".");
}
return null;
}
/**
* Get the class.
*
* @param clazz
* @return
* @throws ClassNotFoundException
*/
@SuppressWarnings("unchecked")
private Class getClassByName(final String clazz) throws ClassNotFoundException {
Class c = null;
if (clazz != null) {
c = Class.forName(clazz);
}
return c;
}
/**
* Get the interfaces that a class implements and see if it implements the
* org.apache.cordova.api.Plugin interface.
*
* @param c The class to check the interfaces of.
* @return Boolean indicating if the class implements org.apache.cordova.api.Plugin
*/
@SuppressWarnings("unchecked")
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;
}
}

View File

@ -15,7 +15,7 @@
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;
@ -29,7 +29,6 @@ import org.xmlpull.v1.XmlPullParserException;
import android.content.Intent;
import android.content.res.XmlResourceParser;
import android.util.Log;
import android.webkit.WebView;
/**
@ -39,16 +38,20 @@ import android.webkit.WebView;
* from JavaScript.
*/
public class PluginManager {
private static String TAG = "PluginManager";
private HashMap<String, IPlugin> plugins = new HashMap<String,IPlugin>();
private HashMap<String, String> services = new HashMap<String,String>();
// List of service entries
private final HashMap<String, PluginEntry> entries = new HashMap<String, PluginEntry>();
private final CordovaInterface ctx;
private final WebView 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>();
protected HashMap<String, String> urlMap = new HashMap<String, String>();
/**
* Constructor.
@ -59,18 +62,30 @@ public class PluginManager {
public PluginManager(WebView app, CordovaInterface ctx) {
this.ctx = ctx;
this.app = app;
this.loadPlugins();
this.firstRun = true;
}
/**
* Re-init when loading a new HTML page into webview.
* Init when loading a new HTML page into webview.
*/
public void reinit() {
public void init() {
LOG.d(TAG, "init()");
// Stop plugins on current HTML page and discard
// If first time, then load plugins from plugins.xml file
if (firstRun) {
this.loadPlugins();
firstRun = false;
}
// Stop plugins on current HTML page and discard plugin objects
else {
this.onPause(false);
this.onDestroy();
this.plugins = new HashMap<String, IPlugin>();
this.clearPluginObjects();
}
// Start up all plugins that have onload specified
this.startupPlugins();
}
/**
@ -78,25 +93,26 @@ public class PluginManager {
*/
public void loadPlugins() {
int id = ctx.getResources().getIdentifier("plugins", "xml", ctx.getPackageName());
if (id == 0) { pluginConfigurationMissing(); }
if (id == 0) {
pluginConfigurationMissing();
}
XmlResourceParser xml = ctx.getResources().getXml(id);
int eventType = -1;
String pluginClass = "", pluginName = "";
String service = "", pluginClass = "";
boolean onload = false;
PluginEntry entry = null;
while (eventType != XmlResourceParser.END_DOCUMENT) {
if (eventType == XmlResourceParser.START_TAG) {
String strNode = xml.getName();
if (strNode.equals("plugin")) {
service = xml.getAttributeValue(null, "name");
pluginClass = xml.getAttributeValue(null, "value");
pluginName = xml.getAttributeValue(null, "name");
//System.out.println("Plugin: "+name+" => "+value);
this.addService(pluginName, pluginClass);
// Create plugin at load time if attribute "onload"
if ("true".equals(xml.getAttributeValue(null, "onload"))) {
this.getPlugin(pluginName);
}
// System.out.println("Plugin: "+name+" => "+value);
onload = "true".equals(xml.getAttributeValue(null, "onload"));
entry = new PluginEntry(service, pluginClass, onload);
this.addService(entry);
} else if (strNode.equals("url-filter")) {
this.urlMap.put(xml.getAttributeValue(null, "value"), pluginName);
this.urlMap.put(xml.getAttributeValue(null, "value"), service);
}
}
try {
@ -109,6 +125,26 @@ public class PluginManager {
}
}
/**
* 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.
@ -182,7 +218,7 @@ public class PluginManager {
}
}
} catch (JSONException e) {
System.out.println("ERROR: "+e.toString());
System.out.println("ERROR: " + e.toString());
cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
}
// if async we have already returned at this point unless there was an error...
@ -192,93 +228,49 @@ public class PluginManager {
}
ctx.sendJavascript(cr.toErrorCallbackString(callbackId));
}
return ( cr != null ? cr.getJSONString() : "{ status: 0, message: 'all good' }" );
return (cr != null ? cr.getJSONString() : "{ status: 0, message: 'all good' }");
}
/**
* Get the class.
* 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 clazz
* @return
* @throws ClassNotFoundException
* @param service The name of the service.
* @return IPlugin or null
*/
@SuppressWarnings("unchecked")
private Class getClassByName(final String clazz) throws ClassNotFoundException {
Class c = null;
if (clazz != null) {
c = Class.forName(clazz);
}
return c;
}
/**
* Get the interfaces that a class implements and see if it implements the
* org.apache.cordova.api.Plugin interface.
*
* @param c The class to check the interfaces of.
* @return Boolean indicating if the class implements org.apache.cordova.api.Plugin
*/
@SuppressWarnings("unchecked")
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. This creates an instance of the plugin.
* If plugin is already created, then just return it.
*
* @param className The class to load
* @param clazz The class object (must be a class object of the className)
* @param callbackId The callback id to use when calling back into JavaScript
* @return The plugin
*/
@SuppressWarnings("unchecked")
private IPlugin addPlugin(String pluginName, String className) {
try {
Class c = getClassByName(className);
if (isCordovaPlugin(c)) {
IPlugin plugin = (IPlugin)c.newInstance();
this.plugins.put(className, plugin);
plugin.setContext(this.ctx);
plugin.setView(this.app);
return plugin;
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("Error adding plugin "+className+".");
}
private IPlugin getPlugin(String service) {
PluginEntry entry = entries.get(service);
if (entry == null) {
return null;
}
/**
* Get the loaded plugin.
*
* If the plugin is not already loaded then load it.
*
* @param className The class of the loaded plugin.
* @return
*/
private IPlugin getPlugin(String pluginName) {
String className = this.services.get(pluginName);
if (this.plugins.containsKey(className)) {
return this.plugins.get(className);
} else {
return this.addPlugin(pluginName, className);
IPlugin plugin = entry.plugin;
if (plugin == null) {
plugin = entry.createPlugin(this.app, this.ctx);
}
return plugin;
}
/**
* Add a class that implements a service.
* This does not create the class instance. It just maps service name to class name.
* Add a plugin class that implements a service to the service entry table.
* This does not create the plugin object instance.
*
* @param serviceType
* @param className
* @param service The service name
* @param className The plugin class name
*/
public void addService(String serviceType, String className) {
this.services.put(serviceType, className);
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);
}
/**
@ -287,8 +279,10 @@ public class PluginManager {
* @param multitasking Flag indicating if multitasking is turned on for app
*/
public void onPause(boolean multitasking) {
for (IPlugin plugin : this.plugins.values()) {
plugin.onPause(multitasking);
for (PluginEntry entry : this.entries.values()) {
if (entry.plugin != null) {
entry.plugin.onPause(multitasking);
}
}
}
@ -298,8 +292,10 @@ public class PluginManager {
* @param multitasking Flag indicating if multitasking is turned on for app
*/
public void onResume(boolean multitasking) {
for (IPlugin plugin : this.plugins.values()) {
plugin.onResume(multitasking);
for (PluginEntry entry : this.entries.values()) {
if (entry.plugin != null) {
entry.plugin.onResume(multitasking);
}
}
}
@ -307,8 +303,10 @@ public class PluginManager {
* The final call you receive before your activity is destroyed.
*/
public void onDestroy() {
for (IPlugin plugin : this.plugins.values()) {
plugin.onDestroy();
for (PluginEntry entry : this.entries.values()) {
if (entry.plugin != null) {
entry.plugin.onDestroy();
}
}
}
@ -319,8 +317,10 @@ public class PluginManager {
* @param data The message data
*/
public void postMessage(String id, Object data) {
for (IPlugin plugin : this.plugins.values()) {
plugin.onMessage(id, data);
for (PluginEntry entry : this.entries.values()) {
if (entry.plugin != null) {
entry.plugin.onMessage(id, data);
}
}
}
@ -328,8 +328,10 @@ public class PluginManager {
* Called when the activity receives a new intent.
*/
public void onNewIntent(Intent intent) {
for (IPlugin plugin : this.plugins.values()) {
plugin.onNewIntent(intent);
for (PluginEntry entry : this.entries.values()) {
if (entry.plugin != null) {
entry.plugin.onNewIntent(intent);
}
}
}