You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by fi...@apache.org on 2013/03/25 22:53:28 UTC

[5/6] 2.6.0rc1 used for libs now. Bumped npm version to 2.6.0. added androids local.properties to gitignore.

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-android/framework/src/org/apache/cordova/CordovaWebViewClient.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/CordovaWebViewClient.java b/lib/cordova-android/framework/src/org/apache/cordova/CordovaWebViewClient.java
index 8b023eb..4751fc3 100755
--- a/lib/cordova-android/framework/src/org/apache/cordova/CordovaWebViewClient.java
+++ b/lib/cordova-android/framework/src/org/apache/cordova/CordovaWebViewClient.java
@@ -18,6 +18,7 @@
 */
 package org.apache.cordova;
 
+import java.io.ByteArrayInputStream;
 import java.util.Hashtable;
 
 import org.apache.cordova.api.CordovaInterface;
@@ -38,6 +39,7 @@ import android.util.Log;
 import android.view.View;
 import android.webkit.HttpAuthHandler;
 import android.webkit.SslErrorHandler;
+import android.webkit.WebResourceResponse;
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
 
@@ -66,7 +68,7 @@ public class CordovaWebViewClient extends WebViewClient {
 
     /**
      * Constructor.
-     * 
+     *
      * @param cordova
      * @param view
      */
@@ -77,7 +79,7 @@ public class CordovaWebViewClient extends WebViewClient {
 
     /**
      * Constructor.
-     * 
+     *
      * @param view
      */
     public void setWebView(CordovaWebView view) {
@@ -101,8 +103,8 @@ public class CordovaWebViewClient extends WebViewClient {
 		String callbackId = url.substring(idx3 + 1, idx4);
 		String jsonArgs   = url.substring(idx4 + 1);
         appView.pluginManager.exec(service, action, callbackId, jsonArgs);
-	}    
-	
+	}
+
     /**
      * Give the host application a chance to take over the control when a new url
      * is about to be loaded in the current WebView.
@@ -192,12 +194,8 @@ public class CordovaWebViewClient extends WebViewClient {
 
             // If our app or file:, then load into a new Cordova webview container by starting a new instance of our activity.
             // Our app continues to run.  When BACK is pressed, our app is redisplayed.
-            if (url.startsWith("file://") || url.startsWith("data:") || url.indexOf(this.appView.baseUrl) == 0 || Config.isUrlWhiteListed(url)) {
-                //This will fix iFrames
-                if (appView.useBrowserHistory || url.startsWith("data:"))
-                    return false;
-                else
-                    this.appView.loadUrl(url);
+            if (url.startsWith("file://") || url.startsWith("data:")  || Config.isUrlWhiteListed(url)) {
+                return false;
             }
 
             // If not our application, let default viewer handle
@@ -215,6 +213,34 @@ public class CordovaWebViewClient extends WebViewClient {
     }
 
     /**
+     * Check for intercepting any requests for resources.
+     * This includes images and scripts and so on, not just top-level pages.
+     * @param view          The WebView.
+     * @param url           The URL to be loaded.
+     * @return              Either null to proceed as normal, or a WebResourceResponse.
+     */
+    @Override
+    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
+      //If something isn't whitelisted, just send a blank response
+        if(!Config.isUrlWhiteListed(url) && (url.startsWith("http://") || url.startsWith("https://")))
+        {
+            return getWhitelistResponse();
+        }
+    	if (this.appView.pluginManager != null) {
+            return this.appView.pluginManager.shouldInterceptRequest(url);
+        }
+        return null;
+    }
+    
+    private WebResourceResponse getWhitelistResponse()
+    {
+        WebResourceResponse emptyResponse;
+        String empty = "";
+        ByteArrayInputStream data = new ByteArrayInputStream(empty.getBytes());
+        return new WebResourceResponse("text/plain", "UTF-8", data);
+    }
+
+    /**
      * On received http auth request.
      * The method reacts on all registered authentication tokens. There is one and only one authentication token for any host + realm combination
      *
@@ -230,7 +256,7 @@ public class CordovaWebViewClient extends WebViewClient {
         AuthenticationToken token = this.getAuthenticationToken(host, realm);
         if (token != null) {
             handler.proceed(token.getUserName(), token.getPassword());
-        } 
+        }
         else {
             // Handle 401 like we'd normally do!
             super.onReceivedHttpAuthRequest(view, handler, host, realm);
@@ -238,22 +264,16 @@ public class CordovaWebViewClient extends WebViewClient {
     }
 
     /**
-     * Notify the host application that a page has started loading. 
-     * This method is called once for each main frame load so a page with iframes or framesets will call onPageStarted 
-     * one time for the main frame. This also means that onPageStarted will not be called when the contents of an 
-     * embedded frame changes, i.e. clicking a link whose target is an iframe. 
-     * 
+     * Notify the host application that a page has started loading.
+     * This method is called once for each main frame load so a page with iframes or framesets will call onPageStarted
+     * one time for the main frame. This also means that onPageStarted will not be called when the contents of an
+     * embedded frame changes, i.e. clicking a link whose target is an iframe.
+     *
      * @param view          The webview initiating the callback.
      * @param url           The url of the page.
      */
     @Override
     public void onPageStarted(WebView view, String url, Bitmap favicon) {
-        // Clear history so history.back() doesn't do anything.
-        // So we can reinit() native side CallbackServer & PluginManager.
-        if (!this.appView.useBrowserHistory) {
-            view.clearHistory();
-            this.doClearHistory = true;
-        }
 
         // Flush stale messages.
         this.appView.jsMessageQueue.reset();
@@ -270,7 +290,7 @@ public class CordovaWebViewClient extends WebViewClient {
     /**
      * Notify the host application that a page has finished loading.
      * This method is called only for main frame. When onPageFinished() is called, the rendering picture may not be updated yet.
-     * 
+     *
      *
      * @param view          The webview initiating the callback.
      * @param url           The url of the page.
@@ -359,11 +379,11 @@ public class CordovaWebViewClient extends WebViewClient {
     }
 
     /**
-     * Notify the host application that an SSL error occurred while loading a resource. 
-     * The host application must call either handler.cancel() or handler.proceed(). 
-     * Note that the decision may be retained for use in response to future SSL errors. 
+     * Notify the host application that an SSL error occurred while loading a resource.
+     * The host application must call either handler.cancel() or handler.proceed().
+     * Note that the decision may be retained for use in response to future SSL errors.
      * The default behavior is to cancel the load.
-     * 
+     *
      * @param view          The WebView that is initiating the callback.
      * @param handler       An SslErrorHandler object that will handle the user's response.
      * @param error         The SSL error object.
@@ -392,27 +412,10 @@ public class CordovaWebViewClient extends WebViewClient {
         }
     }
 
-    /**
-     * Notify the host application to update its visited links database.
-     * 
-     * @param view          The WebView that is initiating the callback.
-     * @param url           The url being visited.
-     * @param isReload      True if this url is being reloaded.
-     */
-    @Override
-    public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) {
-        /*
-         * If you do a document.location.href the url does not get pushed on the stack
-         * so we do a check here to see if the url should be pushed.
-         */
-        if (!this.appView.peekAtUrlStack().equals(url)) {
-            this.appView.pushUrl(url);
-        }
-    }
 
     /**
      * Sets the authentication token.
-     * 
+     *
      * @param authenticationToken
      * @param host
      * @param realm
@@ -429,10 +432,10 @@ public class CordovaWebViewClient extends WebViewClient {
 
     /**
      * Removes the authentication token.
-     * 
+     *
      * @param host
      * @param realm
-     * 
+     *
      * @return the authentication token or null if did not exist
      */
     public AuthenticationToken removeAuthenticationToken(String host, String realm) {
@@ -441,16 +444,16 @@ public class CordovaWebViewClient extends WebViewClient {
 
     /**
      * Gets the authentication token.
-     * 
+     *
      * In order it tries:
      * 1- host + realm
      * 2- host
      * 3- realm
      * 4- no host, no realm
-     * 
+     *
      * @param host
      * @param realm
-     * 
+     *
      * @return the authentication token
      */
     public AuthenticationToken getAuthenticationToken(String host, String realm) {

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-android/framework/src/org/apache/cordova/Device.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/Device.java b/lib/cordova-android/framework/src/org/apache/cordova/Device.java
index 5b09c13..ad399f0 100644
--- a/lib/cordova-android/framework/src/org/apache/cordova/Device.java
+++ b/lib/cordova-android/framework/src/org/apache/cordova/Device.java
@@ -38,7 +38,7 @@ import android.telephony.TelephonyManager;
 public class Device extends CordovaPlugin {
     public static final String TAG = "Device";
 
-    public static String cordovaVersion = "2.5.0";              // Cordova version
+    public static String cordovaVersion = "2.6.0rc1";              // Cordova version
     public static String platform = "Android";                  // Device OS
     public static String uuid;                                  // Device UUID
 

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-android/framework/src/org/apache/cordova/DroidGap.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/DroidGap.java b/lib/cordova-android/framework/src/org/apache/cordova/DroidGap.java
index 9a7be4e..d4296cb 100755
--- a/lib/cordova-android/framework/src/org/apache/cordova/DroidGap.java
+++ b/lib/cordova-android/framework/src/org/apache/cordova/DroidGap.java
@@ -28,6 +28,7 @@ import org.apache.cordova.api.LOG;
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -332,6 +333,7 @@ public class DroidGap extends Activity implements CordovaInterface {
      * @param webViewClient
      * @param webChromeClient
      */
+    @SuppressLint("NewApi")
     public void init(CordovaWebView webView, CordovaWebViewClient webViewClient, CordovaChromeClient webChromeClient) {
         LOG.d(TAG, "DroidGap.init()");
 
@@ -349,6 +351,12 @@ public class DroidGap extends Activity implements CordovaInterface {
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 1.0F));
 
+        if (this.getBooleanProperty("disallowOverscroll", false)) {
+            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.GINGERBREAD) {
+                this.appView.setOverScrollMode(CordovaWebView.OVER_SCROLL_NEVER);
+            }
+        }
+
         // Add web view but make it invisible while loading URL
         this.appView.setVisibility(View.INVISIBLE);
         this.root.addView(this.appView);
@@ -1054,7 +1062,8 @@ public class DroidGap extends Activity implements CordovaInterface {
     {
         //Get whatever has focus!
         View childView = appView.getFocusedChild();
-        if ((appView.isCustomViewShowing() || childView != null ) && keyCode == KeyEvent.KEYCODE_BACK) {
+        if ((appView.isCustomViewShowing() || childView != null ) &&
+                (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU)) {
             return appView.onKeyUp(keyCode, event);
         } else {
             return super.onKeyUp(keyCode, event);
@@ -1074,7 +1083,7 @@ public class DroidGap extends Activity implements CordovaInterface {
         //Get whatever has focus!
         View childView = appView.getFocusedChild();
         //Determine if the focus is on the current view or not
-        if (childView != null && keyCode == KeyEvent.KEYCODE_BACK) {
+        if (childView != null && (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU)) {
                     return appView.onKeyDown(keyCode, event);
         }
         else

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-android/framework/src/org/apache/cordova/FileHelper.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/FileHelper.java b/lib/cordova-android/framework/src/org/apache/cordova/FileHelper.java
new file mode 100644
index 0000000..c10ed96
--- /dev/null
+++ b/lib/cordova-android/framework/src/org/apache/cordova/FileHelper.java
@@ -0,0 +1,142 @@
+/*
+       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.database.Cursor;
+import android.net.Uri;
+import android.webkit.MimeTypeMap;
+
+import org.apache.cordova.api.CordovaInterface;
+import org.apache.cordova.api.LOG;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class FileHelper {
+    private static final String LOG_TAG = "FileUtils";
+    private static final String _DATA = "_data";
+
+    /**
+     * Returns the real path of the given URI string.
+     * If the given URI string represents a content:// URI, the real path is retrieved from the media store.
+     *
+     * @param uriString the URI string of the audio/image/video
+     * @param cordova the current application context
+     * @return the full path to the file
+     */
+    @SuppressWarnings("deprecation")
+    public static String getRealPath(String uriString, CordovaInterface cordova) {
+        String realPath = null;
+
+        if (uriString.startsWith("content://")) {
+            String[] proj = { _DATA };
+            Cursor cursor = cordova.getActivity().managedQuery(Uri.parse(uriString), proj, null, null, null);
+            int column_index = cursor.getColumnIndexOrThrow(_DATA);
+            cursor.moveToFirst();
+            realPath = cursor.getString(column_index);
+            if (realPath == null) {
+                LOG.e(LOG_TAG, "Could get real path for URI string %s", uriString);
+            }
+        } else if (uriString.startsWith("file://")) {
+            realPath = uriString.substring(7);
+            if (realPath.startsWith("/android_asset/")) {
+                LOG.e(LOG_TAG, "Cannot get real path for URI string %s because it is a file:///android_asset/ URI.", uriString);
+                realPath = null;
+            }
+        } else {
+            realPath = uriString;
+        }
+
+        return realPath;
+    }
+
+    /**
+     * Returns the real path of the given URI.
+     * If the given URI is a content:// URI, the real path is retrieved from the media store.
+     *
+     * @param uri the URI of the audio/image/video
+     * @param cordova the current application context
+     * @return the full path to the file
+     */
+    public static String getRealPath(Uri uri, CordovaInterface cordova) {
+        return FileHelper.getRealPath(uri.toString(), cordova);
+    }
+
+    /**
+     * Returns an input stream based on given URI string.
+     *
+     * @param uriString the URI string from which to obtain the input stream
+     * @param cordova the current application context
+     * @return an input stream into the data at the given URI or null if given an invalid URI string
+     * @throws IOException
+     */
+    public static InputStream getInputStreamFromUriString(String uriString, CordovaInterface cordova) throws IOException {
+        if (uriString.startsWith("content")) {
+            Uri uri = Uri.parse(uriString);
+            return cordova.getActivity().getContentResolver().openInputStream(uri);
+        } else if (uriString.startsWith("file:///android_asset/")) {
+            String relativePath = uriString.substring(22);
+            return cordova.getActivity().getAssets().open(relativePath);
+        } else {
+            return new FileInputStream(getRealPath(uriString, cordova));
+        }
+    }
+
+    /**
+     * 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.
+     *
+     * @param uriString the URI string to operate on
+     * @return a path without the "file://" prefix
+     */
+    public static String stripFileProtocol(String uriString) {
+        if (uriString.startsWith("file://")) {
+            uriString = uriString.substring(7);
+        }
+        return uriString;
+    }
+
+    /**
+     * Returns the mime type of the data specified by the given URI string.
+     *
+     * @param uriString the URI string of the data
+     * @return the mime type of the specified data
+     */
+    public static String getMimeType(String uriString, CordovaInterface cordova) {
+        String mimeType = null;
+
+        if (uriString.startsWith("content://")) {
+            Uri uri = Uri.parse(uriString);
+            mimeType = cordova.getActivity().getContentResolver().getType(uri);
+        } else {
+            // MimeTypeMap.getFileExtensionFromUrl has a bug that occurs when the filename has a space, so we encode it.
+            // We also convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185).
+            String encodedUriString = uriString.replace(" ", "%20").toLowerCase();
+            String extension = MimeTypeMap.getFileExtensionFromUrl(encodedUriString);
+            if (extension.equals("3ga")) {
+                mimeType = "audio/3gpp";
+            } else {
+                mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+            }
+        }
+
+        return mimeType;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-android/framework/src/org/apache/cordova/FileTransfer.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/FileTransfer.java b/lib/cordova-android/framework/src/org/apache/cordova/FileTransfer.java
index 623baf8..dba29af 100644
--- a/lib/cordova-android/framework/src/org/apache/cordova/FileTransfer.java
+++ b/lib/cordova-android/framework/src/org/apache/cordova/FileTransfer.java
@@ -18,6 +18,7 @@
 */
 package org.apache.cordova;
 
+import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.File;
@@ -27,7 +28,9 @@ import java.io.FileOutputStream;
 import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
@@ -155,6 +158,25 @@ public class FileTransfer extends CordovaPlugin {
         return false;
     }
 
+    private static void addHeadersToRequest(URLConnection connection, JSONObject headers) {
+        try {
+            for (Iterator<?> iter = headers.keys(); iter.hasNext(); ) {
+                String headerKey = iter.next().toString();
+                JSONArray headerValues = headers.optJSONArray(headerKey);
+                if (headerValues == null) {
+                    headerValues = new JSONArray();
+                    headerValues.put(headers.getString(headerKey));
+                }
+                connection.setRequestProperty(headerKey, headerValues.getString(0));
+                for (int i = 1; i < headerValues.length(); ++i) {
+                    connection.addRequestProperty(headerKey, headerValues.getString(i));
+                }
+            }
+        } catch (JSONException e1) {
+          // No headers to be manipulated!
+        }
+    }
+
     /**
      * Uploads the specified file to the server URL provided using an HTTP multipart request.
      * @param source        Full path of the file on the file system
@@ -196,7 +218,7 @@ public class FileTransfer extends CordovaPlugin {
         try {
             url = new URL(target);
         } catch (MalformedURLException e) {
-            JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, 0);
+            JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, null, 0);
             Log.e(LOG_TAG, error.toString(), e);
             callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
             return;
@@ -269,22 +291,7 @@ public class FileTransfer extends CordovaPlugin {
 
                     // Handle the other headers
                     if (headers != null) {
-                        try {
-                            for (Iterator<?> iter = headers.keys(); iter.hasNext(); ) {
-                                String headerKey = iter.next().toString();
-                                JSONArray headerValues = headers.optJSONArray(headerKey);
-                                if (headerValues == null) {
-                                    headerValues = new JSONArray();
-                                    headerValues.put(headers.getString(headerKey));
-                                }
-                                conn.setRequestProperty(headerKey, headerValues.getString(0));
-                                for (int i = 1; i < headerValues.length(); ++i) {
-                                    conn.addRequestProperty(headerKey, headerValues.getString(i));
-                                }
-                            }
-                        } catch (JSONException e1) {
-                          // No headers to be manipulated!
-                        }
+                        addHeadersToRequest(conn, headers);
                     }
 
                     /*
@@ -530,18 +537,33 @@ public class FileTransfer extends CordovaPlugin {
     private static JSONObject createFileTransferError(int errorCode, String source, String target, URLConnection connection) {
 
         int httpStatus = 0;
-
+        StringBuilder bodyBuilder = new StringBuilder();
+        String body = null;
         if (connection != null) {
             try {
                 if (connection instanceof HttpURLConnection) {
                     httpStatus = ((HttpURLConnection)connection).getResponseCode();
+                    InputStream err = ((HttpURLConnection) connection).getErrorStream();
+                    if(err != null)
+                    {
+                        BufferedReader reader = new BufferedReader(new InputStreamReader(err, "UTF-8"));
+                        String line = reader.readLine();
+                        while(line != null)
+                        {
+                            bodyBuilder.append(line);
+                            line = reader.readLine();
+                            if(line != null)
+                                bodyBuilder.append('\n');
+                        }
+                        body = bodyBuilder.toString();
+                    }
                 }
             } catch (IOException e) {
                 Log.w(LOG_TAG, "Error getting HTTP status code from connection.", e);
             }
         }
 
-        return createFileTransferError(errorCode, source, target, httpStatus);
+        return createFileTransferError(errorCode, source, target, body, httpStatus);
     }
 
         /**
@@ -549,13 +571,17 @@ public class FileTransfer extends CordovaPlugin {
         * @param errorCode 	the error
         * @return JSONObject containing the error
         */
-    private static JSONObject createFileTransferError(int errorCode, String source, String target, Integer httpStatus) {
+    private static JSONObject createFileTransferError(int errorCode, String source, String target, String body, Integer httpStatus) {
         JSONObject error = null;
         try {
             error = new JSONObject();
             error.put("code", errorCode);
             error.put("source", source);
             error.put("target", target);
+            if(body != null)
+            {
+                error.put("body", body);
+            }   
             if (httpStatus != null) {
                 error.put("http_status", httpStatus);
             }
@@ -594,12 +620,13 @@ public class FileTransfer extends CordovaPlugin {
 
         final boolean trustEveryone = args.optBoolean(2);
         final String objectId = args.getString(3);
+        final JSONObject headers = args.optJSONObject(4);
 
         final URL url;
         try {
             url = new URL(source);
         } catch (MalformedURLException e) {
-            JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, 0);
+            JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, null, 0);
             Log.e(LOG_TAG, error.toString(), e);
             callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
             return;
@@ -608,7 +635,7 @@ public class FileTransfer extends CordovaPlugin {
 
         if (!Config.isUrlWhiteListed(source)) {
             Log.w(LOG_TAG, "Source URL is not in white list: '" + source + "'");
-            JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, 401);
+            JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, null, 401);
             callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, error));
             return;
         }
@@ -671,6 +698,11 @@ public class FileTransfer extends CordovaPlugin {
                     {
                         connection.setRequestProperty("cookie", cookie);
                     }
+
+                    // Handle the other headers
+                    if (headers != null) {
+                        addHeadersToRequest(connection, headers);
+                    }
     
                     connection.connect();
     
@@ -718,8 +750,7 @@ public class FileTransfer extends CordovaPlugin {
                     Log.d(LOG_TAG, "Saved file: " + target);
     
                     // create FileEntry object
-                    FileUtils fileUtil = new FileUtils();
-                    JSONObject fileEntry = fileUtil.getEntry(file);
+                    JSONObject fileEntry = FileUtils.getEntry(file);
                     
                     result = new PluginResult(PluginResult.Status.OK, fileEntry);
                 } catch (FileNotFoundException e) {
@@ -826,7 +857,7 @@ public class FileTransfer extends CordovaPlugin {
                 file.delete();
             }
             // Trigger the abort callback immediately to minimize latency between it and abort() being called.
-            JSONObject error = createFileTransferError(ABORTED_ERR, context.source, context.target, -1);
+            JSONObject error = createFileTransferError(ABORTED_ERR, context.source, context.target, null, -1);
             synchronized (context) {
                 context.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, error));
                 context.aborted = true;

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-android/framework/src/org/apache/cordova/FileUtils.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/FileUtils.java b/lib/cordova-android/framework/src/org/apache/cordova/FileUtils.java
index b461b02..2135be9 100755
--- a/lib/cordova-android/framework/src/org/apache/cordova/FileUtils.java
+++ b/lib/cordova-android/framework/src/org/apache/cordova/FileUtils.java
@@ -15,18 +15,17 @@
        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.*;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLDecoder;
-import java.nio.channels.FileChannel;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.util.Log;
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.cordova.api.CallbackContext;
-import org.apache.cordova.api.CordovaInterface;
 import org.apache.cordova.api.CordovaPlugin;
 import org.apache.cordova.api.PluginResult;
 import org.apache.cordova.file.EncodingException;
@@ -38,23 +37,25 @@ import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
-//import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Environment;
-import android.provider.MediaStore;
-import android.webkit.MimeTypeMap;
-
-//import android.app.Activity;
+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.RandomAccessFile;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.nio.channels.FileChannel;
 
 /**
  * This class provides SD card file and directory services to JavaScript.
  * Only files on the SD card can be accessed.
  */
 public class FileUtils extends CordovaPlugin {
-    @SuppressWarnings("unused")
     private static final String LOG_TAG = "FileUtils";
-    private static final String _DATA = "_data";    // The column name where the file path is stored
 
     public static int NOT_FOUND_ERR = 1;
     public static int SECURITY_ERR = 2;
@@ -75,9 +76,6 @@ public class FileUtils extends CordovaPlugin {
     public static int RESOURCE = 2;
     public static int APPLICATION = 3;
 
-    FileReader f_in;
-    FileWriter f_out;
-
     /**
      * Constructor.
      */
@@ -111,30 +109,29 @@ public class FileUtils extends CordovaPlugin {
                 callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
             }
             else if (action.equals("readAsText")) {
-                int start = 0;
-                int end = Integer.MAX_VALUE;
-                if (args.length() >= 3) {
-                    start = args.getInt(2);
-                }
-                if (args.length() >= 4) {
-                    end = args.getInt(3);
-                }
+                String encoding = args.getString(1);
+                int start = args.getInt(2);
+                int end = args.getInt(3);
 
-                String s = this.readAsText(args.getString(0), args.getString(1), start, end);
-                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, s));
+                this.readFileAs(args.getString(0), start, end, callbackContext, encoding, PluginResult.MESSAGE_TYPE_STRING);
             }
             else if (action.equals("readAsDataURL")) {
-                int start = 0;
-                int end = Integer.MAX_VALUE;
-                if (args.length() >= 2) {
-                    start = args.getInt(1);
-                }
-                if (args.length() >= 3) {
-                    end = args.getInt(2);
-                }
+                int start = args.getInt(1);
+                int end = args.getInt(2);
+
+                this.readFileAs(args.getString(0), start, end, callbackContext, null, -1);
+            }
+            else if (action.equals("readAsArrayBuffer")) {
+                int start = args.getInt(1);
+                int end = args.getInt(2);
 
-                String s = this.readAsDataURL(args.getString(0), start, end);
-                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, s));
+                this.readFileAs(args.getString(0), start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_ARRAYBUFFER);
+            }
+            else if (action.equals("readAsBinaryString")) {
+                int start = args.getInt(1);
+                int end = args.getInt(2);
+
+                this.readFileAs(args.getString(0), start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_BINARYSTRING);
             }
             else if (action.equals("write")) {
                 long fileSize = this.write(args.getString(0), args.getString(1), args.getInt(2));
@@ -237,11 +234,11 @@ public class FileUtils extends CordovaPlugin {
      * @param filePath the path to check
      */
     private void notifyDelete(String filePath) {
-        String newFilePath = getRealPathFromURI(Uri.parse(filePath), cordova);
+        String newFilePath = FileHelper.getRealPath(filePath, cordova);
         try {
             this.cordova.getActivity().getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
-                MediaStore.Images.Media.DATA + " = ?",
-                new String[] { newFilePath });
+                    MediaStore.Images.Media.DATA + " = ?",
+                    new String[] { newFilePath });
         } catch (UnsupportedOperationException t) {
             // Was seeing this on the File mobile-spec tests on 4.0.3 x86 emulator.
             // The ContentResolver applies only when the file was registered in the
@@ -344,8 +341,8 @@ public class FileUtils extends CordovaPlugin {
      * @throws FileExistsException
      */
     private JSONObject transferTo(String fileName, String newParent, String newName, boolean move) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
-        String newFileName = getRealPathFromURI(Uri.parse(fileName), cordova);
-        newParent = getRealPathFromURI(Uri.parse(newParent), cordova);
+        String newFileName = FileHelper.getRealPath(fileName, cordova);
+        newParent = FileHelper.getRealPath(newParent, cordova);
 
         // Check for invalid file name
         if (newName != null && newName.contains(":")) {
@@ -384,14 +381,14 @@ public class FileUtils extends CordovaPlugin {
             }
         } else {
             if (move) {
-            	JSONObject newFileEntry = moveFile(source, destination);
+                JSONObject newFileEntry = moveFile(source, destination);
 
-            	// If we've moved a file given its content URI, we need to clean up.
-            	if (fileName.startsWith("content://")) {
-            		notifyDelete(fileName);
-            	}
+                // If we've moved a file given its content URI, we need to clean up.
+                if (fileName.startsWith("content://")) {
+                    notifyDelete(fileName);
+                }
 
-            	return newFileEntry;
+                return newFileEntry;
             } else {
                 return copyFile(source, destination);
             }
@@ -748,7 +745,7 @@ public class FileUtils extends CordovaPlugin {
         if (fileName.startsWith("/")) {
             fp = new File(fileName);
         } else {
-            dirPath = getRealPathFromURI(Uri.parse(dirPath), cordova);
+            dirPath = FileHelper.getRealPath(dirPath, cordova);
             fp = new File(dirPath + File.separator + fileName);
         }
         return fp;
@@ -763,7 +760,7 @@ public class FileUtils extends CordovaPlugin {
      * @throws JSONException
      */
     private JSONObject getParent(String filePath) throws JSONException {
-        filePath = getRealPathFromURI(Uri.parse(filePath), cordova);
+        filePath = FileHelper.getRealPath(filePath, cordova);
 
         if (atRootDirectory(filePath)) {
             return getEntry(filePath);
@@ -779,7 +776,7 @@ public class FileUtils extends CordovaPlugin {
      * @return true if we are at the root, false otherwise.
      */
     private boolean atRootDirectory(String filePath) {
-        filePath = getRealPathFromURI(Uri.parse(filePath), cordova);
+        filePath = FileHelper.getRealPath(filePath, cordova);
 
         if (filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + cordova.getActivity().getPackageName() + "/cache") ||
                 filePath.equals(Environment.getExternalStorageDirectory().getAbsolutePath()) ||
@@ -790,26 +787,13 @@ public class FileUtils extends CordovaPlugin {
     }
 
     /**
-     * This method removes the "file://" from the passed in filePath
-     *
-     * @param filePath to be checked.
-     * @return
-     */
-    public static String stripFileProtocol(String filePath) {
-        if (filePath.startsWith("file://")) {
-            filePath = filePath.substring(7);
-        }
-        return filePath;
-    }
-
-    /**
      * Create a File object from the passed in path
      *
      * @param filePath
      * @return
      */
     private File createFileObject(String filePath) {
-    	filePath = getRealPathFromURI(Uri.parse(filePath), cordova);
+        filePath = FileHelper.getRealPath(filePath, cordova);
 
         File file = new File(filePath);
         return file;
@@ -849,7 +833,7 @@ public class FileUtils extends CordovaPlugin {
 
         JSONObject metadata = new JSONObject();
         metadata.put("size", file.length());
-        metadata.put("type", getMimeType(filePath));
+        metadata.put("type", FileHelper.getMimeType(filePath, cordova));
         metadata.put("name", file.getName());
         metadata.put("fullPath", filePath);
         metadata.put("lastModifiedDate", file.lastModified());
@@ -900,21 +884,21 @@ public class FileUtils extends CordovaPlugin {
     }
 
     /**
-     * Returns a JSON Object representing a directory on the device's file system
+     * Returns a JSON object representing the given File.
      *
-     * @param path to the directory
-     * @return
+     * @param file the File to convert
+     * @return a JSON representation of the given File
      * @throws JSONException
      */
-    public JSONObject getEntry(File file) throws JSONException {
+    public static JSONObject getEntry(File file) throws JSONException {
         JSONObject entry = new JSONObject();
 
         entry.put("isFile", file.isFile());
         entry.put("isDirectory", file.isDirectory());
         entry.put("name", file.getName());
         entry.put("fullPath", "file://" + file.getAbsolutePath());
-        // I can't add the next thing it as it would be an infinite loop
-        //entry.put("filesystem", null);
+        // The file system can't be specified, as it would lead to an infinite loop.
+        // entry.put("filesystem", null);
 
         return entry;
     }
@@ -930,123 +914,83 @@ public class FileUtils extends CordovaPlugin {
         return getEntry(new File(path));
     }
 
-    /**
-     * Identifies if action to be executed returns a value and should be run synchronously.
-     *
-     * @param action	The action to execute
-     * @return			T=returns value
-     */
-    public boolean isSynch(String action) {
-        if (action.equals("testSaveLocationExists")) {
-            return true;
-        }
-        else if (action.equals("getFreeDiskSpace")) {
-            return true;
-        }
-        else if (action.equals("testFileExists")) {
-            return true;
-        }
-        else if (action.equals("testDirectoryExists")) {
-            return true;
-        }
-        return false;
-    }
 
     //--------------------------------------------------------------------------
     // LOCAL METHODS
     //--------------------------------------------------------------------------
 
     /**
-     * Read content of text file.
+     * Read the contents of a file.
+     * This is done in a background thread; the result is sent to the callback.
      *
-     * @param filename			The name of the file.
-     * @param encoding			The encoding to return contents as.  Typical value is UTF-8.
-     * 							(see http://www.iana.org/assignments/character-sets)
-     * @param start                     Start position in the file.
-     * @param end                       End position to stop at (exclusive).
-     * @return					Contents of file.
-     * @throws FileNotFoundException, IOException
+     * @param filename          The name of the file.
+     * @param start             Start position in the file.
+     * @param end               End position to stop at (exclusive).
+     * @param callbackContext   The context through which to send the result.
+     * @param encoding          The encoding to return contents as.  Typical value is UTF-8. (see http://www.iana.org/assignments/character-sets)
+     * @param resultType        The desired type of data to send to the callback.
+     * @return                  Contents of file.
      */
-    public String readAsText(String filename, String encoding, int start, int end) throws FileNotFoundException, IOException {
-        int diff = end - start;
-        byte[] bytes = new byte[1000];
-        BufferedInputStream bis = new BufferedInputStream(getPathFromUri(filename), 1024);
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        int numRead = 0;
-
-        if (start > 0) {
-            bis.skip(start);
-        }
-
-        while ( diff > 0 && (numRead = bis.read(bytes, 0, Math.min(1000, diff))) >= 0) {
-            diff -= numRead;
-            bos.write(bytes, 0, numRead);
-        }
-
-        return new String(bos.toByteArray(), encoding);
+    public void readFileAs(final String filename, final int start, final int end, final CallbackContext callbackContext, final String encoding, final int resultType) {
+        this.cordova.getThreadPool().execute(new Runnable() {
+            public void run() {
+                try {
+                    byte[] bytes = readAsBinaryHelper(filename, start, end);
+                    
+                    PluginResult result;
+                    switch (resultType) {
+                        case PluginResult.MESSAGE_TYPE_STRING:
+                            result = new PluginResult(PluginResult.Status.OK, new String(bytes, encoding));
+                            break;
+                        case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
+                            result = new PluginResult(PluginResult.Status.OK, bytes);
+                            break;
+                        case PluginResult.MESSAGE_TYPE_BINARYSTRING:
+                            result = new PluginResult(PluginResult.Status.OK, bytes, true);
+                            break;
+                        default: // Base64.
+                            String contentType = FileHelper.getMimeType(filename, cordova);
+                            byte[] base64 = Base64.encodeBase64(bytes);
+                            String s = "data:" + contentType + ";base64," + new String(base64, "US-ASCII");
+                            result = new PluginResult(PluginResult.Status.OK, s);
+                    }
+
+                    callbackContext.sendPluginResult(result);
+                } catch (FileNotFoundException e) {
+                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_FOUND_ERR));
+                } catch (IOException e) {
+                    Log.d(LOG_TAG, e.getLocalizedMessage());
+                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_READABLE_ERR));
+                }
+            }
+        });
     }
 
     /**
-     * Read content of text file and return as base64 encoded data url.
+     * Read the contents of a file as binary.
+     * This is done synchronously; the result is returned.
      *
-     * @param filename			The name of the file.
-     * @return					Contents of file = data:<media type>;base64,<data>
-     * @throws FileNotFoundException, IOException
+     * @param filename          The name of the file.
+     * @param start             Start position in the file.
+     * @param end               End position to stop at (exclusive).
+     * @return                  Contents of the file as a byte[].
+     * @throws IOException
      */
-    public String readAsDataURL(String filename, int start, int end) throws FileNotFoundException, IOException {
-        int diff = end - start;
-        byte[] bytes = new byte[1000];
-        BufferedInputStream bis = new BufferedInputStream(getPathFromUri(filename), 1024);
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        int numRead = 0;
+    private byte[] readAsBinaryHelper(String filename, int start, int end) throws IOException {
+        int numBytesToRead = end - start;
+        byte[] bytes = new byte[numBytesToRead];
+        InputStream inputStream = FileHelper.getInputStreamFromUriString(filename, cordova);
+        int numBytesRead = 0;
 
         if (start > 0) {
-            bis.skip(start);
-        }
-
-        while (diff > 0 && (numRead = bis.read(bytes, 0, Math.min(1000, diff))) >= 0) {
-            diff -= numRead;
-            bos.write(bytes, 0, numRead);
+            inputStream.skip(start);
         }
 
-        // Determine content type from file name
-        String contentType = null;
-        if (filename.startsWith("content:")) {
-            Uri fileUri = Uri.parse(filename);
-            contentType = this.cordova.getActivity().getContentResolver().getType(fileUri);
-        }
-        else {
-            contentType = getMimeType(filename);
+        while (numBytesToRead > 0 && (numBytesRead = inputStream.read(bytes, numBytesRead, numBytesToRead)) >= 0) {
+            numBytesToRead -= numBytesRead;
         }
 
-        byte[] base64 = Base64.encodeBase64(bos.toByteArray());
-        String data = "data:" + contentType + ";base64," + new String(base64);
-        return data;
-    }
-
-    /**
-     * Looks up the mime type of a given file name.
-     *
-     * @param filename
-     * @return a mime type
-     */
-    public static String getMimeType(String filename) {
-        if (filename != null) {
-            // Stupid bug in getFileExtensionFromUrl when the file name has a space
-            // So we need to replace the space with a url encoded %20
-
-            // CB-2185: Stupid bug not putting JPG extension in the mime-type map
-            String url = filename.replace(" ", "%20").toLowerCase();
-            MimeTypeMap map = MimeTypeMap.getSingleton();
-            String extension = MimeTypeMap.getFileExtensionFromUrl(url);
-            if (extension.toLowerCase().equals("3ga")) {
-                return "audio/3gpp";
-            } else {
-                return map.getMimeTypeFromExtension(extension);
-            }
-        } else {
-            return "";
-        }
+        return bytes;
     }
 
     /**
@@ -1060,11 +1004,11 @@ public class FileUtils extends CordovaPlugin {
      */
     /**/
     public long write(String filename, String data, int offset) throws FileNotFoundException, IOException, NoModificationAllowedException {
-    	if (filename.startsWith("content://")) {
-    		throw new NoModificationAllowedException("Couldn't write to file given its content URI");
-    	}
+        if (filename.startsWith("content://")) {
+            throw new NoModificationAllowedException("Couldn't write to file given its content URI");
+        }
 
-        filename = getRealPathFromURI(Uri.parse(filename), cordova);
+        filename = FileHelper.getRealPath(filename, cordova);
 
         boolean append = false;
         if (offset > 0) {
@@ -1093,11 +1037,11 @@ public class FileUtils extends CordovaPlugin {
      * @throws NoModificationAllowedException
      */
     private long truncateFile(String filename, long size) throws FileNotFoundException, IOException, NoModificationAllowedException {
-    	if (filename.startsWith("content://")) {
-    		throw new NoModificationAllowedException("Couldn't truncate file given its content URI");
-    	}
+        if (filename.startsWith("content://")) {
+            throw new NoModificationAllowedException("Couldn't truncate file given its content URI");
+        }
 
-        filename = getRealPathFromURI(Uri.parse(filename), cordova);
+        filename = FileHelper.getRealPath(filename, cordova);
 
         RandomAccessFile raf = new RandomAccessFile(filename, "rw");
         try {
@@ -1112,48 +1056,4 @@ public class FileUtils extends CordovaPlugin {
             raf.close();
         }
     }
-
-    /**
-     * Get an input stream based on file path or content:// uri
-     *
-     * @param path
-     * @return an input stream
-     * @throws FileNotFoundException
-     */
-    private InputStream getPathFromUri(String path) throws FileNotFoundException {
-        if (path.startsWith("content")) {
-            Uri uri = Uri.parse(path);
-            return cordova.getActivity().getContentResolver().openInputStream(uri);
-        }
-        else {
-            path = getRealPathFromURI(Uri.parse(path), cordova);
-            return new FileInputStream(path);
-        }
-    }
-
-    /**
-     * Queries the media store to find out what the file path is for the Uri we supply
-     *
-     * @param contentUri the Uri of the audio/image/video
-     * @param cordova the current application context
-     * @return the full path to the file
-     */
-    @SuppressWarnings("deprecation")
-    protected static String getRealPathFromURI(Uri contentUri, CordovaInterface cordova) {
-        final String scheme = contentUri.getScheme();
-
-        if (scheme == null) {
-        	return contentUri.toString();
-    	} else if (scheme.compareTo("content") == 0) {
-            String[] proj = { _DATA };
-            Cursor cursor = cordova.getActivity().managedQuery(contentUri, proj, null, null, null);
-            int column_index = cursor.getColumnIndexOrThrow(_DATA);
-            cursor.moveToFirst();
-            return cursor.getString(column_index);
-        } else if (scheme.compareTo("file") == 0) {
-            return contentUri.getPath();
-        } else {
-            return contentUri.toString();
-        }
-    }
 }

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-android/framework/src/org/apache/cordova/GeoBroker.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/GeoBroker.java b/lib/cordova-android/framework/src/org/apache/cordova/GeoBroker.java
index 86aa628..e7cdce0 100755
--- a/lib/cordova-android/framework/src/org/apache/cordova/GeoBroker.java
+++ b/lib/cordova-android/framework/src/org/apache/cordova/GeoBroker.java
@@ -150,7 +150,7 @@ public class GeoBroker extends CordovaPlugin {
             o.put("altitude", (loc.hasAltitude() ? loc.getAltitude() : null));
             o.put("accuracy", loc.getAccuracy());
             o.put("heading", (loc.hasBearing() ? (loc.hasSpeed() ? loc.getBearing() : null) : null));
-            o.put("speed", loc.getSpeed());
+            o.put("velocity", loc.getSpeed());
             o.put("timestamp", loc.getTime());
         } catch (JSONException e) {
             // TODO Auto-generated catch block

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-android/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java b/lib/cordova-android/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
index a96b242..2142714 100644
--- a/lib/cordova-android/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
+++ b/lib/cordova-android/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
@@ -42,7 +42,7 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
 
     @Override
     public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
-        if(url.contains("?") || url.contains("#")){
+        if(url.contains("?") || url.contains("#") || needsIceCreamSpaceInAssetUrlFix(url)){
             return generateWebResourceResponse(url);
         } else {
             return super.shouldInterceptRequest(view, url);
@@ -80,4 +80,18 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
         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;
+        }
+    }
+    
 }

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-android/framework/src/org/apache/cordova/InAppBrowser.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/InAppBrowser.java b/lib/cordova-android/framework/src/org/apache/cordova/InAppBrowser.java
index 7e7f862..48e27c6 100644
--- a/lib/cordova-android/framework/src/org/apache/cordova/InAppBrowser.java
+++ b/lib/cordova-android/framework/src/org/apache/cordova/InAppBrowser.java
@@ -35,7 +35,9 @@ import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.net.Uri;
+import android.os.Bundle;
 import android.text.InputType;
 import android.util.Log;
 import android.util.TypedValue;
@@ -48,6 +50,7 @@ import android.view.WindowManager.LayoutParams;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.webkit.WebChromeClient;
+import android.webkit.GeolocationPermissions.Callback;
 import android.webkit.WebSettings;
 import android.webkit.WebStorage;
 import android.webkit.WebView;
@@ -69,6 +72,8 @@ public class InAppBrowser extends CordovaPlugin {
     private static final String EXIT_EVENT = "exit";
     private static final String LOAD_START_EVENT = "loadstart";
     private static final String LOAD_STOP_EVENT = "loadstop";
+    private static final String LOAD_ERROR_EVENT = "loaderror";
+    private static final String CLOSE_BUTTON_CAPTION = "closebuttoncaption";
     private long MAX_QUOTA = 100 * 1024 * 1024;
 
     private Dialog dialog;
@@ -76,6 +81,7 @@ public class InAppBrowser extends CordovaPlugin {
     private EditText edittext;
     private boolean showLocationBar = true;
     private CallbackContext callbackContext;
+    private String buttonLabel = "Done";
     
     /**
      * Executes the request and returns PluginResult.
@@ -174,8 +180,12 @@ public class InAppBrowser extends CordovaPlugin {
                 option = new StringTokenizer(features.nextToken(), "=");
                 if (option.hasMoreElements()) {
                     String key = option.nextToken();
-                    Boolean value = option.nextToken().equals("no") ? Boolean.FALSE : Boolean.TRUE;
-                    map.put(key, value);
+                    if (key.equalsIgnoreCase(CLOSE_BUTTON_CAPTION)) {
+                        this.buttonLabel = option.nextToken();
+                    } else {
+                        Boolean value = option.nextToken().equals("no") ? Boolean.FALSE : Boolean.TRUE;
+                        map.put(key, value);
+                    }
                 }
             }
             return map;
@@ -221,6 +231,7 @@ public class InAppBrowser extends CordovaPlugin {
      */
     private void closeDialog() {
         try {
+            this.inAppWebView.loadUrl("about:blank");
             JSONObject obj = new JSONObject();
             obj.put("type", EXIT_EVENT);
 
@@ -289,7 +300,10 @@ public class InAppBrowser extends CordovaPlugin {
         // Determine if we should hide the location bar.
         showLocationBar = true;
         if (features != null) {
-            showLocationBar = features.get(LOCATION).booleanValue();
+            Boolean show = features.get(LOCATION);
+            if (show != null) {
+                showLocationBar = show.booleanValue();
+            }
         }
         
         final CordovaWebView thatWebView = this.webView;
@@ -405,7 +419,7 @@ public class InAppBrowser extends CordovaPlugin {
                 close.setLayoutParams(closeLayoutParams);
                 forward.setContentDescription("Close Button");
                 close.setId(5);
-                close.setText("Done");
+                close.setText(buttonLabel);
                 close.setOnClickListener(new View.OnClickListener() {
                     public void onClick(View v) {
                         closeDialog();
@@ -428,10 +442,18 @@ public class InAppBrowser extends CordovaPlugin {
                  */
                 // @TODO: replace with settings.setPluginState(android.webkit.WebSettings.PluginState.ON)
                 settings.setPluginsEnabled(true);
-                settings.setDatabaseEnabled(true);
-                String databasePath = cordova.getActivity().getApplicationContext().getDir("inAppBrowserDB", Context.MODE_PRIVATE).getPath();
-                settings.setDatabasePath(databasePath);
+                
+                //Toggle whether this is enabled or not!
+                Bundle appSettings = cordova.getActivity().getIntent().getExtras();
+                boolean enableDatabase = appSettings.getBoolean("InAppBrowserStorageEnabled", true);
+                if(enableDatabase)
+                {
+                    String databasePath = cordova.getActivity().getApplicationContext().getDir("inAppBrowserDB", Context.MODE_PRIVATE).getPath();
+                    settings.setDatabasePath(databasePath);
+                    settings.setDatabaseEnabled(true);
+                }
                 settings.setDomStorageEnabled(true);
+               
                 inAppWebView.loadUrl(url);
                 inAppWebView.setId(6);
                 inAppWebView.getSettings().setLoadWithOverviewMode(true);
@@ -472,16 +494,24 @@ public class InAppBrowser extends CordovaPlugin {
     }
 
     /**
-     * Create a new plugin result and send it back to JavaScript
+     * Create a new plugin success result and send it back to JavaScript
      *
      * @param obj a JSONObject contain event payload information
      */
     private void sendUpdate(JSONObject obj, boolean keepCallback) {
-        PluginResult result = new PluginResult(PluginResult.Status.OK, obj);
+        sendUpdate(obj, keepCallback, PluginResult.Status.OK);
+    }
+
+    /**
+     * Create a new plugin result and send it back to JavaScript
+     *
+     * @param obj a JSONObject contain event payload information
+     * @param status the status code to return to the JavaScript environment
+     */    private void sendUpdate(JSONObject obj, boolean keepCallback, PluginResult.Status status) {
+        PluginResult result = new PluginResult(status, obj);
         result.setKeepCallback(keepCallback);
         this.callbackContext.sendPluginResult(result);
     }
-
     public class InAppChromeClient extends WebChromeClient {
 
         /**
@@ -514,6 +544,18 @@ public class InAppBrowser extends CordovaPlugin {
                 quotaUpdater.updateQuota(currentQuota);
             }
         }
+
+        /**
+         * Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin.
+         *
+         * @param origin
+         * @param callback
+         */
+        @Override
+        public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
+            super.onGeolocationPermissionsShowPrompt(origin, callback);
+            callback.invoke(origin, true, false);
+        }
     }
     
     /**
@@ -543,10 +585,62 @@ public class InAppBrowser extends CordovaPlugin {
         @Override
         public void onPageStarted(WebView view, String url,  Bitmap favicon) {
             super.onPageStarted(view, url, favicon);
-            String newloc;
+            String newloc = "";
             if (url.startsWith("http:") || url.startsWith("https:") || url.startsWith("file:")) {
                 newloc = url;
-            } else {
+            } 
+            // If dialing phone (tel:5551212)
+            else if (url.startsWith(WebView.SCHEME_TEL)) {
+                try {
+                    Intent intent = new Intent(Intent.ACTION_DIAL);
+                    intent.setData(Uri.parse(url));
+                    cordova.getActivity().startActivity(intent);
+                } catch (android.content.ActivityNotFoundException e) {
+                    LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString());
+                }
+            }
+
+            else if (url.startsWith("geo:") || url.startsWith(WebView.SCHEME_MAILTO) || url.startsWith("market:")) {
+                try {
+                    Intent intent = new Intent(Intent.ACTION_VIEW);
+                    intent.setData(Uri.parse(url));
+                    cordova.getActivity().startActivity(intent);
+                } catch (android.content.ActivityNotFoundException e) {
+                    LOG.e(LOG_TAG, "Error with " + url + ": " + e.toString());
+                }
+            }
+            // If sms:5551212?body=This is the message
+            else if (url.startsWith("sms:")) {
+                try {
+                    Intent intent = new Intent(Intent.ACTION_VIEW);
+
+                    // Get address
+                    String address = null;
+                    int parmIndex = url.indexOf('?');
+                    if (parmIndex == -1) {
+                        address = url.substring(4);
+                    }
+                    else {
+                        address = url.substring(4, parmIndex);
+
+                        // If body, then set sms body
+                        Uri uri = Uri.parse(url);
+                        String query = uri.getQuery();
+                        if (query != null) {
+                            if (query.startsWith("body=")) {
+                                intent.putExtra("sms_body", query.substring(5));
+                            }
+                        }
+                    }
+                    intent.setData(Uri.parse("sms:" + address));
+                    intent.putExtra("address", address);
+                    intent.setType("vnd.android-dir/mms-sms");
+                    cordova.getActivity().startActivity(intent);
+                } catch (android.content.ActivityNotFoundException e) {
+                    LOG.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString());
+                }
+            }
+            else {
                 newloc = "http://" + url;
             }
 
@@ -578,5 +672,22 @@ public class InAppBrowser extends CordovaPlugin {
                 Log.d(LOG_TAG, "Should never happen");
             }
         }
+        
+        public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
+            super.onReceivedError(view, errorCode, description, failingUrl);
+            
+            try {
+                JSONObject obj = new JSONObject();
+                obj.put("type", LOAD_ERROR_EVENT);
+                obj.put("url", failingUrl);
+                obj.put("code", errorCode);
+                obj.put("message", description);
+    
+                sendUpdate(obj, true, PluginResult.Status.ERROR);
+            } catch (JSONException ex) {
+                Log.d(LOG_TAG, "Should never happen");
+            }
+        	
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-android/framework/src/org/apache/cordova/JSONUtils.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/JSONUtils.java b/lib/cordova-android/framework/src/org/apache/cordova/JSONUtils.java
new file mode 100644
index 0000000..77df876
--- /dev/null
+++ b/lib/cordova-android/framework/src/org/apache/cordova/JSONUtils.java
@@ -0,0 +1,24 @@
+package org.apache.cordova;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+public class JSONUtils {
+	public static List<String> toStringList(JSONArray array) throws JSONException {
+        if(array == null) {
+            return null;
+        }
+        else {
+            List<String> list = new ArrayList<String>();
+
+            for (int i = 0; i < array.length(); i++) {
+                list.add(array.get(i).toString());
+            }
+
+            return list;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-android/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/NativeToJsMessageQueue.java b/lib/cordova-android/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
index 83e1778..ea684a4 100755
--- a/lib/cordova-android/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
+++ b/lib/cordova-android/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
@@ -409,6 +409,9 @@ public class NativeToJsMessageQueue {
                 case PluginResult.MESSAGE_TYPE_STRING: // s
                     ret += 1 + pluginResult.getStrMessage().length();
                     break;
+                case PluginResult.MESSAGE_TYPE_BINARYSTRING:
+                    ret += 1 + pluginResult.getMessage().length();
+                    break;
                 case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
                     ret += 1 + pluginResult.getMessage().length();
                     break;
@@ -451,7 +454,11 @@ public class NativeToJsMessageQueue {
                     sb.append('s');
                     sb.append(pluginResult.getStrMessage());
                     break;
-                case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
+                case PluginResult.MESSAGE_TYPE_BINARYSTRING: // S
+                    sb.append('S');
+                    sb.append(pluginResult.getMessage());
+                    break;                    
+                case PluginResult.MESSAGE_TYPE_ARRAYBUFFER: // A
                     sb.append('A');
                     sb.append(pluginResult.getMessage());
                     break;
@@ -473,9 +480,9 @@ public class NativeToJsMessageQueue {
                   .append(success)
                   .append(",")
                   .append(status)
-                  .append(",")
+                  .append(",[")
                   .append(pluginResult.getMessage())
-                  .append(",")
+                  .append("],")
                   .append(pluginResult.getKeepCallback())
                   .append(");");
             }

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-android/framework/src/org/apache/cordova/Notification.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/Notification.java b/lib/cordova-android/framework/src/org/apache/cordova/Notification.java
index 958ab26..9d96062 100755
--- a/lib/cordova-android/framework/src/org/apache/cordova/Notification.java
+++ b/lib/cordova-android/framework/src/org/apache/cordova/Notification.java
@@ -24,6 +24,7 @@ import org.apache.cordova.api.CordovaPlugin;
 import org.apache.cordova.api.PluginResult;
 import org.json.JSONArray;
 import org.json.JSONException;
+import org.json.JSONObject;
 import android.app.AlertDialog;
 import android.app.ProgressDialog;
 import android.content.Context;
@@ -32,6 +33,7 @@ import android.media.Ringtone;
 import android.media.RingtoneManager;
 import android.net.Uri;
 import android.os.Vibrator;
+import android.widget.EditText;
 
 /**
  * This class provides access to notifications on the device.
@@ -68,7 +70,11 @@ public class Notification extends CordovaPlugin {
             return true;
         }
         else if (action.equals("confirm")) {
-            this.confirm(args.getString(0), args.getString(1), args.getString(2), callbackContext);
+            this.confirm(args.getString(0), args.getString(1), args.getJSONArray(2), callbackContext);
+            return true;
+        }
+        else if (action.equals("prompt")) {
+            this.prompt(args.getString(0), args.getString(1), args.getJSONArray(2), callbackContext);
             return true;
         }
         else if (action.equals("activityStart")) {
@@ -170,7 +176,7 @@ public class Notification extends CordovaPlugin {
                         callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0));
                     }
                 });
-                
+
                 dlg.create();
                 dlg.show();
             };
@@ -188,10 +194,9 @@ public class Notification extends CordovaPlugin {
      * @param buttonLabels      A comma separated list of button labels (Up to 3 buttons)
      * @param callbackContext   The callback context.
      */
-    public synchronized void confirm(final String message, final String title, String buttonLabels, final CallbackContext callbackContext) {
+    public synchronized void confirm(final String message, final String title, final JSONArray buttonLabels, final CallbackContext callbackContext) {
 
         final CordovaInterface cordova = this.cordova;
-        final String[] fButtons = buttonLabels.split(",");
 
         Runnable runnable = new Runnable() {
             public void run() {
@@ -201,37 +206,43 @@ public class Notification extends CordovaPlugin {
                 dlg.setCancelable(true);
 
                 // First button
-                if (fButtons.length > 0) {
-                    dlg.setNegativeButton(fButtons[0],
-                            new AlertDialog.OnClickListener() {
-                                public void onClick(DialogInterface dialog, int which) {
-                                    dialog.dismiss();
-                                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 1));
-                                }
-                            });
+                if (buttonLabels.length() > 0) {
+                    try {
+						dlg.setNegativeButton(buttonLabels.getString(0),
+						        new AlertDialog.OnClickListener() {
+						            public void onClick(DialogInterface dialog, int which) {
+						                dialog.dismiss();
+						                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 1));
+						            }
+						        });
+					} catch (JSONException e) { }
                 }
 
                 // Second button
-                if (fButtons.length > 1) {
-                    dlg.setNeutralButton(fButtons[1],
-                            new AlertDialog.OnClickListener() {
-                                public void onClick(DialogInterface dialog, int which) {
-                                    dialog.dismiss();
-                                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 2));
-                                }
-                            });
+                if (buttonLabels.length() > 1) {
+                    try {
+						dlg.setNeutralButton(buttonLabels.getString(1),
+						        new AlertDialog.OnClickListener() {
+						            public void onClick(DialogInterface dialog, int which) {
+						                dialog.dismiss();
+						                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 2));
+						            }
+						        });
+					} catch (JSONException e) { }
                 }
 
                 // Third button
-                if (fButtons.length > 2) {
-                    dlg.setPositiveButton(fButtons[2],
-                            new AlertDialog.OnClickListener() {
-                                public void onClick(DialogInterface dialog, int which) {
-                                    dialog.dismiss();
-                                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 3));
-                                }
-                            }
-                            );
+                if (buttonLabels.length() > 2) {
+                    try {
+						dlg.setPositiveButton(buttonLabels.getString(2),
+						        new AlertDialog.OnClickListener() {
+						            public void onClick(DialogInterface dialog, int which) {
+						                dialog.dismiss();
+						                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 3));
+						            }
+						        }
+						        );
+					} catch (JSONException e) { }
                 }
                 dlg.setOnCancelListener(new AlertDialog.OnCancelListener() {
                     public void onCancel(DialogInterface dialog)
@@ -249,6 +260,104 @@ public class Notification extends CordovaPlugin {
     }
 
     /**
+     * Builds and shows a native Android prompt dialog with given title, message, buttons.
+     * This dialog only shows up to 3 buttons.  Any labels after that will be ignored.
+     * The following results are returned to the JavaScript callback identified by callbackId:
+     *     buttonIndex			Index number of the button selected
+     *     input1				The text entered in the prompt dialog box
+     *
+     * @param message           The message the dialog should display
+     * @param title             The title of the dialog
+     * @param buttonLabels      A comma separated list of button labels (Up to 3 buttons)
+     * @param callbackContext   The callback context.
+     */
+    public synchronized void prompt(final String message, final String title, final JSONArray buttonLabels, final CallbackContext callbackContext) {
+    	
+        final CordovaInterface cordova = this.cordova;
+        final EditText promptInput =  new EditText(cordova.getActivity());
+
+        Runnable runnable = new Runnable() {
+            public void run() {
+                AlertDialog.Builder dlg = new AlertDialog.Builder(cordova.getActivity());
+                dlg.setMessage(message);
+                dlg.setTitle(title);
+                dlg.setCancelable(true);
+                
+                dlg.setView(promptInput);
+                
+                final JSONObject result = new JSONObject();
+                
+                // First button
+                if (buttonLabels.length() > 0) {
+                    try {
+						dlg.setNegativeButton(buttonLabels.getString(0),
+						        new AlertDialog.OnClickListener() {
+						            public void onClick(DialogInterface dialog, int which) {
+						                dialog.dismiss();
+						                try {
+											result.put("buttonIndex",1);
+							                result.put("input1", promptInput.getText());
+										} catch (JSONException e) { e.printStackTrace(); }
+						                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, result));
+						            }
+						        });
+					} catch (JSONException e) { }
+                }
+
+                // Second button
+                if (buttonLabels.length() > 1) {
+                    try {
+						dlg.setNeutralButton(buttonLabels.getString(1),
+						        new AlertDialog.OnClickListener() {
+						            public void onClick(DialogInterface dialog, int which) {
+						                dialog.dismiss();
+						                try {
+											result.put("buttonIndex",2);
+							                result.put("input1", promptInput.getText());
+										} catch (JSONException e) { e.printStackTrace(); }
+						                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, result));
+						            }
+						        });
+					} catch (JSONException e) { }
+                }
+
+                // Third button
+                if (buttonLabels.length() > 2) {
+                    try {
+						dlg.setPositiveButton(buttonLabels.getString(2),
+						        new AlertDialog.OnClickListener() {
+						            public void onClick(DialogInterface dialog, int which) {
+						                dialog.dismiss();
+						                try {
+											result.put("buttonIndex",3);
+							                result.put("input1", promptInput.getText());
+										} catch (JSONException e) { e.printStackTrace(); }
+						                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, result));
+						            }
+						        }
+						        );
+					} catch (JSONException e) { }
+                }
+                dlg.setOnCancelListener(new AlertDialog.OnCancelListener() {
+                    public void onCancel(DialogInterface dialog)
+                    {
+                        dialog.dismiss();
+		                try {
+							result.put("buttonIndex",0);
+			                result.put("input1", promptInput.getText());
+						} catch (JSONException e) { e.printStackTrace(); }
+		                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, result));
+                    }
+                });
+
+                dlg.create();
+                dlg.show();
+                
+            };
+        };
+        this.cordova.getActivity().runOnUiThread(runnable);
+    }
+    /**
      * Show the spinner.
      *
      * @param title     Title of the dialog

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-android/framework/src/org/apache/cordova/api/CallbackContext.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/api/CallbackContext.java b/lib/cordova-android/framework/src/org/apache/cordova/api/CallbackContext.java
index 2f7b15f..a5d1255 100644
--- a/lib/cordova-android/framework/src/org/apache/cordova/api/CallbackContext.java
+++ b/lib/cordova-android/framework/src/org/apache/cordova/api/CallbackContext.java
@@ -79,6 +79,15 @@ public class CallbackContext {
     public void success(byte[] message) {
         sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
     }
+    
+    /**
+     * Helper for success callbacks that just returns the Status.OK by default
+     *
+     * @param message           The message to add to the success result.
+     */
+    public void success(int message) {
+        sendPluginResult(new PluginResult(PluginResult.Status.OK, message));
+    }
 
     /**
      * Helper for success callbacks that just returns the Status.OK by default

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-android/framework/src/org/apache/cordova/api/CordovaPlugin.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/api/CordovaPlugin.java b/lib/cordova-android/framework/src/org/apache/cordova/api/CordovaPlugin.java
index f4c785e..2b225e6 100644
--- a/lib/cordova-android/framework/src/org/apache/cordova/api/CordovaPlugin.java
+++ b/lib/cordova-android/framework/src/org/apache/cordova/api/CordovaPlugin.java
@@ -22,7 +22,12 @@ import org.apache.cordova.CordovaArgs;
 import org.apache.cordova.CordovaWebView;
 import org.json.JSONArray;
 import org.json.JSONException;
+
+import android.annotation.TargetApi;
 import android.content.Intent;
+import android.os.Build;
+import android.util.Log;
+import android.webkit.WebResourceResponse;
 
 /**
  * Plugins must extend this class and override one of the execute methods.
@@ -150,7 +155,7 @@ public class CordovaPlugin {
     }
 
     /**
-     * By specifying a <url-filter> in plugins.xml you can map a URL (using startsWith atm) to this method.
+     * By specifying a <url-filter> in config.xml you can map a URL (using startsWith atm) to this method.
      *
      * @param url				The URL that is trying to be loaded in the Cordova webview.
      * @return					Return true to prevent the URL from loading. Default is false.
@@ -160,6 +165,17 @@ public class CordovaPlugin {
     }
 
     /**
+     * By specifying a <url-filter> in config.xml you can map a URL prefix to this method. It applies to all resources loaded in the WebView, not just top-level navigation.
+     *
+     * @param url               The URL of the resource to be loaded.
+     * @return                  Return a WebResourceResponse for the resource, or null to let the WebView handle it normally.
+     */
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+	public WebResourceResponse shouldInterceptRequest(String url) {
+        return null;
+    }
+
+    /**
      * Called when the WebView does a top-level navigation or refreshes.
      *
      * Plugins should stop any long-running processes and clean up internal state.

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-android/framework/src/org/apache/cordova/api/PluginManager.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/api/PluginManager.java b/lib/cordova-android/framework/src/org/apache/cordova/api/PluginManager.java
index d0e6aef..337ef12 100755
--- a/lib/cordova-android/framework/src/org/apache/cordova/api/PluginManager.java
+++ b/lib/cordova-android/framework/src/org/apache/cordova/api/PluginManager.java
@@ -30,6 +30,8 @@ import org.xmlpull.v1.XmlPullParserException;
 import android.content.Intent;
 import android.content.res.XmlResourceParser;
 
+import android.webkit.WebResourceResponse;
+
 /**
  * PluginManager is exposed to JavaScript in the Cordova WebView.
  *
@@ -118,7 +120,7 @@ public class PluginManager {
                     // System.out.println("Plugin: "+name+" => "+value);
                     onload = "true".equals(xml.getAttributeValue(null, "onload"));
                     entry = new PluginEntry(service, pluginClass, onload);
-                    this.addService(entry);                   
+                    this.addService(entry);
                 }
                 //What is this?
                 else if (strNode.equals("url-filter")) {
@@ -367,6 +369,25 @@ public class PluginManager {
     }
 
     /**
+     * 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.
      */
     public void onReset() {

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-android/framework/src/org/apache/cordova/api/PluginResult.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/api/PluginResult.java b/lib/cordova-android/framework/src/org/apache/cordova/api/PluginResult.java
index 4c1d833..a642200 100755
--- a/lib/cordova-android/framework/src/org/apache/cordova/api/PluginResult.java
+++ b/lib/cordova-android/framework/src/org/apache/cordova/api/PluginResult.java
@@ -71,11 +71,15 @@ public class PluginResult {
     }
 
     public PluginResult(Status status, byte[] data) {
+        this(status, data, false);
+    }
+
+    public PluginResult(Status status, byte[] data, boolean binaryString) {
         this.status = status.ordinal();
-        this.messageType = MESSAGE_TYPE_ARRAYBUFFER;
+        this.messageType = binaryString ? MESSAGE_TYPE_BINARYSTRING : MESSAGE_TYPE_ARRAYBUFFER;
         this.encodedMessage = Base64.encodeToString(data, Base64.NO_WRAP);
     }
-
+    
     public void setKeepCallback(boolean b) {
         this.keepCallback = b;
     }
@@ -143,6 +147,9 @@ public class PluginResult {
     public static final int MESSAGE_TYPE_BOOLEAN = 4;
     public static final int MESSAGE_TYPE_NULL = 5;
     public static final int MESSAGE_TYPE_ARRAYBUFFER = 6;
+    // Use BINARYSTRING when your string may contain null characters.
+    // This is required to work around a bug in the platform :(.
+    public static final int MESSAGE_TYPE_BINARYSTRING = 7;
 
     public static String[] StatusMessages = new String[] {
         "No result",

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-blackberry/bin/create
----------------------------------------------------------------------
diff --git a/lib/cordova-blackberry/bin/create b/lib/cordova-blackberry/bin/create
index 870bad1..b7e719b 100755
--- a/lib/cordova-blackberry/bin/create
+++ b/lib/cordova-blackberry/bin/create
@@ -56,6 +56,19 @@ function on_error {
     [ -d "$PROJECT_PATH" ] && rm -rf "$PROJECT_PATH"
 }
 
+function replace {
+    local pattern=$1
+    local filename=$2
+    # Mac OS X requires -i argument
+    if [[ "$OSTYPE" =~ "darwin" ]]
+    then
+        /usr/bin/sed -i '' -e $pattern "$filename"
+    elif [[ "$OSTYPE" =~ "linux" ]]
+    then
+        /bin/sed -i -e $pattern "$filename"
+    fi
+}
+
 # we do not want the script to silently fail
 trap on_error ERR
 trap on_exit EXIT
@@ -79,13 +92,13 @@ then
 	(cd "$BUILD_PATH" && "$ANT" create -Dproject.path="$PROJECT_PATH" &> /dev/null )
     # interpolate the activity and package into config.xml
     echo "Updating config.xml ..."
-    sed -i '' -e "s/__NAME__/${NAME}/g" "$MANIFEST_PATH"
-    sed -i '' -e "s/__PACKAGE__/${PACKAGE}/g" "$MANIFEST_PATH"
+    replace "s/__NAME__/${NAME}/g" "$MANIFEST_PATH"
+    replace "s/__PACKAGE__/${PACKAGE}/g" "$MANIFEST_PATH"
 else
 	# copy project template if in distribution
 	echo "Copying assets and resources ..."
 	cp -r "$BUILD_PATH/sample/." "$PROJECT_PATH"
     echo "Updating config.xml ..."
-    sed -i '' -e "s/cordovaExample/${NAME}/g" "$MANIFEST_PATH"
-    sed -i '' -e "s/org.apache.cordova.example/${PACKAGE}/g" "$MANIFEST_PATH"
+    replace "s/cordovaExample/${NAME}/g" "$MANIFEST_PATH"
+    replace "s/org.apache.cordova.example/${PACKAGE}/g" "$MANIFEST_PATH"
 fi

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/37b92ff4/lib/cordova-blackberry/bin/templates/project/www/index.html
----------------------------------------------------------------------
diff --git a/lib/cordova-blackberry/bin/templates/project/www/index.html b/lib/cordova-blackberry/bin/templates/project/www/index.html
index f083790..6b53abc 100644
--- a/lib/cordova-blackberry/bin/templates/project/www/index.html
+++ b/lib/cordova-blackberry/bin/templates/project/www/index.html
@@ -18,8 +18,8 @@
     under the License.
 -->
 <html>
-    <head>      
-        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+    <head>
+        <meta charset="utf-8" />
         <meta name="format-detection" content="telephone=no" />
         <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
         <link rel="stylesheet" type="text/css" href="css/index.css" />