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

[38/52] [partial] support for 2.4.0rc1. "vendored" the platform libs in. added Gord and Braden as contributors. removed dependency on unzip and axed the old download-cordova code.

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-android/framework/src/org/apache/cordova/GeoBroker.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/GeoBroker.java b/lib/cordova-android/framework/src/org/apache/cordova/GeoBroker.java
new file mode 100755
index 0000000..86aa628
--- /dev/null
+++ b/lib/cordova-android/framework/src/org/apache/cordova/GeoBroker.java
@@ -0,0 +1,204 @@
+/*
+       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.api.CallbackContext;
+import org.apache.cordova.api.CordovaPlugin;
+import org.apache.cordova.api.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.content.Context;
+import android.location.Location;
+import android.location.LocationManager;
+
+/*
+ * This class is the interface to the Geolocation.  It's bound to the geo object.
+ *
+ * This class only starts and stops various GeoListeners, which consist of a GPS and a Network Listener
+ */
+
+public class GeoBroker extends CordovaPlugin {
+    private GPSListener gpsListener;
+    private NetworkListener networkListener;
+    private LocationManager locationManager;
+
+    /**
+     * Constructor.
+     */
+    public GeoBroker() {
+    }
+
+    /**
+     * 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, or false if not.
+     */
+    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+        if (this.locationManager == null) {
+            this.locationManager = (LocationManager) this.cordova.getActivity().getSystemService(Context.LOCATION_SERVICE);
+            this.networkListener = new NetworkListener(this.locationManager, this);
+            this.gpsListener = new GPSListener(this.locationManager, this);
+        }
+
+        if ( locationManager.isProviderEnabled( LocationManager.GPS_PROVIDER ) ||
+                locationManager.isProviderEnabled( LocationManager.NETWORK_PROVIDER )) {
+
+            if (action.equals("getLocation")) {
+                boolean enableHighAccuracy = args.getBoolean(0);
+                int maximumAge = args.getInt(1);
+                Location last = this.locationManager.getLastKnownLocation((enableHighAccuracy ? LocationManager.GPS_PROVIDER : LocationManager.NETWORK_PROVIDER));
+                // Check if we can use lastKnownLocation to get a quick reading and use less battery
+                if (last != null && (System.currentTimeMillis() - last.getTime()) <= maximumAge) {
+                    PluginResult result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(last));
+                    callbackContext.sendPluginResult(result);
+                } else {
+                    this.getCurrentLocation(callbackContext, enableHighAccuracy);
+                }
+            }
+            else if (action.equals("addWatch")) {
+                String id = args.getString(0);
+                boolean enableHighAccuracy = args.getBoolean(1);
+                this.addWatch(id, callbackContext, enableHighAccuracy);
+            }
+            else if (action.equals("clearWatch")) {
+                String id = args.getString(0);
+                this.clearWatch(id);
+            }
+            else {
+                return false;
+            }
+        } else {
+            PluginResult.Status status = PluginResult.Status.NO_RESULT;
+            String message = "Location API is not available for this device.";
+            PluginResult result = new PluginResult(status, message);
+            callbackContext.sendPluginResult(result);
+        }
+        return true;
+    }
+
+    private void clearWatch(String id) {
+        this.gpsListener.clearWatch(id);
+        this.networkListener.clearWatch(id);
+    }
+
+    private void getCurrentLocation(CallbackContext callbackContext, boolean enableHighAccuracy) {
+        if (enableHighAccuracy) {
+            this.gpsListener.addCallback(callbackContext);
+        } else {
+            this.networkListener.addCallback(callbackContext);
+        }
+    }
+
+    private void addWatch(String timerId, CallbackContext callbackContext, boolean enableHighAccuracy) {
+        if (enableHighAccuracy) {
+            this.gpsListener.addWatch(timerId, callbackContext);
+        } else {
+            this.networkListener.addWatch(timerId, callbackContext);
+        }
+    }
+
+    /**
+     * Called when the activity is to be shut down.
+     * Stop listener.
+     */
+    public void onDestroy() {
+        if (this.networkListener != null) {
+            this.networkListener.destroy();
+            this.networkListener = null;
+        }
+        if (this.gpsListener != null) {
+            this.gpsListener.destroy();
+            this.gpsListener = null;
+        }
+    }
+
+    /**
+     * Called when the view navigates.
+     * Stop the listeners.
+     */
+    public void onReset() {
+        this.onDestroy();
+    }
+
+    public JSONObject returnLocationJSON(Location loc) {
+        JSONObject o = new JSONObject();
+
+        try {
+            o.put("latitude", loc.getLatitude());
+            o.put("longitude", loc.getLongitude());
+            o.put("altitude", (loc.hasAltitude() ? loc.getAltitude() : null));
+            o.put("accuracy", loc.getAccuracy());
+            o.put("heading", (loc.hasBearing() ? (loc.hasSpeed() ? loc.getBearing() : null) : null));
+            o.put("speed", loc.getSpeed());
+            o.put("timestamp", loc.getTime());
+        } catch (JSONException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+
+        return o;
+    }
+
+    public void win(Location loc, CallbackContext callbackContext) {
+        PluginResult result = new PluginResult(PluginResult.Status.OK, this.returnLocationJSON(loc));
+        callbackContext.sendPluginResult(result);
+    }
+
+    /**
+     * Location failed.  Send error back to JavaScript.
+     * 
+     * @param code			The error code
+     * @param msg			The error message
+     * @throws JSONException 
+     */
+    public void fail(int code, String msg, CallbackContext callbackContext) {
+        JSONObject obj = new JSONObject();
+        String backup = null;
+        try {
+            obj.put("code", code);
+            obj.put("message", msg);
+        } catch (JSONException e) {
+            obj = null;
+            backup = "{'code':" + code + ",'message':'" + msg.replaceAll("'", "\'") + "'}";
+        }
+        PluginResult result;
+        if (obj != null) {
+            result = new PluginResult(PluginResult.Status.ERROR, obj);
+        } else {
+            result = new PluginResult(PluginResult.Status.ERROR, backup);
+        }
+
+        callbackContext.sendPluginResult(result);
+    }
+
+    public boolean isGlobalListener(CordovaLocationListener listener)
+    {
+    	if (gpsListener != null && networkListener != null)
+    	{
+    		return gpsListener.equals(listener) || networkListener.equals(listener);
+    	}
+    	else
+    		return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-android/framework/src/org/apache/cordova/Globalization.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/Globalization.java b/lib/cordova-android/framework/src/org/apache/cordova/Globalization.java
new file mode 100644
index 0000000..5c75e10
--- /dev/null
+++ b/lib/cordova-android/framework/src/org/apache/cordova/Globalization.java
@@ -0,0 +1,577 @@
+/*
+       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.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Currency;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.cordova.api.CallbackContext;
+import org.apache.cordova.api.CordovaPlugin;
+import org.apache.cordova.api.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.annotation.TargetApi;
+import android.text.format.Time;
+
+/**
+ *
+ */
+public class Globalization extends CordovaPlugin  {
+    //GlobalizationCommand Plugin Actions
+    public static final String GETLOCALENAME = "getLocaleName";
+    public static final String DATETOSTRING = "dateToString";
+    public static final String STRINGTODATE = "stringToDate";
+    public static final String GETDATEPATTERN = "getDatePattern";
+    public static final String GETDATENAMES = "getDateNames";
+    public static final String ISDAYLIGHTSAVINGSTIME = "isDayLightSavingsTime";
+    public static final String GETFIRSTDAYOFWEEK = "getFirstDayOfWeek";
+    public static final String NUMBERTOSTRING = "numberToString";
+    public static final String STRINGTONUMBER = "stringToNumber";
+    public static final String GETNUMBERPATTERN = "getNumberPattern";
+    public static final String GETCURRENCYPATTERN = "getCurrencyPattern";
+    public static final String GETPREFERREDLANGUAGE = "getPreferredLanguage";
+
+    //GlobalizationCommand Option Parameters
+    public static final String OPTIONS = "options";
+    public static final String FORMATLENGTH = "formatLength";
+    //public static final String SHORT = "short"; //default for dateToString format
+    public static final String MEDIUM = "medium";
+    public static final String LONG = "long";
+    public static final String FULL = "full";
+    public static final String SELECTOR = "selector";
+    //public static final String DATEANDTIME = "date and time"; //default for dateToString
+    public static final String DATE = "date";
+    public static final String TIME = "time";
+    public static final String DATESTRING = "dateString";
+    public static final String TYPE = "type";
+    public static final String ITEM = "item";
+    public static final String NARROW = "narrow";
+    public static final String WIDE = "wide";
+    public static final String MONTHS = "months";
+    public static final String DAYS = "days";
+    //public static final String DECMIAL = "wide"; //default for numberToString
+    public static final String NUMBER = "number";
+    public static final String NUMBERSTRING = "numberString";
+    public static final String PERCENT = "percent";
+    public static final String CURRENCY = "currency";
+    public static final String CURRENCYCODE = "currencyCode";
+
+    @Override
+    public boolean execute(String action, JSONArray data, CallbackContext callbackContext) {
+        JSONObject obj = new JSONObject();
+
+        try{
+            if (action.equals(GETLOCALENAME)){
+                obj = getLocaleName();
+            }else if (action.equals(GETPREFERREDLANGUAGE)){
+                obj = getPreferredLanguage();
+            } else if (action.equalsIgnoreCase(DATETOSTRING)) {
+                obj = getDateToString(data);
+            }else if(action.equalsIgnoreCase(STRINGTODATE)){
+                obj = getStringtoDate(data);
+            }else if(action.equalsIgnoreCase(GETDATEPATTERN)){
+                obj = getDatePattern(data);
+            }else if(action.equalsIgnoreCase(GETDATENAMES)){
+                if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.GINGERBREAD) {
+                    throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR);
+                } else {
+                    obj = getDateNames(data);
+                }
+            }else if(action.equalsIgnoreCase(ISDAYLIGHTSAVINGSTIME)){
+                obj = getIsDayLightSavingsTime(data);
+            }else if(action.equalsIgnoreCase(GETFIRSTDAYOFWEEK)){
+                obj = getFirstDayOfWeek(data);
+            }else if(action.equalsIgnoreCase(NUMBERTOSTRING)){
+                obj = getNumberToString(data);
+            }else if(action.equalsIgnoreCase(STRINGTONUMBER)){
+                obj = getStringToNumber(data);
+            }else if(action.equalsIgnoreCase(GETNUMBERPATTERN)){
+                obj = getNumberPattern(data);
+            }else if(action.equalsIgnoreCase(GETCURRENCYPATTERN)){
+                obj = getCurrencyPattern(data);
+            }else {
+                return false;
+            }
+
+            callbackContext.success(obj);
+        }catch (GlobalizationError ge){
+            callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, ge.toJson()));
+        }catch (Exception e){
+            callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+        }
+        return true;
+    }
+    /*
+     * @Description: Returns the string identifier for the client's current locale setting
+     *
+     * @Return: JSONObject
+     *          Object.value {String}: The locale identifier
+     *
+     * @throws: GlobalizationError.UNKNOWN_ERROR
+     */
+    private JSONObject getLocaleName() throws GlobalizationError{
+        JSONObject obj = new JSONObject();
+        try{
+            obj.put("value",Locale.getDefault().toString());//get the locale from the Android Device
+            return obj;
+        }catch(Exception e){
+            throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR);
+        }
+    }
+    /*
+     * @Description: Returns the string identifier for the client's current language
+     *
+     * @Return: JSONObject
+     *          Object.value {String}: The language identifier
+     *
+     * @throws: GlobalizationError.UNKNOWN_ERROR
+     */
+    private JSONObject getPreferredLanguage() throws GlobalizationError {
+        JSONObject obj = new JSONObject();
+        try {
+            obj.put("value", Locale.getDefault().getDisplayLanguage().toString());
+            return obj;
+        } catch (Exception e) {
+            throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR);
+        }
+    }
+    /*
+     * @Description: Returns a date formatted as a string according to the client's user preferences and
+     * calendar using the time zone of the client.
+     *
+     * @Return: JSONObject
+     *          Object.value {String}: The localized date string
+     *
+     * @throws: GlobalizationError.FORMATTING_ERROR
+     */
+    private JSONObject getDateToString(JSONArray options) throws GlobalizationError{
+        JSONObject obj = new JSONObject();
+        try{
+            Date date = new Date((Long)options.getJSONObject(0).get(DATE));
+
+            //get formatting pattern from android device (Will only have device specific formatting for short form of date) or options supplied
+            JSONObject datePattern = getDatePattern(options);
+            SimpleDateFormat fmt = new SimpleDateFormat(datePattern.getString("pattern"));
+
+            //return formatted date
+            return obj.put("value",fmt.format(date));
+        }catch(Exception ge){
+            throw new GlobalizationError(GlobalizationError.FORMATTING_ERROR);
+        }
+    }
+
+    /*
+     * @Description: Parses a date formatted as a string according to the client's user
+     * preferences and calendar using the time zone of the client and returns
+     * the corresponding date object
+     * @Return: JSONObject
+     *          Object.year {Number}: The four digit year
+     *          Object.month {Number}: The month from (0 - 11)
+     *          Object.day {Number}: The day from (1 - 31)
+     *          Object.hour {Number}: The hour from (0 - 23)
+     *          Object.minute {Number}: The minute from (0 - 59)
+     *          Object.second {Number}: The second from (0 - 59)
+     *          Object.millisecond {Number}: The milliseconds (from 0 - 999), not available on all platforms
+     *
+     * @throws: GlobalizationError.PARSING_ERROR
+    */
+    private JSONObject getStringtoDate(JSONArray options)throws GlobalizationError{
+        JSONObject obj = new JSONObject();
+        Date date;
+        try{
+            //get format pattern from android device (Will only have device specific formatting for short form of date) or options supplied
+            DateFormat fmt = new SimpleDateFormat(getDatePattern(options).getString("pattern"));
+
+            //attempt parsing string based on user preferences
+            date = fmt.parse(options.getJSONObject(0).get(DATESTRING).toString());
+
+            //set Android Time object
+            Time time = new Time();
+            time.set(date.getTime());
+
+            //return properties;
+            obj.put("year", time.year);
+            obj.put("month", time.month);
+            obj.put("day", time.monthDay);
+            obj.put("hour", time.hour);
+            obj.put("minute", time.minute);
+            obj.put("second", time.second);
+            obj.put("millisecond", new Long(0));
+            return obj;
+        }catch(Exception ge){
+            throw new GlobalizationError(GlobalizationError.PARSING_ERROR);
+        }
+    }
+
+    /*
+     * @Description: Returns a pattern string for formatting and parsing dates according to the client's
+     * user preferences.
+     * @Return: JSONObject
+     *
+     *          Object.pattern {String}: The date and time pattern for formatting and parsing dates.
+     *                                  The patterns follow Unicode Technical Standard #35
+     *                                  http://unicode.org/reports/tr35/tr35-4.html
+     *          Object.timezone {String}: The abbreviated name of the time zone on the client
+     *          Object.utc_offset {Number}: The current difference in seconds between the client's
+     *                                      time zone and coordinated universal time.
+     *          Object.dst_offset {Number}: The current daylight saving time offset in seconds
+     *                                      between the client's non-daylight saving's time zone
+     *                                      and the client's daylight saving's time zone.
+     *
+     * @throws: GlobalizationError.PATTERN_ERROR
+    */
+    private JSONObject getDatePattern(JSONArray options) throws GlobalizationError{
+        JSONObject obj = new JSONObject();
+
+        try{
+            SimpleDateFormat fmtDate = (SimpleDateFormat)android.text.format.DateFormat.getDateFormat(this.cordova.getActivity()); //default user preference for date
+            SimpleDateFormat fmtTime = (SimpleDateFormat)android.text.format.DateFormat.getTimeFormat(this.cordova.getActivity());  //default user preference for time
+
+            String fmt = fmtDate.toLocalizedPattern() + " " + fmtTime.toLocalizedPattern(); //default SHORT date/time format. ex. dd/MM/yyyy h:mm a
+
+            //get Date value + options (if available)
+            if (options.getJSONObject(0).length() > 1){
+                //options were included
+
+                //get formatLength option
+                if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(FORMATLENGTH)){
+                    String fmtOpt = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(FORMATLENGTH);
+                    if (fmtOpt.equalsIgnoreCase(MEDIUM)){//medium
+                        fmtDate = (SimpleDateFormat)android.text.format.DateFormat.getMediumDateFormat(this.cordova.getActivity());
+                    }else if (fmtOpt.equalsIgnoreCase(LONG) || fmtOpt.equalsIgnoreCase(FULL)){ //long/full
+                        fmtDate = (SimpleDateFormat)android.text.format.DateFormat.getLongDateFormat(this.cordova.getActivity());
+                    }
+                }
+
+                //return pattern type
+                fmt = fmtDate.toLocalizedPattern() + " " + fmtTime.toLocalizedPattern();
+                if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(SELECTOR)){
+                    String selOpt = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(SELECTOR);
+                    if (selOpt.equalsIgnoreCase(DATE)){
+                        fmt =  fmtDate.toLocalizedPattern();
+                    }else if (selOpt.equalsIgnoreCase(TIME)){
+                        fmt = fmtTime.toLocalizedPattern();
+                    }
+                }
+            }
+
+            //TimeZone from users device
+            //TimeZone tz = Calendar.getInstance(Locale.getDefault()).getTimeZone(); //substitute method
+            TimeZone tz = TimeZone.getTimeZone(Time.getCurrentTimezone());
+
+            obj.put("pattern", fmt);
+            obj.put("timezone", tz.getDisplayName(tz.inDaylightTime(Calendar.getInstance().getTime()),TimeZone.SHORT));
+            obj.put("utc_offset", tz.getRawOffset()/1000);
+            obj.put("dst_offset", tz.getDSTSavings()/1000);
+            return obj;
+
+        }catch(Exception ge){
+            throw new GlobalizationError(GlobalizationError.PATTERN_ERROR);
+        }
+    }
+
+    /*
+     * @Description: Returns an array of either the names of the months or days of the week
+     * according to the client's user preferences and calendar
+     * @Return: JSONObject
+     *          Object.value {Array{String}}: The array of names starting from either
+     *                                      the first month in the year or the
+     *                                      first day of the week.
+     *
+     * @throws: GlobalizationError.UNKNOWN_ERROR
+    */
+    @TargetApi(9)
+    private JSONObject getDateNames(JSONArray options) throws GlobalizationError{
+        JSONObject obj = new JSONObject();
+        //String[] value;
+        JSONArray value = new JSONArray();
+        List<String> namesList = new ArrayList<String>();
+        final Map<String,Integer> namesMap; // final needed for sorting with anonymous comparator
+        try{
+            int type = 0; //default wide
+            int item = 0; //default months
+
+            //get options if available
+            if (options.getJSONObject(0).length() > 0){
+                //get type if available
+                if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(TYPE)){
+                    String t = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(TYPE);
+                    if (t.equalsIgnoreCase(NARROW)){type++;} //DateUtils.LENGTH_MEDIUM
+                }
+                //get item if available
+                if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(ITEM)){
+                    String t = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(ITEM);
+                    if (t.equalsIgnoreCase(DAYS)){item += 10;} //Days of week start at 1
+                }
+            }
+            //determine return value
+            int method = item + type;
+            if  (method == 1) { //months and narrow
+                namesMap = Calendar.getInstance().getDisplayNames(Calendar.MONTH, Calendar.SHORT, Locale.getDefault());
+            } else if (method == 10) { //days and wide
+                namesMap = Calendar.getInstance().getDisplayNames(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.getDefault());
+            } else if (method == 11) { //days and narrow
+                namesMap = Calendar.getInstance().getDisplayNames(Calendar.DAY_OF_WEEK, Calendar.SHORT, Locale.getDefault());
+            } else { //default: months and wide
+                namesMap = Calendar.getInstance().getDisplayNames(Calendar.MONTH, Calendar.LONG, Locale.getDefault());
+            }
+
+            // save names as a list
+            for(String name : namesMap.keySet()) {
+                namesList.add(name);
+            }
+
+            // sort the list according to values in namesMap
+            Collections.sort(namesList, new Comparator<String>() {
+                public int compare(String arg0, String arg1) {
+                    return namesMap.get(arg0).compareTo(namesMap.get(arg1));
+                }
+            });
+
+            // convert nameList into JSONArray of String objects
+            for (int i = 0; i < namesList.size(); i ++){
+                value.put(namesList.get(i));
+            }
+
+            //return array of names
+            return obj.put("value", value);
+        }catch(Exception ge){
+            throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR);
+        }
+    }
+
+    /*
+     * @Description: Returns whether daylight savings time is in effect for a given date using the client's
+     * time zone and calendar.
+     * @Return: JSONObject
+     *          Object.dst {Boolean}: The value "true" indicates that daylight savings time is
+     *                              in effect for the given date and "false" indicate that it is not.    *
+     *
+     * @throws: GlobalizationError.UNKNOWN_ERROR
+    */
+    private JSONObject getIsDayLightSavingsTime(JSONArray options) throws GlobalizationError{
+        JSONObject obj = new JSONObject();
+        boolean dst = false;
+        try{
+            Date date = new Date((Long)options.getJSONObject(0).get(DATE));
+            //TimeZone tz = Calendar.getInstance(Locale.getDefault()).getTimeZone();
+            TimeZone tz = TimeZone.getTimeZone(Time.getCurrentTimezone());
+            dst = tz.inDaylightTime(date); //get daylight savings data from date object and user timezone settings
+
+            return obj.put("dst",dst);
+        }catch(Exception ge){
+            throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR);
+        }
+    }
+
+    /*
+     * @Description: Returns the first day of the week according to the client's user preferences and calendar.
+     * The days of the week are numbered starting from 1 where 1 is considered to be Sunday.
+     * @Return: JSONObject
+     *          Object.value {Number}: The number of the first day of the week.
+     *
+     * @throws: GlobalizationError.UNKNOWN_ERROR
+    */
+    private JSONObject getFirstDayOfWeek(JSONArray options) throws GlobalizationError{
+        JSONObject obj = new JSONObject();
+        try{
+            int value = Calendar.getInstance(Locale.getDefault()).getFirstDayOfWeek(); //get first day of week based on user locale settings
+            return obj.put("value", value);
+        }catch(Exception ge){
+            throw new GlobalizationError(GlobalizationError.UNKNOWN_ERROR);
+        }
+    }
+
+    /*
+     * @Description: Returns a number formatted as a string according to the client's user preferences.
+     * @Return: JSONObject
+     *          Object.value {String}: The formatted number string.
+     *
+     * @throws: GlobalizationError.FORMATTING_ERROR
+    */
+    private JSONObject getNumberToString(JSONArray options) throws GlobalizationError{
+        JSONObject obj = new JSONObject();
+        String value = "";
+        try{
+            DecimalFormat fmt = getNumberFormatInstance(options);//returns Decimal/Currency/Percent instance
+            value = fmt.format(options.getJSONObject(0).get(NUMBER));
+            return obj.put("value", value);
+        }catch(Exception ge){
+            throw new GlobalizationError(GlobalizationError.FORMATTING_ERROR);
+        }
+    }
+
+    /*
+     * @Description: Parses a number formatted as a string according to the client's user preferences and
+     * returns the corresponding number.
+     * @Return: JSONObject
+     *          Object.value {Number}: The parsed number.
+     *
+     * @throws: GlobalizationError.PARSING_ERROR
+    */
+    private JSONObject getStringToNumber(JSONArray options) throws GlobalizationError{
+        JSONObject obj = new JSONObject();
+        Number value;
+        try{
+            DecimalFormat fmt = getNumberFormatInstance(options); //returns Decimal/Currency/Percent instance
+            value = fmt.parse((String)options.getJSONObject(0).get(NUMBERSTRING));
+            return obj.put("value", value);
+        }catch(Exception ge){
+            throw new GlobalizationError(GlobalizationError.PARSING_ERROR);
+        }
+    }
+
+    /*
+     * @Description: Returns a pattern string for formatting and parsing numbers according to the client's user
+     * preferences.
+     * @Return: JSONObject
+     *          Object.pattern {String}: The number pattern for formatting and parsing numbers.
+     *                                  The patterns follow Unicode Technical Standard #35.
+     *                                  http://unicode.org/reports/tr35/tr35-4.html
+     *          Object.symbol {String}: The symbol to be used when formatting and parsing
+     *                                  e.g., percent or currency symbol.
+     *          Object.fraction {Number}: The number of fractional digits to use when parsing and
+     *                                  formatting numbers.
+     *          Object.rounding {Number}: The rounding increment to use when parsing and formatting.
+     *          Object.positive {String}: The symbol to use for positive numbers when parsing and formatting.
+     *          Object.negative: {String}: The symbol to use for negative numbers when parsing and formatting.
+     *          Object.decimal: {String}: The decimal symbol to use for parsing and formatting.
+     *          Object.grouping: {String}: The grouping symbol to use for parsing and formatting.
+     *
+     * @throws: GlobalizationError.PATTERN_ERROR
+    */
+    private JSONObject getNumberPattern(JSONArray options) throws GlobalizationError{
+        JSONObject obj = new JSONObject();
+        try{
+            //uses java.text.DecimalFormat to format value
+            DecimalFormat fmt = (DecimalFormat) DecimalFormat.getInstance(Locale.getDefault()); //default format
+            String symbol = String.valueOf(fmt.getDecimalFormatSymbols().getDecimalSeparator());
+            //get Date value + options (if available)
+            if (options.getJSONObject(0).length() > 0){
+                //options were included
+                if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(TYPE)){
+                    String fmtOpt = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(TYPE);
+                    if (fmtOpt.equalsIgnoreCase(CURRENCY)){
+                        fmt = (DecimalFormat) DecimalFormat.getCurrencyInstance(Locale.getDefault());
+                        symbol = fmt.getDecimalFormatSymbols().getCurrencySymbol();
+                    }else if(fmtOpt.equalsIgnoreCase(PERCENT)){
+                        fmt = (DecimalFormat) DecimalFormat.getPercentInstance(Locale.getDefault());
+                        symbol = String.valueOf(fmt.getDecimalFormatSymbols().getPercent());
+                    }
+                }
+            }
+
+            //return properties
+            obj.put("pattern", fmt.toPattern());
+            obj.put("symbol", symbol);
+            obj.put("fraction", fmt.getMinimumFractionDigits());
+            obj.put("rounding", new Integer(0));
+            obj.put("positive", fmt.getPositivePrefix());
+            obj.put("negative", fmt.getNegativePrefix());
+            obj.put("decimal", String.valueOf(fmt.getDecimalFormatSymbols().getDecimalSeparator()));
+            obj.put("grouping", String.valueOf(fmt.getDecimalFormatSymbols().getGroupingSeparator()));
+
+            return obj;
+        }catch(Exception ge){
+            throw new GlobalizationError(GlobalizationError.PATTERN_ERROR);
+        }
+    }
+
+    /*
+     * @Description: Returns a pattern string for formatting and parsing currency values according to the client's
+     * user preferences and ISO 4217 currency code.
+     * @Return: JSONObject
+     *          Object.pattern {String}: The currency pattern for formatting and parsing currency values.
+     *                                  The patterns follow Unicode Technical Standard #35
+     *                                  http://unicode.org/reports/tr35/tr35-4.html
+     *          Object.code {String}: The ISO 4217 currency code for the pattern.
+     *          Object.fraction {Number}: The number of fractional digits to use when parsing and
+     *                                  formatting currency.
+     *          Object.rounding {Number}: The rounding increment to use when parsing and formatting.
+     *          Object.decimal: {String}: The decimal symbol to use for parsing and formatting.
+     *          Object.grouping: {String}: The grouping symbol to use for parsing and formatting.
+     *
+     * @throws: GlobalizationError.FORMATTING_ERROR
+    */
+    private JSONObject getCurrencyPattern(JSONArray options) throws GlobalizationError{
+        JSONObject obj = new JSONObject();
+        try{
+            //get ISO 4217 currency code
+            String code = options.getJSONObject(0).getString(CURRENCYCODE);
+
+            //uses java.text.DecimalFormat to format value
+            DecimalFormat fmt = (DecimalFormat) DecimalFormat.getCurrencyInstance(Locale.getDefault());
+
+            //set currency format
+            Currency currency = Currency.getInstance(code);
+            fmt.setCurrency(currency);
+
+            //return properties
+            obj.put("pattern", fmt.toPattern());
+            obj.put("code", currency.getCurrencyCode());
+            obj.put("fraction", fmt.getMinimumFractionDigits());
+            obj.put("rounding", new Integer(0));
+            obj.put("decimal", String.valueOf(fmt.getDecimalFormatSymbols().getDecimalSeparator()));
+            obj.put("grouping", String.valueOf(fmt.getDecimalFormatSymbols().getGroupingSeparator()));
+
+            return obj;
+        }catch(Exception ge){
+            throw new GlobalizationError(GlobalizationError.FORMATTING_ERROR);
+        }
+    }
+
+    /*
+     * @Description: Parses a JSONArray from user options and returns the correct Instance of Decimal/Percent/Currency.
+     * @Return: DecimalFormat : The Instance to use.
+     *
+     * @throws: JSONException
+    */
+    private DecimalFormat getNumberFormatInstance(JSONArray options) throws JSONException{
+        DecimalFormat fmt =  (DecimalFormat)DecimalFormat.getInstance(Locale.getDefault()); //default format
+        try{
+            if (options.getJSONObject(0).length() > 1){
+                //options were included
+                if (!((JSONObject)options.getJSONObject(0).get(OPTIONS)).isNull(TYPE)){
+                    String fmtOpt = (String)((JSONObject)options.getJSONObject(0).get(OPTIONS)).get(TYPE);
+                    if (fmtOpt.equalsIgnoreCase(CURRENCY)){
+                        fmt = (DecimalFormat)DecimalFormat.getCurrencyInstance(Locale.getDefault());
+                    }else if(fmtOpt.equalsIgnoreCase(PERCENT)){
+                        fmt = (DecimalFormat)DecimalFormat.getPercentInstance(Locale.getDefault());
+                    }
+                }
+            }
+
+        }catch (JSONException je){}
+        return fmt;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-android/framework/src/org/apache/cordova/GlobalizationError.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/GlobalizationError.java b/lib/cordova-android/framework/src/org/apache/cordova/GlobalizationError.java
new file mode 100644
index 0000000..8a171d4
--- /dev/null
+++ b/lib/cordova-android/framework/src/org/apache/cordova/GlobalizationError.java
@@ -0,0 +1,108 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+
+package org.apache.cordova;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/** 
+ * @description Exception class representing defined Globalization error codes
+ * @Globalization error codes:
+ *      GlobalizationError.UNKNOWN_ERROR = 0;
+ *      GlobalizationError.FORMATTING_ERROR = 1;   
+ *      GlobalizationError.PARSING_ERROR = 2;   
+ *      GlobalizationError.PATTERN_ERROR = 3;
+ */
+public class GlobalizationError extends Exception{
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 1L;
+    public static final String UNKNOWN_ERROR = "UNKNOWN_ERROR";
+    public static final String FORMATTING_ERROR = "FORMATTING_ERROR";
+    public static final String PARSING_ERROR = "PARSING_ERROR";
+    public static final String PATTERN_ERROR = "PATTERN_ERROR";
+    
+    int error = 0;  //default unknown error thrown
+    /**
+     * Default constructor        
+     */
+    public GlobalizationError() {}
+    /**
+     * Create an exception returning an error code 
+     *    
+     * @param   s           
+     */
+    public GlobalizationError(String s) {       
+        if (s.equalsIgnoreCase(FORMATTING_ERROR)){
+            error = 1;
+        }else if (s.equalsIgnoreCase(PARSING_ERROR)){
+            error = 2;
+        }else if (s.equalsIgnoreCase(PATTERN_ERROR)){
+            error = 3;
+        }       
+    }
+    /**
+     * get error string based on error code 
+     *    
+     * @param   String msg           
+     */
+    public String getErrorString(){
+        String msg = "";
+        switch (error){
+        case 0:
+            msg = UNKNOWN_ERROR;
+            break;
+        case 1:
+            msg =  FORMATTING_ERROR;
+            break;
+        case 2:
+            msg =  PARSING_ERROR;
+            break;
+        case 3:
+            msg =  PATTERN_ERROR;
+            break;
+        }
+        return msg;
+    }
+    /**
+     * get error code 
+     *    
+     * @param   String msg           
+     */
+    public int getErrorCode(){      
+        return error;
+    }
+    
+    /**
+     * get the json version of this object to return to javascript
+     * @return
+     */
+    public JSONObject toJson() {
+        JSONObject obj = new JSONObject();
+        try {
+            obj.put("code", getErrorCode());
+            obj.put("message", getErrorString());
+        } catch (JSONException e) {
+            // never happens
+        }
+        return obj;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-android/framework/src/org/apache/cordova/HttpHandler.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/HttpHandler.java b/lib/cordova-android/framework/src/org/apache/cordova/HttpHandler.java
new file mode 100755
index 0000000..1de8302
--- /dev/null
+++ b/lib/cordova-android/framework/src/org/apache/cordova/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;
+
+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-cli/blob/d61deccd/lib/cordova-android/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java b/lib/cordova-android/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
new file mode 100644
index 0000000..a96b242
--- /dev/null
+++ b/lib/cordova-android/framework/src/org/apache/cordova/IceCreamCordovaWebViewClient.java
@@ -0,0 +1,83 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+package org.apache.cordova;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.cordova.api.CordovaInterface;
+import org.apache.cordova.api.LOG;
+
+import android.content.res.AssetManager;
+import android.net.Uri;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebView;
+
+public class IceCreamCordovaWebViewClient extends CordovaWebViewClient {
+
+
+    public IceCreamCordovaWebViewClient(CordovaInterface cordova) {
+        super(cordova);
+    }
+    
+    public IceCreamCordovaWebViewClient(CordovaInterface cordova, CordovaWebView view) {
+        super(cordova, view);
+    }
+
+    @Override
+    public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
+        if(url.contains("?") || url.contains("#")){
+            return generateWebResourceResponse(url);
+        } else {
+            return super.shouldInterceptRequest(view, url);
+        }
+    }
+
+    private WebResourceResponse generateWebResourceResponse(String url) {
+        final String ANDROID_ASSET = "file:///android_asset/";
+        if (url.startsWith(ANDROID_ASSET)) {
+            String niceUrl = url;
+            niceUrl = url.replaceFirst(ANDROID_ASSET, "");
+            if(niceUrl.contains("?")){
+                niceUrl = niceUrl.split("\\?")[0];
+            }
+            else if(niceUrl.contains("#"))
+            {
+                niceUrl = niceUrl.split("#")[0];
+            }
+
+            String mimetype = null;
+            if(niceUrl.endsWith(".html")){
+                mimetype = "text/html";
+            }
+
+            try {
+                AssetManager assets = cordova.getActivity().getAssets();
+                Uri uri = Uri.parse(niceUrl);
+                InputStream stream = assets.open(uri.getPath(), AssetManager.ACCESS_STREAMING);
+                WebResourceResponse response = new WebResourceResponse(mimetype, "UTF-8", stream);
+                return response;
+            } catch (IOException e) {
+                LOG.e("generateWebResourceResponse", e.getMessage(), e);
+            }
+        }
+        return null;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/cordova-cli/blob/d61deccd/lib/cordova-android/framework/src/org/apache/cordova/InAppBrowser.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/InAppBrowser.java b/lib/cordova-android/framework/src/org/apache/cordova/InAppBrowser.java
new file mode 100644
index 0000000..0d83432
--- /dev/null
+++ b/lib/cordova-android/framework/src/org/apache/cordova/InAppBrowser.java
@@ -0,0 +1,534 @@
+/*
+       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.StringTokenizer;
+
+import org.apache.cordova.api.CallbackContext;
+import org.apache.cordova.api.CordovaPlugin;
+import org.apache.cordova.api.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.annotation.SuppressLint;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+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.WebSettings;
+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 Dialog dialog;
+    private WebView inAppWebView;
+    private EditText edittext;
+    private boolean showLocationBar = true;
+    private CallbackContext callbackContext;
+
+    /**
+     * 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 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();
+                    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) {
+            showLocationBar = features.get(LOCATION).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("Done");
+                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 WebChromeClient());
+                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);
+                settings.setDatabaseEnabled(true);
+                String databasePath = cordova.getActivity().getApplicationContext().getDir("inAppBrowserDB", Context.MODE_PRIVATE).getPath();
+                settings.setDatabasePath(databasePath);
+                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);
+    }
+
+    /**
+     * 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;
+            } 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-cli/blob/d61deccd/lib/cordova-android/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java
----------------------------------------------------------------------
diff --git a/lib/cordova-android/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java b/lib/cordova-android/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java
new file mode 100755
index 0000000..280269b
--- /dev/null
+++ b/lib/cordova-android/framework/src/org/apache/cordova/LinearLayoutSoftKeyboardDetect.java
@@ -0,0 +1,105 @@
+/*
+       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.api.LOG;
+
+import android.content.Context;
+//import android.view.View.MeasureSpec;
+import android.widget.LinearLayout;
+
+/**
+ * This class is used to detect when the soft keyboard is shown and hidden in the web view.
+ */
+public class LinearLayoutSoftKeyboardDetect extends LinearLayout {
+
+    private static final String TAG = "SoftKeyboardDetect";
+
+    private int oldHeight = 0;  // Need to save the old height as not to send redundant events
+    private int oldWidth = 0; // Need to save old width for orientation change
+    private int screenWidth = 0;
+    private int screenHeight = 0;
+    private DroidGap app = null;
+
+    public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) {
+        super(context);
+        screenWidth = width;
+        screenHeight = height;
+        app = (DroidGap) context;
+    }
+
+    @Override
+    /**
+     * Start listening to new measurement events.  Fire events when the height
+     * gets smaller fire a show keyboard event and when height gets bigger fire
+     * a hide keyboard event.
+     *
+     * Note: We are using app.postMessage so that this is more compatible with the API
+     *
+     * @param widthMeasureSpec
+     * @param heightMeasureSpec
+     */
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        LOG.v(TAG, "We are in our onMeasure method");
+
+        // Get the current height of the visible part of the screen.
+        // This height will not included the status bar.\
+        int width, height;
+
+        height = MeasureSpec.getSize(heightMeasureSpec);
+        width = MeasureSpec.getSize(widthMeasureSpec);
+        LOG.v(TAG, "Old Height = %d", oldHeight);
+        LOG.v(TAG, "Height = %d", height);
+        LOG.v(TAG, "Old Width = %d", oldWidth);
+        LOG.v(TAG, "Width = %d", width);
+
+        // If the oldHeight = 0 then this is the first measure event as the app starts up.
+        // If oldHeight == height then we got a measurement change that doesn't affect us.
+        if (oldHeight == 0 || oldHeight == height) {
+            LOG.d(TAG, "Ignore this event");
+        }
+        // Account for orientation change and ignore this event/Fire orientation change
+        else if (screenHeight == width)
+        {
+            int tmp_var = screenHeight;
+            screenHeight = screenWidth;
+            screenWidth = tmp_var;
+            LOG.v(TAG, "Orientation Change");
+        }
+        // If the height as gotten bigger then we will assume the soft keyboard has
+        // gone away.
+        else if (height > oldHeight) {
+            if (app != null)
+                app.appView.sendJavascript("cordova.fireDocumentEvent('hidekeyboard');");
+        }
+        // If the height as gotten smaller then we will assume the soft keyboard has 
+        // been displayed.
+        else if (height < oldHeight) {
+            if (app != null)
+                app.appView.sendJavascript("cordova.fireDocumentEvent('showkeyboard');");
+        }
+
+        // Update the old height for the next event
+        oldHeight = height;
+        oldWidth = width;
+    }
+
+}