mirror of
https://github.com/apache/cordova-android.git
synced 2025-02-12 18:56:11 +08:00
[CB-4048] Merge branch 'master' into 2.9.x
This commit is contained in:
commit
67e97a89ce
19
bin/update
19
bin/update
@ -45,10 +45,6 @@ fi
|
|||||||
|
|
||||||
# cleanup after exit and/or on error
|
# cleanup after exit and/or on error
|
||||||
function on_exit {
|
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 ]
|
if [ -f "$BUILD_PATH"/framework/cordova-$VERSION.jar ]
|
||||||
then
|
then
|
||||||
rm "$BUILD_PATH"/framework/cordova-$VERSION.jar
|
rm "$BUILD_PATH"/framework/cordova-$VERSION.jar
|
||||||
@ -103,16 +99,6 @@ then
|
|||||||
# update the cordova-android framework for the desired target
|
# update the cordova-android framework for the desired target
|
||||||
"$ANDROID_BIN" update project --target $TARGET --path "$BUILD_PATH"/framework &> /dev/null
|
"$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
|
# compile cordova.js and cordova.jar
|
||||||
(cd "$BUILD_PATH"/framework && ant jar &> /dev/null )
|
(cd "$BUILD_PATH"/framework && ant jar &> /dev/null )
|
||||||
fi
|
fi
|
||||||
@ -128,14 +114,17 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# creating cordova folder and copying run/build/log/launch scripts
|
# creating cordova folder and copying run/build/log/launch scripts
|
||||||
|
if [ ! -e "$PROJECT_PATH/cordova" ]
|
||||||
|
then
|
||||||
mkdir "$PROJECT_PATH"/cordova
|
mkdir "$PROJECT_PATH"/cordova
|
||||||
mkdir "$PROJECT_PATH"/cordova/lib
|
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/appinfo.jar "$PROJECT_PATH"/cordova/appinfo.jar
|
||||||
cp "$BUILD_PATH"/bin/templates/cordova/build "$PROJECT_PATH"/cordova/build
|
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/clean "$PROJECT_PATH"/cordova/clean
|
||||||
cp "$BUILD_PATH"/bin/templates/cordova/log "$PROJECT_PATH"/cordova/log
|
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/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-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/install-emulator "$PROJECT_PATH"/cordova/lib/install-emulator
|
||||||
cp "$BUILD_PATH"/bin/templates/cordova/lib/list-devices "$PROJECT_PATH"/cordova/lib/list-devices
|
cp "$BUILD_PATH"/bin/templates/cordova/lib/list-devices "$PROJECT_PATH"/cordova/lib/list-devices
|
||||||
|
4363
framework/assets/www/cordova.js
vendored
4363
framework/assets/www/cordova.js
vendored
File diff suppressed because it is too large
Load Diff
@ -203,6 +203,19 @@ public class Config {
|
|||||||
self._addWhiteListEntry(origin, subdomains);
|
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) {
|
private void _addWhiteListEntry(String origin, boolean subdomains) {
|
||||||
try {
|
try {
|
||||||
@ -210,8 +223,16 @@ public class Config {
|
|||||||
if (origin.compareTo("*") == 0) {
|
if (origin.compareTo("*") == 0) {
|
||||||
LOG.d(TAG, "Unlimited access to network resources");
|
LOG.d(TAG, "Unlimited access to network resources");
|
||||||
this.whiteList.add(Pattern.compile(".*"));
|
this.whiteList.add(Pattern.compile(".*"));
|
||||||
} else { // specific access
|
}
|
||||||
|
else { // specific access
|
||||||
// check if subdomains should be included
|
// 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
|
// TODO: we should not add more domains if * has already been added
|
||||||
Pattern schemeRegex = Pattern.compile("^[a-z-]+://");
|
Pattern schemeRegex = Pattern.compile("^[a-z-]+://");
|
||||||
Matcher matcher = schemeRegex.matcher(origin);
|
Matcher matcher = schemeRegex.matcher(origin);
|
||||||
@ -250,6 +271,7 @@ public class Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if URL is in approved list of URLs to load.
|
* Determine if URL is in approved list of URLs to load.
|
||||||
*
|
*
|
||||||
|
@ -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.
|
* Called when the activity is first created.
|
||||||
*
|
*
|
||||||
@ -258,6 +271,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
|
|||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
checkIntents();
|
||||||
Config.init(this);
|
Config.init(this);
|
||||||
LOG.d(TAG, "CordovaActivity.onCreate()");
|
LOG.d(TAG, "CordovaActivity.onCreate()");
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -805,7 +805,6 @@ public class CordovaWebView extends WebView {
|
|||||||
public void handleDestroy()
|
public void handleDestroy()
|
||||||
{
|
{
|
||||||
// Send destroy event to JavaScript
|
// 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');};");
|
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
|
// 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) {
|
public void storeResult(int requestCode, int resultCode, Intent intent) {
|
||||||
mResult = new ActivityResult(requestCode, resultCode, 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,12 +48,15 @@ import org.json.JSONException;
|
|||||||
|
|
||||||
jsMessageQueue.setPaused(true);
|
jsMessageQueue.setPaused(true);
|
||||||
try {
|
try {
|
||||||
boolean wasSync = pluginManager.exec(service, action, callbackId, arguments);
|
pluginManager.exec(service, action, callbackId, arguments);
|
||||||
String ret = "";
|
String ret = "";
|
||||||
if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING || wasSync) {
|
if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING) {
|
||||||
ret = jsMessageQueue.popAndEncode();
|
ret = jsMessageQueue.popAndEncode();
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return "";
|
||||||
} finally {
|
} finally {
|
||||||
jsMessageQueue.setPaused(false);
|
jsMessageQueue.setPaused(false);
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,12 @@ import org.apache.cordova.api.CordovaInterface;
|
|||||||
import org.apache.cordova.api.LOG;
|
import org.apache.cordova.api.LOG;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URLConnection;
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class FileHelper {
|
public class FileHelper {
|
||||||
@ -124,6 +127,20 @@ public class FileHelper {
|
|||||||
return uriString;
|
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.
|
* Returns the mime type of the data specified by the given URI string.
|
||||||
*
|
*
|
||||||
@ -137,19 +154,7 @@ public class FileHelper {
|
|||||||
if (uriString.startsWith("content://")) {
|
if (uriString.startsWith("content://")) {
|
||||||
mimeType = cordova.getActivity().getContentResolver().getType(uri);
|
mimeType = cordova.getActivity().getContentResolver().getType(uri);
|
||||||
} else {
|
} else {
|
||||||
// MimeTypeMap.getFileExtensionFromUrl() fails when there are query parameters.
|
mimeType = getMimeTypeForExtension(uri.getPath());
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mimeType;
|
return mimeType;
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.cordova;
|
package org.apache.cordova;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
@ -26,6 +25,7 @@ import org.apache.cordova.api.CordovaInterface;
|
|||||||
import org.apache.cordova.api.LOG;
|
import org.apache.cordova.api.LOG;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.webkit.WebResourceResponse;
|
import android.webkit.WebResourceResponse;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
@ -44,40 +44,24 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||||
//Check if plugins intercept the request
|
UriResolver uriResolver = appView.resolveUri(Uri.parse(url), true);
|
||||||
WebResourceResponse ret = super.shouldInterceptRequest(view, url);
|
|
||||||
|
|
||||||
if(!Config.isUrlWhiteListed(url) && (url.startsWith("http://") || url.startsWith("https://")))
|
if (uriResolver == null && url.startsWith("file:///android_asset/")) {
|
||||||
{
|
if (url.contains("?") || url.contains("#") || needsIceCreamSpecialsInAssetUrlFix(url)) {
|
||||||
ret = getWhitelistResponse();
|
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()
|
if (uriResolver != null) {
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
InputStream stream = FileHelper.getInputStreamFromUriString(url, cordova);
|
InputStream stream = uriResolver.getInputStream();
|
||||||
WebResourceResponse response = new WebResourceResponse(mimetype, "UTF-8", stream);
|
String mimeType = uriResolver.getMimeType();
|
||||||
return response;
|
// 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) {
|
} 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;
|
return null;
|
||||||
@ -96,5 +80,4 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
65
framework/src/org/apache/cordova/UriResolver.java
Normal file
65
framework/src/org/apache/cordova/UriResolver.java
Normal 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();
|
||||||
|
}
|
277
framework/src/org/apache/cordova/UriResolvers.java
Normal file
277
framework/src/org/apache/cordova/UriResolvers.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -20,14 +20,12 @@ package org.apache.cordova.api;
|
|||||||
|
|
||||||
import org.apache.cordova.CordovaArgs;
|
import org.apache.cordova.CordovaArgs;
|
||||||
import org.apache.cordova.CordovaWebView;
|
import org.apache.cordova.CordovaWebView;
|
||||||
|
import org.apache.cordova.UriResolver;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Build;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
|
||||||
import android.webkit.WebResourceResponse;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugins must extend this class and override one of the execute methods.
|
* 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.
|
* Hook for overriding the default URI handling mechanism.
|
||||||
*
|
* Applies to WebView requests as well as requests made by plugins.
|
||||||
* @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.
|
|
||||||
*/
|
*/
|
||||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
public UriResolver resolveUri(Uri uri) {
|
||||||
public WebResourceResponse shouldInterceptRequest(String url) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,6 +63,19 @@ public class PluginEntry {
|
|||||||
this.onload = onload;
|
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.
|
* Create plugin object.
|
||||||
* If plugin is already created, then just return it.
|
* If plugin is already created, then just return it.
|
||||||
|
@ -22,14 +22,18 @@ import java.io.IOException;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map.Entry;
|
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.CordovaWebView;
|
||||||
|
import org.apache.cordova.UriResolver;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.res.XmlResourceParser;
|
import android.content.res.XmlResourceParser;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.webkit.WebResourceResponse;
|
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
|
// 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>();
|
||||||
|
|
||||||
|
private AtomicInteger numPendingUiExecs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
@ -65,6 +71,7 @@ public class PluginManager {
|
|||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.app = app;
|
this.app = app;
|
||||||
this.firstRun = true;
|
this.firstRun = true;
|
||||||
|
this.numPendingUiExecs = new AtomicInteger(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,6 +93,9 @@ public class PluginManager {
|
|||||||
this.clearPluginObjects();
|
this.clearPluginObjects();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Insert PluginManager service
|
||||||
|
this.addService(new PluginEntry("PluginManager", new PluginManagerService()));
|
||||||
|
|
||||||
// Start up all plugins that have onload specified
|
// Start up all plugins that have onload specified
|
||||||
this.startupPlugins();
|
this.startupPlugins();
|
||||||
}
|
}
|
||||||
@ -200,15 +210,28 @@ public class PluginManager {
|
|||||||
* this is an async plugin call.
|
* this is an async plugin call.
|
||||||
* @param rawArgs An Array literal string containing any arguments needed in the
|
* @param rawArgs An Array literal string containing any arguments needed in the
|
||||||
* plugin execute method.
|
* plugin execute method.
|
||||||
* @return Whether the task completed synchronously.
|
|
||||||
*/
|
*/
|
||||||
public boolean exec(String service, String action, String callbackId, String rawArgs) {
|
public void exec(final String service, final String action, final String callbackId, final String rawArgs) {
|
||||||
CordovaPlugin plugin = this.getPlugin(service);
|
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) {
|
if (plugin == null) {
|
||||||
Log.d(TAG, "exec() call to unknown plugin: " + service);
|
Log.d(TAG, "exec() call to unknown plugin: " + service);
|
||||||
PluginResult cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION);
|
PluginResult cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION);
|
||||||
app.sendPluginResult(cr, callbackId);
|
app.sendPluginResult(cr, callbackId);
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
CallbackContext callbackContext = new CallbackContext(callbackId, app);
|
CallbackContext callbackContext = new CallbackContext(callbackId, app);
|
||||||
@ -216,19 +239,16 @@ public class PluginManager {
|
|||||||
if (!wasValidAction) {
|
if (!wasValidAction) {
|
||||||
PluginResult cr = new PluginResult(PluginResult.Status.INVALID_ACTION);
|
PluginResult cr = new PluginResult(PluginResult.Status.INVALID_ACTION);
|
||||||
app.sendPluginResult(cr, callbackId);
|
app.sendPluginResult(cr, callbackId);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return callbackContext.isFinished();
|
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
PluginResult cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
|
PluginResult cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION);
|
||||||
app.sendPluginResult(cr, callbackId);
|
app.sendPluginResult(cr, callbackId);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public boolean exec(String service, String action, String callbackId, String jsonArgs, boolean async) {
|
public void exec(String service, String action, String callbackId, String jsonArgs, boolean async) {
|
||||||
return exec(service, action, callbackId, jsonArgs);
|
exec(service, action, callbackId, jsonArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -361,25 +381,6 @@ public class PluginManager {
|
|||||||
return false;
|
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.
|
* Called when the app navigates or refreshes.
|
||||||
*/
|
*/
|
||||||
@ -396,8 +397,42 @@ public class PluginManager {
|
|||||||
|
|
||||||
private void pluginConfigurationMissing() {
|
private void pluginConfigurationMissing() {
|
||||||
LOG.e(TAG, "=====================================================================================");
|
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, "https://git-wip-us.apache.org/repos/asf?p=incubator-cordova-android.git;a=blob;f=framework/res/xml/plugins.xml");
|
||||||
LOG.e(TAG, "=====================================================================================");
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
263
test/src/org/apache/cordova/test/UriResolversTest.java
Normal file
263
test/src/org/apache/cordova/test/UriResolversTest.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -36,7 +36,7 @@ import android.content.Intent;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
public class CordovaWebViewTestActivity extends Activity implements CordovaInterface {
|
public class CordovaWebViewTestActivity extends Activity implements CordovaInterface {
|
||||||
CordovaWebView cordovaWebView;
|
public CordovaWebView cordovaWebView;
|
||||||
|
|
||||||
private final ExecutorService threadPool = Executors.newCachedThreadPool();
|
private final ExecutorService threadPool = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user