diff --git a/README.md b/README.md index 813633ef..5bcd8fd7 100755 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ To create your cordova.jar, copy the commons codec: then run in the framework directory: - android update project -p . -t android-15 + android update project -p . -t android-16 ant jar @@ -61,7 +61,7 @@ Project Commands These commands live in a generated Cordova Android project. - ./cordovap/debug [path] ..................... install to first device + ./cordova/debug [path] ..................... install to first device ./cordova/emulate .......................... start avd (emulator) named default ./cordova/log .............................. starts logcat diff --git a/bin/create b/bin/create index c6609f0c..2f6c0180 100755 --- a/bin/create +++ b/bin/create @@ -66,7 +66,7 @@ function createAppInfoJar { } function on_error { - echo "An error occured. Deleting project..." + echo "An error occurred. Deleting project..." [ -d "$PROJECT_PATH" ] && rm -rf "$PROJECT_PATH" } diff --git a/bin/templates/cordova/cordova b/bin/templates/cordova/cordova index f396b992..8159b330 100755 --- a/bin/templates/cordova/cordova +++ b/bin/templates/cordova/cordova @@ -73,7 +73,7 @@ function emulate { function clean { ant clean } -# has to be used independently and not in conjuction with other commands +# has to be used independently and not in conjunction with other commands function log { adb logcat } diff --git a/bin/templates/project/AndroidManifest.xml b/bin/templates/project/AndroidManifest.xml index a5ff4272..090c41eb 100644 --- a/bin/templates/project/AndroidManifest.xml +++ b/bin/templates/project/AndroidManifest.xml @@ -47,8 +47,10 @@ diff --git a/bin/templates/project/assets/www/js/index.js b/bin/templates/project/assets/www/js/index.js index 31d9064e..87b56604 100644 --- a/bin/templates/project/assets/www/js/index.js +++ b/bin/templates/project/assets/www/js/index.js @@ -31,7 +31,7 @@ var app = { // deviceready Event Handler // // The scope of 'this' is the event. In order to call the 'receivedEvent' - // function, we must explicity call 'app.receivedEvent(...);' + // function, we must explicitly call 'app.receivedEvent(...);' onDeviceReady: function() { app.receivedEvent('deviceready'); }, diff --git a/bin/tests/test_create_unix.js b/bin/tests/test_create_unix.js index b8b45ced..5d8dcd42 100644 --- a/bin/tests/test_create_unix.js +++ b/bin/tests/test_create_unix.js @@ -75,7 +75,7 @@ create_project.on('exit', function(code) { // make sure main Activity was added path.exists(util.format('%s/src/%s/%s.java', project_path, package_as_path, project_name), function(exists) { assert(exists, 'Activity did not get created'); - // TODO check that package name and activity name were substitued properly + // TODO check that package name and activity name were substituted properly }); // make sure plugins.xml was added diff --git a/bin/tests/test_create_win.js b/bin/tests/test_create_win.js index e1e87a8e..238e9165 100644 --- a/bin/tests/test_create_win.js +++ b/bin/tests/test_create_win.js @@ -85,7 +85,7 @@ create_project.on('exit', function(code) { // make sure main Activity was added path.exists(util.format('%s/src/%s/%s.java', project_path, package_as_path, project_name), function(exists) { assert(exists, 'Activity did not get created'); - // TODO check that package name and activity name were substitued properly + // TODO check that package name and activity name were substituted properly }); // make sure plugins.xml was added diff --git a/framework/assets/js/cordova.android.js b/framework/assets/js/cordova.android.js index 5baba2b5..7a7806e7 100644 --- a/framework/assets/js/cordova.android.js +++ b/framework/assets/js/cordova.android.js @@ -1,6 +1,6 @@ -// commit 143f5221a6251c9cbccdedc57005c61551b97f12 +// commit d30179b30152b9383a80637e609cf2d785e1aa3e -// File generated at :: Wed Sep 12 2012 12:51:58 GMT-0700 (PDT) +// File generated at :: Tue Sep 18 2012 11:34:26 GMT-0400 (EDT) /* Licensed to the Apache Software Foundation (ASF) under one @@ -24,11 +24,16 @@ ;(function() { // file: lib/scripts/require.js + var require, define; (function () { var modules = {}; + // Stack of moduleIds currently being built. + var requireStack = []; + // Map of module ID -> index into requireStack of modules currently being built. + var inProgressModules = {}; function build(module) { var factory = module.factory; @@ -41,8 +46,21 @@ var require, require = function (id) { if (!modules[id]) { throw "module " + id + " not found"; + } else if (id in inProgressModules) { + var cycle = requireStack.slice(inProgressModules[id]).join('->') + '->' + id; + throw "Cycle in require graph: " + cycle; } - return modules[id].factory ? build(modules[id]) : modules[id].exports; + if (modules[id].factory) { + try { + inProgressModules[id] = requireStack.length; + requireStack.push(id); + return build(modules[id]); + } finally { + delete inProgressModules[id]; + requireStack.pop(); + } + } + return modules[id].exports; }; define = function (id, factory) { @@ -67,8 +85,11 @@ if (typeof module === "object" && typeof require === "function") { module.exports.require = require; module.exports.define = define; } + // file: lib/cordova.js define("cordova", function(require, exports, module) { + + var channel = require('cordova/channel'); /** @@ -99,11 +120,7 @@ var documentEventHandlers = {}, document.addEventListener = function(evt, handler, capture) { var e = evt.toLowerCase(); if (typeof documentEventHandlers[e] != 'undefined') { - if (evt === 'deviceready') { - documentEventHandlers[e].subscribeOnce(handler); - } else { - documentEventHandlers[e].subscribe(handler); - } + documentEventHandlers[e].subscribe(handler); } else { m_document_addEventListener.call(document, evt, handler, capture); } @@ -163,11 +180,14 @@ var cordova = { /** * Methods to add/remove your own addEventListener hijacking on document + window. */ - addWindowEventHandler:function(event, opts) { - return (windowEventHandlers[event] = channel.create(event, opts)); + addWindowEventHandler:function(event) { + return (windowEventHandlers[event] = channel.create(event)); }, - addDocumentEventHandler:function(event, opts) { - return (documentEventHandlers[event] = channel.create(event, opts)); + addStickyDocumentEventHandler:function(event) { + return (documentEventHandlers[event] = channel.createSticky(event)); + }, + addDocumentEventHandler:function(event) { + return (documentEventHandlers[event] = channel.create(event)); }, removeWindowEventHandler:function(event) { delete windowEventHandlers[event]; @@ -242,57 +262,48 @@ var cordova = { /** * Called by native code when returning successful result from an action. - * - * @param callbackId - * @param args */ callbackSuccess: function(callbackId, args) { - if (cordova.callbacks[callbackId]) { - - // If result is to be sent to callback - if (args.status == cordova.callbackStatus.OK) { - try { - if (cordova.callbacks[callbackId].success) { - cordova.callbacks[callbackId].success(args.message); - } - } - catch (e) { - console.log("Error in success callback: "+callbackId+" = "+e); - } - } - - // Clear callback if not expecting any more results - if (!args.keepCallback) { - delete cordova.callbacks[callbackId]; - } + try { + cordova.callbackFromNative(callbackId, true, args.status, args.message, args.keepCallback); + } catch (e) { + console.log("Error in error callback: " + callbackId + " = "+e); } }, /** * Called by native code when returning error result from an action. - * - * @param callbackId - * @param args */ callbackError: function(callbackId, args) { - if (cordova.callbacks[callbackId]) { - try { - if (cordova.callbacks[callbackId].fail) { - cordova.callbacks[callbackId].fail(args.message); - } - } - catch (e) { - console.log("Error in error callback: "+callbackId+" = "+e); + // TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative. + // Derive success from status. + try { + cordova.callbackFromNative(callbackId, false, args.status, args.message, args.keepCallback); + } catch (e) { + console.log("Error in error callback: " + callbackId + " = "+e); + } + }, + + /** + * Called by native code when returning the result from an action. + */ + callbackFromNative: function(callbackId, success, status, message, keepCallback) { + var callback = cordova.callbacks[callbackId]; + if (callback) { + if (success && status == cordova.callbackStatus.OK) { + callback.success && callback.success(message); + } else if (!success) { + callback.fail && callback.fail(message); } // Clear callback if not expecting any more results - if (!args.keepCallback) { + if (!keepCallback) { delete cordova.callbacks[callbackId]; } } }, addConstructor: function(func) { - channel.onCordovaReady.subscribeOnce(function() { + channel.onCordovaReady.subscribe(function() { try { func(); } catch(e) { @@ -305,7 +316,7 @@ var cordova = { // Register pause, resume and deviceready channels as events on document. channel.onPause = cordova.addDocumentEventHandler('pause'); channel.onResume = cordova.addDocumentEventHandler('resume'); -channel.onDeviceReady = cordova.addDocumentEventHandler('deviceready'); +channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready'); module.exports = cordova; @@ -313,6 +324,7 @@ module.exports = cordova; // file: lib/common/builder.js define("cordova/builder", function(require, exports, module) { + var utils = require('cordova/utils'); function each(objects, func, context) { @@ -323,6 +335,17 @@ function each(objects, func, context) { } } +function assignOrWrapInDeprecateGetter(obj, key, value, message) { + if (message) { + utils.defineGetter(obj, key, function() { + window.console && console.log(message); + return value; + }); + } else { + obj[key] = value; + } +} + function include(parent, objects, clobber, merge) { each(objects, function (obj, key) { try { @@ -331,20 +354,20 @@ function include(parent, objects, clobber, merge) { if (clobber) { // Clobber if it doesn't exist. if (typeof parent[key] === 'undefined') { - parent[key] = result; + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); } else if (typeof obj.path !== 'undefined') { // If merging, merge properties onto parent, otherwise, clobber. if (merge) { recursiveMerge(parent[key], result); } else { - parent[key] = result; + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); } } result = parent[key]; } else { // Overwrite if not currently defined. if (typeof parent[key] == 'undefined') { - parent[key] = result; + assignOrWrapInDeprecateGetter(parent, key, result, obj.deprecated); } else if (merge && typeof obj.path !== 'undefined') { // If merging, merge parent onto result recursiveMerge(result, parent[key]); @@ -406,25 +429,29 @@ module.exports = { // file: lib/common/channel.js define("cordova/channel", function(require, exports, module) { + var utils = require('cordova/utils'), nextGuid = 1; /** * Custom pub-sub "channel" that can have functions subscribed to it * This object is used to define and control firing of events for - * cordova initialization. + * cordova initialization, as well as for custom events thereafter. * * The order of events during page load and Cordova startup is as follows: * - * onDOMContentLoaded Internal event that is received when the web page is loaded and parsed. - * onNativeReady Internal event that indicates the Cordova native side is ready. - * onCordovaReady Internal event fired when all Cordova JavaScript objects have been created. - * onCordovaInfoReady Internal event fired when device properties are available. - * onCordovaConnectionReady Internal event fired when the connection property has been set. - * onDeviceReady User event fired to indicate that Cordova is ready - * onResume User event fired to indicate a start/resume lifecycle event - * onPause User event fired to indicate a pause lifecycle event - * onDestroy Internal event fired when app is being destroyed (User should use window.onunload event, not this one). + * onDOMContentLoaded* Internal event that is received when the web page is loaded and parsed. + * onNativeReady* Internal event that indicates the Cordova native side is ready. + * onCordovaReady* Internal event fired when all Cordova JavaScript objects have been created. + * onCordovaInfoReady* Internal event fired when device properties are available. + * onCordovaConnectionReady* Internal event fired when the connection property has been set. + * onDeviceReady* User event fired to indicate that Cordova is ready + * onResume User event fired to indicate a start/resume lifecycle event + * onPause User event fired to indicate a pause lifecycle event + * onDestroy* Internal event fired when app is being destroyed (User should use window.onunload event, not this one). + * + * The events marked with an * are sticky. Once they have fired, they will stay in the fired state. + * All listeners that subscribe after the event is fired will be executed right away. * * The only Cordova events that user code should register for are: * deviceready Cordova native code is initialized and Cordova APIs can be called from JavaScript @@ -446,49 +473,45 @@ var utils = require('cordova/utils'), * Channel * @constructor * @param type String the channel name - * @param opts Object options to pass into the channel, currently - * supports: - * onSubscribe: callback that fires when - * something subscribes to the Channel. Sets - * context to the Channel. - * onUnsubscribe: callback that fires when - * something unsubscribes to the Channel. Sets - * context to the Channel. */ -var Channel = function(type, opts) { +var Channel = function(type, sticky) { this.type = type; + // Map of guid -> function. this.handlers = {}; + // 0 = Non-sticky, 1 = Sticky non-fired, 2 = Sticky fired. + this.state = sticky ? 1 : 0; + // Used in sticky mode to remember args passed to fire(). + this.fireArgs = null; + // Used by onHasSubscribersChange to know if there are any listeners. this.numHandlers = 0; - this.fired = false; - this.enabled = true; - this.events = { - onSubscribe:null, - onUnsubscribe:null - }; - if (opts) { - if (opts.onSubscribe) this.events.onSubscribe = opts.onSubscribe; - if (opts.onUnsubscribe) this.events.onUnsubscribe = opts.onUnsubscribe; - } + // Function that is called when the first listener is subscribed, or when + // the last listener is unsubscribed. + this.onHasSubscribersChange = null; }, channel = { /** * Calls the provided function only after all of the channels specified - * have been fired. + * have been fired. All channels must be sticky channels. */ - join: function (h, c) { - var i = c.length; - var len = i; - var f = function() { - if (!(--i)) h(); - }; + join: function(h, c) { + var len = c.length, + i = len, + f = function() { + if (!(--i)) h(); + }; for (var j=0; jNative 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. @@ -930,94 +944,52 @@ function androidExec(success, fail, service, action, args) { androidExec.setNativeToJsBridgeMode(nativeToJsModes.POLLING); } } - try { - var callbackId = service + cordova.callbackId++, - argsJson = JSON.stringify(args), - result; - if (success || fail) { - cordova.callbacks[callbackId] = {success:success, fail:fail}; - } + var callbackId = service + cordova.callbackId++, + argsJson = JSON.stringify(args); + if (success || fail) { + cordova.callbacks[callbackId] = {success:success, fail:fail}; + } - if (jsToNativeBridgeMode == jsToNativeModes.LOCATION_CHANGE) { - window.location = 'http://cdv_exec/' + service + '#' + action + '#' + callbackId + '#' + argsJson; - } else if (jsToNativeBridgeMode == jsToNativeModes.JS_OBJECT) { - // Explicit cast to string is required on Android 2.1 to convert from - // a Java string to a JS string. - result = '' + _cordovaExec.exec(service, action, callbackId, argsJson); - } else { - result = prompt(argsJson, "gap:"+JSON.stringify([service, action, callbackId, true])); - } - - // If a result was returned - if (result) { - var v = JSON.parse(result); - - // If status is OK, then return value back to caller - if (v.status === cordova.callbackStatus.OK) { - - // If there is a success callback, then call it now with - // returned value - if (success) { - try { - success(v.message); - } catch (e) { - console.log("Error in success callback: " + callbackId + " = " + e); - } - - // Clear callback if not expecting any more results - if (!v.keepCallback) { - delete cordova.callbacks[callbackId]; - } - } - return v.message; - } - - // If no result - else if (v.status === cordova.callbackStatus.NO_RESULT) { - // Clear callback if not expecting any more results - if (!v.keepCallback) { - delete cordova.callbacks[callbackId]; - } - } - - // If error, then display error - else { - console.log("Error: Status="+v.status+" Message="+v.message); - - // If there is a fail callback, then call it now with returned value - if (fail) { - try { - fail(v.message); - } - catch (e1) { - console.log("Error in error callback: "+callbackId+" = "+e1); - } - - // Clear callback if not expecting any more results - if (!v.keepCallback) { - delete cordova.callbacks[callbackId]; - } - } - return null; - } - } - } catch (e2) { - console.log("Error: "+e2); + if (jsToNativeBridgeMode == jsToNativeModes.LOCATION_CHANGE) { + window.location = 'http://cdv_exec/' + service + '#' + action + '#' + callbackId + '#' + argsJson; + } else { + var messages = nativeApiProvider.get().exec(service, action, callbackId, argsJson); + // Explicit cast to string is required on Android 2.1 to convert from + // a Java string to a JS string. + if (messages) { + messages = String(messages); + } + androidExec.processMessages(messages); } } -function onOnLineEvent(e) { - while (polling.pollOnce()); +function hookOnlineApis() { + function proxyEvent(e) { + cordova.fireWindowEvent(e.type); + } + // The network module takes care of firing online and offline events. + // 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); + cordova.addWindowEventHandler('online'); + cordova.addWindowEventHandler('offline'); + document.addEventListener('online', proxyEvent, false); + document.addEventListener('offline', proxyEvent, false); } +hookOnlineApis(); + androidExec.jsToNativeModes = jsToNativeModes; androidExec.nativeToJsModes = nativeToJsModes; androidExec.setJsToNativeBridgeMode = function(mode) { - if (mode == jsToNativeModes.JS_OBJECT && !window._cordovaExec) { - console.log('Falling back on PROMPT mode since _cordovaExec is missing.'); + if (mode == jsToNativeModes.JS_OBJECT && !window._cordovaNative) { + console.log('Falling back on PROMPT mode since _cordovaNative is missing.'); mode = jsToNativeModes.PROMPT; } + nativeApiProvider.setPreferPrompt(mode == jsToNativeModes.PROMPT); jsToNativeBridgeMode = mode; }; @@ -1029,22 +1001,68 @@ androidExec.setNativeToJsBridgeMode = function(mode) { polling.stop(); } else if (nativeToJsBridgeMode == nativeToJsModes.HANGING_GET) { callback.stop(); - } else if (nativeToJsBridgeMode == nativeToJsModes.ONLINE_EVENT) { - window.removeEventListener('online', onOnLineEvent, false); - window.removeEventListener('offline', onOnLineEvent, false); } nativeToJsBridgeMode = mode; // Tell the native side to switch modes. - prompt(mode, "gap_bridge_mode:"); + nativeApiProvider.get().setNativeToJsBridgeMode(mode); if (mode == nativeToJsModes.POLLING) { polling.start(); } else if (mode == nativeToJsModes.HANGING_GET) { callback.start(); - } else if (mode == nativeToJsModes.ONLINE_EVENT) { - window.addEventListener('online', onOnLineEvent, false); - window.addEventListener('offline', onOnLineEvent, false); + } +}; + +// Processes a single message, as encoded by NativeToJsMessageQueue.java. +function processMessage(message) { + try { + var firstChar = message.charAt(0); + if (firstChar == 'J') { + eval(message.slice(1)); + } else if (firstChar == 'S' || firstChar == 'F') { + var success = firstChar == 'S'; + var keepCallback = message.charAt(1) == '1'; + var spaceIdx = message.indexOf(' ', 2); + var status = +message.slice(2, spaceIdx); + var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1); + var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx); + var payloadKind = message.charAt(nextSpaceIdx + 1); + var payload; + if (payloadKind == 's') { + payload = message.slice(nextSpaceIdx + 2); + } else if (payloadKind == 't') { + payload = true; + } else if (payloadKind == 'f') { + payload = false; + } else if (payloadKind == 'n') { + payload = +message.slice(nextSpaceIdx + 2); + } else { + payload = JSON.parse(message.slice(nextSpaceIdx + 1)); + } + cordova.callbackFromNative(callbackId, success, status, payload, keepCallback); + } else { + console.log("processMessage failed: invalid message:" + message); + } + } catch (e) { + console.log("processMessage failed: Message: " + message); + console.log("processMessage failed: Error: " + e); + console.log("processMessage failed: Stack: " + e.stack); + } +} + +// This is called from the NativeToJsMessageQueue.java. +androidExec.processMessages = function(messages) { + while (messages) { + if (messages == '*') { + window.setTimeout(polling.pollOnce, 0); + break; + } + var spaceIdx = messages.indexOf(' '); + var msgLen = +messages.slice(0, spaceIdx); + var message = messages.substr(spaceIdx + 1, msgLen); + messages = messages.slice(spaceIdx + msgLen + 1); + processMessage(message); } }; @@ -1054,6 +1072,7 @@ module.exports = androidExec; // file: lib/android/platform.js define("cordova/platform", function(require, exports, module) { + module.exports = { id: "android", initialize:function() { @@ -1062,20 +1081,12 @@ module.exports = { exec = require('cordova/exec'); // Inject a listener for the backbutton on the document. - var backButtonChannel = cordova.addDocumentEventHandler('backbutton', { - onSubscribe:function() { - // If we just attached the first handler, let native know we need to override the back button. - if (this.numHandlers === 1) { - exec(null, null, "App", "overrideBackbutton", [true]); - } - }, - onUnsubscribe:function() { - // If we just detached the last handler, let native know we no longer override the back button. - if (this.numHandlers === 0) { - exec(null, null, "App", "overrideBackbutton", [false]); - } - } - }); + var backButtonChannel = cordova.addDocumentEventHandler('backbutton'); + backButtonChannel.onHasSubscribersChange = function() { + // If we just attached the first handler or detached the last handler, + // let native know we need to override the back button. + exec(null, null, "App", "overrideBackbutton", [this.numHandlers == 1]); + }; // Add hardware MENU and SEARCH button handlers cordova.addDocumentEventHandler('menubutton'); @@ -1177,6 +1188,7 @@ module.exports = { // file: lib/common/plugin/Acceleration.js define("cordova/plugin/Acceleration", function(require, exports, module) { + var Acceleration = function(x, y, z, timestamp) { this.x = x; this.y = y; @@ -1190,6 +1202,7 @@ module.exports = Acceleration; // file: lib/common/plugin/Camera.js define("cordova/plugin/Camera", function(require, exports, module) { + var exec = require('cordova/exec'), Camera = require('cordova/plugin/CameraConstants'); @@ -1306,10 +1319,12 @@ cameraExport.cleanup = function(successCallback, errorCallback) { }; module.exports = cameraExport; + }); // file: lib/common/plugin/CameraConstants.js define("cordova/plugin/CameraConstants", function(require, exports, module) { + module.exports = { DestinationType:{ DATA_URL: 0, // Return base64 encoded string @@ -1337,10 +1352,12 @@ module.exports = { ARROW_ANY : 15 } }; + }); // file: lib/common/plugin/CameraPopoverOptions.js define("cordova/plugin/CameraPopoverOptions", function(require, exports, module) { + var Camera = require('cordova/plugin/CameraConstants'); /** @@ -1357,10 +1374,12 @@ var CameraPopoverOptions = function(x,y,width,height,arrowDir){ }; module.exports = CameraPopoverOptions; + }); // file: lib/common/plugin/CaptureAudioOptions.js define("cordova/plugin/CaptureAudioOptions", function(require, exports, module) { + /** * Encapsulates all audio capture operation configuration options. */ @@ -1374,10 +1393,12 @@ var CaptureAudioOptions = function(){ }; module.exports = CaptureAudioOptions; + }); // file: lib/common/plugin/CaptureError.js define("cordova/plugin/CaptureError", function(require, exports, module) { + /** * The CaptureError interface encapsulates all errors in the Capture API. */ @@ -1397,10 +1418,12 @@ CaptureError.CAPTURE_NO_MEDIA_FILES = 3; CaptureError.CAPTURE_NOT_SUPPORTED = 20; module.exports = CaptureError; + }); // file: lib/common/plugin/CaptureImageOptions.js define("cordova/plugin/CaptureImageOptions", function(require, exports, module) { + /** * Encapsulates all image capture operation configuration options. */ @@ -1412,10 +1435,12 @@ var CaptureImageOptions = function(){ }; module.exports = CaptureImageOptions; + }); // file: lib/common/plugin/CaptureVideoOptions.js define("cordova/plugin/CaptureVideoOptions", function(require, exports, module) { + /** * Encapsulates all video capture operation configuration options. */ @@ -1429,10 +1454,12 @@ var CaptureVideoOptions = function(){ }; module.exports = CaptureVideoOptions; + }); // file: lib/common/plugin/CompassError.js define("cordova/plugin/CompassError", function(require, exports, module) { + /** * CompassError. * An error code assigned by an implementation when an error has occured @@ -1446,10 +1473,12 @@ CompassError.COMPASS_INTERNAL_ERR = 0; CompassError.COMPASS_NOT_SUPPORTED = 20; module.exports = CompassError; + }); // file: lib/common/plugin/CompassHeading.js define("cordova/plugin/CompassHeading", function(require, exports, module) { + var CompassHeading = function(magneticHeading, trueHeading, headingAccuracy, timestamp) { this.magneticHeading = (magneticHeading !== undefined ? magneticHeading : null); this.trueHeading = (trueHeading !== undefined ? trueHeading : null); @@ -1458,10 +1487,12 @@ var CompassHeading = function(magneticHeading, trueHeading, headingAccuracy, tim }; module.exports = CompassHeading; + }); // file: lib/common/plugin/ConfigurationData.js define("cordova/plugin/ConfigurationData", function(require, exports, module) { + /** * Encapsulates a set of parameters that the capture device supports. */ @@ -1477,10 +1508,12 @@ function ConfigurationData() { } module.exports = ConfigurationData; + }); // file: lib/common/plugin/Connection.js define("cordova/plugin/Connection", function(require, exports, module) { + /** * Network status */ @@ -1493,10 +1526,12 @@ module.exports = { CELL_4G: "4g", NONE: "none" }; + }); // file: lib/common/plugin/Contact.js define("cordova/plugin/Contact", function(require, exports, module) { + var exec = require('cordova/exec'), ContactError = require('cordova/plugin/ContactError'), utils = require('cordova/utils'); @@ -1679,6 +1714,7 @@ module.exports = Contact; // file: lib/common/plugin/ContactAddress.js define("cordova/plugin/ContactAddress", function(require, exports, module) { + /** * Contact address. * @constructor @@ -1704,10 +1740,12 @@ var ContactAddress = function(pref, type, formatted, streetAddress, locality, re }; module.exports = ContactAddress; + }); // file: lib/common/plugin/ContactError.js define("cordova/plugin/ContactError", function(require, exports, module) { + /** * ContactError. * An error code assigned by an implementation when an error has occured @@ -1729,10 +1767,12 @@ ContactError.NOT_SUPPORTED_ERROR = 5; ContactError.PERMISSION_DENIED_ERROR = 20; module.exports = ContactError; + }); // file: lib/common/plugin/ContactField.js define("cordova/plugin/ContactField", function(require, exports, module) { + /** * Generic contact field. * @constructor @@ -1749,10 +1789,12 @@ var ContactField = function(type, value, pref) { }; module.exports = ContactField; + }); // file: lib/common/plugin/ContactFindOptions.js define("cordova/plugin/ContactFindOptions", function(require, exports, module) { + /** * ContactFindOptions. * @constructor @@ -1766,10 +1808,12 @@ var ContactFindOptions = function(filter, multiple) { }; module.exports = ContactFindOptions; + }); // file: lib/common/plugin/ContactName.js define("cordova/plugin/ContactName", function(require, exports, module) { + /** * Contact name. * @constructor @@ -1790,10 +1834,12 @@ var ContactName = function(formatted, familyName, givenName, middle, prefix, suf }; module.exports = ContactName; + }); // file: lib/common/plugin/ContactOrganization.js define("cordova/plugin/ContactOrganization", function(require, exports, module) { + /** * Contact organization. * @constructor @@ -1817,10 +1863,12 @@ var ContactOrganization = function(pref, type, name, dept, title) { }; module.exports = ContactOrganization; + }); // file: lib/common/plugin/Coordinates.js define("cordova/plugin/Coordinates", function(require, exports, module) { + /** * This class contains position information. * @param {Object} lat @@ -1874,6 +1922,7 @@ module.exports = Coordinates; // file: lib/common/plugin/DirectoryEntry.js define("cordova/plugin/DirectoryEntry", function(require, exports, module) { + var utils = require('cordova/utils'), exec = require('cordova/exec'), Entry = require('cordova/plugin/Entry'), @@ -1960,6 +2009,7 @@ module.exports = DirectoryEntry; // file: lib/common/plugin/DirectoryReader.js define("cordova/plugin/DirectoryReader", function(require, exports, module) { + var exec = require('cordova/exec'), FileError = require('cordova/plugin/FileError') ; @@ -2007,6 +2057,7 @@ module.exports = DirectoryReader; // file: lib/common/plugin/Entry.js define("cordova/plugin/Entry", function(require, exports, module) { + var exec = require('cordova/exec'), FileError = require('cordova/plugin/FileError'), Metadata = require('cordova/plugin/Metadata'); @@ -2225,10 +2276,12 @@ Entry.prototype.getParent = function(successCallback, errorCallback) { }; module.exports = Entry; + }); // file: lib/common/plugin/File.js define("cordova/plugin/File", function(require, exports, module) { + /** * Constructor. * name {DOMString} name of the file, without path information @@ -2247,10 +2300,12 @@ var File = function(name, fullPath, type, lastModifiedDate, size){ }; module.exports = File; + }); // file: lib/common/plugin/FileEntry.js define("cordova/plugin/FileEntry", function(require, exports, module) { + var utils = require('cordova/utils'), exec = require('cordova/exec'), Entry = require('cordova/plugin/Entry'), @@ -2314,10 +2369,12 @@ FileEntry.prototype.file = function(successCallback, errorCallback) { module.exports = FileEntry; + }); // file: lib/common/plugin/FileError.js define("cordova/plugin/FileError", function(require, exports, module) { + /** * FileError */ @@ -2343,10 +2400,12 @@ FileError.TYPE_MISMATCH_ERR = 11; FileError.PATH_EXISTS_ERR = 12; module.exports = FileError; + }); // file: lib/common/plugin/FileReader.js define("cordova/plugin/FileReader", function(require, exports, module) { + var exec = require('cordova/exec'), FileError = require('cordova/plugin/FileError'), ProgressEvent = require('cordova/plugin/ProgressEvent'); @@ -2596,10 +2655,12 @@ FileReader.prototype.readAsArrayBuffer = function(file) { }; module.exports = FileReader; + }); // file: lib/common/plugin/FileSystem.js define("cordova/plugin/FileSystem", function(require, exports, module) { + var DirectoryEntry = require('cordova/plugin/DirectoryEntry'); /** @@ -2622,6 +2683,7 @@ module.exports = FileSystem; // file: lib/common/plugin/FileTransfer.js define("cordova/plugin/FileTransfer", function(require, exports, module) { + var exec = require('cordova/exec'), FileTransferError = require('cordova/plugin/FileTransferError'); @@ -2714,6 +2776,7 @@ module.exports = FileTransfer; // file: lib/common/plugin/FileTransferError.js define("cordova/plugin/FileTransferError", function(require, exports, module) { + /** * FileTransferError * @constructor @@ -2735,6 +2798,7 @@ module.exports = FileTransferError; // file: lib/common/plugin/FileUploadOptions.js define("cordova/plugin/FileUploadOptions", function(require, exports, module) { + /** * Options to customize the HTTP request used to upload files. * @constructor @@ -2759,6 +2823,7 @@ module.exports = FileUploadOptions; // file: lib/common/plugin/FileUploadResult.js define("cordova/plugin/FileUploadResult", function(require, exports, module) { + /** * FileUploadResult * @constructor @@ -2770,10 +2835,12 @@ var FileUploadResult = function() { }; module.exports = FileUploadResult; + }); // file: lib/common/plugin/FileWriter.js define("cordova/plugin/FileWriter", function(require, exports, module) { + var exec = require('cordova/exec'), FileError = require('cordova/plugin/FileError'), ProgressEvent = require('cordova/plugin/ProgressEvent'); @@ -3032,6 +3099,7 @@ module.exports = FileWriter; // file: lib/common/plugin/Flags.js define("cordova/plugin/Flags", function(require, exports, module) { + /** * Supplies arguments to methods that lookup or create files and directories. * @@ -3047,10 +3115,12 @@ function Flags(create, exclusive) { } module.exports = Flags; + }); // file: lib/common/plugin/LocalFileSystem.js define("cordova/plugin/LocalFileSystem", function(require, exports, module) { + var exec = require('cordova/exec'); /** @@ -3064,10 +3134,12 @@ LocalFileSystem.TEMPORARY = 0; //temporary, with no guarantee of persistence LocalFileSystem.PERSISTENT = 1; //persistent module.exports = LocalFileSystem; + }); // file: lib/common/plugin/Media.js define("cordova/plugin/Media", function(require, exports, module) { + var utils = require('cordova/utils'), exec = require('cordova/exec'); @@ -3259,10 +3331,12 @@ Media.onStatus = function(id, msgType, value) { }; module.exports = Media; + }); // file: lib/common/plugin/MediaError.js define("cordova/plugin/MediaError", function(require, exports, module) { + /** * This class contains information about any Media errors. */ @@ -3299,6 +3373,7 @@ module.exports = MediaError; // file: lib/common/plugin/MediaFile.js define("cordova/plugin/MediaFile", function(require, exports, module) { + var utils = require('cordova/utils'), exec = require('cordova/exec'), File = require('cordova/plugin/File'), @@ -3338,6 +3413,7 @@ module.exports = MediaFile; // file: lib/common/plugin/MediaFileData.js define("cordova/plugin/MediaFileData", function(require, exports, module) { + /** * MediaFileData encapsulates format information of a media file. * @@ -3356,10 +3432,12 @@ var MediaFileData = function(codecs, bitrate, height, width, duration){ }; module.exports = MediaFileData; + }); // file: lib/common/plugin/Metadata.js define("cordova/plugin/Metadata", function(require, exports, module) { + /** * Information about the state of the file or directory * @@ -3370,10 +3448,12 @@ var Metadata = function(time) { }; module.exports = Metadata; + }); // file: lib/common/plugin/Position.js define("cordova/plugin/Position", function(require, exports, module) { + var Coordinates = require('cordova/plugin/Coordinates'); var Position = function(coords, timestamp) { @@ -3391,6 +3471,7 @@ module.exports = Position; // file: lib/common/plugin/PositionError.js define("cordova/plugin/PositionError", function(require, exports, module) { + /** * Position error object * @@ -3408,10 +3489,12 @@ PositionError.POSITION_UNAVAILABLE = 2; PositionError.TIMEOUT = 3; module.exports = PositionError; + }); // file: lib/common/plugin/ProgressEvent.js define("cordova/plugin/ProgressEvent", function(require, exports, module) { + // If ProgressEvent exists in global context, use it already, otherwise use our own polyfill // Feature test: See if we can instantiate a native ProgressEvent; // if so, use that approach, @@ -3458,10 +3541,12 @@ var ProgressEvent = (function() { })(); module.exports = ProgressEvent; + }); // file: lib/common/plugin/accelerometer.js define("cordova/plugin/accelerometer", function(require, exports, module) { + /** * This class provides access to device accelerometer data. * @constructor @@ -3622,6 +3707,7 @@ module.exports = accelerometer; // file: lib/android/plugin/android/app.js define("cordova/plugin/android/app", function(require, exports, module) { + var exec = require('cordova/exec'); module.exports = { @@ -3693,10 +3779,12 @@ module.exports = { return exec(null, null, "App", "exitApp", []); } }; + }); // file: lib/android/plugin/android/callback.js define("cordova/plugin/android/callback", function(require, exports, module) { + var port = null, token = null, xmlhttp; @@ -3717,17 +3805,8 @@ function startXhr() { // Need to url decode the response var msg = decodeURIComponent(xmlhttp.responseText); - setTimeout(function() { - try { - var t = eval(msg); - } - catch (e) { - // If we're getting an error here, seeing the message will help in debugging - console.log("JSCallback: Message from Server: " + msg); - console.log("JSCallback Error: "+e); - } - }, 1); setTimeout(startXhr, 1); + exec.processMessages(msg); } // If callback ping (used to keep XHR request from timing out) @@ -3779,6 +3858,7 @@ module.exports = { // file: lib/android/plugin/android/device.js define("cordova/plugin/android/device", function(require, exports, module) { + var channel = require('cordova/channel'), utils = require('cordova/utils'), exec = require('cordova/exec'), @@ -3821,8 +3901,24 @@ module.exports = { }); +// file: lib/android/plugin/android/nativeapiprovider.js +define("cordova/plugin/android/nativeapiprovider", function(require, exports, module) { + +var nativeApi = this._cordovaNative || require('cordova/plugin/android/promptbasednativeapi'); +var currentApi = nativeApi; + +module.exports = { + get: function() { return currentApi }, + setPreferPrompt: function(value) { + currentApi = value ? require('cordova/plugin/android/promptbasednativeapi') : nativeApi; + } +}; + +}); + // file: lib/android/plugin/android/notification.js define("cordova/plugin/android/notification", function(require, exports, module) { + var exec = require('cordova/exec'); /** @@ -3876,38 +3972,29 @@ module.exports = { exec(null, null, 'Notification', 'progressValue', [ value ]); } }; + }); // 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 msg = prompt("", "gap_poll:"); - if (msg) { - try { - eval(""+msg); - } - catch (e) { - console.log("JSCallbackPolling: Message from Server: " + msg); - console.log("JSCallbackPolling Error: "+e); - } - return true; - } - return false; + var exec = require('cordova/exec'), + msg = nativeApiProvider.get().retrieveJsMessages(); + exec.processMessages(msg); } function doPoll() { if (!enabled) { return; } - var nextDelay = POLL_INTERVAL; - if (pollOnce()) { - nextDelay = 0; - } - setTimeout(doPoll, nextDelay); + pollOnce(); + setTimeout(doPoll, POLL_INTERVAL); } module.exports = { @@ -3922,10 +4009,28 @@ module.exports = { }; +}); + +// file: lib/android/plugin/android/promptbasednativeapi.js +define("cordova/plugin/android/promptbasednativeapi", function(require, exports, module) { + +module.exports = { + exec: function(service, action, callbackId, argsJson) { + return prompt(argsJson, 'gap:'+JSON.stringify([service, action, callbackId])); + }, + setNativeToJsBridgeMode: function(value) { + prompt(value, 'gap_bridge_mode:'); + }, + retrieveJsMessages: function() { + return prompt('', 'gap_poll:'); + } +}; + }); // file: lib/android/plugin/android/storage.js define("cordova/plugin/android/storage", function(require, exports, module) { + var utils = require('cordova/utils'), exec = require('cordova/exec'), channel = require('cordova/channel'); @@ -4308,6 +4413,7 @@ module.exports = { // file: lib/common/plugin/battery.js define("cordova/plugin/battery", function(require, exports, module) { + /** * This class contains information about the current battery status. * @constructor @@ -4325,34 +4431,26 @@ var Battery = function() { this._level = null; this._isPlugged = null; // Create new event handlers on the window (returns a channel instance) - var subscriptionEvents = { - onSubscribe:this.onSubscribe, - onUnsubscribe:this.onUnsubscribe - }; this.channels = { - batterystatus:cordova.addWindowEventHandler("batterystatus", subscriptionEvents), - batterylow:cordova.addWindowEventHandler("batterylow", subscriptionEvents), - batterycritical:cordova.addWindowEventHandler("batterycritical", subscriptionEvents) + batterystatus:cordova.addWindowEventHandler("batterystatus"), + batterylow:cordova.addWindowEventHandler("batterylow"), + batterycritical:cordova.addWindowEventHandler("batterycritical") }; + for (var key in this.channels) { + this.channels[key].onHasSubscribersChange = Battery.onHasSubscribersChange; + } + }; /** * Event handlers for when callbacks get registered for the battery. * Keep track of how many handlers we have so we can start and stop the native battery listener * appropriately (and hopefully save on battery life!). */ -Battery.prototype.onSubscribe = function() { - var me = battery; +Battery.onHasSubscribersChange = function() { // If we just registered the first handler, make sure native listener is started. - if (handlers() === 1) { - exec(me._status, me._error, "Battery", "start", []); - } -}; - -Battery.prototype.onUnsubscribe = function() { - var me = battery; - - // If we just unregistered the last handler, make sure native listener is stopped. - if (handlers() === 0) { + if (this.numHandlers === 1 && handlers() === 1) { + exec(battery._status, battery._error, "Battery", "start", []); + } else if (handlers() === 0) { exec(null, null, "Battery", "stop", []); } }; @@ -4395,10 +4493,12 @@ Battery.prototype._error = function(e) { var battery = new Battery(); module.exports = battery; + }); // file: lib/common/plugin/capture.js define("cordova/plugin/capture", function(require, exports, module) { + var exec = require('cordova/exec'), MediaFile = require('cordova/plugin/MediaFile'); @@ -4476,6 +4576,7 @@ module.exports = new Capture(); // file: lib/common/plugin/compass.js define("cordova/plugin/compass", function(require, exports, module) { + var exec = require('cordova/exec'), utils = require('cordova/utils'), CompassHeading = require('cordova/plugin/CompassHeading'), @@ -4577,10 +4678,12 @@ var exec = require('cordova/exec'), }; module.exports = compass; + }); // file: lib/common/plugin/console-via-logger.js define("cordova/plugin/console-via-logger", function(require, exports, module) { + //------------------------------------------------------------------------------ var logger = require("cordova/plugin/logger"); @@ -4752,6 +4855,7 @@ for (var key in console) { // file: lib/common/plugin/contacts.js define("cordova/plugin/contacts", function(require, exports, module) { + var exec = require('cordova/exec'), ContactError = require('cordova/plugin/ContactError'), utils = require('cordova/utils'), @@ -4815,6 +4919,7 @@ module.exports = contacts; // file: lib/common/plugin/device.js define("cordova/plugin/device", function(require, exports, module) { + var channel = require('cordova/channel'), utils = require('cordova/utils'), exec = require('cordova/exec'); @@ -4837,7 +4942,7 @@ function Device() { var me = this; - channel.onCordovaReady.subscribeOnce(function() { + channel.onCordovaReady.subscribe(function() { me.getInfo(function(info) { me.available = true; me.platform = info.platform; @@ -4883,6 +4988,7 @@ module.exports = new Device(); // file: lib/common/plugin/echo.js define("cordova/plugin/echo", function(require, exports, module) { + var exec = require('cordova/exec'); /** @@ -4902,6 +5008,7 @@ module.exports = function(successCallback, errorCallback, message, forceAsync) { // file: lib/common/plugin/geolocation.js define("cordova/plugin/geolocation", function(require, exports, module) { + var utils = require('cordova/utils'), exec = require('cordova/exec'), PositionError = require('cordova/plugin/PositionError'), @@ -4966,11 +5073,11 @@ var geolocation = { // Timer var that will fire an error callback if no position is retrieved from native // before the "timeout" param provided expires - var timeoutTimer = null; + var timeoutTimer = {timer:null}; var win = function(p) { - clearTimeout(timeoutTimer); - if (!timeoutTimer) { + clearTimeout(timeoutTimer.timer); + if (!(timeoutTimer.timer)) { // Timeout already happened, or native fired error callback for // this geo request. // Don't continue with success callback. @@ -4992,8 +5099,8 @@ var geolocation = { successCallback(pos); }; var fail = function(e) { - clearTimeout(timeoutTimer); - timeoutTimer = null; + clearTimeout(timeoutTimer.timer); + timeoutTimer.timer = null; var err = new PositionError(e.code, e.message); if (errorCallback) { errorCallback(err); @@ -5016,12 +5123,12 @@ var geolocation = { // If the timeout value was not set to Infinity (default), then // set up a timeout function that will fire the error callback // if no successful position was retrieved before timeout expired. - timeoutTimer = createTimeout(fail, options.timeout); + timeoutTimer.timer = createTimeout(fail, options.timeout); } else { // This is here so the check in the win function doesn't mess stuff up // may seem weird but this guarantees timeoutTimer is // always truthy before we call into native - timeoutTimer = true; + timeoutTimer.timer = true; } exec(win, fail, "Geolocation", "getLocation", [options.enableHighAccuracy, options.maximumAge]); } @@ -5048,7 +5155,7 @@ var geolocation = { timers[id] = geolocation.getCurrentPosition(successCallback, errorCallback, options); var fail = function(e) { - clearTimeout(timers[id]); + clearTimeout(timers[id].timer); var err = new PositionError(e.code, e.message); if (errorCallback) { errorCallback(err); @@ -5056,9 +5163,9 @@ var geolocation = { }; var win = function(p) { - clearTimeout(timers[id]); + clearTimeout(timers[id].timer); if (options.timeout !== Infinity) { - timers[id] = createTimeout(fail, options.timeout); + timers[id].timer = createTimeout(fail, options.timeout); } var pos = new Position( { @@ -5087,8 +5194,8 @@ var geolocation = { */ clearWatch:function(id) { if (id && timers[id] !== undefined) { - clearTimeout(timers[id]); - delete timers[id]; + clearTimeout(timers[id].timer); + timers[id].timer = false; exec(null, null, "Geolocation", "clearWatch", [id]); } } @@ -5100,6 +5207,7 @@ module.exports = geolocation; // file: lib/common/plugin/logger.js define("cordova/plugin/logger", function(require, exports, module) { + //------------------------------------------------------------------------------ // The logger module exports the following properties/functions: // @@ -5327,9 +5435,20 @@ document.addEventListener("deviceready", logger.__onDeviceReady, false); // file: lib/common/plugin/network.js define("cordova/plugin/network", function(require, exports, module) { + var exec = require('cordova/exec'), cordova = require('cordova'), - channel = require('cordova/channel'); + channel = require('cordova/channel'), + utils = require('cordova/utils'); + +// Link the onLine property with the Cordova-supplied network info. +// This works because we clobber the naviagtor object with our own +// object in bootstrap.js. +if (typeof navigator != 'undefined') { + utils.defineGetter(navigator, 'onLine', function() { + return this.connection.type != 'none'; + }); +} var NetworkConnection = function () { this.type = null; @@ -5339,7 +5458,7 @@ var NetworkConnection = function () { var me = this; - channel.onCordovaReady.subscribeOnce(function() { + channel.onCordovaReady.subscribe(function() { me.getInfo(function (info) { me.type = info; if (info === "none") { @@ -5387,10 +5506,12 @@ NetworkConnection.prototype.getInfo = function (successCallback, errorCallback) }; module.exports = new NetworkConnection(); + }); // file: lib/common/plugin/notification.js define("cordova/plugin/notification", function(require, exports, module) { + var exec = require('cordova/exec'); /** @@ -5447,10 +5568,12 @@ module.exports = { exec(null, null, "Notification", "beep", [count]); } }; + }); // file: lib/common/plugin/requestFileSystem.js define("cordova/plugin/requestFileSystem", function(require, exports, module) { + var FileError = require('cordova/plugin/FileError'), FileSystem = require('cordova/plugin/FileSystem'), exec = require('cordova/exec'); @@ -5491,10 +5614,12 @@ var requestFileSystem = function(type, size, successCallback, errorCallback) { }; module.exports = requestFileSystem; + }); // file: lib/common/plugin/resolveLocalFileSystemURI.js define("cordova/plugin/resolveLocalFileSystemURI", function(require, exports, module) { + var DirectoryEntry = require('cordova/plugin/DirectoryEntry'), FileEntry = require('cordova/plugin/FileEntry'), FileError = require('cordova/plugin/FileError'), @@ -5548,6 +5673,7 @@ module.exports = function(uri, successCallback, errorCallback) { // file: lib/common/plugin/splashscreen.js define("cordova/plugin/splashscreen", function(require, exports, module) { + var exec = require('cordova/exec'); var splashscreen = { @@ -5560,12 +5686,25 @@ var splashscreen = { }; module.exports = splashscreen; + }); // file: lib/common/utils.js define("cordova/utils", function(require, exports, module) { + var utils = exports; +/** + * Defines a property getter for obj[key]. + */ +utils.defineGetter = function(obj, key, func) { + if (Object.defineProperty) { + Object.defineProperty(obj, key, { get: func }); + } else { + obj.__defineGetter__(key, func); + } +}; + /** * Returns an indication of whether the argument is an array or not */ @@ -5757,7 +5896,16 @@ function formatted(object, formatChar) { window.cordova = require('cordova'); // file: lib/scripts/bootstrap.js + (function (context) { + // Replace navigator before any modules are required(), to ensure it happens as soon as possible. + // We replace it so that properties that can't be clobbered can instead be overridden. + if (typeof navigator != 'undefined') { + function CordovaNavigator() {} + CordovaNavigator.prototype = navigator; + navigator = new CordovaNavigator(); + } + var channel = require("cordova/channel"), _self = { boot: function () { @@ -5799,7 +5947,7 @@ window.cordova = require('cordova'); }; // boot up once native side is ready - channel.onNativeReady.subscribeOnce(_self.boot); + channel.onNativeReady.subscribe(_self.boot); // _nativeReady is global variable that the native side can set // to signify that the native code is ready. It is a global since diff --git a/framework/src/org/apache/cordova/AudioHandler.java b/framework/src/org/apache/cordova/AudioHandler.java index 3f8d2e8b..ccf9f6cf 100644 --- a/framework/src/org/apache/cordova/AudioHandler.java +++ b/framework/src/org/apache/cordova/AudioHandler.java @@ -235,7 +235,7 @@ public class AudioHandler extends Plugin { /** * Seek to a location. * @param id The id of the audio player - * @param miliseconds int: number of milliseconds to skip 1000 = 1 second + * @param milliseconds int: number of milliseconds to skip 1000 = 1 second */ public void seekToAudio(String id, int milliseconds) { AudioPlayer audio = this.players.get(id); diff --git a/framework/src/org/apache/cordova/AudioPlayer.java b/framework/src/org/apache/cordova/AudioPlayer.java index e8ea1d3c..c0d6b932 100644 --- a/framework/src/org/apache/cordova/AudioPlayer.java +++ b/framework/src/org/apache/cordova/AudioPlayer.java @@ -410,7 +410,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On */ private void setMode(MODE mode) { if (this.mode != mode) { - //mode is not part of the expected behaviour, so no notification + //mode is not part of the expected behavior, so no notification //this.handler.sendJavascript("cordova.require('cordova/plugin/Media').onStatus('" + this.id + "', " + MEDIA_STATE + ", " + mode + ");"); } this.mode = mode; diff --git a/framework/src/org/apache/cordova/CallbackServer.java b/framework/src/org/apache/cordova/CallbackServer.java index d0296a30..dcf3b6e4 100755 --- a/framework/src/org/apache/cordova/CallbackServer.java +++ b/framework/src/org/apache/cordova/CallbackServer.java @@ -207,19 +207,18 @@ public class CallbackServer implements Runnable { // Must have security token if ((requestParts.length == 3) && (requestParts[1].substring(1).equals(this.token))) { //Log.d(LOG_TAG, "CallbackServer -- Processing GET request"); - String js = null; + 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 - synchronized (this) { - while (this.active) { - if (jsMessageQueue != null) { - // TODO(agrieve): Should this use popAll() instead? - js = jsMessageQueue.pop(); - if (js != null) { - break; - } - } + 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 <<<"); @@ -233,14 +232,14 @@ public class CallbackServer implements Runnable { if (this.active) { // If no data, then send 404 back to client before it times out - if (js == null) { + 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(js, "UTF-8"); + response += encode(payload, "UTF-8"); } } else { diff --git a/framework/src/org/apache/cordova/CameraLauncher.java b/framework/src/org/apache/cordova/CameraLauncher.java index a72de6af..211cb42d 100755 --- a/framework/src/org/apache/cordova/CameraLauncher.java +++ b/framework/src/org/apache/cordova/CameraLauncher.java @@ -143,7 +143,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie this.saveToPhotoAlbum = args.getBoolean(9); // If the user specifies a 0 or smaller width/height - // make it -1 so later comparrisions succeed + // make it -1 so later comparisons succeed if (this.targetWidth < 1) { this.targetWidth = -1; } @@ -392,7 +392,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie // If we don't have a valid image so quit. if (imagePath == null) { Log.d(LOG_TAG, "I either have a null image path or bitmap"); - this.failPicture("Unable to retreive path to picture!"); + this.failPicture("Unable to retrieve path to picture!"); return; } Bitmap bitmap = getScaledBitmap(imagePath); @@ -762,7 +762,7 @@ public class CameraLauncher extends Plugin implements MediaScannerConnectionClie try{ this.conn.scanFile(this.scanMe.toString(), "image/*"); } catch (java.lang.IllegalStateException e){ - LOG.e(LOG_TAG, "Can't scan file in MediaScanner aftering taking picture"); + LOG.e(LOG_TAG, "Can't scan file in MediaScanner after taking picture"); } } diff --git a/framework/src/org/apache/cordova/ContactAccessorSdk5.java b/framework/src/org/apache/cordova/ContactAccessorSdk5.java index f4ca847e..8497135e 100644 --- a/framework/src/org/apache/cordova/ContactAccessorSdk5.java +++ b/framework/src/org/apache/cordova/ContactAccessorSdk5.java @@ -28,7 +28,6 @@ import android.content.ContentValues; import android.content.OperationApplicationException; import android.database.Cursor; import android.net.Uri; -import android.os.Debug; import android.os.RemoteException; import android.provider.ContactsContract; import android.util.Log; @@ -331,8 +330,6 @@ public class ContactAccessorSdk5 extends ContactAccessor { JSONArray websites = new JSONArray(); JSONArray photos = new JSONArray(); - ArrayList names = new ArrayList(); - // Column indices int colContactId = c.getColumnIndex(ContactsContract.Data.CONTACT_ID); int colRawContactId = c.getColumnIndex(ContactsContract.Data.RAW_CONTACT_ID); @@ -795,10 +792,10 @@ public class ContactAccessorSdk5 extends ContactAccessor { formatted.append(middleName + " "); } if (familyName != null) { - formatted.append(familyName + " "); + formatted.append(familyName); } if (honorificSuffix != null) { - formatted.append(honorificSuffix + " "); + formatted.append(" " + honorificSuffix); } contactName.put("familyName", familyName); @@ -862,7 +859,8 @@ public class ContactAccessorSdk5 extends ContactAccessor { im.put("id", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im._ID))); im.put("pref", false); // Android does not store pref attribute im.put("value", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA))); - im.put("type", getContactType(cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.TYPE)))); + String type = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.PROTOCOL)); + im.put("type", getImType(new Integer(type).intValue())); } catch (JSONException e) { Log.e(LOG_TAG, e.getMessage(), e); } @@ -1248,7 +1246,7 @@ public class ContactAccessorSdk5 extends ContactAccessor { contentValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawId); contentValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE); contentValues.put(ContactsContract.CommonDataKinds.Im.DATA, getJsonString(im, "value")); - contentValues.put(ContactsContract.CommonDataKinds.Im.TYPE, getContactType(getJsonString(im, "type"))); + contentValues.put(ContactsContract.CommonDataKinds.Im.TYPE, getImType(getJsonString(im, "type"))); ops.add(ContentProviderOperation.newInsert( ContactsContract.Data.CONTENT_URI).withValues(contentValues).build()); @@ -1412,7 +1410,7 @@ public class ContactAccessorSdk5 extends ContactAccessor { retVal = false; } - // if the save was a succes return the contact ID + // if the save was a success return the contact ID if (retVal) { return id; } else { @@ -1447,7 +1445,7 @@ public class ContactAccessorSdk5 extends ContactAccessor { .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) .withValue(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE) .withValue(ContactsContract.CommonDataKinds.Im.DATA, getJsonString(im, "value")) - .withValue(ContactsContract.CommonDataKinds.Im.TYPE, getContactType(getJsonString(im, "type"))) + .withValue(ContactsContract.CommonDataKinds.Im.TYPE, getImType(getJsonString(im, "type"))) .build()); } @@ -2091,5 +2089,86 @@ public class ContactAccessorSdk5 extends ContactAccessor { } return stringType; } + + /** + * Converts a string from the W3C Contact API to it's Android int value. + * @param string + * @return Android int value + */ + private int getImType(String string) { + int type = ContactsContract.CommonDataKinds.Im.PROTOCOL_CUSTOM; + if (string != null) { + if ("aim".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_AIM; + } + else if ("google talk".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK; + } + else if ("icq".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_ICQ; + } + else if ("jabber".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER; + } + else if ("msn".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_MSN; + } + else if ("netmeeting".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_NETMEETING; + } + else if ("qq".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_QQ; + } + else if ("skype".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_SKYPE; + } + else if ("yahoo".equals(string.toLowerCase())) { + return ContactsContract.CommonDataKinds.Im.PROTOCOL_YAHOO; + } + } + return type; + } + + /** + * getPhoneType converts an Android phone type into a string + * @param type + * @return phone type as string. + */ + private String getImType(int type) { + String stringType; + switch (type) { + case ContactsContract.CommonDataKinds.Im.PROTOCOL_AIM: + stringType = "AIM"; + break; + case ContactsContract.CommonDataKinds.Im.PROTOCOL_GOOGLE_TALK: + stringType = "Google Talk"; + break; + case ContactsContract.CommonDataKinds.Im.PROTOCOL_ICQ: + stringType = "ICQ"; + break; + case ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER: + stringType = "Jabber"; + break; + case ContactsContract.CommonDataKinds.Im.PROTOCOL_MSN: + stringType = "MSN"; + break; + case ContactsContract.CommonDataKinds.Im.PROTOCOL_NETMEETING: + stringType = "NetMeeting"; + break; + case ContactsContract.CommonDataKinds.Im.PROTOCOL_QQ: + stringType = "QQ"; + break; + case ContactsContract.CommonDataKinds.Im.PROTOCOL_SKYPE: + stringType = "Skype"; + break; + case ContactsContract.CommonDataKinds.Im.PROTOCOL_YAHOO: + stringType = "Yahoo"; + break; + default: + stringType = "custom"; + break; + } + return stringType; + } } diff --git a/framework/src/org/apache/cordova/CordovaChromeClient.java b/framework/src/org/apache/cordova/CordovaChromeClient.java index 4fe56ad1..398286d2 100755 --- a/framework/src/org/apache/cordova/CordovaChromeClient.java +++ b/framework/src/org/apache/cordova/CordovaChromeClient.java @@ -202,9 +202,8 @@ public class CordovaChromeClient extends WebChromeClient { String service = array.getString(0); String action = array.getString(1); String callbackId = array.getString(2); - boolean async = array.getBoolean(3); - PluginResult r = this.appView.pluginManager.exec(service, action, callbackId, message, async); - result.confirm(r == null ? "" : r.getJSONString()); + String r = this.appView.exposedJsApi.exec(service, action, callbackId, message); + result.confirm(r == null ? "" : r); } catch (JSONException e) { e.printStackTrace(); } @@ -212,15 +211,14 @@ public class CordovaChromeClient extends WebChromeClient { // Sets the native->JS bridge mode. else if (reqOk && defaultValue != null && defaultValue.equals("gap_bridge_mode:")) { - this.appView.jsMessageQueue.setBridgeMode(Integer.parseInt(message)); + this.appView.exposedJsApi.setNativeToJsBridgeMode(Integer.parseInt(message)); result.confirm(""); } // Polling for JavaScript messages else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) { - // TODO(agrieve): Use popAll() here. - String r = this.appView.jsMessageQueue.pop(); - result.confirm(r); + String r = this.appView.exposedJsApi.retrieveJsMessages(); + result.confirm(r == null ? "" : r); } // Do NO-OP so older code doesn't display dialog diff --git a/framework/src/org/apache/cordova/CordovaLocationListener.java b/framework/src/org/apache/cordova/CordovaLocationListener.java index fcb6bf34..0ad441d6 100755 --- a/framework/src/org/apache/cordova/CordovaLocationListener.java +++ b/framework/src/org/apache/cordova/CordovaLocationListener.java @@ -55,6 +55,11 @@ public class CordovaLocationListener implements LocationListener { { this.owner.fail(code, message, callbackId); } + if(this.owner.isGlobalListener(this)) + { + Log.d(TAG, "Stopping global listener"); + this.stop(); + } this.callbacks.clear(); Iterator it = this.watches.entrySet().iterator(); @@ -69,6 +74,11 @@ public class CordovaLocationListener implements LocationListener { { this.owner.win(loc, callbackId); } + if(this.owner.isGlobalListener(this)) + { + Log.d(TAG, "Stopping global listener"); + this.stop(); + } this.callbacks.clear(); Iterator it = this.watches.entrySet().iterator(); diff --git a/framework/src/org/apache/cordova/CordovaWebView.java b/framework/src/org/apache/cordova/CordovaWebView.java index e878a74b..ddae1dda 100755 --- a/framework/src/org/apache/cordova/CordovaWebView.java +++ b/framework/src/org/apache/cordova/CordovaWebView.java @@ -36,9 +36,13 @@ import org.xmlpull.v1.XmlPullParserException; import android.annotation.SuppressLint; import android.annotation.TargetApi; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.res.XmlResourceParser; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -63,15 +67,17 @@ public class CordovaWebView extends WebView { public PluginManager pluginManager; public CallbackServer callbackServer; private boolean paused; + + private BroadcastReceiver receiver; - /** Actvities and other important classes **/ + /** Activities and other important classes **/ private CordovaInterface cordova; CordovaWebViewClient viewClient; @SuppressWarnings("unused") private CordovaChromeClient chromeClient; - //This is for the polyfil history + //This is for the polyfill history private String url; String baseUrl; private Stack urls = new Stack(); @@ -90,6 +96,7 @@ public class CordovaWebView extends WebView { private boolean handleButton = false; NativeToJsMessageQueue jsMessageQueue; + ExposedJsApi exposedJsApi; /** * Constructor. @@ -199,8 +206,6 @@ public class CordovaWebView extends WebView { @SuppressWarnings("deprecation") @SuppressLint("NewApi") private void setup() { - jsMessageQueue = new NativeToJsMessageQueue(this, cordova); - this.setInitialScale(0); this.setVerticalScrollBarEnabled(false); this.requestFocusFromTouch(); @@ -229,16 +234,33 @@ public class CordovaWebView extends WebView { // Enable built-in geolocation settings.setGeolocationEnabled(true); - - //Start up the plugin manager - try { - this.pluginManager = new PluginManager(this, this.cordova); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); + + // Fix for CB-1405 + // Google issue 4641 + this.updateUserAgentString(); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + if (this.receiver == null) { + this.receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + updateUserAgentString(); + } + }; + this.cordova.getActivity().registerReceiver(this.receiver, intentFilter); } + // end CB-1405 + + pluginManager = new PluginManager(this, this.cordova); + jsMessageQueue = new NativeToJsMessageQueue(this, cordova); + exposedJsApi = new ExposedJsApi(pluginManager, jsMessageQueue); exposeJsInterface(); } + + private void updateUserAgentString() { + this.getSettings().getUserAgentString(); + } private void exposeJsInterface() { // addJavascriptInterface crashes on the 2.3 emulator. @@ -246,13 +268,7 @@ public class CordovaWebView extends WebView { Log.i(TAG, "Disabled addJavascriptInterface() bridge callback due to a bug on the 2.3 emulator"); return; } - this.addJavascriptInterface(new Object() { - @SuppressWarnings("unused") - public String exec(String service, String action, String callbackId, String arguments) throws JSONException { - PluginResult r = pluginManager.exec(service, action, callbackId, arguments, true /* async */); - return r == null ? "" : r.getJSONString(); - } - }, "_cordovaExec"); + this.addJavascriptInterface(exposedJsApi, "_cordovaNative"); } /** @@ -461,7 +477,9 @@ public class CordovaWebView extends WebView { * @param url */ void loadUrlNow(String url) { - LOG.d(TAG, ">>> loadUrlNow()"); + if (LOG.isLoggable(LOG.DEBUG) && !url.startsWith("javascript:")) { + LOG.d(TAG, ">>> loadUrlNow()"); + } super.loadUrl(url); } @@ -499,7 +517,17 @@ public class CordovaWebView extends WebView { * @param message */ public void sendJavascript(String statement) { - this.jsMessageQueue.add(statement); + this.jsMessageQueue.addJavaScript(statement); + } + + /** + * Send a plugin result back to JavaScript. + * (This is a convenience method) + * + * @param message + */ + public void sendPluginResult(PluginResult result, String callbackId) { + this.jsMessageQueue.addPluginResult(result, callbackId); } /** @@ -731,7 +759,7 @@ public class CordovaWebView extends WebView { if(keyDownCodes.contains(keyCode)) { if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { - // only override default behaviour is event bound + // only override default behavior is event bound LOG.d(TAG, "Down Key Hit"); this.loadUrl("javascript:cordova.fireDocumentEvent('volumedownbutton');"); return true; @@ -766,7 +794,7 @@ public class CordovaWebView extends WebView { if (this.backHistory()) { return true; } - // If not, then invoke default behaviour + // If not, then invoke default behavior else { //this.activityState = ACTIVITY_EXITING; return false; @@ -789,7 +817,7 @@ public class CordovaWebView extends WebView { return super.onKeyUp(keyCode, event); } - //Does webkit change this behaviour? + //Does webkit change this behavior? return super.onKeyUp(keyCode, event); } @@ -873,6 +901,15 @@ public class CordovaWebView extends WebView { if (this.pluginManager != null) { this.pluginManager.onDestroy(); } + + // unregister the receiver + if (this.receiver != null) { + try { + this.cordova.getActivity().unregisterReceiver(this.receiver); + } catch (Exception e) { + Log.e(TAG, "Error unregistering configuration receiver: " + e.getMessage(), e); + } + } } public void onNewIntent(Intent intent) diff --git a/framework/src/org/apache/cordova/CordovaWebViewClient.java b/framework/src/org/apache/cordova/CordovaWebViewClient.java index 596411fb..fe0e9e9f 100755 --- a/framework/src/org/apache/cordova/CordovaWebViewClient.java +++ b/framework/src/org/apache/cordova/CordovaWebViewClient.java @@ -54,9 +54,6 @@ import android.webkit.WebViewClient; public class CordovaWebViewClient extends WebViewClient { private static final String TAG = "Cordova"; - // Disable URL-based exec() bridge by default since it's a bit of a - // security concern. - private static boolean ENABLE_LOCATION_CHANGE_EXEC_MODE = false; private static final String CORDOVA_EXEC_URL_PREFIX = "http://cdv_exec/"; CordovaInterface cordova; CordovaWebView appView; @@ -110,11 +107,7 @@ public class CordovaWebViewClient extends WebViewClient { String action = url.substring(idx2 + 1, idx3); String callbackId = url.substring(idx3 + 1, idx4); String jsonArgs = url.substring(idx4 + 1); - PluginResult r = appView.pluginManager.exec(service, action, callbackId, jsonArgs, true /* async */); - String callbackString = r.toCallbackString(callbackId); - if (r != null) { - appView.sendJavascript(callbackString); - } + appView.pluginManager.exec(service, action, callbackId, jsonArgs, true /* async */); } /** @@ -128,7 +121,7 @@ public class CordovaWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // Check if it's an exec() bridge command message. - if (ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) { + if (NativeToJsMessageQueue.ENABLE_LOCATION_CHANGE_EXEC_MODE && url.startsWith(CORDOVA_EXEC_URL_PREFIX)) { handleExecUrl(url); } diff --git a/framework/src/org/apache/cordova/DroidGap.java b/framework/src/org/apache/cordova/DroidGap.java index 9827ed53..b36320fa 100755 --- a/framework/src/org/apache/cordova/DroidGap.java +++ b/framework/src/org/apache/cordova/DroidGap.java @@ -406,6 +406,8 @@ public class DroidGap extends Activity implements CordovaInterface { } this.splashscreenTime = time; + this.splashscreen = this.getIntegerProperty("splashscreen", 0); + this.showSplashScreen(this.splashscreenTime); this.appView.loadUrl(url, time); } @@ -716,7 +718,7 @@ public class DroidGap extends Activity implements CordovaInterface { */ public void sendJavascript(String statement) { if (this.appView != null) { - this.appView.jsMessageQueue.add(statement); + this.appView.jsMessageQueue.addJavaScript(statement); } } @@ -996,7 +998,6 @@ public class DroidGap extends Activity implements CordovaInterface { this.runOnUiThread(runnable); } - @Override public boolean onKeyUp(int keyCode, KeyEvent event) { @@ -1024,8 +1025,11 @@ public class DroidGap extends Activity implements CordovaInterface { this.removeSplashScreen(); } else { - this.splashscreen = this.getIntegerProperty("splashscreen", 0); - this.showSplashScreen(this.splashscreenTime); + // If the splash dialog is showing don't try to show it again + if (this.splashDialog != null && !this.splashDialog.isShowing()) { + this.splashscreen = this.getIntegerProperty("splashscreen", 0); + this.showSplashScreen(this.splashscreenTime); + } } } else if ("spinner".equals(id)) { diff --git a/framework/src/org/apache/cordova/ExifHelper.java b/framework/src/org/apache/cordova/ExifHelper.java index 4be79f1f..38ad0a60 100644 --- a/framework/src/org/apache/cordova/ExifHelper.java +++ b/framework/src/org/apache/cordova/ExifHelper.java @@ -23,7 +23,7 @@ import java.io.IOException; import android.media.ExifInterface; public class ExifHelper { - private String aperature = null; + private String aperture = null; private String datetime = null; private String exposureTime = null; private String flash = null; @@ -70,7 +70,7 @@ public class ExifHelper { * Reads all the EXIF data from the input file. */ public void readExifData() { - this.aperature = inFile.getAttribute(ExifInterface.TAG_APERTURE); + this.aperture = inFile.getAttribute(ExifInterface.TAG_APERTURE); this.datetime = inFile.getAttribute(ExifInterface.TAG_DATETIME); this.exposureTime = inFile.getAttribute(ExifInterface.TAG_EXPOSURE_TIME); this.flash = inFile.getAttribute(ExifInterface.TAG_FLASH); @@ -102,8 +102,8 @@ public class ExifHelper { return; } - if (this.aperature != null) { - this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperature); + if (this.aperture != null) { + this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperture); } if (this.datetime != null) { this.outFile.setAttribute(ExifInterface.TAG_DATETIME, this.datetime); diff --git a/framework/src/org/apache/cordova/ExposedJsApi.java b/framework/src/org/apache/cordova/ExposedJsApi.java new file mode 100755 index 00000000..710b2e0c --- /dev/null +++ b/framework/src/org/apache/cordova/ExposedJsApi.java @@ -0,0 +1,61 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ +package org.apache.cordova; + +import org.apache.cordova.api.PluginManager; +import org.apache.cordova.api.PluginResult; +import org.json.JSONException; + +/** + * Contains APIs that the JS can call. All functions in here should also have + * an equivalent entry in CordovaChromeClient.java, and be added to + * cordova-js/lib/android/plugin/android/promptbasednativeapi.js + */ +/* package */ class ExposedJsApi { + + private PluginManager pluginManager; + private NativeToJsMessageQueue jsMessageQueue; + + public ExposedJsApi(PluginManager pluginManager, NativeToJsMessageQueue jsMessageQueue) { + this.pluginManager = pluginManager; + this.jsMessageQueue = jsMessageQueue; + } + + 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 */); + String ret = ""; + if (!NativeToJsMessageQueue.DISABLE_EXEC_CHAINING || wasSync) { + ret = jsMessageQueue.popAndEncode(); + } + return ret; + } finally { + jsMessageQueue.setPaused(false); + } + } + + public void setNativeToJsBridgeMode(int value) { + jsMessageQueue.setBridgeMode(value); + } + + public String retrieveJsMessages() { + return jsMessageQueue.popAndEncode(); + } +} diff --git a/framework/src/org/apache/cordova/FileTransfer.java b/framework/src/org/apache/cordova/FileTransfer.java index 15b2f2e0..881caa54 100644 --- a/framework/src/org/apache/cordova/FileTransfer.java +++ b/framework/src/org/apache/cordova/FileTransfer.java @@ -85,7 +85,7 @@ public class FileTransfer extends Plugin { if (action.equals("upload")) { return upload(URLDecoder.decode(source), target, args); } else if (action.equals("download")) { - return download(source, target); + return download(source, target, args.optBoolean(2)); } else { return new PluginResult(PluginResult.Status.INVALID_ACTION); } @@ -287,7 +287,7 @@ public class FileTransfer extends Plugin { bytesRead = fileInputStream.read(buffer, 0, bufferSize); } - // send multipart form data necesssary after file data... + // send multipart form data necessary after file data... dos.writeBytes(tailParams); // close streams @@ -459,7 +459,7 @@ public class FileTransfer extends Plugin { * @param target Full path of the file on the file system * @return JSONObject the downloaded file */ - private PluginResult download(String source, String target) { + private PluginResult download(String source, String target, boolean trustEveryone) { Log.d(LOG_TAG, "download " + source + " to " + target); HttpURLConnection connection = null; @@ -473,7 +473,30 @@ public class FileTransfer extends Plugin { if (webView.isUrlWhiteListed(source)) { URL url = new URL(source); - connection = (HttpURLConnection) url.openConnection(); + boolean useHttps = url.getProtocol().toLowerCase().equals("https"); + // Open a HTTP connection to the URL based on protocol + if (useHttps) { + // Using standard HTTPS connection. Will not allow self signed certificate + if (!trustEveryone) { + connection = (HttpsURLConnection) url.openConnection(); + } + // Use our HTTPS connection that blindly trusts everyone. + // This should only be used in debug environments + else { + // Setup the HTTPS connection class to trust everyone + trustAllHosts(); + HttpsURLConnection https = (HttpsURLConnection) url.openConnection(); + // Save the current hostnameVerifier + defaultHostnameVerifier = https.getHostnameVerifier(); + // Setup the connection not to verify hostnames + https.setHostnameVerifier(DO_NOT_VERIFY); + connection = https; + } + } + // Return a standard HTTP connection + else { + connection = (HttpURLConnection) url.openConnection(); + } connection.setRequestMethod("GET"); //Add cookie support @@ -516,6 +539,12 @@ public class FileTransfer extends Plugin { FileUtils fileUtil = new FileUtils(); JSONObject fileEntry = fileUtil.getEntry(file); + // Revert back to the proper verifier and socket factories + if (trustEveryone && url.getProtocol().toLowerCase().equals("https")) { + ((HttpsURLConnection) connection).setHostnameVerifier(defaultHostnameVerifier); + HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory); + } + return new PluginResult(PluginResult.Status.OK, fileEntry); } else diff --git a/framework/src/org/apache/cordova/FileUtils.java b/framework/src/org/apache/cordova/FileUtils.java index ceb30eeb..1c8f0841 100755 --- a/framework/src/org/apache/cordova/FileUtils.java +++ b/framework/src/org/apache/cordova/FileUtils.java @@ -409,19 +409,19 @@ public class FileUtils extends Plugin { throw new InvalidModificationException("Can't rename a file to a directory"); } - FileChannel input = new FileInputStream(srcFile).getChannel(); - FileChannel output = new FileOutputStream(destFile).getChannel(); + FileInputStream istream = new FileInputStream(srcFile); + FileOutputStream ostream = new FileOutputStream(destFile); + FileChannel input = istream.getChannel(); + FileChannel output = ostream.getChannel(); - input.transferTo(0, input.size(), output); - - input.close(); - output.close(); - - /* - if (srcFile.length() != destFile.length()) { - return false; + try { + input.transferTo(0, input.size(), output); + } finally { + istream.close(); + ostream.close(); + input.close(); + output.close(); } - */ return getEntry(destFile); } @@ -480,7 +480,7 @@ public class FileUtils extends Plugin { // This weird test is to determine if we are copying or moving a directory into itself. // Copy /sdcard/myDir to /sdcard/myDir-backup is okay but - // Copy /sdcard/myDir to /sdcard/myDir/backup should thow an INVALID_MODIFICATION_ERR + // Copy /sdcard/myDir to /sdcard/myDir/backup should throw an INVALID_MODIFICATION_ERR if (dest.startsWith(src) && dest.indexOf(File.separator, src.length() - 1) != -1) { return true; } @@ -1008,14 +1008,17 @@ public class FileUtils extends Plugin { filename = stripFileProtocol(filename); RandomAccessFile raf = new RandomAccessFile(filename, "rw"); - - if (raf.length() >= size) { - FileChannel channel = raf.getChannel(); - channel.truncate(size); - return size; + try { + if (raf.length() >= size) { + FileChannel channel = raf.getChannel(); + channel.truncate(size); + return size; + } + + return raf.length(); + } finally { + raf.close(); } - - return raf.length(); } /** @@ -1040,7 +1043,7 @@ public class FileUtils extends Plugin { * Queries the media store to find out what the file path is for the Uri we supply * * @param contentUri the Uri of the audio/image/video - * @param cordova) the current applicaiton context + * @param cordova the current application context * @return the full path to the file */ @SuppressWarnings("deprecation") diff --git a/framework/src/org/apache/cordova/GeoBroker.java b/framework/src/org/apache/cordova/GeoBroker.java index 9b0838b8..d3bf6b34 100755 --- a/framework/src/org/apache/cordova/GeoBroker.java +++ b/framework/src/org/apache/cordova/GeoBroker.java @@ -191,4 +191,14 @@ public class GeoBroker extends Plugin { this.error(result, callbackId); } + + public boolean isGlobalListener(CordovaLocationListener listener) + { + if (gpsListener != null && networkListener != null) + { + return gpsListener.equals(listener) || networkListener.equals(listener); + } + else + return false; + } } diff --git a/framework/src/org/apache/cordova/NativeToJsMessageQueue.java b/framework/src/org/apache/cordova/NativeToJsMessageQueue.java index 9ba15623..d2732c46 100755 --- a/framework/src/org/apache/cordova/NativeToJsMessageQueue.java +++ b/framework/src/org/apache/cordova/NativeToJsMessageQueue.java @@ -19,12 +19,11 @@ package org.apache.cordova; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.LinkedList; import org.apache.cordova.api.CordovaInterface; +import org.apache.cordova.api.PluginResult; import android.os.Message; import android.util.Log; @@ -39,15 +38,36 @@ public class NativeToJsMessageQueue { // This must match the default value in incubator-cordova-js/lib/android/exec.js private static final int DEFAULT_BRIDGE_MODE = 1; + // Set this to true to force plugin results to be encoding as + // JS instead of the custom format (useful for benchmarking). + private static final boolean FORCE_ENCODE_USING_EVAL = false; + + // Disable URL-based exec() bridge by default since it's a bit of a + // security concern. + static final boolean ENABLE_LOCATION_CHANGE_EXEC_MODE = false; + + // Disable sending back native->JS messages during an exec() when the active + // exec() is asynchronous. Set this to true when running bridge benchmarks. + static final boolean DISABLE_EXEC_CHAINING = false; + + // Arbitrarily chosen upper limit for how much data to send to JS in one shot. + private static final int MAX_PAYLOAD_SIZE = 50 * 1024; + /** * The index into registeredListeners to treat as active. */ private int activeListenerIndex; + /** + * When true, the active listener is not fired upon enqueue. When set to false, + * the active listener will be fired if the queue is non-empty. + */ + private boolean paused; + /** * The list of JavaScript statements to be sent to JavaScript. */ - private final LinkedList queue = new LinkedList(); + private final LinkedList queue = new LinkedList(); /** * The array of listeners that can be used to send messages to JS. @@ -56,7 +76,7 @@ public class NativeToJsMessageQueue { private final CordovaInterface cordova; private final CordovaWebView webView; - + public NativeToJsMessageQueue(CordovaWebView webView, CordovaInterface cordova) { this.cordova = cordova; this.webView = webView; @@ -81,7 +101,7 @@ public class NativeToJsMessageQueue { synchronized (this) { activeListenerIndex = value; BridgeMode activeListener = registeredListeners[value]; - if (!queue.isEmpty() && activeListener != null) { + if (!paused && !queue.isEmpty() && activeListener != null) { activeListener.onNativeToJsMessageAvailable(); } } @@ -99,60 +119,151 @@ public class NativeToJsMessageQueue { } } + private int calculatePackedMessageLength(JsMessage message) { + int messageLen = message.calculateEncodedLength(); + String messageLenStr = String.valueOf(messageLen); + return messageLenStr.length() + messageLen + 1; + } + + private void packMessage(JsMessage message, StringBuilder sb) { + sb.append(message.calculateEncodedLength()) + .append(' '); + message.encodeAsMessage(sb); + } + /** - * Removes and returns the last statement in the queue. + * Combines and returns queued messages combined into a single string. + * Combines as many messages as possible, while staying under MAX_PAYLOAD_SIZE. * Returns null if the queue is empty. */ - public String pop() { + public String popAndEncode() { synchronized (this) { if (queue.isEmpty()) { return null; } - return queue.remove(0); + int totalPayloadLen = 0; + int numMessagesToSend = 0; + for (JsMessage message : queue) { + int messageSize = calculatePackedMessageLength(message); + if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE) { + break; + } + totalPayloadLen += messageSize; + numMessagesToSend += 1; + } + + StringBuilder sb = new StringBuilder(totalPayloadLen); + for (int i = 0; i < numMessagesToSend; ++i) { + JsMessage message = queue.removeFirst(); + packMessage(message, sb); + } + + if (!queue.isEmpty()) { + // Attach a char to indicate that there are more messages pending. + sb.append('*'); + } + return sb.toString(); } } - + /** - * Combines and returns all statements. Clears the queue. - * Returns null if the queue is empty. + * Same as popAndEncode(), except encodes in a form that can be executed as JS. */ - public String popAll() { + private String popAndEncodeAsJs() { synchronized (this) { int length = queue.size(); if (length == 0) { return null; } - StringBuffer sb = new StringBuffer(); + int totalPayloadLen = 0; + int numMessagesToSend = 0; + for (JsMessage message : queue) { + int messageSize = message.calculateEncodedLength() + 50; // overestimate. + if (numMessagesToSend > 0 && totalPayloadLen + messageSize > MAX_PAYLOAD_SIZE) { + break; + } + totalPayloadLen += messageSize; + numMessagesToSend += 1; + } + boolean willSendAllMessages = numMessagesToSend == queue.size(); + StringBuilder sb = new StringBuilder(totalPayloadLen + (willSendAllMessages ? 0 : 100)); // Wrap each statement in a try/finally so that if one throws it does // not affect the next. - int i = 0; - for (String message : queue) { - if (++i == length) { - sb.append(message); + for (int i = 0; i < numMessagesToSend; ++i) { + JsMessage message = queue.removeFirst(); + if (willSendAllMessages && (i + 1 == numMessagesToSend)) { + message.encodeAsJsMessage(sb); } else { - sb.append("try{") - .append(message) - .append("}finally{"); + sb.append("try{"); + message.encodeAsJsMessage(sb); + sb.append("}finally{"); } } - for ( i = 1; i < length; ++i) { + if (!willSendAllMessages) { + sb.append("window.setTimeout(function(){cordova.require('cordova/plugin/android/polling').pollOnce();},0);"); + } + for (int i = willSendAllMessages ? 1 : 0; i < numMessagesToSend; ++i) { sb.append('}'); } - queue.clear(); return sb.toString(); } - } + } /** * Add a JavaScript statement to the list. */ - public void add(String statement) { + public void addJavaScript(String statement) { + enqueueMessage(new JsMessage(statement)); + } + + /** + * Add a JavaScript statement to the list. + */ + public void addPluginResult(PluginResult result, String callbackId) { + // Don't send anything if there is no result and there is no need to + // clear the callbacks. + boolean noResult = result.getStatus() == PluginResult.Status.NO_RESULT.ordinal(); + boolean keepCallback = result.getKeepCallback(); + if (noResult && keepCallback) { + return; + } + JsMessage message = new JsMessage(result, callbackId); + if (FORCE_ENCODE_USING_EVAL) { + StringBuilder sb = new StringBuilder(message.calculateEncodedLength() + 50); + message.encodeAsJsMessage(sb); + message = new JsMessage(sb.toString()); + } + + enqueueMessage(message); + } + + private void enqueueMessage(JsMessage message) { synchronized (this) { - queue.add(statement); - if (registeredListeners[activeListenerIndex] != null) { + queue.add(message); + if (!paused && registeredListeners[activeListenerIndex] != null) { registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable(); } + } + } + + public void setPaused(boolean value) { + if (paused && value) { + // This should never happen. If a use-case for it comes up, we should + // change pause to be a counter. + Log.e(LOG_TAG, "nested call to setPaused detected.", new Throwable()); } + paused = value; + if (!value) { + synchronized (this) { + if (!queue.isEmpty() && registeredListeners[activeListenerIndex] != null) { + registeredListeners[activeListenerIndex].onNativeToJsMessageAvailable(); + } + } + } + } + + public boolean getPaused() { + return paused; } private interface BridgeMode { @@ -170,8 +281,17 @@ public class NativeToJsMessageQueue { /** Uses webView.loadUrl("javascript:") to execute messages. */ private class LoadUrlBridgeMode implements BridgeMode { + final Runnable runnable = new Runnable() { + public void run() { + String js = popAndEncodeAsJs(); + if (js != null) { + webView.loadUrlNow("javascript:" + js); + } + } + }; + public void onNativeToJsMessageAvailable() { - webView.loadUrlNow("javascript:" + popAll()); + cordova.getActivity().runOnUiThread(runnable); } } @@ -187,7 +307,9 @@ public class NativeToJsMessageQueue { } } }; - + OnlineEventsBridgeMode() { + webView.setNetworkAvailable(true); + } public void onNativeToJsMessageAvailable() { cordova.getActivity().runOnUiThread(runnable); } @@ -241,7 +363,7 @@ public class NativeToJsMessageQueue { } // webViewCore is lazily initialized, and so may not be available right away. if (sendMessageMethod != null) { - String js = popAll(); + String js = popAndEncodeAsJs(); Message execJsMessage = Message.obtain(null, EXECUTE_JS, js); try { sendMessageMethod.invoke(webViewCore, execJsMessage); @@ -251,4 +373,94 @@ public class NativeToJsMessageQueue { } } } + private static class JsMessage { + final String jsPayloadOrCallbackId; + final PluginResult pluginResult; + JsMessage(String js) { + jsPayloadOrCallbackId = js; + pluginResult = null; + } + JsMessage(PluginResult pluginResult, String callbackId) { + jsPayloadOrCallbackId = callbackId; + this.pluginResult = pluginResult; + } + + int calculateEncodedLength() { + if (pluginResult == null) { + return jsPayloadOrCallbackId.length() + 1; + } + int statusLen = String.valueOf(pluginResult.getStatus()).length(); + int ret = 2 + statusLen + 1 + jsPayloadOrCallbackId.length() + 1; + switch (pluginResult.getMessageType()) { + case PluginResult.MESSAGE_TYPE_BOOLEAN: + ret += 1; + break; + case PluginResult.MESSAGE_TYPE_NUMBER: // n + ret += 1 + pluginResult.getMessage().length(); + break; + case PluginResult.MESSAGE_TYPE_STRING: // s + ret += 1 + pluginResult.getStrMessage().length(); + break; + case PluginResult.MESSAGE_TYPE_JSON: + default: + ret += pluginResult.getMessage().length(); + } + return ret; + } + + void encodeAsMessage(StringBuilder sb) { + if (pluginResult == null) { + sb.append('J') + .append(jsPayloadOrCallbackId); + return; + } + int status = pluginResult.getStatus(); + boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal(); + boolean resultOk = status == PluginResult.Status.OK.ordinal(); + boolean keepCallback = pluginResult.getKeepCallback(); + + sb.append((noResult || resultOk) ? 'S' : 'F') + .append(keepCallback ? '1' : '0') + .append(status) + .append(' ') + .append(jsPayloadOrCallbackId) + .append(' '); + switch (pluginResult.getMessageType()) { + case PluginResult.MESSAGE_TYPE_BOOLEAN: + sb.append(pluginResult.getMessage().charAt(0)); // t or f. + break; + case PluginResult.MESSAGE_TYPE_NUMBER: // n + sb.append('n') + .append(pluginResult.getMessage()); + break; + case PluginResult.MESSAGE_TYPE_STRING: // s + sb.append('s') + .append(pluginResult.getStrMessage()); + break; + case PluginResult.MESSAGE_TYPE_JSON: + default: + sb.append(pluginResult.getMessage()); // [ or { + } + } + + void encodeAsJsMessage(StringBuilder sb) { + if (pluginResult == null) { + sb.append(jsPayloadOrCallbackId); + } else { + int status = pluginResult.getStatus(); + boolean success = (status == PluginResult.Status.OK.ordinal()) || (status == PluginResult.Status.NO_RESULT.ordinal()); + sb.append("cordova.callbackFromNative('") + .append(jsPayloadOrCallbackId) + .append("',") + .append(success) + .append(",") + .append(status) + .append(",") + .append(pluginResult.getMessage()) + .append(",") + .append(pluginResult.getKeepCallback()) + .append(");"); + } + } + } } diff --git a/framework/src/org/apache/cordova/Storage.java b/framework/src/org/apache/cordova/Storage.java index 13fd919c..551e9159 100755 --- a/framework/src/org/apache/cordova/Storage.java +++ b/framework/src/org/apache/cordova/Storage.java @@ -180,7 +180,7 @@ public class Storage extends Plugin { } /** - * Checks to see the the query is a Data Definintion command + * Checks to see the the query is a Data Definition command * * @param query to be executed * @return true if it is a DDL command, false otherwise diff --git a/framework/src/org/apache/cordova/api/Plugin.java b/framework/src/org/apache/cordova/api/Plugin.java index c7c2be79..84b67e00 100755 --- a/framework/src/org/apache/cordova/api/Plugin.java +++ b/framework/src/org/apache/cordova/api/Plugin.java @@ -139,14 +139,19 @@ public abstract class Plugin implements IPlugin { /** * Send generic JavaScript statement back to JavaScript. - * success(...) and error(...) should be used instead where possible. - * - * @param statement + * sendPluginResult() should be used instead where possible. */ public void sendJavascript(String statement) { this.webView.sendJavascript(statement); } + /** + * Send generic JavaScript statement back to JavaScript. + */ + public void sendPluginResult(PluginResult pluginResult, String callbackId) { + this.webView.sendPluginResult(pluginResult, callbackId); + } + /** * Call the JavaScript success callback for this plugin. * @@ -158,7 +163,7 @@ public abstract class Plugin implements IPlugin { * @param callbackId The callback id used when calling back into JavaScript. */ public void success(PluginResult pluginResult, String callbackId) { - this.webView.sendJavascript(pluginResult.toSuccessCallbackString(callbackId)); + this.webView.sendPluginResult(pluginResult, callbackId); } /** @@ -168,7 +173,7 @@ public abstract class Plugin implements IPlugin { * @param callbackId The callback id used when calling back into JavaScript. */ public void success(JSONObject message, String callbackId) { - this.webView.sendJavascript(new PluginResult(PluginResult.Status.OK, message).toSuccessCallbackString(callbackId)); + this.webView.sendPluginResult(new PluginResult(PluginResult.Status.OK, message), callbackId); } /** @@ -178,7 +183,7 @@ public abstract class Plugin implements IPlugin { * @param callbackId The callback id used when calling back into JavaScript. */ public void success(String message, String callbackId) { - this.webView.sendJavascript(new PluginResult(PluginResult.Status.OK, message).toSuccessCallbackString(callbackId)); + this.webView.sendPluginResult(new PluginResult(PluginResult.Status.OK, message), callbackId); } /** @@ -188,7 +193,7 @@ public abstract class Plugin implements IPlugin { * @param callbackId The callback id used when calling back into JavaScript. */ public void error(PluginResult pluginResult, String callbackId) { - this.webView.sendJavascript(pluginResult.toErrorCallbackString(callbackId)); + this.webView.sendPluginResult(pluginResult, callbackId); } /** @@ -198,7 +203,7 @@ public abstract class Plugin implements IPlugin { * @param callbackId The callback id used when calling back into JavaScript. */ public void error(JSONObject message, String callbackId) { - this.webView.sendJavascript(new PluginResult(PluginResult.Status.ERROR, message).toErrorCallbackString(callbackId)); + this.webView.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message), callbackId); } /** @@ -208,6 +213,6 @@ public abstract class Plugin implements IPlugin { * @param callbackId The callback id used when calling back into JavaScript. */ public void error(String message, String callbackId) { - this.webView.sendJavascript(new PluginResult(PluginResult.Status.ERROR, message).toErrorCallbackString(callbackId)); + this.webView.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, message), callbackId); } } diff --git a/framework/src/org/apache/cordova/api/PluginManager.java b/framework/src/org/apache/cordova/api/PluginManager.java index 7c6c28e6..aef63023 100755 --- a/framework/src/org/apache/cordova/api/PluginManager.java +++ b/framework/src/org/apache/cordova/api/PluginManager.java @@ -22,6 +22,8 @@ import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import org.apache.cordova.CordovaWebView; import org.json.JSONArray; @@ -45,6 +47,7 @@ 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; @@ -200,7 +203,7 @@ public class PluginManager { * or execute the class denoted by the clazz argument. * * @param service String containing the service to run - * @param action String containt the action that the class is supposed to perform. This is + * @param action String containing the action that the class is supposed to perform. This is * passed to the plugin execute method and it is up to the plugin developer * how to deal with it. * @param callbackId String containing the id of the callback that is execute in JavaScript if @@ -210,10 +213,9 @@ public class PluginManager { * @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 PluginResult to send to the page, or null if no response is ready yet. + * @return Whether the task completed synchronously. */ - public PluginResult 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, final boolean async) { PluginResult cr = null; boolean runAsync = async; try { @@ -224,30 +226,26 @@ public class PluginManager { runAsync = async && !plugin.isSynch(action); if (runAsync) { // Run this on a different thread so that this one can return back to JS - Thread thread = new Thread(new Runnable() { + execThreadPool.execute(new Runnable() { public void run() { try { // Call execute on the plugin so that it can do it's thing PluginResult cr = plugin.execute(action, args, callbackId); - String callbackString = cr.toCallbackString(callbackId); - if (callbackString != null) { - app.sendJavascript(callbackString); - } + app.sendPluginResult(cr, callbackId); } catch (Exception e) { PluginResult cr = new PluginResult(PluginResult.Status.ERROR, e.getMessage()); - app.sendJavascript(cr.toErrorCallbackString(callbackId)); + app.sendPluginResult(cr, callbackId); } } }); - thread.start(); - return null; + return false; } else { // Call execute on the plugin so that it can do it's thing cr = plugin.execute(action, args, callbackId); // If no result to be sent and keeping callback, then no need to sent back to JavaScript if ((cr.getStatus() == PluginResult.Status.NO_RESULT.ordinal()) && cr.getKeepCallback()) { - return null; + return true; } } } @@ -260,12 +258,13 @@ public class PluginManager { if (cr == null) { cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION); } - app.sendJavascript(cr.toErrorCallbackString(callbackId)); + app.sendPluginResult(cr, callbackId); } if (cr == null) { cr = new PluginResult(PluginResult.Status.NO_RESULT); } - return cr; + app.sendPluginResult(cr, callbackId); + return true; } /** diff --git a/framework/src/org/apache/cordova/api/PluginResult.java b/framework/src/org/apache/cordova/api/PluginResult.java index eacf6d5c..28c7b980 100755 --- a/framework/src/org/apache/cordova/api/PluginResult.java +++ b/framework/src/org/apache/cordova/api/PluginResult.java @@ -23,43 +23,49 @@ import org.json.JSONObject; public class PluginResult { private final int status; - private final String message; + private final int messageType; private boolean keepCallback = false; - + private String strMessage; + private String encodedMessage; public PluginResult(Status status) { - this.status = status.ordinal(); - this.message = "\"" + PluginResult.StatusMessages[this.status] + "\""; + this(status, PluginResult.StatusMessages[status.ordinal()]); } public PluginResult(Status status, String message) { this.status = status.ordinal(); - this.message = JSONObject.quote(message); + this.messageType = MESSAGE_TYPE_STRING; + this.strMessage = message; } public PluginResult(Status status, JSONArray message) { this.status = status.ordinal(); - this.message = message.toString(); + this.messageType = MESSAGE_TYPE_JSON; + encodedMessage = message.toString(); } public PluginResult(Status status, JSONObject message) { this.status = status.ordinal(); - this.message = message.toString(); + this.messageType = MESSAGE_TYPE_JSON; + encodedMessage = message.toString(); } public PluginResult(Status status, int i) { this.status = status.ordinal(); - this.message = ""+i; + this.messageType = MESSAGE_TYPE_NUMBER; + this.encodedMessage = ""+i; } public PluginResult(Status status, float f) { this.status = status.ordinal(); - this.message = ""+f; + this.messageType = MESSAGE_TYPE_NUMBER; + this.encodedMessage = ""+f; } public PluginResult(Status status, boolean b) { this.status = status.ordinal(); - this.message = ""+b; + this.messageType = MESSAGE_TYPE_BOOLEAN; + this.encodedMessage = Boolean.toString(b); } public void setKeepCallback(boolean b) { @@ -70,18 +76,35 @@ public class PluginResult { return status; } + public int getMessageType() { + return messageType; + } + public String getMessage() { - return message; + if (encodedMessage == null) { + encodedMessage = JSONObject.quote(strMessage); + } + return encodedMessage; + } + + /** + * If messageType == MESSAGE_TYPE_STRING, then returns the message string. + * Otherwise, returns null. + */ + public String getStrMessage() { + return strMessage; } public boolean getKeepCallback() { return this.keepCallback; } + @Deprecated // Use sendPluginResult instead of sendJavascript. public String getJSONString() { - return "{\"status\":" + this.status + ",\"message\":" + this.message + ",\"keepCallback\":" + this.keepCallback + "}"; + return "{\"status\":" + this.status + ",\"message\":" + this.getMessage() + ",\"keepCallback\":" + this.keepCallback + "}"; } + @Deprecated // Use sendPluginResult instead of sendJavascript. public String toCallbackString(String callbackId) { // If no result to be sent and keeping callback, then no need to sent back to JavaScript if ((status == PluginResult.Status.NO_RESULT.ordinal()) && keepCallback) { @@ -95,14 +118,22 @@ public class PluginResult { return toErrorCallbackString(callbackId); } + + @Deprecated // Use sendPluginResult instead of sendJavascript. public String toSuccessCallbackString(String callbackId) { return "cordova.callbackSuccess('"+callbackId+"',"+this.getJSONString()+");"; } + @Deprecated // Use sendPluginResult instead of sendJavascript. public String toErrorCallbackString(String callbackId) { return "cordova.callbackError('"+callbackId+"', " + this.getJSONString()+ ");"; } + public static final int MESSAGE_TYPE_STRING = 1; + public static final int MESSAGE_TYPE_JSON = 2; + public static final int MESSAGE_TYPE_NUMBER = 3; + public static final int MESSAGE_TYPE_BOOLEAN = 4; + public static String[] StatusMessages = new String[] { "No result", "OK",