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 2014/07/04 05:02:42 UTC

[1/8] android commit: Refactoring the URI handling on Cordova, removing dead code

Repository: cordova-android
Updated Branches:
  refs/heads/4.0.x 428e1bc14 -> 4ca230569


Refactoring the URI handling on Cordova, removing dead code


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

Branch: refs/heads/4.0.x
Commit: b0b628ffc24bdd952e86908cf6cb4064b6f3c405
Parents: 4b4a2e9
Author: Joe Bowser <bo...@apache.org>
Authored: Tue Jun 24 12:30:12 2014 -0700
Committer: Joe Bowser <bo...@apache.org>
Committed: Tue Jun 24 12:30:34 2014 -0700

----------------------------------------------------------------------
 .../org/apache/cordova/CordovaUriHelper.java    | 112 +++++++++++++++++++
 .../src/org/apache/cordova/CordovaWebView.java  |  27 ++---
 .../apache/cordova/CordovaWebViewClient.java    | 110 +-----------------
 .../cordova/IceCreamCordovaWebViewClient.java   |  11 +-
 4 files changed, 133 insertions(+), 127 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/b0b628ff/framework/src/org/apache/cordova/CordovaUriHelper.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CordovaUriHelper.java b/framework/src/org/apache/cordova/CordovaUriHelper.java
new file mode 100644
index 0000000..1a113de
--- /dev/null
+++ b/framework/src/org/apache/cordova/CordovaUriHelper.java
@@ -0,0 +1,112 @@
+/*
+       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 org.json.JSONException;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+import android.webkit.WebView;
+
+public class CordovaUriHelper {
+    
+    private static final String TAG = "CordovaUriHelper";
+    private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
+    
+    private CordovaWebView appView;
+    private CordovaInterface cordova;
+    
+    CordovaUriHelper(CordovaInterface cdv, CordovaWebView webView)
+    {
+        appView = webView;
+        cordova = cdv;
+    }
+    
+    
+    // Parses commands sent by setting the webView's URL to:
+    // cdvbrg:service/action/callbackId#jsonArgs
+    void handleExecUrl(String url) {
+        int idx1 = CORDOVA_EXEC_URL_PREFIX.length();
+        int idx2 = url.indexOf('#', idx1 + 1);
+        int idx3 = url.indexOf('#', idx2 + 1);
+        int idx4 = url.indexOf('#', idx3 + 1);
+        if (idx1 == -1 || idx2 == -1 || idx3 == -1 || idx4 == -1) {
+            Log.e(TAG, "Could not decode URL command: " + url);
+            return;
+        }
+        String service    = url.substring(idx1, idx2);
+        String action     = url.substring(idx2 + 1, idx3);
+        String callbackId = url.substring(idx3 + 1, idx4);
+        String jsonArgs   = url.substring(idx4 + 1);
+        appView.pluginManager.exec(service, action, callbackId, jsonArgs);
+        //There is no reason to not send this directly to the pluginManager
+    }
+    
+
+    /**
+     * Give the host application a chance to take over the control when a new url
+     * is about to be loaded in the current WebView.
+     *
+     * @param view          The WebView that is initiating the callback.
+     * @param url           The url to be loaded.
+     * @return              true to override, false for default behavior
+     */
+    public boolean shouldOverrideUrlLoading(WebView view, String url) {
+        // The WebView should support http and https when going on the Internet
+        if(url.startsWith("http:") || url.startsWith("https:"))
+        {
+            // Check if it's an exec() bridge command message.
+            if (NativeToJsMessageQueue.ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) {
+                handleExecUrl(url);
+            }
+            // We only need to whitelist sites on the Internet! 
+            else if(Config.isUrlWhiteListed(url))
+            {
+                return false;
+            }
+        }
+        // Give plugins the chance to handle the url
+        else if (this.appView.pluginManager.onOverrideUrlLoading(url)) {
+            
+        }
+        else if(url.startsWith("file://") | url.startsWith("data:"))
+        {
+            //This directory on WebKit/Blink based webviews contains SQLite databases!
+            //DON'T CHANGE THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
+            return url.contains("app_webview");
+        }
+        else
+        {
+            try {
+                Intent intent = new Intent(Intent.ACTION_VIEW);
+                intent.setData(Uri.parse(url));
+                this.cordova.getActivity().startActivity(intent);
+            } catch (android.content.ActivityNotFoundException e) {
+                LOG.e(TAG, "Error loading url " + url, e);
+            }
+        }
+        //Default behaviour should be to load the default intent, let's see what happens! 
+        return true;
+    }
+
+    
+
+}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/b0b628ff/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 b31eec5..ad5c0f0 100755
--- a/framework/src/org/apache/cordova/CordovaWebView.java
+++ b/framework/src/org/apache/cordova/CordovaWebView.java
@@ -401,17 +401,7 @@ public class CordovaWebView extends WebView {
             this.loadUrlNow(url);
         }
         else {
-
-            String initUrl = this.getProperty("url", null);
-
-            // If first page of app, then set URL to load to be the one passed in
-            if (initUrl == null) {
-                this.loadUrlIntoView(url);
-            }
-            // Otherwise use the URL specified in the activity's extras bundle
-            else {
-                this.loadUrlIntoView(initUrl);
-            }
+            this.loadUrlIntoView(url);
         }
     }
 
@@ -422,16 +412,15 @@ public class CordovaWebView extends WebView {
      * @param url
      * @param time              The number of ms to wait before loading webview
      */
+    @Deprecated
     public void loadUrl(final String url, int time) {
-        String initUrl = this.getProperty("url", null);
-
-        // If first page of app, then set URL to load to be the one passed in
-        if (initUrl == null) {
-            this.loadUrlIntoView(url, time);
+        if(url == null)
+        {
+            this.loadUrlIntoView(Config.getStartUrl());
         }
-        // Otherwise use the URL specified in the activity's extras bundle
-        else {
-            this.loadUrlIntoView(initUrl);
+        else
+        {
+            this.loadUrlIntoView(url);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/b0b628ff/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 407c1fb..4a72fea 100755
--- a/framework/src/org/apache/cordova/CordovaWebViewClient.java
+++ b/framework/src/org/apache/cordova/CordovaWebViewClient.java
@@ -61,6 +61,7 @@ public class CordovaWebViewClient extends WebViewClient {
 	private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
     CordovaInterface cordova;
     CordovaWebView appView;
+    CordovaUriHelper helper;
     private boolean doClearHistory = false;
     boolean isCurrentlyLoading;
 
@@ -85,6 +86,7 @@ public class CordovaWebViewClient extends WebViewClient {
     public CordovaWebViewClient(CordovaInterface cordova, CordovaWebView view) {
         this.cordova = cordova;
         this.appView = view;
+        helper = new CordovaUriHelper(cordova, view);
     }
 
     /**
@@ -94,6 +96,7 @@ public class CordovaWebViewClient extends WebViewClient {
      */
     public void setWebView(CordovaWebView view) {
         this.appView = view;
+        helper = new CordovaUriHelper(cordova, view);
     }
 
 
@@ -125,112 +128,7 @@ public class CordovaWebViewClient extends WebViewClient {
      */
 	@Override
     public boolean shouldOverrideUrlLoading(WebView view, String url) {
-    	// Check if it's an exec() bridge command message.
-    	if (NativeToJsMessageQueue.ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) {
-    		handleExecUrl(url);
-    	}
-
-        // Give plugins the chance to handle the url
-    	else if ((this.appView.pluginManager != null) && this.appView.pluginManager.onOverrideUrlLoading(url)) {
-        }
-
-        // 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));
-                this.cordova.getActivity().startActivity(intent);
-            } catch (android.content.ActivityNotFoundException e) {
-                LOG.e(TAG, "Error dialing " + url + ": " + e.toString());
-            }
-        }
-
-        // If displaying map (geo:0,0?q=address)
-        else if (url.startsWith("geo:")) {
-            try {
-                Intent intent = new Intent(Intent.ACTION_VIEW);
-                intent.setData(Uri.parse(url));
-                this.cordova.getActivity().startActivity(intent);
-            } catch (android.content.ActivityNotFoundException e) {
-                LOG.e(TAG, "Error showing map " + url + ": " + e.toString());
-            }
-        }
-
-        // If sending email (mailto:abc@corp.com)
-        else if (url.startsWith(WebView.SCHEME_MAILTO)) {
-            try {
-                Intent intent = new Intent(Intent.ACTION_VIEW);
-                intent.setData(Uri.parse(url));
-                this.cordova.getActivity().startActivity(intent);
-            } catch (android.content.ActivityNotFoundException e) {
-                LOG.e(TAG, "Error sending email " + 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");
-                this.cordova.getActivity().startActivity(intent);
-            } catch (android.content.ActivityNotFoundException e) {
-                LOG.e(TAG, "Error sending sms " + url + ":" + e.toString());
-            }
-        }
-        
-        //Android Market
-        else if(url.startsWith("market:")) {
-            try {
-                Intent intent = new Intent(Intent.ACTION_VIEW);
-                intent.setData(Uri.parse(url));
-                this.cordova.getActivity().startActivity(intent);
-            } catch (android.content.ActivityNotFoundException e) {
-                LOG.e(TAG, "Error loading Google Play Store: " + url, e);
-            }
-        }
-
-        // All else
-        else {
-
-            // 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:")  || Config.isUrlWhiteListed(url)) {
-                return false;
-            }
-
-            // If not our application, let default viewer handle
-            else {
-                try {
-                    Intent intent = new Intent(Intent.ACTION_VIEW);
-                    intent.setData(Uri.parse(url));
-                    this.cordova.getActivity().startActivity(intent);
-                } catch (android.content.ActivityNotFoundException e) {
-                    LOG.e(TAG, "Error loading url " + url, e);
-                }
-            }
-        }
-        return true;
+        return helper.shouldOverrideUrlLoading(view, url);
     }
     
     /**

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/b0b628ff/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 63cfb91..67793d7 100644
--- a/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
+++ b/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
@@ -35,6 +35,7 @@ import android.webkit.WebView;
 public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
 
     private static final String TAG = "IceCreamCordovaWebViewClient";
+    private CordovaUriHelper helper;
 
     public IceCreamCordovaWebViewClient(CordovaInterface cordova) {
         super(cordova);
@@ -47,8 +48,9 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
     @Override
     public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
         try {
-            // Check the against the white-list.
-            if ((url.startsWith("http:") || url.startsWith("https:")) && !Config.isUrlWhiteListed(url)) {
+            // Check the against the whitelist and lock out access to the WebView directory
+            // Changing this will cause problems for your application
+            if (isUrlHarmful(url)) {
                 LOG.w(TAG, "URL blocked by whitelist: " + url);
                 // Results in a 404.
                 return new WebResourceResponse("text/plain", "UTF-8", null);
@@ -74,6 +76,11 @@ public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
         }
     }
 
+    private boolean isUrlHarmful(String url) {
+        return ((url.startsWith("http:") || url.startsWith("https:")) && !Config.isUrlWhiteListed(url))
+            || url.contains("app_webview");
+    }
+
     private static boolean needsKitKatContentUrlFix(Uri uri) {
         return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && "content".equals(uri.getScheme());
     }


[5/8] android commit: CB-5988 Allow exec() only from file: or start-up URL's domain

Posted by ag...@apache.org.
CB-5988 Allow exec() only from file: or start-up URL's domain

Uses prompt() to validate the origin of the calling JS.
This change also simplifies the start-up logic by explicitly disabling
the bridge during page transitions and explictly enabling it when the
JS asks for the bridgeSecret.

We now wait to fire onNativeReady in JS until the bridge is initialized.
It is therefore safe to delete the queue-clear/new exec race condition
code that was in PluginManager.


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

Branch: refs/heads/4.0.x
Commit: aab47bd4532bfe8707d745638eb5695ac543c681
Parents: 445ddd8
Author: Andrew Grieve <ag...@chromium.org>
Authored: Thu Jul 3 21:58:35 2014 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Thu Jul 3 22:06:09 2014 -0400

----------------------------------------------------------------------
 .../org/apache/cordova/CordovaChromeClient.java | 134 ++++++++++---------
 .../apache/cordova/CordovaWebViewClient.java    |   5 +-
 .../src/org/apache/cordova/ExposedJsApi.java    |  28 +++-
 .../apache/cordova/NativeToJsMessageQueue.java  |  63 ++++-----
 .../src/org/apache/cordova/PluginManager.java   |  44 ------
 5 files changed, 132 insertions(+), 142 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/aab47bd4/framework/src/org/apache/cordova/CordovaChromeClient.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CordovaChromeClient.java b/framework/src/org/apache/cordova/CordovaChromeClient.java
index 2edabf1..f2c3350 100755
--- a/framework/src/org/apache/cordova/CordovaChromeClient.java
+++ b/framework/src/org/apache/cordova/CordovaChromeClient.java
@@ -28,6 +28,7 @@ import android.app.AlertDialog;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.net.Uri;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.View;
@@ -201,64 +202,75 @@ public class CordovaChromeClient extends WebChromeClient {
      * Since we are hacking prompts for our own purposes, we should not be using them for
      * this purpose, perhaps we should hack console.log to do this instead!
      *
-     * @param view
-     * @param url
-     * @param message
-     * @param defaultValue
-     * @param result
      * @see Other implementation in the Dialogs plugin.
      */
     @Override
-    public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
-
-        // Security check to make sure any requests are coming from the page initially
-        // loaded in webview and not another loaded in an iframe.
-        boolean reqOk = false;
-        if (url.startsWith("file://") || Config.isUrlWhiteListed(url)) {
-            reqOk = true;
-        }
-
-        // Calling PluginManager.exec() to call a native service using 
-        // prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true]));
-        if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) {
+    public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, JsPromptResult result) {
+        // Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
+        if (defaultValue != null && defaultValue.length() > 3 && defaultValue.startsWith("gap:")) {
             JSONArray array;
             try {
                 array = new JSONArray(defaultValue.substring(4));
-                String service = array.getString(0);
-                String action = array.getString(1);
-                String callbackId = array.getString(2);
-                String r = this.appView.exposedJsApi.exec(service, action, callbackId, message);
+                int bridgeSecret = array.getInt(0);
+                String service = array.getString(1);
+                String action = array.getString(2);
+                String callbackId = array.getString(3);
+                String r = appView.exposedJsApi.exec(bridgeSecret, service, action, callbackId, message);
                 result.confirm(r == null ? "" : r);
             } catch (JSONException e) {
                 e.printStackTrace();
-                return false;
+                result.cancel();
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+                result.cancel();
             }
         }
 
         // Sets the native->JS bridge mode. 
-        else if (reqOk && defaultValue != null && defaultValue.equals("gap_bridge_mode:")) {
-        	try {
-                this.appView.exposedJsApi.setNativeToJsBridgeMode(Integer.parseInt(message));
-                result.confirm("");
-        	} catch (NumberFormatException e){
-                result.confirm("");
+        else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {
+            try {
+                int bridgeSecret = Integer.parseInt(defaultValue.substring(16));
+                appView.exposedJsApi.setNativeToJsBridgeMode(bridgeSecret, Integer.parseInt(message));
+                result.cancel();
+            } catch (NumberFormatException e){
                 e.printStackTrace();
-        	}
+                result.cancel();
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+                result.cancel();
+            }
         }
 
         // Polling for JavaScript messages 
-        else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) {
-            String r = this.appView.exposedJsApi.retrieveJsMessages("1".equals(message));
-            result.confirm(r == null ? "" : r);
-        }
-
-        // Do NO-OP so older code doesn't display dialog
-        else if (defaultValue != null && defaultValue.equals("gap_init:")) {
-            result.confirm("OK");
+        else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {
+            int bridgeSecret = Integer.parseInt(defaultValue.substring(9));
+            try {
+                String r = appView.exposedJsApi.retrieveJsMessages(bridgeSecret, "1".equals(message));
+                result.confirm(r == null ? "" : r);
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+                result.cancel();
+            }
         }
 
-        // Show dialog
-        else {
+        else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
+            String startUrl = Config.getStartUrl();
+            // Protect against random iframes being able to talk through the bridge.
+            // Trust only file URLs and the start URL's domain.
+            // The extra origin.startsWith("http") is to protect against iframes with data: having "" as origin.
+            if (origin.startsWith("file:") || (origin.startsWith("http") && startUrl.startsWith(origin))) {
+                // Enable the bridge
+                int bridgeMode = Integer.parseInt(defaultValue.substring(9));
+                appView.jsMessageQueue.setBridgeMode(bridgeMode);
+                // Tell JS the bridge secret.
+                int secret = appView.exposedJsApi.generateBridgeSecret();
+                result.confirm(""+secret);
+            } else {
+                Log.e(LOG_TAG, "gap_init called from restricted origin: " + origin);
+                result.cancel();
+            }
+        } else {
+            // Returning false would also show a dialog, but the default one shows the origin (ugly).
             final JsPromptResult res = result;
             AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
             dlg.setMessage(message);
@@ -338,10 +350,10 @@ public class CordovaChromeClient extends WebChromeClient {
         this.appView.showCustomView(view, callback);
     }
 
-	@Override
-	public void onHideCustomView() {
-    	this.appView.hideCustomView();
-	}
+    @Override
+    public void onHideCustomView() {
+        this.appView.hideCustomView();
+    }
     
     @Override
     /**
@@ -351,24 +363,24 @@ public class CordovaChromeClient extends WebChromeClient {
      */
     public View getVideoLoadingProgressView() {
 
-	    if (mVideoProgressView == null) {	        
-	    	// Create a new Loading view programmatically.
-	    	
-	    	// create the linear layout
-	    	LinearLayout layout = new LinearLayout(this.appView.getContext());
-	        layout.setOrientation(LinearLayout.VERTICAL);
-	        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
-	        layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
-	        layout.setLayoutParams(layoutParams);
-	        // the proress bar
-	        ProgressBar bar = new ProgressBar(this.appView.getContext());
-	        LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
-	        barLayoutParams.gravity = Gravity.CENTER;
-	        bar.setLayoutParams(barLayoutParams);   
-	        layout.addView(bar);
-	        
-	        mVideoProgressView = layout;
-	    }
+        if (mVideoProgressView == null) {            
+            // Create a new Loading view programmatically.
+            
+            // create the linear layout
+            LinearLayout layout = new LinearLayout(this.appView.getContext());
+            layout.setOrientation(LinearLayout.VERTICAL);
+            RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+            layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
+            layout.setLayoutParams(layoutParams);
+            // the proress bar
+            ProgressBar bar = new ProgressBar(this.appView.getContext());
+            LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+            barLayoutParams.gravity = Gravity.CENTER;
+            bar.setLayoutParams(barLayoutParams);   
+            layout.addView(bar);
+            
+            mVideoProgressView = layout;
+        }
     return mVideoProgressView; 
     }
     

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/aab47bd4/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 4a72fea..9e276d7 100755
--- a/framework/src/org/apache/cordova/CordovaWebViewClient.java
+++ b/framework/src/org/apache/cordova/CordovaWebViewClient.java
@@ -18,7 +18,6 @@
 */
 package org.apache.cordova;
 
-import java.io.ByteArrayInputStream;
 import java.util.Hashtable;
 
 import org.apache.cordova.CordovaInterface;
@@ -28,18 +27,15 @@ import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.annotation.TargetApi;
-import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.graphics.Bitmap;
-import android.net.Uri;
 import android.net.http.SslError;
 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;
 
@@ -170,6 +166,7 @@ public class CordovaWebViewClient extends WebViewClient {
         LOG.d(TAG, "onPageStarted(" + url + ")");
         // Flush stale messages.
         this.appView.jsMessageQueue.reset();
+        this.appView.exposedJsApi.clearBridgeSecret();
 
         // Broadcast message that page has loaded
         this.appView.postMessage("onPageStarted", url);

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/aab47bd4/framework/src/org/apache/cordova/ExposedJsApi.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/ExposedJsApi.java b/framework/src/org/apache/cordova/ExposedJsApi.java
index fde5722..97f6038 100755
--- a/framework/src/org/apache/cordova/ExposedJsApi.java
+++ b/framework/src/org/apache/cordova/ExposedJsApi.java
@@ -19,6 +19,7 @@
 package org.apache.cordova;
 
 import android.webkit.JavascriptInterface;
+
 import org.apache.cordova.PluginManager;
 import org.json.JSONException;
 
@@ -31,6 +32,7 @@ import org.json.JSONException;
     
     private PluginManager pluginManager;
     private NativeToJsMessageQueue jsMessageQueue;
+    private volatile int bridgeSecret = -1; // written by UI thread, read by JS thread.
     
     public ExposedJsApi(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
         this.pluginManager = pluginManager;
@@ -38,7 +40,8 @@ import org.json.JSONException;
     }
 
     @JavascriptInterface
-    public String exec(String service, String action, String callbackId, String arguments) throws JSONException {
+    public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
+        verifySecret(bridgeSecret);
         // If the arguments weren't received, send a message back to JS.  It will switch bridge modes and try again.  See CB-2666.
         // We send a message meant specifically for this case.  It starts with "@" so no other message can be encoded into the same string.
         if (arguments == null) {
@@ -65,12 +68,31 @@ import org.json.JSONException;
     }
     
     @JavascriptInterface
-    public void setNativeToJsBridgeMode(int value) {
+    public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
+        verifySecret(bridgeSecret);
         jsMessageQueue.setBridgeMode(value);
     }
     
     @JavascriptInterface
-    public String retrieveJsMessages(boolean fromOnlineEvent) {
+    public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
+        verifySecret(bridgeSecret);
         return jsMessageQueue.popAndEncode(fromOnlineEvent);
     }
+
+    private void verifySecret(int value) throws IllegalAccessException {
+        if (bridgeSecret < 0 || value != bridgeSecret) {
+            throw new IllegalAccessException();
+        }
+    }
+
+    /** Called on page transitions */
+    void clearBridgeSecret() {
+        bridgeSecret = -1;
+    }
+
+    /** Called by cordova.js to initialize the bridge. */
+    int generateBridgeSecret() {
+        bridgeSecret = (int)(Math.random() * Integer.MAX_VALUE);
+        return bridgeSecret;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/aab47bd4/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/NativeToJsMessageQueue.java b/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
index 9f6f96e..063fc7e 100755
--- a/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
+++ b/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
@@ -35,9 +35,6 @@ import android.webkit.WebView;
 public class NativeToJsMessageQueue {
     private static final String LOG_TAG = "JsMessageQueue";
 
-    // This must match the default value in cordova-js/lib/android/exec.js
-    private static final int DEFAULT_BRIDGE_MODE = 2;
-    
     // Set this to true to force plugin results to be encoding as
     // JS instead of the custom format (useful for benchmarking).
     private static final boolean FORCE_ENCODE_USING_EVAL = false;
@@ -49,18 +46,13 @@ public class NativeToJsMessageQueue {
     // Disable sending back native->JS messages during an exec() when the active
     // exec() is asynchronous. Set this to true when running bridge benchmarks.
     static final boolean DISABLE_EXEC_CHAINING = false;
-    
+
     // Arbitrarily chosen upper limit for how much data to send to JS in one shot.
     // This currently only chops up on message boundaries. It may be useful
     // to allow it to break up messages.
     private static int MAX_PAYLOAD_SIZE = 50 * 1024 * 10240;
     
     /**
-     * The index into registeredListeners to treat as active. 
-     */
-    private int activeListenerIndex;
-    
-    /**
      * When true, the active listener is not fired upon enqueue. When set to false,
      * the active listener will be fired if the queue is non-empty. 
      */
@@ -76,6 +68,13 @@ public class NativeToJsMessageQueue {
      */
     private final BridgeMode[] registeredListeners;    
     
+    /**
+     * When null, the bridge is disabled. This occurs during page transitions.
+     * When disabled, all callbacks are dropped since they are assumed to be
+     * relevant to the previous page.
+     */
+    private BridgeMode activeBridgeMode;
+
     private final CordovaInterface cordova;
     private final CordovaWebView webView;
 
@@ -94,17 +93,19 @@ public class NativeToJsMessageQueue {
      * Changes the bridge mode.
      */
     public void setBridgeMode(int value) {
-        if (value < 0 || value >= registeredListeners.length) {
+        if (value < -1 || value >= registeredListeners.length) {
             Log.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
         } else {
-            if (value != activeListenerIndex) {
-                Log.d(LOG_TAG, "Set native->JS mode to " + value);
+            BridgeMode newMode = value < 0 ? null : registeredListeners[value];
+            if (newMode != activeBridgeMode) {
+                Log.d(LOG_TAG, "Set native->JS mode to " + (newMode == null ? "null" : newMode.getClass().getSimpleName()));
                 synchronized (this) {
-                    activeListenerIndex = value;
-                    BridgeMode activeListener = registeredListeners[value];
-                    activeListener.reset();
-                    if (!paused && !queue.isEmpty()) {
-                        activeListener.onNativeToJsMessageAvailable();
+                    activeBridgeMode = newMode;
+                    if (newMode != null) {
+                        newMode.reset();
+                        if (!paused && !queue.isEmpty()) {
+                            newMode.onNativeToJsMessageAvailable();
+                        }
                     }
                 }
             }
@@ -117,8 +118,7 @@ public class NativeToJsMessageQueue {
     public void reset() {
         synchronized (this) {
             queue.clear();
-            setBridgeMode(DEFAULT_BRIDGE_MODE);
-            registeredListeners[activeListenerIndex].reset();
+            setBridgeMode(-1);
         }
     }
 
@@ -142,7 +142,10 @@ public class NativeToJsMessageQueue {
      */
     public String popAndEncode(boolean fromOnlineEvent) {
         synchronized (this) {
-            registeredListeners[activeListenerIndex].notifyOfFlush(fromOnlineEvent);
+            if (activeBridgeMode == null) {
+                return null;
+            }
+            activeBridgeMode.notifyOfFlush(fromOnlineEvent);
             if (queue.isEmpty()) {
                 return null;
             }
@@ -247,16 +250,20 @@ public class NativeToJsMessageQueue {
 
         enqueueMessage(message);
     }
-    
+
     private void enqueueMessage(JsMessage message) {
         synchronized (this) {
+            if (activeBridgeMode == null) {
+                Log.d(LOG_TAG, "Dropping Native->JS message due to disabled bridge");
+                return;
+            }
             queue.add(message);
             if (!paused) {
-                registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable();
+                activeBridgeMode.onNativeToJsMessageAvailable();
             }
-        }        
+        }
     }
-    
+
     public void setPaused(boolean value) {
         if (paused && value) {
             // This should never happen. If a use-case for it comes up, we should
@@ -266,16 +273,12 @@ public class NativeToJsMessageQueue {
         paused = value;
         if (!value) {
             synchronized (this) {
-                if (!queue.isEmpty()) {
-                    registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable();
+                if (!queue.isEmpty() && activeBridgeMode != null) {
+                    activeBridgeMode.onNativeToJsMessageAvailable();
                 }
             }   
         }
     }
-    
-    public boolean getPaused() {
-        return paused;
-    }
 
     private abstract class BridgeMode {
         abstract void onNativeToJsMessageAvailable();

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/aab47bd4/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 1ed0523..02536ba 100755
--- a/framework/src/org/apache/cordova/PluginManager.java
+++ b/framework/src/org/apache/cordova/PluginManager.java
@@ -23,9 +23,7 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
 
-import org.apache.cordova.CordovaArgs;
 import org.apache.cordova.CordovaWebView;
 import org.apache.cordova.CallbackContext;
 import org.apache.cordova.CordovaInterface;
@@ -65,8 +63,6 @@ public class PluginManager {
     // Using <url-filter> is deprecated.
     protected HashMap<String, List<String>> urlMap = new HashMap<String, List<String>>();
 
-    private AtomicInteger numPendingUiExecs;
-
     /**
      * Constructor.
      *
@@ -77,7 +73,6 @@ public class PluginManager {
         this.ctx = ctx;
         this.app = app;
         this.firstRun = true;
-        this.numPendingUiExecs = new AtomicInteger(0);
     }
 
     /**
@@ -99,9 +94,6 @@ public class PluginManager {
             this.clearPluginObjects();
         }
 
-        // Insert PluginManager service
-        this.addService(new PluginEntry("PluginManager", new PluginManagerService()));
-
         // Start up all plugins that have onload specified
         this.startupPlugins();
     }
@@ -216,20 +208,6 @@ public class PluginManager {
      *                      plugin execute method.
      */
     public void exec(final String service, final String action, final String callbackId, final String rawArgs) {
-        if (numPendingUiExecs.get() > 0) {
-            numPendingUiExecs.getAndIncrement();
-            this.ctx.getActivity().runOnUiThread(new Runnable() {
-                public void run() {
-                    execHelper(service, action, callbackId, rawArgs);
-                    numPendingUiExecs.getAndDecrement();
-                }
-            });
-        } else {
-            execHelper(service, action, callbackId, rawArgs);
-        }
-    }
-
-    private void execHelper(final String service, final String action, final String callbackId, final String rawArgs) {
         CordovaPlugin plugin = getPlugin(service);
         if (plugin == null) {
             Log.d(TAG, "exec() call to unknown plugin: " + service);
@@ -437,26 +415,4 @@ public class PluginManager {
         }
         return null;
     }
-
-    private class PluginManagerService extends CordovaPlugin {
-        @Override
-        public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException {
-            if ("startup".equals(action)) {
-                // The onPageStarted event of CordovaWebViewClient resets the queue of messages to be returned to javascript in response
-                // to exec calls. Since this event occurs on the UI thread and exec calls happen on the WebCore thread it is possible
-                // that onPageStarted occurs after exec calls have started happening on a new page, which can cause the message queue
-                // to be reset between the queuing of a new message and its retrieval by javascript. To avoid this from happening,
-                // javascript always sends a "startup" exec upon loading a new page which causes all future exec calls to happen on the UI
-                // thread (and hence after onPageStarted) until there are no more pending exec calls remaining.
-                numPendingUiExecs.getAndIncrement();
-                ctx.getActivity().runOnUiThread(new Runnable() {
-                    public void run() {
-                        numPendingUiExecs.getAndDecrement();
-                    }
-                });
-                return true;
-            }
-            return false;
-        }
-    }
 }


[7/8] Merge branch 'master' into 4.0.x (Bridge fixes)

Posted by ag...@apache.org.
http://git-wip-us.apache.org/repos/asf/cordova-android/blob/4ca23056/framework/src/org/apache/cordova/CordovaChromeClient.java
----------------------------------------------------------------------
diff --cc framework/src/org/apache/cordova/CordovaChromeClient.java
index 847a466,f2c3350..87177b4
mode 100644,100755..100644
--- a/framework/src/org/apache/cordova/CordovaChromeClient.java
+++ b/framework/src/org/apache/cordova/CordovaChromeClient.java
@@@ -18,15 -18,391 +18,10 @@@
  */
  package org.apache.cordova;
  
 -import org.apache.cordova.CordovaInterface;
 -import org.apache.cordova.LOG;
 -import org.json.JSONArray;
 -import org.json.JSONException;
 -
 -import android.annotation.TargetApi;
 -import android.app.AlertDialog;
 -import android.content.DialogInterface;
 -import android.content.Intent;
  import android.net.Uri;
 -import android.util.Log;
 -import android.view.Gravity;
 -import android.view.KeyEvent;
 -import android.view.View;
 -import android.view.ViewGroup.LayoutParams;
 -import android.webkit.ConsoleMessage;
 -import android.webkit.JsPromptResult;
 -import android.webkit.JsResult;
  import android.webkit.ValueCallback;
 -import android.webkit.WebChromeClient;
 -import android.webkit.WebStorage;
 -import android.webkit.WebView;
 -import android.webkit.GeolocationPermissions.Callback;
 -import android.widget.EditText;
 -import android.widget.LinearLayout;
 -import android.widget.ProgressBar;
 -import android.widget.RelativeLayout;
 -
 -/**
 - * This class is the WebChromeClient that implements callbacks for our web view.
 - * The kind of callbacks that happen here are on the chrome outside the document,
 - * such as onCreateWindow(), onConsoleMessage(), onProgressChanged(), etc. Related
 - * to but different than CordovaWebViewClient.
 - *
 - * @see <a href="http://developer.android.com/reference/android/webkit/WebChromeClient.html">WebChromeClient</a>
 - * @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
 - * @see CordovaWebViewClient
 - * @see CordovaWebView
 - */
 -public class CordovaChromeClient extends WebChromeClient {
 -
 -    public static final int FILECHOOSER_RESULTCODE = 5173;
 -    private static final String LOG_TAG = "CordovaChromeClient";
 -    private String TAG = "CordovaLog";
 -    private long MAX_QUOTA = 100 * 1024 * 1024;
 -    protected CordovaInterface cordova;
 -    protected CordovaWebView appView;
 -
 -    // the video progress view
 -    private View mVideoProgressView;
 -    
 -    // File Chooser
 -    public ValueCallback<Uri> mUploadMessage;
 -    
 -    /**
 -     * Constructor.
 -     *
 -     * @param cordova
 -     */
 -    public CordovaChromeClient(CordovaInterface cordova) {
 -        this.cordova = cordova;
 -    }
 -
 -    /**
 -     * Constructor.
 -     * 
 -     * @param ctx
 -     * @param app
 -     */
 -    public CordovaChromeClient(CordovaInterface ctx, CordovaWebView app) {
 -        this.cordova = ctx;
 -        this.appView = app;
 -    }
 -
 -    /**
 -     * Constructor.
 -     * 
 -     * @param view
 -     */
 -    public void setWebView(CordovaWebView view) {
 -        this.appView = view;
 -    }
 -
 -    /**
 -     * Tell the client to display a javascript alert dialog.
 -     *
 -     * @param view
 -     * @param url
 -     * @param message
 -     * @param result
 -     * @see Other implementation in the Dialogs plugin.
 -     */
 -    @Override
 -    public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
 -        AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
 -        dlg.setMessage(message);
 -        dlg.setTitle("Alert");
 -        //Don't let alerts break the back button
 -        dlg.setCancelable(true);
 -        dlg.setPositiveButton(android.R.string.ok,
 -                new AlertDialog.OnClickListener() {
 -                    public void onClick(DialogInterface dialog, int which) {
 -                        result.confirm();
 -                    }
 -                });
 -        dlg.setOnCancelListener(
 -                new DialogInterface.OnCancelListener() {
 -                    public void onCancel(DialogInterface dialog) {
 -                        result.cancel();
 -                    }
 -                });
 -        dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
 -            //DO NOTHING
 -            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
 -                if (keyCode == KeyEvent.KEYCODE_BACK)
 -                {
 -                    result.confirm();
 -                    return false;
 -                }
 -                else
 -                    return true;
 -            }
 -        });
 -        dlg.show();
 -        return true;
 -    }
 -
 -    /**
 -     * Tell the client to display a confirm dialog to the user.
 -     *
 -     * @param view
 -     * @param url
 -     * @param message
 -     * @param result
 -     * @see Other implementation in the Dialogs plugin.
 -     */
 -    @Override
 -    public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
 -        AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
 -        dlg.setMessage(message);
 -        dlg.setTitle("Confirm");
 -        dlg.setCancelable(true);
 -        dlg.setPositiveButton(android.R.string.ok,
 -                new DialogInterface.OnClickListener() {
 -                    public void onClick(DialogInterface dialog, int which) {
 -                        result.confirm();
 -                    }
 -                });
 -        dlg.setNegativeButton(android.R.string.cancel,
 -                new DialogInterface.OnClickListener() {
 -                    public void onClick(DialogInterface dialog, int which) {
 -                        result.cancel();
 -                    }
 -                });
 -        dlg.setOnCancelListener(
 -                new DialogInterface.OnCancelListener() {
 -                    public void onCancel(DialogInterface dialog) {
 -                        result.cancel();
 -                    }
 -                });
 -        dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
 -            //DO NOTHING
 -            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
 -                if (keyCode == KeyEvent.KEYCODE_BACK)
 -                {
 -                    result.cancel();
 -                    return false;
 -                }
 -                else
 -                    return true;
 -            }
 -        });
 -        dlg.show();
 -        return true;
 -    }
 -
 -    /**
 -     * Tell the client to display a prompt dialog to the user.
 -     * If the client returns true, WebView will assume that the client will
 -     * handle the prompt dialog and call the appropriate JsPromptResult method.
 -     *
 -     * Since we are hacking prompts for our own purposes, we should not be using them for
 -     * this purpose, perhaps we should hack console.log to do this instead!
 -     *
 -     * @see Other implementation in the Dialogs plugin.
 -     */
 -    @Override
 -    public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, JsPromptResult result) {
 -        // Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
 -        if (defaultValue != null && defaultValue.length() > 3 && defaultValue.startsWith("gap:")) {
 -            JSONArray array;
 -            try {
 -                array = new JSONArray(defaultValue.substring(4));
 -                int bridgeSecret = array.getInt(0);
 -                String service = array.getString(1);
 -                String action = array.getString(2);
 -                String callbackId = array.getString(3);
 -                String r = appView.exposedJsApi.exec(bridgeSecret, service, action, callbackId, message);
 -                result.confirm(r == null ? "" : r);
 -            } catch (JSONException e) {
 -                e.printStackTrace();
 -                result.cancel();
 -            } catch (IllegalAccessException e) {
 -                e.printStackTrace();
 -                result.cancel();
 -            }
 -        }
 -
 -        // Sets the native->JS bridge mode. 
 -        else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {
 -            try {
 -                int bridgeSecret = Integer.parseInt(defaultValue.substring(16));
 -                appView.exposedJsApi.setNativeToJsBridgeMode(bridgeSecret, Integer.parseInt(message));
 -                result.cancel();
 -            } catch (NumberFormatException e){
 -                e.printStackTrace();
 -                result.cancel();
 -            } catch (IllegalAccessException e) {
 -                e.printStackTrace();
 -                result.cancel();
 -            }
 -        }
 -
 -        // Polling for JavaScript messages 
 -        else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {
 -            int bridgeSecret = Integer.parseInt(defaultValue.substring(9));
 -            try {
 -                String r = appView.exposedJsApi.retrieveJsMessages(bridgeSecret, "1".equals(message));
 -                result.confirm(r == null ? "" : r);
 -            } catch (IllegalAccessException e) {
 -                e.printStackTrace();
 -                result.cancel();
 -            }
 -        }
 -
 -        else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
 -            String startUrl = Config.getStartUrl();
 -            // Protect against random iframes being able to talk through the bridge.
 -            // Trust only file URLs and the start URL's domain.
 -            // The extra origin.startsWith("http") is to protect against iframes with data: having "" as origin.
 -            if (origin.startsWith("file:") || (origin.startsWith("http") && startUrl.startsWith(origin))) {
 -                // Enable the bridge
 -                int bridgeMode = Integer.parseInt(defaultValue.substring(9));
 -                appView.jsMessageQueue.setBridgeMode(bridgeMode);
 -                // Tell JS the bridge secret.
 -                int secret = appView.exposedJsApi.generateBridgeSecret();
 -                result.confirm(""+secret);
 -            } else {
 -                Log.e(LOG_TAG, "gap_init called from restricted origin: " + origin);
 -                result.cancel();
 -            }
 -        } else {
 -            // Returning false would also show a dialog, but the default one shows the origin (ugly).
 -            final JsPromptResult res = result;
 -            AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
 -            dlg.setMessage(message);
 -            final EditText input = new EditText(this.cordova.getActivity());
 -            if (defaultValue != null) {
 -                input.setText(defaultValue);
 -            }
 -            dlg.setView(input);
 -            dlg.setCancelable(false);
 -            dlg.setPositiveButton(android.R.string.ok,
 -                    new DialogInterface.OnClickListener() {
 -                        public void onClick(DialogInterface dialog, int which) {
 -                            String usertext = input.getText().toString();
 -                            res.confirm(usertext);
 -                        }
 -                    });
 -            dlg.setNegativeButton(android.R.string.cancel,
 -                    new DialogInterface.OnClickListener() {
 -                        public void onClick(DialogInterface dialog, int which) {
 -                            res.cancel();
 -                        }
 -                    });
 -            dlg.show();
 -        }
 -        return true;
 -    }
 -
 -    /**
 -     * Handle database quota exceeded notification.
 -     */
 -    @Override
 -    public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
 -            long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
 -    {
 -        LOG.d(TAG, "onExceededDatabaseQuota estimatedSize: %d  currentQuota: %d  totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
 -        quotaUpdater.updateQuota(MAX_QUOTA);
 -    }
 -
 -    // console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html
 -    // Expect this to not compile in a future Android release!
 -    @SuppressWarnings("deprecation")
 -    @Override
 -    public void onConsoleMessage(String message, int lineNumber, String sourceID)
 -    {
 -        //This is only for Android 2.1
 -        if(android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.ECLAIR_MR1)
 -        {
 -            LOG.d(TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
 -            super.onConsoleMessage(message, lineNumber, sourceID);
 -        }
 -    }
 -
 -    @TargetApi(8)
 -    @Override
 -    public boolean onConsoleMessage(ConsoleMessage consoleMessage)
 -    {
 -        if (consoleMessage.message() != null)
 -            LOG.d(TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
 -         return super.onConsoleMessage(consoleMessage);
 -    }
 -
 -    @Override
 -    /**
 -     * 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
 -     */
 -    public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
 -        super.onGeolocationPermissionsShowPrompt(origin, callback);
 -        callback.invoke(origin, true, false);
 -    }
 -    
 -    // API level 7 is required for this, see if we could lower this using something else
 -    @Override
 -    public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
 -        this.appView.showCustomView(view, callback);
 -    }
 -
 -    @Override
 -    public void onHideCustomView() {
 -        this.appView.hideCustomView();
 -    }
 -    
 -    @Override
 -    /**
 -     * Ask the host application for a custom progress view to show while
 -     * a <video> is loading.
 -     * @return View The progress view.
 -     */
 -    public View getVideoLoadingProgressView() {
 -
 -        if (mVideoProgressView == null) {            
 -            // Create a new Loading view programmatically.
 -            
 -            // create the linear layout
 -            LinearLayout layout = new LinearLayout(this.appView.getContext());
 -            layout.setOrientation(LinearLayout.VERTICAL);
 -            RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
 -            layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
 -            layout.setLayoutParams(layoutParams);
 -            // the proress bar
 -            ProgressBar bar = new ProgressBar(this.appView.getContext());
 -            LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
 -            barLayoutParams.gravity = Gravity.CENTER;
 -            bar.setLayoutParams(barLayoutParams);   
 -            layout.addView(bar);
 -            
 -            mVideoProgressView = layout;
 -        }
 -    return mVideoProgressView; 
 -    }
 -    
 -    public void openFileChooser(ValueCallback<Uri> uploadMsg) {
 -        this.openFileChooser(uploadMsg, "*/*");
 -    }
  
 -    public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
 -        this.openFileChooser(uploadMsg, acceptType, null);
 -    }
 -    
 -    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)
 -    {
 -        mUploadMessage = uploadMsg;
 -        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
 -        i.addCategory(Intent.CATEGORY_OPENABLE);
 -        i.setType("*/*");
 -        this.cordova.getActivity().startActivityForResult(Intent.createChooser(i, "File Browser"),
 -                FILECHOOSER_RESULTCODE);
 -    }
 -    
 -    public ValueCallback<Uri> getValueCallback() {
 -        return this.mUploadMessage;
 -    }
 +public interface CordovaChromeClient {
- 
-     int FILECHOOSER_RESULTCODE = 0;
- 
 +    void setWebView(CordovaWebView appView);
- 
 +    ValueCallback<Uri> getValueCallback();
- 
  }

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/4ca23056/framework/src/org/apache/cordova/CordovaUriHelper.java
----------------------------------------------------------------------

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/4ca23056/framework/src/org/apache/cordova/ExposedJsApi.java
----------------------------------------------------------------------
diff --cc framework/src/org/apache/cordova/ExposedJsApi.java
index d0f8abf,97f6038..b84fcfb
mode 100644,100755..100644
--- a/framework/src/org/apache/cordova/ExposedJsApi.java
+++ b/framework/src/org/apache/cordova/ExposedJsApi.java
@@@ -1,17 -1,98 +1,12 @@@
 -/*
 -       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.webkit.JavascriptInterface;
 -
 -import org.apache.cordova.PluginManager;
  import org.json.JSONException;
  
- import android.webkit.JavascriptInterface;
- 
 -/**
 - * Contains APIs that the JS can call. All functions in here should also have
 - * an equivalent entry in CordovaChromeClient.java, and be added to
 - * cordova-js/lib/android/plugin/android/promptbasednativeapi.js
 +/*
 + * Any exposed Javascript API MUST implement these three things!
   */
- 
 -/* package */ class ExposedJsApi {
 -    
 -    private PluginManager pluginManager;
 -    private NativeToJsMessageQueue jsMessageQueue;
 -    private volatile int bridgeSecret = -1; // written by UI thread, read by JS thread.
 -    
 -    public ExposedJsApi(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
 -        this.pluginManager = pluginManager;
 -        this.jsMessageQueue = jsMessageQueue;
 -    }
 -
 -    @JavascriptInterface
 -    public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
 -        verifySecret(bridgeSecret);
 -        // If the arguments weren't received, send a message back to JS.  It will switch bridge modes and try again.  See CB-2666.
 -        // We send a message meant specifically for this case.  It starts with "@" so no other message can be encoded into the same string.
 -        if (arguments == null) {
 -            return "@Null arguments.";
 -        }
 -
 -        jsMessageQueue.setPaused(true);
 -        try {
 -            // Tell the resourceApi what thread the JS is running on.
 -            CordovaResourceApi.jsThread = Thread.currentThread();
 -            
 -            pluginManager.exec(service, action, callbackId, arguments);
 -            String ret = "";
 -            if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING) {
 -                ret = jsMessageQueue.popAndEncode(false);
 -            }
 -            return ret;
 -        } catch (Throwable e) {
 -            e.printStackTrace();
 -            return "";
 -        } finally {
 -            jsMessageQueue.setPaused(false);
 -        }
 -    }
 -    
 -    @JavascriptInterface
 -    public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
 -        verifySecret(bridgeSecret);
 -        jsMessageQueue.setBridgeMode(value);
 -    }
 -    
 -    @JavascriptInterface
 -    public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
 -        verifySecret(bridgeSecret);
 -        return jsMessageQueue.popAndEncode(fromOnlineEvent);
 -    }
 -
 -    private void verifySecret(int value) throws IllegalAccessException {
 -        if (bridgeSecret < 0 || value != bridgeSecret) {
 -            throw new IllegalAccessException();
 -        }
 -    }
 -
 -    /** Called on page transitions */
 -    void clearBridgeSecret() {
 -        bridgeSecret = -1;
 -    }
 -
 -    /** Called by cordova.js to initialize the bridge. */
 -    int generateBridgeSecret() {
 -        bridgeSecret = (int)(Math.random() * Integer.MAX_VALUE);
 -        return bridgeSecret;
 -    }
 +public interface ExposedJsApi {
- 
-     @JavascriptInterface
-     public String exec(String service, String action, String callbackId, String arguments) throws JSONException;
-     public void setNativeToJsBridgeMode(int value);
-     public String retrieveJsMessages(boolean fromOnlineEvent);
++    public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException;
++    public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException;
++    public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException;
  }

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/4ca23056/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
----------------------------------------------------------------------
diff --cc framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
index 2501c98,67793d7..3c6ad21
--- a/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
+++ b/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
@@@ -32,9 -32,10 +32,10 @@@ import android.webkit.WebResourceRespon
  import android.webkit.WebView;
  
  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
 -public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
 +public class IceCreamCordovaWebViewClient extends AndroidWebViewClient implements CordovaWebViewClient{
  
      private static final String TAG = "IceCreamCordovaWebViewClient";
+     private CordovaUriHelper helper;
  
      public IceCreamCordovaWebViewClient(CordovaInterface cordova) {
          super(cordova);

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/4ca23056/framework/src/org/apache/cordova/PluginManager.java
----------------------------------------------------------------------
diff --cc framework/src/org/apache/cordova/PluginManager.java
index 50fe6b7,02536ba..c8cecf2
--- a/framework/src/org/apache/cordova/PluginManager.java
+++ b/framework/src/org/apache/cordova/PluginManager.java
@@@ -65,10 -63,6 +62,8 @@@ public class PluginManager 
      // Using <url-filter> is deprecated.
      protected HashMap<String, List<String>> urlMap = new HashMap<String, List<String>>();
  
-     private AtomicInteger numPendingUiExecs;
-     
 +    private Set<String> pluginIdWhitelist;
 +
      /**
       * Constructor.
       *
@@@ -79,12 -73,7 +74,11 @@@
          this.ctx = ctx;
          this.app = app;
          this.firstRun = true;
-         this.numPendingUiExecs = new AtomicInteger(0);
      }
 +    
 +    public void setPluginIdWhitelist(Set<String> pluginIdWhitelist) {
 +        this.pluginIdWhitelist = pluginIdWhitelist;
 +    }
  
      /**
       * Init when loading a new HTML page into webview.


[2/8] android commit: This breaks running the JUnit tests, we'll bring it back soon

Posted by ag...@apache.org.
This breaks running the JUnit tests, we'll bring it back soon


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

Branch: refs/heads/4.0.x
Commit: c47bcb2f54a098f4f428350bc0053fc29ec2efea
Parents: b0b628f
Author: Joe Bowser <bo...@apache.org>
Authored: Tue Jun 24 12:55:56 2014 -0700
Committer: Joe Bowser <bo...@apache.org>
Committed: Tue Jun 24 12:55:56 2014 -0700

----------------------------------------------------------------------
 .../apache/cordova/test/junit/MessageTest.java  | 73 --------------------
 1 file changed, 73 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/c47bcb2f/test/src/org/apache/cordova/test/junit/MessageTest.java
----------------------------------------------------------------------
diff --git a/test/src/org/apache/cordova/test/junit/MessageTest.java b/test/src/org/apache/cordova/test/junit/MessageTest.java
deleted file mode 100644
index 1d23696..0000000
--- a/test/src/org/apache/cordova/test/junit/MessageTest.java
+++ /dev/null
@@ -1,73 +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.test.junit;
-
-import org.apache.cordova.CordovaPlugin;
-import org.apache.cordova.CordovaWebView;
-import org.apache.cordova.ScrollEvent;
-import org.apache.cordova.pluginApi.pluginStub;
-import org.apache.cordova.test.CordovaWebViewTestActivity;
-import org.apache.cordova.test.R;
-
-import com.robotium.solo.By;
-import com.robotium.solo.Solo;
-
-import android.test.ActivityInstrumentationTestCase2;
-import android.view.View;
-
-public class MessageTest extends
-ActivityInstrumentationTestCase2<CordovaWebViewTestActivity> { 
-    private CordovaWebViewTestActivity testActivity;
-    private CordovaWebView testView;
-    private pluginStub testPlugin;
-    private int TIMEOUT = 1000;
-    
-    private Solo solo;
-
-    public MessageTest() {
-        super("org.apache.cordova.test.activities", CordovaWebViewTestActivity.class);
-      }
-
-      protected void setUp() throws Exception {
-        super.setUp();
-        testActivity = this.getActivity();
-        testView = (CordovaWebView) testActivity.findViewById(R.id.cordovaWebView);
-        testPlugin = (pluginStub) testView.pluginManager.getPlugin("PluginStub");
-        solo = new Solo(getInstrumentation(), getActivity());
-      }
-      
-      public void testOnScrollChanged()
-      {
-          solo.waitForWebElement(By.textContent("Cordova Android Tests"));
-          solo.scrollDown();
-          sleep();
-          Object data = testPlugin.data;
-          assertTrue(data.getClass().getSimpleName().equals("ScrollEvent"));
-      }
-
-      
-      
-      private void sleep() {
-          try {
-            Thread.sleep(TIMEOUT);
-          } catch (InterruptedException e) {
-            fail("Unexpected Timeout");
-          }
-        }
-}


[8/8] android commit: Merge branch 'master' into 4.0.x (Bridge fixes)

Posted by ag...@apache.org.
Merge branch 'master' into 4.0.x (Bridge fixes)

Conflicts:
	framework/src/org/apache/cordova/CordovaChromeClient.java
	framework/src/org/apache/cordova/CordovaUriHelper.java
	framework/src/org/apache/cordova/CordovaWebView.java
	framework/src/org/apache/cordova/CordovaWebViewClient.java
	framework/src/org/apache/cordova/ExposedJsApi.java
	framework/src/org/apache/cordova/NativeToJsMessageQueue.java
	framework/src/org/apache/cordova/PluginManager.java


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

Branch: refs/heads/4.0.x
Commit: 4ca2305693918233d150702d82729cb1ab46ebbd
Parents: 428e1bc f577af0
Author: Andrew Grieve <ag...@chromium.org>
Authored: Thu Jul 3 23:02:02 2014 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Thu Jul 3 23:02:02 2014 -0400

----------------------------------------------------------------------
 .../org/apache/cordova/AndroidChromeClient.java | 149 ++++++++++---------
 .../org/apache/cordova/AndroidExposedJsApi.java |  39 +++--
 .../src/org/apache/cordova/AndroidWebView.java  |  28 ++--
 .../apache/cordova/AndroidWebViewClient.java    | 143 +-----------------
 framework/src/org/apache/cordova/Config.java    |  15 +-
 .../src/org/apache/cordova/CordovaActivity.java |   2 +-
 .../org/apache/cordova/CordovaChromeClient.java |   5 -
 .../org/apache/cordova/CordovaUriHelper.java    |  43 +-----
 .../src/org/apache/cordova/ExposedJsApi.java    |  11 +-
 .../cordova/IceCreamCordovaWebViewClient.java   |  11 +-
 .../apache/cordova/NativeToJsMessageQueue.java  |  82 +++++-----
 .../src/org/apache/cordova/PluginManager.java   |  45 ------
 12 files changed, 193 insertions(+), 380 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/4ca23056/framework/src/org/apache/cordova/AndroidChromeClient.java
----------------------------------------------------------------------
diff --cc framework/src/org/apache/cordova/AndroidChromeClient.java
index e0e9dfa,0000000..31d9b68
mode 100755,000000..100755
--- a/framework/src/org/apache/cordova/AndroidChromeClient.java
+++ b/framework/src/org/apache/cordova/AndroidChromeClient.java
@@@ -1,400 -1,0 +1,409 @@@
 +/*
 +       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 org.apache.cordova.CordovaInterface;
 +import org.apache.cordova.LOG;
 +import org.json.JSONArray;
 +import org.json.JSONException;
 +
 +import android.annotation.TargetApi;
 +import android.app.AlertDialog;
 +import android.content.DialogInterface;
 +import android.content.Intent;
 +import android.net.Uri;
 +import android.view.Gravity;
 +import android.view.KeyEvent;
 +import android.view.View;
 +import android.view.ViewGroup.LayoutParams;
 +import android.webkit.ConsoleMessage;
 +import android.webkit.JsPromptResult;
 +import android.webkit.JsResult;
 +import android.webkit.ValueCallback;
 +import android.webkit.WebChromeClient;
 +import android.webkit.WebStorage;
 +import android.webkit.WebView;
 +import android.webkit.GeolocationPermissions.Callback;
 +import android.widget.EditText;
 +import android.widget.LinearLayout;
 +import android.widget.ProgressBar;
 +import android.widget.RelativeLayout;
++import android.util.Log;
++
++
 +
 +/**
 + * This class is the WebChromeClient that implements callbacks for our web view.
 + * The kind of callbacks that happen here are on the chrome outside the document,
 + * such as onCreateWindow(), onConsoleMessage(), onProgressChanged(), etc. Related
 + * to but different than CordovaWebViewClient.
 + *
 + * @see <a href="http://developer.android.com/reference/android/webkit/WebChromeClient.html">WebChromeClient</a>
 + * @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
 + * @see CordovaWebViewClient
 + * @see CordovaWebView
 + */
 +public class AndroidChromeClient extends WebChromeClient implements CordovaChromeClient {
 +
 +    public static final int FILECHOOSER_RESULTCODE = 5173;
 +    private static final String LOG_TAG = "CordovaChromeClient";
-     private String TAG = "CordovaLog";
 +    private long MAX_QUOTA = 100 * 1024 * 1024;
 +    protected CordovaInterface cordova;
 +    protected CordovaWebView appView;
 +
 +    // the video progress view
 +    private View mVideoProgressView;
 +    
 +    // File Chooser
 +    public ValueCallback<Uri> mUploadMessage;
 +    
 +    /**
 +     * Constructor.
 +     *
 +     * @param cordova
 +     */
 +    public AndroidChromeClient(CordovaInterface cordova) {
 +        this.cordova = cordova;
 +    }
 +
 +    /**
 +     * Constructor.
 +     * 
 +     * @param ctx
 +     * @param app
 +     */
 +    public AndroidChromeClient(CordovaInterface ctx, CordovaWebView app) {
 +        this.cordova = ctx;
 +        this.appView = app;
 +    }
 +
 +    /**
 +     * Constructor.
 +     * 
 +     * @param view
 +     */
 +    public void setWebView(CordovaWebView view) {
 +        this.appView = view;
 +    }
 +
 +    /**
 +     * Tell the client to display a javascript alert dialog.
 +     *
 +     * @param view
 +     * @param url
 +     * @param message
 +     * @param result
 +     * @see Other implementation in the Dialogs plugin.
 +     */
 +    @Override
 +    public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
 +        AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
 +        dlg.setMessage(message);
 +        dlg.setTitle("Alert");
 +        //Don't let alerts break the back button
 +        dlg.setCancelable(true);
 +        dlg.setPositiveButton(android.R.string.ok,
 +                new AlertDialog.OnClickListener() {
 +                    public void onClick(DialogInterface dialog, int which) {
 +                        result.confirm();
 +                    }
 +                });
 +        dlg.setOnCancelListener(
 +                new DialogInterface.OnCancelListener() {
 +                    public void onCancel(DialogInterface dialog) {
 +                        result.cancel();
 +                    }
 +                });
 +        dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
 +            //DO NOTHING
 +            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
 +                if (keyCode == KeyEvent.KEYCODE_BACK)
 +                {
 +                    result.confirm();
 +                    return false;
 +                }
 +                else
 +                    return true;
 +            }
 +        });
 +        dlg.show();
 +        return true;
 +    }
 +
 +    /**
 +     * Tell the client to display a confirm dialog to the user.
 +     *
 +     * @param view
 +     * @param url
 +     * @param message
 +     * @param result
 +     * @see Other implementation in the Dialogs plugin.
 +     */
 +    @Override
 +    public boolean onJsConfirm(WebView view, String url, String message, final JsResult result) {
 +        AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
 +        dlg.setMessage(message);
 +        dlg.setTitle("Confirm");
 +        dlg.setCancelable(true);
 +        dlg.setPositiveButton(android.R.string.ok,
 +                new DialogInterface.OnClickListener() {
 +                    public void onClick(DialogInterface dialog, int which) {
 +                        result.confirm();
 +                    }
 +                });
 +        dlg.setNegativeButton(android.R.string.cancel,
 +                new DialogInterface.OnClickListener() {
 +                    public void onClick(DialogInterface dialog, int which) {
 +                        result.cancel();
 +                    }
 +                });
 +        dlg.setOnCancelListener(
 +                new DialogInterface.OnCancelListener() {
 +                    public void onCancel(DialogInterface dialog) {
 +                        result.cancel();
 +                    }
 +                });
 +        dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
 +            //DO NOTHING
 +            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
 +                if (keyCode == KeyEvent.KEYCODE_BACK)
 +                {
 +                    result.cancel();
 +                    return false;
 +                }
 +                else
 +                    return true;
 +            }
 +        });
 +        dlg.show();
 +        return true;
 +    }
 +
 +    /**
 +     * Tell the client to display a prompt dialog to the user.
 +     * If the client returns true, WebView will assume that the client will
 +     * handle the prompt dialog and call the appropriate JsPromptResult method.
 +     *
 +     * Since we are hacking prompts for our own purposes, we should not be using them for
 +     * this purpose, perhaps we should hack console.log to do this instead!
 +     *
-      * @param view
-      * @param url
-      * @param message
-      * @param defaultValue
-      * @param result
 +     * @see Other implementation in the Dialogs plugin.
 +     */
 +    @Override
-     public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
- 
-         // Security check to make sure any requests are coming from the page initially
-         // loaded in webview and not another loaded in an iframe.
-         boolean reqOk = false;
-         if (url.startsWith("file://") || Config.isUrlWhiteListed(url)) {
-             reqOk = true;
-         }
- 
-         // Calling PluginManager.exec() to call a native service using 
-         // prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true]));
-         if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) {
++    public boolean onJsPrompt(WebView view, String origin, String message, String defaultValue, JsPromptResult result) {
++        // Unlike the @JavascriptInterface bridge, this method is always called on the UI thread.
++        if (defaultValue != null && defaultValue.length() > 3 && defaultValue.startsWith("gap:")) {
 +            JSONArray array;
 +            try {
 +                array = new JSONArray(defaultValue.substring(4));
-                 String service = array.getString(0);
-                 String action = array.getString(1);
-                 String callbackId = array.getString(2);
-                 
-                 //String r = this.appView.exposedJsApi.exec(service, action, callbackId, message);
-                 String r = this.appView.exec(service, action, callbackId, message);
++                int bridgeSecret = array.getInt(0);
++                String service = array.getString(1);
++                String action = array.getString(2);
++                String callbackId = array.getString(3);
++                String r = appView.exposedJsApi.exec(bridgeSecret, service, action, callbackId, message);
 +                result.confirm(r == null ? "" : r);
 +            } catch (JSONException e) {
 +                e.printStackTrace();
-                 return false;
++                result.cancel();
++            } catch (IllegalAccessException e) {
++                e.printStackTrace();
++                result.cancel();
 +            }
 +        }
 +
 +        // Sets the native->JS bridge mode. 
-         else if (reqOk && defaultValue != null && defaultValue.equals("gap_bridge_mode:")) {
-         	try {
-                 //this.appView.exposedJsApi.setNativeToJsBridgeMode(Integer.parseInt(message));
-         	    this.appView.setNativeToJsBridgeMode(Integer.parseInt(message));
-                 result.confirm("");
-         	} catch (NumberFormatException e){
-                 result.confirm("");
++        else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {
++            try {
++                int bridgeSecret = Integer.parseInt(defaultValue.substring(16));
++                appView.exposedJsApi.setNativeToJsBridgeMode(bridgeSecret, Integer.parseInt(message));
++                result.cancel();
++            } catch (NumberFormatException e){
++                e.printStackTrace();
++                result.cancel();
++            } catch (IllegalAccessException e) {
 +                e.printStackTrace();
-         	}
++                result.cancel();
++            }
 +        }
 +
 +        // Polling for JavaScript messages 
-         else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) {
-             //String r = this.appView.exposedJsApi.retrieveJsMessages("1".equals(message));
-             String r = this.appView.retrieveJsMessages("1".equals(message));
-             result.confirm(r == null ? "" : r);
-         }
- 
-         // Do NO-OP so older code doesn't display dialog
-         else if (defaultValue != null && defaultValue.equals("gap_init:")) {
-             result.confirm("OK");
++        else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {
++            int bridgeSecret = Integer.parseInt(defaultValue.substring(9));
++            try {
++                String r = appView.exposedJsApi.retrieveJsMessages(bridgeSecret, "1".equals(message));
++                result.confirm(r == null ? "" : r);
++            } catch (IllegalAccessException e) {
++                e.printStackTrace();
++                result.cancel();
++            }
 +        }
 +
-         // Show dialog
-         else {
++        else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
++            String startUrl = Config.getStartUrl();
++            // Protect against random iframes being able to talk through the bridge.
++            // Trust only file URLs and the start URL's domain.
++            // The extra origin.startsWith("http") is to protect against iframes with data: having "" as origin.
++            if (origin.startsWith("file:") || (origin.startsWith("http") && startUrl.startsWith(origin))) {
++                // Enable the bridge
++                int bridgeMode = Integer.parseInt(defaultValue.substring(9));
++                appView.jsMessageQueue.setBridgeMode(bridgeMode);
++                // Tell JS the bridge secret.
++                int secret = appView.exposedJsApi.generateBridgeSecret();
++                result.confirm(""+secret);
++            } else {
++                Log.e(LOG_TAG, "gap_init called from restricted origin: " + origin);
++                result.cancel();
++            }
++        } else {
++            // Returning false would also show a dialog, but the default one shows the origin (ugly).
 +            final JsPromptResult res = result;
 +            AlertDialog.Builder dlg = new AlertDialog.Builder(this.cordova.getActivity());
 +            dlg.setMessage(message);
 +            final EditText input = new EditText(this.cordova.getActivity());
 +            if (defaultValue != null) {
 +                input.setText(defaultValue);
 +            }
 +            dlg.setView(input);
 +            dlg.setCancelable(false);
 +            dlg.setPositiveButton(android.R.string.ok,
 +                    new DialogInterface.OnClickListener() {
 +                        public void onClick(DialogInterface dialog, int which) {
 +                            String usertext = input.getText().toString();
 +                            res.confirm(usertext);
 +                        }
 +                    });
 +            dlg.setNegativeButton(android.R.string.cancel,
 +                    new DialogInterface.OnClickListener() {
 +                        public void onClick(DialogInterface dialog, int which) {
 +                            res.cancel();
 +                        }
 +                    });
 +            dlg.show();
 +        }
 +        return true;
 +    }
 +
 +    /**
 +     * Handle database quota exceeded notification.
 +     */
 +    @Override
 +    public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
 +            long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
 +    {
-         LOG.d(TAG, "onExceededDatabaseQuota estimatedSize: %d  currentQuota: %d  totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
++        LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d  currentQuota: %d  totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
 +        quotaUpdater.updateQuota(MAX_QUOTA);
 +    }
 +
 +    // console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html
 +    // Expect this to not compile in a future Android release!
 +    @SuppressWarnings("deprecation")
 +    @Override
 +    public void onConsoleMessage(String message, int lineNumber, String sourceID)
 +    {
 +        //This is only for Android 2.1
 +        if(android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.ECLAIR_MR1)
 +        {
-             LOG.d(TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
++            LOG.d(LOG_TAG, "%s: Line %d : %s", sourceID, lineNumber, message);
 +            super.onConsoleMessage(message, lineNumber, sourceID);
 +        }
 +    }
 +
 +    @TargetApi(8)
 +    @Override
 +    public boolean onConsoleMessage(ConsoleMessage consoleMessage)
 +    {
 +        if (consoleMessage.message() != null)
-             LOG.d(TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
++            LOG.d(LOG_TAG, "%s: Line %d : %s" , consoleMessage.sourceId() , consoleMessage.lineNumber(), consoleMessage.message());
 +         return super.onConsoleMessage(consoleMessage);
 +    }
 +
 +    @Override
 +    /**
 +     * 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
 +     */
 +    public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
 +        super.onGeolocationPermissionsShowPrompt(origin, callback);
 +        callback.invoke(origin, true, false);
 +    }
 +    
 +    // API level 7 is required for this, see if we could lower this using something else
 +    @Override
 +    public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback) {
 +        this.appView.showCustomView(view, callback);
 +    }
 +
- 	@Override
- 	public void onHideCustomView() {
-     	this.appView.hideCustomView();
- 	}
++    @Override
++    public void onHideCustomView() {
++        this.appView.hideCustomView();
++    }
 +    
 +    @Override
 +    /**
 +     * Ask the host application for a custom progress view to show while
 +     * a <video> is loading.
 +     * @return View The progress view.
 +     */
 +    public View getVideoLoadingProgressView() {
 +
- 	    if (mVideoProgressView == null) {	        
- 	    	// Create a new Loading view programmatically.
- 	    	
- 	    	// create the linear layout
- 	    	LinearLayout layout = new LinearLayout(this.appView.getContext());
- 	        layout.setOrientation(LinearLayout.VERTICAL);
- 	        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
- 	        layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
- 	        layout.setLayoutParams(layoutParams);
- 	        // the proress bar
- 	        ProgressBar bar = new ProgressBar(this.appView.getContext());
- 	        LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
- 	        barLayoutParams.gravity = Gravity.CENTER;
- 	        bar.setLayoutParams(barLayoutParams);   
- 	        layout.addView(bar);
- 	        
- 	        mVideoProgressView = layout;
- 	    }
++        if (mVideoProgressView == null) {            
++            // Create a new Loading view programmatically.
++            
++            // create the linear layout
++            LinearLayout layout = new LinearLayout(this.appView.getContext());
++            layout.setOrientation(LinearLayout.VERTICAL);
++            RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
++            layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
++            layout.setLayoutParams(layoutParams);
++            // the proress bar
++            ProgressBar bar = new ProgressBar(this.appView.getContext());
++            LinearLayout.LayoutParams barLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
++            barLayoutParams.gravity = Gravity.CENTER;
++            bar.setLayoutParams(barLayoutParams);   
++            layout.addView(bar);
++            
++            mVideoProgressView = layout;
++        }
 +    return mVideoProgressView; 
 +    }
 +    
 +    public void openFileChooser(ValueCallback<Uri> uploadMsg) {
 +        this.openFileChooser(uploadMsg, "*/*");
 +    }
- 
++    
 +    public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType ) {
 +        this.openFileChooser(uploadMsg, acceptType, null);
 +    }
 +    
 +    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture)
 +    {
 +        mUploadMessage = uploadMsg;
 +        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
 +        i.addCategory(Intent.CATEGORY_OPENABLE);
 +        i.setType("*/*");
 +        this.cordova.getActivity().startActivityForResult(Intent.createChooser(i, "File Browser"),
 +                FILECHOOSER_RESULTCODE);
 +    }
 +    
 +    public ValueCallback<Uri> getValueCallback() {
 +        return this.mUploadMessage;
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/4ca23056/framework/src/org/apache/cordova/AndroidExposedJsApi.java
----------------------------------------------------------------------
diff --cc framework/src/org/apache/cordova/AndroidExposedJsApi.java
index 74945cc,0000000..5f53774
mode 100755,000000..100755
--- a/framework/src/org/apache/cordova/AndroidExposedJsApi.java
+++ b/framework/src/org/apache/cordova/AndroidExposedJsApi.java
@@@ -1,76 -1,0 +1,97 @@@
 +/*
 +       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.webkit.JavascriptInterface;
 +import org.apache.cordova.PluginManager;
 +import org.json.JSONException;
 +
 +/**
 + * Contains APIs that the JS can call. All functions in here should also have
 + * an equivalent entry in CordovaChromeClient.java, and be added to
 + * cordova-js/lib/android/plugin/android/promptbasednativeapi.js
 + */
- public /* package */ class AndroidExposedJsApi implements ExposedJsApi {
-     
++class AndroidExposedJsApi implements ExposedJsApi {
++
 +    private PluginManager pluginManager;
 +    private NativeToJsMessageQueue jsMessageQueue;
-     
++    private volatile int bridgeSecret = -1; // written by UI thread, read by JS thread.
++
 +    public AndroidExposedJsApi(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
 +        this.pluginManager = pluginManager;
 +        this.jsMessageQueue = jsMessageQueue;
 +    }
 +
 +    @JavascriptInterface
-     public String exec(String service, String action, String callbackId, String arguments) throws JSONException {
++    public String exec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
++        verifySecret(bridgeSecret);
 +        // If the arguments weren't received, send a message back to JS.  It will switch bridge modes and try again.  See CB-2666.
 +        // We send a message meant specifically for this case.  It starts with "@" so no other message can be encoded into the same string.
 +        if (arguments == null) {
 +            return "@Null arguments.";
 +        }
 +
 +        jsMessageQueue.setPaused(true);
 +        try {
 +            // Tell the resourceApi what thread the JS is running on.
 +            CordovaResourceApi.jsThread = Thread.currentThread();
-             
++
 +            pluginManager.exec(service, action, callbackId, arguments);
 +            String ret = "";
 +            if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING) {
 +                ret = jsMessageQueue.popAndEncode(false);
 +            }
 +            return ret;
 +        } catch (Throwable e) {
 +            e.printStackTrace();
 +            return "";
 +        } finally {
 +            jsMessageQueue.setPaused(false);
 +        }
 +    }
-     
++
 +    @JavascriptInterface
-     public void setNativeToJsBridgeMode(int value) {
++    public void setNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
++        verifySecret(bridgeSecret);
 +        jsMessageQueue.setBridgeMode(value);
 +    }
-     
++
 +    @JavascriptInterface
-     public String retrieveJsMessages(boolean fromOnlineEvent) {
++    public String retrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
++        verifySecret(bridgeSecret);
 +        return jsMessageQueue.popAndEncode(fromOnlineEvent);
 +    }
++
++    private void verifySecret(int value) throws IllegalAccessException {
++        if (bridgeSecret < 0 || value != bridgeSecret) {
++            throw new IllegalAccessException();
++        }
++    }
++
++    /** Called on page transitions */
++    void clearBridgeSecret() {
++        bridgeSecret = -1;
++    }
++
++    /** Called by cordova.js to initialize the bridge. */
++    int generateBridgeSecret() {
++        bridgeSecret = (int)(Math.random() * Integer.MAX_VALUE);
++        return bridgeSecret;
++    }
 +}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/4ca23056/framework/src/org/apache/cordova/AndroidWebView.java
----------------------------------------------------------------------
diff --cc framework/src/org/apache/cordova/AndroidWebView.java
index 5fd6495,0000000..a169d30
mode 100755,000000..100755
--- a/framework/src/org/apache/cordova/AndroidWebView.java
+++ b/framework/src/org/apache/cordova/AndroidWebView.java
@@@ -1,1055 -1,0 +1,1045 @@@
 +/*
 +       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.lang.reflect.InvocationTargetException;
 +import java.lang.reflect.Method;
 +import java.util.HashMap;
 +import java.util.HashSet;
 +import java.util.Locale;
 +
 +import org.apache.cordova.Config;
 +import org.apache.cordova.CordovaInterface;
 +import org.apache.cordova.LOG;
 +import org.apache.cordova.PluginManager;
 +import org.apache.cordova.PluginResult;
 +import org.json.JSONException;
 +
 +import android.annotation.SuppressLint;
 +import android.annotation.TargetApi;
 +import android.content.BroadcastReceiver;
 +import android.content.Context;
 +import android.content.Intent;
 +import android.content.IntentFilter;
 +import android.content.pm.ApplicationInfo;
 +import android.content.pm.PackageManager;
 +import android.content.pm.PackageManager.NameNotFoundException;
 +import android.net.Uri;
 +import android.os.Build;
 +import android.os.Bundle;
 +import android.util.AttributeSet;
 +import android.util.Log;
 +import android.view.Gravity;
 +import android.view.KeyEvent;
 +import android.view.View;
 +import android.view.ViewGroup;
 +import android.view.WindowManager;
 +import android.view.inputmethod.InputMethodManager;
 +import android.webkit.WebBackForwardList;
 +import android.webkit.WebHistoryItem;
 +import android.webkit.WebChromeClient;
 +import android.webkit.WebSettings;
 +import android.webkit.WebView;
 +import android.webkit.WebSettings.LayoutAlgorithm;
 +import android.webkit.WebViewClient;
 +import android.widget.FrameLayout;
 +
 +/*
 + * This class is our web view.
 + *
 + * @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
 + * @see <a href="http://developer.android.com/reference/android/webkit/WebView.html">WebView</a>
 + */
 +public class AndroidWebView extends WebView implements CordovaWebView {
 +
 +    public static final String TAG = "AndroidWebView";
 +
 +    private HashSet<Integer> boundKeyCodes = new HashSet<Integer>();
 +
 +    PluginManager pluginManager;
 +    private boolean paused;
 +
 +    private BroadcastReceiver receiver;
 +
 +
 +    /** Activities and other important classes **/
 +    private CordovaInterface cordova;
 +    CordovaWebViewClient viewClient;
 +    @SuppressWarnings("unused")
 +    private CordovaChromeClient chromeClient;
 +
 +    private String url;
 +
 +    // Flag to track that a loadUrl timeout occurred
 +    int loadUrlTimeout = 0;
 +
 +    private long lastMenuEventTime = 0;
 +
 +    NativeToJsMessageQueue jsMessageQueue;
 +    ExposedJsApi exposedJsApi;
 +
 +    /** custom view created by the browser (a video player for example) */
 +    private View mCustomView;
 +    private WebChromeClient.CustomViewCallback mCustomViewCallback;
 +
 +    private ActivityResult mResult = null;
 +
 +    private CordovaResourceApi resourceApi;
 +
 +    class ActivityResult {
 +        
 +        int request;
 +        int result;
 +        Intent incoming;
 +        
 +        public ActivityResult(int req, int res, Intent intent) {
 +            request = req;
 +            result = res;
 +            incoming = intent;
 +        }
 +
 +        
 +    }
 +    
 +    static final FrameLayout.LayoutParams COVER_SCREEN_GRAVITY_CENTER =
 +            new FrameLayout.LayoutParams(
 +            ViewGroup.LayoutParams.MATCH_PARENT,
 +            ViewGroup.LayoutParams.MATCH_PARENT,
 +            Gravity.CENTER);
 +    
 +    
 +    /**
 +     * Constructor.
 +     *
 +     * @param context
 +     */
 +    public AndroidWebView(Context context) {
 +        super(context);
 +        if (CordovaInterface.class.isInstance(context))
 +        {
 +            this.cordova = (CordovaInterface) context;
 +        }
 +        else
 +        {
 +            Log.d(TAG, "Your activity must implement CordovaInterface to work");
 +        }
 +        this.loadConfiguration();
 +        this.setup();
 +    }
 +
 +    /**
 +     * Constructor.
 +     *
 +     * @param context
 +     * @param attrs
 +     */
 +    public AndroidWebView(Context context, AttributeSet attrs) {
 +        super(context, attrs);
 +        if (CordovaInterface.class.isInstance(context))
 +        {
 +            this.cordova = (CordovaInterface) context;
 +        }
 +        else
 +        {
 +            Log.d(TAG, "Your activity must implement CordovaInterface to work");
 +        }
 +        this.loadConfiguration();
 +        this.setup();
 +    }
 +
 +    /**
 +     * Constructor.
 +     *
 +     * @param context
 +     * @param attrs
 +     * @param defStyle
 +     *
 +     */
 +    public AndroidWebView(Context context, AttributeSet attrs, int defStyle) {
 +        super(context, attrs, defStyle);
 +        if (CordovaInterface.class.isInstance(context))
 +        {
 +            this.cordova = (CordovaInterface) context;
 +        }
 +        else
 +        {
 +            Log.d(TAG, "Your activity must implement CordovaInterface to work");
 +        }
 +        this.loadConfiguration();
 +        this.setup();
 +    }
 +
 +    /**
 +     * Constructor.
 +     *
 +     * @param context
 +     * @param attrs
 +     * @param defStyle
 +     * @param privateBrowsing
 +     */
 +    @TargetApi(11)
 +    public AndroidWebView(Context context, AttributeSet attrs, int defStyle, boolean privateBrowsing) {
 +        super(context, attrs, defStyle, privateBrowsing);
 +        if (CordovaInterface.class.isInstance(context))
 +        {
 +            this.cordova = (CordovaInterface) context;
 +        }
 +        else
 +        {
 +            Log.d(TAG, "Your activity must implement CordovaInterface to work");
 +        }
 +        this.loadConfiguration();
 +        this.setup();
 +    }
 +
 +    /**
 +     * Create a default WebViewClient object for this webview. This can be overridden by the
 +     * main application's CordovaActivity subclass.
 +     *
 +     * There are two possible client objects that can be returned:
 +     *   AndroidWebViewClient for android < 3.0
 +     *   IceCreamCordovaWebViewClient for Android >= 3.0 (Supports shouldInterceptRequest)
 +     */
 +    @Override
 +    public CordovaWebViewClient makeWebViewClient() {
 +        if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB)
 +        {
 +            return (CordovaWebViewClient) new AndroidWebViewClient(this.cordova, this);
 +        }
 +        else
 +        {
 +            return (CordovaWebViewClient) new IceCreamCordovaWebViewClient(this.cordova, this);
 +        }
 +    }
 +
 +    /**
 +     * Create a default WebViewClient object for this webview. This can be overridden by the
 +     * main application's CordovaActivity subclass.
 +     */
 +    @Override
 +    public CordovaChromeClient makeWebChromeClient() {
 +        return (CordovaChromeClient) new AndroidChromeClient(this.cordova);
 +    }
 +
 +    /**
 +     * Initialize webview.
 +     */
 +    @SuppressWarnings("deprecation")
 +    @SuppressLint("NewApi")
 +    private void setup() {
 +        this.setInitialScale(0);
 +        this.setVerticalScrollBarEnabled(false);
 +        if (shouldRequestFocusOnInit()) {
 +			this.requestFocusFromTouch();
 +		}
 +		// Enable JavaScript
 +        WebSettings settings = this.getSettings();
 +        settings.setJavaScriptEnabled(true);
 +        settings.setJavaScriptCanOpenWindowsAutomatically(true);
 +        settings.setLayoutAlgorithm(LayoutAlgorithm.NORMAL);
 +        
 +        // Set the nav dump for HTC 2.x devices (disabling for ICS, deprecated entirely for Jellybean 4.2)
 +        try {
 +            Method gingerbread_getMethod =  WebSettings.class.getMethod("setNavDump", new Class[] { boolean.class });
 +            
 +            String manufacturer = android.os.Build.MANUFACTURER;
 +            Log.d(TAG, "CordovaWebView is running on device made by: " + manufacturer);
 +            if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB &&
 +                    android.os.Build.MANUFACTURER.contains("HTC"))
 +            {
 +                gingerbread_getMethod.invoke(settings, true);
 +            }
 +        } catch (NoSuchMethodException e) {
 +            Log.d(TAG, "We are on a modern version of Android, we will deprecate HTC 2.3 devices in 2.8");
 +        } catch (IllegalArgumentException e) {
 +            Log.d(TAG, "Doing the NavDump failed with bad arguments");
 +        } catch (IllegalAccessException e) {
 +            Log.d(TAG, "This should never happen: IllegalAccessException means this isn't Android anymore");
 +        } catch (InvocationTargetException e) {
 +            Log.d(TAG, "This should never happen: InvocationTargetException means this isn't Android anymore.");
 +        }
 +
 +        //We don't save any form data in the application
 +        settings.setSaveFormData(false);
 +        settings.setSavePassword(false);
 +        
 +        // Jellybean rightfully tried to lock this down. Too bad they didn't give us a whitelist
 +        // while we do this
 +        if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
 +            Level16Apis.enableUniversalAccess(settings);
 +        // Enable database
 +        // We keep this disabled because we use or shim to get around DOM_EXCEPTION_ERROR_16
 +        String databasePath = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
 +        settings.setDatabaseEnabled(true);
 +        settings.setDatabasePath(databasePath);
 +        
 +        
 +        //Determine whether we're in debug or release mode, and turn on Debugging!
 +        try {
 +            final String packageName = this.cordova.getActivity().getPackageName();
 +            final PackageManager pm = this.cordova.getActivity().getPackageManager();
 +            ApplicationInfo appInfo;
 +            
 +            appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
 +            
 +            if((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 &&  
 +                android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT)
 +            {
 +                setWebContentsDebuggingEnabled(true);
 +            }
 +        } catch (IllegalArgumentException e) {
 +            Log.d(TAG, "You have one job! To turn on Remote Web Debugging! YOU HAVE FAILED! ");
 +            e.printStackTrace();
 +        } catch (NameNotFoundException e) {
 +            Log.d(TAG, "This should never happen: Your application's package can't be found.");
 +            e.printStackTrace();
 +        }  
 +        
 +        settings.setGeolocationDatabasePath(databasePath);
 +
 +        // Enable DOM storage
 +        settings.setDomStorageEnabled(true);
 +
 +        // Enable built-in geolocation
 +        settings.setGeolocationEnabled(true);
 +        
 +        // Enable AppCache
 +        // Fix for CB-2282
 +        settings.setAppCacheMaxSize(5 * 1048576);
 +        String pathToCache = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
 +        settings.setAppCachePath(pathToCache);
 +        settings.setAppCacheEnabled(true);
 +        
 +        // Fix for CB-1405
 +        // Google issue 4641
 +        this.updateUserAgentString();
 +        
 +        IntentFilter intentFilter = new IntentFilter();
 +        intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
 +        if (this.receiver == null) {
 +            this.receiver = new BroadcastReceiver() {
 +                @Override
 +                public void onReceive(Context context, Intent intent) {
 +                    updateUserAgentString();
 +                }
 +            };
 +            this.cordova.getActivity().registerReceiver(this.receiver, intentFilter);
 +        }
 +        // end CB-1405
 +
 +        pluginManager = new PluginManager(this, this.cordova);
 +        jsMessageQueue = new NativeToJsMessageQueue(this, cordova);
 +        exposedJsApi = new AndroidExposedJsApi(pluginManager, jsMessageQueue);
 +        resourceApi = new CordovaResourceApi(this.getContext(), pluginManager);
 +        exposeJsInterface();
 +    }
 +
 +	/**
 +	 * Override this method to decide whether or not you need to request the
 +	 * focus when your application start
 +	 * 
 +	 * @return true unless this method is overriden to return a different value
 +	 */
 +    protected boolean shouldRequestFocusOnInit() {
 +		return true;
 +	}
 +
 +	private void updateUserAgentString() {
 +        this.getSettings().getUserAgentString();
 +    }
 +
 +    private void exposeJsInterface() {
 +        int SDK_INT = Build.VERSION.SDK_INT;
 +        if ((SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)) {
 +            Log.i(TAG, "Disabled addJavascriptInterface() bridge since Android version is old.");
 +            // Bug being that Java Strings do not get converted to JS strings automatically.
 +            // This isn't hard to work-around on the JS side, but it's easier to just
 +            // use the prompt bridge instead.
 +            return;            
 +        } 
 +        this.addJavascriptInterface(exposedJsApi, "_cordovaNative");
 +    }
 +
 +    /**
 +     * Set the WebViewClient.
 +     *
 +     * @param client
 +     */
 +    public void setWebViewClient(CordovaWebViewClient client) {
 +        this.viewClient = client;
 +        super.setWebViewClient((WebViewClient) client);
 +    }
 +
 +    /**
 +     * Set the WebChromeClient.
 +     *
 +     * @param client
 +     */
 +    public void setWebChromeClient(CordovaChromeClient client) {
 +        this.chromeClient = client;
 +        super.setWebChromeClient((WebChromeClient) client);
 +    }
 +    
 +    public CordovaChromeClient getWebChromeClient() {
 +        return this.chromeClient;
 +    }
 +
 +    /**
 +     * Load the url into the webview.
 +     *
 +     * @param url
 +     */
 +    @Override
 +    public void loadUrl(String url) {
 +        if (url.equals("about:blank") || url.startsWith("javascript:")) {
 +            this.loadUrlNow(url);
 +        }
 +        else {
- 
-             String initUrl = this.getProperty("url", null);
- 
-             // If first page of app, then set URL to load to be the one passed in
-             if (initUrl == null) {
-                 this.loadUrlIntoView(url);
-             }
-             // Otherwise use the URL specified in the activity's extras bundle
-             else {
-                 this.loadUrlIntoView(initUrl);
-             }
++            this.loadUrlIntoView(url);
 +        }
 +    }
 +
 +    /**
 +     * Load the url into the webview after waiting for period of time.
 +     * This is used to display the splashscreen for certain amount of time.
 +     *
 +     * @param url
 +     * @param time              The number of ms to wait before loading webview
 +     */
++    @Deprecated
 +    public void loadUrl(final String url, int time) {
-         String initUrl = this.getProperty("url", null);
- 
-         // If first page of app, then set URL to load to be the one passed in
-         if (initUrl == null) {
-             this.loadUrlIntoView(url, time);
++        if(url == null)
++        {
++            this.loadUrlIntoView(Config.getStartUrl());
 +        }
-         // Otherwise use the URL specified in the activity's extras bundle
-         else {
-             this.loadUrlIntoView(initUrl);
++        else
++        {
++            this.loadUrlIntoView(url);
 +        }
 +    }
 +
 +    public void loadUrlIntoView(final String url) {
 +        loadUrlIntoView(url, true);
 +    }
 +
 +    /**
 +     * Load the url into the webview.
 +     *
 +     * @param url
 +     */
 +    public void loadUrlIntoView(final String url, boolean recreatePlugins) {
 +        LOG.d(TAG, ">>> loadUrl(" + url + ")");
 +
 +        if (recreatePlugins) {
 +            this.url = url;
 +            this.pluginManager.init();
 +        }
 +
 +        // Create a timeout timer for loadUrl
 +        final AndroidWebView me = this;
 +        final int currentLoadUrlTimeout = me.loadUrlTimeout;
 +        final int loadUrlTimeoutValue = Integer.parseInt(this.getProperty("LoadUrlTimeoutValue", "20000"));
 +
 +        // Timeout error method
 +        final Runnable loadError = new Runnable() {
 +            public void run() {
 +                me.stopLoading();
 +                LOG.e(TAG, "CordovaWebView: TIMEOUT ERROR!");
 +                if (viewClient != null) {
 +                    viewClient.onReceivedError(-6, "The connection to the server was unsuccessful.", url);
 +                }
 +            }
 +        };
 +
 +        // Timeout timer method
 +        final Runnable timeoutCheck = new Runnable() {
 +            public void run() {
 +                try {
 +                    synchronized (this) {
 +                        wait(loadUrlTimeoutValue);
 +                    }
 +                } catch (InterruptedException e) {
 +                    e.printStackTrace();
 +                }
 +
 +                // If timeout, then stop loading and handle error
 +                if (me.loadUrlTimeout == currentLoadUrlTimeout) {
 +                    me.cordova.getActivity().runOnUiThread(loadError);
 +                }
 +            }
 +        };
 +
 +        // Load url
 +        this.cordova.getActivity().runOnUiThread(new Runnable() {
 +            public void run() {
 +                cordova.getThreadPool().execute(timeoutCheck);
 +                me.loadUrlNow(url);
 +            }
 +        });
 +    }
 +
 +    /**
 +     * Load URL in webview.
 +     *
 +     * @param url
 +     */
 +    public void loadUrlNow(String url) {
 +        if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) {
 +            LOG.d(TAG, ">>> loadUrlNow()");
 +        }
 +        if (url.startsWith("file://") || url.startsWith("javascript:") || Config.isUrlWhiteListed(url)) {
 +            super.loadUrl(url);
 +        }
 +    }
 +
 +    /**
 +     * Load the url into the webview after waiting for period of time.
 +     * This is used to display the splashscreen for certain amount of time.
 +     *
 +     * @param url
 +     * @param time              The number of ms to wait before loading webview
 +     */
 +    public void loadUrlIntoView(final String url, final int time) {
 +
 +        // If not first page of app, then load immediately
 +        // Add support for browser history if we use it.
 +        if ((url.startsWith("javascript:")) || this.canGoBack()) {
 +        }
 +
 +        // If first page, then show splashscreen
 +        else {
 +
 +            LOG.d(TAG, "loadUrlIntoView(%s, %d)", url, time);
 +
 +            // Send message to show splashscreen now if desired
 +            this.postMessage("splashscreen", "show");
 +        }
 +
 +        // Load url
 +        this.loadUrlIntoView(url);
 +    }
 +    
 +    @Override
 +    public void stopLoading() {
 +        //viewClient.isCurrentlyLoading = false;
 +        super.stopLoading();
 +    }
 +    
 +    public void onScrollChanged(int l, int t, int oldl, int oldt)
 +    {
 +        super.onScrollChanged(l, t, oldl, oldt);
 +        //We should post a message that the scroll changed
 +        ScrollEvent myEvent = new ScrollEvent(l, t, oldl, oldt, this);
 +        this.postMessage("onScrollChanged", myEvent);
 +    }
 +    
 +    /**
 +     * Send JavaScript statement back to JavaScript.
 +     * (This is a convenience method)
 +     *
 +     * @param statement
 +     */
 +    public void sendJavascript(String statement) {
 +        this.jsMessageQueue.addJavaScript(statement);
 +    }
 +
 +    /**
 +     * Send a plugin result back to JavaScript.
 +     * (This is a convenience method)
 +     *
 +     * @param result
 +     * @param callbackId
 +     */
 +    public void sendPluginResult(PluginResult result, String callbackId) {
 +        this.jsMessageQueue.addPluginResult(result, callbackId);
 +    }
 +
 +    /**
 +     * Send a message to all plugins.
 +     *
 +     * @param id            The message id
 +     * @param data          The message data
 +     */
 +    public void postMessage(String id, Object data) {
 +        if (this.pluginManager != null) {
 +            this.pluginManager.postMessage(id, data);
 +        }
 +    }
 +
 +
 +    /**
 +     * Go to previous page in history.  (We manage our own history)
 +     *
 +     * @return true if we went back, false if we are already at top
 +     */
 +    public boolean backHistory() {
 +
 +        // Check webview first to see if there is a history
 +        // This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior)
 +        if (super.canGoBack()) {
 +            printBackForwardList();
 +            super.goBack();
 +            
 +            return true;
 +        }
 +        return false;
 +    }
 +
 +
 +    /**
 +     * Load the specified URL in the Cordova webview or a new browser instance.
 +     *
 +     * NOTE: If openExternal is false, only URLs listed in whitelist can be loaded.
 +     *
 +     * @param url           The url to load.
 +     * @param openExternal  Load url in browser instead of Cordova webview.
 +     * @param clearHistory  Clear the history stack, so new page becomes top of history
 +     * @param params        Parameters for new app
 +     */
 +    public void showWebPage(String url, boolean openExternal, boolean clearHistory, HashMap<String, Object> params) {
 +        LOG.d(TAG, "showWebPage(%s, %b, %b, HashMap", url, openExternal, clearHistory);
 +
 +        // If clearing history
 +        if (clearHistory) {
 +            this.clearHistory();
 +        }
 +
 +        // If loading into our webview
 +        if (!openExternal) {
 +
 +            // Make sure url is in whitelist
 +            if (url.startsWith("file://") || Config.isUrlWhiteListed(url)) {
 +                // TODO: What about params?
 +                // Load new URL
 +                this.loadUrl(url);
 +                return;
 +            }
 +            // Load in default viewer if not
 +            LOG.w(TAG, "showWebPage: Cannot load URL into webview since it is not in white list.  Loading into browser instead. (URL=" + url + ")");
 +        }
 +        try {
 +            // Omitting the MIME type for file: URLs causes "No Activity found to handle Intent".
 +            // Adding the MIME type to http: URLs causes them to not be handled by the downloader.
 +            Intent intent = new Intent(Intent.ACTION_VIEW);
 +            Uri uri = Uri.parse(url);
 +            if ("file".equals(uri.getScheme())) {
 +                intent.setDataAndType(uri, resourceApi.getMimeType(uri));
 +            } else {
 +                intent.setData(uri);
 +            }
 +            cordova.getActivity().startActivity(intent);
 +        } catch (android.content.ActivityNotFoundException e) {
 +            LOG.e(TAG, "Error loading url " + url, e);
 +        }
 +    }
 +
 +    /**
 +     * Check configuration parameters from Config.
 +     * Approved list of URLs that can be loaded into Cordova
 +     *      <access origin="http://server regexp" subdomains="true" />
 +     * Log level: ERROR, WARN, INFO, DEBUG, VERBOSE (default=ERROR)
 +     *      <log level="DEBUG" />
 +     */
 +    private void loadConfiguration() {
 + 
 +        if ("true".equals(this.getProperty("Fullscreen", "false"))) {
 +            this.cordova.getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
 +            this.cordova.getActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
 +        }
 +    }
 +
 +    /**
 +     * Get string property for activity.
 +     *
 +     * @param name
 +     * @param defaultValue
 +     * @return the String value for the named property
 +     */
 +    public String getProperty(String name, String defaultValue) {
 +        Bundle bundle = this.cordova.getActivity().getIntent().getExtras();
 +        if (bundle == null) {
 +            return defaultValue;
 +        }
 +        name = name.toLowerCase(Locale.getDefault());
 +        Object p = bundle.get(name);
 +        if (p == null) {
 +            return defaultValue;
 +        }
 +        return p.toString();
 +    }
 +
 +    /*
 +     * onKeyDown
 +     */
 +    @Override
 +    public boolean onKeyDown(int keyCode, KeyEvent event)
 +    {
 +        if(boundKeyCodes.contains(keyCode))
 +        {
 +            if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
 +                    this.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');");
 +                    return true;
 +            }
 +            else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
 +                    this.loadUrl("javascript:cordova.fireDocumentEvent('volumeupbutton');");
 +                    return true;
 +            }
 +            else
 +            {
 +                return super.onKeyDown(keyCode, event);
 +            }
 +        }
 +        else if(keyCode == KeyEvent.KEYCODE_BACK)
 +        {
 +            return !(this.startOfHistory()) || isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK);
 +
 +        }
 +        else if(keyCode == KeyEvent.KEYCODE_MENU)
 +        {
 +            //How did we get here?  Is there a childView?
 +            View childView = this.getFocusedChild();
 +            if(childView != null)
 +            {
 +                //Make sure we close the keyboard if it's present
 +                InputMethodManager imm = (InputMethodManager) cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
 +                imm.hideSoftInputFromWindow(childView.getWindowToken(), 0);
 +                cordova.getActivity().openOptionsMenu();
 +                return true;
 +            } else {
 +                return super.onKeyDown(keyCode, event);
 +            }
 +        }
 +        return super.onKeyDown(keyCode, event);
 +    }
 +
 +    @Override
 +    public boolean onKeyUp(int keyCode, KeyEvent event)
 +    {
 +        // If back key
 +        if (keyCode == KeyEvent.KEYCODE_BACK) {
 +            // A custom view is currently displayed  (e.g. playing a video)
 +            if(mCustomView != null) {
 +                this.hideCustomView();
 +                return true;
 +            } else {
 +                // The webview is currently displayed
 +                // If back key is bound, then send event to JavaScript
 +                if (isButtonPlumbedToJs(KeyEvent.KEYCODE_BACK)) {
 +                    this.loadUrl("javascript:cordova.fireDocumentEvent('backbutton');");
 +                    return true;
 +                } else {
 +                    // If not bound
 +                    // Go to previous page in webview if it is possible to go back
 +                    if (this.backHistory()) {
 +                        return true;
 +                    }
 +                    // If not, then invoke default behavior
 +                }
 +            }
 +        }
 +        // Legacy
 +        else if (keyCode == KeyEvent.KEYCODE_MENU) {
 +            if (this.lastMenuEventTime < event.getEventTime()) {
 +                this.loadUrl("javascript:cordova.fireDocumentEvent('menubutton');");
 +            }
 +            this.lastMenuEventTime = event.getEventTime();
 +            return super.onKeyUp(keyCode, event);
 +        }
 +        // If search key
 +        else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
 +            this.loadUrl("javascript:cordova.fireDocumentEvent('searchbutton');");
 +            return true;
 +        }
 +
 +        //Does webkit change this behavior?
 +        return super.onKeyUp(keyCode, event);
 +    }
 +
 +    @Override
 +    public void setButtonPlumbedToJs(int keyCode, boolean value) {
 +        switch (keyCode) {
 +            case KeyEvent.KEYCODE_VOLUME_DOWN:
 +            case KeyEvent.KEYCODE_VOLUME_UP:
 +            case KeyEvent.KEYCODE_BACK:
 +                // TODO: Why are search and menu buttons handled separately?
 +                boundKeyCodes.add(keyCode);
 +                return;
 +            default:
 +                throw new IllegalArgumentException("Unsupported keycode: " + keyCode);
 +        }
 +    }
 +
 +    @Override
 +    public boolean isButtonPlumbedToJs(int keyCode)
 +    {
 +        return boundKeyCodes.contains(keyCode);
 +    }
 +
 +    public void handlePause(boolean keepRunning)
 +    {
 +        LOG.d(TAG, "Handle the pause");
 +        // Send pause event to JavaScript
 +        this.loadUrl("javascript:try{cordova.fireDocumentEvent('pause');}catch(e){console.log('exception firing pause event from native');};");
 +
 +        // Forward to plugins
 +        if (this.pluginManager != null) {
 +            this.pluginManager.onPause(keepRunning);
 +        }
 +
 +        // If app doesn't want to run in background
 +        if (!keepRunning) {
 +            // Pause JavaScript timers (including setInterval)
 +            this.pauseTimers();
 +        }
 +        paused = true;
 +   
 +    }
 +    
 +    public void handleResume(boolean keepRunning, boolean activityResultKeepRunning)
 +    {
 +
 +        this.loadUrl("javascript:try{cordova.fireDocumentEvent('resume');}catch(e){console.log('exception firing resume event from native');};");
 +        
 +        // Forward to plugins
 +        if (this.pluginManager != null) {
 +            this.pluginManager.onResume(keepRunning);
 +        }
 +
 +        // Resume JavaScript timers (including setInterval)
 +        this.resumeTimers();
 +        paused = false;
 +    }
 +    
 +    public void handleDestroy()
 +    {
 +        // Send destroy event to JavaScript
 +        this.loadUrl("javascript:try{cordova.require('cordova/channel').onDestroy.fire();}catch(e){console.log('exception firing destroy event from native');};");
 +
 +        // Load blank page so that JavaScript onunload is called
 +        this.loadUrl("about:blank");
 +
 +        // Forward to plugins
 +        if (this.pluginManager != null) {
 +            this.pluginManager.onDestroy();
 +        }
 +        
 +        // unregister the receiver
 +        if (this.receiver != null) {
 +            try {
 +                this.cordova.getActivity().unregisterReceiver(this.receiver);
 +            } catch (Exception e) {
 +                Log.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e);
 +            }
 +        }
 +    }
 +    
 +    public void onNewIntent(Intent intent)
 +    {
 +        //Forward to plugins
 +        if (this.pluginManager != null) {
 +            this.pluginManager.onNewIntent(intent);
 +        }
 +    }
 +    
 +    public boolean isPaused()
 +    {
 +        return paused;
 +    }
 +
 +    // Wrapping these functions in their own class prevents warnings in adb like:
 +    // VFY: unable to resolve virtual method 285: Landroid/webkit/WebSettings;.setAllowUniversalAccessFromFileURLs
 +    @TargetApi(16)
 +    private static class Level16Apis {
 +        static void enableUniversalAccess(WebSettings settings) {
 +            settings.setAllowUniversalAccessFromFileURLs(true);
 +        }
 +    }
 +    
 +    public void printBackForwardList() {
 +        WebBackForwardList currentList = this.copyBackForwardList();
 +        int currentSize = currentList.getSize();
 +        for(int i = 0; i < currentSize; ++i)
 +        {
 +            WebHistoryItem item = currentList.getItemAtIndex(i);
 +            String url = item.getUrl();
 +            LOG.d(TAG, "The URL at index: " + Integer.toString(i) + " is " + url );
 +        }
 +    }
 +    
 +    
 +    //Can Go Back is BROKEN!
 +    public boolean startOfHistory()
 +    {
 +        WebBackForwardList currentList = this.copyBackForwardList();
 +        WebHistoryItem item = currentList.getItemAtIndex(0);
 +        if( item!=null){	// Null-fence in case they haven't called loadUrl yet (CB-2458)
 +	        String url = item.getUrl();
 +	        String currentUrl = this.getUrl();
 +	        LOG.d(TAG, "The current URL is: " + currentUrl);
 +	        LOG.d(TAG, "The URL at item 0 is: " + url);
 +	        return currentUrl.equals(url);
 +        }
 +        return false;
 +    }
 +
 +    public void showCustomView(View view, WebChromeClient.CustomViewCallback callback) {
 +        // This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
 +        Log.d(TAG, "showing Custom View");
 +        // if a view already exists then immediately terminate the new one
 +        if (mCustomView != null) {
 +            callback.onCustomViewHidden();
 +            return;
 +        }
 +        
 +        // Store the view and its callback for later (to kill it properly)
 +        mCustomView = view;
 +        mCustomViewCallback = callback;
 +        
 +        // Add the custom view to its container.
 +        ViewGroup parent = (ViewGroup) this.getParent();
 +        parent.addView(view, COVER_SCREEN_GRAVITY_CENTER);
 +        
 +        // Hide the content view.
 +        this.setVisibility(View.GONE);
 +        
 +        // Finally show the custom view container.
 +        parent.setVisibility(View.VISIBLE);
 +        parent.bringToFront();
 +    }
 +
 +    public void hideCustomView() {
 +        // This code is adapted from the original Android Browser code, licensed under the Apache License, Version 2.0
 +        Log.d(TAG, "Hiding Custom View");
 +        if (mCustomView == null) return;
 +
 +        // Hide the custom view.
 +        mCustomView.setVisibility(View.GONE);
 +        
 +        // Remove the custom view from its container.
 +        ViewGroup parent = (ViewGroup) this.getParent();
 +        parent.removeView(mCustomView);
 +        mCustomView = null;
 +        mCustomViewCallback.onCustomViewHidden();
 +        
 +        // Show the content view.
 +        this.setVisibility(View.VISIBLE);
 +    }
 +    
 +    /**
 +     * if the video overlay is showing then we need to know 
 +     * as it effects back button handling
 +     * 
 +     * @return true if custom view is showing
 +     */
 +    public boolean isCustomViewShowing() {
 +        return mCustomView != null;
 +    }
 +    
 +    public WebBackForwardList restoreState(Bundle savedInstanceState)
 +    {
 +        WebBackForwardList myList = super.restoreState(savedInstanceState);
 +        Log.d(TAG, "WebView restoration crew now restoring!");
 +        //Initialize the plugin manager once more
 +        this.pluginManager.init();
 +        return myList;
 +    }
 +
 +    public void storeResult(int requestCode, int resultCode, Intent intent) {
 +        mResult = new ActivityResult(requestCode, resultCode, intent);
 +    }
 +    
 +    public CordovaResourceApi getResourceApi() {
 +        return resourceApi;
 +    }
 +
 +    @Override
 +    public void setLayoutParams(
 +            android.widget.LinearLayout.LayoutParams layoutParams) {
 +        super.setLayoutParams(layoutParams);
 +    }
 +
 +    @Override
 +    public void setOverScrollMode(int mode) {
 +        super.setOverScrollMode(mode);
 +    }
 +
 +    @Override
 +    public void addJavascript(String statement) {
 +        this.jsMessageQueue.addJavaScript(statement);
 +    }
 +
 +    @Override
 +    public CordovaPlugin getPlugin(String initCallbackClass) {
 +        // TODO Auto-generated method stub
 +        return this.pluginManager.getPlugin(initCallbackClass);
 +    }
 +
 +    @Override
 +    public String exec(String service, String action, String callbackId,
 +            String message) throws JSONException {
 +        return this.exposedJsApi.exec(service, action, callbackId, message);
 +    }
 +
 +    @Override
 +    public void setNativeToJsBridgeMode(int parseInt) {
 +        this.exposedJsApi.setNativeToJsBridgeMode(parseInt);
 +    }
 +
 +    @Override
 +    public String retrieveJsMessages(boolean equals) {
 +        return this.exposedJsApi.retrieveJsMessages(equals);
 +    }
 +
 +    @Override
 +    public boolean onOverrideUrlLoading(String url) {
 +        return this.pluginManager.onOverrideUrlLoading(url);
 +    }
 +
 +    void onPageReset() {
 +        boundKeyCodes.clear();
 +        pluginManager.onReset();
 +        jsMessageQueue.reset();
++        exposedJsApi.clearBridgeSecret();
 +    }
 +
 +    @Override
 +    public void incUrlTimeout() {
 +        this.loadUrlTimeout++;
 +    }
 +
 +    @Override
 +    public PluginManager getPluginManager() {
 +        // TODO Auto-generated method stub
 +        return this.pluginManager;
 +    }
 +
 +    @Override
 +    public void setLayoutParams(
 +            android.widget.FrameLayout.LayoutParams layoutParams) {
 +        super.setLayoutParams(layoutParams);
 +    }
 +
 +    @Override
 +    public View getView() {
 +        return this;
 +    }
 +
 +
 +}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/4ca23056/framework/src/org/apache/cordova/AndroidWebViewClient.java
----------------------------------------------------------------------
diff --cc framework/src/org/apache/cordova/AndroidWebViewClient.java
index b57e0b6,0000000..f3b295b
mode 100755,000000..100755
--- a/framework/src/org/apache/cordova/AndroidWebViewClient.java
+++ b/framework/src/org/apache/cordova/AndroidWebViewClient.java
@@@ -1,493 -1,0 +1,358 @@@
 +/*
 +       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.util.Hashtable;
 +
- import org.apache.cordova.CordovaInterface;
- import org.apache.cordova.LOG;
 +import org.json.JSONException;
 +import org.json.JSONObject;
 +
 +import android.annotation.TargetApi;
 +import android.content.Intent;
 +import android.content.pm.ApplicationInfo;
 +import android.content.pm.PackageManager;
 +import android.content.pm.PackageManager.NameNotFoundException;
 +import android.graphics.Bitmap;
- import android.net.Uri;
 +import android.net.http.SslError;
 +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;
 +
++
 +/**
 + * This class is the WebViewClient that implements callbacks for our web view.
 + * The kind of callbacks that happen here are regarding the rendering of the
 + * document instead of the chrome surrounding it, such as onPageStarted(), 
 + * shouldOverrideUrlLoading(), etc. Related to but different than
 + * CordovaChromeClient.
 + *
 + * @see <a href="http://developer.android.com/reference/android/webkit/WebViewClient.html">WebViewClient</a>
 + * @see <a href="http://developer.android.com/guide/webapps/webview.html">WebView guide</a>
 + * @see CordovaChromeClient
 + * @see CordovaWebView
 + */
 +public class AndroidWebViewClient extends WebViewClient implements CordovaWebViewClient{
 +
- 	private static final String TAG = "AndroidWebViewClient";
- 	private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
++    private static final String TAG = "AndroidWebViewClient";
++    private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
 +    CordovaInterface cordova;
 +    AndroidWebView appView;
 +    CordovaUriHelper helper;
 +    private boolean doClearHistory = false;
 +    boolean isCurrentlyLoading;
 +
 +    /** The authorization tokens. */
 +    private Hashtable<String, AuthenticationToken> authenticationTokens = new Hashtable<String, AuthenticationToken>();
 +
 +    /**
 +     * Constructor.
 +     *
 +     * @param cordova
 +     */
 +    public AndroidWebViewClient(CordovaInterface cordova) {
 +        this.cordova = cordova;
 +    }
 +
 +    /**
 +     * Constructor.
 +     *
 +     * @param cordova
 +     * @param view
 +     */
 +    public AndroidWebViewClient(CordovaInterface cordova, AndroidWebView view) {
 +        this.cordova = cordova;
 +        this.appView = view;
 +        helper = new CordovaUriHelper(cordova, view);
 +    }
 +
 +    /**
 +     * Constructor.
 +     *
 +     * @param view
 +     */
 +    public void setWebView(AndroidWebView view) {
 +        this.appView = view;
 +        helper = new CordovaUriHelper(cordova, view);
 +    }
 +
- 
-     // Parses commands sent by setting the webView's URL to:
-     // cdvbrg:service/action/callbackId#jsonArgs
- 	private void handleExecUrl(String url) {
- 		int idx1 = CORDOVA_EXEC_URL_PREFIX.length();
- 		int idx2 = url.indexOf('#', idx1 + 1);
- 		int idx3 = url.indexOf('#', idx2 + 1);
- 		int idx4 = url.indexOf('#', idx3 + 1);
- 		if (idx1 == -1 || idx2 == -1 || idx3 == -1 || idx4 == -1) {
- 			Log.e(TAG, "Could not decode URL command: " + url);
- 			return;
- 		}
- 		String service    = url.substring(idx1, idx2);
- 		String action     = url.substring(idx2 + 1, idx3);
- 		String callbackId = url.substring(idx3 + 1, idx4);
- 		String jsonArgs   = url.substring(idx4 + 1);
-         try {
-             appView.exec(service, action, callbackId, jsonArgs);
-         } catch (JSONException e) {
-             // TODO Auto-generated catch block
-             e.printStackTrace();
-         }
- 	}
- 
 +    /**
 +     * Give the host application a chance to take over the control when a new url
 +     * is about to be loaded in the current WebView.
 +     *
 +     * @param view          The WebView that is initiating the callback.
 +     * @param url           The url to be loaded.
 +     * @return              true to override, false for default behavior
 +     */
 +	@Override
 +    public boolean shouldOverrideUrlLoading(WebView view, String url) {
-     	// Check if it's an exec() bridge command message.
- 	    /*
-     	if (NativeToJsMessageQueue.ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) {
-     		handleExecUrl(url);
-     	}
- 
-         // Give plugins the chance to handle the url
-     	else if (this.appView.onOverrideUrlLoading(url)) {
-         }
- 
-         // 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));
-                 this.cordova.getActivity().startActivity(intent);
-             } catch (android.content.ActivityNotFoundException e) {
-                 LOG.e(TAG, "Error dialing " + url + ": " + e.toString());
-             }
-         }
- 
-         // If displaying map (geo:0,0?q=address)
-         else if (url.startsWith("geo:")) {
-             try {
-                 Intent intent = new Intent(Intent.ACTION_VIEW);
-                 intent.setData(Uri.parse(url));
-                 this.cordova.getActivity().startActivity(intent);
-             } catch (android.content.ActivityNotFoundException e) {
-                 LOG.e(TAG, "Error showing map " + url + ": " + e.toString());
-             }
-         }
- 
-         // If sending email (mailto:abc@corp.com)
-         else if (url.startsWith(WebView.SCHEME_MAILTO)) {
-             try {
-                 Intent intent = new Intent(Intent.ACTION_VIEW);
-                 intent.setData(Uri.parse(url));
-                 this.cordova.getActivity().startActivity(intent);
-             } catch (android.content.ActivityNotFoundException e) {
-                 LOG.e(TAG, "Error sending email " + 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");
-                 this.cordova.getActivity().startActivity(intent);
-             } catch (android.content.ActivityNotFoundException e) {
-                 LOG.e(TAG, "Error sending sms " + url + ":" + e.toString());
-             }
-         }
-         
-         //Android Market
-         else if(url.startsWith("market:")) {
-             try {
-                 Intent intent = new Intent(Intent.ACTION_VIEW);
-                 intent.setData(Uri.parse(url));
-                 this.cordova.getActivity().startActivity(intent);
-             } catch (android.content.ActivityNotFoundException e) {
-                 LOG.e(TAG, "Error loading Google Play Store: " + url, e);
-             }
-         }
- 
-         // All else
-         else {
- 
-             // 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:")  || Config.isUrlWhiteListed(url)) {
-                 return false;
-             }
- 
-             // If not our application, let default viewer handle
-             else {
-                 try {
-                     Intent intent = new Intent(Intent.ACTION_VIEW);
-                     intent.setData(Uri.parse(url));
-                     this.cordova.getActivity().startActivity(intent);
-                 } catch (android.content.ActivityNotFoundException e) {
-                     LOG.e(TAG, "Error loading url " + url, e);
-                 }
-             }
-         }
-         return true;
-         */
- 	    return helper.shouldOverrideUrlLoading(view, url);
++        return helper.shouldOverrideUrlLoading(view, url);
 +    }
 +    
 +    /**
 +     * 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
 +     *
 +     * @param view
 +     * @param handler
 +     * @param host
 +     * @param realm
 +     */
 +    @Override
 +    public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) {
 +
 +        // Get the authentication token
 +        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);
 +        }
 +    }
 +
 +    /**
 +     * 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) {
 +        super.onPageStarted(view, url, favicon);
 +        isCurrentlyLoading = true;
 +        LOG.d(TAG, "onPageStarted(" + url + ")");
 +        // Flush stale messages & reset plugins.
 +        this.appView.onPageReset();
 +
 +        // Broadcast message that page has loaded
 +        this.appView.postMessage("onPageStarted", url);
 +    }
 +
 +    /**
 +     * 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.
 +     */
 +    @Override
 +    public void onPageFinished(WebView view, String url) {
 +        super.onPageFinished(view, url);
 +        // Ignore excessive calls.
 +        if (!isCurrentlyLoading) {
 +            return;
 +        }
 +        isCurrentlyLoading = false;
 +        LOG.d(TAG, "onPageFinished(" + url + ")");
 +
 +        /**
 +         * Because of a timing issue we need to clear this history in onPageFinished as well as
 +         * onPageStarted. However we only want to do this if the doClearHistory boolean is set to
 +         * true. You see when you load a url with a # in it which is common in jQuery applications
 +         * onPageStared is not called. Clearing the history at that point would break jQuery apps.
 +         */
 +        if (this.doClearHistory) {
 +            view.clearHistory();
 +            this.doClearHistory = false;
 +        }
 +
 +        // Clear timeout flag
 +        this.appView.incUrlTimeout();
 +
 +        // Broadcast message that page has loaded
 +        this.appView.postMessage("onPageFinished", url);
 +
 +        // Make app visible after 2 sec in case there was a JS error and Cordova JS never initialized correctly
 +        if (this.appView.getVisibility() == View.INVISIBLE) {
 +            Thread t = new Thread(new Runnable() {
 +                public void run() {
 +                    try {
 +                        Thread.sleep(2000);
 +                        cordova.getActivity().runOnUiThread(new Runnable() {
 +                            public void run() {
 +                                appView.postMessage("spinner", "stop");
 +                            }
 +                        });
 +                    } catch (InterruptedException e) {
 +                    }
 +                }
 +            });
 +            t.start();
 +        }
 +
 +        // Shutdown if blank loaded
 +        if (url.equals("about:blank")) {
 +            appView.postMessage("exit", null);
 +        }
 +    }
 +
 +    /**
 +     * Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable).
 +     * The errorCode parameter corresponds to one of the ERROR_* constants.
 +     *
 +     * @param view          The WebView that is initiating the callback.
 +     * @param errorCode     The error code corresponding to an ERROR_* value.
 +     * @param description   A String describing the error.
 +     * @param failingUrl    The url that failed to load.
 +     */
 +    @Override
 +    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
 +        // Ignore error due to stopLoading().
 +        if (!isCurrentlyLoading) {
 +            return;
 +        }
 +        LOG.d(TAG, "CordovaWebViewClient.onReceivedError: Error code=%s Description=%s URL=%s", errorCode, description, failingUrl);
 +
 +        // Clear timeout flag
 +        this.appView.incUrlTimeout();
 +
 +        // Handle error
 +        JSONObject data = new JSONObject();
 +        try {
 +            data.put("errorCode", errorCode);
 +            data.put("description", description);
 +            data.put("url", failingUrl);
 +        } catch (JSONException e) {
 +            e.printStackTrace();
 +        }
 +        this.appView.postMessage("onReceivedError", data);
 +    }
 +
 +    /**
 +     * 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.
 +     */
 +    @TargetApi(8)
 +    @Override
 +    public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
 +
 +        final String packageName = this.cordova.getActivity().getPackageName();
 +        final PackageManager pm = this.cordova.getActivity().getPackageManager();
 +
 +        ApplicationInfo appInfo;
 +        try {
 +            appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
 +            if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
 +                // debug = true
 +                handler.proceed();
 +                return;
 +            } else {
 +                // debug = false
 +                super.onReceivedSslError(view, handler, error);
 +            }
 +        } catch (NameNotFoundException e) {
 +            // When it doubt, lock it out!
 +            super.onReceivedSslError(view, handler, error);
 +        }
 +    }
 +
 +
 +    /**
 +     * Sets the authentication token.
 +     *
 +     * @param authenticationToken
 +     * @param host
 +     * @param realm
 +     */
 +    public void setAuthenticationToken(AuthenticationToken authenticationToken, String host, String realm) {
 +        if (host == null) {
 +            host = "";
 +        }
 +        if (realm == null) {
 +            realm = "";
 +        }
 +        this.authenticationTokens.put(host.concat(realm), authenticationToken);
 +    }
 +
 +    /**
 +     * 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) {
 +        return this.authenticationTokens.remove(host.concat(realm));
 +    }
 +
 +    /**
 +     * 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) {
 +        AuthenticationToken token = null;
 +        token = this.authenticationTokens.get(host.concat(realm));
 +
 +        if (token == null) {
 +            // try with just the host
 +            token = this.authenticationTokens.get(host);
 +
 +            // Try the realm
 +            if (token == null) {
 +                token = this.authenticationTokens.get(realm);
 +            }
 +
 +            // if no host found, just query for default
 +            if (token == null) {
 +                token = this.authenticationTokens.get("");
 +            }
 +        }
 +
 +        return token;
 +    }
 +
 +    /**
 +     * Clear all authentication tokens.
 +     */
 +    public void clearAuthenticationTokens() {
 +        this.authenticationTokens.clear();
 +    }
 +
 +    @Override
 +    public void onReceivedError(int errorCode, String description, String url) {
 +        this.onReceivedError(appView, errorCode, description, url);
 +    }
 +
 +}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/4ca23056/framework/src/org/apache/cordova/CordovaActivity.java
----------------------------------------------------------------------


[4/8] android commit: CB-6761 Fix native->JS bridge ceasing to fire when page changes and online is set to false and the JS loads quickly

Posted by ag...@apache.org.
CB-6761 Fix native->JS bridge ceasing to fire when page changes and online is set to false and the JS loads quickly


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

Branch: refs/heads/4.0.x
Commit: 445ddd89fb3269a772978a9860247065e5886249
Parents: 6f21a96
Author: Andrew Grieve <ag...@chromium.org>
Authored: Thu Jul 3 13:27:30 2014 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Thu Jul 3 13:27:30 2014 -0400

----------------------------------------------------------------------
 .../src/org/apache/cordova/NativeToJsMessageQueue.java | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/445ddd89/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/NativeToJsMessageQueue.java b/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
index 5d0c061..9f6f96e 100755
--- a/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
+++ b/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
@@ -308,23 +308,28 @@ public class NativeToJsMessageQueue {
     /** Uses online/offline events to tell the JS when to poll for messages. */
     private class OnlineEventsBridgeMode extends BridgeMode {
         private boolean online;
-        final Runnable runnable = new Runnable() {
+        private boolean ignoreNextFlush;
+
+        final Runnable toggleNetworkRunnable = new Runnable() {
             public void run() {
                 if (!queue.isEmpty()) {
+                    ignoreNextFlush = false;
                     webView.setNetworkAvailable(online);
                 }
-            }                
+            }
         };
         @Override void reset() {
             online = false;
+            // If the following call triggers a notifyOfFlush, then ignore it.
+            ignoreNextFlush = true;
             webView.setNetworkAvailable(true);
         }
         @Override void onNativeToJsMessageAvailable() {
-            cordova.getActivity().runOnUiThread(runnable);
+            cordova.getActivity().runOnUiThread(toggleNetworkRunnable);
         }
         // Track when online/offline events are fired so that we don't fire excess events.
         @Override void notifyOfFlush(boolean fromOnlineEvent) {
-            if (fromOnlineEvent) {
+            if (fromOnlineEvent && !ignoreNextFlush) {
                 online = !online;
             }
         }


[3/8] android commit: Update the errorurl to no longer use intents

Posted by ag...@apache.org.
Update the errorurl to no longer use intents


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

Branch: refs/heads/4.0.x
Commit: 6f21a96238a298a94cb66bfa4f2a969f768cea69
Parents: c47bcb2
Author: Joe Bowser <bo...@apache.org>
Authored: Tue Jun 24 12:57:46 2014 -0700
Committer: Joe Bowser <bo...@apache.org>
Committed: Tue Jun 24 12:57:46 2014 -0700

----------------------------------------------------------------------
 framework/src/org/apache/cordova/Config.java         | 15 ++++++++++-----
 .../src/org/apache/cordova/CordovaActivity.java      |  2 +-
 2 files changed, 11 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/6f21a962/framework/src/org/apache/cordova/Config.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/Config.java b/framework/src/org/apache/cordova/Config.java
index 31a1370..5da0856 100644
--- a/framework/src/org/apache/cordova/Config.java
+++ b/framework/src/org/apache/cordova/Config.java
@@ -20,21 +20,16 @@
 package org.apache.cordova;
 
 import java.io.IOException;
-
 import java.util.Locale;
-
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.apache.cordova.LOG;
-
 import org.xmlpull.v1.XmlPullParserException;
 
 import android.app.Activity;
-
 import android.content.res.XmlResourceParser;
 import android.graphics.Color;
-
 import android.util.Log;
 
 public class Config {
@@ -44,6 +39,8 @@ public class Config {
     private Whitelist whitelist = new Whitelist();
     private String startUrl;
 
+    private static String errorUrl;
+
     private static Config self = null;
 
     public static void init(Activity action) {
@@ -156,6 +153,10 @@ public class Config {
                         boolean value = xml.getAttributeValue(null, "value").equals("true");
                         action.getIntent().putExtra(name, value);
                     }
+                    else if(name.equalsIgnoreCase("errorurl"))
+                    {
+                        errorUrl = xml.getAttributeValue(null, "value");
+                    }
                     else
                     {
                         String value = xml.getAttributeValue(null, "value");
@@ -230,4 +231,8 @@ public class Config {
         }
         return self.startUrl;
     }
+
+    public static String getErrorUrl() {
+        return errorUrl;
+    }
 }

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/6f21a962/framework/src/org/apache/cordova/CordovaActivity.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CordovaActivity.java b/framework/src/org/apache/cordova/CordovaActivity.java
index a2610c5..1cfe265 100755
--- a/framework/src/org/apache/cordova/CordovaActivity.java
+++ b/framework/src/org/apache/cordova/CordovaActivity.java
@@ -716,7 +716,7 @@ public class CordovaActivity extends Activity implements CordovaInterface {
         
 
         //Code to test CB-3064
-        String errorUrl = this.getStringProperty("ErrorUrl", null);
+        String errorUrl = Config.getErrorUrl();
         LOG.d(TAG, "CB-3064: The errorUrl is " + errorUrl);
           
         if (this.activityState == ACTIVITY_STARTING) {


[6/8] android commit: Delete Location-change JS->Native bridge mode

Posted by ag...@apache.org.
Delete Location-change JS->Native bridge mode

It was always disabled, and there's really no reason to keep it around.


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

Branch: refs/heads/4.0.x
Commit: f577af08864c4503fad5afc7d9ca7b9611ba27ce
Parents: aab47bd
Author: Andrew Grieve <ag...@chromium.org>
Authored: Thu Jul 3 22:18:18 2014 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Thu Jul 3 22:18:18 2014 -0400

----------------------------------------------------------------------
 .../org/apache/cordova/CordovaUriHelper.java    | 34 +-------------------
 .../apache/cordova/NativeToJsMessageQueue.java  |  4 ---
 2 files changed, 1 insertion(+), 37 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/f577af08/framework/src/org/apache/cordova/CordovaUriHelper.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/CordovaUriHelper.java b/framework/src/org/apache/cordova/CordovaUriHelper.java
index 1a113de..1502a1f 100644
--- a/framework/src/org/apache/cordova/CordovaUriHelper.java
+++ b/framework/src/org/apache/cordova/CordovaUriHelper.java
@@ -19,17 +19,13 @@
 
 package org.apache.cordova;
 
-import org.json.JSONException;
-
 import android.content.Intent;
 import android.net.Uri;
-import android.util.Log;
 import android.webkit.WebView;
 
 public class CordovaUriHelper {
     
     private static final String TAG = "CordovaUriHelper";
-    private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/";
     
     private CordovaWebView appView;
     private CordovaInterface cordova;
@@ -40,27 +36,6 @@ public class CordovaUriHelper {
         cordova = cdv;
     }
     
-    
-    // Parses commands sent by setting the webView's URL to:
-    // cdvbrg:service/action/callbackId#jsonArgs
-    void handleExecUrl(String url) {
-        int idx1 = CORDOVA_EXEC_URL_PREFIX.length();
-        int idx2 = url.indexOf('#', idx1 + 1);
-        int idx3 = url.indexOf('#', idx2 + 1);
-        int idx4 = url.indexOf('#', idx3 + 1);
-        if (idx1 == -1 || idx2 == -1 || idx3 == -1 || idx4 == -1) {
-            Log.e(TAG, "Could not decode URL command: " + url);
-            return;
-        }
-        String service    = url.substring(idx1, idx2);
-        String action     = url.substring(idx2 + 1, idx3);
-        String callbackId = url.substring(idx3 + 1, idx4);
-        String jsonArgs   = url.substring(idx4 + 1);
-        appView.pluginManager.exec(service, action, callbackId, jsonArgs);
-        //There is no reason to not send this directly to the pluginManager
-    }
-    
-
     /**
      * Give the host application a chance to take over the control when a new url
      * is about to be loaded in the current WebView.
@@ -73,12 +48,8 @@ public class CordovaUriHelper {
         // The WebView should support http and https when going on the Internet
         if(url.startsWith("http:") || url.startsWith("https:"))
         {
-            // Check if it's an exec() bridge command message.
-            if (NativeToJsMessageQueue.ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) {
-                handleExecUrl(url);
-            }
             // We only need to whitelist sites on the Internet! 
-            else if(Config.isUrlWhiteListed(url))
+            if(Config.isUrlWhiteListed(url))
             {
                 return false;
             }
@@ -106,7 +77,4 @@ public class CordovaUriHelper {
         //Default behaviour should be to load the default intent, let's see what happens! 
         return true;
     }
-
-    
-
 }

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/f577af08/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/NativeToJsMessageQueue.java b/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
index 063fc7e..b822800 100755
--- a/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
+++ b/framework/src/org/apache/cordova/NativeToJsMessageQueue.java
@@ -39,10 +39,6 @@ public class NativeToJsMessageQueue {
     // JS instead of the custom format (useful for benchmarking).
     private static final boolean FORCE_ENCODE_USING_EVAL = false;
 
-    // Disable URL-based exec() bridge by default since it's a bit of a
-    // security concern.
-    static final boolean ENABLE_LOCATION_CHANGE_EXEC_MODE = false;
-        
     // Disable sending back native->JS messages during an exec() when the active
     // exec() is asynchronous. Set this to true when running bridge benchmarks.
     static final boolean DISABLE_EXEC_CHAINING = false;