You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ag...@apache.org on 2013/07/15 21:18:59 UTC

android commit: [CB-3384] Reworked UriResolver into CordovaResourceApi.

Updated Branches:
  refs/heads/master 210d7c76e -> 77e909210


[CB-3384] Reworked UriResolver into CordovaResourceApi.

Changes were made after trying to use the API for Camera, FileTransfer, Media.
The main difference is separating the concept of URI remapping from the read/write helpers.


Project: http://git-wip-us.apache.org/repos/asf/cordova-android/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-android/commit/77e90921
Tree: http://git-wip-us.apache.org/repos/asf/cordova-android/tree/77e90921
Diff: http://git-wip-us.apache.org/repos/asf/cordova-android/diff/77e90921

Branch: refs/heads/master
Commit: 77e9092108b4997abdc08afa630b8c16ee094f90
Parents: 210d7c7
Author: Andrew Grieve <ag...@chromium.org>
Authored: Sun Jul 14 21:39:55 2013 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Mon Jul 15 15:18:39 2013 -0400

----------------------------------------------------------------------
 .../src/org/apache/cordova/CordovaPlugin.java   |   8 +-
 .../org/apache/cordova/CordovaResourceApi.java  | 347 +++++++++++++++++++
 .../src/org/apache/cordova/CordovaWebView.java  |  39 +--
 .../apache/cordova/CordovaWebViewClient.java    |   2 +-
 .../cordova/IceCreamCordovaWebViewClient.java   |  61 ++--
 .../src/org/apache/cordova/PluginManager.java   |   6 +-
 .../src/org/apache/cordova/UriResolver.java     |  69 ----
 .../src/org/apache/cordova/UriResolvers.java    | 341 ------------------
 test/AndroidManifest.xml                        |   2 +-
 .../cordova/test/CordovaResourceApiTest.java    | 279 +++++++++++++++
 .../apache/cordova/test/UriResolversTest.java   | 263 --------------
 11 files changed, 672 insertions(+), 745 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/77e90921/framework/src/org/apache/cordova/CordovaPlugin.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CordovaPlugin.java b/framework/src/org/apache/cordova/CordovaPlugin.java
index 456633b..455f56f 100644
--- a/framework/src/org/apache/cordova/CordovaPlugin.java
+++ b/framework/src/org/apache/cordova/CordovaPlugin.java
@@ -22,7 +22,6 @@ import org.apache.cordova.CordovaArgs;
 import org.apache.cordova.CordovaWebView;
 import org.apache.cordova.CordovaInterface;
 import org.apache.cordova.CallbackContext;
-import org.apache.cordova.UriResolver;
 import org.json.JSONArray;
 import org.json.JSONException;
 
@@ -165,13 +164,12 @@ public class CordovaPlugin {
     }
 
     /**
-     * Hook for overriding the default URI handling mechanism.
-     * Applies to WebView requests as well as requests made by plugins.
+     * Hook for redirecting requests. Applies to WebView requests as well as requests made by plugins.
      */
-    public UriResolver resolveUri(Uri uri) {
+    public Uri remapUri(Uri uri) {
         return null;
     }
-
+    
     /**
      * Called when the WebView does a top-level navigation or refreshes.
      *

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/77e90921/framework/src/org/apache/cordova/CordovaResourceApi.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CordovaResourceApi.java b/framework/src/org/apache/cordova/CordovaResourceApi.java
new file mode 100644
index 0000000..b891b51
--- /dev/null
+++ b/framework/src/org/apache/cordova/CordovaResourceApi.java
@@ -0,0 +1,347 @@
+/*
+       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 android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.AssetManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Looper;
+import android.util.Base64;
+import android.util.Base64InputStream;
+
+import com.squareup.okhttp.OkHttpClient;
+
+import org.apache.http.util.EncodingUtils;
+
+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 java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.channels.FileChannel;
+
+public class CordovaResourceApi {
+    @SuppressWarnings("unused")
+    private static final String LOG_TAG = "CordovaResourceApi";
+
+    public static final int URI_TYPE_FILE = 0;
+    public static final int URI_TYPE_ASSET = 1;
+    public static final int URI_TYPE_CONTENT = 2;
+    public static final int URI_TYPE_RESOURCE = 3;
+    public static final int URI_TYPE_DATA = 4;
+    public static final int URI_TYPE_HTTP = 5;
+    public static final int URI_TYPE_HTTPS = 6;
+    public static final int URI_TYPE_UNKNOWN = -1;
+    
+    private static final String[] LOCAL_FILE_PROJECTION = { "_data" };
+    
+    // Creating this is light-weight.
+    private static OkHttpClient httpClient = new OkHttpClient();
+    
+    static Thread webCoreThread;
+
+    private final AssetManager assetManager;
+    private final ContentResolver contentResolver;
+    private final PluginManager pluginManager;
+    private boolean threadCheckingEnabled = true;
+
+
+    public CordovaResourceApi(Context context, PluginManager pluginManager) {
+        this.contentResolver = context.getContentResolver();
+        this.assetManager = context.getAssets();
+        this.pluginManager = pluginManager;
+    }
+    
+    public void setThreadCheckingEnabled(boolean value) {
+        threadCheckingEnabled = value;
+    }
+
+    public boolean isThreadCheckingEnabled() {
+        return threadCheckingEnabled;
+    }
+    
+    public static int getUriType(Uri uri) {
+        assertNonRelative(uri);
+        String scheme = uri.getScheme();
+        if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
+            return URI_TYPE_CONTENT;
+        }
+        if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
+            return URI_TYPE_RESOURCE;
+        }
+        if (ContentResolver.SCHEME_FILE.equals(scheme)) {
+            if (uri.getPath().startsWith("/android_asset/")) {
+                return URI_TYPE_ASSET;
+            }
+            return URI_TYPE_FILE;
+        }
+        if ("data".equals(scheme)) {
+            return URI_TYPE_DATA;
+        }
+        if ("http".equals(scheme)) {
+            return URI_TYPE_HTTP;
+        }
+        if ("https".equals(scheme)) {
+            return URI_TYPE_HTTPS;
+        }
+        return URI_TYPE_UNKNOWN;
+    }
+    
+    public Uri remapUri(Uri uri) {
+        assertNonRelative(uri);
+        Uri pluginUri = pluginManager.remapUri(uri);
+        return pluginUri != null ? pluginUri : uri;
+    }
+
+    public String remapPath(String path) {
+        return remapUri(Uri.fromFile(new File(path))).getPath();
+    }
+    
+    /**
+     * Returns a File that points to the resource, or null if the resource
+     * is not on the local filesystem.
+     */
+    public File mapUriToFile(Uri uri) {
+        assertBackgroundThread();
+        switch (getUriType(uri)) {
+            case URI_TYPE_FILE:
+                return new File(uri.getPath());
+            case URI_TYPE_CONTENT: {
+                Cursor cursor = contentResolver.query(uri, LOCAL_FILE_PROJECTION, null, null, null);
+                if (cursor != null) {
+                    try {
+                        int columnIndex = cursor.getColumnIndex(LOCAL_FILE_PROJECTION[0]);
+                        if (columnIndex != -1 && cursor.getCount() > 0) {
+                            cursor.moveToFirst();
+                            String realPath = cursor.getString(columnIndex);
+                            if (realPath != null) {
+                                return new File(realPath);
+                            }
+                        }
+                    } finally {
+                        cursor.close();
+                    }
+                }
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * Opens a stream to the givne URI, also providing the MIME type & length.
+     * @return Never returns null.
+     * @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
+     *     resolved before being passed into this function.
+     * @throws Throws an IOException if the URI cannot be opened.
+     */
+    public OpenForReadResult openForRead(Uri uri) throws IOException {
+        assertBackgroundThread();
+        switch (getUriType(uri)) {
+            case URI_TYPE_FILE: {
+                FileInputStream inputStream = new FileInputStream(uri.getPath());
+                String mimeType = FileHelper.getMimeTypeForExtension(uri.getPath());
+                long length = inputStream.getChannel().size();
+                return new OpenForReadResult(uri, inputStream, mimeType, length, null);
+            }
+            case URI_TYPE_ASSET: {
+                String assetPath = uri.getPath().substring(15);
+                AssetFileDescriptor assetFd = null;
+                InputStream inputStream;
+                long length = -1;
+                try {
+                    assetFd = assetManager.openFd(assetPath);
+                    inputStream = assetFd.createInputStream();
+                    length = assetFd.getLength();
+                } catch (FileNotFoundException e) {
+                    // Will occur if the file is compressed.
+                    inputStream = assetManager.open(assetPath);
+                }
+                String mimeType = FileHelper.getMimeTypeForExtension(assetPath);
+                return new OpenForReadResult(uri, inputStream, mimeType, length, assetFd);
+            }
+            case URI_TYPE_CONTENT:
+            case URI_TYPE_RESOURCE: {
+                String mimeType = contentResolver.getType(uri);
+                AssetFileDescriptor assetFd = contentResolver.openAssetFileDescriptor(uri, "r");
+                InputStream inputStream = assetFd.createInputStream();
+                long length = assetFd.getLength();
+                return new OpenForReadResult(uri, inputStream, mimeType, length, assetFd);
+            }
+            case URI_TYPE_DATA: {
+                OpenForReadResult ret = readDataUri(uri);
+                if (ret == null) {
+                    break;
+                }
+                return ret;
+            }
+            case URI_TYPE_HTTP:
+            case URI_TYPE_HTTPS: {
+                HttpURLConnection conn = httpClient.open(new URL(uri.toString()));
+                conn.setDoInput(true);
+                String mimeType = conn.getHeaderField("Content-Type");
+                int length = conn.getContentLength();
+                InputStream inputStream = conn.getInputStream();
+                return new OpenForReadResult(uri, inputStream, mimeType, length, null);
+            }
+        }
+        throw new FileNotFoundException("URI not supported by CordovaResourceApi: " + uri);
+    }
+
+    public OutputStream openOutputStream(Uri uri) throws IOException {
+        return openOutputStream(uri, false);
+    }
+
+    /**
+     * Opens a stream to the given URI.
+     * @return Never returns null.
+     * @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
+     *     resolved before being passed into this function.
+     * @throws Throws an IOException if the URI cannot be opened.
+     */
+    public OutputStream openOutputStream(Uri uri, boolean append) throws IOException {
+        assertBackgroundThread();
+        switch (getUriType(uri)) {
+            case URI_TYPE_FILE: {
+                File localFile = new File(uri.getPath());
+                File parent = localFile.getParentFile();
+                if (parent != null) {
+                    parent.mkdirs();
+                }
+                return new FileOutputStream(localFile, append);
+            }
+            case URI_TYPE_CONTENT:
+            case URI_TYPE_RESOURCE: {
+                AssetFileDescriptor assetFd = contentResolver.openAssetFileDescriptor(uri, append ? "wa" : "w");
+                return assetFd.createOutputStream();
+            }
+        }
+        throw new FileNotFoundException("URI not supported by CordovaResourceApi: " + uri);
+    }
+    
+    public HttpURLConnection createHttpConnection(Uri uri) throws IOException {
+        assertBackgroundThread();
+        return httpClient.open(new URL(uri.toString()));
+    }
+    
+    // Copies the input to the output in the most efficient manner possible.
+    // Closes both streams.
+    public void copyResource(OpenForReadResult input, OutputStream outputStream) throws IOException {
+        assertBackgroundThread();
+        try {
+            InputStream inputStream = input.inputStream;
+            if (inputStream instanceof FileInputStream && outputStream instanceof FileOutputStream) {
+                FileChannel inChannel = ((FileInputStream)input.inputStream).getChannel();
+                FileChannel outChannel = ((FileOutputStream)outputStream).getChannel();
+                long offset = 0;
+                long length = input.length;
+                if (input.assetFd != null) {
+                    offset = input.assetFd.getStartOffset();
+                }
+                outChannel.transferFrom(inChannel, offset, length);
+            } else {
+                final int BUFFER_SIZE = 8192;
+                byte[] buffer = new byte[BUFFER_SIZE];
+                
+                for (;;) {
+                    int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
+                    
+                    if (bytesRead <= 0) {
+                        break;
+                    }
+                    outputStream.write(buffer, 0, bytesRead);
+                }
+            }            
+        } finally {
+            input.inputStream.close();
+            if (outputStream != null) {
+                outputStream.close();
+            }
+        }
+    }
+
+    public void copyResource(Uri sourceUri, OutputStream outputStream) throws IOException {
+        copyResource(openForRead(sourceUri), outputStream);
+    }
+
+    
+    private void assertBackgroundThread() {
+        if (threadCheckingEnabled) {
+            Thread curThread = Thread.currentThread();
+            if (curThread == Looper.getMainLooper().getThread()) {
+                throw new IllegalStateException("Do not perform IO operations on the UI thread. Use CordovaInterface.getThreadPool() instead.");
+            }
+            if (curThread == webCoreThread) {
+                throw new IllegalStateException("Tried to perform an IO operation on the WebCore thread. Use CordovaInterface.getThreadPool() instead.");
+            }
+        }
+    }
+    
+    private OpenForReadResult readDataUri(Uri uri) {
+        String uriAsString = uri.toString().substring(5);
+        int commaPos = uriAsString.indexOf(',');
+        if (commaPos == -1) {
+            return null;
+        }
+        String[] mimeParts = uriAsString.substring(0, commaPos).split(";");
+        String contentType = null;
+        boolean base64 = false;
+        if (mimeParts.length > 0) {
+            contentType = mimeParts[0];
+        }
+        for (int i = 1; i < mimeParts.length; ++i) {
+            if ("base64".equalsIgnoreCase(mimeParts[i])) {
+                base64 = true;
+            }
+        }
+        String dataPartAsString = uriAsString.substring(commaPos + 1);
+        byte[] data = base64 ? Base64.decode(dataPartAsString, Base64.DEFAULT) : EncodingUtils.getBytes(dataPartAsString, "UTF-8");
+        InputStream inputStream = new ByteArrayInputStream(data);
+        return new OpenForReadResult(uri, inputStream, contentType, data.length, null);
+    }
+    
+    private static void assertNonRelative(Uri uri) {
+        if (!uri.isAbsolute()) {
+            throw new IllegalArgumentException("Relative URIs are not supported.");
+        }
+    }
+    
+    public static final class OpenForReadResult {
+        public final Uri uri;
+        public final InputStream inputStream;
+        public final String mimeType;
+        public final long length;
+        public final AssetFileDescriptor assetFd;
+        
+        OpenForReadResult(Uri uri, InputStream inputStream, String mimeType, long length, AssetFileDescriptor assetFd) {
+            this.uri = uri;
+            this.inputStream = inputStream;
+            this.mimeType = mimeType;
+            this.length = length;
+            this.assetFd = assetFd;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/77e90921/framework/src/org/apache/cordova/CordovaWebView.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CordovaWebView.java b/framework/src/org/apache/cordova/CordovaWebView.java
index 1035f3a..06cee9c 100755
--- a/framework/src/org/apache/cordova/CordovaWebView.java
+++ b/framework/src/org/apache/cordova/CordovaWebView.java
@@ -23,8 +23,6 @@ import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.Stack;
-import java.util.regex.Pattern;
 
 import org.apache.cordova.Config;
 import org.apache.cordova.CordovaInterface;
@@ -96,6 +94,8 @@ public class CordovaWebView extends WebView {
 
     private ActivityResult mResult = null;
 
+    private CordovaResourceApi resourceApi;
+
     class ActivityResult {
         
         int request;
@@ -306,6 +306,7 @@ public class CordovaWebView extends WebView {
         pluginManager = new PluginManager(this, this.cordova);
         jsMessageQueue = new NativeToJsMessageQueue(this, cordova);
         exposedJsApi = new ExposedJsApi(pluginManager, jsMessageQueue);
+        resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
         exposeJsInterface();
     }
 
@@ -955,37 +956,7 @@ public class CordovaWebView extends WebView {
         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.");
-        }
-        UriResolver ret = null;
-        // 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);
-            ret = UriResolvers.createError("Whitelist rejection for: " + uri);
-        } else {
-            // Give plugins a chance to handle the request.
-            ret = ((org.apache.cordova.PluginManager)pluginManager).resolveUri(uri);
-        }
-        if (ret == null && !fromWebView) {
-            ret = UriResolvers.forUri(uri, cordova.getActivity());
-            if (ret == null) {
-                ret = UriResolvers.createError("Unresolvable URI: " + uri);
-            }
-        }
-        return ret == null ? null : UriResolvers.makeThreadChecking(ret);
+    public CordovaResourceApi getResourceApi() {
+        return resourceApi;
     }
 }

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/77e90921/framework/src/org/apache/cordova/CordovaWebViewClient.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CordovaWebViewClient.java b/framework/src/org/apache/cordova/CordovaWebViewClient.java
index 6dbc4ef..b3a31ae 100755
--- a/framework/src/org/apache/cordova/CordovaWebViewClient.java
+++ b/framework/src/org/apache/cordova/CordovaWebViewClient.java
@@ -48,7 +48,7 @@ import android.webkit.WebViewClient;
  */
 public class CordovaWebViewClient extends WebViewClient {
 
-	private static final String TAG = "Cordova";
+	private static final String TAG = "CordovaWebViewClient";
 	private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
     CordovaInterface cordova;
     CordovaWebView appView;

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/77e90921/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java b/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
index 9000736..d1e632d 100644
--- a/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
+++ b/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
@@ -19,20 +19,22 @@
 package org.apache.cordova;
 
 import java.io.IOException;
-import java.io.InputStream;
 
 import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CordovaResourceApi.OpenForReadResult;
 import org.apache.cordova.LOG;
 
 import android.annotation.TargetApi;
 import android.net.Uri;
 import android.os.Build;
+import android.util.Log;
 import android.webkit.WebResourceResponse;
 import android.webkit.WebView;
 
 @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
 
+    private static final String TAG = "IceCreamCordovaWebViewClient";
 
     public IceCreamCordovaWebViewClient(CordovaInterface cordova) {
         super(cordova);
@@ -44,38 +46,44 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
 
     @Override
     public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
-        // Disable checks during shouldInterceptRequest since there is no way to avoid IO here :(.
-        UriResolvers.webCoreThread = null;
+        // Tell the Thread-Checking resolve what thread the WebCore thread is.
+        CordovaResourceApi.webCoreThread = Thread.currentThread();
+        Log.e("WHAAAA", "FOOD " + CordovaResourceApi.webCoreThread);
         try {
-            UriResolver uriResolver = appView.resolveUri(Uri.parse(url), true);
-            
-            if (uriResolver == null && url.startsWith("file:///android_asset/")) {
-                if (url.contains("?") || url.contains("#") || needsIceCreamSpecialsInAssetUrlFix(url)) {
-                    uriResolver = appView.resolveUri(Uri.parse(url), false);
-                }
+            // Check the against the white-list.
+            if ((url.startsWith("http:") || url.startsWith("https:")) && !Config.isUrlWhiteListed(url)) {
+                LOG.w(TAG, "URL blocked by whitelist: " + url);
+                // Results in a 404.
+                return new WebResourceResponse("text/plain", "UTF-8", null);
             }
+
+            CordovaResourceApi resourceApi = appView.getResourceApi();
+            Uri origUri = Uri.parse(url);
+            // Allow plugins to intercept WebView requests.
+            Uri remappedUri = resourceApi.remapUri(origUri);
             
-            if (uriResolver != null) {
-                try {
-                    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("IceCreamCordovaWebViewClient", "Error occurred while loading a file.", e);
-                    // Results in a 404.
-                    return new WebResourceResponse("text/plain", "UTF-8", null);
-                }
+            if (!origUri.equals(remappedUri) || needsSpecialsInAssetUrlFix(origUri)) {
+                OpenForReadResult result = resourceApi.openForRead(remappedUri);
+                return new WebResourceResponse(result.mimeType, "UTF-8", result.inputStream);
             }
+            // If we don't need to special-case the request, let the browser load it.
             return null;
-        } finally {
-            // Tell the Thread-Checking resolve what thread the WebCore thread is.
-            UriResolvers.webCoreThread = Thread.currentThread();
+        } catch (IOException e) {
+            LOG.e("IceCreamCordovaWebViewClient", "Error occurred while loading a file.", e);
+            // Results in a 404.
+            return new WebResourceResponse("text/plain", "UTF-8", null);
         }
     }
+
+    private static boolean needsSpecialsInAssetUrlFix(Uri uri) {
+        if (CordovaResourceApi.getUriType(uri) != CordovaResourceApi.URI_TYPE_ASSET) {
+            return false;
+        }
+        if (uri.getQuery() != null || uri.getFragment() != null) {
+            return true;
+        }
         
-    private static boolean needsIceCreamSpecialsInAssetUrlFix(String url) {
-        if (!url.contains("%20")){
+        if (!uri.toString().contains("%")) {
             return false;
         }
 
@@ -83,8 +91,7 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
             case android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH:
             case android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1:
                 return true;
-            default:
-                return false;
         }
+        return false;
     }
 }

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/77e90921/framework/src/org/apache/cordova/PluginManager.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/PluginManager.java b/framework/src/org/apache/cordova/PluginManager.java
index 768f253..3d0169a 100755
--- a/framework/src/org/apache/cordova/PluginManager.java
+++ b/framework/src/org/apache/cordova/PluginManager.java
@@ -26,7 +26,6 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.cordova.CordovaArgs;
 import org.apache.cordova.CordovaWebView;
-import org.apache.cordova.UriResolver;
 import org.apache.cordova.CallbackContext;
 import org.apache.cordova.CordovaInterface;
 import org.apache.cordova.CordovaPlugin;
@@ -40,7 +39,6 @@ import android.content.res.XmlResourceParser;
 
 import android.net.Uri;
 import android.util.Log;
-import android.webkit.WebResourceResponse;
 
 /**
  * PluginManager is exposed to JavaScript in the Cordova WebView.
@@ -394,10 +392,10 @@ public class PluginManager {
         LOG.e(TAG, "=====================================================================================");
     }
 
-    UriResolver resolveUri(Uri uri) {
+    Uri remapUri(Uri uri) {
         for (PluginEntry entry : this.entries.values()) {
             if (entry.plugin != null) {
-                UriResolver ret = entry.plugin.resolveUri(uri);
+                Uri ret = entry.plugin.remapUri(uri);
                 if (ret != null) {
                     return ret;
                 }

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/77e90921/framework/src/org/apache/cordova/UriResolver.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/UriResolver.java b/framework/src/org/apache/cordova/UriResolver.java
deleted file mode 100644
index b3bfa4d..0000000
--- a/framework/src/org/apache/cordova/UriResolver.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
-       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;
-
-/*
- * Interface for a class that can resolve URIs.
- * See CordovaUriResolver for an example.
- */
-public abstract class UriResolver {
-
-    /** 
-     * Returns the InputStream for the resource. 
-     * Throws an exception if it cannot be read. 
-     * Never returns null.
-     */
-    public abstract InputStream getInputStream() 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).
-     */
-    public abstract String getMimeType();
-
-    /** Returns whether the resource is writable. */
-    public abstract boolean isWritable();
-
-    /**
-     * Returns a File that points to the resource, or null if the resource
-     * is not on the local file system.
-     */
-    public abstract File getLocalFile();
-
-    /** 
-     * Returns the OutputStream for the resource. 
-     * Throws an exception if it cannot be written to. 
-     * Never returns null.
-     */
-    public OutputStream getOutputStream() throws IOException {
-        throw new IOException("Writing is not suppported");
-    }
-    
-    /**
-     * Returns the length of the input stream, or -1 if it is not computable.
-     */
-    public long computeLength() throws IOException {
-        return -1;
-    }
-}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/77e90921/framework/src/org/apache/cordova/UriResolvers.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/UriResolvers.java b/framework/src/org/apache/cordova/UriResolvers.java
deleted file mode 100644
index 294fc6b..0000000
--- a/framework/src/org/apache/cordova/UriResolvers.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
-       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;
-import android.os.Looper;
-
-/*
- * UriResolver implementations.
- */
-public final class UriResolvers {
-    static Thread webCoreThread;
-
-    private UriResolvers() {}
-
-    private static long computeSizeFromResolver(UriResolver resolver) throws IOException {
-        InputStream inputStream = resolver.getInputStream();
-        if (inputStream instanceof FileInputStream) {
-            return ((FileInputStream)inputStream).getChannel().size();
-        }
-        if (inputStream instanceof ByteArrayInputStream) {
-            return ((ByteArrayInputStream)inputStream).available();
-        }
-        return -1;
-    }
-    
-    private static final class FileUriResolver extends UriResolver {
-        private final File localFile;
-        private String mimeType;
-        private FileInputStream cachedInputStream;
-    
-        FileUriResolver(Uri uri) {
-            localFile = new File(uri.getPath());
-        }
-        
-        public InputStream getInputStream() throws IOException {
-            if (cachedInputStream == null) {
-                cachedInputStream = new FileInputStream(localFile);
-            }
-            return cachedInputStream;
-        }
-        
-        public OutputStream getOutputStream() throws FileNotFoundException {
-            File parent = localFile.getParentFile();
-            if (parent != null) {
-                localFile.getParentFile().mkdirs();
-            }
-            return new FileOutputStream(localFile);
-        }
-        
-        public String getMimeType() {
-            if (mimeType == null) {
-                mimeType = FileHelper.getMimeTypeForExtension(localFile.getName());
-            }
-            return mimeType;
-        }
-        
-        public boolean isWritable() {
-            if (localFile.isDirectory()) {
-                return false;
-            }
-            if (localFile.exists()) {
-                return localFile.canWrite();
-            }
-            return localFile.getParentFile().canWrite();
-        }
-        
-        public File getLocalFile() {
-            return localFile;
-        }
-        
-        public long computeLength() throws IOException {
-            return localFile.length();
-        }
-    }
-    
-    private static final class AssetUriResolver extends UriResolver {
-        private final AssetManager assetManager;
-        private final String assetPath;
-        private String mimeType;
-        private InputStream cachedInputStream;
-    
-        AssetUriResolver(Uri uri, AssetManager assetManager) {
-            this.assetManager = assetManager;
-            this.assetPath = uri.getPath().substring(15);
-        }
-        
-        public InputStream getInputStream() throws IOException {
-            if (cachedInputStream == null) {
-                cachedInputStream = assetManager.open(assetPath);
-            }
-            return cachedInputStream;
-        }
-        
-        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;
-        }
-
-        public long computeLength() throws IOException {
-            return computeSizeFromResolver(this);
-        }
-    }
-    
-    private static final class ContentUriResolver extends UriResolver {
-        private final Uri uri;
-        private final ContentResolver contentResolver;
-        private String mimeType;
-        private InputStream cachedInputStream;
-    
-        ContentUriResolver(Uri uri, ContentResolver contentResolver) {
-            this.uri = uri;
-            this.contentResolver = contentResolver;
-        }
-        
-        public InputStream getInputStream() throws IOException {
-            if (cachedInputStream == null) {
-                cachedInputStream = contentResolver.openInputStream(uri);
-            }
-            return cachedInputStream;
-        }
-        
-        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;
-        }
-        
-        public long computeLength() throws IOException {
-            return computeSizeFromResolver(this);
-        }
-    }
-    
-    private static final class ErrorUriResolver extends UriResolver {
-        final String errorMsg;
-        
-        ErrorUriResolver(String errorMsg) {
-            this.errorMsg = errorMsg;
-        }
-        
-        public boolean isWritable() {
-            return false;
-        }
-        
-        public File getLocalFile() {
-            return null;
-        }
-        
-        public OutputStream getOutputStream() throws IOException {
-            throw new FileNotFoundException(errorMsg);
-        }
-        
-        public String getMimeType() {
-            return null;
-        }
-        
-        public InputStream getInputStream() throws IOException {
-            throw new FileNotFoundException(errorMsg);
-        }
-    }
-    
-    private static final class ReadOnlyResolver extends UriResolver {
-        private InputStream inputStream;
-        private String mimeType;
-        
-        public ReadOnlyResolver(Uri uri, InputStream inputStream, String mimeType) {
-            this.inputStream = inputStream;
-            this.mimeType = mimeType;
-        }
-        
-        public boolean isWritable() {
-            return false;
-        }
-        
-        public File getLocalFile() {
-            return null;
-        }
-        
-        public OutputStream getOutputStream() throws IOException {
-            throw new FileNotFoundException("URI is not writable");
-        }
-        
-        public String getMimeType() {
-            return mimeType;
-        }
-        
-        public InputStream getInputStream() throws IOException {
-            return inputStream;
-        }
-        
-        public long computeLength() throws IOException {
-            return computeSizeFromResolver(this);
-        }
-    }
-    
-    private static final class ThreadCheckingResolver extends UriResolver {
-        final UriResolver delegate;
-        
-        ThreadCheckingResolver(UriResolver delegate) {
-            this.delegate = delegate;
-        }
-
-        private static void checkThread() {
-            Thread curThread = Thread.currentThread();
-            if (curThread == Looper.getMainLooper().getThread()) {
-                throw new IllegalStateException("Do not perform IO operations on the UI thread. Use CordovaInterface.getThreadPool() instead.");
-            }
-            if (curThread == webCoreThread) {
-                throw new IllegalStateException("Tried to perform an IO operation on the WebCore thread. Use CordovaInterface.getThreadPool() instead.");
-            }
-        }
-        
-        public boolean isWritable() {
-            checkThread();
-            return delegate.isWritable();
-        }
-        
-
-        public File getLocalFile() {
-            checkThread();
-            return delegate.getLocalFile();
-        }
-        
-        public OutputStream getOutputStream() throws IOException {
-            checkThread();
-            return delegate.getOutputStream();
-        }
-        
-        public String getMimeType() {
-            checkThread();
-            return delegate.getMimeType();
-        }
-        
-        public InputStream getInputStream() throws IOException {
-            checkThread();
-            return delegate.getInputStream();
-        }
-        
-        public long computeLength() throws IOException {
-            checkThread();
-            return delegate.computeLength();
-        }
-    }
-    
-    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);
-    }
-    
-    public static UriResolver createError(String errorMsg) {
-        return new ErrorUriResolver(errorMsg);
-    }
-    
-    /* 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;
-    }
-    
-    /* Used only by CordovaWebView.resolveUri(). */
-    static UriResolver makeThreadChecking(UriResolver resolver) {
-        if (resolver instanceof ThreadCheckingResolver) {
-            return resolver;
-        }
-        return new ThreadCheckingResolver(resolver);
-    }
-}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/77e90921/test/AndroidManifest.xml
----------------------------------------------------------------------
diff --git a/test/AndroidManifest.xml b/test/AndroidManifest.xml
index f6c840e..04ef3c6 100755
--- a/test/AndroidManifest.xml
+++ b/test/AndroidManifest.xml
@@ -45,7 +45,7 @@
     <uses-permission android:name="android.permission.GET_ACCOUNTS" />
     <uses-permission android:name="android.permission.BROADCAST_STICKY" />
    
-    <uses-sdk android:minSdkVersion="7" />
+    <uses-sdk android:minSdkVersion="8" />
 
     <instrumentation
         android:name="android.test.InstrumentationTestRunner"

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/77e90921/test/src/org/apache/cordova/test/CordovaResourceApiTest.java
----------------------------------------------------------------------
diff --git a/test/src/org/apache/cordova/test/CordovaResourceApiTest.java b/test/src/org/apache/cordova/test/CordovaResourceApiTest.java
new file mode 100644
index 0000000..fdaf972
--- /dev/null
+++ b/test/src/org/apache/cordova/test/CordovaResourceApiTest.java
@@ -0,0 +1,279 @@
+
+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.CordovaResourceApi;
+import org.apache.cordova.CordovaResourceApi.OpenForReadResult;
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.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 java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.util.Scanner;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.test.ActivityInstrumentationTestCase2;
+
+public class CordovaResourceApiTest extends ActivityInstrumentationTestCase2<CordovaWebViewTestActivity> {
+
+    public CordovaResourceApiTest()
+    {
+        super(CordovaWebViewTestActivity.class);
+    }
+
+    CordovaWebView cordovaWebView;
+    CordovaResourceApi resourceApi;
+
+    private CordovaWebViewTestActivity activity;
+    String execPayload;
+    Integer execStatus;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        activity = this.getActivity();
+        cordovaWebView = activity.cordovaWebView;
+        resourceApi = cordovaWebView.getResourceApi();
+        resourceApi.setThreadCheckingEnabled(false);
+        cordovaWebView.pluginManager.addService(new PluginEntry("CordovaResourceApiTestPlugin1", new CordovaPlugin() {
+            @Override
+            public Uri remapUri(Uri uri) {
+                if (uri.getQuery() != null && uri.getQuery().contains("pluginRewrite")) {
+                    return cordovaWebView.getResourceApi().remapUri(
+                            Uri.parse("data:text/plain;charset=utf-8,pass"));
+                }
+                return null;
+            }
+            public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+                synchronized (CordovaResourceApiTest.this) {
+                    execPayload = args.getString(0);
+                    execStatus = args.getInt(1);
+                    CordovaResourceApiTest.this.notify();
+                }
+                return true;
+            }
+        }));
+    }
+
+    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 performApiTest(Uri uri, String expectedMimeType, File expectedLocalFile,
+            boolean expectRead, boolean expectWrite) throws IOException {
+        uri = resourceApi.remapUri(uri);
+        assertEquals(expectedLocalFile, resourceApi.mapUriToFile(uri));
+        
+        try {
+            OpenForReadResult readResult = resourceApi.openForRead(uri);
+            assertEquals(expectedMimeType, readResult.mimeType);
+            readResult.inputStream.read();
+            if (!expectRead) {
+                fail("Expected getInputStream to throw.");
+            }
+        } catch (IOException e) {
+            if (expectRead) {
+                throw e;
+            }
+        }
+        try {
+            OutputStream outStream = resourceApi.openOutputStream(uri);
+            outStream.write(123);
+            if (!expectWrite) {
+                fail("Expected getOutputStream to throw.");
+            }
+            outStream.close();
+        } catch (IOException e) {
+            if (expectWrite) {
+                throw e;
+            }
+        }
+    }
+
+    public void testValidContentUri() throws IOException
+    {
+        Uri contentUri = createTestImageContentUri();
+        File localFile = resourceApi.mapUriToFile(contentUri);
+        assertNotNull(localFile);
+        performApiTest(contentUri, "image/jpeg", localFile, true, true);
+    }
+
+    public void testInvalidContentUri() throws IOException
+    {
+        Uri contentUri = Uri.parse("content://media/external/images/media/999999999");
+        performApiTest(contentUri, null, null, 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.
+        performApiTest(assetUri, "text/html", null, true, false);
+    }
+
+    public void testInvalidAssetUri() throws IOException
+    {
+        Uri assetUri = Uri.parse("file:///android_asset/www/missing.html");
+        performApiTest(assetUri, "text/html", null, 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.
+            performApiTest(fileUri, "text/plain", f, 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 {
+            performApiTest(fileUri, null, f, false, true);
+        } finally {
+            f.delete();
+        }
+    }
+    
+    public void testFileUriToMissingFileWithMissingParent() throws IOException
+    {
+        File f = new File(Environment.getExternalStorageDirectory() + "/somedirthatismissing" + System.currentTimeMillis() + "/somefilethatdoesntexist");
+        Uri fileUri = Uri.parse(f.toURI().toString());
+        performApiTest(fileUri, null, f, false, true);
+    }
+
+    public void testUnrecognizedUri() throws IOException
+    {
+        Uri uri = Uri.parse("somescheme://foo");
+        performApiTest(uri, null, null, false, false);
+    }
+
+    public void testRelativeUri()
+    {
+        try {
+            resourceApi.openForRead(Uri.parse("/foo"));
+            fail("Should have thrown for relative URI 1.");
+        } catch (Throwable t) {
+        }
+        try {
+            resourceApi.openForRead(Uri.parse("//foo/bar"));
+            fail("Should have thrown for relative URI 2.");
+        } catch (Throwable t) {
+        }
+        try {
+            resourceApi.openForRead(Uri.parse("foo.png"));
+            fail("Should have thrown for relative URI 3.");
+        } catch (Throwable t) {
+        }
+    }
+    
+    public void testPluginOverride() throws IOException
+    {
+        Uri uri = Uri.parse("plugin-uri://foohost/android_asset/www/index.html?pluginRewrite=yes");
+        performApiTest(uri, "text/plain", null, true, false);
+    }
+
+    public void testMainThreadUsage() throws IOException
+    {
+        Uri assetUri = Uri.parse("file:///android_asset/www/index.html");
+        resourceApi.setThreadCheckingEnabled(true);
+        try {
+            resourceApi.openForRead(assetUri);
+            fail("Should have thrown for main thread check.");
+        } catch (Throwable t) {
+        }
+    }
+    
+    
+    public void testDataUriPlain() throws IOException
+    {
+        Uri uri = Uri.parse("data:text/plain;charset=utf-8,pass");
+        OpenForReadResult readResult = resourceApi.openForRead(uri);
+        assertEquals("text/plain", readResult.mimeType);
+        String data = new Scanner(readResult.inputStream, "UTF-8").useDelimiter("\\A").next();
+        assertEquals("pass", data);
+    }
+    
+    public void testDataUriBase64() throws IOException
+    {
+        Uri uri = Uri.parse("data:text/js;charset=utf-8;base64,cGFzcw==");
+        OpenForReadResult readResult = resourceApi.openForRead(uri);
+        assertEquals("text/js", readResult.mimeType);
+        String data = new Scanner(readResult.inputStream, "UTF-8").useDelimiter("\\A").next();
+        assertEquals("pass", data);
+    }
+    
+    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,'CordovaResourceApiTestPlugin1', '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,'CordovaResourceApiTestPlugin1', 'foo', [x.responseText, x.status])");
+        execPayload = null;
+        execStatus = null;
+        try {
+            synchronized (this) {
+                this.wait(2000);
+            }
+        } catch (InterruptedException e) {
+        }
+        assertEquals("", execPayload);
+        assertEquals(execStatus.intValue(), 404);
+    }    
+}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/77e90921/test/src/org/apache/cordova/test/UriResolversTest.java
----------------------------------------------------------------------
diff --git a/test/src/org/apache/cordova/test/UriResolversTest.java b/test/src/org/apache/cordova/test/UriResolversTest.java
deleted file mode 100644
index bf2fd63..0000000
--- a/test/src/org/apache/cordova/test/UriResolversTest.java
+++ /dev/null
@@ -1,263 +0,0 @@
-
-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.CallbackContext;
-import org.apache.cordova.CordovaPlugin;
-import org.apache.cordova.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);
-    }    
-}