From 6312457425a0833564a4726998dac79729479b91 Mon Sep 17 00:00:00 2001 From: Simon MacDonald Date: Wed, 19 Sep 2012 14:08:52 -0400 Subject: [PATCH 01/33] CB-1469: Add Globalization Plug-in for Android --- framework/assets/js/cordova.android.js | 547 ++++++++++++++++- framework/res/xml/config.xml | 1 + .../src/org/apache/cordova/Globalization.java | 560 ++++++++++++++++++ .../apache/cordova/GlobalizationError.java | 72 +++ 4 files changed, 1178 insertions(+), 2 deletions(-) create mode 100644 framework/src/org/apache/cordova/Globalization.java create mode 100644 framework/src/org/apache/cordova/GlobalizationError.java diff --git a/framework/assets/js/cordova.android.js b/framework/assets/js/cordova.android.js index 7a7806e7fc..7f9a826836 100644 --- a/framework/assets/js/cordova.android.js +++ b/framework/assets/js/cordova.android.js @@ -1,6 +1,6 @@ -// commit d30179b30152b9383a80637e609cf2d785e1aa3e +// commit 65b59c7e484a9e5227fa7b4de8e784a8466b2ef5 -// File generated at :: Tue Sep 18 2012 11:34:26 GMT-0400 (EDT) +// File generated at :: Wed Sep 19 2012 13:58:04 GMT-0400 (EDT) /* Licensed to the Apache Software Foundation (ASF) under one @@ -729,6 +729,9 @@ module.exports = { geolocation: { path: 'cordova/plugin/geolocation' }, + globalization: { + path: 'cordova/plugin/globalization' + }, network: { children: { connection: { @@ -844,6 +847,9 @@ module.exports = { Flags: { path: 'cordova/plugin/Flags' }, + GlobalizationError: { + path: 'cordova/plugin/GlobalizationError' + }, LocalFileSystem: { path: 'cordova/plugin/LocalFileSystem' }, @@ -3118,6 +3124,22 @@ module.exports = Flags; }); +// file: lib/common/plugin/GlobalizationError.js +define("cordova/plugin/GlobalizationError", function(require, exports, module) { +var GlobalizationError = function(code, message) { + this.code = code || null; + this.message = message || ''; +}; + +// Globalization error codes +GlobalizationError.UNKNOWN_ERROR = 0; +GlobalizationError.FORMATTING_ERROR = 1; +GlobalizationError.PARSING_ERROR = 2; +GlobalizationError.PATTERN_ERROR = 3; + +module.exports = GlobalizationError; +}); + // file: lib/common/plugin/LocalFileSystem.js define("cordova/plugin/LocalFileSystem", function(require, exports, module) { @@ -5205,6 +5227,527 @@ module.exports = geolocation; }); +// file: lib/common/plugin/globalization.js +define("cordova/plugin/globalization", function(require, exports, module) { +var exec = require('cordova/exec'), + GlobalizationError = require('cordova/plugin/GlobalizationError'); + +var globalization = { + +getPreferredLanguage:function(successCB, failureCB) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.getPreferredLanguage Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.getPreferredLanguage Error: failureCB is not a function"); + return; + } + + exec(successCB, failureCB, "Globalization","getPreferredLanguage", []); +}, + +/** +* Returns the string identifier for the client's current locale setting. +* It returns the locale identifier string to the successCB callback with a +* properties object as a parameter. If there is an error getting the locale, +* then the errorCB callback is invoked. +* +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.value {String}: The locale identifier +* +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.getLocaleName(function (locale) {alert('locale:' + locale.value + '\n');}, +* function () {}); +*/ +getLocaleName:function(successCB, failureCB) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.getLocaleName Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.getLocaleName Error: failureCB is not a function"); + return; + } + exec(successCB, failureCB, "Globalization","getLocaleName", []); +}, + + +/** +* Returns a date formatted as a string according to the client's user preferences and +* calendar using the time zone of the client. It returns the formatted date string to the +* successCB callback with a properties object as a parameter. If there is an error +* formatting the date, then the errorCB callback is invoked. +* +* The defaults are: formatLenght="short" and selector="date and time" +* +* @param {Date} date +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* formatLength {String}: 'short', 'medium', 'long', or 'full' +* selector {String}: 'date', 'time', or 'date and time' +* +* @return Object.value {String}: The localized date string +* +* @error GlobalizationError.FORMATTING_ERROR +* +* Example +* globalization.dateToString(new Date(), +* function (date) {alert('date:' + date.value + '\n');}, +* function (errorCode) {alert(errorCode);}, +* {formatLength:'short'}); +*/ +dateToString:function(date, successCB, failureCB, options) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.dateToString Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.dateToString Error: failureCB is not a function"); + return; + } + + + if (date instanceof Date){ + var dateValue; + dateValue = date.valueOf(); + exec(successCB, failureCB, "Globalization", "dateToString", [{"date": dateValue, "options": options}]); + } + else { + console.log("Globalization.dateToString Error: date is not a Date object"); + } +}, + + +/** +* 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. It returns the date to the successCB +* callback with a properties object as a parameter. If there is an error +* parsing the date string, then the errorCB callback is invoked. +* +* The defaults are: formatLength="short" and selector="date and time" +* +* @param {String} dateString +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* formatLength {String}: 'short', 'medium', 'long', or 'full' +* selector {String}: 'date', 'time', or 'date and time' +* +* @return 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 +* +* @error GlobalizationError.PARSING_ERROR +* +* Example +* globalization.stringToDate('4/11/2011', +* function (date) { alert('Month:' + date.month + '\n' + +* 'Day:' + date.day + '\n' + +* 'Year:' + date.year + '\n');}, +* function (errorCode) {alert(errorCode);}, +* {selector:'date'}); +*/ +stringToDate:function(dateString, successCB, failureCB, options) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.stringToDate Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.stringToDate Error: failureCB is not a function"); + return; + } + if (typeof dateString == "string"){ + exec(successCB, failureCB, "Globalization", "stringToDate", [{"dateString": dateString, "options": options}]); + } + else { + console.log("Globalization.stringToDate Error: dateString is not a string"); + } +}, + + +/** +* Returns a pattern string for formatting and parsing dates according to the client's +* user preferences. It returns the pattern to the successCB callback with a +* properties object as a parameter. If there is an error obtaining the pattern, +* then the errorCB callback is invoked. +* +* The defaults are: formatLength="short" and selector="date and time" +* +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* formatLength {String}: 'short', 'medium', 'long', or 'full' +* selector {String}: 'date', 'time', or 'date and time' +* +* @return 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. +* +* @error GlobalizationError.PATTERN_ERROR +* +* Example +* globalization.getDatePattern( +* function (date) {alert('pattern:' + date.pattern + '\n');}, +* function () {}, +* {formatLength:'short'}); +*/ +getDatePattern:function(successCB, failureCB, options) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.getDatePattern Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.getDatePattern Error: failureCB is not a function"); + return; + } + + exec(successCB, failureCB, "Globalization", "getDatePattern", [{"options": options}]); +}, + + +/** +* Returns an array of either the names of the months or days of the week +* according to the client's user preferences and calendar. It returns the array of names to the +* successCB callback with a properties object as a parameter. If there is an error obtaining the +* names, then the errorCB callback is invoked. +* +* The defaults are: type="wide" and item="months" +* +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* type {String}: 'narrow' or 'wide' +* item {String}: 'months', or 'days' +* +* @return Object.value {Array{String}}: The array of names starting from either +* the first month in the year or the +* first day of the week. +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.getDateNames(function (names) { +* for(var i = 0; i < names.value.length; i++) { +* alert('Month:' + names.value[i] + '\n');}}, +* function () {}); +*/ +getDateNames:function(successCB, failureCB, options) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.getDateNames Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.getDateNames Error: failureCB is not a function"); + return; + } + exec(successCB, failureCB, "Globalization", "getDateNames", [{"options": options}]); +}, + +/** +* Returns whether daylight savings time is in effect for a given date using the client's +* time zone and calendar. It returns whether or not daylight savings time is in effect +* to the successCB callback with a properties object as a parameter. If there is an error +* reading the date, then the errorCB callback is invoked. +* +* @param {Date} date +* @param {Function} successCB +* @param {Function} errorCB +* +* @return 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. +* +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.isDayLightSavingsTime(new Date(), +* function (date) {alert('dst:' + date.dst + '\n');} +* function () {}); +*/ +isDayLightSavingsTime:function(date, successCB, failureCB) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.isDayLightSavingsTime Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.isDayLightSavingsTime Error: failureCB is not a function"); + return; + } + + + if (date instanceof Date){ + var dateValue; + dateValue = date.valueOf(); + exec(successCB, failureCB, "Globalization", "isDayLightSavingsTime", [{"date": dateValue}]); + } + else { + console.log("Globalization.isDayLightSavingsTime Error: date is not a Date object"); + } + +}, + +/** +* 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. +* It returns the day to the successCB callback with a properties object as a parameter. +* If there is an error obtaining the pattern, then the errorCB callback is invoked. +* +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.value {Number}: The number of the first day of the week. +* +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.getFirstDayOfWeek(function (day) +* { alert('Day:' + day.value + '\n');}, +* function () {}); +*/ +getFirstDayOfWeek:function(successCB, failureCB) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.getFirstDayOfWeek Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.getFirstDayOfWeek Error: failureCB is not a function"); + return; + } + + exec(successCB, failureCB, "Globalization", "getFirstDayOfWeek", []); +}, + + +/** +* Returns a number formatted as a string according to the client's user preferences. +* It returns the formatted number string to the successCB callback with a properties object as a +* parameter. If there is an error formatting the number, then the errorCB callback is invoked. +* +* The defaults are: type="decimal" +* +* @param {Number} number +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* type {String}: 'decimal', "percent", or 'currency' +* +* @return Object.value {String}: The formatted number string. +* +* @error GlobalizationError.FORMATTING_ERROR +* +* Example +* globalization.numberToString(3.25, +* function (number) {alert('number:' + number.value + '\n');}, +* function () {}, +* {type:'decimal'}); +*/ +numberToString:function(number, successCB, failureCB, options) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.numberToString Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.numberToString Error: failureCB is not a function"); + return; + } + + if(typeof number == "number") { + exec(successCB, failureCB, "Globalization", "numberToString", [{"number": number, "options": options}]); + } + else { + console.log("Globalization.numberToString Error: number is not a number"); + } +}, + +/** +* Parses a number formatted as a string according to the client's user preferences and +* returns the corresponding number. It returns the number to the successCB callback with a +* properties object as a parameter. If there is an error parsing the number string, then +* the errorCB callback is invoked. +* +* The defaults are: type="decimal" +* +* @param {String} numberString +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* type {String}: 'decimal', "percent", or 'currency' +* +* @return Object.value {Number}: The parsed number. +* +* @error GlobalizationError.PARSING_ERROR +* +* Example +* globalization.stringToNumber('1234.56', +* function (number) {alert('Number:' + number.value + '\n');}, +* function () { alert('Error parsing number');}); +*/ +stringToNumber:function(numberString, successCB, failureCB, options) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.stringToNumber Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.stringToNumber Error: failureCB is not a function"); + return; + } + + if(typeof numberString == "string") { + exec(successCB, failureCB, "Globalization", "stringToNumber", [{"numberString": numberString, "options": options}]); + } + else { + console.log("Globalization.stringToNumber Error: numberString is not a string"); + } +}, + +/** +* Returns a pattern string for formatting and parsing numbers according to the client's user +* preferences. It returns the pattern to the successCB callback with a properties object as a +* parameter. If there is an error obtaining the pattern, then the errorCB callback is invoked. +* +* The defaults are: type="decimal" +* +* @param {Function} successCB +* @param {Function} errorCB +* @param {Object} options {optional} +* type {String}: 'decimal', "percent", or 'currency' +* +* @return 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. +* +* @error GlobalizationError.PATTERN_ERROR +* +* Example +* globalization.getNumberPattern( +* function (pattern) {alert('Pattern:' + pattern.pattern + '\n');}, +* function () {}); +*/ +getNumberPattern:function(successCB, failureCB, options) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.getNumberPattern Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.getNumberPattern Error: failureCB is not a function"); + return; + } + + exec(successCB, failureCB, "Globalization", "getNumberPattern", [{"options": options}]); +}, + +/** +* Returns a pattern string for formatting and parsing currency values according to the client's +* user preferences and ISO 4217 currency code. It returns the pattern to the successCB callback with a +* properties object as a parameter. If there is an error obtaining the pattern, then the errorCB +* callback is invoked. +* +* @param {String} currencyCode +* @param {Function} successCB +* @param {Function} errorCB +* +* @return 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. +* +* @error GlobalizationError.FORMATTING_ERROR +* +* Example +* globalization.getCurrencyPattern('EUR', +* function (currency) {alert('Pattern:' + currency.pattern + '\n');} +* function () {}); +*/ +getCurrencyPattern:function(currencyCode, successCB, failureCB) { + // successCallback required + if (typeof successCB != "function") { + console.log("Globalization.getCurrencyPattern Error: successCB is not a function"); + return; + } + + // errorCallback required + if (typeof failureCB != "function") { + console.log("Globalization.getCurrencyPattern Error: failureCB is not a function"); + return; + } + + if(typeof currencyCode == "string") { + exec(successCB, failureCB, "Globalization", "getCurrencyPattern", [{"currencyCode": currencyCode}]); + } + else { + console.log("Globalization.getCurrencyPattern Error: currencyCode is not a currency code"); + } +} + +}; + +module.exports = globalization; + +}); + // file: lib/common/plugin/logger.js define("cordova/plugin/logger", function(require, exports, module) { diff --git a/framework/res/xml/config.xml b/framework/res/xml/config.xml index 4a6fffccb0..1117f8d13a 100644 --- a/framework/res/xml/config.xml +++ b/framework/res/xml/config.xml @@ -51,6 +51,7 @@ + diff --git a/framework/src/org/apache/cordova/Globalization.java b/framework/src/org/apache/cordova/Globalization.java new file mode 100644 index 0000000000..1c2a13cf8b --- /dev/null +++ b/framework/src/org/apache/cordova/Globalization.java @@ -0,0 +1,560 @@ +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.Plugin; +import org.apache.cordova.api.PluginResult; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import android.text.format.Time; + +/** + * + */ +public class Globalization extends Plugin { + //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 PluginResult execute(String action, JSONArray data, String callbackId) { + PluginResult.Status status = PluginResult.Status.OK; + JSONObject obj = new JSONObject(); + + try{ + if (action.equals(GETLOCALENAME)){ + obj = getLocaleName(); + return new PluginResult(status, obj); + }else if (action.equals(GETPREFERREDLANGUAGE)){ + obj = getPreferredLanguage(); + return new PluginResult(status, obj); + } else if (action.equalsIgnoreCase(DATETOSTRING)) { + obj = getDateToString(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(STRINGTODATE)){ + obj = getStringtoDate(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(GETDATEPATTERN)){ + obj = getDatePattern(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(GETDATENAMES)){ + obj = getDateNames(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(ISDAYLIGHTSAVINGSTIME)){ + obj = getIsDayLightSavingsTime(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(GETFIRSTDAYOFWEEK)){ + obj = getFirstDayOfWeek(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(NUMBERTOSTRING)){ + obj = getNumberToString(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(STRINGTONUMBER)){ + obj = getStringToNumber(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(GETNUMBERPATTERN)){ + obj = getNumberPattern(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(GETCURRENCYPATTERN)){ + obj = getCurrencyPattern(data); + return new PluginResult(PluginResult.Status.OK, obj); + } + }catch (GlobalizationError ge){ + return new PluginResult(PluginResult.Status.ERROR, ge.getErrorCode()); + }catch (Exception e){ + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } + return new PluginResult(PluginResult.Status.INVALID_ACTION); + } + /* + * @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 + */ + private JSONObject getDateNames(JSONArray options) throws GlobalizationError{ + JSONObject obj = new JSONObject(); + //String[] value; + JSONArray value = new JSONArray(); + List namesList = new ArrayList(); + final Map 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() { + 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; + } +} \ No newline at end of file diff --git a/framework/src/org/apache/cordova/GlobalizationError.java b/framework/src/org/apache/cordova/GlobalizationError.java new file mode 100644 index 0000000000..c83184519d --- /dev/null +++ b/framework/src/org/apache/cordova/GlobalizationError.java @@ -0,0 +1,72 @@ +package org.apache.cordova; + +/** + * @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; + } + +} \ No newline at end of file From 34840175f31b7bb13c1870592ba9c70f86e4bcc4 Mon Sep 17 00:00:00 2001 From: Joe Bowser Date: Wed, 19 Sep 2012 11:12:55 -0700 Subject: [PATCH 02/33] Adding headers and converting tabs to spaces --- .../src/org/apache/cordova/Globalization.java | 1071 +++++++++-------- .../apache/cordova/GlobalizationError.java | 101 +- 2 files changed, 605 insertions(+), 567 deletions(-) diff --git a/framework/src/org/apache/cordova/Globalization.java b/framework/src/org/apache/cordova/Globalization.java index 1c2a13cf8b..7d724c94e1 100644 --- a/framework/src/org/apache/cordova/Globalization.java +++ b/framework/src/org/apache/cordova/Globalization.java @@ -1,3 +1,22 @@ +/* + 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; @@ -26,535 +45,535 @@ * */ public class Globalization extends Plugin { - //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 PluginResult execute(String action, JSONArray data, String callbackId) { - PluginResult.Status status = PluginResult.Status.OK; - JSONObject obj = new JSONObject(); + //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 PluginResult execute(String action, JSONArray data, String callbackId) { + PluginResult.Status status = PluginResult.Status.OK; + JSONObject obj = new JSONObject(); - try{ - if (action.equals(GETLOCALENAME)){ - obj = getLocaleName(); - return new PluginResult(status, obj); - }else if (action.equals(GETPREFERREDLANGUAGE)){ - obj = getPreferredLanguage(); - return new PluginResult(status, obj); - } else if (action.equalsIgnoreCase(DATETOSTRING)) { - obj = getDateToString(data); - return new PluginResult(PluginResult.Status.OK, obj); - }else if(action.equalsIgnoreCase(STRINGTODATE)){ - obj = getStringtoDate(data); - return new PluginResult(PluginResult.Status.OK, obj); - }else if(action.equalsIgnoreCase(GETDATEPATTERN)){ - obj = getDatePattern(data); - return new PluginResult(PluginResult.Status.OK, obj); - }else if(action.equalsIgnoreCase(GETDATENAMES)){ - obj = getDateNames(data); - return new PluginResult(PluginResult.Status.OK, obj); - }else if(action.equalsIgnoreCase(ISDAYLIGHTSAVINGSTIME)){ - obj = getIsDayLightSavingsTime(data); - return new PluginResult(PluginResult.Status.OK, obj); - }else if(action.equalsIgnoreCase(GETFIRSTDAYOFWEEK)){ - obj = getFirstDayOfWeek(data); - return new PluginResult(PluginResult.Status.OK, obj); - }else if(action.equalsIgnoreCase(NUMBERTOSTRING)){ - obj = getNumberToString(data); - return new PluginResult(PluginResult.Status.OK, obj); - }else if(action.equalsIgnoreCase(STRINGTONUMBER)){ - obj = getStringToNumber(data); - return new PluginResult(PluginResult.Status.OK, obj); - }else if(action.equalsIgnoreCase(GETNUMBERPATTERN)){ - obj = getNumberPattern(data); - return new PluginResult(PluginResult.Status.OK, obj); - }else if(action.equalsIgnoreCase(GETCURRENCYPATTERN)){ - obj = getCurrencyPattern(data); - return new PluginResult(PluginResult.Status.OK, obj); - } - }catch (GlobalizationError ge){ - return new PluginResult(PluginResult.Status.ERROR, ge.getErrorCode()); - }catch (Exception e){ - return new PluginResult(PluginResult.Status.JSON_EXCEPTION); - } - return new PluginResult(PluginResult.Status.INVALID_ACTION); - } - /* - * @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 - * + try{ + if (action.equals(GETLOCALENAME)){ + obj = getLocaleName(); + return new PluginResult(status, obj); + }else if (action.equals(GETPREFERREDLANGUAGE)){ + obj = getPreferredLanguage(); + return new PluginResult(status, obj); + } else if (action.equalsIgnoreCase(DATETOSTRING)) { + obj = getDateToString(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(STRINGTODATE)){ + obj = getStringtoDate(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(GETDATEPATTERN)){ + obj = getDatePattern(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(GETDATENAMES)){ + obj = getDateNames(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(ISDAYLIGHTSAVINGSTIME)){ + obj = getIsDayLightSavingsTime(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(GETFIRSTDAYOFWEEK)){ + obj = getFirstDayOfWeek(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(NUMBERTOSTRING)){ + obj = getNumberToString(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(STRINGTONUMBER)){ + obj = getStringToNumber(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(GETNUMBERPATTERN)){ + obj = getNumberPattern(data); + return new PluginResult(PluginResult.Status.OK, obj); + }else if(action.equalsIgnoreCase(GETCURRENCYPATTERN)){ + obj = getCurrencyPattern(data); + return new PluginResult(PluginResult.Status.OK, obj); + } + }catch (GlobalizationError ge){ + return new PluginResult(PluginResult.Status.ERROR, ge.getErrorCode()); + }catch (Exception e){ + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } + return new PluginResult(PluginResult.Status.INVALID_ACTION); + } + /* + * @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 + */ + 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 - */ - private JSONObject getDateNames(JSONArray options) throws GlobalizationError{ - JSONObject obj = new JSONObject(); - //String[] value; - JSONArray value = new JSONArray(); - List namesList = new ArrayList(); - final Map 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()); - } + */ + 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 + */ + private JSONObject getDateNames(JSONArray options) throws GlobalizationError{ + JSONObject obj = new JSONObject(); + //String[] value; + JSONArray value = new JSONArray(); + List namesList = new ArrayList(); + final Map 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() { - 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)); - } + // 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() { + 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; - } -} \ No newline at end of file + //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; + } +} diff --git a/framework/src/org/apache/cordova/GlobalizationError.java b/framework/src/org/apache/cordova/GlobalizationError.java index c83184519d..5c1b09e785 100644 --- a/framework/src/org/apache/cordova/GlobalizationError.java +++ b/framework/src/org/apache/cordova/GlobalizationError.java @@ -1,41 +1,60 @@ +/* + 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; /** * @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; + * 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 - /** + /** + * + */ + 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; + public GlobalizationError(String s) { + if (s.equalsIgnoreCase(FORMATTING_ERROR)){ + error = 1; }else if (s.equalsIgnoreCase(PARSING_ERROR)){ - error = 2; + error = 2; }else if (s.equalsIgnoreCase(PATTERN_ERROR)){ - error = 3; - } + error = 3; + } } /** * get error string based on error code @@ -43,30 +62,30 @@ public GlobalizationError(String s) { * @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; + 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; + public int getErrorCode(){ + return error; } -} \ No newline at end of file +} From c206ac03350b6eb8fe38d1c87503e288ed8c558f Mon Sep 17 00:00:00 2001 From: Joe Bowser Date: Wed, 19 Sep 2012 13:47:09 -0700 Subject: [PATCH 03/33] Fixing CB-1504 --- .../src/org/apache/cordova/CordovaWebView.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/framework/src/org/apache/cordova/CordovaWebView.java b/framework/src/org/apache/cordova/CordovaWebView.java index ddae1ddac3..9c05e44972 100755 --- a/framework/src/org/apache/cordova/CordovaWebView.java +++ b/framework/src/org/apache/cordova/CordovaWebView.java @@ -50,6 +50,8 @@ Licensed to the Apache Software Foundation (ASF) under one import android.util.Log; import android.view.KeyEvent; import android.view.WindowManager; +import android.webkit.WebBackForwardList; +import android.webkit.WebHistoryItem; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebSettings.LayoutAlgorithm; @@ -572,12 +574,13 @@ public boolean backHistory() { // Check webview first to see if there is a history // This is needed to support curPage#diffLink, since they are added to appView's history, but not our history url array (JQMobile behavior) if (super.canGoBack()) { + printBackForwardList(); super.goBack(); return true; } // If our managed history has prev url - if (this.urls.size() > 1) { + if (this.urls.size() > 1 && !this.useBrowserHistory) { this.urls.pop(); // Pop current url String url = this.urls.pop(); // Pop prev url that we want to load, since it will be added back by loadUrl() this.loadUrl(url); @@ -937,4 +940,17 @@ static void enableUniversalAccess(WebSettings settings) { settings.setAllowUniversalAccessFromFileURLs(true); } } + + + + public void printBackForwardList() { + WebBackForwardList currentList = this.copyBackForwardList(); + int currentSize = currentList.getSize(); + for(int i = 0; i < currentSize; ++i) + { + WebHistoryItem item = currentList.getItemAtIndex(i); + String url = item.getUrl(); + LOG.d(TAG, "The URL at index: " + Integer.toString(i) + "is " + url ); + } + } } From 79682f5d523c0c985ad6c010fbbb37d333fc3765 Mon Sep 17 00:00:00 2001 From: Anis Kadri Date: Thu, 20 Sep 2012 16:17:59 -0700 Subject: [PATCH 04/33] updating windows create script test --- bin/tests/test_create_win.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/bin/tests/test_create_win.js b/bin/tests/test_create_win.js index 238e9165da..c634a94018 100644 --- a/bin/tests/test_create_win.js +++ b/bin/tests/test_create_win.js @@ -88,14 +88,9 @@ create_project.on('exit', function(code) { // TODO check that package name and activity name were substituted properly }); - // make sure plugins.xml was added - path.exists(util.format('%s/res/xml/plugins.xml', project_path), function(exists) { - assert(exists, 'plugins.xml did not get created'); - }); - - // make sure cordova.xml was added - path.exists(util.format('%s/res/xml/cordova.xml', project_path), function(exists) { - assert(exists, 'plugins.xml did not get created'); + // make sure config.xml was added + path.exists(util.format('%s/res/xml/config.xml', project_path), function(exists) { + assert(exists, 'config.xml did not get created'); }); // make sure cordova.jar was added From 9bc89c784f58e242c9e3dec19afbb39d7dd1f364 Mon Sep 17 00:00:00 2001 From: Joe Bowser Date: Thu, 20 Sep 2012 16:27:44 -0700 Subject: [PATCH 05/33] Switching to ONLINE_EVENT --- framework/src/org/apache/cordova/NativeToJsMessageQueue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/org/apache/cordova/NativeToJsMessageQueue.java b/framework/src/org/apache/cordova/NativeToJsMessageQueue.java index d2732c4621..f0c07a17a4 100755 --- a/framework/src/org/apache/cordova/NativeToJsMessageQueue.java +++ b/framework/src/org/apache/cordova/NativeToJsMessageQueue.java @@ -36,7 +36,7 @@ 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 = 1; + private static final int DEFAULT_BRIDGE_MODE = 3; // Set this to true to force plugin results to be encoding as // JS instead of the custom format (useful for benchmarking). From 610e0c984a9cb9c6e202a0f6e2a711f8e326d9d6 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Sun, 26 Aug 2012 16:07:17 -0700 Subject: [PATCH 06/33] Add progress callbacks, abort for FileTransfer.upload and FileTransfer.download --- .../apache/cordova/FileProgressResult.java | 63 ++++++++++ .../src/org/apache/cordova/FileTransfer.java | 112 +++++++++++++++--- .../org/apache/cordova/FileUploadResult.java | 12 +- 3 files changed, 171 insertions(+), 16 deletions(-) create mode 100644 framework/src/org/apache/cordova/FileProgressResult.java diff --git a/framework/src/org/apache/cordova/FileProgressResult.java b/framework/src/org/apache/cordova/FileProgressResult.java new file mode 100644 index 0000000000..d9811755df --- /dev/null +++ b/framework/src/org/apache/cordova/FileProgressResult.java @@ -0,0 +1,63 @@ +/* + 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; + +/** + * Encapsulates in-progress status of uploading or downloading a file to a remote server. + */ +public class FileProgressResult { + + private boolean lengthComputable = false; // declares whether total is known + private long loaded = 0; // bytes sent so far + private long total = 0; // bytes total, if known + + public boolean getLengthComputable() { + return lengthComputable; + } + + public void setLengthComputable(boolean computable) { + this.lengthComputable = computable; + } + + public long getLoaded() { + return loaded; + } + + public void setLoaded(long bytes) { + this.loaded = bytes; + } + + public long getTotal() { + return total; + } + + public void setTotal(long bytes) { + this.total = bytes; + } + + public JSONObject toJSONObject() throws JSONException { + return new JSONObject( + "{loaded:" + loaded + + ",total:" + total + + ",lengthComputable:" + (lengthComputable ? "true" : "false") + "}"); + } +} diff --git a/framework/src/org/apache/cordova/FileTransfer.java b/framework/src/org/apache/cordova/FileTransfer.java index 881caa54a9..ebce784f13 100644 --- a/framework/src/org/apache/cordova/FileTransfer.java +++ b/framework/src/org/apache/cordova/FileTransfer.java @@ -32,6 +32,7 @@ Licensed to the Apache Software Foundation (ASF) under one import java.net.URLDecoder; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.HashSet; import java.util.Iterator; import javax.net.ssl.HostnameVerifier; @@ -63,29 +64,44 @@ public class FileTransfer extends Plugin { public static int FILE_NOT_FOUND_ERR = 1; public static int INVALID_URL_ERR = 2; public static int CONNECTION_ERR = 3; + public static int ABORTED_ERR = 4; + + private static HashSet abortTriggered = new HashSet(); private SSLSocketFactory defaultSSLSocketFactory = null; private HostnameVerifier defaultHostnameVerifier = null; + static class AbortException extends Exception { + public AbortException(String str) { + super(str); + } + } + /* (non-Javadoc) * @see org.apache.cordova.api.Plugin#execute(java.lang.String, org.json.JSONArray, java.lang.String) */ @Override public PluginResult execute(String action, JSONArray args, String callbackId) { - String source = null; - String target = null; - try { - source = args.getString(0); - target = args.getString(1); - } catch (JSONException e) { - Log.d(LOG_TAG, "Missing source or target"); - return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "Missing source or target"); - } + if (action.equals("upload") || action.equals("download")) { + String source = null; + String target = null; + try { + source = args.getString(0); + target = args.getString(1); + } catch (JSONException e) { + Log.d(LOG_TAG, "Missing source or target"); + return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "Missing source or target"); + } - if (action.equals("upload")) { - return upload(URLDecoder.decode(source), target, args); - } else if (action.equals("download")) { - return download(source, target, args.optBoolean(2)); + if (action.equals("upload")) { + return upload(URLDecoder.decode(source), target, args, callbackId); + } else if (action.equals("download")) { + String objectId = args.getString(2); + boolean trustEveryone = args.optBoolean(3); + return download(source, target, trustEveryone, objectId, callbackId); + } + } else if (action.equals("abort")) { + return abort(args); } else { return new PluginResult(PluginResult.Status.INVALID_ACTION); } @@ -96,6 +112,7 @@ public PluginResult execute(String action, JSONArray args, String callbackId) { * @param source Full path of the file on the file system * @param target URL of the server to receive the file * @param args JSON Array of args + * @param callbackId callback id for optional progress reports * * args[2] fileKey Name of file request parameter * args[3] fileName File name to be used on server @@ -103,7 +120,7 @@ public PluginResult execute(String action, JSONArray args, String callbackId) { * args[5] params key:value pairs of user-defined parameters * @return FileUploadResult containing result of upload request */ - private PluginResult upload(String source, String target, JSONArray args) { + private PluginResult upload(String source, String target, JSONArray args, String callbackId) { Log.d(LOG_TAG, "upload " + source + " to " + target); HttpURLConnection conn = null; @@ -121,6 +138,7 @@ private PluginResult upload(String source, String target, JSONArray args) { if (headers == null && params != null) { headers = params.optJSONObject("headers"); } + String objectId = args.getString(9); Log.d(LOG_TAG, "fileKey: " + fileKey); Log.d(LOG_TAG, "fileName: " + fileName); @@ -129,9 +147,11 @@ private PluginResult upload(String source, String target, JSONArray args) { Log.d(LOG_TAG, "trustEveryone: " + trustEveryone); Log.d(LOG_TAG, "chunkedMode: " + chunkedMode); Log.d(LOG_TAG, "headers: " + headers); + Log.d(LOG_TAG, "objectId: " + objectId); // Create return object FileUploadResult result = new FileUploadResult(); + FileProgressResult progress = new FileProgressResult(); // Get a input stream of the file on the phone FileInputStream fileInputStream = (FileInputStream) getPathFromUri(source); @@ -285,6 +305,21 @@ private PluginResult upload(String source, String target, JSONArray args) { bytesAvailable = fileInputStream.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); bytesRead = fileInputStream.read(buffer, 0, bufferSize); + if (objectId != null) { + // Only send progress callbacks if the JS code sent us an object ID, + // so we don't spam old versions with unrecognized callbacks. + Log.d(LOG_TAG, "****** About to send a progress result from upload"); + progress.setLoaded(totalBytes); + PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject()); + progressResult.setKeepCallback(true); + success(progressResult, callbackId); + } + synchronized (abortTriggered) { + if (objectId != null && abortTriggered.contains(objectId)) { + abortTriggered.remove(objectId); + throw new AbortException("upload aborted"); + } + } } // send multipart form data necessary after file data... @@ -342,6 +377,10 @@ private PluginResult upload(String source, String target, JSONArray args) { } catch (JSONException e) { Log.e(LOG_TAG, e.getMessage(), e); return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } catch (AbortException e) { + JSONObject error = createFileTransferError(ABORTED_ERR, source, target, conn); + Log.e(LOG_TAG, error.toString(), e); + return new PluginResult(PluginResult.Status.ERROR, error); } catch (Throwable t) { // Shouldn't happen, but will JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn); @@ -459,7 +498,7 @@ private String getArgument(JSONArray args, int position, String defaultString) { * @param target Full path of the file on the file system * @return JSONObject the downloaded file */ - private PluginResult download(String source, String target, boolean trustEveryone) { + private PluginResult download(String source, String target, boolean trustEveryone, String objectId, String callbackId) { Log.d(LOG_TAG, "download " + source + " to " + target); HttpURLConnection connection = null; @@ -523,12 +562,36 @@ private PluginResult download(String source, String target, boolean trustEveryon byte[] buffer = new byte[1024]; int bytesRead = 0; + long totalBytes = 0; + FileProgressResult progress = new FileProgressResult(); + + if (connection.getContentEncoding() == null) { + // Only trust content-length header if no gzip etc + progress.setLengthComputable(true); + progress.setTotal(connection.getContentLength()); + } FileOutputStream outputStream = new FileOutputStream(file); // write bytes to file while ((bytesRead = inputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, bytesRead); + totalBytes += bytesRead; + if (objectId != null) { + // Only send progress callbacks if the JS code sent us an object ID, + // so we don't spam old versions with unrecognized callbacks. + Log.d(LOG_TAG, "****** About to send a progress result from download"); + progress.setLoaded(totalBytes); + PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject()); + progressResult.setKeepCallback(true); + success(progressResult, callbackId); + } + synchronized (abortTriggered) { + if (objectId != null && abortTriggered.contains(objectId)) { + abortTriggered.remove(objectId); + throw new AbortException("download aborted"); + } + } } outputStream.close(); @@ -621,4 +684,23 @@ private File getFileFromPath(String path) throws FileNotFoundException { return file; } + + /** + * Abort an ongoing upload or download. + * + * @param args args + */ + private PluginResult abort(JSONArray args) { + String objectId; + try { + objectId = args.getString(0); + } catch (JSONException e) { + Log.d(LOG_TAG, "Missing objectId"); + return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "Missing objectId"); + } + synchronized (abortTriggered) { + abortTriggered.add(objectId); + } + return new PluginResult(PluginResult.Status.OK); + } } diff --git a/framework/src/org/apache/cordova/FileUploadResult.java b/framework/src/org/apache/cordova/FileUploadResult.java index 36fae93fc1..b556869ec7 100644 --- a/framework/src/org/apache/cordova/FileUploadResult.java +++ b/framework/src/org/apache/cordova/FileUploadResult.java @@ -29,6 +29,7 @@ public class FileUploadResult { private long bytesSent = 0; // bytes sent private int responseCode = -1; // HTTP response code private String response = null; // HTTP response + private String objectId = null; // FileTransfer object id public long getBytesSent() { return bytesSent; @@ -54,10 +55,19 @@ public void setResponse(String response) { this.response = response; } + public String getObjectId() { + return objectId; + } + + public void setObjectId(String objectId) { + this.objectId = objectId; + } + public JSONObject toJSONObject() throws JSONException { return new JSONObject( "{bytesSent:" + bytesSent + ",responseCode:" + responseCode + - ",response:" + JSONObject.quote(response) + "}"); + ",response:" + JSONObject.quote(response) + + ",objectId:" + JSONObject.quote(objectId) + "}"); } } From df9d314361dab8307e4fa045591f3b65c63a5a7b Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Thu, 20 Sep 2012 23:39:09 -0400 Subject: [PATCH 07/33] Update JS to include FileProgress abort & progress support. --- framework/assets/js/cordova.android.js | 105 ++++++++++++++++++------- 1 file changed, 76 insertions(+), 29 deletions(-) diff --git a/framework/assets/js/cordova.android.js b/framework/assets/js/cordova.android.js index 7f9a826836..ad20adcfd5 100644 --- a/framework/assets/js/cordova.android.js +++ b/framework/assets/js/cordova.android.js @@ -1,6 +1,6 @@ -// commit 65b59c7e484a9e5227fa7b4de8e784a8466b2ef5 +// commit a9db8e3d85a08cab6ccf86f29cc476c1178d2d57 -// File generated at :: Wed Sep 19 2012 13:58:04 GMT-0400 (EDT) +// File generated at :: Thu Sep 20 2012 23:13:39 GMT-0400 (EDT) /* Licensed to the Apache Software Foundation (ASF) under one @@ -909,8 +909,6 @@ var cordova = require('cordova'), callback = require('cordova/plugin/android/callback'), polling = require('cordova/plugin/android/polling'), nativeApiProvider = require('cordova/plugin/android/nativeapiprovider'), - jsToNativeBridgeMode, - nativeToJsBridgeMode, jsToNativeModes = { PROMPT: 0, JS_OBJECT: 1, @@ -936,20 +934,16 @@ var cordova = require('cordova'), // to be executed. // Requires Android 3.2.4 or above. PRIVATE_API: 4 - }; + }, + jsToNativeBridgeMode, // Set lazily. + nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT; function androidExec(success, fail, service, action, args) { // Set default bridge modes if they have not already been set. if (jsToNativeBridgeMode === undefined) { - androidExec.setJsToNativeBridgeMode(jsToNativeModes.PROMPT); - } - if (nativeToJsBridgeMode === undefined) { - if (callback.isAvailable()) { - androidExec.setNativeToJsBridgeMode(nativeToJsModes.HANGING_GET); - } else { - androidExec.setNativeToJsBridgeMode(nativeToJsModes.POLLING); - } + androidExec.setJsToNativeBridgeMode(jsToNativeModes.JS_OBJECT); } + var callbackId = service + cordova.callbackId++, argsJson = JSON.stringify(args); if (success || fail) { @@ -2691,13 +2685,27 @@ module.exports = FileSystem; define("cordova/plugin/FileTransfer", function(require, exports, module) { var exec = require('cordova/exec'), - FileTransferError = require('cordova/plugin/FileTransferError'); + FileTransferError = require('cordova/plugin/FileTransferError'), + ProgressEvent = require('cordova/plugin/ProgressEvent'); + +function newProgressEvent(result) { + var pe = new ProgressEvent(); + pe.lengthComputable = result.lengthComputable; + pe.loaded = result.loaded; + pe.total = result.total; + return pe; +} + +var idCounter = 0; /** * FileTransfer uploads a file to a remote server. * @constructor */ -var FileTransfer = function() {}; +var FileTransfer = function() { + this._id = ++idCounter; + this.onprogress = null; // optional callback +}; /** * Given an absolute file path, uploads a file on the device to a remote server @@ -2740,7 +2748,17 @@ FileTransfer.prototype.upload = function(filePath, server, successCallback, erro errorCallback(error); }; - exec(successCallback, fail, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers]); + var self = this; + var win = function(result) { + if (typeof result.lengthComputable != "undefined") { + if (self.onprogress) { + return self.onprogress(newProgressEvent(result)); + } + } else { + return successCallback(result); + } + }; + exec(win, fail, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id]); }; /** @@ -2749,23 +2767,31 @@ FileTransfer.prototype.upload = function(filePath, server, successCallback, erro * @param target {String} Full path of the file on the device * @param successCallback (Function} Callback to be invoked when upload has completed * @param errorCallback {Function} Callback to be invoked upon error + * @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false */ -FileTransfer.prototype.download = function(source, target, successCallback, errorCallback) { +FileTransfer.prototype.download = function(source, target, successCallback, errorCallback, trustAllHosts) { // sanity parameter checking if (!source || !target) throw new Error("FileTransfer.download requires source URI and target URI parameters at the minimum."); + var self = this; var win = function(result) { - var entry = null; - if (result.isDirectory) { - entry = new (require('cordova/plugin/DirectoryEntry'))(); - } - else if (result.isFile) { - entry = new (require('cordova/plugin/FileEntry'))(); + if (typeof result.lengthComputable != "undefined") { + if (self.onprogress) { + return self.onprogress(newProgressEvent(result)); + } + } else { + var entry = null; + if (result.isDirectory) { + entry = new (require('cordova/plugin/DirectoryEntry'))(); + } + else if (result.isFile) { + entry = new (require('cordova/plugin/FileEntry'))(); + } + entry.isDirectory = result.isDirectory; + entry.isFile = result.isFile; + entry.name = result.name; + entry.fullPath = result.fullPath; + successCallback(entry); } - entry.isDirectory = result.isDirectory; - entry.isFile = result.isFile; - entry.name = result.name; - entry.fullPath = result.fullPath; - successCallback(entry); }; var fail = function(e) { @@ -2773,9 +2799,18 @@ FileTransfer.prototype.download = function(source, target, successCallback, erro errorCallback(error); }; - exec(win, errorCallback, 'FileTransfer', 'download', [source, target]); + exec(win, errorCallback, 'FileTransfer', 'download', [source, target, trustAllHosts, this._id]); }; +/** + * Aborts the ongoing file transfer on this object + * @param successCallback {Function} Callback to be invoked upon success + * @param errorCallback {Function} Callback to be invoked upon error + */ +FileTransfer.prototype.abort = function(successCallback, errorCallback) { + exec(successCallback, errorCallback, 'FileTransfer', 'abort', [this._id]); +} + module.exports = FileTransfer; }); @@ -2797,6 +2832,7 @@ var FileTransferError = function(code, source, target, status) { FileTransferError.FILE_NOT_FOUND_ERR = 1; FileTransferError.INVALID_URL_ERR = 2; FileTransferError.CONNECTION_ERR = 3; +FileTransferError.ABORT_ERR = 4; module.exports = FileTransferError; @@ -3126,6 +3162,15 @@ module.exports = Flags; // file: lib/common/plugin/GlobalizationError.js define("cordova/plugin/GlobalizationError", function(require, exports, module) { + + +/** + * Globalization error object + * + * @constructor + * @param code + * @param message + */ var GlobalizationError = function(code, message) { this.code = code || null; this.message = message || ''; @@ -3138,6 +3183,7 @@ GlobalizationError.PARSING_ERROR = 2; GlobalizationError.PATTERN_ERROR = 3; module.exports = GlobalizationError; + }); // file: lib/common/plugin/LocalFileSystem.js @@ -5229,6 +5275,7 @@ module.exports = geolocation; // file: lib/common/plugin/globalization.js define("cordova/plugin/globalization", function(require, exports, module) { + var exec = require('cordova/exec'), GlobalizationError = require('cordova/plugin/GlobalizationError'); From 17af41723547410778e194aa77c2b5f55309ca43 Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Thu, 20 Sep 2012 23:39:42 -0400 Subject: [PATCH 08/33] Fix up some minor FileTransfer bugs / warnings. - Catch abort error in download - Fix up merge conflicts - Fixed a couple of compiler warnings --- .../src/org/apache/cordova/FileTransfer.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/framework/src/org/apache/cordova/FileTransfer.java b/framework/src/org/apache/cordova/FileTransfer.java index ebce784f13..91e9357a8c 100644 --- a/framework/src/org/apache/cordova/FileTransfer.java +++ b/framework/src/org/apache/cordova/FileTransfer.java @@ -66,12 +66,13 @@ public class FileTransfer extends Plugin { public static int CONNECTION_ERR = 3; public static int ABORTED_ERR = 4; - private static HashSet abortTriggered = new HashSet(); + private static HashSet abortTriggered = new HashSet(); private SSLSocketFactory defaultSSLSocketFactory = null; private HostnameVerifier defaultHostnameVerifier = null; - static class AbortException extends Exception { + private static class AbortException extends Exception { + private static final long serialVersionUID = 1L; public AbortException(String str) { super(str); } @@ -95,10 +96,8 @@ public PluginResult execute(String action, JSONArray args, String callbackId) { if (action.equals("upload")) { return upload(URLDecoder.decode(source), target, args, callbackId); - } else if (action.equals("download")) { - String objectId = args.getString(2); - boolean trustEveryone = args.optBoolean(3); - return download(source, target, trustEveryone, objectId, callbackId); + } else { + return download(source, target, args, callbackId); } } else if (action.equals("abort")) { return abort(args); @@ -308,7 +307,6 @@ private PluginResult upload(String source, String target, JSONArray args, String if (objectId != null) { // Only send progress callbacks if the JS code sent us an object ID, // so we don't spam old versions with unrecognized callbacks. - Log.d(LOG_TAG, "****** About to send a progress result from upload"); progress.setLoaded(totalBytes); PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject()); progressResult.setKeepCallback(true); @@ -379,7 +377,6 @@ private PluginResult upload(String source, String target, JSONArray args, String return new PluginResult(PluginResult.Status.JSON_EXCEPTION); } catch (AbortException e) { JSONObject error = createFileTransferError(ABORTED_ERR, source, target, conn); - Log.e(LOG_TAG, error.toString(), e); return new PluginResult(PluginResult.Status.ERROR, error); } catch (Throwable t) { // Shouldn't happen, but will @@ -498,11 +495,13 @@ private String getArgument(JSONArray args, int position, String defaultString) { * @param target Full path of the file on the file system * @return JSONObject the downloaded file */ - private PluginResult download(String source, String target, boolean trustEveryone, String objectId, String callbackId) { + private PluginResult download(String source, String target, JSONArray args, String callbackId) { Log.d(LOG_TAG, "download " + source + " to " + target); HttpURLConnection connection = null; try { + boolean trustEveryone = args.optBoolean(2); + String objectId = args.getString(3); File file = getFileFromPath(target); // create needed directories @@ -580,7 +579,6 @@ private PluginResult download(String source, String target, boolean trustEveryon if (objectId != null) { // Only send progress callbacks if the JS code sent us an object ID, // so we don't spam old versions with unrecognized callbacks. - Log.d(LOG_TAG, "****** About to send a progress result from download"); progress.setLoaded(totalBytes); PluginResult progressResult = new PluginResult(PluginResult.Status.OK, progress.toJSONObject()); progressResult.setKeepCallback(true); @@ -617,6 +615,9 @@ private PluginResult download(String source, String target, boolean trustEveryon return new PluginResult(PluginResult.Status.IO_EXCEPTION, error); } + } catch (AbortException e) { + JSONObject error = createFileTransferError(ABORTED_ERR, source, target, connection); + return new PluginResult(PluginResult.Status.ERROR, error); } catch (FileNotFoundException e) { JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, connection); Log.d(LOG_TAG, "I got a file not found exception"); From 3d627446019bce3cf7c2c9b134d2664a014ebe94 Mon Sep 17 00:00:00 2001 From: Simon MacDonald Date: Fri, 21 Sep 2012 11:05:54 -0400 Subject: [PATCH 09/33] CB-1512: FileTransfer API and Mojolicious --- framework/src/org/apache/cordova/FileTransfer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/org/apache/cordova/FileTransfer.java b/framework/src/org/apache/cordova/FileTransfer.java index 91e9357a8c..67403cb654 100644 --- a/framework/src/org/apache/cordova/FileTransfer.java +++ b/framework/src/org/apache/cordova/FileTransfer.java @@ -59,7 +59,7 @@ public class FileTransfer extends Plugin { private static final String LOG_TAG = "FileTransfer"; private static final String LINE_START = "--"; private static final String LINE_END = "\r\n"; - private static final String BOUNDARY = "*****"; + private static final String BOUNDARY = "+++++"; public static int FILE_NOT_FOUND_ERR = 1; public static int INVALID_URL_ERR = 2; From 7eb12110d1ec33481a19adcc0666940972fd520d Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Fri, 21 Sep 2012 11:54:24 -0400 Subject: [PATCH 10/33] Add a work-around for a FileTransfer bug on 2.3 only. Fixes https://issues.apache.org/jira/browse/CB-1413 --- .../src/org/apache/cordova/FileTransfer.java | 59 ++++++++++++++----- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/framework/src/org/apache/cordova/FileTransfer.java b/framework/src/org/apache/cordova/FileTransfer.java index 67403cb654..72adc91a6d 100644 --- a/framework/src/org/apache/cordova/FileTransfer.java +++ b/framework/src/org/apache/cordova/FileTransfer.java @@ -24,6 +24,7 @@ Licensed to the Apache Software Foundation (ASF) under one import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; @@ -71,13 +72,46 @@ public class FileTransfer extends Plugin { private SSLSocketFactory defaultSSLSocketFactory = null; private HostnameVerifier defaultHostnameVerifier = null; - private static class AbortException extends Exception { + private static final class AbortException extends Exception { private static final long serialVersionUID = 1L; public AbortException(String str) { super(str); } } + /** + * Works around a bug on Android 2.3. + * http://code.google.com/p/android/issues/detail?id=14562 + */ + private static final class DoneHandlerInputStream extends FilterInputStream { + private boolean done; + + public DoneHandlerInputStream(InputStream stream) { + super(stream); + } + + @Override + public int read() throws IOException { + int result = done ? -1 : super.read(); + done = (result == -1); + return result; + } + + @Override + public int read(byte[] buffer) throws IOException { + int result = done ? -1 : super.read(buffer); + done = (result == -1); + return result; + } + + @Override + public int read(byte[] bytes, int offset, int count) throws IOException { + int result = done ? -1 : super.read(bytes, offset, count); + done = (result == -1); + return result; + } + } + /* (non-Javadoc) * @see org.apache.cordova.api.Plugin#execute(java.lang.String, org.json.JSONArray, java.lang.String) */ @@ -330,13 +364,7 @@ private PluginResult upload(String source, String target, JSONArray args, String //------------------ read the SERVER RESPONSE StringBuffer responseString = new StringBuffer(""); - DataInputStream inStream; - try { - inStream = new DataInputStream ( conn.getInputStream() ); - } catch(FileNotFoundException e) { - Log.e(LOG_TAG, e.toString(), e); - throw new IOException("Received error from server"); - } + DataInputStream inStream = new DataInputStream(getInputStream(conn)); String line; while (( line = inStream.readLine()) != null) { @@ -390,6 +418,13 @@ private PluginResult upload(String source, String target, JSONArray args, String } } + private InputStream getInputStream(HttpURLConnection conn) throws IOException { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + return new DoneHandlerInputStream(conn.getInputStream()); + } + return conn.getInputStream(); + } + // always verify the host - don't check for certificate final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { @@ -551,13 +586,7 @@ private PluginResult download(String source, String target, JSONArray args, Stri connection.connect(); Log.d(LOG_TAG, "Download file:" + url); - InputStream inputStream; - try { - inputStream = connection.getInputStream(); - } catch(FileNotFoundException e) { - Log.e(LOG_TAG, e.toString(), e); - throw new IOException("Received error from server"); - } + InputStream inputStream = getInputStream(connection); byte[] buffer = new byte[1024]; int bytesRead = 0; From 9961d9e54d09edfb25e38b0faa48b5d69af927b9 Mon Sep 17 00:00:00 2001 From: Braden Shepherdson Date: Wed, 12 Sep 2012 14:31:01 -0400 Subject: [PATCH 11/33] Add onReset to Plugin API, call on navigate. --- .../org/apache/cordova/CordovaWebViewClient.java | 11 +++++------ framework/src/org/apache/cordova/api/IPlugin.java | 7 +++++++ framework/src/org/apache/cordova/api/Plugin.java | 12 ++++++++++++ .../src/org/apache/cordova/api/PluginManager.java | 14 ++++++++++++++ 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/framework/src/org/apache/cordova/CordovaWebViewClient.java b/framework/src/org/apache/cordova/CordovaWebViewClient.java index fe0e9e9fee..5f907636d3 100755 --- a/framework/src/org/apache/cordova/CordovaWebViewClient.java +++ b/framework/src/org/apache/cordova/CordovaWebViewClient.java @@ -23,20 +23,15 @@ Licensed to the Apache Software Foundation (ASF) under one import org.apache.cordova.api.CordovaInterface; import org.apache.cordova.api.PluginResult; -import java.io.IOException; -import java.io.InputStream; - import org.apache.cordova.api.LOG; import org.json.JSONException; import org.json.JSONObject; import android.annotation.TargetApi; -import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.AssetManager; import android.graphics.Bitmap; import android.net.Uri; import android.net.http.SslError; @@ -44,7 +39,6 @@ Licensed to the Apache Software Foundation (ASF) under one import android.view.View; import android.webkit.HttpAuthHandler; import android.webkit.SslErrorHandler; -import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; @@ -269,6 +263,11 @@ public void onPageStarted(WebView view, String url, Bitmap favicon) { // Broadcast message that page has loaded this.appView.postMessage("onPageStarted", url); + + // Notify all plugins of the navigation, so they can clean up if necessary. + if (this.appView.pluginManager != null) { + this.appView.pluginManager.onReset(); + } } /** diff --git a/framework/src/org/apache/cordova/api/IPlugin.java b/framework/src/org/apache/cordova/api/IPlugin.java index 870bb9e11d..a33a663fed 100755 --- a/framework/src/org/apache/cordova/api/IPlugin.java +++ b/framework/src/org/apache/cordova/api/IPlugin.java @@ -116,4 +116,11 @@ public interface IPlugin { * @return Return true to prevent the URL from loading. Default is false. */ boolean onOverrideUrlLoading(String url); + + /** + * Called when the WebView does a top-level navigation or refreshes. + * + * Plugins should stop any long-running processes and clean up internal state. + */ + void onReset(); } diff --git a/framework/src/org/apache/cordova/api/Plugin.java b/framework/src/org/apache/cordova/api/Plugin.java index 84b67e00f1..c27b1e542a 100755 --- a/framework/src/org/apache/cordova/api/Plugin.java +++ b/framework/src/org/apache/cordova/api/Plugin.java @@ -23,6 +23,8 @@ Licensed to the Apache Software Foundation (ASF) under one import org.json.JSONObject; import android.content.Intent; +import android.util.Log; + /** * Plugin interface must be implemented by any plugin classes. * @@ -215,4 +217,14 @@ public void error(JSONObject message, String callbackId) { public void error(String message, String callbackId) { this.webView.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message), callbackId); } + + /** + * Called when the WebView does a top-level navigation or refreshes. + * + * Plugins should stop any long-running processes and clean up internal state. + * + * Does nothing by default. + */ + public void onReset() { + } } diff --git a/framework/src/org/apache/cordova/api/PluginManager.java b/framework/src/org/apache/cordova/api/PluginManager.java index aef63023d8..aaf614d78f 100755 --- a/framework/src/org/apache/cordova/api/PluginManager.java +++ b/framework/src/org/apache/cordova/api/PluginManager.java @@ -397,6 +397,20 @@ public boolean onOverrideUrlLoading(String url) { return false; } + /** + * Called when the app navigates or refreshes. + */ + public void onReset() { + Iterator it = this.entries.values().iterator(); + while (it.hasNext()) { + IPlugin plugin = it.next().plugin; + if (plugin != null) { + plugin.onReset(); + } + } + } + + private void pluginConfigurationMissing() { LOG.e(TAG, "====================================================================================="); LOG.e(TAG, "ERROR: plugin.xml is missing. Add res/xml/plugins.xml to your project."); From 2a9582ebb11d50dfc9413170a0fab69cd5d2fce1 Mon Sep 17 00:00:00 2001 From: Joe Bowser Date: Fri, 21 Sep 2012 11:48:33 -0700 Subject: [PATCH 12/33] Fixing CB-1521 - NullPointerException on Default Jellybean Emulator --- framework/src/org/apache/cordova/NetworkManager.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/framework/src/org/apache/cordova/NetworkManager.java b/framework/src/org/apache/cordova/NetworkManager.java index 37f0933e12..bf4102bc54 100755 --- a/framework/src/org/apache/cordova/NetworkManager.java +++ b/framework/src/org/apache/cordova/NetworkManager.java @@ -99,7 +99,9 @@ public void setContext(CordovaInterface cordova) { @SuppressWarnings("deprecation") @Override public void onReceive(Context context, Intent intent) { - updateConnectionInfo((NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO)); + // (The null check is for the ARM Emulator, please use Intel Emulator for better results) + if(webView != null) + updateConnectionInfo((NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO)); } }; cordova.getActivity().registerReceiver(this.receiver, intentFilter); @@ -198,10 +200,9 @@ private void sendUpdate(String type) { result.setKeepCallback(true); this.success(result, this.connectionCallbackId); - // Send to all plugins webView.postMessage("networkconnection", type); } - + /** * Determine the type of connection * From 313148136ae5f2e50d4b92a5de9e5e2eccaa9b09 Mon Sep 17 00:00:00 2001 From: Braden Shepherdson Date: Fri, 21 Sep 2012 14:51:45 -0400 Subject: [PATCH 13/33] Make AccelListener stop listening onReset() --- framework/src/org/apache/cordova/AccelListener.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/framework/src/org/apache/cordova/AccelListener.java b/framework/src/org/apache/cordova/AccelListener.java index 4fd8718139..df7181b569 100755 --- a/framework/src/org/apache/cordova/AccelListener.java +++ b/framework/src/org/apache/cordova/AccelListener.java @@ -19,6 +19,7 @@ Licensed to the Apache Software Foundation (ASF) under one package org.apache.cordova; import java.util.List; + import org.apache.cordova.api.CordovaInterface; import org.apache.cordova.api.Plugin; import org.apache.cordova.api.PluginResult; @@ -26,11 +27,11 @@ Licensed to the Apache Software Foundation (ASF) under one import org.json.JSONException; import org.json.JSONObject; +import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; -import android.content.Context; /** * This class listens to the accelerometer sensor and stores the latest @@ -224,6 +225,16 @@ public void onSensorChanged(SensorEvent event) { } } + /** + * Called when the view navigates. + */ + @Override + public void onReset() { + if (this.status == AccelListener.RUNNING) { + this.stop(); + } + } + // Sends an error back to JS private void fail(int code, String message) { // Error object From 8b6c9574df051ab5000cd8e8591cce57b6d4e3d0 Mon Sep 17 00:00:00 2001 From: Braden Shepherdson Date: Fri, 21 Sep 2012 15:33:56 -0400 Subject: [PATCH 14/33] Make AudioHandler stop and clean up on onReset() --- framework/src/org/apache/cordova/AudioHandler.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/framework/src/org/apache/cordova/AudioHandler.java b/framework/src/org/apache/cordova/AudioHandler.java index ccf9f6cf15..9ee226abc5 100644 --- a/framework/src/org/apache/cordova/AudioHandler.java +++ b/framework/src/org/apache/cordova/AudioHandler.java @@ -139,6 +139,14 @@ public void onDestroy() { this.players.clear(); } + /** + * Stop all audio players and recorders on navigate. + */ + @Override + public void onReset() { + onDestroy(); + } + /** * Called when a message is sent to plugin. * From 9318ee30bd3dc79e4a77451f2f7a8e3099ec8da7 Mon Sep 17 00:00:00 2001 From: Braden Shepherdson Date: Mon, 24 Sep 2012 11:35:35 -0400 Subject: [PATCH 15/33] Add onReset to BatteryListener. --- framework/src/org/apache/cordova/BatteryListener.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/framework/src/org/apache/cordova/BatteryListener.java b/framework/src/org/apache/cordova/BatteryListener.java index eb0ea98acf..4476cc3c7f 100755 --- a/framework/src/org/apache/cordova/BatteryListener.java +++ b/framework/src/org/apache/cordova/BatteryListener.java @@ -99,6 +99,13 @@ public void onDestroy() { removeBatteryListener(); } + /** + * Stop battery receiver. + */ + public void onReset() { + removeBatteryListener(); + } + /** * Stop the battery receiver and set it to null. */ From 20c885418e97759a15b9b6564d2f87fcd812dac6 Mon Sep 17 00:00:00 2001 From: Braden Shepherdson Date: Mon, 24 Sep 2012 11:40:06 -0400 Subject: [PATCH 16/33] Add onReset to CompassListener. --- framework/src/org/apache/cordova/CompassListener.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/framework/src/org/apache/cordova/CompassListener.java b/framework/src/org/apache/cordova/CompassListener.java index 83fcdd0a5d..458afaf470 100755 --- a/framework/src/org/apache/cordova/CompassListener.java +++ b/framework/src/org/apache/cordova/CompassListener.java @@ -33,6 +33,8 @@ Licensed to the Apache Software Foundation (ASF) under one import android.hardware.SensorManager; import android.content.Context; +import android.util.Log; + /** * This class listens to the compass sensor and stores the latest heading value. */ @@ -166,6 +168,13 @@ public void onDestroy() { this.stop(); } + /** + * Called when app has navigated and JS listeners have been destroyed. + */ + public void onReset() { + this.stop(); + } + //-------------------------------------------------------------------------- // LOCAL METHODS //-------------------------------------------------------------------------- From fed368d553b1823b4a4b9c7400e096073bd53af1 Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Mon, 24 Sep 2012 11:50:55 -0400 Subject: [PATCH 17/33] Set the total field for FileTransfer upload progress events. This also removes an incorrect assumption that content: InputStreams will be FileInputStreams. --- .../src/org/apache/cordova/FileTransfer.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/framework/src/org/apache/cordova/FileTransfer.java b/framework/src/org/apache/cordova/FileTransfer.java index 72adc91a6d..1917d8a78e 100644 --- a/framework/src/org/apache/cordova/FileTransfer.java +++ b/framework/src/org/apache/cordova/FileTransfer.java @@ -187,7 +187,7 @@ private PluginResult upload(String source, String target, JSONArray args, String FileProgressResult progress = new FileProgressResult(); // Get a input stream of the file on the phone - FileInputStream fileInputStream = (FileInputStream) getPathFromUri(source); + InputStream inputStream = getPathFromUri(source); DataOutputStream dos = null; @@ -295,12 +295,18 @@ private PluginResult upload(String source, String target, JSONArray args, String int stringLength = extraBytes.length + midParams.length() + tailParams.length() + fileNameBytes.length; Log.d(LOG_TAG, "String Length: " + stringLength); - int fixedLength = (int) fileInputStream.getChannel().size() + stringLength; + int fixedLength = -1; + if (inputStream instanceof FileInputStream) { + fixedLength = (int) ((FileInputStream)inputStream).getChannel().size() + stringLength; + progress.setLengthComputable(true); + progress.setTotal(fixedLength); + } Log.d(LOG_TAG, "Content Length: " + fixedLength); // setFixedLengthStreamingMode causes and OutOfMemoryException on pre-Froyo devices. // http://code.google.com/p/android/issues/detail?id=3164 // It also causes OOM if HTTPS is used, even on newer devices. chunkedMode = chunkedMode && (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO || useHttps); + chunkedMode = chunkedMode || (fixedLength == -1); if (chunkedMode) { conn.setChunkedStreamingMode(maxBufferSize); @@ -318,12 +324,12 @@ private PluginResult upload(String source, String target, JSONArray args, String dos.writeBytes(midParams); // create a buffer of maximum size - bytesAvailable = fileInputStream.available(); + bytesAvailable = inputStream.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); buffer = new byte[bufferSize]; // read file and write it into form... - bytesRead = fileInputStream.read(buffer, 0, bufferSize); + bytesRead = inputStream.read(buffer, 0, bufferSize); totalBytes = 0; long prevBytesRead = 0; @@ -335,9 +341,9 @@ private PluginResult upload(String source, String target, JSONArray args, String prevBytesRead = totalBytes; Log.d(LOG_TAG, "Uploaded " + totalBytes + " of " + fixedLength + " bytes"); } - bytesAvailable = fileInputStream.available(); + bytesAvailable = inputStream.available(); bufferSize = Math.min(bytesAvailable, maxBufferSize); - bytesRead = fileInputStream.read(buffer, 0, bufferSize); + bytesRead = inputStream.read(buffer, 0, bufferSize); if (objectId != null) { // Only send progress callbacks if the JS code sent us an object ID, // so we don't spam old versions with unrecognized callbacks. @@ -358,7 +364,7 @@ private PluginResult upload(String source, String target, JSONArray args, String dos.writeBytes(tailParams); // close streams - fileInputStream.close(); + inputStream.close(); dos.flush(); dos.close(); From 6192319f8cf4e64ff3f59eff9fda630412a6fbf9 Mon Sep 17 00:00:00 2001 From: Braden Shepherdson Date: Mon, 24 Sep 2012 14:20:36 -0400 Subject: [PATCH 18/33] Add onReset() to GeoBroker. --- framework/src/org/apache/cordova/GeoBroker.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/framework/src/org/apache/cordova/GeoBroker.java b/framework/src/org/apache/cordova/GeoBroker.java index d3bf6b3414..5b0714cd35 100755 --- a/framework/src/org/apache/cordova/GeoBroker.java +++ b/framework/src/org/apache/cordova/GeoBroker.java @@ -141,6 +141,14 @@ public void onDestroy() { 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(); From ba8577fa5f178474739f84d6e0b1df710453a4f0 Mon Sep 17 00:00:00 2001 From: Braden Shepherdson Date: Mon, 24 Sep 2012 14:20:52 -0400 Subject: [PATCH 19/33] Add onReset() to NetworkManager. --- framework/src/org/apache/cordova/NetworkManager.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/framework/src/org/apache/cordova/NetworkManager.java b/framework/src/org/apache/cordova/NetworkManager.java index 37f0933e12..e3ec9a9fad 100755 --- a/framework/src/org/apache/cordova/NetworkManager.java +++ b/framework/src/org/apache/cordova/NetworkManager.java @@ -153,6 +153,13 @@ public void onDestroy() { } } + /** + * Stop the network receiver on navigation. + */ + public void onReset() { + this.onDestroy(); + } + //-------------------------------------------------------------------------- // LOCAL METHODS //-------------------------------------------------------------------------- From dd4de16d1d6676b4b2a0f60c4ac0732b6e64555e Mon Sep 17 00:00:00 2001 From: Braden Shepherdson Date: Mon, 24 Sep 2012 14:21:05 -0400 Subject: [PATCH 20/33] Add onReset to Storage. --- framework/src/org/apache/cordova/Storage.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/framework/src/org/apache/cordova/Storage.java b/framework/src/org/apache/cordova/Storage.java index 551e915958..ea38544276 100755 --- a/framework/src/org/apache/cordova/Storage.java +++ b/framework/src/org/apache/cordova/Storage.java @@ -115,6 +115,13 @@ public void onDestroy() { } } + /** + * Clean up on navigation/refresh. + */ + public void onReset() { + this.onDestroy(); + } + // -------------------------------------------------------------------------- // LOCAL METHODS // -------------------------------------------------------------------------- From 7e3af6c2351d0b17d5ff4f2aa3c8b190a22f8ae0 Mon Sep 17 00:00:00 2001 From: Braden Shepherdson Date: Mon, 24 Sep 2012 14:21:18 -0400 Subject: [PATCH 21/33] Add onReset() to TempListener. --- framework/src/org/apache/cordova/TempListener.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/framework/src/org/apache/cordova/TempListener.java b/framework/src/org/apache/cordova/TempListener.java index c65bc8795b..5b594045ab 100755 --- a/framework/src/org/apache/cordova/TempListener.java +++ b/framework/src/org/apache/cordova/TempListener.java @@ -82,6 +82,13 @@ public void onDestroy() { this.stop(); } + /** + * Called on navigation. + */ + public void onReset() { + this.stop(); + } + //-------------------------------------------------------------------------- // LOCAL METHODS //-------------------------------------------------------------------------- From 2cd3ebc7a8db1cb883253796fed52cf39d9635b2 Mon Sep 17 00:00:00 2001 From: Braden Shepherdson Date: Mon, 24 Sep 2012 14:36:29 -0400 Subject: [PATCH 22/33] Fix NPE on reset with undefined NetworkListener. --- framework/src/org/apache/cordova/GeoBroker.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/framework/src/org/apache/cordova/GeoBroker.java b/framework/src/org/apache/cordova/GeoBroker.java index 5b0714cd35..e6798a993c 100755 --- a/framework/src/org/apache/cordova/GeoBroker.java +++ b/framework/src/org/apache/cordova/GeoBroker.java @@ -135,10 +135,14 @@ public boolean isSynch(String action) { * Stop listener. */ public void onDestroy() { - this.networkListener.destroy(); - this.gpsListener.destroy(); - this.networkListener = null; - this.gpsListener = null; + if (this.networkListener != null) { + this.networkListener.destroy(); + this.networkListener = null; + } + if (this.gpsListener != null) { + this.gpsListener.destroy(); + this.gpsListener = null; + } } /** From faa034a2053ac6988bf5196fc3ad88feb143657f Mon Sep 17 00:00:00 2001 From: Braden Shepherdson Date: Mon, 24 Sep 2012 14:37:04 -0400 Subject: [PATCH 23/33] Don't unregister the listener if it was never registered. --- framework/src/org/apache/cordova/NetworkManager.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/src/org/apache/cordova/NetworkManager.java b/framework/src/org/apache/cordova/NetworkManager.java index e3ec9a9fad..4a9d658258 100755 --- a/framework/src/org/apache/cordova/NetworkManager.java +++ b/framework/src/org/apache/cordova/NetworkManager.java @@ -69,6 +69,7 @@ public class NetworkManager extends Plugin { private static final String LOG_TAG = "NetworkManager"; private String connectionCallbackId; + private boolean registered = false; ConnectivityManager sockMan; BroadcastReceiver receiver; @@ -103,6 +104,7 @@ public void onReceive(Context context, Intent intent) { } }; cordova.getActivity().registerReceiver(this.receiver, intentFilter); + this.registered = true; } } @@ -144,9 +146,10 @@ public boolean isSynch(String action) { * Stop network receiver. */ public void onDestroy() { - if (this.receiver != null) { + 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); } From 54caa6e438f0edeb6764164b33ed5f51a02561c9 Mon Sep 17 00:00:00 2001 From: Marcel Kinard Date: Tue, 25 Sep 2012 13:09:40 -0400 Subject: [PATCH 24/33] Fail the build gracefully with helpful error messages if the local.properties file is missing, or if the commons-codec jar is missing, or if ant is not at the minimum required version. Also add a little more detail to README.md. --- README.md | 4 ++-- framework/build.xml | 40 ++++++++++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5bcd8fd7b3..d2d2e99f93 100755 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ indicate that the project has yet to be fully endorsed by the ASF. Requires --- -- Java JDK 1.5 -- Apache ANT +- Java JDK 1.5 or greater +- Apache ANT 1.8.0 or greater - Android SDK [http://developer.android.com](http://developer.android.com) - Apache Commons Codec [http://commons.apache.org/codec/](http://commons.apache.org/codec/) diff --git a/framework/build.xml b/framework/build.xml index e8910c353e..9e7b1278ca 100644 --- a/framework/build.xml +++ b/framework/build.xml @@ -26,9 +26,37 @@ - + + + + + + + + + + + + + + + + + + - + - + - + From 1b4096b01db41de633aeadd3e2fae43111722d97 Mon Sep 17 00:00:00 2001 From: Simon MacDonald Date: Wed, 26 Sep 2012 15:52:37 -0400 Subject: [PATCH 25/33] Guard against null pointer exception in ES File Explorer being used to get a picture --- framework/src/org/apache/cordova/FileUtils.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/framework/src/org/apache/cordova/FileUtils.java b/framework/src/org/apache/cordova/FileUtils.java index 1c8f084197..cbeff98a63 100755 --- a/framework/src/org/apache/cordova/FileUtils.java +++ b/framework/src/org/apache/cordova/FileUtils.java @@ -1048,10 +1048,16 @@ private InputStream getPathFromUri(String path) throws FileNotFoundException { */ @SuppressWarnings("deprecation") protected static String getRealPathFromURI(Uri contentUri, CordovaInterface cordova) { - String[] proj = { _DATA }; - Cursor cursor = cordova.getActivity().managedQuery(contentUri, proj, null, null, null); - int column_index = cursor.getColumnIndexOrThrow(_DATA); - cursor.moveToFirst(); - return cursor.getString(column_index); + String uri = contentUri.toString(); + if (uri.startsWith("content:")) { + String[] proj = { _DATA }; + Cursor cursor = cordova.getActivity().managedQuery(contentUri, proj, null, null, null); + int column_index = cursor.getColumnIndexOrThrow(_DATA); + cursor.moveToFirst(); + return cursor.getString(column_index); + } else { + return uri; + } + } } From 8eab8438cfa27b4939f217e26fa688533b0ebacf Mon Sep 17 00:00:00 2001 From: Anis Kadri Date: Wed, 26 Sep 2012 16:22:35 -0700 Subject: [PATCH 26/33] CB-1468 fixing paths with spaces --- bin/create.js | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/bin/create.js b/bin/create.js index 2e77494507..40bce8ff2a 100644 --- a/bin/create.js +++ b/bin/create.js @@ -163,40 +163,40 @@ if (!fso.FileExists(ROOT+'\\cordova-'+VERSION+'.jar') && // copy in the project template WScript.Echo("Copying template files..."); -exec('%comspec% /c xcopy '+ ROOT + '\\bin\\templates\\project\\res '+PROJECT_PATH+'\\res\\ /E /Y'); -exec('%comspec% /c xcopy '+ ROOT + '\\bin\\templates\\project\\assets '+PROJECT_PATH+'\\assets\\ /E /Y'); -exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\project\\AndroidManifest.xml ' + PROJECT_PATH + '\\AndroidManifest.xml /Y'); -exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\project\\Activity.java '+ ACTIVITY_PATH +' /Y'); +exec('%comspec% /c xcopy "'+ ROOT + '"\\bin\\templates\\project\\res '+PROJECT_PATH+'\\res\\ /E /Y'); +exec('%comspec% /c xcopy "'+ ROOT + '"\\bin\\templates\\project\\assets '+PROJECT_PATH+'\\assets\\ /E /Y'); +exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\project\\AndroidManifest.xml ' + PROJECT_PATH + '\\AndroidManifest.xml /Y'); +exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\project\\Activity.java '+ ACTIVITY_PATH +' /Y'); // check if we have the source or the distro files WScript.Echo("Copying js, jar & config.xml files..."); if(fso.FolderExists(ROOT + '\\framework')) { - exec('%comspec% /c copy '+ROOT+'\\framework\\assets\\www\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y'); - exec('%comspec% /c copy '+ROOT+'\\framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y'); + exec('%comspec% /c copy "'+ROOT+'"\\framework\\assets\\www\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y'); + exec('%comspec% /c copy "'+ROOT+'"\\framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y'); fso.CreateFolder(PROJECT_PATH + '\\res\\xml'); - exec('%comspec% /c copy '+ROOT+'\\framework\\res\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y'); + exec('%comspec% /c copy "'+ROOT+'"\\framework\\res\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y'); } else { // copy in cordova.js - exec('%comspec% /c copy '+ROOT+'\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y'); + exec('%comspec% /c copy "'+ROOT+'"\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y'); // copy in cordova.jar - exec('%comspec% /c copy '+ROOT+'\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y'); + exec('%comspec% /c copy "'+ROOT+'"\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y'); // copy in xml fso.CreateFolder(PROJECT_PATH + '\\res\\xml'); - exec('%comspec% /c copy '+ROOT+'\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y'); + exec('%comspec% /c copy "'+ROOT+'"\\xml\\config.xml ' + PROJECT_PATH + '\\res\\xml\\config.xml /Y'); } // copy cordova scripts fso.CreateFolder(PROJECT_PATH + '\\cordova'); createAppInfoJar(); WScript.Echo("Copying cordova command tools..."); -exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\appinfo.jar ' + PROJECT_PATH + '\\cordova\\appinfo.jar /Y'); -exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\cordova.js ' + PROJECT_PATH + '\\cordova\\cordova.js /Y'); -exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\cordova.bat ' + PROJECT_PATH + '\\cordova\\cordova.bat /Y'); -exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\clean.bat ' + PROJECT_PATH + '\\cordova\\clean.bat /Y'); -exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\debug.bat ' + PROJECT_PATH + '\\cordova\\debug.bat /Y'); -exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\log.bat ' + PROJECT_PATH + '\\cordova\\log.bat /Y'); -exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\emulate.bat ' + PROJECT_PATH + '\\cordova\\emulate.bat /Y'); -exec('%comspec% /c copy '+ROOT+'\\bin\\templates\\cordova\\BOOM.bat ' + PROJECT_PATH + '\\cordova\\BOOM.bat /Y'); +exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\appinfo.jar ' + PROJECT_PATH + '\\cordova\\appinfo.jar /Y'); +exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\cordova.js ' + PROJECT_PATH + '\\cordova\\cordova.js /Y'); +exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\cordova.bat ' + PROJECT_PATH + '\\cordova\\cordova.bat /Y'); +exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\clean.bat ' + PROJECT_PATH + '\\cordova\\clean.bat /Y'); +exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\debug.bat ' + PROJECT_PATH + '\\cordova\\debug.bat /Y'); +exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\log.bat ' + PROJECT_PATH + '\\cordova\\log.bat /Y'); +exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\emulate.bat ' + PROJECT_PATH + '\\cordova\\emulate.bat /Y'); +exec('%comspec% /c copy "'+ROOT+'"\\bin\\templates\\cordova\\BOOM.bat ' + PROJECT_PATH + '\\cordova\\BOOM.bat /Y'); // interpolate the activity name and package WScript.Echo("Updating AndroidManifest.xml and Main Activity..."); From 4021f26e76d6c19988d0faabc831242df4683d98 Mon Sep 17 00:00:00 2001 From: Simon MacDonald Date: Thu, 27 Sep 2012 11:16:43 -0400 Subject: [PATCH 27/33] Globalization plugin should return an error object and not a code --- .../src/org/apache/cordova/Globalization.java | 2 +- .../org/apache/cordova/GlobalizationError.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/framework/src/org/apache/cordova/Globalization.java b/framework/src/org/apache/cordova/Globalization.java index 7d724c94e1..98bf9e5a78 100644 --- a/framework/src/org/apache/cordova/Globalization.java +++ b/framework/src/org/apache/cordova/Globalization.java @@ -128,7 +128,7 @@ public PluginResult execute(String action, JSONArray data, String callbackId) { return new PluginResult(PluginResult.Status.OK, obj); } }catch (GlobalizationError ge){ - return new PluginResult(PluginResult.Status.ERROR, ge.getErrorCode()); + return new PluginResult(PluginResult.Status.ERROR, ge.toJson()); }catch (Exception e){ return new PluginResult(PluginResult.Status.JSON_EXCEPTION); } diff --git a/framework/src/org/apache/cordova/GlobalizationError.java b/framework/src/org/apache/cordova/GlobalizationError.java index 5c1b09e785..8a171d4240 100644 --- a/framework/src/org/apache/cordova/GlobalizationError.java +++ b/framework/src/org/apache/cordova/GlobalizationError.java @@ -19,6 +19,9 @@ Licensed to the Apache Software Foundation (ASF) under one package org.apache.cordova; +import org.json.JSONException; +import org.json.JSONObject; + /** * @description Exception class representing defined Globalization error codes * @Globalization error codes: @@ -88,4 +91,18 @@ 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; + } } From 1bf12842caab98ae8ecddb9520317df86e068efd Mon Sep 17 00:00:00 2001 From: Joshua Granick Date: Mon, 13 Aug 2012 15:56:54 -0700 Subject: [PATCH 28/33] Allow for predefined ANDROID_BIN value, fix for paths with spaces --- bin/create | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/bin/create b/bin/create index 2f6c0180cb..958a030144 100755 --- a/bin/create +++ b/bin/create @@ -30,10 +30,10 @@ then exit 0 fi -BUILD_PATH=$( cd "$( dirname "$0" )/.." && pwd ) +BUILD_PATH="$( cd "$( dirname "$0" )/.." && pwd )" VERSION=$(cat "$BUILD_PATH"/VERSION) -PROJECT_PATH=${1:-'./example'} +PROJECT_PATH="${1:-'./example'}" PACKAGE=${2:-"org.apache.cordova.example"} ACTIVITY=${3:-"cordovaExample"} @@ -87,19 +87,20 @@ function replace { trap on_error ERR trap on_exit EXIT -ANDROID_BIN=$( which android ) +ANDROID_BIN="${ANDROID_BIN:=$( which android )}" PACKAGE_AS_PATH=$(echo $PACKAGE | sed 's/\./\//g') ACTIVITY_PATH="$PROJECT_PATH"/src/$PACKAGE_AS_PATH/$ACTIVITY.java MANIFEST_PATH="$PROJECT_PATH"/AndroidManifest.xml TARGET=$($ANDROID_BIN list targets | grep id: | tail -1 | cut -f 2 -d ' ' ) -API_LEVEL=$($ANDROID_BIN list target | grep "API level:" | tail -n 1 | awk '{gsub(" API level:", "");print}' | cut -f 2 -d ' ') +TARGET=$("$ANDROID_BIN" list targets | grep id: | tail -1 | cut -f 2 -d ' ' ) +API_LEVEL=$("$ANDROID_BIN" list target | grep "API level:" | tail -n 1 | awk '{gsub(" API level:", "");print}' | cut -f 2 -d ' ') # if this a distribution release no need to build a jar if [ ! -e "$BUILD_PATH"/cordova-$VERSION.jar ] && [ -d "$BUILD_PATH"/framework ] then # update the cordova-android framework for the desired target - $ANDROID_BIN update project --target $TARGET --path "$BUILD_PATH"/framework &> /dev/null + "$ANDROID_BIN" update project --target $TARGET --path "$BUILD_PATH"/framework &> /dev/null if [ ! -e "$BUILD_PATH"/framework/libs/commons-codec-1.7.jar ]; then # Use curl to get the jar (TODO: Support Apache Mirrors) @@ -116,7 +117,7 @@ then fi # create new android project -$ANDROID_BIN create project --target $TARGET --path "$PROJECT_PATH" --package $PACKAGE --activity $ACTIVITY &> /dev/null +"$ANDROID_BIN" create project --target $TARGET --path "$PROJECT_PATH" --package $PACKAGE --activity $ACTIVITY &> /dev/null # copy project template cp -r "$BUILD_PATH"/bin/templates/project/assets "$PROJECT_PATH" From afcdccf78370b62936d1fe36fe7b46129a34aeb3 Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Mon, 24 Sep 2012 15:30:59 -0400 Subject: [PATCH 29/33] Add an app-wide thead pool to CordovaInterface. --- framework/src/org/apache/cordova/DroidGap.java | 9 +++++++++ .../src/org/apache/cordova/api/CordovaInterface.java | 8 +++++++- framework/src/org/apache/cordova/api/LegacyContext.java | 8 ++++++++ framework/src/org/apache/cordova/api/PluginManager.java | 3 +-- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/framework/src/org/apache/cordova/DroidGap.java b/framework/src/org/apache/cordova/DroidGap.java index b36320fafe..354a484cf3 100755 --- a/framework/src/org/apache/cordova/DroidGap.java +++ b/framework/src/org/apache/cordova/DroidGap.java @@ -19,6 +19,8 @@ Licensed to the Apache Software Foundation (ASF) under one package org.apache.cordova; import java.util.HashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import org.apache.cordova.api.IPlugin; import org.apache.cordova.api.LOG; @@ -142,6 +144,8 @@ public class DroidGap extends Activity implements CordovaInterface { protected LinearLayout root; protected boolean cancelLoadUrl = false; protected ProgressDialog spinnerDialog = null; + private final ExecutorService threadPool = Executors.newCachedThreadPool(); + // The initial URL for our app // ie http://server/path/index.html#abc?query @@ -1051,4 +1055,9 @@ else if ("exit".equals(id)) { } return null; } + + @Override + public ExecutorService getThreadPool() { + return threadPool; + } } diff --git a/framework/src/org/apache/cordova/api/CordovaInterface.java b/framework/src/org/apache/cordova/api/CordovaInterface.java index 93b31a0270..5a052c41bc 100755 --- a/framework/src/org/apache/cordova/api/CordovaInterface.java +++ b/framework/src/org/apache/cordova/api/CordovaInterface.java @@ -22,6 +22,8 @@ Licensed to the Apache Software Foundation (ASF) under one import android.content.Context; import android.content.Intent; +import java.util.concurrent.ExecutorService; + /** * The Cordova activity abstract class that is extended by DroidGap. * It is used to isolate plugin development, and remove dependency on entire Cordova library. @@ -67,5 +69,9 @@ public interface CordovaInterface { * @return Object or null */ public Object onMessage(String id, Object data); - + + /** + * Returns a shared thread pool that can be used for background tasks. + */ + public ExecutorService getThreadPool(); } diff --git a/framework/src/org/apache/cordova/api/LegacyContext.java b/framework/src/org/apache/cordova/api/LegacyContext.java index 073ba941e2..0d2228134e 100644 --- a/framework/src/org/apache/cordova/api/LegacyContext.java +++ b/framework/src/org/apache/cordova/api/LegacyContext.java @@ -29,6 +29,8 @@ import android.content.res.Resources; import android.util.Log; +import java.util.concurrent.ExecutorService; + @Deprecated public class LegacyContext implements CordovaInterface { private static final String LOG_TAG = "Deprecation Notice"; @@ -145,4 +147,10 @@ public void unbindService(ServiceConnection conn) { Log.i(LOG_TAG, "Replace ctx.unbindService() with cordova.getActivity().unbindService()"); this.cordova.getActivity().unbindService(conn); } + + @Override + public ExecutorService getThreadPool() { + Log.i(LOG_TAG, "Replace ctx.getThreadPool() with cordova.getThreadPool()"); + return this.cordova.getThreadPool(); + } } diff --git a/framework/src/org/apache/cordova/api/PluginManager.java b/framework/src/org/apache/cordova/api/PluginManager.java index aaf614d78f..589b103720 100755 --- a/framework/src/org/apache/cordova/api/PluginManager.java +++ b/framework/src/org/apache/cordova/api/PluginManager.java @@ -47,7 +47,6 @@ public class PluginManager { private final CordovaInterface ctx; private final CordovaWebView app; - private final ExecutorService execThreadPool = Executors.newCachedThreadPool(); // Flag to track first time through private boolean firstRun; @@ -226,7 +225,7 @@ public boolean exec(final String service, final String action, final String call runAsync = async && !plugin.isSynch(action); if (runAsync) { // Run this on a different thread so that this one can return back to JS - execThreadPool.execute(new Runnable() { + ctx.getThreadPool().execute(new Runnable() { public void run() { try { // Call execute on the plugin so that it can do it's thing From c7ce9598a8fbd9d617822edec5fb73bf1c535f76 Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Mon, 24 Sep 2012 23:32:31 -0400 Subject: [PATCH 30/33] Remove unused async arg from PluginManager.exec(). --- .../org/apache/cordova/CordovaWebViewClient.java | 2 +- .../src/org/apache/cordova/ExposedJsApi.java | 2 +- .../src/org/apache/cordova/api/PluginManager.java | 15 ++++++++------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/framework/src/org/apache/cordova/CordovaWebViewClient.java b/framework/src/org/apache/cordova/CordovaWebViewClient.java index 5f907636d3..55e0d9042d 100755 --- a/framework/src/org/apache/cordova/CordovaWebViewClient.java +++ b/framework/src/org/apache/cordova/CordovaWebViewClient.java @@ -101,7 +101,7 @@ private void handleExecUrl(String url) { String action = url.substring(idx2 + 1, idx3); String callbackId = url.substring(idx3 + 1, idx4); String jsonArgs = url.substring(idx4 + 1); - appView.pluginManager.exec(service, action, callbackId, jsonArgs, true /* async */); + appView.pluginManager.exec(service, action, callbackId, jsonArgs); } /** diff --git a/framework/src/org/apache/cordova/ExposedJsApi.java b/framework/src/org/apache/cordova/ExposedJsApi.java index 710b2e0c07..b386a402d9 100755 --- a/framework/src/org/apache/cordova/ExposedJsApi.java +++ b/framework/src/org/apache/cordova/ExposedJsApi.java @@ -40,7 +40,7 @@ public ExposedJsApi(PluginManager pluginManager, NativeToJsMessageQueue jsMessag public String exec(String service, String action, String callbackId, String arguments) throws JSONException { jsMessageQueue.setPaused(true); try { - boolean wasSync = pluginManager.exec(service, action, callbackId, arguments, true /* async */); + boolean wasSync = pluginManager.exec(service, action, callbackId, arguments); String ret = ""; if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING || wasSync) { ret = jsMessageQueue.popAndEncode(); diff --git a/framework/src/org/apache/cordova/api/PluginManager.java b/framework/src/org/apache/cordova/api/PluginManager.java index 589b103720..e51739b0f0 100755 --- a/framework/src/org/apache/cordova/api/PluginManager.java +++ b/framework/src/org/apache/cordova/api/PluginManager.java @@ -209,20 +209,16 @@ public void startupPlugins() { * this is an async plugin call. * @param args An Array literal string containing any arguments needed in the * plugin execute method. - * @param async Boolean indicating whether the calling JavaScript code is expecting an - * immediate return value. If true, either Cordova.callbackSuccess(...) or - * Cordova.callbackError(...) is called once the plugin code has executed. * @return Whether the task completed synchronously. */ - public boolean exec(final String service, final String action, final String callbackId, final String jsonArgs, final boolean async) { + public boolean exec(final String service, final String action, final String callbackId, final String jsonArgs) { PluginResult cr = null; - boolean runAsync = async; + final IPlugin plugin = this.getPlugin(service); + boolean runAsync = !plugin.isSynch(action); try { final JSONArray args = new JSONArray(jsonArgs); - final IPlugin plugin = this.getPlugin(service); //final CordovaInterface ctx = this.ctx; if (plugin != null) { - runAsync = async && !plugin.isSynch(action); if (runAsync) { // Run this on a different thread so that this one can return back to JS ctx.getThreadPool().execute(new Runnable() { @@ -266,6 +262,11 @@ public void run() { return true; } + @Deprecated + public boolean exec(String service, String action, String callbackId, String jsonArgs, boolean async) { + return exec(service, action, callbackId, jsonArgs); + } + /** * Get the plugin object that implements the service. * If the plugin object does not already exist, then create it. From 6f19a50c9843bbec41de6146dfa3047baf8d9a4b Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Fri, 28 Sep 2012 14:27:38 -0400 Subject: [PATCH 31/33] Update .classpath file to use commons-codec-1.7.jar --- framework/.classpath | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/.classpath b/framework/.classpath index 350a98b62c..1b06df2c65 100644 --- a/framework/.classpath +++ b/framework/.classpath @@ -3,7 +3,7 @@ - + From 2245db3e80c279bac3e534e0a80839b73d07f2af Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Fri, 28 Sep 2012 14:36:51 -0400 Subject: [PATCH 32/33] Delete CallbackServer.java --- .../org/apache/cordova/CallbackServer.java | 380 ------------------ .../apache/cordova/CordovaChromeClient.java | 18 - .../org/apache/cordova/CordovaWebView.java | 1 - .../apache/cordova/CordovaWebViewClient.java | 9 - .../cordova/NativeToJsMessageQueue.java | 18 +- 5 files changed, 4 insertions(+), 422 deletions(-) delete mode 100755 framework/src/org/apache/cordova/CallbackServer.java diff --git a/framework/src/org/apache/cordova/CallbackServer.java b/framework/src/org/apache/cordova/CallbackServer.java deleted file mode 100755 index dcf3b6e436..0000000000 --- a/framework/src/org/apache/cordova/CallbackServer.java +++ /dev/null @@ -1,380 +0,0 @@ -/* - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. -*/ -package org.apache.cordova; - -import java.io.BufferedReader; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; -import java.net.ServerSocket; -import java.net.Socket; -import java.util.LinkedList; - -/** - * This class provides a way for Java to run JavaScript in the web page that has loaded Cordova. - * The CallbackServer class implements an XHR server and a polling server with a list of JavaScript - * statements that are to be executed on the web page. - * - * The process flow for XHR is: - * 1. JavaScript makes an async XHR call. - * 2. The server holds the connection open until data is available. - * 3. The server writes the data to the client and closes the connection. - * 4. The server immediately starts listening for the next XHR call. - * 5. The client receives this XHR response, processes it. - * 6. The client sends a new async XHR request. - * - * The CallbackServer class requires the following permission in Android manifest file - * - * - * If the device has a proxy set, then XHR cannot be used, so polling must be used instead. - * This can be determined by the client by calling CallbackServer.usePolling(). - * - * The process flow for polling is: - * 1. The client calls CallbackServer.getJavascript() to retrieve next statement. - * 2. If statement available, then client processes it. - * 3. The client repeats #1 in loop. - */ -public class CallbackServer implements Runnable { - - @SuppressWarnings("unused") - private static final String LOG_TAG = "CallbackServer"; - - private ServerSocket waitSocket; - /** - * The list of JavaScript statements to be sent to JavaScript. - * This can be null when there are no messages available. - */ - private NativeToJsMessageQueue jsMessageQueue; - - /** - * The port to listen on. - */ - private int port; - - /** - * The server thread. - */ - private Thread serverThread; - - /** - * Indicates the server is running. - */ - private boolean active; - - - /** - * Indicates that polling should be used instead of XHR. - */ - private boolean usePolling = true; - - /** - * Security token to prevent other apps from accessing this callback server via XHR - */ - private String token; - - /** - * Constructor. - */ - public CallbackServer() { - //Log.d(LOG_TAG, "CallbackServer()"); - this.active = false; - this.port = 0; - } - - /** - * Init callback server and start XHR if running local app. - * - * If Cordova app is loaded from file://, then we can use XHR - * otherwise we have to use polling due to cross-domain security restrictions. - * - * @param url The URL of the Cordova app being loaded - */ - public void init(String url) { - //System.out.println("CallbackServer.start("+url+")"); - this.stopServer(); - this.port = 0; - - // Determine if XHR or polling is to be used - if ((url != null) && !url.startsWith("file://")) { - this.usePolling = true; - this.stopServer(); - } - else if (android.net.Proxy.getDefaultHost() != null) { - this.usePolling = true; - this.stopServer(); - } - else { - this.usePolling = false; - this.startServer(); - } - } - - /** - * Return if polling is being used instead of XHR. - * @return - */ - public boolean usePolling() { - return this.usePolling; - } - - /** - * Get the port that this server is running on. - * @return - */ - public int getPort() { - return this.port; - } - - /** - * Get the security token that this server requires when calling getJavascript(). - * @return - */ - public String getToken() { - return this.token; - } - - /** - * Start the server on a new thread. - */ - public void startServer() { - //Log.d(LOG_TAG, "CallbackServer.startServer()"); - this.active = false; - - // Start server on new thread - this.serverThread = new Thread(this); - this.serverThread.start(); - } - - /** - * Restart the server on a new thread. - */ - public void restartServer() { - - // Stop server - this.stopServer(); - - // Start server again - this.startServer(); - } - - /** - * Start running the server. - * This is called automatically when the server thread is started. - */ - public void run() { - - // Start server - try { - this.active = true; - String request; - waitSocket = new ServerSocket(0); - this.port = waitSocket.getLocalPort(); - //Log.d(LOG_TAG, "CallbackServer -- using port " +this.port); - this.token = java.util.UUID.randomUUID().toString(); - //Log.d(LOG_TAG, "CallbackServer -- using token "+this.token); - - while (this.active) { - //Log.d(LOG_TAG, "CallbackServer: Waiting for data on socket"); - Socket connection = waitSocket.accept(); - BufferedReader xhrReader = new BufferedReader(new InputStreamReader(connection.getInputStream()), 40); - DataOutputStream output = new DataOutputStream(connection.getOutputStream()); - request = xhrReader.readLine(); - String response = ""; - //Log.d(LOG_TAG, "CallbackServerRequest="+request); - if (this.active && (request != null)) { - if (request.contains("GET")) { - - // Get requested file - String[] requestParts = request.split(" "); - - // Must have security token - if ((requestParts.length == 3) && (requestParts[1].substring(1).equals(this.token))) { - //Log.d(LOG_TAG, "CallbackServer -- Processing GET request"); - String payload = null; - - // Wait until there is some data to send, or send empty data every 10 sec - // to prevent XHR timeout on the client - while (this.active) { - if (jsMessageQueue != null) { - payload = jsMessageQueue.popAndEncode(); - if (payload != null) { - break; - } - } - synchronized (this) { - try { - this.wait(10000); // prevent timeout from happening - //Log.d(LOG_TAG, "CallbackServer>>> break <<<"); - break; - } catch (Exception e) { - } - } - } - - // If server is still running - if (this.active) { - - // If no data, then send 404 back to client before it times out - if (payload == null) { - //Log.d(LOG_TAG, "CallbackServer -- sending data 0"); - response = "HTTP/1.1 404 NO DATA\r\n\r\n "; // need to send content otherwise some Android devices fail, so send space - } - else { - //Log.d(LOG_TAG, "CallbackServer -- sending item"); - response = "HTTP/1.1 200 OK\r\n\r\n"; - response += encode(payload, "UTF-8"); - } - } - else { - response = "HTTP/1.1 503 Service Unavailable\r\n\r\n "; - } - } - else { - response = "HTTP/1.1 403 Forbidden\r\n\r\n "; - } - } - else { - response = "HTTP/1.1 400 Bad Request\r\n\r\n "; - } - //Log.d(LOG_TAG, "CallbackServer: response="+response); - //Log.d(LOG_TAG, "CallbackServer: closing output"); - output.writeBytes(response); - output.flush(); - } - output.close(); - xhrReader.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - this.active = false; - //Log.d(LOG_TAG, "CallbackServer.startServer() - EXIT"); - } - - /** - * Stop server. - * This stops the thread that the server is running on. - */ - public void stopServer() { - //Log.d(LOG_TAG, "CallbackServer.stopServer()"); - if (this.active) { - this.active = false; - - try { waitSocket.close(); } catch (IOException ignore) {} - - // Break out of server wait - synchronized (this) { - this.notify(); - } - } - } - - /** - * Destroy - */ - public void destroy() { - this.stopServer(); - } - - public void onNativeToJsMessageAvailable(NativeToJsMessageQueue queue) { - synchronized (this) { - this.jsMessageQueue = queue; - this.notify(); - } - } - - /* The Following code has been modified from original implementation of URLEncoder */ - - /* start */ - - /* - * 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. - */ - static final String digits = "0123456789ABCDEF"; - - /** - * This will encode the return value to JavaScript. We revert the encoding for - * common characters that don't require encoding to reduce the size of the string - * being passed to JavaScript. - * - * @param s to be encoded - * @param enc encoding type - * @return encoded string - */ - public static String encode(String s, String enc) throws UnsupportedEncodingException { - if (s == null || enc == null) { - throw new NullPointerException(); - } - // check for UnsupportedEncodingException - "".getBytes(enc); - - // Guess a bit bigger for encoded form - StringBuilder buf = new StringBuilder(s.length() + 16); - int start = -1; - for (int i = 0; i < s.length(); i++) { - char ch = s.charAt(i); - if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') - || (ch >= '0' && ch <= '9') - || " .-*_'(),<>=?@[]{}:~\"\\/;!".indexOf(ch) > -1) { - if (start >= 0) { - convert(s.substring(start, i), buf, enc); - start = -1; - } - if (ch != ' ') { - buf.append(ch); - } else { - buf.append(' '); - } - } else { - if (start < 0) { - start = i; - } - } - } - if (start >= 0) { - convert(s.substring(start, s.length()), buf, enc); - } - return buf.toString(); - } - - private static void convert(String s, StringBuilder buf, String enc) throws UnsupportedEncodingException { - byte[] bytes = s.getBytes(enc); - for (int j = 0; j < bytes.length; j++) { - buf.append('%'); - buf.append(digits.charAt((bytes[j] & 0xf0) >> 4)); - buf.append(digits.charAt(bytes[j] & 0xf)); - } - } - - /* end */ -} diff --git a/framework/src/org/apache/cordova/CordovaChromeClient.java b/framework/src/org/apache/cordova/CordovaChromeClient.java index 398286d22c..2f4cdff949 100755 --- a/framework/src/org/apache/cordova/CordovaChromeClient.java +++ b/framework/src/org/apache/cordova/CordovaChromeClient.java @@ -226,24 +226,6 @@ else if (defaultValue != null && defaultValue.equals("gap_init:")) { result.confirm("OK"); } - // Calling into CallbackServer - else if (reqOk && defaultValue != null && defaultValue.equals("gap_callbackServer:")) { - String r = ""; - if (message.equals("usePolling")) { - r = "" + this.appView.callbackServer.usePolling(); - } - else if (message.equals("restartServer")) { - this.appView.callbackServer.restartServer(); - } - else if (message.equals("getPort")) { - r = Integer.toString(this.appView.callbackServer.getPort()); - } - else if (message.equals("getToken")) { - r = this.appView.callbackServer.getToken(); - } - result.confirm(r); - } - // Show dialog else { final JsPromptResult res = result; diff --git a/framework/src/org/apache/cordova/CordovaWebView.java b/framework/src/org/apache/cordova/CordovaWebView.java index 9c05e44972..106f60d9db 100755 --- a/framework/src/org/apache/cordova/CordovaWebView.java +++ b/framework/src/org/apache/cordova/CordovaWebView.java @@ -67,7 +67,6 @@ public class CordovaWebView extends WebView { private ArrayList keyUpCodes = new ArrayList(); public PluginManager pluginManager; - public CallbackServer callbackServer; private boolean paused; private BroadcastReceiver receiver; diff --git a/framework/src/org/apache/cordova/CordovaWebViewClient.java b/framework/src/org/apache/cordova/CordovaWebViewClient.java index 55e0d9042d..b0c318cdba 100755 --- a/framework/src/org/apache/cordova/CordovaWebViewClient.java +++ b/framework/src/org/apache/cordova/CordovaWebViewClient.java @@ -255,12 +255,6 @@ public void onPageStarted(WebView view, String url, Bitmap favicon) { // Flush stale messages. this.appView.jsMessageQueue.reset(); - // Create callback server - if (this.appView.callbackServer == null) { - this.appView.callbackServer = new CallbackServer(); - } - this.appView.callbackServer.init(url); - // Broadcast message that page has loaded this.appView.postMessage("onPageStarted", url); @@ -328,9 +322,6 @@ public void run() { // Shutdown if blank loaded if (url.equals("about:blank")) { - if (this.appView.callbackServer != null) { - this.appView.callbackServer.destroy(); - } appView.postMessage("exit", null); } } diff --git a/framework/src/org/apache/cordova/NativeToJsMessageQueue.java b/framework/src/org/apache/cordova/NativeToJsMessageQueue.java index f0c07a17a4..e1291990aa 100755 --- a/framework/src/org/apache/cordova/NativeToJsMessageQueue.java +++ b/framework/src/org/apache/cordova/NativeToJsMessageQueue.java @@ -80,12 +80,11 @@ public class NativeToJsMessageQueue { public NativeToJsMessageQueue(CordovaWebView webView, CordovaInterface cordova) { this.cordova = cordova; this.webView = webView; - registeredListeners = new BridgeMode[5]; + registeredListeners = new BridgeMode[4]; registeredListeners[0] = null; // Polling. Requires no logic. - registeredListeners[1] = new CallbackBridgeMode(); - registeredListeners[2] = new LoadUrlBridgeMode(); - registeredListeners[3] = new OnlineEventsBridgeMode(); - registeredListeners[4] = new PrivateApiBridgeMode(); + registeredListeners[1] = new LoadUrlBridgeMode(); + registeredListeners[2] = new OnlineEventsBridgeMode(); + registeredListeners[3] = new PrivateApiBridgeMode(); reset(); } @@ -270,15 +269,6 @@ private interface BridgeMode { void onNativeToJsMessageAvailable(); } - /** Uses a local server to send messages to JS via an XHR */ - private class CallbackBridgeMode implements BridgeMode { - public void onNativeToJsMessageAvailable() { - if (webView.callbackServer != null) { - webView.callbackServer.onNativeToJsMessageAvailable(NativeToJsMessageQueue.this); - } - } - } - /** Uses webView.loadUrl("javascript:") to execute messages. */ private class LoadUrlBridgeMode implements BridgeMode { final Runnable runnable = new Runnable() { From 64c6cbe30351cf642516865d4db3f3330d1f6d95 Mon Sep 17 00:00:00 2001 From: Andrew Grieve Date: Fri, 28 Sep 2012 14:36:36 -0400 Subject: [PATCH 33/33] Update JS snapshot after deleting callback server. --- framework/assets/js/cordova.android.js | 223 +++++++------------------ 1 file changed, 64 insertions(+), 159 deletions(-) diff --git a/framework/assets/js/cordova.android.js b/framework/assets/js/cordova.android.js index ad20adcfd5..e2e5bfd40f 100644 --- a/framework/assets/js/cordova.android.js +++ b/framework/assets/js/cordova.android.js @@ -1,6 +1,6 @@ -// commit a9db8e3d85a08cab6ccf86f29cc476c1178d2d57 +// commit 968764b2f67ff2ed755eace083b83f395cf0e9c2 -// File generated at :: Thu Sep 20 2012 23:13:39 GMT-0400 (EDT) +// File generated at :: Fri Sep 28 2012 14:33:38 GMT-0400 (EDT) /* Licensed to the Apache Software Foundation (ASF) under one @@ -137,7 +137,7 @@ window.addEventListener = function(evt, handler, capture) { document.removeEventListener = function(evt, handler, capture) { var e = evt.toLowerCase(); - // If unsubcribing from an event that is handled by a plugin + // If unsubscribing from an event that is handled by a plugin if (typeof documentEventHandlers[e] != "undefined") { documentEventHandlers[e].unsubscribe(handler); } else { @@ -147,7 +147,7 @@ document.removeEventListener = function(evt, handler, capture) { window.removeEventListener = function(evt, handler, capture) { var e = evt.toLowerCase(); - // If unsubcribing from an event that is handled by a plugin + // If unsubscribing from an event that is handled by a plugin if (typeof windowEventHandlers[e] != "undefined") { windowEventHandlers[e].unsubscribe(handler); } else { @@ -196,7 +196,7 @@ var cordova = { delete documentEventHandlers[event]; }, /** - * Retreive original event handlers that were replaced by Cordova + * Retrieve original event handlers that were replaced by Cordova * * @return object */ @@ -245,7 +245,9 @@ var cordova = { /** * Plugin callback mechanism. */ - callbackId: 0, + // Randomize the starting callbackId to avoid collisions after refreshing or navigating. + // This way, it's very unlikely that any new callback would get the same callbackId as an old callback. + callbackId: Math.floor(Math.random() * 2000000000), callbacks: {}, callbackStatus: { NO_RESULT: 0, @@ -412,7 +414,7 @@ function recursiveMerge(target, src) { module.exports = { build: function (objects) { return { - intoButDontClobber: function (target) { + intoButDoNotClobber: function (target) { include(target, objects, false, false); }, intoAndClobber: function(target) { @@ -895,7 +897,7 @@ define("cordova/exec", function(require, exports, module) { * Execute a cordova command. It is up to the native side whether this action * is synchronous or asynchronous. The native side can return: * Synchronous: PluginResult object as a JSON string - * Asynchrounous: Empty string "" + * Asynchronous: Empty string "" * If async, the native side will cordova.callbackSuccess or cordova.callbackError, * depending upon the result of the action. * @@ -906,8 +908,6 @@ define("cordova/exec", function(require, exports, module) { * @param {String[]} [args] Zero or more arguments to pass to the method */ var cordova = require('cordova'), - callback = require('cordova/plugin/android/callback'), - polling = require('cordova/plugin/android/polling'), nativeApiProvider = require('cordova/plugin/android/nativeapiprovider'), jsToNativeModes = { PROMPT: 0, @@ -920,9 +920,6 @@ var cordova = require('cordova'), nativeToJsModes = { // Polls for messages using the JS->Native bridge. POLLING: 0, - // Does an XHR to a local server, which will send back messages. This is - // broken on ICS when a proxy server is configured. - HANGING_GET: 1, // For LOAD_URL to be viable, it would need to have a work-around for // the bug where the soft-keyboard gets dismissed when a message is sent. LOAD_URL: 2, @@ -936,7 +933,8 @@ var cordova = require('cordova'), PRIVATE_API: 4 }, jsToNativeBridgeMode, // Set lazily. - nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT; + nativeToJsBridgeMode = nativeToJsModes.ONLINE_EVENT + pollEnabled = false; function androidExec(success, fail, service, action, args) { // Set default bridge modes if they have not already been set. @@ -963,6 +961,18 @@ function androidExec(success, fail, service, action, args) { } } +function pollOnce() { + var msg = nativeApiProvider.get().retrieveJsMessages(); + androidExec.processMessages(msg); +} + +function pollingTimerFunc() { + if (pollEnabled) { + pollOnce(); + setTimeout(pollingTimerFunc, 50); + } +} + function hookOnlineApis() { function proxyEvent(e) { cordova.fireWindowEvent(e.type); @@ -971,8 +981,8 @@ function hookOnlineApis() { // It currently fires them only on document though, so we bridge them // to window here (while first listening for exec()-releated online/offline // events). - window.addEventListener('online', polling.pollOnce, false); - window.addEventListener('offline', polling.pollOnce, false); + window.addEventListener('online', pollOnce, false); + window.addEventListener('offline', pollOnce, false); cordova.addWindowEventHandler('online'); cordova.addWindowEventHandler('offline'); document.addEventListener('online', proxyEvent, false); @@ -998,9 +1008,7 @@ androidExec.setNativeToJsBridgeMode = function(mode) { return; } if (nativeToJsBridgeMode == nativeToJsModes.POLLING) { - polling.stop(); - } else if (nativeToJsBridgeMode == nativeToJsModes.HANGING_GET) { - callback.stop(); + pollEnabled = false; } nativeToJsBridgeMode = mode; @@ -1008,9 +1016,8 @@ androidExec.setNativeToJsBridgeMode = function(mode) { nativeApiProvider.get().setNativeToJsBridgeMode(mode); if (mode == nativeToJsModes.POLLING) { - polling.start(); - } else if (mode == nativeToJsModes.HANGING_GET) { - callback.start(); + pollEnabled = true; + setTimeout(pollingTimerFunc, 1); } }; @@ -1055,7 +1062,7 @@ function processMessage(message) { androidExec.processMessages = function(messages) { while (messages) { if (messages == '*') { - window.setTimeout(polling.pollOnce, 0); + window.setTimeout(pollOnce, 0); break; } var spaceIdx = messages.indexOf(' '); @@ -1140,16 +1147,6 @@ module.exports = { }, [channel.onCordovaReady]); }, objects: { - cordova: { - children: { - JSCallback:{ - path:"cordova/plugin/android/callback" - }, - JSCallbackPolling:{ - path:"cordova/plugin/android/polling" - } - } - }, navigator: { children: { app:{ @@ -1224,6 +1221,7 @@ for (var key in Camera) { * @param {Object} options */ cameraExport.getPicture = function(successCallback, errorCallback, options) { + options = options || {}; // successCallback required if (typeof successCallback != "function") { console.log("Camera Error: successCallback is not a function"); @@ -1237,9 +1235,9 @@ cameraExport.getPicture = function(successCallback, errorCallback, options) { } var quality = 50; - if (options && typeof options.quality == "number") { + if (typeof options.quality == "number") { quality = options.quality; - } else if (options && typeof options.quality == "string") { + } else if (typeof options.quality == "string") { var qlity = parseInt(options.quality, 10); if (isNaN(qlity) === false) { quality = qlity.valueOf(); @@ -1462,7 +1460,7 @@ define("cordova/plugin/CompassError", function(require, exports, module) { /** * CompassError. - * An error code assigned by an implementation when an error has occured + * An error code assigned by an implementation when an error has occurred * @constructor */ var CompassError = function(err) { @@ -1748,7 +1746,7 @@ define("cordova/plugin/ContactError", function(require, exports, module) { /** * ContactError. - * An error code assigned by an implementation when an error has occured + * An error code assigned by an implementation when an error has occurred * @constructor */ var ContactError = function(err) { @@ -1955,7 +1953,7 @@ DirectoryEntry.prototype.createReader = function() { * Creates or looks up a directory * * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory - * @param {Flags} options to create or excluively create the directory + * @param {Flags} options to create or exclusively create the directory * @param {Function} successCallback is called with the new entry * @param {Function} errorCallback is called with a FileError */ @@ -1987,7 +1985,7 @@ DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCall * Creates or looks up a file * * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file - * @param {Flags} options to create or excluively create the file + * @param {Flags} options to create or exclusively create the file * @param {Function} successCallback is called with the new entry * @param {Function} errorCallback is called with a FileError */ @@ -2431,7 +2429,7 @@ var FileReader = function() { // Event handlers this.onloadstart = null; // When the read starts. - this.onprogress = null; // While reading (and decoding) file or fileBlob data, and reporting partial file data (progess.loaded/progress.total) + this.onprogress = null; // While reading (and decoding) file or fileBlob data, and reporting partial file data (progress.loaded/progress.total) this.onload = null; // When the read has successfully completed. this.onerror = null; // When the read has failed (see errors). this.onloadend = null; // When the request has completed (either in success or failure). @@ -3676,7 +3674,7 @@ function removeListeners(l) { var accelerometer = { /** - * Asynchronously aquires the current acceleration. + * Asynchronously acquires the current acceleration. * * @param {Function} successCallback The function to call when the acceleration data is available * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL) @@ -3707,7 +3705,7 @@ var accelerometer = { }, /** - * Asynchronously aquires the acceleration repeatedly at a given interval. + * Asynchronously acquires the acceleration repeatedly at a given interval. * * @param {Function} successCallback The function to call each time the acceleration data is available * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL) @@ -3848,80 +3846,6 @@ module.exports = { } }; -}); - -// file: lib/android/plugin/android/callback.js -define("cordova/plugin/android/callback", function(require, exports, module) { - -var port = null, - token = null, - xmlhttp; - -function startXhr() { - // cordova/exec depends on this module, so we can't require cordova/exec on the module level. - var exec = require('cordova/exec'), - xmlhttp = new XMLHttpRequest(); - - // Callback function when XMLHttpRequest is ready - xmlhttp.onreadystatechange=function(){ - if (!xmlhttp) { - return; - } - if (xmlhttp.readyState === 4){ - // If callback has JavaScript statement to execute - if (xmlhttp.status === 200) { - - // Need to url decode the response - var msg = decodeURIComponent(xmlhttp.responseText); - setTimeout(startXhr, 1); - exec.processMessages(msg); - } - - // If callback ping (used to keep XHR request from timing out) - else if (xmlhttp.status === 404) { - setTimeout(startXhr, 10); - } - - // 0 == Page is unloading. - // 400 == Bad request. - // 403 == invalid token. - // 503 == server stopped. - else { - console.log("JSCallback Error: Request failed with status " + xmlhttp.status); - exec.setNativeToJsBridgeMode(exec.nativeToJsModes.POLLING); - } - } - }; - - if (port === null) { - port = prompt("getPort", "gap_callbackServer:"); - } - if (token === null) { - token = prompt("getToken", "gap_callbackServer:"); - } - xmlhttp.open("GET", "http://127.0.0.1:"+port+"/"+token , true); - xmlhttp.send(); -} - -module.exports = { - start: function() { - startXhr(); - }, - - stop: function() { - if (xmlhttp) { - var tmp = xmlhttp; - xmlhttp = null; - tmp.abort(); - } - }, - - isAvailable: function() { - return ("true" != prompt("usePolling", "gap_callbackServer:")); - } -}; - - }); // file: lib/android/plugin/android/device.js @@ -3948,7 +3872,7 @@ module.exports = { * DEPRECATED * This is only for Android. * - * This resets the back button to the default behaviour + * This resets the back button to the default behavior */ resetBackButton:function() { console.log("Device.resetBackButton() is deprecated. Use App.overrideBackbutton(false)."); @@ -4041,42 +3965,6 @@ module.exports = { } }; -}); - -// file: lib/android/plugin/android/polling.js -define("cordova/plugin/android/polling", function(require, exports, module) { - -var cordova = require('cordova'), - nativeApiProvider = require('cordova/plugin/android/nativeapiprovider'), - POLL_INTERVAL = 50, - enabled = false; - -function pollOnce() { - var exec = require('cordova/exec'), - msg = nativeApiProvider.get().retrieveJsMessages(); - exec.processMessages(msg); -} - -function doPoll() { - if (!enabled) { - return; - } - pollOnce(); - setTimeout(doPoll, POLL_INTERVAL); -} - -module.exports = { - start: function() { - enabled = true; - setTimeout(doPoll, 1); - }, - stop: function() { - enabled = false; - }, - pollOnce: pollOnce -}; - - }); // file: lib/android/plugin/android/promptbasednativeapi.js @@ -4900,7 +4788,7 @@ console.table = function(data, columns) { //------------------------------------------------------------------------------ // return a new function that calls both functions passed as args //------------------------------------------------------------------------------ -function wrapperedOrigCall(orgFunc, newFunc) { +function wrappedOrigCall(orgFunc, newFunc) { return function() { var args = [].slice.call(arguments); try { orgFunc.apply(WinConsole, args); } catch (e) {} @@ -4915,7 +4803,7 @@ function wrapperedOrigCall(orgFunc, newFunc) { //------------------------------------------------------------------------------ for (var key in console) { if (typeof WinConsole[key] == "function") { - console[key] = wrapperedOrigCall(WinConsole[key], console[key]); + console[key] = wrappedOrigCall(WinConsole[key], console[key]); } } @@ -5060,7 +4948,7 @@ define("cordova/plugin/echo", function(require, exports, module) { var exec = require('cordova/exec'); /** - * Sends the given message through exec() to the Echo plugink, which sends it back to the successCallback. + * Sends the given message through exec() to the Echo plugin, which sends it back to the successCallback. * @param successCallback invoked with a FileSystem object * @param errorCallback invoked if error occurs retrieving file system * @param message The string to be echoed. @@ -5127,7 +5015,7 @@ function createTimeout(errorCallback, timeout) { var geolocation = { lastPosition:null, // reference to last known (cached) position returned /** - * Asynchronously aquires the current position. + * Asynchronously acquires the current position. * * @param {Function} successCallback The function to call when the position data is available * @param {Function} errorCallback The function to call when there is an error getting the heading position. (OPTIONAL) @@ -5281,6 +5169,23 @@ var exec = require('cordova/exec'), var globalization = { +/** +* Returns the string identifier for the client's current language. +* It returns the language identifier string to the successCB callback with a +* properties object as a parameter. If there is an error getting the language, +* then the errorCB callback is invoked. +* +* @param {Function} successCB +* @param {Function} errorCB +* +* @return Object.value {String}: The language identifier +* +* @error GlobalizationError.UNKNOWN_ERROR +* +* Example +* globalization.getPreferredLanguage(function (language) {alert('language:' + language.value + '\n');}, +* function () {}); +*/ getPreferredLanguage:function(successCB, failureCB) { // successCallback required if (typeof successCB != "function") { @@ -6337,7 +6242,7 @@ utils.clone = function(obj) { }; /** - * Returns a wrappered version of the function + * Returns a wrapped version of the function */ utils.close = function(context, func, params) { if (typeof params == 'undefined') { @@ -6508,7 +6413,7 @@ window.cordova = require('cordova'); platform = require('cordova/platform'); // Drop the common globals into the window object, but be nice and don't overwrite anything. - builder.build(base.objects).intoButDontClobber(window); + builder.build(base.objects).intoButDoNotClobber(window); // Drop the platform-specific globals into the window object // and clobber any existing object.