[CB-4048] Merge branch 'master' into 2.9.x

This commit is contained in:
Andrew Grieve 2013-06-28 11:23:58 -04:00
commit 67e97a89ce
15 changed files with 810 additions and 4466 deletions

View File

@ -45,10 +45,6 @@ fi
# cleanup after exit and/or on error
function on_exit {
if [ -f "$BUILD_PATH"/framework/assets/www/cordova.js ]
then
rm "$BUILD_PATH"/framework/assets/www/cordova.js
fi
if [ -f "$BUILD_PATH"/framework/cordova-$VERSION.jar ]
then
rm "$BUILD_PATH"/framework/cordova-$VERSION.jar
@ -103,16 +99,6 @@ then
# update the cordova-android framework for the desired target
"$ANDROID_BIN" update project --target $TARGET --path "$BUILD_PATH"/framework &> /dev/null
if [ ! -e "$BUILD_PATH"/framework/libs/commons-codec-1.7.jar ]; then
# Use curl to get the jar (TODO: Support Apache Mirrors)
curl -OL http://archive.apache.org/dist/commons/codec/binaries/commons-codec-1.7-bin.zip &> /dev/null
unzip commons-codec-1.7-bin.zip &> /dev/null
mkdir -p "$BUILD_PATH"/framework/libs
cp commons-codec-1.7/commons-codec-1.7.jar "$BUILD_PATH"/framework/libs
# cleanup yo
rm commons-codec-1.7-bin.zip && rm -rf commons-codec-1.7
fi
# compile cordova.js and cordova.jar
(cd "$BUILD_PATH"/framework && ant jar &> /dev/null )
fi
@ -128,14 +114,17 @@ else
fi
# creating cordova folder and copying run/build/log/launch scripts
mkdir "$PROJECT_PATH"/cordova
mkdir "$PROJECT_PATH"/cordova/lib
if [ ! -e "$PROJECT_PATH/cordova" ]
then
mkdir "$PROJECT_PATH"/cordova
mkdir "$PROJECT_PATH"/cordova/lib
fi
cp "$BUILD_PATH"/bin/templates/cordova/appinfo.jar "$PROJECT_PATH"/cordova/appinfo.jar
cp "$BUILD_PATH"/bin/templates/cordova/build "$PROJECT_PATH"/cordova/build
cp "$BUILD_PATH"/bin/templates/cordova/clean "$PROJECT_PATH"/cordova/clean
cp "$BUILD_PATH"/bin/templates/cordova/log "$PROJECT_PATH"/cordova/log
cp "$BUILD_PATH"/bin/templates/cordova/run "$PROJECT_PATH"/cordova/run
cp "$BUILD_PATH"/bin/templates/cordova/lib/cordova "$PROJECT_PATH"/cordova/lib/cordova
cp "$BUILD_PATH"/bin/templates/cordova/lib/cordova.js "$PROJECT_PATH"/cordova/lib/cordova.js
cp "$BUILD_PATH"/bin/templates/cordova/lib/install-device "$PROJECT_PATH"/cordova/lib/install-device
cp "$BUILD_PATH"/bin/templates/cordova/lib/install-emulator "$PROJECT_PATH"/cordova/lib/install-emulator
cp "$BUILD_PATH"/bin/templates/cordova/lib/list-devices "$PROJECT_PATH"/cordova/lib/list-devices

File diff suppressed because it is too large Load Diff

View File

@ -203,6 +203,19 @@ public class Config {
self._addWhiteListEntry(origin, subdomains);
}
/*
* Trying to figure out how to match * is a pain
* So, we don't use a regex here
*/
private boolean originHasWildcard(String origin){
//First, check for a protocol, then split it if it has one.
if(origin.contains("//"))
{
origin = origin.split("//")[1];
}
return origin.startsWith("*");
}
private void _addWhiteListEntry(String origin, boolean subdomains) {
try {
@ -210,8 +223,16 @@ public class Config {
if (origin.compareTo("*") == 0) {
LOG.d(TAG, "Unlimited access to network resources");
this.whiteList.add(Pattern.compile(".*"));
} else { // specific access
}
else { // specific access
// check if subdomains should be included
if(originHasWildcard(origin))
{
subdomains = true;
//Remove the wildcard so this works properly
origin = origin.replace("*.", "");
}
// TODO: we should not add more domains if * has already been added
Pattern schemeRegex = Pattern.compile("^[a-z-]+://");
Matcher matcher = schemeRegex.matcher(origin);
@ -250,6 +271,7 @@ public class Config {
}
}
/**
* Determine if URL is in approved list of URLs to load.
*

View File

@ -250,6 +250,19 @@ public class CordovaActivity extends Activity implements CordovaInterface {
}
}
//CB-3949: Workaround for weird Android Launcher Bug!
private void checkIntents()
{
Intent intent = getIntent();
String intentAction = intent.getAction();
if (!isTaskRoot() && intent.hasCategory(Intent.CATEGORY_LAUNCHER) && intentAction != null) {
if(intentAction.equals(Intent.ACTION_MAIN)) {
Log.d("Cordova", "This isn't the root activity. Clearing it and returning to the root activity.");
finish();
return;
}
}
}
/**
* Called when the activity is first created.
*
@ -258,6 +271,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
@SuppressWarnings("deprecation")
@Override
public void onCreate(Bundle savedInstanceState) {
checkIntents();
Config.init(this);
LOG.d(TAG, "CordovaActivity.onCreate()");
super.onCreate(savedInstanceState);

View File

@ -805,7 +805,6 @@ public class CordovaWebView extends WebView {
public void handleDestroy()
{
// Send destroy event to JavaScript
// Since baseUrl is set in loadUrlIntoView, if user hit Back button before loadUrl was called, we'll get an NPE on baseUrl (CB-2458)
this.loadUrl("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
// Load blank page so that JavaScript onunload is called
@ -944,4 +943,38 @@ public class CordovaWebView extends WebView {
public void storeResult(int requestCode, int resultCode, Intent intent) {
mResult = new ActivityResult(requestCode, resultCode, intent);
}
/**
* Resolves the given URI, giving plugins a chance to re-route or customly handle the URI.
* A white-list rejection will be returned if the URI does not pass the white-list.
* @return Never returns null.
* @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
* resolved before being passed into this function.
*/
public UriResolver resolveUri(Uri uri) {
return resolveUri(uri, false);
}
UriResolver resolveUri(Uri uri, boolean fromWebView) {
if (!uri.isAbsolute()) {
throw new IllegalArgumentException("Relative URIs are not yet supported by resolveUri.");
}
// Check the against the white-list before delegating to plugins.
if (("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) && !Config.isUrlWhiteListed(uri.toString()))
{
LOG.w(TAG, "resolveUri - URL is not in whitelist: " + uri);
return new UriResolvers.ErrorUriResolver(uri, "Whitelist rejection");
}
// Give plugins a chance to handle the request.
UriResolver resolver = pluginManager.resolveUri(uri);
if (resolver == null && !fromWebView) {
resolver = UriResolvers.forUri(uri, cordova.getActivity());
if (resolver == null) {
resolver = new UriResolvers.ErrorUriResolver(uri, "Unresolvable URI");
}
}
return resolver;
}
}

View File

@ -48,12 +48,15 @@ import org.json.JSONException;
jsMessageQueue.setPaused(true);
try {
boolean wasSync = pluginManager.exec(service, action, callbackId, arguments);
pluginManager.exec(service, action, callbackId, arguments);
String ret = "";
if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING || wasSync) {
if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING) {
ret = jsMessageQueue.popAndEncode();
}
return ret;
} catch (Throwable e) {
e.printStackTrace();
return "";
} finally {
jsMessageQueue.setPaused(false);
}

View File

@ -26,9 +26,12 @@ import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.LOG;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Locale;
public class FileHelper {
@ -124,6 +127,20 @@ public class FileHelper {
return uriString;
}
public static String getMimeTypeForExtension(String path) {
String extension = path;
int lastDot = extension.lastIndexOf('.');
if (lastDot != -1) {
extension = extension.substring(lastDot + 1);
}
// Convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185).
extension = extension.toLowerCase(Locale.getDefault());
if (extension.equals("3ga")) {
return "audio/3gpp";
}
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
/**
* Returns the mime type of the data specified by the given URI string.
*
@ -137,19 +154,7 @@ public class FileHelper {
if (uriString.startsWith("content://")) {
mimeType = cordova.getActivity().getContentResolver().getType(uri);
} else {
// MimeTypeMap.getFileExtensionFromUrl() fails when there are query parameters.
String extension = uri.getPath();
int lastDot = extension.lastIndexOf('.');
if (lastDot != -1) {
extension = extension.substring(lastDot + 1);
}
// Convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185).
extension = extension.toLowerCase();
if (extension.equals("3ga")) {
mimeType = "audio/3gpp";
} else {
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
mimeType = getMimeTypeForExtension(uri.getPath());
}
return mimeType;

View File

@ -18,7 +18,6 @@
*/
package org.apache.cordova;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@ -26,6 +25,7 @@ import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.LOG;
import android.annotation.TargetApi;
import android.net.Uri;
import android.os.Build;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
@ -44,45 +44,29 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
//Check if plugins intercept the request
WebResourceResponse ret = super.shouldInterceptRequest(view, url);
UriResolver uriResolver = appView.resolveUri(Uri.parse(url), true);
if(!Config.isUrlWhiteListed(url) && (url.startsWith("http://") || url.startsWith("https://")))
{
ret = getWhitelistResponse();
if (uriResolver == null && url.startsWith("file:///android_asset/")) {
if (url.contains("?") || url.contains("#") || needsIceCreamSpecialsInAssetUrlFix(url)) {
uriResolver = appView.resolveUri(Uri.parse(url), false);
}
}
else if(ret == null && (url.contains("?") || url.contains("#") || needsIceCreamSpecialsInAssetUrlFix(url))){
ret = generateWebResourceResponse(url);
}
else if (ret == null && this.appView.pluginManager != null) {
ret = this.appView.pluginManager.shouldInterceptRequest(url);
}
return ret;
}
private WebResourceResponse getWhitelistResponse()
{
WebResourceResponse emptyResponse;
String empty = "";
ByteArrayInputStream data = new ByteArrayInputStream(empty.getBytes());
return new WebResourceResponse("text/plain", "UTF-8", data);
}
private WebResourceResponse generateWebResourceResponse(String url) {
if (url.startsWith("file:///android_asset/")) {
String mimetype = FileHelper.getMimeType(url, cordova);
if (uriResolver != null) {
try {
InputStream stream = FileHelper.getInputStreamFromUriString(url, cordova);
WebResourceResponse response = new WebResourceResponse(mimetype, "UTF-8", stream);
return response;
InputStream stream = uriResolver.getInputStream();
String mimeType = uriResolver.getMimeType();
// If we don't know how to open this file, let the browser continue loading
return new WebResourceResponse(mimeType, "UTF-8", stream);
} catch (IOException e) {
LOG.e("generateWebResourceResponse", e.getMessage(), e);
LOG.e("IceCreamCordovaWebViewClient", "Error occurred while loading a file.", e);
// Results in a 404.
return new WebResourceResponse("text/plain", "UTF-8", null);
}
}
return null;
}
private static boolean needsIceCreamSpecialsInAssetUrlFix(String url) {
if (!url.contains("%20")){
return false;
@ -96,5 +80,4 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
return false;
}
}
}

View File

@ -0,0 +1,65 @@
/*
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.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import android.net.Uri;
/*
* Interface for a class that can resolve URIs.
* See CordovaUriResolver for an example.
*/
public interface UriResolver {
/** Returns the URI that this instance will resolve. */
Uri getUri();
/**
* Returns the InputStream for the resource.
* Throws an exception if it cannot be read.
* Never returns null.
*/
InputStream getInputStream() throws IOException;
/**
* Returns the OutputStream for the resource.
* Throws an exception if it cannot be written to.
* Never returns null.
*/
OutputStream getOutputStream() throws IOException;
/**
* Returns the MIME type of the resource.
* Returns null if the MIME type cannot be determined (e.g. content: that doesn't exist).
*/
String getMimeType();
/** Returns whether the resource is writable. */
boolean isWritable();
/**
* Returns a File that points to the resource, or null if the resource
* is not on the local file system.
*/
File getLocalFile();
}

View File

@ -0,0 +1,277 @@
/*
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.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.cordova.FileHelper;
import org.apache.http.util.EncodingUtils;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetManager;
import android.net.Uri;
/*
* UriResolver implementations.
*/
public final class UriResolvers {
private UriResolvers() {}
private static final class FileUriResolver implements UriResolver {
private final Uri uri;
private String mimeType;
private File localFile;
FileUriResolver(Uri uri) {
this.uri = uri;
}
public Uri getUri() {
return uri;
}
public InputStream getInputStream() throws IOException {
return new FileInputStream(getLocalFile());
}
public OutputStream getOutputStream() throws FileNotFoundException {
return new FileOutputStream(getLocalFile());
}
public String getMimeType() {
if (mimeType == null) {
mimeType = FileHelper.getMimeTypeForExtension(getLocalFile().getName());
}
return mimeType;
}
public boolean isWritable() {
File f = getLocalFile();
if (f.isDirectory()) {
return false;
}
if (f.exists()) {
return f.canWrite();
}
return f.getParentFile().canWrite();
}
public File getLocalFile() {
if (localFile == null) {
localFile = new File(uri.getPath());
}
return localFile;
}
}
private static final class AssetUriResolver implements UriResolver {
private final Uri uri;
private final AssetManager assetManager;
private final String assetPath;
private String mimeType;
AssetUriResolver(Uri uri, AssetManager assetManager) {
this.uri = uri;
this.assetManager = assetManager;
this.assetPath = uri.getPath().substring(15);
}
public Uri getUri() {
return uri;
}
public InputStream getInputStream() throws IOException {
return assetManager.open(assetPath);
}
public OutputStream getOutputStream() throws FileNotFoundException {
throw new FileNotFoundException("URI not writable.");
}
public String getMimeType() {
if (mimeType == null) {
mimeType = FileHelper.getMimeTypeForExtension(assetPath);
}
return mimeType;
}
public boolean isWritable() {
return false;
}
public File getLocalFile() {
return null;
}
}
private static final class ContentUriResolver implements UriResolver {
private final Uri uri;
private final ContentResolver contentResolver;
private String mimeType;
ContentUriResolver(Uri uri, ContentResolver contentResolver) {
this.uri = uri;
this.contentResolver = contentResolver;
}
public Uri getUri() {
return uri;
}
public InputStream getInputStream() throws IOException {
return contentResolver.openInputStream(uri);
}
public OutputStream getOutputStream() throws FileNotFoundException {
return contentResolver.openOutputStream(uri);
}
public String getMimeType() {
if (mimeType == null) {
mimeType = contentResolver.getType(uri);
}
return mimeType;
}
public boolean isWritable() {
return uri.getScheme().equals(ContentResolver.SCHEME_CONTENT);
}
public File getLocalFile() {
return null;
}
}
static final class ErrorUriResolver implements UriResolver {
final Uri uri;
final String errorMsg;
ErrorUriResolver(Uri uri, String errorMsg) {
this.uri = uri;
this.errorMsg = errorMsg;
}
@Override
public boolean isWritable() {
return false;
}
@Override
public Uri getUri() {
return uri;
}
@Override
public File getLocalFile() {
return null;
}
@Override
public OutputStream getOutputStream() throws IOException {
throw new FileNotFoundException(errorMsg);
}
@Override
public String getMimeType() {
return null;
}
@Override
public InputStream getInputStream() throws IOException {
throw new FileNotFoundException(errorMsg);
}
}
private static final class ReadOnlyResolver implements UriResolver {
private Uri uri;
private InputStream inputStream;
private String mimeType;
public ReadOnlyResolver(Uri uri, InputStream inputStream, String mimeType) {
this.uri = uri;
this.inputStream = inputStream;
this.mimeType = mimeType;
}
@Override
public boolean isWritable() {
return false;
}
@Override
public Uri getUri() {
return uri;
}
@Override
public File getLocalFile() {
return null;
}
@Override
public OutputStream getOutputStream() throws IOException {
throw new FileNotFoundException("URI is not writable");
}
@Override
public String getMimeType() {
return mimeType;
}
@Override
public InputStream getInputStream() throws IOException {
return inputStream;
}
}
public static UriResolver createInline(Uri uri, String response, String mimeType) {
return createInline(uri, EncodingUtils.getBytes(response, "UTF-8"), mimeType);
}
public static UriResolver createInline(Uri uri, byte[] response, String mimeType) {
return new ReadOnlyResolver(uri, new ByteArrayInputStream(response), mimeType);
}
public static UriResolver createReadOnly(Uri uri, InputStream inputStream, String mimeType) {
return new ReadOnlyResolver(uri, inputStream, mimeType);
}
/* Package-private to force clients to go through CordovaWebView.resolveUri(). */
static UriResolver forUri(Uri uri, Context context) {
String scheme = uri.getScheme();
if (ContentResolver.SCHEME_CONTENT.equals(scheme) || ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
return new ContentUriResolver(uri, context.getContentResolver());
}
if (ContentResolver.SCHEME_FILE.equals(scheme)) {
if (uri.getPath().startsWith("/android_asset/")) {
return new AssetUriResolver(uri, context.getAssets());
}
return new FileUriResolver(uri);
}
return null;
}
}

View File

@ -20,14 +20,12 @@ package org.apache.cordova.api;
import org.apache.cordova.CordovaArgs;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.UriResolver;
import org.json.JSONArray;
import org.json.JSONException;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import android.webkit.WebResourceResponse;
import android.net.Uri;
/**
* Plugins must extend this class and override one of the execute methods.
@ -165,13 +163,10 @@ public class CordovaPlugin {
}
/**
* By specifying a <url-filter> in config.xml you can map a URL prefix to this method. It applies to all resources loaded in the WebView, not just top-level navigation.
*
* @param url The URL of the resource to be loaded.
* @return Return a WebResourceResponse for the resource, or null to let the WebView handle it normally.
* Hook for overriding the default URI handling mechanism.
* Applies to WebView requests as well as requests made by plugins.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public WebResourceResponse shouldInterceptRequest(String url) {
public UriResolver resolveUri(Uri uri) {
return null;
}

View File

@ -63,6 +63,19 @@ public class PluginEntry {
this.onload = onload;
}
/**
* Alternate constructor
*
* @param service The name of the service
* @param plugin The plugin associated with this entry
*/
public PluginEntry(String service, CordovaPlugin plugin) {
this.service = service;
this.plugin = plugin;
this.pluginClass = plugin.getClass().getName();
this.onload = false;
}
/**
* Create plugin object.
* If plugin is already created, then just return it.

View File

@ -22,14 +22,18 @@ 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.UriResolver;
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;
import android.webkit.WebResourceResponse;
@ -55,6 +59,8 @@ public class PluginManager {
// 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.
*
@ -65,6 +71,7 @@ public class PluginManager {
this.ctx = ctx;
this.app = app;
this.firstRun = true;
this.numPendingUiExecs = new AtomicInteger(0);
}
/**
@ -86,6 +93,9 @@ public class PluginManager {
this.clearPluginObjects();
}
// Insert PluginManager service
this.addService(new PluginEntry("PluginManager", new PluginManagerService()));
// Start up all plugins that have onload specified
this.startupPlugins();
}
@ -200,15 +210,28 @@ public class PluginManager {
* 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);
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 true;
return;
}
try {
CallbackContext callbackContext = new CallbackContext(callbackId, app);
@ -216,19 +239,16 @@ public class PluginManager {
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);
public void exec(String service, String action, String callbackId, String jsonArgs, boolean async) {
exec(service, action, callbackId, jsonArgs);
}
/**
@ -361,25 +381,6 @@ public class PluginManager {
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.
*/
@ -396,8 +397,42 @@ public class PluginManager {
private void pluginConfigurationMissing() {
LOG.e(TAG, "=====================================================================================");
LOG.e(TAG, "ERROR: config.xml is missing. Add res/xml/plugins.xml to your project.");
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, "=====================================================================================");
}
/* Should be package private */ public UriResolver resolveUri(Uri uri) {
for (PluginEntry entry : this.entries.values()) {
if (entry.plugin != null) {
UriResolver ret = entry.plugin.resolveUri(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;
}
}
}

View File

@ -0,0 +1,263 @@
package org.apache.cordova.test;
/*
*
* 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.
*
*/
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.UriResolver;
import org.apache.cordova.UriResolvers;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.PluginEntry;
import org.apache.cordova.test.actions.CordovaWebViewTestActivity;
import org.json.JSONArray;
import org.json.JSONException;
import java.io.File;
import java.io.IOException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
public class UriResolversTest extends ActivityInstrumentationTestCase2<CordovaWebViewTestActivity> {
public UriResolversTest()
{
super(CordovaWebViewTestActivity.class);
}
CordovaWebView cordovaWebView;
private CordovaWebViewTestActivity activity;
String execPayload;
Integer execStatus;
protected void setUp() throws Exception {
super.setUp();
activity = this.getActivity();
cordovaWebView = activity.cordovaWebView;
cordovaWebView.pluginManager.addService(new PluginEntry("UriResolverTestPlugin1", new CordovaPlugin() {
@Override
public UriResolver resolveUri(Uri uri) {
if ("plugin-uri".equals(uri.getScheme())) {
return cordovaWebView.resolveUri(uri.buildUpon().scheme("file").build());
}
return null;
}
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
synchronized (UriResolversTest.this) {
execPayload = args.getString(0);
execStatus = args.getInt(1);
UriResolversTest.this.notify();
}
return true;
}
}));
cordovaWebView.pluginManager.addService(new PluginEntry("UriResolverTestPlugin2", new CordovaPlugin() {
@Override
public UriResolver resolveUri(Uri uri) {
if (uri.getQueryParameter("pluginRewrite") != null) {
return UriResolvers.createInline(uri, "pass", "my/mime");
}
return null;
}
}));
}
private Uri createTestImageContentUri() {
Bitmap imageBitmap = BitmapFactory.decodeResource(activity.getResources(), R.drawable.icon);
String stored = MediaStore.Images.Media.insertImage(activity.getContentResolver(),
imageBitmap, "app-icon", "desc");
return Uri.parse(stored);
}
private void performResolverTest(Uri uri, String expectedMimeType, File expectedLocalFile,
boolean expectedIsWritable,
boolean expectRead, boolean expectWrite) throws IOException {
UriResolver resolver = cordovaWebView.resolveUri(uri);
assertEquals(expectedLocalFile, resolver.getLocalFile());
assertEquals(expectedMimeType, resolver.getMimeType());
if (expectedIsWritable) {
assertTrue(resolver.isWritable());
} else {
assertFalse(resolver.isWritable());
}
try {
resolver.getInputStream().read();
if (!expectRead) {
fail("Expected getInputStream to throw.");
}
} catch (IOException e) {
if (expectRead) {
throw e;
}
}
try {
resolver.getOutputStream().write(123);
if (!expectWrite) {
fail("Expected getOutputStream to throw.");
}
} catch (IOException e) {
if (expectWrite) {
throw e;
}
}
}
public void testValidContentUri() throws IOException
{
Uri contentUri = createTestImageContentUri();
performResolverTest(contentUri, "image/jpeg", null, true, true, true);
}
public void testInvalidContentUri() throws IOException
{
Uri contentUri = Uri.parse("content://media/external/images/media/999999999");
performResolverTest(contentUri, null, null, true, false, false);
}
public void testValidAssetUri() throws IOException
{
Uri assetUri = Uri.parse("file:///android_asset/www/index.html?foo#bar"); // Also check for stripping off ? and # correctly.
performResolverTest(assetUri, "text/html", null, false, true, false);
}
public void testInvalidAssetUri() throws IOException
{
Uri assetUri = Uri.parse("file:///android_asset/www/missing.html");
performResolverTest(assetUri, "text/html", null, false, false, false);
}
public void testFileUriToExistingFile() throws IOException
{
File f = File.createTempFile("te s t", ".txt"); // Also check for dealing with spaces.
try {
Uri fileUri = Uri.parse(f.toURI().toString() + "?foo#bar"); // Also check for stripping off ? and # correctly.
performResolverTest(fileUri, "text/plain", f, true, true, true);
} finally {
f.delete();
}
}
public void testFileUriToMissingFile() throws IOException
{
File f = new File(Environment.getExternalStorageDirectory() + "/somefilethatdoesntexist");
Uri fileUri = Uri.parse(f.toURI().toString());
try {
performResolverTest(fileUri, null, f, true, false, true);
} finally {
f.delete();
}
}
public void testFileUriToMissingFileWithMissingParent() throws IOException
{
File f = new File(Environment.getExternalStorageDirectory() + "/somedirthatismissing/somefilethatdoesntexist");
Uri fileUri = Uri.parse(f.toURI().toString());
performResolverTest(fileUri, null, f, false, false, false);
}
public void testUnrecognizedUri() throws IOException
{
Uri uri = Uri.parse("somescheme://foo");
performResolverTest(uri, null, null, false, false, false);
}
public void testRelativeUri()
{
try {
cordovaWebView.resolveUri(Uri.parse("/foo"));
fail("Should have thrown for relative URI 1.");
} catch (Throwable t) {
}
try {
cordovaWebView.resolveUri(Uri.parse("//foo/bar"));
fail("Should have thrown for relative URI 2.");
} catch (Throwable t) {
}
try {
cordovaWebView.resolveUri(Uri.parse("foo.png"));
fail("Should have thrown for relative URI 3.");
} catch (Throwable t) {
}
}
public void testPluginOverrides1() throws IOException
{
Uri uri = Uri.parse("plugin-uri://foohost/android_asset/www/index.html");
performResolverTest(uri, "text/html", null, false, true, false);
}
public void testPluginOverrides2() throws IOException
{
Uri uri = Uri.parse("plugin-uri://foohost/android_asset/www/index.html?pluginRewrite=yes");
performResolverTest(uri, "my/mime", null, false, true, false);
}
public void testWhitelistRejection() throws IOException
{
Uri uri = Uri.parse("http://foohost.com/");
performResolverTest(uri, null, null, false, false, false);
}
public void testWebViewRequestIntercept() throws IOException
{
cordovaWebView.sendJavascript(
"var x = new XMLHttpRequest;\n" +
"x.open('GET', 'file://foo?pluginRewrite=1', false);\n" +
"x.send();\n" +
"cordova.require('cordova/exec')(null,null,'UriResolverTestPlugin1', 'foo', [x.responseText, x.status])");
execPayload = null;
execStatus = null;
try {
synchronized (this) {
this.wait(2000);
}
} catch (InterruptedException e) {
}
assertEquals("pass", execPayload);
assertEquals(execStatus.intValue(), 200);
}
public void testWebViewWhiteListRejection() throws IOException
{
cordovaWebView.sendJavascript(
"var x = new XMLHttpRequest;\n" +
"x.open('GET', 'http://foo/bar', false);\n" +
"x.send();\n" +
"cordova.require('cordova/exec')(null,null,'UriResolverTestPlugin1', 'foo', [x.responseText, x.status])");
execPayload = null;
execStatus = null;
try {
synchronized (this) {
this.wait(2000);
}
} catch (InterruptedException e) {
}
assertEquals("", execPayload);
assertEquals(execStatus.intValue(), 404);
}
}

View File

@ -36,7 +36,7 @@ import android.content.Intent;
import android.os.Bundle;
public class CordovaWebViewTestActivity extends Activity implements CordovaInterface {
CordovaWebView cordovaWebView;
public CordovaWebView cordovaWebView;
private final ExecutorService threadPool = Executors.newCachedThreadPool();