You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by st...@apache.org on 2017/05/01 21:17:30 UTC

[48/63] [abbrv] cordova-lib git commit: CB-11242: updated tests and fixtures

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaBridge.java
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaBridge.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaBridge.java
new file mode 100644
index 0000000..9459a11
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaBridge.java
@@ -0,0 +1,182 @@
+/*
+       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.security.SecureRandom;
+
+import org.json.JSONArray;
+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 class CordovaBridge {
+    private static final String LOG_TAG = "CordovaBridge";
+    private PluginManager pluginManager;
+    private NativeToJsMessageQueue jsMessageQueue;
+    private volatile int expectedBridgeSecret = -1; // written by UI thread, read by JS thread.
+
+    public CordovaBridge(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) {
+        this.pluginManager = pluginManager;
+        this.jsMessageQueue = jsMessageQueue;
+    }
+
+    public String jsExec(int bridgeSecret, String service, String action, String callbackId, String arguments) throws JSONException, IllegalAccessException {
+        if (!verifySecret("exec()", bridgeSecret)) {
+            return null;
+        }
+        // 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 = null;
+            if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING) {
+                ret = jsMessageQueue.popAndEncode(false);
+            }
+            return ret;
+        } catch (Throwable e) {
+            e.printStackTrace();
+            return "";
+        } finally {
+            jsMessageQueue.setPaused(false);
+        }
+    }
+
+    public void jsSetNativeToJsBridgeMode(int bridgeSecret, int value) throws IllegalAccessException {
+        if (!verifySecret("setNativeToJsBridgeMode()", bridgeSecret)) {
+            return;
+        }
+        jsMessageQueue.setBridgeMode(value);
+    }
+
+    public String jsRetrieveJsMessages(int bridgeSecret, boolean fromOnlineEvent) throws IllegalAccessException {
+        if (!verifySecret("retrieveJsMessages()", bridgeSecret)) {
+            return null;
+        }
+        return jsMessageQueue.popAndEncode(fromOnlineEvent);
+    }
+
+    private boolean verifySecret(String action, int bridgeSecret) throws IllegalAccessException {
+        if (!jsMessageQueue.isBridgeEnabled()) {
+            if (bridgeSecret == -1) {
+                LOG.d(LOG_TAG, action + " call made before bridge was enabled.");
+            } else {
+                LOG.d(LOG_TAG, "Ignoring " + action + " from previous page load.");
+            }
+            return false;
+        }
+        // Bridge secret wrong and bridge not due to it being from the previous page.
+        if (expectedBridgeSecret < 0 || bridgeSecret != expectedBridgeSecret) {
+            LOG.e(LOG_TAG, "Bridge access attempt with wrong secret token, possibly from malicious code. Disabling exec() bridge!");
+            clearBridgeSecret();
+            throw new IllegalAccessException();
+        }
+        return true;
+    }
+
+    /** Called on page transitions */
+    void clearBridgeSecret() {
+        expectedBridgeSecret = -1;
+    }
+
+    public boolean isSecretEstablished() {
+        return expectedBridgeSecret != -1;
+    }
+
+    /** Called by cordova.js to initialize the bridge. */
+    int generateBridgeSecret() {
+        SecureRandom randGen = new SecureRandom();
+        expectedBridgeSecret = randGen.nextInt(Integer.MAX_VALUE);
+        return expectedBridgeSecret;
+    }
+
+    public void reset() {
+        jsMessageQueue.reset();
+        clearBridgeSecret();
+    }
+
+    public String promptOnJsPrompt(String origin, String message, String defaultValue) {
+        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 = jsExec(bridgeSecret, service, action, callbackId, message);
+                return r == null ? "" : r;
+            } catch (JSONException e) {
+                e.printStackTrace();
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            }
+            return "";
+        }
+        // Sets the native->JS bridge mode.
+        else if (defaultValue != null && defaultValue.startsWith("gap_bridge_mode:")) {
+            try {
+                int bridgeSecret = Integer.parseInt(defaultValue.substring(16));
+                jsSetNativeToJsBridgeMode(bridgeSecret, Integer.parseInt(message));
+            } catch (NumberFormatException e){
+                e.printStackTrace();
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            }
+            return "";
+        }
+        // Polling for JavaScript messages
+        else if (defaultValue != null && defaultValue.startsWith("gap_poll:")) {
+            int bridgeSecret = Integer.parseInt(defaultValue.substring(9));
+            try {
+                String r = jsRetrieveJsMessages(bridgeSecret, "1".equals(message));
+                return r == null ? "" : r;
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            }
+            return "";
+        }
+        else if (defaultValue != null && defaultValue.startsWith("gap_init:")) {
+            // Protect against random iframes being able to talk through the bridge.
+            // Trust only pages which the app would have been allowed to navigate to anyway.
+            if (pluginManager.shouldAllowBridgeAccess(origin)) {
+                // Enable the bridge
+                int bridgeMode = Integer.parseInt(defaultValue.substring(9));
+                jsMessageQueue.setBridgeMode(bridgeMode);
+                // Tell JS the bridge secret.
+                int secret = generateBridgeSecret();
+                return ""+secret;
+            } else {
+                LOG.e(LOG_TAG, "gap_init called from restricted origin: " + origin);
+            }
+            return "";
+        }
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaClientCertRequest.java
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaClientCertRequest.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaClientCertRequest.java
new file mode 100644
index 0000000..5dd0eca
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaClientCertRequest.java
@@ -0,0 +1,96 @@
+/*
+       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.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import android.webkit.ClientCertRequest;
+
+/**
+ * Implementation of the ICordovaClientCertRequest for Android WebView.
+ */
+public class CordovaClientCertRequest implements ICordovaClientCertRequest {
+
+    private final ClientCertRequest request;
+
+    public CordovaClientCertRequest(ClientCertRequest request) {
+        this.request = request;
+    }
+    
+    /**
+     * Cancel this request
+     */
+    public void cancel()
+    {
+        request.cancel();
+    }
+    
+    /*
+     * Returns the host name of the server requesting the certificate.
+     */
+    public String getHost()
+    {
+        return request.getHost();
+    }
+    
+    /*
+     * Returns the acceptable types of asymmetric keys (can be null).
+     */
+    public String[] getKeyTypes()
+    {
+        return request.getKeyTypes();
+    }
+    
+    /*
+     * Returns the port number of the server requesting the certificate.
+     */
+    public int getPort()
+    {
+        return request.getPort();
+    }
+    
+    /*
+     * Returns the acceptable certificate issuers for the certificate matching the private key (can be null).
+     */
+    public Principal[] getPrincipals()
+    {
+        return request.getPrincipals();
+    }
+    
+    /*
+     * Ignore the request for now. Do not remember user's choice.
+     */
+    public void ignore()
+    {
+        request.ignore();
+    }
+    
+    /*
+     * Proceed with the specified private key and client certificate chain. Remember the user's positive choice and use it for future requests.
+     * 
+     * @param privateKey The privateKey
+     * @param chain The certificate chain 
+     */
+    public void proceed(PrivateKey privateKey, X509Certificate[] chain)
+    {
+        request.proceed(privateKey, chain);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaDialogsHelper.java
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaDialogsHelper.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaDialogsHelper.java
new file mode 100644
index 0000000..a219c99
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaDialogsHelper.java
@@ -0,0 +1,152 @@
+/*
+       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.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+/**
+ * Helper class for WebViews to implement prompt(), alert(), confirm() dialogs.
+ */
+public class CordovaDialogsHelper {
+    private final Context context;
+    private AlertDialog lastHandledDialog;
+
+    public CordovaDialogsHelper(Context context) {
+        this.context = context;
+    }
+
+    public void showAlert(String message, final Result result) {
+        AlertDialog.Builder dlg = new AlertDialog.Builder(context);
+        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.gotResult(true, null);
+                    }
+                });
+        dlg.setOnCancelListener(
+                new DialogInterface.OnCancelListener() {
+                    public void onCancel(DialogInterface dialog) {
+                        result.gotResult(false, null);
+                    }
+                });
+        dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
+            //DO NOTHING
+            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+                if (keyCode == KeyEvent.KEYCODE_BACK)
+                {
+                    result.gotResult(true, null);
+                    return false;
+                }
+                else
+                    return true;
+            }
+        });
+        lastHandledDialog = dlg.show();
+    }
+
+    public void showConfirm(String message, final Result result) {
+        AlertDialog.Builder dlg = new AlertDialog.Builder(context);
+        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.gotResult(true, null);
+                    }
+                });
+        dlg.setNegativeButton(android.R.string.cancel,
+                new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int which) {
+                        result.gotResult(false, null);
+                    }
+                });
+        dlg.setOnCancelListener(
+                new DialogInterface.OnCancelListener() {
+                    public void onCancel(DialogInterface dialog) {
+                        result.gotResult(false, null);
+                    }
+                });
+        dlg.setOnKeyListener(new DialogInterface.OnKeyListener() {
+            //DO NOTHING
+            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+                if (keyCode == KeyEvent.KEYCODE_BACK)
+                {
+                    result.gotResult(false, null);
+                    return false;
+                }
+                else
+                    return true;
+            }
+        });
+        lastHandledDialog = dlg.show();
+    }
+
+    /**
+     * 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!
+     */
+    public void showPrompt(String message, String defaultValue, final Result result) {
+        // Returning false would also show a dialog, but the default one shows the origin (ugly).
+        AlertDialog.Builder dlg = new AlertDialog.Builder(context);
+        dlg.setMessage(message);
+        final EditText input = new EditText(context);
+        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();
+                        result.gotResult(true, userText);
+                    }
+                });
+        dlg.setNegativeButton(android.R.string.cancel,
+                new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int which) {
+                        result.gotResult(false, null);
+                    }
+                });
+        lastHandledDialog = dlg.show();
+    }
+
+    public void destroyLastDialog(){
+        if (lastHandledDialog != null){
+            lastHandledDialog.cancel();
+        }
+    }
+
+    public interface Result {
+        public void gotResult(boolean success, String value);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaHttpAuthHandler.java
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaHttpAuthHandler.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaHttpAuthHandler.java
new file mode 100644
index 0000000..724381e
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaHttpAuthHandler.java
@@ -0,0 +1,51 @@
+/*
+       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.HttpAuthHandler;
+
+/**
+ * Specifies interface for HTTP auth handler object which is used to handle auth requests and
+ * specifying user credentials.
+ */
+public class CordovaHttpAuthHandler implements ICordovaHttpAuthHandler {
+
+    private final HttpAuthHandler handler;
+
+    public CordovaHttpAuthHandler(HttpAuthHandler handler) {
+        this.handler = handler;
+    }
+    
+    /**
+     * Instructs the WebView to cancel the authentication request.
+     */
+    public void cancel () {
+        this.handler.cancel();
+    }
+    
+    /**
+     * Instructs the WebView to proceed with the authentication with the given credentials.
+     * 
+     * @param username
+     * @param password
+     */
+    public void proceed (String username, String password) {
+        this.handler.proceed(username, password);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaInterface.java
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaInterface.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaInterface.java
new file mode 100755
index 0000000..3b8468f
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaInterface.java
@@ -0,0 +1,88 @@
+/*
+       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.app.Activity;
+import android.content.Intent;
+
+import org.apache.cordova.CordovaPlugin;
+
+import java.util.concurrent.ExecutorService;
+
+/**
+ * The Activity interface that is implemented by CordovaActivity.
+ * It is used to isolate plugin development, and remove dependency on entire Cordova library.
+ */
+public interface CordovaInterface {
+
+    /**
+     * Launch an activity for which you would like a result when it finished. When this activity exits,
+     * your onActivityResult() method will be called.
+     *
+     * @param command     The command object
+     * @param intent      The intent to start
+     * @param requestCode   The request code that is passed to callback to identify the activity
+     */
+    abstract public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode);
+
+    /**
+     * Set the plugin to be called when a sub-activity exits.
+     *
+     * @param plugin      The plugin on which onActivityResult is to be called
+     */
+    abstract public void setActivityResultCallback(CordovaPlugin plugin);
+
+    /**
+     * Get the Android activity.
+     *
+     * @return the Activity
+     */
+    public abstract Activity getActivity();
+    
+
+    /**
+     * Called when a message is sent to plugin.
+     *
+     * @param id            The message id
+     * @param data          The message data
+     * @return              Object or null
+     */
+    public Object onMessage(String id, Object data);
+    
+    /**
+     * Returns a shared thread pool that can be used for background tasks.
+     */
+    public ExecutorService getThreadPool();
+
+    /**
+     * Sends a permission request to the activity for one permission.
+     */
+    public void requestPermission(CordovaPlugin plugin, int requestCode, String permission);
+
+    /**
+     * Sends a permission request to the activity for a group of permissions
+     */
+    public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions);
+
+    /**
+     * Check for a permission.  Returns true if the permission is granted, false otherwise.
+     */
+    public boolean hasPermission(String permission);
+
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaInterfaceImpl.java
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaInterfaceImpl.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaInterfaceImpl.java
new file mode 100644
index 0000000..71dcb78
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaInterfaceImpl.java
@@ -0,0 +1,241 @@
+/*
+       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.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Pair;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Default implementation of CordovaInterface.
+ */
+public class CordovaInterfaceImpl implements CordovaInterface {
+    private static final String TAG = "CordovaInterfaceImpl";
+    protected Activity activity;
+    protected ExecutorService threadPool;
+    protected PluginManager pluginManager;
+
+    protected ActivityResultHolder savedResult;
+    protected CallbackMap permissionResultCallbacks;
+    protected CordovaPlugin activityResultCallback;
+    protected String initCallbackService;
+    protected int activityResultRequestCode;
+    protected boolean activityWasDestroyed = false;
+    protected Bundle savedPluginState;
+
+    public CordovaInterfaceImpl(Activity activity) {
+        this(activity, Executors.newCachedThreadPool());
+    }
+
+    public CordovaInterfaceImpl(Activity activity, ExecutorService threadPool) {
+        this.activity = activity;
+        this.threadPool = threadPool;
+        this.permissionResultCallbacks = new CallbackMap();
+    }
+
+    @Override
+    public void startActivityForResult(CordovaPlugin command, Intent intent, int requestCode) {
+        setActivityResultCallback(command);
+        try {
+            activity.startActivityForResult(intent, requestCode);
+        } catch (RuntimeException e) { // E.g.: ActivityNotFoundException
+            activityResultCallback = null;
+            throw e;
+        }
+    }
+
+    @Override
+    public void setActivityResultCallback(CordovaPlugin plugin) {
+        // Cancel any previously pending activity.
+        if (activityResultCallback != null) {
+            activityResultCallback.onActivityResult(activityResultRequestCode, Activity.RESULT_CANCELED, null);
+        }
+        activityResultCallback = plugin;
+    }
+
+    @Override
+    public Activity getActivity() {
+        return activity;
+    }
+
+    @Override
+    public Object onMessage(String id, Object data) {
+        if ("exit".equals(id)) {
+            activity.finish();
+        }
+        return null;
+    }
+
+    @Override
+    public ExecutorService getThreadPool() {
+        return threadPool;
+    }
+
+    /**
+     * Dispatches any pending onActivityResult callbacks and sends the resume event if the
+     * Activity was destroyed by the OS.
+     */
+    public void onCordovaInit(PluginManager pluginManager) {
+        this.pluginManager = pluginManager;
+        if (savedResult != null) {
+            onActivityResult(savedResult.requestCode, savedResult.resultCode, savedResult.intent);
+        } else if(activityWasDestroyed) {
+            // If there was no Activity result, we still need to send out the resume event if the
+            // Activity was destroyed by the OS
+            activityWasDestroyed = false;
+            if(pluginManager != null)
+            {
+                CoreAndroid appPlugin = (CoreAndroid) pluginManager.getPlugin(CoreAndroid.PLUGIN_NAME);
+                if(appPlugin != null) {
+                    JSONObject obj = new JSONObject();
+                    try {
+                        obj.put("action", "resume");
+                    } catch (JSONException e) {
+                        LOG.e(TAG, "Failed to create event message", e);
+                    }
+                    appPlugin.sendResumeEvent(new PluginResult(PluginResult.Status.OK, obj));
+                }
+            }
+
+        }
+    }
+
+    /**
+     * Routes the result to the awaiting plugin. Returns false if no plugin was waiting.
+     */
+    public boolean onActivityResult(int requestCode, int resultCode, Intent intent) {
+        CordovaPlugin callback = activityResultCallback;
+        if(callback == null && initCallbackService != null) {
+            // The application was restarted, but had defined an initial callback
+            // before being shut down.
+            savedResult = new ActivityResultHolder(requestCode, resultCode, intent);
+            if (pluginManager != null) {
+                callback = pluginManager.getPlugin(initCallbackService);
+                if(callback != null) {
+                    callback.onRestoreStateForActivityResult(savedPluginState.getBundle(callback.getServiceName()),
+                            new ResumeCallback(callback.getServiceName(), pluginManager));
+                }
+            }
+        }
+        activityResultCallback = null;
+
+        if (callback != null) {
+            LOG.d(TAG, "Sending activity result to plugin");
+            initCallbackService = null;
+            savedResult = null;
+            callback.onActivityResult(requestCode, resultCode, intent);
+            return true;
+        }
+        LOG.w(TAG, "Got an activity result, but no plugin was registered to receive it" + (savedResult != null ? " yet!" : "."));
+        return false;
+    }
+
+    /**
+     * Call this from your startActivityForResult() overload. This is required to catch the case
+     * where plugins use Activity.startActivityForResult() + CordovaInterface.setActivityResultCallback()
+     * rather than CordovaInterface.startActivityForResult().
+     */
+    public void setActivityResultRequestCode(int requestCode) {
+        activityResultRequestCode = requestCode;
+    }
+
+    /**
+     * Saves parameters for startActivityForResult().
+     */
+    public void onSaveInstanceState(Bundle outState) {
+        if (activityResultCallback != null) {
+            String serviceName = activityResultCallback.getServiceName();
+            outState.putString("callbackService", serviceName);
+        }
+        if(pluginManager != null){
+            outState.putBundle("plugin", pluginManager.onSaveInstanceState());
+        }
+
+    }
+
+    /**
+     * Call this from onCreate() so that any saved startActivityForResult parameters will be restored.
+     */
+    public void restoreInstanceState(Bundle savedInstanceState) {
+        initCallbackService = savedInstanceState.getString("callbackService");
+        savedPluginState = savedInstanceState.getBundle("plugin");
+        activityWasDestroyed = true;
+    }
+
+    private static class ActivityResultHolder {
+        private int requestCode;
+        private int resultCode;
+        private Intent intent;
+
+        public ActivityResultHolder(int requestCode, int resultCode, Intent intent) {
+            this.requestCode = requestCode;
+            this.resultCode = resultCode;
+            this.intent = intent;
+        }
+    }
+
+    /**
+     * Called by the system when the user grants permissions
+     *
+     * @param requestCode
+     * @param permissions
+     * @param grantResults
+     */
+    public void onRequestPermissionResult(int requestCode, String[] permissions,
+                                          int[] grantResults) throws JSONException {
+        Pair<CordovaPlugin, Integer> callback = permissionResultCallbacks.getAndRemoveCallback(requestCode);
+        if(callback != null) {
+            callback.first.onRequestPermissionResult(callback.second, permissions, grantResults);
+        }
+    }
+
+    public void requestPermission(CordovaPlugin plugin, int requestCode, String permission) {
+        String[] permissions = new String [1];
+        permissions[0] = permission;
+        requestPermissions(plugin, requestCode, permissions);
+    }
+
+    public void requestPermissions(CordovaPlugin plugin, int requestCode, String [] permissions) {
+        int mappedRequestCode = permissionResultCallbacks.registerCallback(plugin, requestCode);
+        getActivity().requestPermissions(permissions, mappedRequestCode);
+    }
+
+    public boolean hasPermission(String permission)
+    {
+        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+        {
+            int result = activity.checkSelfPermission(permission);
+            return PackageManager.PERMISSION_GRANTED == result;
+        }
+        else
+        {
+            return true;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaPlugin.java
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaPlugin.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaPlugin.java
new file mode 100644
index 0000000..41af1db
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaPlugin.java
@@ -0,0 +1,422 @@
+/*
+       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.CordovaArgs;
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CallbackContext;
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * Plugins must extend this class and override one of the execute methods.
+ */
+public class CordovaPlugin {
+    public CordovaWebView webView;
+    public CordovaInterface cordova;
+    protected CordovaPreferences preferences;
+    private String serviceName;
+
+    /**
+     * Call this after constructing to initialize the plugin.
+     * Final because we want to be able to change args without breaking plugins.
+     */
+    public final void privateInitialize(String serviceName, CordovaInterface cordova, CordovaWebView webView, CordovaPreferences preferences) {
+        assert this.cordova == null;
+        this.serviceName = serviceName;
+        this.cordova = cordova;
+        this.webView = webView;
+        this.preferences = preferences;
+        initialize(cordova, webView);
+        pluginInitialize();
+    }
+
+    /**
+     * Called after plugin construction and fields have been initialized.
+     * Prefer to use pluginInitialize instead since there is no value in
+     * having parameters on the initialize() function.
+     */
+    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
+    }
+
+    /**
+     * Called after plugin construction and fields have been initialized.
+     */
+    protected void pluginInitialize() {
+    }
+
+    /**
+     * Returns the plugin's service name (what you'd use when calling pluginManger.getPlugin())
+     */
+    public String getServiceName() {
+        return serviceName;
+    }
+
+    /**
+     * Executes the request.
+     *
+     * This method is called from the WebView thread. To do a non-trivial amount of work, use:
+     *     cordova.getThreadPool().execute(runnable);
+     *
+     * To run on the UI thread, use:
+     *     cordova.getActivity().runOnUiThread(runnable);
+     *
+     * @param action          The action to execute.
+     * @param rawArgs         The exec() arguments in JSON form.
+     * @param callbackContext The callback context used when calling back into JavaScript.
+     * @return                Whether the action was valid.
+     */
+    public boolean execute(String action, String rawArgs, CallbackContext callbackContext) throws JSONException {
+        JSONArray args = new JSONArray(rawArgs);
+        return execute(action, args, callbackContext);
+    }
+
+    /**
+     * Executes the request.
+     *
+     * This method is called from the WebView thread. To do a non-trivial amount of work, use:
+     *     cordova.getThreadPool().execute(runnable);
+     *
+     * To run on the UI thread, use:
+     *     cordova.getActivity().runOnUiThread(runnable);
+     *
+     * @param action          The action to execute.
+     * @param args            The exec() arguments.
+     * @param callbackContext The callback context used when calling back into JavaScript.
+     * @return                Whether the action was valid.
+     */
+    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+        CordovaArgs cordovaArgs = new CordovaArgs(args);
+        return execute(action, cordovaArgs, callbackContext);
+    }
+
+    /**
+     * Executes the request.
+     *
+     * This method is called from the WebView thread. To do a non-trivial amount of work, use:
+     *     cordova.getThreadPool().execute(runnable);
+     *
+     * To run on the UI thread, use:
+     *     cordova.getActivity().runOnUiThread(runnable);
+     *
+     * @param action          The action to execute.
+     * @param args            The exec() arguments, wrapped with some Cordova helpers.
+     * @param callbackContext The callback context used when calling back into JavaScript.
+     * @return                Whether the action was valid.
+     */
+    public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) throws JSONException {
+        return false;
+    }
+
+    /**
+     * Called when the system is about to start resuming a previous activity.
+     *
+     * @param multitasking		Flag indicating if multitasking is turned on for app
+     */
+    public void onPause(boolean multitasking) {
+    }
+
+    /**
+     * Called when the activity will start interacting with the user.
+     *
+     * @param multitasking		Flag indicating if multitasking is turned on for app
+     */
+    public void onResume(boolean multitasking) {
+    }
+
+    /**
+     * Called when the activity is becoming visible to the user.
+     */
+    public void onStart() {
+    }
+
+    /**
+     * Called when the activity is no longer visible to the user.
+     */
+    public void onStop() {
+    }
+
+    /**
+     * Called when the activity receives a new intent.
+     */
+    public void onNewIntent(Intent intent) {
+    }
+
+    /**
+     * The final call you receive before your activity is destroyed.
+     */
+    public void onDestroy() {
+    }
+
+    /**
+     * Called when the Activity is being destroyed (e.g. if a plugin calls out to an external
+     * Activity and the OS kills the CordovaActivity in the background). The plugin should save its
+     * state in this method only if it is awaiting the result of an external Activity and needs
+     * to preserve some information so as to handle that result; onRestoreStateForActivityResult()
+     * will only be called if the plugin is the recipient of an Activity result
+     *
+     * @return  Bundle containing the state of the plugin or null if state does not need to be saved
+     */
+    public Bundle onSaveInstanceState() {
+        return null;
+    }
+
+    /**
+     * Called when a plugin is the recipient of an Activity result after the CordovaActivity has
+     * been destroyed. The Bundle will be the same as the one the plugin returned in
+     * onSaveInstanceState()
+     *
+     * @param state             Bundle containing the state of the plugin
+     * @param callbackContext   Replacement Context to return the plugin result to
+     */
+    public void onRestoreStateForActivityResult(Bundle state, CallbackContext callbackContext) {}
+
+    /**
+     * Called when a message is sent to plugin.
+     *
+     * @param id            The message id
+     * @param data          The message data
+     * @return              Object to stop propagation or null
+     */
+    public Object onMessage(String id, Object data) {
+        return null;
+    }
+
+    /**
+     * Called when an activity you launched exits, giving you the requestCode you started it with,
+     * the resultCode it returned, and any additional data from it.
+     *
+     * @param requestCode   The request code originally supplied to startActivityForResult(),
+     *                      allowing you to identify who this result came from.
+     * @param resultCode    The integer result code returned by the child activity through its setResult().
+     * @param intent        An Intent, which can return result data to the caller (various data can be
+     *                      attached to Intent "extras").
+     */
+    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+    }
+
+    /**
+     * Hook for blocking the loading of external resources.
+     *
+     * This will be called when the WebView's shouldInterceptRequest wants to
+     * know whether to open a connection to an external resource. Return false
+     * to block the request: if any plugin returns false, Cordova will block
+     * the request. If all plugins return null, the default policy will be
+     * enforced. If at least one plugin returns true, and no plugins return
+     * false, then the request will proceed.
+     *
+     * Note that this only affects resource requests which are routed through
+     * WebViewClient.shouldInterceptRequest, such as XMLHttpRequest requests and
+     * img tag loads. WebSockets and media requests (such as <video> and <audio>
+     * tags) are not affected by this method. Use CSP headers to control access
+     * to such resources.
+     */
+    public Boolean shouldAllowRequest(String url) {
+        return null;
+    }
+
+    /**
+     * Hook for blocking navigation by the Cordova WebView. This applies both to top-level and
+     * iframe navigations.
+     *
+     * This will be called when the WebView's needs to know whether to navigate
+     * to a new page. Return false to block the navigation: if any plugin
+     * returns false, Cordova will block the navigation. If all plugins return
+     * null, the default policy will be enforced. It at least one plugin returns
+     * true, and no plugins return false, then the navigation will proceed.
+     */
+    public Boolean shouldAllowNavigation(String url) {
+        return null;
+    }
+
+    /**
+     * Hook for allowing page to call exec(). By default, this returns the result of
+     * shouldAllowNavigation(). It's generally unsafe to allow untrusted content to be loaded
+     * into a CordovaWebView, even within an iframe, so it's best not to touch this.
+     */
+    public Boolean shouldAllowBridgeAccess(String url) {
+        return shouldAllowNavigation(url);
+    }
+
+    /**
+     * Hook for blocking the launching of Intents by the Cordova application.
+     *
+     * This will be called when the WebView will not navigate to a page, but
+     * could launch an intent to handle the URL. Return false to block this: if
+     * any plugin returns false, Cordova will block the navigation. If all
+     * plugins return null, the default policy will be enforced. If at least one
+     * plugin returns true, and no plugins return false, then the URL will be
+     * opened.
+     */
+    public Boolean shouldOpenExternalUrl(String url) {
+        return null;
+    }
+
+    /**
+     * Allows plugins to handle a link being clicked. Return true here to cancel the navigation.
+     *
+     * @param url           The URL that is trying to be loaded in the Cordova webview.
+     * @return              Return true to prevent the URL from loading. Default is false.
+     */
+    public boolean onOverrideUrlLoading(String url) {
+        return false;
+    }
+
+    /**
+     * Hook for redirecting requests. Applies to WebView requests as well as requests made by plugins.
+     * To handle the request directly, return a URI in the form:
+     *
+     *    cdvplugin://pluginId/...
+     *
+     * And implement handleOpenForRead().
+     * To make this easier, use the toPluginUri() and fromPluginUri() helpers:
+     *
+     *     public Uri remapUri(Uri uri) { return toPluginUri(uri); }
+     *
+     *     public CordovaResourceApi.OpenForReadResult handleOpenForRead(Uri uri) throws IOException {
+     *         Uri origUri = fromPluginUri(uri);
+     *         ...
+     *     }
+     */
+    public Uri remapUri(Uri uri) {
+        return null;
+    }
+
+    /**
+     * Called to handle CordovaResourceApi.openForRead() calls for a cdvplugin://pluginId/ URL.
+     * Should never return null.
+     * Added in cordova-android@4.0.0
+     */
+    public CordovaResourceApi.OpenForReadResult handleOpenForRead(Uri uri) throws IOException {
+        throw new FileNotFoundException("Plugin can't handle uri: " + uri);
+    }
+
+    /**
+     * Refer to remapUri()
+     * Added in cordova-android@4.0.0
+     */
+    protected Uri toPluginUri(Uri origUri) {
+        return new Uri.Builder()
+            .scheme(CordovaResourceApi.PLUGIN_URI_SCHEME)
+            .authority(serviceName)
+            .appendQueryParameter("origUri", origUri.toString())
+            .build();
+    }
+
+    /**
+     * Refer to remapUri()
+     * Added in cordova-android@4.0.0
+     */
+    protected Uri fromPluginUri(Uri pluginUri) {
+        return Uri.parse(pluginUri.getQueryParameter("origUri"));
+    }
+
+    /**
+     * Called when the WebView does a top-level navigation or refreshes.
+     *
+     * Plugins should stop any long-running processes and clean up internal state.
+     *
+     * Does nothing by default.
+     */
+    public void onReset() {
+    }
+
+    /**
+     * Called when the system received an HTTP authentication request. Plugin can use
+     * the supplied HttpAuthHandler to process this auth challenge.
+     *
+     * @param view              The WebView that is initiating the callback
+     * @param handler           The HttpAuthHandler used to set the WebView's response
+     * @param host              The host requiring authentication
+     * @param realm             The realm for which authentication is required
+     *
+     * @return                  Returns True if plugin will resolve this auth challenge, otherwise False
+     *
+     */
+    public boolean onReceivedHttpAuthRequest(CordovaWebView view, ICordovaHttpAuthHandler handler, String host, String realm) {
+        return false;
+    }
+
+    /**
+     * Called when he system received an SSL client certificate request.  Plugin can use
+     * the supplied ClientCertRequest to process this certificate challenge.
+     *
+     * @param view              The WebView that is initiating the callback
+     * @param request           The client certificate request
+     *
+     * @return                  Returns True if plugin will resolve this auth challenge, otherwise False
+     *
+     */
+    public boolean onReceivedClientCertRequest(CordovaWebView view, ICordovaClientCertRequest request) {
+        return false;
+    }
+
+    /**
+     * Called by the system when the device configuration changes while your activity is running.
+     *
+     * @param newConfig		The new device configuration
+     */
+    public void onConfigurationChanged(Configuration newConfig) {
+    }
+
+    /**
+     * Called by the Plugin Manager when we need to actually request permissions
+     *
+     * @param requestCode   Passed to the activity to track the request
+     *
+     * @return              Returns the permission that was stored in the plugin
+     */
+
+    public void requestPermissions(int requestCode) {
+    }
+
+    /*
+     * Called by the WebView implementation to check for geolocation permissions, can be used
+     * by other Java methods in the event that a plugin is using this as a dependency.
+     *
+     * @return          Returns true if the plugin has all the permissions it needs to operate.
+     */
+
+    public boolean hasPermisssion() {
+        return true;
+    }
+
+    /**
+     * Called by the system when the user grants permissions
+     *
+     * @param requestCode
+     * @param permissions
+     * @param grantResults
+     */
+    public void onRequestPermissionResult(int requestCode, String[] permissions,
+                                          int[] grantResults) throws JSONException {
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaPreferences.java
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaPreferences.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaPreferences.java
new file mode 100644
index 0000000..4dbc93e
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaPreferences.java
@@ -0,0 +1,101 @@
+/*
+       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.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.cordova.LOG;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class CordovaPreferences {
+    private HashMap<String, String> prefs = new HashMap<String, String>(20);
+    private Bundle preferencesBundleExtras;
+
+    public void setPreferencesBundle(Bundle extras) {
+        preferencesBundleExtras = extras;
+    }
+
+    public void set(String name, String value) {
+        prefs.put(name.toLowerCase(Locale.ENGLISH), value);
+    }
+
+    public void set(String name, boolean value) {
+        set(name, "" + value);
+    }
+
+    public void set(String name, int value) {
+        set(name, "" + value);
+    }
+    
+    public void set(String name, double value) {
+        set(name, "" + value);
+    }
+    
+    public Map<String, String> getAll() {
+        return prefs;
+    }
+
+    public boolean getBoolean(String name, boolean defaultValue) {
+        name = name.toLowerCase(Locale.ENGLISH);
+        String value = prefs.get(name);
+        if (value != null) {
+            return Boolean.parseBoolean(value);
+        }
+        return defaultValue;
+    }
+
+    // Added in 4.0.0
+    public boolean contains(String name) {
+        return getString(name, null) != null;
+    }
+
+    public int getInteger(String name, int defaultValue) {
+        name = name.toLowerCase(Locale.ENGLISH);
+        String value = prefs.get(name);
+        if (value != null) {
+            // Use Integer.decode() can't handle it if the highest bit is set.
+            return (int)(long)Long.decode(value);
+        }
+        return defaultValue;
+    }
+
+    public double getDouble(String name, double defaultValue) {
+        name = name.toLowerCase(Locale.ENGLISH);
+        String value = prefs.get(name);
+        if (value != null) {
+            return Double.valueOf(value);
+        }
+        return defaultValue;
+    }
+
+    public String getString(String name, String defaultValue) {
+        name = name.toLowerCase(Locale.ENGLISH);
+        String value = prefs.get(name);
+        if (value != null) {
+            return value;
+        }
+        return defaultValue;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaResourceApi.java
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaResourceApi.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaResourceApi.java
new file mode 100644
index 0000000..e725e25
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaResourceApi.java
@@ -0,0 +1,471 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+ */
+package org.apache.cordova;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.AssetManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Looper;
+import android.util.Base64;
+import android.webkit.MimeTypeMap;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.channels.FileChannel;
+import java.util.Locale;
+
+/**
+ * What this class provides:
+ * 1. Helpers for reading & writing to URLs.
+ *   - E.g. handles assets, resources, content providers, files, data URIs, http[s]
+ *   - E.g. Can be used to query for mime-type & content length.
+ *
+ * 2. To allow plugins to redirect URLs (via remapUrl).
+ *   - All plugins should call remapUrl() on URLs they receive from JS *before*
+ *     passing the URL onto other utility functions in this class.
+ *   - For an example usage of this, refer to the org.apache.cordova.file plugin.
+ *
+ * Future Work:
+ *   - Consider using a Cursor to query content URLs for their size (like the file plugin does).
+ *   - Allow plugins to remapUri to "cdv-plugin://plugin-name/foo", which CordovaResourceApi
+ *     would then delegate to pluginManager.getPlugin(plugin-name).openForRead(url)
+ *     - Currently, plugins *can* do this by remapping to a data: URL, but it's inefficient
+ *       for large payloads.
+ */
+public class CordovaResourceApi {
+    @SuppressWarnings("unused")
+    private static final String LOG_TAG = "CordovaResourceApi";
+
+    public static final int URI_TYPE_FILE = 0;
+    public static final int URI_TYPE_ASSET = 1;
+    public static final int URI_TYPE_CONTENT = 2;
+    public static final int URI_TYPE_RESOURCE = 3;
+    public static final int URI_TYPE_DATA = 4;
+    public static final int URI_TYPE_HTTP = 5;
+    public static final int URI_TYPE_HTTPS = 6;
+    public static final int URI_TYPE_PLUGIN = 7;
+    public static final int URI_TYPE_UNKNOWN = -1;
+
+    public static final String PLUGIN_URI_SCHEME = "cdvplugin";
+
+    private static final String[] LOCAL_FILE_PROJECTION = { "_data" };
+    
+    public static Thread jsThread;
+
+    private final AssetManager assetManager;
+    private final ContentResolver contentResolver;
+    private final PluginManager pluginManager;
+    private boolean threadCheckingEnabled = true;
+
+
+    public CordovaResourceApi(Context context, PluginManager pluginManager) {
+        this.contentResolver = context.getContentResolver();
+        this.assetManager = context.getAssets();
+        this.pluginManager = pluginManager;
+    }
+    
+    public void setThreadCheckingEnabled(boolean value) {
+        threadCheckingEnabled = value;
+    }
+
+    public boolean isThreadCheckingEnabled() {
+        return threadCheckingEnabled;
+    }
+    
+    
+    public static int getUriType(Uri uri) {
+        assertNonRelative(uri);
+        String scheme = uri.getScheme();
+        if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(scheme)) {
+            return URI_TYPE_CONTENT;
+        }
+        if (ContentResolver.SCHEME_ANDROID_RESOURCE.equalsIgnoreCase(scheme)) {
+            return URI_TYPE_RESOURCE;
+        }
+        if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(scheme)) {
+            if (uri.getPath().startsWith("/android_asset/")) {
+                return URI_TYPE_ASSET;
+            }
+            return URI_TYPE_FILE;
+        }
+        if ("data".equalsIgnoreCase(scheme)) {
+            return URI_TYPE_DATA;
+        }
+        if ("http".equalsIgnoreCase(scheme)) {
+            return URI_TYPE_HTTP;
+        }
+        if ("https".equalsIgnoreCase(scheme)) {
+            return URI_TYPE_HTTPS;
+        }
+        if (PLUGIN_URI_SCHEME.equalsIgnoreCase(scheme)) {
+            return URI_TYPE_PLUGIN;
+        }
+        return URI_TYPE_UNKNOWN;
+    }
+    
+    public Uri remapUri(Uri uri) {
+        assertNonRelative(uri);
+        Uri pluginUri = pluginManager.remapUri(uri);
+        return pluginUri != null ? pluginUri : uri;
+    }
+
+    public String remapPath(String path) {
+        return remapUri(Uri.fromFile(new File(path))).getPath();
+    }
+    
+    /**
+     * Returns a File that points to the resource, or null if the resource
+     * is not on the local filesystem.
+     */
+    public File mapUriToFile(Uri uri) {
+        assertBackgroundThread();
+        switch (getUriType(uri)) {
+            case URI_TYPE_FILE:
+                return new File(uri.getPath());
+            case URI_TYPE_CONTENT: {
+                Cursor cursor = contentResolver.query(uri, LOCAL_FILE_PROJECTION, null, null, null);
+                if (cursor != null) {
+                    try {
+                        int columnIndex = cursor.getColumnIndex(LOCAL_FILE_PROJECTION[0]);
+                        if (columnIndex != -1 && cursor.getCount() > 0) {
+                            cursor.moveToFirst();
+                            String realPath = cursor.getString(columnIndex);
+                            if (realPath != null) {
+                                return new File(realPath);
+                            }
+                        }
+                    } finally {
+                        cursor.close();
+                    }
+                }
+            }
+        }
+        return null;
+    }
+    
+    public String getMimeType(Uri uri) {
+        switch (getUriType(uri)) {
+            case URI_TYPE_FILE:
+            case URI_TYPE_ASSET:
+                return getMimeTypeFromPath(uri.getPath());
+            case URI_TYPE_CONTENT:
+            case URI_TYPE_RESOURCE:
+                return contentResolver.getType(uri);
+            case URI_TYPE_DATA: {
+                return getDataUriMimeType(uri);
+            }
+            case URI_TYPE_HTTP:
+            case URI_TYPE_HTTPS: {
+                try {
+                    HttpURLConnection conn = (HttpURLConnection)new URL(uri.toString()).openConnection();
+                    conn.setDoInput(false);
+                    conn.setRequestMethod("HEAD");
+                    String mimeType = conn.getHeaderField("Content-Type");
+                    if (mimeType != null) {
+                        mimeType = mimeType.split(";")[0];
+                    }
+                    return mimeType;
+                } catch (IOException e) {
+                }
+            }
+        }
+        
+        return null;
+    }
+    
+    
+    //This already exists
+    private String getMimeTypeFromPath(String path) {
+        String extension = path;
+        int lastDot = extension.lastIndexOf('.');
+        if (lastDot != -1) {
+            extension = extension.substring(lastDot + 1);
+        }
+        // Convert the URI string to lower case to ensure compatibility with MimeTypeMap (see CB-2185).
+        extension = extension.toLowerCase(Locale.getDefault());
+        if (extension.equals("3ga")) {
+            return "audio/3gpp";
+        } else if (extension.equals("js")) {
+            // Missing from the map :(.
+            return "text/javascript";
+        }
+        return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+    }
+    
+    /**
+     * Opens a stream to the given URI, also providing the MIME type & length.
+     * @return Never returns null.
+     * @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
+     *     resolved before being passed into this function.
+     * @throws Throws an IOException if the URI cannot be opened.
+     * @throws Throws an IllegalStateException if called on a foreground thread.
+     */
+    public OpenForReadResult openForRead(Uri uri) throws IOException {
+        return openForRead(uri, false);
+    }
+
+    /**
+     * Opens a stream to the given URI, also providing the MIME type & length.
+     * @return Never returns null.
+     * @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
+     *     resolved before being passed into this function.
+     * @throws Throws an IOException if the URI cannot be opened.
+     * @throws Throws an IllegalStateException if called on a foreground thread and skipThreadCheck is false.
+     */
+    public OpenForReadResult openForRead(Uri uri, boolean skipThreadCheck) throws IOException {
+        if (!skipThreadCheck) {
+            assertBackgroundThread();
+        }
+        switch (getUriType(uri)) {
+            case URI_TYPE_FILE: {
+                FileInputStream inputStream = new FileInputStream(uri.getPath());
+                String mimeType = getMimeTypeFromPath(uri.getPath());
+                long length = inputStream.getChannel().size();
+                return new OpenForReadResult(uri, inputStream, mimeType, length, null);
+            }
+            case URI_TYPE_ASSET: {
+                String assetPath = uri.getPath().substring(15);
+                AssetFileDescriptor assetFd = null;
+                InputStream inputStream;
+                long length = -1;
+                try {
+                    assetFd = assetManager.openFd(assetPath);
+                    inputStream = assetFd.createInputStream();
+                    length = assetFd.getLength();
+                } catch (FileNotFoundException e) {
+                    // Will occur if the file is compressed.
+                    inputStream = assetManager.open(assetPath);
+                }
+                String mimeType = getMimeTypeFromPath(assetPath);
+                return new OpenForReadResult(uri, inputStream, mimeType, length, assetFd);
+            }
+            case URI_TYPE_CONTENT:
+            case URI_TYPE_RESOURCE: {
+                String mimeType = contentResolver.getType(uri);
+                AssetFileDescriptor assetFd = contentResolver.openAssetFileDescriptor(uri, "r");
+                InputStream inputStream = assetFd.createInputStream();
+                long length = assetFd.getLength();
+                return new OpenForReadResult(uri, inputStream, mimeType, length, assetFd);
+            }
+            case URI_TYPE_DATA: {
+                OpenForReadResult ret = readDataUri(uri);
+                if (ret == null) {
+                    break;
+                }
+                return ret;
+            }
+            case URI_TYPE_HTTP:
+            case URI_TYPE_HTTPS: {
+                HttpURLConnection conn = (HttpURLConnection)new URL(uri.toString()).openConnection();
+                conn.setDoInput(true);
+                String mimeType = conn.getHeaderField("Content-Type");
+                if (mimeType != null) {
+                    mimeType = mimeType.split(";")[0];
+                }
+                int length = conn.getContentLength();
+                InputStream inputStream = conn.getInputStream();
+                return new OpenForReadResult(uri, inputStream, mimeType, length, null);
+            }
+            case URI_TYPE_PLUGIN: {
+                String pluginId = uri.getHost();
+                CordovaPlugin plugin = pluginManager.getPlugin(pluginId);
+                if (plugin == null) {
+                    throw new FileNotFoundException("Invalid plugin ID in URI: " + uri);
+                }
+                return plugin.handleOpenForRead(uri);
+            }
+        }
+        throw new FileNotFoundException("URI not supported by CordovaResourceApi: " + uri);
+    }
+
+    public OutputStream openOutputStream(Uri uri) throws IOException {
+        return openOutputStream(uri, false);
+    }
+
+    /**
+     * Opens a stream to the given URI.
+     * @return Never returns null.
+     * @throws Throws an InvalidArgumentException for relative URIs. Relative URIs should be
+     *     resolved before being passed into this function.
+     * @throws Throws an IOException if the URI cannot be opened.
+     */
+    public OutputStream openOutputStream(Uri uri, boolean append) throws IOException {
+        assertBackgroundThread();
+        switch (getUriType(uri)) {
+            case URI_TYPE_FILE: {
+                File localFile = new File(uri.getPath());
+                File parent = localFile.getParentFile();
+                if (parent != null) {
+                    parent.mkdirs();
+                }
+                return new FileOutputStream(localFile, append);
+            }
+            case URI_TYPE_CONTENT:
+            case URI_TYPE_RESOURCE: {
+                AssetFileDescriptor assetFd = contentResolver.openAssetFileDescriptor(uri, append ? "wa" : "w");
+                return assetFd.createOutputStream();
+            }
+        }
+        throw new FileNotFoundException("URI not supported by CordovaResourceApi: " + uri);
+    }
+
+    public HttpURLConnection createHttpConnection(Uri uri) throws IOException {
+        assertBackgroundThread();
+        return (HttpURLConnection)new URL(uri.toString()).openConnection();
+    }
+    
+    // Copies the input to the output in the most efficient manner possible.
+    // Closes both streams.
+    public void copyResource(OpenForReadResult input, OutputStream outputStream) throws IOException {
+        assertBackgroundThread();
+        try {
+            InputStream inputStream = input.inputStream;
+            if (inputStream instanceof FileInputStream && outputStream instanceof FileOutputStream) {
+                FileChannel inChannel = ((FileInputStream)input.inputStream).getChannel();
+                FileChannel outChannel = ((FileOutputStream)outputStream).getChannel();
+                long offset = 0;
+                long length = input.length;
+                if (input.assetFd != null) {
+                    offset = input.assetFd.getStartOffset();
+                }
+                // transferFrom()'s 2nd arg is a relative position. Need to set the absolute
+                // position first.
+                inChannel.position(offset);
+                outChannel.transferFrom(inChannel, 0, length);
+            } else {
+                final int BUFFER_SIZE = 8192;
+                byte[] buffer = new byte[BUFFER_SIZE];
+                
+                for (;;) {
+                    int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
+                    
+                    if (bytesRead <= 0) {
+                        break;
+                    }
+                    outputStream.write(buffer, 0, bytesRead);
+                }
+            }            
+        } finally {
+            input.inputStream.close();
+            if (outputStream != null) {
+                outputStream.close();
+            }
+        }
+    }
+
+    public void copyResource(Uri sourceUri, OutputStream outputStream) throws IOException {
+        copyResource(openForRead(sourceUri), outputStream);
+    }
+
+    // Added in 3.5.0.
+    public void copyResource(Uri sourceUri, Uri dstUri) throws IOException {
+        copyResource(openForRead(sourceUri), openOutputStream(dstUri));
+    }
+    
+    private void assertBackgroundThread() {
+        if (threadCheckingEnabled) {
+            Thread curThread = Thread.currentThread();
+            if (curThread == Looper.getMainLooper().getThread()) {
+                throw new IllegalStateException("Do not perform IO operations on the UI thread. Use CordovaInterface.getThreadPool() instead.");
+            }
+            if (curThread == jsThread) {
+                throw new IllegalStateException("Tried to perform an IO operation on the WebCore thread. Use CordovaInterface.getThreadPool() instead.");
+            }
+        }
+    }
+    
+    private String getDataUriMimeType(Uri uri) {
+        String uriAsString = uri.getSchemeSpecificPart();
+        int commaPos = uriAsString.indexOf(',');
+        if (commaPos == -1) {
+            return null;
+        }
+        String[] mimeParts = uriAsString.substring(0, commaPos).split(";");
+        if (mimeParts.length > 0) {
+            return mimeParts[0];
+        }
+        return null;
+    }
+
+    private OpenForReadResult readDataUri(Uri uri) {
+        String uriAsString = uri.getSchemeSpecificPart();
+        int commaPos = uriAsString.indexOf(',');
+        if (commaPos == -1) {
+            return null;
+        }
+        String[] mimeParts = uriAsString.substring(0, commaPos).split(";");
+        String contentType = null;
+        boolean base64 = false;
+        if (mimeParts.length > 0) {
+            contentType = mimeParts[0];
+        }
+        for (int i = 1; i < mimeParts.length; ++i) {
+            if ("base64".equalsIgnoreCase(mimeParts[i])) {
+                base64 = true;
+            }
+        }
+        String dataPartAsString = uriAsString.substring(commaPos + 1);
+        byte[] data;
+        if (base64) {
+            data = Base64.decode(dataPartAsString, Base64.DEFAULT);
+        } else {
+            try {
+                data = dataPartAsString.getBytes("UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                data = dataPartAsString.getBytes();
+            }
+        }
+        InputStream inputStream = new ByteArrayInputStream(data);
+        return new OpenForReadResult(uri, inputStream, contentType, data.length, null);
+    }
+    
+    private static void assertNonRelative(Uri uri) {
+        if (!uri.isAbsolute()) {
+            throw new IllegalArgumentException("Relative URIs are not supported.");
+        }
+    }
+    
+    public static final class OpenForReadResult {
+        public final Uri uri;
+        public final InputStream inputStream;
+        public final String mimeType;
+        public final long length;
+        public final AssetFileDescriptor assetFd;
+        
+        public OpenForReadResult(Uri uri, InputStream inputStream, String mimeType, long length, AssetFileDescriptor assetFd) {
+            this.uri = uri;
+            this.inputStream = inputStream;
+            this.mimeType = mimeType;
+            this.length = length;
+            this.assetFd = assetFd;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaWebView.java
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaWebView.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaWebView.java
new file mode 100644
index 0000000..62e53a0
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaWebView.java
@@ -0,0 +1,142 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+         http://www.apache.org/licenses/LICENSE-2.0
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+package org.apache.cordova;
+
+import java.util.List;
+import java.util.Map;
+
+import android.content.Context;
+import android.content.Intent;
+import android.view.View;
+import android.webkit.WebChromeClient.CustomViewCallback;
+
+/**
+ * Main interface for interacting with a Cordova webview - implemented by CordovaWebViewImpl.
+ * This is an interface so that it can be easily mocked in tests.
+ * Methods may be added to this interface without a major version bump, as plugins & embedders
+ * are not expected to implement it.
+ */
+public interface CordovaWebView {
+    public static final String CORDOVA_VERSION = "6.2.1";
+
+    void init(CordovaInterface cordova, List<PluginEntry> pluginEntries, CordovaPreferences preferences);
+
+    boolean isInitialized();
+
+    View getView();
+
+    void loadUrlIntoView(String url, boolean recreatePlugins);
+
+    void stopLoading();
+
+    boolean canGoBack();
+
+    void clearCache();
+
+    /** Use parameter-less overload */
+    @Deprecated
+    void clearCache(boolean b);
+
+    void clearHistory();
+
+    boolean backHistory();
+
+    void handlePause(boolean keepRunning);
+
+    void onNewIntent(Intent intent);
+
+    void handleResume(boolean keepRunning);
+
+    void handleStart();
+
+    void handleStop();
+
+    void handleDestroy();
+
+    /**
+     * Send JavaScript statement back to JavaScript.
+     *
+     * Deprecated (https://issues.apache.org/jira/browse/CB-6851)
+     * Instead of executing snippets of JS, you should use the exec bridge
+     * to create a Java->JS communication channel.
+     * To do this:
+     * 1. Within plugin.xml (to have your JS run before deviceready):
+     *    <js-module><runs/></js-module>
+     * 2. Within your .js (call exec on start-up):
+     *    require('cordova/channel').onCordovaReady.subscribe(function() {
+     *      require('cordova/exec')(win, null, 'Plugin', 'method', []);
+     *      function win(message) {
+     *        ... process message from java here ...
+     *      }
+     *    });
+     * 3. Within your .java:
+     *    PluginResult dataResult = new PluginResult(PluginResult.Status.OK, CODE);
+     *    dataResult.setKeepCallback(true);
+     *    savedCallbackContext.sendPluginResult(dataResult);
+     */
+    @Deprecated
+    void sendJavascript(String statememt);
+
+    /**
+     * Load the specified URL in the Cordova webview or a new browser instance.
+     *
+     * NOTE: If openExternal is false, only whitelisted URLs 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
+     */
+    void showWebPage(String url, boolean openExternal, boolean clearHistory, Map<String, Object> params);
+
+    /**
+     * Deprecated in 4.0.0. Use your own View-toggling logic.
+     */
+    @Deprecated
+    boolean isCustomViewShowing();
+
+    /**
+     * Deprecated in 4.0.0. Use your own View-toggling logic.
+     */
+    @Deprecated
+    void showCustomView(View view, CustomViewCallback callback);
+
+    /**
+     * Deprecated in 4.0.0. Use your own View-toggling logic.
+     */
+    @Deprecated
+    void hideCustomView();
+
+    CordovaResourceApi getResourceApi();
+
+    void setButtonPlumbedToJs(int keyCode, boolean override);
+    boolean isButtonPlumbedToJs(int keyCode);
+
+    void sendPluginResult(PluginResult cr, String callbackId);
+
+    PluginManager getPluginManager();
+    CordovaWebViewEngine getEngine();
+    CordovaPreferences getPreferences();
+    ICordovaCookieManager getCookieManager();
+
+    String getUrl();
+
+    // TODO: Work on deleting these by removing refs from plugins.
+    Context getContext();
+    void loadUrl(String url);
+    Object postMessage(String id, Object data);
+}

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/07bed560/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaWebViewEngine.java
----------------------------------------------------------------------
diff --git a/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaWebViewEngine.java b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaWebViewEngine.java
new file mode 100644
index 0000000..c8e5a55
--- /dev/null
+++ b/cordova-lib/spec-plugman/projects/android/CordovaLib/src/org/apache/cordova/CordovaWebViewEngine.java
@@ -0,0 +1,85 @@
+/*
+       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.view.KeyEvent;
+import android.view.View;
+import android.webkit.ValueCallback;
+
+/**
+ * Interface for all Cordova engines.
+ * No methods will be added to this class (in order to be compatible with existing engines).
+ * Instead, we will create a new interface: e.g. CordovaWebViewEngineV2
+ */
+public interface CordovaWebViewEngine {
+    void init(CordovaWebView parentWebView, CordovaInterface cordova, Client client,
+              CordovaResourceApi resourceApi, PluginManager pluginManager,
+              NativeToJsMessageQueue nativeToJsMessageQueue);
+
+    CordovaWebView getCordovaWebView();
+    ICordovaCookieManager getCookieManager();
+    View getView();
+
+    void loadUrl(String url, boolean clearNavigationStack);
+
+    void stopLoading();
+
+    /** Return the currently loaded URL */
+    String getUrl();
+
+    void clearCache();
+
+    /** After calling clearHistory(), canGoBack() should be false. */
+    void clearHistory();
+
+    boolean canGoBack();
+
+    /** Returns whether a navigation occurred */
+    boolean goBack();
+
+    /** Pauses / resumes the WebView's event loop. */
+    void setPaused(boolean value);
+
+    /** Clean up all resources associated with the WebView. */
+    void destroy();
+
+    /** Add the evaulate Javascript method **/
+    void evaluateJavascript(String js, ValueCallback<String> callback);
+
+    /**
+     * Used to retrieve the associated CordovaWebView given a View without knowing the type of Engine.
+     * E.g. ((CordovaWebView.EngineView)activity.findViewById(android.R.id.webView)).getCordovaWebView();
+     */
+    public interface EngineView {
+        CordovaWebView getCordovaWebView();
+    }
+
+    /**
+     * Contains methods that an engine uses to communicate with the parent CordovaWebView.
+     * Methods may be added in future cordova versions, but never removed.
+     */
+    public interface Client {
+        Boolean onDispatchKeyEvent(KeyEvent event);
+        void clearLoadTimeoutTimer();
+        void onPageStarted(String newUrl);
+        void onReceivedError(int errorCode, String description, String failingUrl);
+        void onPageFinishedLoading(String url);
+        boolean onNavigationAttempt(String url);
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org