cordova-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From agri...@apache.org
Subject [8/8] android commit: Merge branch 'master' into 4.0.x (Bridge fixes)
Date Fri, 04 Jul 2014 03:02:49 GMT
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 <agrieve@chromium.org>
Authored: Thu Jul 3 23:02:02 2014 -0400
Committer: Andrew Grieve <agrieve@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
----------------------------------------------------------------------


Mime
View raw message