cordova-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ste...@apache.org
Subject [08/53] Re-adding the plugins for a merge
Date Tue, 30 Apr 2013 21:35:24 GMT
http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e52404f2/framework/src/org/apache/cordova/core/HttpHandler.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/core/HttpHandler.java b/framework/src/org/apache/cordova/core/HttpHandler.java
new file mode 100755
index 0000000..daeb4aa
--- /dev/null
+++ b/framework/src/org/apache/cordova/core/HttpHandler.java
@@ -0,0 +1,86 @@
+/*
+       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.core;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+
+public class HttpHandler {
+
+    protected Boolean get(String url, String file)
+    {
+        HttpEntity entity = getHttpEntity(url);
+        try {
+            writeToDisk(entity, file);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+        try {
+            entity.consumeContent();
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+        return true;
+    }
+
+    private HttpEntity getHttpEntity(String url)
+    /**
+     * get the http entity at a given url
+     */
+    {
+        HttpEntity entity = null;
+        try {
+            DefaultHttpClient httpclient = new DefaultHttpClient();
+            HttpGet httpget = new HttpGet(url);
+            HttpResponse response = httpclient.execute(httpget);
+            entity = response.getEntity();
+        } catch (Exception e) { e.printStackTrace(); return null; }
+        return entity;
+    }
+
+    private void writeToDisk(HttpEntity entity, String file) throws IllegalStateException, IOException
+    /**
+     * writes a HTTP entity to the specified filename and location on disk
+     */
+    {
+        //int i = 0;
+        String FilePath = "/sdcard/" + file;
+        InputStream in = entity.getContent();
+        byte buff[] = new byte[1024];
+        FileOutputStream out =
+                new FileOutputStream(FilePath);
+       do {
+            int numread = in.read(buff);
+            if (numread <= 0)
+                break;
+            out.write(buff, 0, numread);
+            //i++;
+        } while (true);
+        out.flush();
+        out.close();
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e52404f2/framework/src/org/apache/cordova/core/InAppBrowser.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/core/InAppBrowser.java b/framework/src/org/apache/cordova/core/InAppBrowser.java
new file mode 100644
index 0000000..6b7193f
--- /dev/null
+++ b/framework/src/org/apache/cordova/core/InAppBrowser.java
@@ -0,0 +1,668 @@
+/*
+       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.core;
+
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+import org.apache.cordova.Config;
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.api.CallbackContext;
+import org.apache.cordova.api.CordovaPlugin;
+import org.apache.cordova.api.LOG;
+import org.apache.cordova.api.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.annotation.SuppressLint;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.InputType;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.webkit.WebChromeClient;
+import android.webkit.GeolocationPermissions.Callback;
+import android.webkit.WebSettings;
+import android.webkit.WebStorage;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+
+@SuppressLint("SetJavaScriptEnabled")
+public class InAppBrowser extends CordovaPlugin {
+
+    private static final String NULL = "null";
+    protected static final String LOG_TAG = "InAppBrowser";
+    private static final String SELF = "_self";
+    private static final String SYSTEM = "_system";
+    // private static final String BLANK = "_blank";
+    private static final String LOCATION = "location";
+    private static final String EXIT_EVENT = "exit";
+    private static final String LOAD_START_EVENT = "loadstart";
+    private static final String LOAD_STOP_EVENT = "loadstop";
+    private static final String CLOSE_BUTTON_CAPTION = "closebuttoncaption";
+    private long MAX_QUOTA = 100 * 1024 * 1024;
+
+    private Dialog dialog;
+    private WebView inAppWebView;
+    private EditText edittext;
+    private boolean showLocationBar = true;
+    private CallbackContext callbackContext;
+    private String buttonLabel = "Done";
+    
+    /**
+     * Executes the request and returns PluginResult.
+     *
+     * @param action        The action to execute.
+     * @param args          JSONArry of arguments for the plugin.
+     * @param callbackId    The callback id used when calling back into JavaScript.
+     * @return              A PluginResult object with a status and message.
+     */
+    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+        PluginResult.Status status = PluginResult.Status.OK;
+        String result = "";
+        this.callbackContext = callbackContext;
+        
+        try {
+            if (action.equals("open")) {
+                String url = args.getString(0);
+                String target = args.optString(1);
+                if (target == null || target.equals("") || target.equals(NULL)) {
+                    target = SELF;
+                }
+                HashMap<String, Boolean> features = parseFeature(args.optString(2));
+                
+                Log.d(LOG_TAG, "target = " + target);
+
+                url = updateUrl(url);
+
+                // SELF
+                if (SELF.equals(target)) {
+                    Log.d(LOG_TAG, "in self");
+                    // load in webview
+                    if (url.startsWith("file://") || url.startsWith("javascript:") 
+                            || Config.isUrlWhiteListed(url)) {
+                        this.webView.loadUrl(url);
+                    }
+                    //Load the dialer
+                    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(LOG_TAG, "Error dialing " + url + ": " + e.toString());
+                        }
+                    }
+                    // load in InAppBrowser
+                    else {
+                        result = this.showWebPage(url, features);
+                    }
+                }
+                // SYSTEM
+                else if (SYSTEM.equals(target)) {
+                    Log.d(LOG_TAG, "in system");
+                    result = this.openExternal(url);
+                }
+                // BLANK - or anything else
+                else {
+                    Log.d(LOG_TAG, "in blank");
+                    result = this.showWebPage(url, features);
+                }
+            }
+            else if (action.equals("close")) {
+                closeDialog();
+
+                PluginResult pluginResult = new PluginResult(PluginResult.Status.OK);
+                pluginResult.setKeepCallback(false);
+                this.callbackContext.sendPluginResult(pluginResult);
+            }
+            else {
+                status = PluginResult.Status.INVALID_ACTION;
+            }
+            PluginResult pluginResult = new PluginResult(status, result);
+            pluginResult.setKeepCallback(true);
+            this.callbackContext.sendPluginResult(pluginResult);
+        } catch (JSONException e) {
+            this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+        }
+        return true;
+    }
+
+    /**
+     * Put the list of features into a hash map
+     * 
+     * @param optString
+     * @return
+     */
+    private HashMap<String, Boolean> parseFeature(String optString) {
+        if (optString.equals(NULL)) {
+            return null;
+        } else {
+            HashMap<String, Boolean> map = new HashMap<String, Boolean>();
+            StringTokenizer features = new StringTokenizer(optString, ",");
+            StringTokenizer option;
+            while(features.hasMoreElements()) {
+                option = new StringTokenizer(features.nextToken(), "=");
+                if (option.hasMoreElements()) {
+                    String key = option.nextToken();
+                    if (key.equalsIgnoreCase(CLOSE_BUTTON_CAPTION)) {
+                        this.buttonLabel = option.nextToken();
+                    } else {
+                        Boolean value = option.nextToken().equals("no") ? Boolean.FALSE : Boolean.TRUE;
+                        map.put(key, value);
+                    }
+                }
+            }
+            return map;
+        }
+    }
+
+    /**
+     * Convert relative URL to full path
+     * 
+     * @param url
+     * @return 
+     */
+    private String updateUrl(String url) {
+        Uri newUrl = Uri.parse(url);
+        if (newUrl.isRelative()) {
+            url = this.webView.getUrl().substring(0, this.webView.getUrl().lastIndexOf("/")+1) + url;
+        }
+        return url;
+    }
+
+    /**
+     * Display a new browser with the specified URL.
+     *
+     * @param url           The url to load.
+     * @param usePhoneGap   Load url in PhoneGap webview
+     * @return              "" if ok, or error message.
+     */
+    public String openExternal(String url) {
+        try {
+            Intent intent = null;
+            intent = new Intent(Intent.ACTION_VIEW);
+            intent.setData(Uri.parse(url));
+            this.cordova.getActivity().startActivity(intent);
+            return "";
+        } catch (android.content.ActivityNotFoundException e) {
+            Log.d(LOG_TAG, "InAppBrowser: Error loading url "+url+":"+ e.toString());
+            return e.toString();
+        }
+    }
+
+    /**
+     * Closes the dialog
+     */
+    private void closeDialog() {
+        try {
+            JSONObject obj = new JSONObject();
+            obj.put("type", EXIT_EVENT);
+
+            sendUpdate(obj, false);
+        } catch (JSONException ex) {
+            Log.d(LOG_TAG, "Should never happen");
+        }
+        
+        if (dialog != null) {
+            dialog.dismiss();
+        }
+    }
+
+    /**
+     * Checks to see if it is possible to go back one page in history, then does so.
+     */
+    private void goBack() {
+        if (this.inAppWebView.canGoBack()) {
+            this.inAppWebView.goBack();
+        }
+    }
+
+    /**
+     * Checks to see if it is possible to go forward one page in history, then does so.
+     */
+    private void goForward() {
+        if (this.inAppWebView.canGoForward()) {
+            this.inAppWebView.goForward();
+        }
+    }
+
+    /**
+     * Navigate to the new page
+     *
+     * @param url to load
+     */
+    private void navigate(String url) {
+        InputMethodManager imm = (InputMethodManager)this.cordova.getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+        imm.hideSoftInputFromWindow(edittext.getWindowToken(), 0);
+
+        if (!url.startsWith("http") && !url.startsWith("file:")) {
+            this.inAppWebView.loadUrl("http://" + url);
+        } else {
+            this.inAppWebView.loadUrl(url);
+        }
+        this.inAppWebView.requestFocus();
+    }
+
+
+    /**
+     * Should we show the location bar?
+     *
+     * @return boolean
+     */
+    private boolean getShowLocationBar() {
+        return this.showLocationBar;
+    }
+
+    /**
+     * Display a new browser with the specified URL.
+     *
+     * @param url           The url to load.
+     * @param jsonObject
+     */
+    public String showWebPage(final String url, HashMap<String, Boolean> features) {
+        // Determine if we should hide the location bar.
+        showLocationBar = true;
+        if (features != null) {
+            Boolean show = features.get(LOCATION);
+            if (show != null) {
+                showLocationBar = show.booleanValue();
+            }
+        }
+        
+        final CordovaWebView thatWebView = this.webView;
+
+        // Create dialog in new thread
+        Runnable runnable = new Runnable() {
+            /**
+             * Convert our DIP units to Pixels
+             *
+             * @return int
+             */
+            private int dpToPixels(int dipValue) {
+                int value = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP,
+                                                            (float) dipValue,
+                                                            cordova.getActivity().getResources().getDisplayMetrics()
+                );
+
+                return value;
+            }
+
+            public void run() {
+                // Let's create the main dialog
+                dialog = new Dialog(cordova.getActivity(), android.R.style.Theme_NoTitleBar);
+                dialog.getWindow().getAttributes().windowAnimations = android.R.style.Animation_Dialog;
+                dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+                dialog.setCancelable(true);
+                dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
+                        public void onDismiss(DialogInterface dialog) {
+                            try {
+                                JSONObject obj = new JSONObject();
+                                obj.put("type", EXIT_EVENT);
+
+                                sendUpdate(obj, false);
+                            } catch (JSONException e) {
+                                Log.d(LOG_TAG, "Should never happen");
+                            }
+                        }
+                });
+
+                // Main container layout
+                LinearLayout main = new LinearLayout(cordova.getActivity());
+                main.setOrientation(LinearLayout.VERTICAL);
+
+                // Toolbar layout
+                RelativeLayout toolbar = new RelativeLayout(cordova.getActivity());
+                toolbar.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, this.dpToPixels(44)));
+                toolbar.setPadding(this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2), this.dpToPixels(2));
+                toolbar.setHorizontalGravity(Gravity.LEFT);
+                toolbar.setVerticalGravity(Gravity.TOP);
+
+                // Action Button Container layout
+                RelativeLayout actionButtonContainer = new RelativeLayout(cordova.getActivity());
+                actionButtonContainer.setLayoutParams(new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+                actionButtonContainer.setHorizontalGravity(Gravity.LEFT);
+                actionButtonContainer.setVerticalGravity(Gravity.CENTER_VERTICAL);
+                actionButtonContainer.setId(1);
+
+                // Back button
+                Button back = new Button(cordova.getActivity());
+                RelativeLayout.LayoutParams backLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+                backLayoutParams.addRule(RelativeLayout.ALIGN_LEFT);
+                back.setLayoutParams(backLayoutParams);
+                back.setContentDescription("Back Button");
+                back.setId(2);
+                back.setText("<");
+                back.setOnClickListener(new View.OnClickListener() {
+                    public void onClick(View v) {
+                        goBack();
+                    }
+                });
+
+                // Forward button
+                Button forward = new Button(cordova.getActivity());
+                RelativeLayout.LayoutParams forwardLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+                forwardLayoutParams.addRule(RelativeLayout.RIGHT_OF, 2);
+                forward.setLayoutParams(forwardLayoutParams);
+                forward.setContentDescription("Forward Button");
+                forward.setId(3);
+                forward.setText(">");
+                forward.setOnClickListener(new View.OnClickListener() {
+                    public void onClick(View v) {
+                        goForward();
+                    }
+                });
+
+                // Edit Text Box
+                edittext = new EditText(cordova.getActivity());
+                RelativeLayout.LayoutParams textLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+                textLayoutParams.addRule(RelativeLayout.RIGHT_OF, 1);
+                textLayoutParams.addRule(RelativeLayout.LEFT_OF, 5);
+                edittext.setLayoutParams(textLayoutParams);
+                edittext.setId(4);
+                edittext.setSingleLine(true);
+                edittext.setText(url);
+                edittext.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
+                edittext.setImeOptions(EditorInfo.IME_ACTION_GO);
+                edittext.setInputType(InputType.TYPE_NULL); // Will not except input... Makes the text NON-EDITABLE
+                edittext.setOnKeyListener(new View.OnKeyListener() {
+                    public boolean onKey(View v, int keyCode, KeyEvent event) {
+                        // If the event is a key-down event on the "enter" button
+                        if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
+                          navigate(edittext.getText().toString());
+                          return true;
+                        }
+                        return false;
+                    }
+                });
+
+                // Close button
+                Button close = new Button(cordova.getActivity());
+                RelativeLayout.LayoutParams closeLayoutParams = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+                closeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
+                close.setLayoutParams(closeLayoutParams);
+                forward.setContentDescription("Close Button");
+                close.setId(5);
+                close.setText(buttonLabel);
+                close.setOnClickListener(new View.OnClickListener() {
+                    public void onClick(View v) {
+                        closeDialog();
+                    }
+                });
+
+                // WebView
+                inAppWebView = new WebView(cordova.getActivity());
+                inAppWebView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+                inAppWebView.setWebChromeClient(new InAppChromeClient());
+                WebViewClient client = new InAppBrowserClient(thatWebView, edittext);
+                inAppWebView.setWebViewClient(client);
+                WebSettings settings = inAppWebView.getSettings();
+                settings.setJavaScriptEnabled(true);
+                settings.setJavaScriptCanOpenWindowsAutomatically(true);
+                settings.setBuiltInZoomControls(true);
+                /** 
+                 * We need to be careful of this line as a future Android release may deprecate it out of existence.
+                 * Can't replace it with the API 8 level call right now as our minimum SDK is 7 until May 2013
+                 */
+                // @TODO: replace with settings.setPluginState(android.webkit.WebSettings.PluginState.ON)
+                settings.setPluginsEnabled(true);
+                
+                //Toggle whether this is enabled or not!
+                Bundle appSettings = cordova.getActivity().getIntent().getExtras();
+                boolean enableDatabase = appSettings.getBoolean("InAppBrowserStorageEnabled", true);
+                if(enableDatabase)
+                {
+                    String databasePath = cordova.getActivity().getApplicationContext().getDir("inAppBrowserDB", Context.MODE_PRIVATE).getPath();
+                    settings.setDatabasePath(databasePath);
+                    settings.setDatabaseEnabled(true);
+                }
+                settings.setDomStorageEnabled(true);
+               
+                inAppWebView.loadUrl(url);
+                inAppWebView.setId(6);
+                inAppWebView.getSettings().setLoadWithOverviewMode(true);
+                inAppWebView.getSettings().setUseWideViewPort(true);
+                inAppWebView.requestFocus();
+                inAppWebView.requestFocusFromTouch();
+
+                // Add the back and forward buttons to our action button container layout
+                actionButtonContainer.addView(back);
+                actionButtonContainer.addView(forward);
+
+                // Add the views to our toolbar
+                toolbar.addView(actionButtonContainer);
+                toolbar.addView(edittext);
+                toolbar.addView(close);
+
+                // Don't add the toolbar if its been disabled
+                if (getShowLocationBar()) {
+                    // Add our toolbar to our main view/layout
+                    main.addView(toolbar);
+                }
+
+                // Add our webview to our main view/layout
+                main.addView(inAppWebView);
+
+                WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
+                lp.copyFrom(dialog.getWindow().getAttributes());
+                lp.width = WindowManager.LayoutParams.MATCH_PARENT;
+                lp.height = WindowManager.LayoutParams.MATCH_PARENT;
+
+                dialog.setContentView(main);
+                dialog.show();
+                dialog.getWindow().setAttributes(lp);
+            }
+        };
+        this.cordova.getActivity().runOnUiThread(runnable);
+        return "";
+    }
+
+    /**
+     * Create a new plugin result and send it back to JavaScript
+     *
+     * @param obj a JSONObject contain event payload information
+     */
+    private void sendUpdate(JSONObject obj, boolean keepCallback) {
+        PluginResult result = new PluginResult(PluginResult.Status.OK, obj);
+        result.setKeepCallback(keepCallback);
+        this.callbackContext.sendPluginResult(result);
+    }
+
+    public class InAppChromeClient extends WebChromeClient {
+
+        /**
+         * Handle database quota exceeded notification.
+         *
+         * @param url
+         * @param databaseIdentifier
+         * @param currentQuota
+         * @param estimatedSize
+         * @param totalUsedQuota
+         * @param quotaUpdater
+         */
+        @Override
+        public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize,
+                long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater)
+        {
+            LOG.d(LOG_TAG, "onExceededDatabaseQuota estimatedSize: %d  currentQuota: %d  totalUsedQuota: %d", estimatedSize, currentQuota, totalUsedQuota);
+
+            if (estimatedSize < MAX_QUOTA)
+            {
+                //increase for 1Mb
+                long newQuota = estimatedSize;
+                LOG.d(LOG_TAG, "calling quotaUpdater.updateQuota newQuota: %d", newQuota);
+                quotaUpdater.updateQuota(newQuota);
+            }
+            else
+            {
+                // Set the quota to whatever it is and force an error
+                // TODO: get docs on how to handle this properly
+                quotaUpdater.updateQuota(currentQuota);
+            }
+        }
+
+        /**
+         * Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin.
+         *
+         * @param origin
+         * @param callback
+         */
+        @Override
+        public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) {
+            super.onGeolocationPermissionsShowPrompt(origin, callback);
+            callback.invoke(origin, true, false);
+        }
+    }
+    
+    /**
+     * The webview client receives notifications about appView
+     */
+    public class InAppBrowserClient extends WebViewClient {
+        EditText edittext;
+        CordovaWebView webView;
+
+        /**
+         * Constructor.
+         *
+         * @param mContext
+         * @param edittext
+         */
+        public InAppBrowserClient(CordovaWebView webView, EditText mEditText) {
+            this.webView = webView;
+            this.edittext = mEditText;
+        }
+
+        /**
+         * Notify the host application that a page has started loading.
+         *
+         * @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);
+            String newloc = "";
+            if (url.startsWith("http:") || url.startsWith("https:") || url.startsWith("file:")) {
+                newloc = 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));
+                    cordova.getActivity().startActivity(intent);
+                } catch (android.content.ActivityNotFoundException e) {
+                    LOG.e(LOG_TAG, "Error dialing " + url + ": " + e.toString());
+                }
+            }
+
+            else if (url.startsWith("geo:") || url.startsWith(WebView.SCHEME_MAILTO) || url.startsWith("market:")) {
+                try {
+                    Intent intent = new Intent(Intent.ACTION_VIEW);
+                    intent.setData(Uri.parse(url));
+                    cordova.getActivity().startActivity(intent);
+                } catch (android.content.ActivityNotFoundException e) {
+                    LOG.e(LOG_TAG, "Error with " + url + ": " + e.toString());
+                }
+            }
+            // If sms:5551212?body=This is the message
+            else if (url.startsWith("sms:")) {
+                try {
+                    Intent intent = new Intent(Intent.ACTION_VIEW);
+
+                    // Get address
+                    String address = null;
+                    int parmIndex = url.indexOf('?');
+                    if (parmIndex == -1) {
+                        address = url.substring(4);
+                    }
+                    else {
+                        address = url.substring(4, parmIndex);
+
+                        // If body, then set sms body
+                        Uri uri = Uri.parse(url);
+                        String query = uri.getQuery();
+                        if (query != null) {
+                            if (query.startsWith("body=")) {
+                                intent.putExtra("sms_body", query.substring(5));
+                            }
+                        }
+                    }
+                    intent.setData(Uri.parse("sms:" + address));
+                    intent.putExtra("address", address);
+                    intent.setType("vnd.android-dir/mms-sms");
+                    cordova.getActivity().startActivity(intent);
+                } catch (android.content.ActivityNotFoundException e) {
+                    LOG.e(LOG_TAG, "Error sending sms " + url + ":" + e.toString());
+                }
+            }
+            else {
+                newloc = "http://" + url;
+            }
+
+            if (!newloc.equals(edittext.getText().toString())) {
+                edittext.setText(newloc);
+            }
+
+            try {
+                JSONObject obj = new JSONObject();
+                obj.put("type", LOAD_START_EVENT);
+                obj.put("url", newloc);
+    
+                sendUpdate(obj, true);
+            } catch (JSONException ex) {
+                Log.d(LOG_TAG, "Should never happen");
+            }
+        }
+        
+        public void onPageFinished(WebView view, String url) {
+            super.onPageFinished(view, url);
+            
+            try {
+                JSONObject obj = new JSONObject();
+                obj.put("type", LOAD_STOP_EVENT);
+                obj.put("url", url);
+    
+                sendUpdate(obj, true);
+            } catch (JSONException ex) {
+                Log.d(LOG_TAG, "Should never happen");
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e52404f2/framework/src/org/apache/cordova/core/NativeToJsMessageQueue.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/core/NativeToJsMessageQueue.java b/framework/src/org/apache/cordova/core/NativeToJsMessageQueue.java
new file mode 100755
index 0000000..4ed2c82
--- /dev/null
+++ b/framework/src/org/apache/cordova/core/NativeToJsMessageQueue.java
@@ -0,0 +1,485 @@
+/*
+       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.core;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.api.CordovaInterface;
+import org.apache.cordova.api.PluginResult;
+
+import android.os.Message;
+import android.util.Log;
+import android.webkit.WebView;
+
+/**
+ * Holds the list of messages to be sent to the WebView.
+ */
+public class NativeToJsMessageQueue {
+    private static final String LOG_TAG = "JsMessageQueue";
+
+    // This must match the default value in incubator-cordova-js/lib/android/exec.js
+    private static final int DEFAULT_BRIDGE_MODE = 2;
+    
+    // Set this to true to force plugin results to be encoding as
+    // JS instead of the custom format (useful for benchmarking).
+    private static final boolean FORCE_ENCODE_USING_EVAL = false;
+
+    // Disable URL-based exec() bridge by default since it's a bit of a
+    // security concern.
+    static final boolean ENABLE_LOCATION_CHANGE_EXEC_MODE = false;
+        
+    // Disable sending back native->JS messages during an exec() when the active
+    // exec() is asynchronous. Set this to true when running bridge benchmarks.
+    static final boolean DISABLE_EXEC_CHAINING = false;
+    
+    // Upper limit for how much data to send to JS in one shot.
+    // TODO(agrieve): This is currently disable. It should be re-enabled once we
+    // remove support for returning values from exec() calls. This was
+    // deprecated in 2.2.0.
+    // Also, this currently only chops up on message boundaries. It may be useful
+    // to allow it to break up messages.
+    private static int MAX_PAYLOAD_SIZE = -1; //50 * 1024 * 10240;
+    
+    /**
+     * The index into registeredListeners to treat as active. 
+     */
+    private int activeListenerIndex;
+    
+    /**
+     * When true, the active listener is not fired upon enqueue. When set to false,
+     * the active listener will be fired if the queue is non-empty. 
+     */
+    private boolean paused;
+    
+    /**
+     * The list of JavaScript statements to be sent to JavaScript.
+     */
+    private final LinkedList<JsMessage> queue = new LinkedList<JsMessage>();
+
+    /**
+     * The array of listeners that can be used to send messages to JS.
+     */
+    private final BridgeMode[] registeredListeners;    
+    
+    private final CordovaInterface cordova;
+    private final CordovaWebView webView;
+
+    public NativeToJsMessageQueue(CordovaWebView webView, CordovaInterface cordova) {
+        this.cordova = cordova;
+        this.webView = webView;
+        registeredListeners = new BridgeMode[4];
+        registeredListeners[0] = null;  // Polling. Requires no logic.
+        registeredListeners[1] = new LoadUrlBridgeMode();
+        registeredListeners[2] = new OnlineEventsBridgeMode();
+        registeredListeners[3] = new PrivateApiBridgeMode();
+        reset();
+    }
+    
+    /**
+     * Changes the bridge mode.
+     */
+    public void setBridgeMode(int value) {
+        if (value < 0 || value >= registeredListeners.length) {
+            Log.d(LOG_TAG, "Invalid NativeToJsBridgeMode: " + value);
+        } else {
+            if (value != activeListenerIndex) {
+                Log.d(LOG_TAG, "Set native->JS mode to " + value);
+                synchronized (this) {
+                    activeListenerIndex = value;
+                    BridgeMode activeListener = registeredListeners[value];
+                    if (!paused && !queue.isEmpty() && activeListener != null) {
+                        activeListener.onNativeToJsMessageAvailable();
+                    }
+                }
+            }
+        }
+    }
+    
+    /**
+     * Clears all messages and resets to the default bridge mode.
+     */
+    public void reset() {
+        synchronized (this) {
+            queue.clear();
+            setBridgeMode(DEFAULT_BRIDGE_MODE);
+        }
+    }
+
+    private int calculatePackedMessageLength(JsMessage message) {
+        int messageLen = message.calculateEncodedLength();
+        String messageLenStr = String.valueOf(messageLen);
+        return messageLenStr.length() + messageLen + 1;        
+    }
+    
+    private void packMessage(JsMessage message, StringBuilder sb) {
+        int len = message.calculateEncodedLength();
+        sb.append(len)
+          .append(' ');
+        message.encodeAsMessage(sb);
+    }
+    
+    /**
+     * Combines and returns queued messages combined into a single string.
+     * Combines as many messages as possible, while staying under MAX_PAYLOAD_SIZE.
+     * Returns null if the queue is empty.
+     */
+    public String popAndEncode() {
+        synchronized (this) {
+            if (queue.isEmpty()) {
+                return null;
+            }
+            int totalPayloadLen = 0;
+            int numMessagesToSend = 0;
+            for (JsMessage message : queue) {
+                int messageSize = calculatePackedMessageLength(message);
+                if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE && MAX_PAYLOAD_SIZE > 0) {
+                    break;
+                }
+                totalPayloadLen += messageSize;
+                numMessagesToSend += 1;
+            }
+
+            StringBuilder sb = new StringBuilder(totalPayloadLen);
+            for (int i = 0; i < numMessagesToSend; ++i) {
+                JsMessage message = queue.removeFirst();
+                packMessage(message, sb);
+            }
+            
+            if (!queue.isEmpty()) {
+                // Attach a char to indicate that there are more messages pending.
+                sb.append('*');
+            }
+            String ret = sb.toString();
+            return ret;
+        }
+    }
+    
+    /**
+     * Same as popAndEncode(), except encodes in a form that can be executed as JS.
+     */
+    private String popAndEncodeAsJs() {
+        synchronized (this) {
+            int length = queue.size();
+            if (length == 0) {
+                return null;
+            }
+            int totalPayloadLen = 0;
+            int numMessagesToSend = 0;
+            for (JsMessage message : queue) {
+                int messageSize = message.calculateEncodedLength() + 50; // overestimate.
+                if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE && MAX_PAYLOAD_SIZE > 0) {
+                    break;
+                }
+                totalPayloadLen += messageSize;
+                numMessagesToSend += 1;
+            }
+            boolean willSendAllMessages = numMessagesToSend == queue.size();
+            StringBuilder sb = new StringBuilder(totalPayloadLen + (willSendAllMessages ? 0 : 100));
+            // Wrap each statement in a try/finally so that if one throws it does 
+            // not affect the next.
+            for (int i = 0; i < numMessagesToSend; ++i) {
+                JsMessage message = queue.removeFirst();
+                if (willSendAllMessages && (i + 1 == numMessagesToSend)) {
+                    message.encodeAsJsMessage(sb);
+                } else {
+                    sb.append("try{");
+                    message.encodeAsJsMessage(sb);
+                    sb.append("}finally{");
+                }
+            }
+            if (!willSendAllMessages) {
+                sb.append("window.setTimeout(function(){cordova.require('cordova/plugin/android/polling').pollOnce();},0);");
+            }
+            for (int i = willSendAllMessages ? 1 : 0; i < numMessagesToSend; ++i) {
+                sb.append('}');
+            }
+            String ret = sb.toString();
+            return ret;
+        }
+    }   
+
+    /**
+     * Add a JavaScript statement to the list.
+     */
+    public void addJavaScript(String statement) {
+        enqueueMessage(new JsMessage(statement));
+    }
+
+    /**
+     * Add a JavaScript statement to the list.
+     */
+    public void addPluginResult(PluginResult result, String callbackId) {
+        if (callbackId == null) {
+            Log.e(LOG_TAG, "Got plugin result with no callbackId", new Throwable());
+            return;
+        }
+        // Don't send anything if there is no result and there is no need to
+        // clear the callbacks.
+        boolean noResult = result.getStatus() == PluginResult.Status.NO_RESULT.ordinal();
+        boolean keepCallback = result.getKeepCallback();
+        if (noResult && keepCallback) {
+            return;
+        }
+        JsMessage message = new JsMessage(result, callbackId);
+        if (FORCE_ENCODE_USING_EVAL) {
+            StringBuilder sb = new StringBuilder(message.calculateEncodedLength() + 50);
+            message.encodeAsJsMessage(sb);
+            message = new JsMessage(sb.toString());
+        }
+
+        enqueueMessage(message);
+    }
+    
+    private void enqueueMessage(JsMessage message) {
+        synchronized (this) {
+            queue.add(message);
+            if (!paused && registeredListeners[activeListenerIndex] != null) {
+                registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable();
+            }
+        }        
+    }
+    
+    public void setPaused(boolean value) {
+        if (paused && value) {
+            // This should never happen. If a use-case for it comes up, we should
+            // change pause to be a counter.
+            Log.e(LOG_TAG, "nested call to setPaused detected.", new Throwable());
+        }
+        paused = value;
+        if (!value) {
+            synchronized (this) {
+                if (!queue.isEmpty() && registeredListeners[activeListenerIndex] != null) {
+                    registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable();
+                }
+            }   
+        }
+    }
+    
+    public boolean getPaused() {
+        return paused;
+    }
+
+    private interface BridgeMode {
+        void onNativeToJsMessageAvailable();
+    }
+    
+    /** Uses webView.loadUrl("javascript:") to execute messages. */
+    private class LoadUrlBridgeMode implements BridgeMode {
+        final Runnable runnable = new Runnable() {
+            public void run() {
+                String js = popAndEncodeAsJs();
+                if (js != null) {
+                    webView.loadUrlNow("javascript:" + js);
+                }
+            }
+        };
+        
+        public void onNativeToJsMessageAvailable() {
+            cordova.getActivity().runOnUiThread(runnable);
+        }
+    }
+
+    /** Uses online/offline events to tell the JS when to poll for messages. */
+    private class OnlineEventsBridgeMode implements BridgeMode {
+        boolean online = true;
+        final Runnable runnable = new Runnable() {
+            public void run() {
+                if (!queue.isEmpty()) {
+                    online = !online;
+                    webView.setNetworkAvailable(online);
+                }
+            }                
+        };
+        OnlineEventsBridgeMode() {
+            webView.setNetworkAvailable(true);
+        }
+        public void onNativeToJsMessageAvailable() {
+            cordova.getActivity().runOnUiThread(runnable);
+        }
+    }
+    
+    /**
+     * Uses Java reflection to access an API that lets us eval JS.
+     * Requires Android 3.2.4 or above. 
+     */
+    private class PrivateApiBridgeMode implements BridgeMode {
+    	// Message added in commit:
+    	// http://omapzoom.org/?p=platform/frameworks/base.git;a=commitdiff;h=9497c5f8c4bc7c47789e5ccde01179abc31ffeb2
+    	// Which first appeared in 3.2.4ish.
+    	private static final int EXECUTE_JS = 194;
+    	
+    	Method sendMessageMethod;
+    	Object webViewCore;
+    	boolean initFailed;
+
+    	@SuppressWarnings("rawtypes")
+    	private void initReflection() {
+        	Object webViewObject = webView;
+    		Class webViewClass = WebView.class;
+        	try {
+    			Field f = webViewClass.getDeclaredField("mProvider");
+    			f.setAccessible(true);
+    			webViewObject = f.get(webView);
+    			webViewClass = webViewObject.getClass();
+        	} catch (Throwable e) {
+        		// mProvider is only required on newer Android releases.
+    		}
+        	
+        	try {
+    			Field f = webViewClass.getDeclaredField("mWebViewCore");
+                f.setAccessible(true);
+    			webViewCore = f.get(webViewObject);
+    			
+    			if (webViewCore != null) {
+    				sendMessageMethod = webViewCore.getClass().getDeclaredMethod("sendMessage", Message.class);
+	    			sendMessageMethod.setAccessible(true);	    			
+    			}
+    		} catch (Throwable e) {
+    			initFailed = true;
+				Log.e(LOG_TAG, "PrivateApiBridgeMode failed to find the expected APIs.", e);
+    		}
+    	}
+    	
+        public void onNativeToJsMessageAvailable() {
+        	if (sendMessageMethod == null && !initFailed) {
+        		initReflection();
+        	}
+        	// webViewCore is lazily initialized, and so may not be available right away.
+        	if (sendMessageMethod != null) {
+	        	String js = popAndEncodeAsJs();
+	        	Message execJsMessage = Message.obtain(null, EXECUTE_JS, js);
+				try {
+				    sendMessageMethod.invoke(webViewCore, execJsMessage);
+				} catch (Throwable e) {
+					Log.e(LOG_TAG, "Reflection message bridge failed.", e);
+				}
+        	}
+        }
+    }    
+    private static class JsMessage {
+        final String jsPayloadOrCallbackId;
+        final PluginResult pluginResult;
+        JsMessage(String js) {
+            if (js == null) {
+                throw new NullPointerException();
+            }
+            jsPayloadOrCallbackId = js;
+            pluginResult = null;
+        }
+        JsMessage(PluginResult pluginResult, String callbackId) {
+            if (callbackId == null || pluginResult == null) {
+                throw new NullPointerException();
+            }
+            jsPayloadOrCallbackId = callbackId;
+            this.pluginResult = pluginResult;
+        }
+        
+        int calculateEncodedLength() {
+            if (pluginResult == null) {
+                return jsPayloadOrCallbackId.length() + 1;
+            }
+            int statusLen = String.valueOf(pluginResult.getStatus()).length();
+            int ret = 2 + statusLen + 1 + jsPayloadOrCallbackId.length() + 1;
+            switch (pluginResult.getMessageType()) {
+                case PluginResult.MESSAGE_TYPE_BOOLEAN: // f or t
+                case PluginResult.MESSAGE_TYPE_NULL: // N
+                    ret += 1;
+                    break;
+                case PluginResult.MESSAGE_TYPE_NUMBER: // n
+                    ret += 1 + pluginResult.getMessage().length();
+                    break;
+                case PluginResult.MESSAGE_TYPE_STRING: // s
+                    ret += 1 + pluginResult.getStrMessage().length();
+                    break;
+                case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
+                    ret += 1 + pluginResult.getMessage().length();
+                    break;
+                case PluginResult.MESSAGE_TYPE_JSON:
+                default:
+                    ret += pluginResult.getMessage().length();
+            }
+            return ret;
+        }
+        
+        void encodeAsMessage(StringBuilder sb) {
+            if (pluginResult == null) {
+                sb.append('J')
+                  .append(jsPayloadOrCallbackId);
+                return;
+            }
+            int status = pluginResult.getStatus();
+            boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal();
+            boolean resultOk = status == PluginResult.Status.OK.ordinal();
+            boolean keepCallback = pluginResult.getKeepCallback();
+
+            sb.append((noResult || resultOk) ? 'S' : 'F')
+              .append(keepCallback ? '1' : '0')
+              .append(status)
+              .append(' ')
+              .append(jsPayloadOrCallbackId)
+              .append(' ');
+            switch (pluginResult.getMessageType()) {
+                case PluginResult.MESSAGE_TYPE_BOOLEAN:
+                    sb.append(pluginResult.getMessage().charAt(0)); // t or f.
+                    break;
+                case PluginResult.MESSAGE_TYPE_NULL: // N
+                    sb.append('N');
+                    break;
+                case PluginResult.MESSAGE_TYPE_NUMBER: // n
+                    sb.append('n')
+                      .append(pluginResult.getMessage());
+                    break;
+                case PluginResult.MESSAGE_TYPE_STRING: // s
+                    sb.append('s');
+                    sb.append(pluginResult.getStrMessage());
+                    break;
+                case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
+                    sb.append('A');
+                    sb.append(pluginResult.getMessage());
+                    break;
+                case PluginResult.MESSAGE_TYPE_JSON:
+                default:
+                    sb.append(pluginResult.getMessage()); // [ or {
+            }
+        }
+        
+        void encodeAsJsMessage(StringBuilder sb) {
+            if (pluginResult == null) {
+                sb.append(jsPayloadOrCallbackId);
+            } else {
+                int status = pluginResult.getStatus();
+                boolean success = (status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal());
+                sb.append("cordova.callbackFromNative('")
+                  .append(jsPayloadOrCallbackId)
+                  .append("',")
+                  .append(success)
+                  .append(",")
+                  .append(status)
+                  .append(",[")
+                  .append(pluginResult.getMessage())
+                  .append("],")
+                  .append(pluginResult.getKeepCallback())
+                  .append(");");
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e52404f2/framework/src/org/apache/cordova/core/NetworkListener.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/core/NetworkListener.java b/framework/src/org/apache/cordova/core/NetworkListener.java
new file mode 100755
index 0000000..eadf91a
--- /dev/null
+++ b/framework/src/org/apache/cordova/core/NetworkListener.java
@@ -0,0 +1,33 @@
+/*
+       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.core;
+
+
+import android.location.LocationManager;
+
+/**
+ * This class handles requests for GPS location services.
+ *
+ */
+public class NetworkListener extends CordovaLocationListener {
+    public NetworkListener(LocationManager locationManager, GeoBroker m) {
+        super(locationManager, m, "[Cordova NetworkListener]");
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e52404f2/framework/src/org/apache/cordova/core/NetworkManager.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/core/NetworkManager.java b/framework/src/org/apache/cordova/core/NetworkManager.java
new file mode 100755
index 0000000..2d23168
--- /dev/null
+++ b/framework/src/org/apache/cordova/core/NetworkManager.java
@@ -0,0 +1,249 @@
+/*
+       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.core;
+
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.api.CallbackContext;
+import org.apache.cordova.api.CordovaInterface;
+import org.apache.cordova.api.CordovaPlugin;
+import org.apache.cordova.api.PluginResult;
+import org.json.JSONArray;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.util.Log;
+
+public class NetworkManager extends CordovaPlugin {
+
+    public static int NOT_REACHABLE = 0;
+    public static int REACHABLE_VIA_CARRIER_DATA_NETWORK = 1;
+    public static int REACHABLE_VIA_WIFI_NETWORK = 2;
+
+    public static final String WIFI = "wifi";
+    public static final String WIMAX = "wimax";
+    // mobile
+    public static final String MOBILE = "mobile";
+    // 2G network types
+    public static final String GSM = "gsm";
+    public static final String GPRS = "gprs";
+    public static final String EDGE = "edge";
+    // 3G network types
+    public static final String CDMA = "cdma";
+    public static final String UMTS = "umts";
+    public static final String HSPA = "hspa";
+    public static final String HSUPA = "hsupa";
+    public static final String HSDPA = "hsdpa";
+    public static final String ONEXRTT = "1xrtt";
+    public static final String EHRPD = "ehrpd";
+    // 4G network types
+    public static final String LTE = "lte";
+    public static final String UMB = "umb";
+    public static final String HSPA_PLUS = "hspa+";
+    // return type
+    public static final String TYPE_UNKNOWN = "unknown";
+    public static final String TYPE_ETHERNET = "ethernet";
+    public static final String TYPE_WIFI = "wifi";
+    public static final String TYPE_2G = "2g";
+    public static final String TYPE_3G = "3g";
+    public static final String TYPE_4G = "4g";
+    public static final String TYPE_NONE = "none";
+
+    private static final String LOG_TAG = "NetworkManager";
+
+    private CallbackContext connectionCallbackContext;
+    private boolean registered = false;
+
+    ConnectivityManager sockMan;
+    BroadcastReceiver receiver;
+    private String lastStatus = "";
+
+    /**
+     * Constructor.
+     */
+    public NetworkManager() {
+        this.receiver = null;
+    }
+
+    /**
+     * Sets the context of the Command. This can then be used to do things like
+     * get file paths associated with the Activity.
+     *
+     * @param cordova The context of the main Activity.
+     * @param webView The CordovaWebView Cordova is running in.
+     */
+    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
+        super.initialize(cordova, webView);
+        this.sockMan = (ConnectivityManager) cordova.getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
+        this.connectionCallbackContext = null;
+
+        // We need to listen to connectivity events to update navigator.connection
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        if (this.receiver == null) {
+            this.receiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    // (The null check is for the ARM Emulator, please use Intel Emulator for better results)
+                    if(NetworkManager.this.webView != null)                        
+                        updateConnectionInfo(sockMan.getActiveNetworkInfo());
+                }
+            };
+            cordova.getActivity().registerReceiver(this.receiver, intentFilter);
+            this.registered = true;
+        }
+
+    }
+
+    /**
+     * Executes the request and returns PluginResult.
+     *
+     * @param action            The action to execute.
+     * @param args              JSONArry of arguments for the plugin.
+     * @param callbackContext   The callback id used when calling back into JavaScript.
+     * @return                  True if the action was valid, false otherwise.
+     */
+    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
+        if (action.equals("getConnectionInfo")) {
+            this.connectionCallbackContext = callbackContext;
+            NetworkInfo info = sockMan.getActiveNetworkInfo();
+            PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, this.getConnectionInfo(info));
+            pluginResult.setKeepCallback(true);
+            callbackContext.sendPluginResult(pluginResult);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Stop network receiver.
+     */
+    public void onDestroy() {
+        if (this.receiver != null && this.registered) {
+            try {
+                this.cordova.getActivity().unregisterReceiver(this.receiver);
+                this.registered = false;
+            } catch (Exception e) {
+                Log.e(LOG_TAG, "Error unregistering network receiver: " + e.getMessage(), e);
+            }
+        }
+    }
+
+    //--------------------------------------------------------------------------
+    // LOCAL METHODS
+    //--------------------------------------------------------------------------
+
+    /**
+     * Updates the JavaScript side whenever the connection changes
+     *
+     * @param info the current active network info
+     * @return
+     */
+    private void updateConnectionInfo(NetworkInfo info) {
+        // send update to javascript "navigator.network.connection"
+        // Jellybean sends its own info
+        String thisStatus = this.getConnectionInfo(info);
+        if(!thisStatus.equals(lastStatus))
+        {
+            sendUpdate(thisStatus);
+            lastStatus = thisStatus;
+        }
+            
+    }
+
+    /**
+     * Get the latest network connection information
+     *
+     * @param info the current active network info
+     * @return a JSONObject that represents the network info
+     */
+    private String getConnectionInfo(NetworkInfo info) {
+        String type = TYPE_NONE;
+        if (info != null) {
+            // If we are not connected to any network set type to none
+            if (!info.isConnected()) {
+                type = TYPE_NONE;
+            }
+            else {
+                type = getType(info);
+            }
+        }
+        Log.d("CordovaNetworkManager", "Connection Type: " + type);
+        return type;
+    }
+
+    /**
+     * Create a new plugin result and send it back to JavaScript
+     *
+     * @param connection the network info to set as navigator.connection
+     */
+    private void sendUpdate(String type) {
+        if (connectionCallbackContext != null) {
+            PluginResult result = new PluginResult(PluginResult.Status.OK, type);
+            result.setKeepCallback(true);
+            connectionCallbackContext.sendPluginResult(result);
+        }
+        webView.postMessage("networkconnection", type);
+    }
+
+    /**
+     * Determine the type of connection
+     *
+     * @param info the network info so we can determine connection type.
+     * @return the type of mobile network we are on
+     */
+    private String getType(NetworkInfo info) {
+        if (info != null) {
+            String type = info.getTypeName();
+
+            if (type.toLowerCase().equals(WIFI)) {
+                return TYPE_WIFI;
+            }
+            else if (type.toLowerCase().equals(MOBILE)) {
+                type = info.getSubtypeName();
+                if (type.toLowerCase().equals(GSM) ||
+                        type.toLowerCase().equals(GPRS) ||
+                        type.toLowerCase().equals(EDGE)) {
+                    return TYPE_2G;
+                }
+                else if (type.toLowerCase().startsWith(CDMA) ||
+                        type.toLowerCase().equals(UMTS) ||
+                        type.toLowerCase().equals(ONEXRTT) ||
+                        type.toLowerCase().equals(EHRPD) ||
+                        type.toLowerCase().equals(HSUPA) ||
+                        type.toLowerCase().equals(HSDPA) ||
+                        type.toLowerCase().equals(HSPA)) {
+                    return TYPE_3G;
+                }
+                else if (type.toLowerCase().equals(LTE) ||
+                        type.toLowerCase().equals(UMB) ||
+                        type.toLowerCase().equals(HSPA_PLUS)) {
+                    return TYPE_4G;
+                }
+            }
+        }
+        else {
+            return TYPE_NONE;
+        }
+        return TYPE_UNKNOWN;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e52404f2/framework/src/org/apache/cordova/core/Notification.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/core/Notification.java b/framework/src/org/apache/cordova/core/Notification.java
new file mode 100755
index 0000000..58bfc0d
--- /dev/null
+++ b/framework/src/org/apache/cordova/core/Notification.java
@@ -0,0 +1,341 @@
+/*
+       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.core;
+
+import org.apache.cordova.api.CallbackContext;
+import org.apache.cordova.api.CordovaInterface;
+import org.apache.cordova.api.CordovaPlugin;
+import org.apache.cordova.api.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Vibrator;
+
+/**
+ * This class provides access to notifications on the device.
+ */
+public class Notification extends CordovaPlugin {
+
+    public int confirmResult = -1;
+    public ProgressDialog spinnerDialog = null;
+    public ProgressDialog progressDialog = null;
+
+    /**
+     * Constructor.
+     */
+    public Notification() {
+    }
+
+    /**
+     * Executes the request and returns PluginResult.
+     *
+     * @param action            The action to execute.
+     * @param args              JSONArray of arguments for the plugin.
+     * @param callbackContext   The callback context used when calling back into JavaScript.
+     * @return                  True when the action was valid, false otherwise.
+     */
+    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+        if (action.equals("beep")) {
+            this.beep(args.getLong(0));
+        }
+        else if (action.equals("vibrate")) {
+            this.vibrate(args.getLong(0));
+        }
+        else if (action.equals("alert")) {
+            this.alert(args.getString(0), args.getString(1), args.getString(2), callbackContext);
+            return true;
+        }
+        else if (action.equals("confirm")) {
+            this.confirm(args.getString(0), args.getString(1), args.getString(2), callbackContext);
+            return true;
+        }
+        else if (action.equals("activityStart")) {
+            this.activityStart(args.getString(0), args.getString(1));
+        }
+        else if (action.equals("activityStop")) {
+            this.activityStop();
+        }
+        else if (action.equals("progressStart")) {
+            this.progressStart(args.getString(0), args.getString(1));
+        }
+        else if (action.equals("progressValue")) {
+            this.progressValue(args.getInt(0));
+        }
+        else if (action.equals("progressStop")) {
+            this.progressStop();
+        }
+        else {
+            return false;
+        }
+
+        // Only alert and confirm are async.
+        callbackContext.success();
+        return true;
+    }
+
+    //--------------------------------------------------------------------------
+    // LOCAL METHODS
+    //--------------------------------------------------------------------------
+
+    /**
+     * Beep plays the default notification ringtone.
+     *
+     * @param count     Number of times to play notification
+     */
+    public void beep(long count) {
+        Uri ringtone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
+        Ringtone notification = RingtoneManager.getRingtone(this.cordova.getActivity().getBaseContext(), ringtone);
+
+        // If phone is not set to silent mode
+        if (notification != null) {
+            for (long i = 0; i < count; ++i) {
+                notification.play();
+                long timeout = 5000;
+                while (notification.isPlaying() && (timeout > 0)) {
+                    timeout = timeout - 100;
+                    try {
+                        Thread.sleep(100);
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Vibrates the device for the specified amount of time.
+     *
+     * @param time      Time to vibrate in ms.
+     */
+    public void vibrate(long time) {
+        // Start the vibration, 0 defaults to half a second.
+        if (time == 0) {
+            time = 500;
+        }
+        Vibrator vibrator = (Vibrator) this.cordova.getActivity().getSystemService(Context.VIBRATOR_SERVICE);
+        vibrator.vibrate(time);
+    }
+
+    /**
+     * Builds and shows a native Android alert with given Strings
+     * @param message           The message the alert should display
+     * @param title             The title of the alert
+     * @param buttonLabel       The label of the button
+     * @param callbackContext   The callback context
+     */
+    public synchronized void alert(final String message, final String title, final String buttonLabel, final CallbackContext callbackContext) {
+
+        final CordovaInterface cordova = this.cordova;
+
+        Runnable runnable = new Runnable() {
+            public void run() {
+
+                AlertDialog.Builder dlg = new AlertDialog.Builder(cordova.getActivity());
+                dlg.setMessage(message);
+                dlg.setTitle(title);
+                dlg.setCancelable(true);
+                dlg.setPositiveButton(buttonLabel,
+                        new AlertDialog.OnClickListener() {
+                            public void onClick(DialogInterface dialog, int which) {
+                                dialog.dismiss();
+                                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0));
+                            }
+                        });
+                dlg.setOnCancelListener(new AlertDialog.OnCancelListener() {
+                    public void onCancel(DialogInterface dialog)
+                    {
+                        dialog.dismiss();
+                        callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0));
+                    }
+                });
+                
+                dlg.create();
+                dlg.show();
+            };
+        };
+        this.cordova.getActivity().runOnUiThread(runnable);
+    }
+
+    /**
+     * Builds and shows a native Android confirm dialog with given title, message, buttons.
+     * This dialog only shows up to 3 buttons.  Any labels after that will be ignored.
+     * The index of the button pressed will be returned to the JavaScript callback identified by callbackId.
+     *
+     * @param message           The message the dialog should display
+     * @param title             The title of the dialog
+     * @param buttonLabels      A comma separated list of button labels (Up to 3 buttons)
+     * @param callbackContext   The callback context.
+     */
+    public synchronized void confirm(final String message, final String title, String buttonLabels, final CallbackContext callbackContext) {
+
+        final CordovaInterface cordova = this.cordova;
+        final String[] fButtons = buttonLabels.split(",");
+
+        Runnable runnable = new Runnable() {
+            public void run() {
+                AlertDialog.Builder dlg = new AlertDialog.Builder(cordova.getActivity());
+                dlg.setMessage(message);
+                dlg.setTitle(title);
+                dlg.setCancelable(true);
+
+                // First button
+                if (fButtons.length > 0) {
+                    dlg.setNegativeButton(fButtons[0],
+                            new AlertDialog.OnClickListener() {
+                                public void onClick(DialogInterface dialog, int which) {
+                                    dialog.dismiss();
+                                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 1));
+                                }
+                            });
+                }
+
+                // Second button
+                if (fButtons.length > 1) {
+                    dlg.setNeutralButton(fButtons[1],
+                            new AlertDialog.OnClickListener() {
+                                public void onClick(DialogInterface dialog, int which) {
+                                    dialog.dismiss();
+                                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 2));
+                                }
+                            });
+                }
+
+                // Third button
+                if (fButtons.length > 2) {
+                    dlg.setPositiveButton(fButtons[2],
+                            new AlertDialog.OnClickListener() {
+                                public void onClick(DialogInterface dialog, int which) {
+                                    dialog.dismiss();
+                                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 3));
+                                }
+                            }
+                            );
+                }
+                dlg.setOnCancelListener(new AlertDialog.OnCancelListener() {
+                    public void onCancel(DialogInterface dialog)
+                    {
+                        dialog.dismiss();
+                        callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, 0));
+                    }
+                });
+
+                dlg.create();
+                dlg.show();
+            };
+        };
+        this.cordova.getActivity().runOnUiThread(runnable);
+    }
+
+    /**
+     * Show the spinner.
+     *
+     * @param title     Title of the dialog
+     * @param message   The message of the dialog
+     */
+    public synchronized void activityStart(final String title, final String message) {
+        if (this.spinnerDialog != null) {
+            this.spinnerDialog.dismiss();
+            this.spinnerDialog = null;
+        }
+        final CordovaInterface cordova = this.cordova;
+        Runnable runnable = new Runnable() {
+            public void run() {
+                Notification.this.spinnerDialog = ProgressDialog.show(cordova.getActivity(), title, message, true, true,
+                        new DialogInterface.OnCancelListener() {
+                            public void onCancel(DialogInterface dialog) {
+                                Notification.this.spinnerDialog = null;
+                            }
+                        });
+            }
+        };
+        this.cordova.getActivity().runOnUiThread(runnable);
+    }
+
+    /**
+     * Stop spinner.
+     */
+    public synchronized void activityStop() {
+        if (this.spinnerDialog != null) {
+            this.spinnerDialog.dismiss();
+            this.spinnerDialog = null;
+        }
+    }
+
+    /**
+     * Show the progress dialog.
+     *
+     * @param title     Title of the dialog
+     * @param message   The message of the dialog
+     */
+    public synchronized void progressStart(final String title, final String message) {
+        if (this.progressDialog != null) {
+            this.progressDialog.dismiss();
+            this.progressDialog = null;
+        }
+        final Notification notification = this;
+        final CordovaInterface cordova = this.cordova;
+        Runnable runnable = new Runnable() {
+            public void run() {
+                notification.progressDialog = new ProgressDialog(cordova.getActivity());
+                notification.progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+                notification.progressDialog.setTitle(title);
+                notification.progressDialog.setMessage(message);
+                notification.progressDialog.setCancelable(true);
+                notification.progressDialog.setMax(100);
+                notification.progressDialog.setProgress(0);
+                notification.progressDialog.setOnCancelListener(
+                        new DialogInterface.OnCancelListener() {
+                            public void onCancel(DialogInterface dialog) {
+                                notification.progressDialog = null;
+                            }
+                        });
+                notification.progressDialog.show();
+            }
+        };
+        this.cordova.getActivity().runOnUiThread(runnable);
+    }
+
+    /**
+     * Set value of progress bar.
+     *
+     * @param value     0-100
+     */
+    public synchronized void progressValue(int value) {
+        if (this.progressDialog != null) {
+            this.progressDialog.setProgress(value);
+        }
+    }
+
+    /**
+     * Stop progress dialog.
+     */
+    public synchronized void progressStop() {
+        if (this.progressDialog != null) {
+            this.progressDialog.dismiss();
+            this.progressDialog = null;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e52404f2/framework/src/org/apache/cordova/core/SplashScreen.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/core/SplashScreen.java b/framework/src/org/apache/cordova/core/SplashScreen.java
new file mode 100644
index 0000000..5e398af
--- /dev/null
+++ b/framework/src/org/apache/cordova/core/SplashScreen.java
@@ -0,0 +1,43 @@
+/*
+       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.core;
+
+import org.apache.cordova.api.CallbackContext;
+import org.apache.cordova.api.CordovaPlugin;
+import org.json.JSONArray;
+
+public class SplashScreen extends CordovaPlugin {
+
+    @Override
+    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) {
+        if (action.equals("hide")) {
+            this.webView.postMessage("splashscreen", "hide");
+        } else if (action.equals("show")){
+            this.webView.postMessage("splashscreen", "show");
+        }
+        else {
+            return false;
+        }
+
+        callbackContext.success();
+        return true;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e52404f2/framework/src/org/apache/cordova/core/StandAlone.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/core/StandAlone.java b/framework/src/org/apache/cordova/core/StandAlone.java
new file mode 100644
index 0000000..0c204ef
--- /dev/null
+++ b/framework/src/org/apache/cordova/core/StandAlone.java
@@ -0,0 +1,33 @@
+/*
+       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.core;
+
+
+import android.os.Bundle;
+
+public class StandAlone extends DroidGap {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        super.loadUrl("file:///android_asset/www/index.html");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/e52404f2/framework/src/org/apache/cordova/core/Storage.java
----------------------------------------------------------------------
diff --git a/framework/src/org/apache/cordova/core/Storage.java b/framework/src/org/apache/cordova/core/Storage.java
new file mode 100755
index 0000000..51497ae
--- /dev/null
+++ b/framework/src/org/apache/cordova/core/Storage.java
@@ -0,0 +1,244 @@
+/*
+       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.core;
+
+import java.io.File;
+
+import org.apache.cordova.api.CallbackContext;
+import org.apache.cordova.api.CordovaPlugin;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.*;
+
+/**
+ * This class implements the HTML5 database support to work around a bug for
+ * Android 3.0 devices. It is not used for other versions of Android, since
+ * HTML5 database is built in to the browser.
+ */
+public class Storage extends CordovaPlugin {
+
+    // Data Definition Language
+    private static final String ALTER = "alter";
+    private static final String CREATE = "create";
+    private static final String DROP = "drop";
+    private static final String TRUNCATE = "truncate";
+
+    SQLiteDatabase myDb = null; // Database object
+    String path = null; // Database path
+    String dbName = null; // Database name
+
+    /**
+     * Constructor.
+     */
+    public Storage() {
+    }
+
+    /**
+     * Executes the request and returns PluginResult.
+     *
+     * @param action
+     *            The action to execute.
+     * @param args
+     *            JSONArry of arguments for the plugin.
+     * @param callbackContext
+     *            The callback context used when calling back into JavaScript.
+     * @return True if the action was valid, false otherwise.
+     */
+    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+        if (action.equals("openDatabase")) {
+            this.openDatabase(args.getString(0), args.getString(1),
+                    args.getString(2), args.getLong(3));
+        } else if (action.equals("executeSql")) {
+            String[] s = null;
+            if (args.isNull(1)) {
+                s = new String[0];
+            } else {
+                JSONArray a = args.getJSONArray(1);
+                int len = a.length();
+                s = new String[len];
+                for (int i = 0; i < len; i++) {
+                    s[i] = a.getString(i);
+                }
+            }
+            this.executeSql(args.getString(0), s, args.getString(2));
+        }
+        else {
+            return false;
+        }
+        callbackContext.success();
+        return true;
+    }
+
+    /**
+     * Clean up and close database.
+     */
+    @Override
+    public void onDestroy() {
+        if (this.myDb != null) {
+            this.myDb.close();
+            this.myDb = null;
+        }
+    }
+
+    /**
+     * Clean up on navigation/refresh.
+     */
+    public void onReset() {
+        this.onDestroy();
+    }
+
+    // --------------------------------------------------------------------------
+    // LOCAL METHODS
+    // --------------------------------------------------------------------------
+
+    /**
+     * Open database.
+     *
+     * @param db
+     *            The name of the database
+     * @param version
+     *            The version
+     * @param display_name
+     *            The display name
+     * @param size
+     *            The size in bytes
+     */
+    public void openDatabase(String db, String version, String display_name,
+            long size) {
+
+        // If database is open, then close it
+        if (this.myDb != null) {
+            this.myDb.close();
+        }
+
+        // If no database path, generate from application package
+        if (this.path == null) {
+            this.path = this.cordova.getActivity().getApplicationContext().getDir("database", Context.MODE_PRIVATE).getPath();
+        }
+
+        this.dbName = this.path + File.separator + db + ".db";
+
+        /*
+         * What is all this nonsense? Well the separator was incorrect so the db was showing up in the wrong 
+         * directory. This bit of code fixes that issue and moves the db to the correct directory.
+         */
+        File oldDbFile = new File(this.path + File.pathSeparator + db + ".db");
+        if (oldDbFile.exists()) {
+            File dbPath = new File(this.path);
+            File dbFile = new File(dbName);
+            dbPath.mkdirs();
+            oldDbFile.renameTo(dbFile);
+        }
+        
+        this.myDb = SQLiteDatabase.openOrCreateDatabase(this.dbName, null);
+    }
+
+    /**
+     * Execute SQL statement.
+     *
+     * @param query
+     *            The SQL query
+     * @param params
+     *            Parameters for the query
+     * @param tx_id
+     *            Transaction id
+     */
+    public void executeSql(String query, String[] params, String tx_id) {
+        try {
+            if (isDDL(query)) {
+                this.myDb.execSQL(query);
+                this.webView.sendJavascript("cordova.require('cordova/plugin/android/storage').completeQuery('" + tx_id + "', '');");
+            }
+            else {
+                Cursor myCursor = this.myDb.rawQuery(query, params);
+                this.processResults(myCursor, tx_id);
+                myCursor.close();
+            }
+        }
+        catch (SQLiteException ex) {
+            ex.printStackTrace();
+            System.out.println("Storage.executeSql(): Error=" +  ex.getMessage());
+
+            // Send error message back to JavaScript
+            this.webView.sendJavascript("cordova.require('cordova/plugin/android/storage').failQuery('" + ex.getMessage() + "','" + tx_id + "');");
+        }
+    }
+
+    /**
+     * Checks to see the the query is a Data Definition command
+     *
+     * @param query to be executed
+     * @return true if it is a DDL command, false otherwise
+     */
+    private boolean isDDL(String query) {
+        String cmd = query.toLowerCase();
+        if (cmd.startsWith(DROP) || cmd.startsWith(CREATE) || cmd.startsWith(ALTER) || cmd.startsWith(TRUNCATE)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Process query results.
+     *
+     * @param cur
+     *            Cursor into query results
+     * @param tx_id
+     *            Transaction id
+     */
+    public void processResults(Cursor cur, String tx_id) {
+
+        String result = "[]";
+        // If query result has rows
+
+        if (cur.moveToFirst()) {
+            JSONArray fullresult = new JSONArray();
+            String key = "";
+            String value = "";
+            int colCount = cur.getColumnCount();
+
+            // Build up JSON result object for each row
+            do {
+                JSONObject row = new JSONObject();
+                try {
+                    for (int i = 0; i < colCount; ++i) {
+                        key = cur.getColumnName(i);
+                        value = cur.getString(i);
+                        row.put(key, value);
+                    }
+                    fullresult.put(row);
+
+                } catch (JSONException e) {
+                    e.printStackTrace();
+                }
+
+            } while (cur.moveToNext());
+
+            result = fullresult.toString();
+        }
+
+        // Let JavaScript know that there are no more rows
+        this.webView.sendJavascript("cordova.require('cordova/plugin/android/storage').completeQuery('" + tx_id + "', " + result + ");");
+    }
+
+}


Mime
View raw message