From 3bab41f138d57a7a92ab01586dd8f58bc91dddfd Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Fri, 4 Jul 2014 11:31:32 -0400 Subject: [PATCH] Refactor Config into ConfigXmlParser, CordovaPreferences Intention here is to be 100% backwards compatible. --- framework/src/org/apache/cordova/Config.java | 189 ++---------------- .../org/apache/cordova/ConfigXmlParser.java | 163 +++++++++++++++ .../apache/cordova/CordovaPreferences.java | 158 +++++++++++++++ .../src/org/apache/cordova/PluginManager.java | 100 +-------- .../src/org/apache/cordova/Whitelist.java | 4 + 5 files changed, 351 insertions(+), 263 deletions(-) create mode 100644 framework/src/org/apache/cordova/ConfigXmlParser.java create mode 100644 framework/src/org/apache/cordova/CordovaPreferences.java diff --git a/framework/src/org/apache/cordova/Config.java b/framework/src/org/apache/cordova/Config.java index 5da08566..7865e004 100644 --- a/framework/src/org/apache/cordova/Config.java +++ b/framework/src/org/apache/cordova/Config.java @@ -19,184 +19,31 @@ package org.apache.cordova; -import java.io.IOException; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.cordova.LOG; -import org.xmlpull.v1.XmlPullParserException; - import android.app.Activity; -import android.content.res.XmlResourceParser; -import android.graphics.Color; import android.util.Log; public class Config { + private static final String TAG = "Config"; - public static final String TAG = "Config"; + private static ConfigXmlParser parser; - private Whitelist whitelist = new Whitelist(); - private String startUrl; - - private static String errorUrl; - - private static Config self = null; + private Config() { + } public static void init(Activity action) { - //Just re-initialize this! Seriously, we lose this all the time - self = new Config(action); + parser = new ConfigXmlParser(); + parser.parse(action); + parser.getPreferences().setPreferencesBundle(action.getIntent().getExtras()); + parser.getPreferences().copyIntoIntentExtras(action); } // Intended to be used for testing only; creates an empty configuration. public static void init() { - if (self == null) { - self = new Config(); + if (parser == null) { + parser = new ConfigXmlParser(); } } - - // Intended to be used for testing only; creates an empty configuration. - private Config() { - } - - private Config(Activity action) { - if (action == null) { - LOG.i("CordovaLog", "There is no activity. Is this on the lock screen?"); - return; - } - - // First checking the class namespace for config.xml - int id = action.getResources().getIdentifier("config", "xml", action.getClass().getPackage().getName()); - if (id == 0) { - // If we couldn't find config.xml there, we'll look in the namespace from AndroidManifest.xml - id = action.getResources().getIdentifier("config", "xml", action.getPackageName()); - if (id == 0) { - LOG.i("CordovaLog", "config.xml missing. Ignoring..."); - return; - } - } - - // Add implicitly allowed URLs - whitelist.addWhiteListEntry("file:///*", false); - whitelist.addWhiteListEntry("content:///*", false); - whitelist.addWhiteListEntry("data:*", false); - - XmlResourceParser xml = action.getResources().getXml(id); - int eventType = -1; - while (eventType != XmlResourceParser.END_DOCUMENT) { - if (eventType == XmlResourceParser.START_TAG) { - String strNode = xml.getName(); - - if (strNode.equals("access")) { - String origin = xml.getAttributeValue(null, "origin"); - String subdomains = xml.getAttributeValue(null, "subdomains"); - if (origin != null) { - whitelist.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0)); - } - } - else if (strNode.equals("log")) { - String level = xml.getAttributeValue(null, "level"); - Log.d(TAG, "The tag is deprecated. Use instead."); - if (level != null) { - LOG.setLogLevel(level); - } - } - else if (strNode.equals("preference")) { - String name = xml.getAttributeValue(null, "name").toLowerCase(Locale.getDefault()); - /* Java 1.6 does not support switch-based strings - Java 7 does, but we're using Dalvik, which is apparently not Java. - Since we're reading XML, this has to be an ugly if/else. - - Also, due to cast issues, each of them has to call their separate putExtra! - Wheee!!! Isn't Java FUN!?!?!? - - Note: We should probably pass in the classname for the variable splash on splashscreen! - */ - if (name.equalsIgnoreCase("LogLevel")) { - String level = xml.getAttributeValue(null, "value"); - LOG.setLogLevel(level); - } else if (name.equalsIgnoreCase("SplashScreen")) { - String value = xml.getAttributeValue(null, "value"); - int resource = 0; - if (value == null) - { - value = "splash"; - } - resource = action.getResources().getIdentifier(value, "drawable", action.getClass().getPackage().getName()); - - action.getIntent().putExtra(name, resource); - } - else if(name.equalsIgnoreCase("BackgroundColor")) { - int value = xml.getAttributeIntValue(null, "value", Color.BLACK); - action.getIntent().putExtra(name, value); - } - else if(name.equalsIgnoreCase("LoadUrlTimeoutValue")) { - int value = xml.getAttributeIntValue(null, "value", 20000); - action.getIntent().putExtra(name, value); - } - else if(name.equalsIgnoreCase("SplashScreenDelay")) { - int value = xml.getAttributeIntValue(null, "value", 3000); - action.getIntent().putExtra(name, value); - } - else if(name.equalsIgnoreCase("KeepRunning")) - { - boolean value = xml.getAttributeValue(null, "value").equals("true"); - action.getIntent().putExtra(name, value); - } - else if(name.equalsIgnoreCase("InAppBrowserStorageEnabled")) - { - boolean value = xml.getAttributeValue(null, "value").equals("true"); - action.getIntent().putExtra(name, value); - } - else if(name.equalsIgnoreCase("DisallowOverscroll")) - { - boolean value = xml.getAttributeValue(null, "value").equals("true"); - action.getIntent().putExtra(name, value); - } - else if(name.equalsIgnoreCase("errorurl")) - { - errorUrl = xml.getAttributeValue(null, "value"); - } - else - { - String value = xml.getAttributeValue(null, "value"); - action.getIntent().putExtra(name, value); - } - /* - LOG.i("CordovaLog", "Found preference for %s=%s", name, value); - */ - } - else if (strNode.equals("content")) { - String src = xml.getAttributeValue(null, "src"); - - LOG.i("CordovaLog", "Found start page location: %s", src); - - if (src != null) { - Pattern schemeRegex = Pattern.compile("^[a-z-]+://"); - Matcher matcher = schemeRegex.matcher(src); - if (matcher.find()) { - startUrl = src; - } else { - if (src.charAt(0) == '/') { - src = src.substring(1); - } - startUrl = "file:///android_asset/www/" + src; - } - } - } - - } - - try { - eventType = xml.next(); - } catch (XmlPullParserException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - + /** * Add entry to approved list of URLs (whitelist) * @@ -204,11 +51,11 @@ public class Config { * @param subdomains T=include all subdomains under origin */ public static void addWhiteListEntry(String origin, boolean subdomains) { - if (self == null) { + if (parser == null) { Log.e(TAG, "Config was not initialised. Did you forget to Config.init(this)?"); return; } - self.whitelist.addWhiteListEntry(origin, subdomains); + parser.getWhitelist().addWhiteListEntry(origin, subdomains); } /** @@ -218,21 +65,21 @@ public class Config { * @return true if whitelisted */ public static boolean isUrlWhiteListed(String url) { - if (self == null) { + if (parser == null) { Log.e(TAG, "Config was not initialised. Did you forget to Config.init(this)?"); return false; } - return self.whitelist.isUrlWhiteListed(url); + return parser.getWhitelist().isUrlWhiteListed(url); } public static String getStartUrl() { - if (self == null || self.startUrl == null) { + if (parser == null) { return "file:///android_asset/www/index.html"; } - return self.startUrl; + return parser.getLaunchUrl(); } public static String getErrorUrl() { - return errorUrl; + return parser.getPreferences().getString("errorurl", null); } } diff --git a/framework/src/org/apache/cordova/ConfigXmlParser.java b/framework/src/org/apache/cordova/ConfigXmlParser.java new file mode 100644 index 00000000..8062168a --- /dev/null +++ b/framework/src/org/apache/cordova/ConfigXmlParser.java @@ -0,0 +1,163 @@ +/* + 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; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.cordova.LOG; +import org.xmlpull.v1.XmlPullParserException; + +import android.app.Activity; +import android.content.res.XmlResourceParser; +import android.util.Log; + +public class ConfigXmlParser { + private static String TAG = "ConfigXmlParser"; + + private String launchUrl = "file:///android_asset/www/index.html"; + private CordovaPreferences prefs = new CordovaPreferences(); + private Whitelist whitelist = new Whitelist(); + private ArrayList pluginEntries = new ArrayList(20); + private HashMap> urlMap = new HashMap>(); + + public Whitelist getWhitelist() { + return whitelist; + } + + public CordovaPreferences getPreferences() { + return prefs; + } + + public ArrayList getPluginEntries() { + return pluginEntries; + } + + public String getLaunchUrl() { + return launchUrl; + } + + public HashMap> getPluginUrlMap() { + return urlMap; + } + + public void parse(Activity action) { + // First checking the class namespace for config.xml + int id = action.getResources().getIdentifier("config", "xml", action.getClass().getPackage().getName()); + if (id == 0) { + // If we couldn't find config.xml there, we'll look in the namespace from AndroidManifest.xml + id = action.getResources().getIdentifier("config", "xml", action.getPackageName()); + if (id == 0) { + LOG.e(TAG, "res/xml/config.xml is missing!"); + return; + } + } + parse(action.getResources().getXml(id)); + } + + public void parse(XmlResourceParser xml) { + 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(); + if (strNode.equals("url-filter")) { + Log.w(TAG, "Plugin " + service + " is using deprecated tag "); + if (urlMap.get(service) == null) { + urlMap.put(service, new ArrayList(2)); + } + List filters = urlMap.get(service); + filters.add(xml.getAttributeValue(null, "value")); + } 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 (strNode.equals("access")) { + String origin = xml.getAttributeValue(null, "origin"); + String subdomains = xml.getAttributeValue(null, "subdomains"); + if (origin != null) { + whitelist.addWhiteListEntry(origin, (subdomains != null) && (subdomains.compareToIgnoreCase("true") == 0)); + } + } + else if (strNode.equals("preference")) { + String name = xml.getAttributeValue(null, "name").toLowerCase(Locale.ENGLISH); + String value = xml.getAttributeValue(null, "value"); + prefs.set(name, value); + } + else if (strNode.equals("content")) { + String src = xml.getAttributeValue(null, "src"); + if (src != null) { + setStartUrl(src); + } + } + } + else if (eventType == XmlResourceParser.END_TAG) + { + String strNode = xml.getName(); + if (strNode.equals("feature")) { + pluginEntries.add(new PluginEntry(service, pluginClass, onload)); + + service = ""; + pluginClass = ""; + insideFeature = false; + onload = false; + } + } + try { + eventType = xml.next(); + } catch (XmlPullParserException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private void setStartUrl(String src) { + Pattern schemeRegex = Pattern.compile("^[a-z-]+://"); + Matcher matcher = schemeRegex.matcher(src); + if (matcher.find()) { + launchUrl = src; + } else { + if (src.charAt(0) == '/') { + src = src.substring(1); + } + launchUrl = "file:///android_asset/www/" + src; + } + } +} diff --git a/framework/src/org/apache/cordova/CordovaPreferences.java b/framework/src/org/apache/cordova/CordovaPreferences.java new file mode 100644 index 00000000..9fa1898f --- /dev/null +++ b/framework/src/org/apache/cordova/CordovaPreferences.java @@ -0,0 +1,158 @@ +/* + 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; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.apache.cordova.LOG; + +import android.app.Activity; +import android.os.Bundle; + +public class CordovaPreferences { + private HashMap prefs = new HashMap(20); + private Bundle preferencesBundleExtras; + + public void setPreferencesBundle(Bundle extras) { + preferencesBundleExtras = extras; + } + + public void set(String name, String value) { + prefs.put(name.toLowerCase(Locale.ENGLISH), value); + } + + public Map getAll() { + return prefs; + } + + public boolean getBoolean(String name, boolean defaultValue) { + name = name.toLowerCase(Locale.ENGLISH); + String value = prefs.get(name); + if (value != null) { + return "true".equals(value); + } else if (preferencesBundleExtras != null) { + Object bundleValue = preferencesBundleExtras.get(name); + if (bundleValue instanceof String) { + return "true".equals(bundleValue); + } + // Gives a nice warning if type is wrong. + return preferencesBundleExtras.getBoolean(name, defaultValue); + } + return defaultValue; + } + + public int getInteger(String name, int defaultValue) { + name = name.toLowerCase(Locale.ENGLISH); + String value = prefs.get(name); + if (value != null) { + return Integer.valueOf(value); + } else if (preferencesBundleExtras != null) { + Object bundleValue = preferencesBundleExtras.get(name); + if (bundleValue instanceof String) { + return Integer.valueOf((String)bundleValue); + } + // Gives a nice warning if type is wrong. + return preferencesBundleExtras.getInt(name, defaultValue); + } + return defaultValue; + } + + public double getDouble(String name, double defaultValue) { + name = name.toLowerCase(Locale.ENGLISH); + String value = prefs.get(name); + if (value != null) { + return Double.valueOf(value); + } else if (preferencesBundleExtras != null) { + Object bundleValue = preferencesBundleExtras.get(name); + if (bundleValue instanceof String) { + return Double.valueOf((String)bundleValue); + } + // Gives a nice warning if type is wrong. + return preferencesBundleExtras.getDouble(name, defaultValue); + } + return defaultValue; + } + + public String getString(String name, String defaultValue) { + name = name.toLowerCase(Locale.ENGLISH); + String value = prefs.get(name); + if (value != null) { + return value; + } else if (preferencesBundleExtras != null && !"errorurl".equals(name)) { + Object bundleValue = preferencesBundleExtras.get(name); + if (bundleValue != null) { + return bundleValue.toString(); + } + } + return defaultValue; + } + + // Plugins should not rely on values within the intent since this does not work + // for apps with multiple webviews. Instead, they should retrieve prefs from the + // Config object associated with their webview. + public void copyIntoIntentExtras(Activity action) { + for (String name : prefs.keySet()) { + String value = prefs.get(name); + if (value == null) { + continue; + } + if (name.equals("loglevel")) { + LOG.setLogLevel(value); + } else if (name.equals("splashscreen")) { + // Note: We should probably pass in the classname for the variable splash on splashscreen! + int resource = action.getResources().getIdentifier(value, "drawable", action.getClass().getPackage().getName()); + action.getIntent().putExtra(name, resource); + } + else if(name.equals("backgroundcolor")) { + int asInt = Integer.valueOf(value); + action.getIntent().putExtra(name, asInt); + } + else if(name.equals("loadurltimeoutvalue")) { + int asInt = Integer.valueOf(value); + action.getIntent().putExtra(name, asInt); + } + else if(name.equals("splashscreendelay")) { + int asInt = Integer.valueOf(value); + action.getIntent().putExtra(name, asInt); + } + else if(name.equals("keeprunning")) + { + boolean asBool = "true".equals(value); + action.getIntent().putExtra(name, asBool); + } + else if(name.equals("inappbrowserstorageenabled")) + { + boolean asBool = "true".equals(value); + action.getIntent().putExtra(name, asBool); + } + else if(name.equals("disallowoverscroll")) + { + boolean asBool = "true".equals(value); + action.getIntent().putExtra(name, asBool); + } + else + { + action.getIntent().putExtra(name, value); + } + } + } +} diff --git a/framework/src/org/apache/cordova/PluginManager.java b/framework/src/org/apache/cordova/PluginManager.java index 02536ba3..c5ffd6cd 100755 --- a/framework/src/org/apache/cordova/PluginManager.java +++ b/framework/src/org/apache/cordova/PluginManager.java @@ -18,8 +18,6 @@ */ package org.apache.cordova; -import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -31,11 +29,8 @@ import org.apache.cordova.CordovaPlugin; import org.apache.cordova.PluginEntry; import org.apache.cordova.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.os.Debug; import android.util.Log; @@ -56,23 +51,13 @@ public class PluginManager { private final CordovaInterface ctx; private final CordovaWebView app; - // Flag to track first time through - private boolean firstRun; - // Stores mapping of Plugin Name -> values. // Using is deprecated. - protected HashMap> urlMap = new HashMap>(); + protected HashMap> urlMap; - /** - * Constructor. - * - * @param app - * @param ctx - */ public PluginManager(CordovaWebView app, CordovaInterface ctx) { this.ctx = ctx; this.app = app; - this.firstRun = true; } /** @@ -82,9 +67,8 @@ public class PluginManager { LOG.d(TAG, "init()"); // If first time, then load plugins from config.xml file - if (this.firstRun) { + if (urlMap == null) { this.loadPlugins(); - this.firstRun = false; } // Stop plugins on current HTML page and discard plugin objects @@ -102,72 +86,12 @@ public class PluginManager { * Load plugins from res/xml/config.xml */ public void loadPlugins() { - // First checking the class namespace for config.xml - int id = this.ctx.getActivity().getResources().getIdentifier("config", "xml", this.ctx.getActivity().getClass().getPackage().getName()); - if (id == 0) { - // If we couldn't find config.xml there, we'll look in the namespace from AndroidManifest.xml - id = this.ctx.getActivity().getResources().getIdentifier("config", "xml", this.ctx.getActivity().getPackageName()); - 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(); - if (strNode.equals("url-filter")) { - Log.w(TAG, "Plugin " + service + " is using deprecated tag "); - if (urlMap.get(service) == null) { - urlMap.put(service, new ArrayList(2)); - } - List filters = urlMap.get(service); - filters.add(xml.getAttributeValue(null, "value")); - } - 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; - onload = false; - } - } - try { - eventType = xml.next(); - } catch (XmlPullParserException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } + ConfigXmlParser parser = new ConfigXmlParser(); + parser.parse(ctx.getActivity()); + for (PluginEntry entry : parser.getPluginEntries()) { + addService(entry); + } + urlMap = parser.getPluginUrlMap(); } /** @@ -396,14 +320,6 @@ public class PluginManager { } } - - 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=cordova-android.git;a=blob;f=framework/res/xml/config.xml"); - LOG.e(TAG, "====================================================================================="); - } - Uri remapUri(Uri uri) { for (PluginEntry entry : this.entries.values()) { if (entry.plugin != null) { diff --git a/framework/src/org/apache/cordova/Whitelist.java b/framework/src/org/apache/cordova/Whitelist.java index a01d7aaf..ecbb7f61 100644 --- a/framework/src/org/apache/cordova/Whitelist.java +++ b/framework/src/org/apache/cordova/Whitelist.java @@ -98,6 +98,10 @@ public class Whitelist { public Whitelist() { this.whiteList = new ArrayList(); + // Add implicitly allowed URLs + addWhiteListEntry("file:///*", false); + addWhiteListEntry("content:///*", false); + addWhiteListEntry("data:*", false); } /* Match patterns (from http://developer.chrome.com/extensions/match_patterns.html)