Added "DataResource" - allows many plugins to intercept a single request

This commit is contained in:
Shravan Narayan 2013-05-04 17:59:24 -04:00 committed by Braden Shepherdson
parent 8a95ed8ee6
commit 62c3e46529
6 changed files with 288 additions and 36 deletions

View File

@ -26,10 +26,11 @@ 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.util.Locale;
import java.io.OutputStream;
public class FileHelper {
private static final String LOG_TAG = "FileUtils";
@ -90,7 +91,7 @@ public class FileHelper {
* @throws IOException
*/
public static InputStream getInputStreamFromUriString(String uriString, CordovaInterface cordova) throws IOException {
if (uriString.startsWith("content")) {
if (uriString.startsWith("content:")) {
Uri uri = Uri.parse(uriString);
return cordova.getActivity().getContentResolver().openInputStream(uri);
} else if (uriString.startsWith("file:///android_asset/")) {
@ -102,6 +103,57 @@ public class FileHelper {
}
}
public static OutputStream getOutputStreamFromUriString(String uriString, CordovaInterface cordova) throws FileNotFoundException{
if (uriString.startsWith("content:")) {
Uri uri = Uri.parse(uriString);
return cordova.getActivity().getContentResolver().openOutputStream(uri);
} else if (uriString.startsWith("file:") && !uriString.startsWith("file:///android_asset/")) {
String realPath = uriString.substring(7);
return new FileOutputStream(realPath);
} else {
return null;
}
}
/**
* Returns whether the uri can be written to by openeing a File to that uri
*
* @param the URI to test
* @return boolean indicating whether the uri is writable
*/
public static boolean isUriWritable(String uriString) {
String scheme = uriString.split(":")[0];
String writableSchemes[] = new String[]{ "content" };
if(scheme.equals("file")){
// special case file
if(uriString.startsWith("file:///android_asset/")){
return false;
} else {
return true;
}
}
for(int i = writableSchemes.length - 1; i >= 0 ; i--){
if(writableSchemes[i].equals(scheme)){
return true;
}
}
return false;
}
/**
* Ensures the "file://" prefix exists for the given string
* If the given URI string has a "file://" prefix, it is returned unchanged
*
* @param path - the path string to operate on
* @return a String with the "file://" scheme set
*/
public static String insertFileProtocol(String path) {
if(!path.matches("^[a-z0-9+.-]+:.*")){
path = "file://" + path;
}
return path;
}
/**
* Removes the "file://" prefix from the given URI string, if applicable.
* If the given URI string doesn't have a "file://" prefix, it is returned unchanged.

View File

@ -19,9 +19,10 @@
package org.apache.cordova;
import java.io.IOException;
import java.io.InputStream;
import org.apache.cordova.api.CordovaInterface;
import org.apache.cordova.api.DataResource;
import org.apache.cordova.api.DataResourceContext;
import org.apache.cordova.api.LOG;
import android.annotation.TargetApi;
@ -43,41 +44,24 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
// We need to support the new DataResource intercepts without breaking the shouldInterceptRequest mechanism.
DataResource dataResource = DataResource.initiateNewDataRequestForUri(url, this.appView.pluginManager, cordova,
new DataResourceContext("WebViewClient.shouldInterceptRequest", true /* this is from a browser request*/));
url = dataResource.getUri().toString();
//Check if plugins intercept the request
WebResourceResponse ret = super.shouldInterceptRequest(view, url);
if(ret == null && (url.contains("?") || url.contains("#") || needsIceCreamSpaceInAssetUrlFix(url))){
ret = generateWebResourceResponse(url);
// The below bugfix is taken care of by the dataResource mechanism
// if(ret == null && (url.contains("?") || url.contains("#") || needsIceCreamSpaceInAssetUrlFix(url))){
// ret = generateWebResourceResponse(url);
// }
if(ret == null) {
try {
ret = new WebResourceResponse(dataResource.getMimeType(), "UTF-8", dataResource.getIs());
} catch(IOException e) {
LOG.e("IceCreamCordovaWebViewClient", "Error occurred while loading a file.", e);
}
}
return ret;
}
private WebResourceResponse generateWebResourceResponse(String url) {
if (url.startsWith("file:///android_asset/")) {
String mimetype = FileHelper.getMimeType(url, cordova);
try {
InputStream stream = FileHelper.getInputStreamFromUriString(url, cordova);
WebResourceResponse response = new WebResourceResponse(mimetype, "UTF-8", stream);
return response;
} catch (IOException e) {
LOG.e("generateWebResourceResponse", e.getMessage(), e);
}
}
return null;
}
private static boolean needsIceCreamSpaceInAssetUrlFix(String url) {
if (!url.contains("%20")){
return false;
}
switch(android.os.Build.VERSION.SDK_INT){
case android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH:
case android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1:
return true;
default:
return false;
}
}
}

View File

@ -175,6 +175,20 @@ public class CordovaPlugin {
return null;
}
/**
* All plugins can now choose if they want to modify any uri requests. This includes all webview requests, opening of files, content uri's etc.
* This mechanism allows several plugins to modify the same request
* @param requestSource The source of the incoming request
*
* @param dataResource The resource to be loaded.
* @param dataResourceContext Context associated with the resource load
* @return Return a new DataResource if the plugin wants o assist in loading the request or null if it doesn't.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public DataResource shouldInterceptDataResourceRequest(DataResource dataResource, DataResourceContext dataResourceContext) {
return null;
}
/**
* Called when the WebView does a top-level navigation or refreshes.
*

View File

@ -0,0 +1,138 @@
package org.apache.cordova.api;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.cordova.FileHelper;
import android.net.Uri;
/*
* All requests to access files, browser network requests etc have to go through this class.
*/
public class DataResource {
private CordovaInterface cordova;
// Uri of the request. Always required.
private Uri uri;
// Remaining fields may or may not be null
private InputStream is;
private OutputStream os;
private String mimeType;
private Boolean writable;
private File realFile;
private boolean retryLoad = true;
public DataResource(CordovaInterface cordova, Uri uri) {
super();
this.cordova = cordova;
this.uri = uri;
}
public DataResource(CordovaInterface cordova, Uri uri, InputStream is,
OutputStream os, String mimeType, boolean writable, File realFile) {
this(cordova, uri);
this.is = is;
this.mimeType = mimeType;
this.writable = Boolean.valueOf(writable);
this.realFile = realFile;
}
public Uri getUri() {
// Uri is always provided
return uri;
}
public InputStream getIs() throws IOException {
if(is == null && retryLoad) {
try {
is = FileHelper.getInputStreamFromUriString(uri.toString(), cordova);
} finally {
// We failed loading once, don't try loading anymore
if(is == null) {
retryLoad = false;
}
}
}
return is;
}
public OutputStream getOs() throws FileNotFoundException {
if(os == null && retryLoad) {
try {
os = FileHelper.getOutputStreamFromUriString(uri.toString(), cordova);
} finally {
// We failed loading once, don't try loading anymore
if(os == null) {
retryLoad = false;
}
}
}
return os;
}
public String getMimeType() {
if(mimeType == null && retryLoad) {
try {
mimeType = FileHelper.getMimeType(uri.toString(), cordova);
} finally {
// We failed loading once, don't try loading anymore
if(mimeType == null) {
retryLoad = false;
}
}
}
return mimeType;
}
public boolean isWritable() {
if(writable == null && retryLoad) {
try {
writable = FileHelper.isUriWritable(uri.toString());
} finally {
// We failed loading once, don't try loading anymore
if(writable == null) {
retryLoad = false;
}
}
}
// default to false
return writable != null? writable.booleanValue() : false;
}
public File getRealFile() {
if(realFile == null && retryLoad) {
try {
String realPath = FileHelper.getRealPath(uri, cordova);
if(realPath != null) {
realFile = new File(realPath);
}
} finally {
// We failed loading once, don't try loading anymore
if(realFile == null) {
retryLoad = false;
}
}
}
return realFile;
}
// static instantiation methods
public static DataResource initiateNewDataRequestForUri(String uriString, PluginManager pluginManager, CordovaInterface cordova, String requestSourceTag){
// if no protocol is specified, assume its file:
uriString = FileHelper.insertFileProtocol(uriString);
return initiateNewDataRequestForUri(Uri.parse(uriString), pluginManager, cordova, requestSourceTag);
}
public static DataResource initiateNewDataRequestForUri(Uri uri, PluginManager pluginManager, CordovaInterface cordova, String requestSourceTag){
return initiateNewDataRequestForUri(uri, pluginManager, cordova, new DataResourceContext(requestSourceTag, false /* Assume, not a browser request by default */ ));
}
public static DataResource initiateNewDataRequestForUri(String uriString, PluginManager pluginManager, CordovaInterface cordova, DataResourceContext dataResourceContext){
// if no protocol is specified, assume its file:
uriString = FileHelper.insertFileProtocol(uriString);
return initiateNewDataRequestForUri(Uri.parse(uriString), pluginManager, cordova, dataResourceContext);
}
public static DataResource initiateNewDataRequestForUri(Uri uri, PluginManager pluginManager, CordovaInterface cordova, DataResourceContext dataResourceContext){
DataResource dataResource = new DataResource(cordova, uri);
if (pluginManager != null) {
// get the resource as returned by plugins
dataResource = pluginManager.shouldInterceptDataResourceRequest(dataResource, dataResourceContext);
}
return dataResource;
}
}

View File

@ -0,0 +1,38 @@
package org.apache.cordova.api;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/*
* Some context information associated with a DataRequest.
*/
public class DataResourceContext {
// A random id that is unique for a particular request.
private int requestId;
// A tag associated with the source of this dataResourceContext
private String source;
// If needed, any data associated with core plugins can be a part of the context object
// This field indicates whether the request came from a browser network request
private boolean isFromBrowser;
// If needed, any data associated with non core plugins should store data in a Map so as to not clutter the context object
private Map<String, Object> dataMap;
public DataResourceContext(String source, boolean isFromBrowser) {
super();
this.requestId = new Random().nextInt();
this.source = source;
this.isFromBrowser = isFromBrowser;
this.dataMap = new HashMap<String, Object>();
}
public int getRequestId() {
return requestId;
}
public String getSource() {
return source;
}
public boolean isFromBrowser() {
return isFromBrowser;
}
public Map<String, Object> getDataMap() {
return dataMap;
}
}

View File

@ -400,4 +400,30 @@ public class PluginManager {
LOG.e(TAG, "https://git-wip-us.apache.org/repos/asf?p=incubator-cordova-android.git;a=blob;f=framework/res/xml/plugins.xml");
LOG.e(TAG, "=====================================================================================");
}
/**
* Called when the any resource is going to be loaded - either from the webview, files or any other resource
*
*
* @param dataResource The resource request to be loaded.
* @param dataResourceContext The context of the dataResource request
* @return Return the resource request that will be loaded. The returned request may be modified or unchanged.
*/
public DataResource shouldInterceptDataResourceRequest(DataResource dataResource, DataResourceContext dataResourceContext){
boolean requestModified = true;
while(requestModified) {
requestModified = false;
for (PluginEntry entry : this.entries.values()) {
if (entry.plugin != null) {
DataResource ret = entry.plugin.shouldInterceptDataResourceRequest(dataResource, dataResourceContext);
if(ret != null) {
dataResource = ret;
requestModified = true;
break;
}
}
}
}
return dataResource;
}
}