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 @@
 
 
     <application android:icon="@drawable/icon" android:label="@string/app_name"
+        android:hardwareAccelerated="true"
         android:debuggable="true">
         <activity android:name="__ACTIVITY__" android:label="@string/app_name"
+                android:theme="@android:style/Theme.Black.NoTitleBar"
                 android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
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; j<len; j++) {
-                !c[j].fired?c[j].subscribeOnce(f):i--;
+                if (c[j].state == 0) {
+                    throw Error('Can only use join with sticky channels.')
+                }
+                c[j].subscribe(f);
             }
-            if (!i) h();
+            if (!len) h();
         },
-        create: function (type, opts) {
-            channel[type] = new Channel(type, opts);
-            return channel[type];
+        create: function(type) {
+            return channel[type] = new Channel(type, false);
+        },
+        createSticky: function(type) {
+            return channel[type] = new Channel(type, true);
         },
 
         /**
@@ -506,13 +529,7 @@ var Channel = function(type, opts) {
          */
         waitForInitialization: function(feature) {
             if (feature) {
-                var c = null;
-                if (this[feature]) {
-                    c = this[feature];
-                }
-                else {
-                    c = this.create(feature);
-                }
+                var c = channel[feature] || this.createSticky(feature);
                 this.deviceReadyChannelsMap[feature] = c;
                 this.deviceReadyChannelsArray.push(c);
             }
@@ -532,7 +549,7 @@ var Channel = function(type, opts) {
     };
 
 function forceFunction(f) {
-    if (f === null || f === undefined || typeof f != 'function') throw "Function required as first argument!";
+    if (typeof f != 'function') throw "Function required as first argument!";
 }
 
 /**
@@ -542,67 +559,50 @@ function forceFunction(f) {
  * and a guid that can be used to stop subscribing to the channel.
  * Returns the guid.
  */
-Channel.prototype.subscribe = function(f, c, g) {
+Channel.prototype.subscribe = function(f, c) {
     // need a function to call
     forceFunction(f);
+    if (this.state == 2) {
+        f.apply(c || this, this.fireArgs);
+        return;
+    }
 
-    var func = f;
+    var func = f,
+        guid = f.observer_guid;
     if (typeof c == "object") { func = utils.close(c, f); }
 
-    g = g || func.observer_guid || f.observer_guid;
-    if (!g) {
+    if (!guid) {
         // first time any channel has seen this subscriber
-        g = nextGuid++;
+        guid = '' + nextGuid++;
     }
-    func.observer_guid = g;
-    f.observer_guid = g;
+    func.observer_guid = guid;
+    f.observer_guid = guid;
 
     // Don't add the same handler more than once.
-    if (!this.handlers[g]) {
-        this.handlers[g] = func;
+    if (!this.handlers[guid]) {
+        this.handlers[guid] = func;
         this.numHandlers++;
-        if (this.events.onSubscribe) this.events.onSubscribe.call(this);
-        if (this.fired) func.apply(this, this.fireArgs);
+        if (this.numHandlers == 1) {
+            this.onHasSubscribersChange && this.onHasSubscribersChange();
+        }
     }
-    return g;
-};
-
-/**
- * Like subscribe but the function is only called once and then it
- * auto-unsubscribes itself.
- */
-Channel.prototype.subscribeOnce = function(f, c) {
-    // need a function to call
-    forceFunction(f);
-
-    var g = null;
-    var _this = this;
-    if (this.fired) {
-        f.apply(c || null, this.fireArgs);
-    } else {
-        g = this.subscribe(function() {
-            _this.unsubscribe(g);
-            f.apply(c || null, arguments);
-        });
-        f.observer_guid = g;
-    }
-    return g;
 };
 
 /**
  * Unsubscribes the function with the given guid from the channel.
  */
-Channel.prototype.unsubscribe = function(g) {
+Channel.prototype.unsubscribe = function(f) {
     // need a function to unsubscribe
-    if (g === null || g === undefined) { throw "You must pass _something_ into Channel.unsubscribe"; }
+    forceFunction(f);
 
-    if (typeof g == 'function') { g = g.observer_guid; }
-    var handler = this.handlers[g];
+    var guid = f.observer_guid,
+        handler = this.handlers[guid];
     if (handler) {
-        if (handler.observer_guid) handler.observer_guid=null;
-        delete this.handlers[g];
+        delete this.handlers[guid];
         this.numHandlers--;
-        if (this.events.onUnsubscribe) this.events.onUnsubscribe.call(this);
+        if (this.numHandlers == 0) {
+            this.onHasSubscribersChange && this.onHasSubscribersChange();
+        }
     }
 };
 
@@ -610,10 +610,14 @@ Channel.prototype.unsubscribe = function(g) {
  * Calls all functions subscribed to this channel.
  */
 Channel.prototype.fire = function(e) {
-    if (this.enabled) {
-        var fail = false;
-        this.fired = true;
-        this.fireArgs = arguments;
+    var fail = false,
+        fireArgs = Array.prototype.slice.call(arguments);
+    // Apply stickiness.
+    if (this.state == 1) {
+        this.state = 2;
+        this.fireArgs = fireArgs;
+    }
+    if (this.numHandlers) {
         // Copy the values first so that it is safe to modify it from within
         // callbacks.
         var toCall = [];
@@ -621,33 +625,36 @@ Channel.prototype.fire = function(e) {
             toCall.push(this.handlers[item]);
         }
         for (var i = 0; i < toCall.length; ++i) {
-            var rv = (toCall[i].apply(this, arguments)===false);
-            fail = fail || rv;
+            toCall[i].apply(this, fireArgs);
+        }
+        if (this.state == 2 && this.numHandlers) {
+            this.numHandlers = 0;
+            this.handlers = {};
+            this.onHasSubscribersChange && this.onHasSubscribersChange();
         }
-        return !fail;
     }
-    return true;
 };
 
+
 // defining them here so they are ready super fast!
 // DOM event that is received when the web page is loaded and parsed.
-channel.create('onDOMContentLoaded');
+channel.createSticky('onDOMContentLoaded');
 
 // Event to indicate the Cordova native side is ready.
-channel.create('onNativeReady');
+channel.createSticky('onNativeReady');
 
 // Event to indicate that all Cordova JavaScript objects have been created
 // and it's time to run plugin constructors.
-channel.create('onCordovaReady');
+channel.createSticky('onCordovaReady');
 
 // Event to indicate that device properties are available
-channel.create('onCordovaInfoReady');
+channel.createSticky('onCordovaInfoReady');
 
 // Event to indicate that the connection property has been set.
-channel.create('onCordovaConnectionReady');
+channel.createSticky('onCordovaConnectionReady');
 
 // Event to indicate that Cordova is ready
-channel.create('onDeviceReady');
+channel.createSticky('onDeviceReady');
 
 // Event to indicate a resume lifecycle event
 channel.create('onResume');
@@ -656,7 +663,7 @@ channel.create('onResume');
 channel.create('onPause');
 
 // Event to indicate a destroy lifecycle event
-channel.create('onDestroy');
+channel.createSticky('onDestroy');
 
 // Channels that must fire before "deviceready" is fired.
 channel.waitForInitialization('onCordovaReady');
@@ -668,6 +675,7 @@ module.exports = channel;
 
 // file: lib/common/common.js
 define("cordova/common", function(require, exports, module) {
+
 module.exports = {
     objects: {
         cordova: {
@@ -705,6 +713,9 @@ module.exports = {
                 compass:{
                     path: 'cordova/plugin/compass'
                 },
+                connection: {
+                    path: 'cordova/plugin/network'
+                },
                 contacts: {
                     path: 'cordova/plugin/contacts'
                 },
@@ -721,7 +732,8 @@ module.exports = {
                 network: {
                     children: {
                         connection: {
-                            path: 'cordova/plugin/network'
+                            path: 'cordova/plugin/network',
+                            deprecated: 'navigator.network.connection is deprecated. Use navigator.connection instead.'
                         }
                     }
                 },
@@ -872,6 +884,7 @@ module.exports = {
 
 // file: lib/android/exec.js
 define("cordova/exec", function(require, exports, module) {
+
 /**
  * Execute a cordova command.  It is up to the native side whether this action
  * is synchronous or asynchronous.  The native side can return:
@@ -889,6 +902,7 @@ define("cordova/exec", function(require, exports, module) {
 var cordova = require('cordova'),
     callback = require('cordova/plugin/android/callback'),
     polling = require('cordova/plugin/android/polling'),
+    nativeApiProvider = require('cordova/plugin/android/nativeapiprovider'),
     jsToNativeBridgeMode,
     nativeToJsBridgeMode,
     jsToNativeModes = {
@@ -900,7 +914,7 @@ var cordova = require('cordova'),
         LOCATION_CHANGE: 2
     },
     nativeToJsModes = {
-        // Polls for messages using the prompt() bridge.
+        // Polls for messages using the JS->Native bridge.
         POLLING: 0,
         // Does an XHR to a local server, which will send back messages. This is
         // broken on ICS when a proxy server is configured.
@@ -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<String> names = new ArrayList<String>();
-
         // 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<String> urls = new Stack<String>();
@@ -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<String> queue = new LinkedList<String>();
+    private final LinkedList<JsMessage> queue = new LinkedList<JsMessage>();
 
     /**
      * 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",