diff --git a/bin/create.bat b/bin/create.bat
index b9c9392b..7182d21e 100644
--- a/bin/create.bat
+++ b/bin/create.bat
@@ -1 +1 @@
-cscript create.js
\ No newline at end of file
+cscript bin\create.js %*
\ No newline at end of file
diff --git a/bin/create.js b/bin/create.js
index 24e70325..a15db7a2 100644
--- a/bin/create.js
+++ b/bin/create.js
@@ -25,8 +25,9 @@
  */
 
 function read(filename) {
+    WScript.Echo('Reading in ' + filename);
     var fso=WScript.CreateObject("Scripting.FileSystemObject");
-    var f=fso.OpenTextFile(filename, 1, true);
+    var f=fso.OpenTextFile(filename, 1);
     var s=f.ReadAll();
     f.Close();
     return s;
@@ -40,13 +41,32 @@ function write(filename, contents) {
 function replaceInFile(filename, regexp, replacement) {
     write(filename, read(filename).replace(regexp, replacement));
 }
-function exec(s) {
+function exec(s, output) {
+    WScript.Echo('Executing ' + s);
     var o=shell.Exec(s);
+    while (o.Status == 0) {
+        WScript.Sleep(100);
+    }
+    WScript.Echo("Command exited with code " + o.Status);
+}
+
+function fork(s) {
+    WScript.Echo('Executing ' + s);
+    var o=shell.Exec(s);
+    while (o.Status != 1) {
+        WScript.Sleep(100);
+    }
+    WScript.Echo(o.StdOut.ReadAll());
+    WScript.Echo(o.StdErr.ReadAll());
+    WScript.Echo("Command exited with code " + o.Status);
 }
 
 var args = WScript.Arguments, PROJECT_PATH="example", 
     PACKAGE="org.apache.cordova.example", ACTIVITY="cordovaExample",
     shell=WScript.CreateObject("WScript.Shell");
+    
+// working dir
+var ROOT = WScript.ScriptFullName.split('\\bin\\create.js').join('');
 
 if (args.Count() == 3) {
     WScript.Echo('Found expected arguments');
@@ -61,7 +81,15 @@ var MANIFEST_PATH=PROJECT_PATH+'\\AndroidManifest.xml';
 var TARGET=shell.Exec('android.bat list targets').StdOut.ReadAll().match(/id:\s([0-9]).*/)[1];
 var VERSION=read('VERSION').replace(/\r\n/,'').replace(/\n/,'');
 
-// clobber any existing example
+WScript.Echo("Project path: " + PROJECT_PATH);
+WScript.Echo("Package: " + PACKAGE);
+WScript.Echo("Activity: " + ACTIVITY);
+WScript.Echo("Package as path: " + PACKAGE_AS_PATH);
+WScript.Echo("Activity path: " + ACTIVITY_PATH);
+WScript.Echo("Manifest path: " + MANIFEST_PATH);
+WScript.Echo("Cordova version: " + VERSION);
+
+// TODO: clobber any existing example
 
 /*
 if [ $# -eq 0 ]
@@ -76,32 +104,71 @@ exec('android.bat create project --target '+TARGET+' --path '+PROJECT_PATH+' --p
 // update the cordova framework project to a target that exists on this machine
 exec('android.bat update project --target '+TARGET+' --path framework');
 
+// pull down commons codec if necessary
+var fso = WScript.CreateObject('Scripting.FileSystemObject');
+if (!fso.FileExists(ROOT + '\\framework\\libs\\commons-codec-1.6.jar')) {
+  // We need the .jar
+  var url = 'http://mirror.symnds.com/software/Apache//commons/codec/binaries/commons-codec-1.6-bin.zip';
+  var savePath = ROOT + '\\framework\\libs\\commons-codec-1.6-bin.zip';
+  if (!fso.FileExists(savePath)) {
+    // We need the zip to get the jar
+    var xhr = WScript.CreateObject('MSXML2.XMLHTTP');
+    xhr.open('GET', url, false);
+    xhr.send();
+    if (xhr.status == 200) {
+      var stream = WScript.CreateObject('ADODB.Stream');
+      stream.Open();
+      stream.Type = 1;
+      stream.Write(xhr.ResponseBody);
+      stream.Position = 0;
+      stream.SaveToFile(savePath);
+      stream.Close();
+    } else {
+      WScript.Echo('Could not retrieve the commons-codec. Please download it yourself and put into the framework/libs directory. This process may fail now. Sorry.');
+    }
+  }
+  var app = WScript.CreateObject('Shell.Application');
+  var source = app.NameSpace(savePath).Items();
+  var target = app.NameSpace(ROOT + '\\framework\\libs');
+  target.CopyHere(source, 256);
+  
+  // Move the jar into libs
+  fso.MoveFile(ROOT + '\\framework\\libs\\commons-codec-1.6\\commons-codec-1.6.jar', ROOT + '\\framework\\libs\\commons-codec-1.6.jar');
+  
+  // Clean up
+  fso.DeleteFile(ROOT + '\\framework\\libs\\commons-codec-1.6-bin.zip');
+  fso.DeleteFolder(ROOT + '\\framework\\libs\\commons-codec-1.6', true);
+}
+
+
 // compile cordova.js and cordova.jar
 // if you see an error about "Unable to resolve target" then you may need to 
 // update your android tools or install an additional Android platform version
 exec('ant.bat -f framework\\build.xml jar');
 
 // copy in the project template
-exec('cmd /c xcopy bin\\templates\\project '+PROJECT_PATH+' /S /Y');
+exec('cmd /c xcopy bin\\templates\\project\\* '+PROJECT_PATH+' /S /Y');
+
+// copy example www assets
+exec('cmd /c xcopy ' + PROJECT_PATH + '\\cordova\\assets ' + PROJECT_PATH + ' /S /Y');
 
 // copy in cordova.js
-exec('cmd /c copy framework\\assets\\www\\cordova-'+VERSION+'.js '+PROJECT_PATH+'\\assets\\www\\cordova-'+VERSION+'.js /Y');
+exec('cmd /c copy framework\\assets\\js\\cordova.android.js '+PROJECT_PATH+'\\.cordova\\android\\cordova-'+VERSION+'.js /Y');
 
 // copy in cordova.jar
-exec('cmd /c copy framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\libs\\cordova-'+VERSION+'.jar /Y');
+exec('cmd /c copy framework\\cordova-'+VERSION+'.jar '+PROJECT_PATH+'\\.cordova\\android\\cordova-'+VERSION+'.jar /Y');
 
-// copy in default activity
-exec('cmd /c copy bin\\templates\\Activity.java '+ACTIVITY_PATH+' /Y');
+// copy in xml
+exec('cmd /c copy framework\\res\\xml\\cordova.xml ' + PROJECT_PATH + '\\.cordova\\android\\cordova.xml /Y');
+exec('cmd /c copy framework\\res\\xml\\plugins.xml ' + PROJECT_PATH + '\\.cordova\\android\\plugins.xml /Y');
 
-// interpolate the activity name and package
-replaceInFile(ACTIVITY_PATH, /__ACTIVITY__/, ACTIVITY);
-replaceInFile(ACTIVITY_PATH, /__ID__/, PACKAGE);
+// write out config file
+write(PROJECT_PATH + '\\.cordova\\config',
+  'VERSION=' + VERSION + '\r\n' +
+  'PROJECT_PATH=' + PROJECT_PATH + '\r\n' +
+  'PACKAGE=' + PACKAGE + '\r\n' +
+  'ACTIVITY=' + ACTIVITY + '\r\n' +
+  'TARGET=' + TARGET);
 
-replaceInFile(MANIFEST_PATH, /__ACTIVITY__/, ACTIVITY);
-replaceInFile(MANIFEST_PATH, /__PACKAGE__/, PACKAGE);
-
-/*
-# leave the id for launching
-touch $PROJECT_PATH/package-activity
-echo $PACKAGE/$PACKAGE.$ACTIVITY >  $PROJECT_PATH/package-activity 
-*/
+// run project-specific create process
+fork('cscript.exe ' + PROJECT_PATH + '\\cordova\\create.js');
\ No newline at end of file
diff --git a/bin/templates/project/cordova/create.bat b/bin/templates/project/cordova/create.bat
new file mode 100644
index 00000000..cfde65a5
--- /dev/null
+++ b/bin/templates/project/cordova/create.bat
@@ -0,0 +1,2 @@
+echo "BALLS"
+cscript cordova\create.js
\ No newline at end of file
diff --git a/bin/templates/project/cordova/create.js b/bin/templates/project/cordova/create.js
new file mode 100644
index 00000000..2bf56031
--- /dev/null
+++ b/bin/templates/project/cordova/create.js
@@ -0,0 +1,69 @@
+var shell=WScript.CreateObject("WScript.Shell");
+
+function exec(s, output) {
+    WScript.Echo('Executing ' + s);
+    var o=shell.Exec(s);
+    while (o.Status == 0) {
+        WScript.Sleep(100);
+    }
+    WScript.Echo("Command exited with code " + o.Status);
+}
+function read(filename) {
+    var fso=WScript.CreateObject("Scripting.FileSystemObject");
+    var f=fso.OpenTextFile(filename, 1);
+    var s=f.ReadAll();
+    f.Close();
+    return s;
+}
+function write(filename, contents) {
+    var fso=WScript.CreateObject("Scripting.FileSystemObject");
+    var f=fso.OpenTextFile(filename, 2, true);
+    f.Write(contents);
+    f.Close();
+}
+function replaceInFile(filename, regexp, replacement) {
+    write(filename, read(filename).replace(regexp, replacement));
+}
+
+// working dir
+var PWD = WScript.ScriptFullName.split('\\cordova\\create.js').join('');
+
+var fso=WScript.CreateObject("Scripting.FileSystemObject");
+var f=fso.OpenTextFile(PWD + '\\.cordova\\config', 1);
+while (!f.AtEndOfStream) {
+  var prop = f.ReadLine().split('=');
+  var line = 'var ' + prop[0] + '=' + "'" + prop[1] + "';";
+  eval(line); // hacky shit to load config but whatevs
+}
+
+var PACKAGE_AS_PATH=PACKAGE.replace(/\./g, '\\');
+var ACTIVITY_PATH=PWD+'\\src\\'+PACKAGE_AS_PATH+'\\'+ACTIVITY+'.java';
+var MANIFEST_PATH=PWD+'\\AndroidManifest.xml';
+
+exec('android.bat create project --target ' + TARGET + ' --path ' + PWD + ' --package ' + PACKAGE + ' --activity ' + ACTIVITY);
+
+// copy in activity and other android assets
+exec('cmd /c xcopy ' + PWD + '\\cordova\\templates\\project\\* ' + PWD +' /Y /S');
+
+// copy in cordova.js
+exec('cmd /c copy ' + PWD + '\\.cordova\\android\\cordova-' + VERSION + '.js ' + PWD + '\\assets\\www /Y');
+
+// copy in cordova.jar
+exec('cmd /c copy ' + PWD + '\\.cordova\\android\\cordova-' + VERSION + '.jar ' + PWD + '\\libs /Y');
+
+// copy in res/xml
+exec('cmd /c md ' + PWD + '\\res\\xml');
+exec('cmd /c copy ' + PWD + '\\.cordova\\android\\cordova.xml ' + PWD + '\\res\\xml /Y');
+exec('cmd /c copy ' + PWD + '\\.cordova\\android\\plugins.xml ' + PWD + '\\res\\xml /Y');
+
+// copy in default activity
+exec('cmd /c copy ' + PWD + '\\cordova\\templates\\Activity.java ' + ACTIVITY_PATH + ' /Y');
+
+// interpolate the activity name and package
+replaceInFile(ACTIVITY_PATH, /__ACTIVITY__/, ACTIVITY);
+replaceInFile(ACTIVITY_PATH, /__ID__/, PACKAGE);
+
+replaceInFile(MANIFEST_PATH, /__ACTIVITY__/, ACTIVITY);
+replaceInFile(MANIFEST_PATH, /__PACKAGE__/, PACKAGE);
+
+WScript.Echo('Create completed successfully.');
\ No newline at end of file
diff --git a/bin/templates/project/cordova/debug.bat b/bin/templates/project/cordova/debug.bat
new file mode 100644
index 00000000..e69de29b
diff --git a/bin/templates/project/cordova/emulate.bat b/bin/templates/project/cordova/emulate.bat
new file mode 100644
index 00000000..e69de29b
diff --git a/bin/templates/project/cordova/log.bat b/bin/templates/project/cordova/log.bat
new file mode 100644
index 00000000..e69de29b
diff --git a/bin/templates/project/cordova/templates/project/assets/www/main.js b/bin/templates/project/cordova/templates/project/assets/www/main.js
index 739a02e3..3a8b04a1 100644
--- a/bin/templates/project/cordova/templates/project/assets/www/main.js
+++ b/bin/templates/project/cordova/templates/project/assets/www/main.js
@@ -88,7 +88,7 @@ function dump_pic(data) {
     viewport.style.position = "absolute";
     viewport.style.top = "10px";
     viewport.style.left = "10px";
-    document.getElementById("test_img").src = "data:image/jpeg;base64," + data;
+    document.getElementById("test_img").src = data;
 }
 
 function fail(msg) {
diff --git a/bin/templates/project/cordova/templates/project/res/drawable-hdpi/icon.png b/bin/templates/project/cordova/templates/project/res/drawable-hdpi/icon.png
new file mode 100644
index 00000000..4d276344
Binary files /dev/null and b/bin/templates/project/cordova/templates/project/res/drawable-hdpi/icon.png differ
diff --git a/bin/templates/project/cordova/templates/project/res/drawable-ldpi/icon.png b/bin/templates/project/cordova/templates/project/res/drawable-ldpi/icon.png
new file mode 100644
index 00000000..cd5032a4
Binary files /dev/null and b/bin/templates/project/cordova/templates/project/res/drawable-ldpi/icon.png differ
diff --git a/bin/templates/project/cordova/templates/project/res/drawable-mdpi/icon.png b/bin/templates/project/cordova/templates/project/res/drawable-mdpi/icon.png
new file mode 100644
index 00000000..e79c6062
Binary files /dev/null and b/bin/templates/project/cordova/templates/project/res/drawable-mdpi/icon.png differ
diff --git a/bin/templates/project/cordova/templates/project/res/drawable-xhdpi/icon.png b/bin/templates/project/cordova/templates/project/res/drawable-xhdpi/icon.png
new file mode 100644
index 00000000..ec7ffbfb
Binary files /dev/null and b/bin/templates/project/cordova/templates/project/res/drawable-xhdpi/icon.png differ
diff --git a/bin/templates/project/cordova/templates/project/res/drawable/icon.png b/bin/templates/project/cordova/templates/project/res/drawable/icon.png
old mode 100755
new mode 100644
index 697df7f3..ec7ffbfb
Binary files a/bin/templates/project/cordova/templates/project/res/drawable/icon.png and b/bin/templates/project/cordova/templates/project/res/drawable/icon.png differ
diff --git a/framework/assets/js/cordova.android.js b/framework/assets/js/cordova.android.js
index 3b5c7d90..dc716fd1 100644
--- a/framework/assets/js/cordova.android.js
+++ b/framework/assets/js/cordova.android.js
@@ -1,6 +1,6 @@
-// commit 55e46cecd73e06a4866f084ffa8513219ef68421
+// commit 68eebbca4a3691fed773d7599dd77c0030beabe6
 
-// File generated at :: Fri May 11 2012 10:34:50 GMT-0700 (PDT)
+// File generated at :: Thu May 24 2012 09:30:21 GMT-0700 (PDT)
 
 /*
  Licensed to the Apache Software Foundation (ASF) under one
@@ -711,6 +711,9 @@ module.exports = {
             children: {
                 exec: {
                     path: 'cordova/exec'
+                },
+                logger: {
+                    path: 'cordova/plugin/logger'
                 }
             }
         },
@@ -1144,13 +1147,14 @@ 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;
-  this.z = z;
-  this.timestamp = timestamp || (new Date()).getTime();
+    this.x = x;
+    this.y = y;
+    this.z = z;
+    this.timestamp = timestamp || (new Date()).getTime();
 };
 
 module.exports = Acceleration;
+
 });
 
 // file: lib/common/plugin/Camera.js
@@ -1169,7 +1173,7 @@ for (var key in Camera) {
  * Gets a picture from source defined by "options.sourceType", and returns the
  * image as defined by the "options.destinationType" option.
 
- * The defaults are sourceType=CAMERA and destinationType=FILE_URL.
+ * The defaults are sourceType=CAMERA and destinationType=FILE_URI.
  *
  * @param {Function} successCallback
  * @param {Function} errorCallback
@@ -2115,7 +2119,7 @@ Entry.prototype.toURL = function() {
 Entry.prototype.toURI = function(mimeType) {
     console.log("DEPRECATED: Update your code to use 'toURL'");
     // fullPath attribute contains the full URI
-    return this.fullPath;
+    return this.toURL();
 };
 
 /**
@@ -3369,11 +3373,60 @@ define("cordova/plugin/accelerometer", function(require, exports, module) {
  * @constructor
  */
 var utils = require("cordova/utils"),
-    exec = require("cordova/exec");
+    exec = require("cordova/exec"),
+    Acceleration = require('cordova/plugin/Acceleration');
 
-// Local singleton variables.
+// Is the accel sensor running?
+var running = false;
+
+// Keeps reference to watchAcceleration calls.
 var timers = {};
 
+// Array of listeners; used to keep track of when we should call start and stop.
+var listeners = [];
+
+// Last returned acceleration object from native
+var accel = null;
+
+// Tells native to start.
+function start() {
+    exec(function(a) {
+        var tempListeners = listeners.slice(0);
+        accel = new Acceleration(a.x, a.y, a.z, a.timestamp);
+        for (var i = 0, l = tempListeners.length; i < l; i++) {
+            tempListeners[i].win(accel);
+        }
+    }, function(e) {
+        var tempListeners = listeners.slice(0);
+        for (var i = 0, l = tempListeners.length; i < l; i++) {
+            tempListeners[i].fail(e);
+        }
+    }, "Accelerometer", "start", []);
+    running = true;
+}
+
+// Tells native to stop.
+function stop() {
+    exec(null, null, "Accelerometer", "stop", []);
+    running = false;
+}
+
+// Adds a callback pair to the listeners array
+function createCallbackPair(win, fail) {
+    return {win:win, fail:fail};
+}
+
+// Removes a win/fail listener pair from the listeners array
+function removeListeners(l) {
+    var idx = listeners.indexOf(l);
+    if (idx > -1) {
+        listeners.splice(idx, 1);
+        if (listeners.length === 0) {
+            stop();
+        }
+    }
+}
+
 var accelerometer = {
     /**
      * Asynchronously aquires the current acceleration.
@@ -3383,21 +3436,27 @@ var accelerometer = {
      * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL)
      */
     getCurrentAcceleration: function(successCallback, errorCallback, options) {
-
         // successCallback required
         if (typeof successCallback !== "function") {
-            console.log("Accelerometer Error: successCallback is not a function");
-            return;
+            throw "getCurrentAcceleration must be called with at least a success callback function as first parameter.";
         }
 
-        // errorCallback optional
-        if (errorCallback && (typeof errorCallback !== "function")) {
-            console.log("Accelerometer Error: errorCallback is not a function");
-            return;
-        }
+        var p;
+        var win = function(a) {
+            successCallback(a);
+            removeListeners(p);
+        };
+        var fail = function(e) {
+            errorCallback(e);
+            removeListeners(p);
+        };
 
-        // Get acceleration
-        exec(successCallback, errorCallback, "Accelerometer", "getAcceleration", []);
+        p = createCallbackPair(win, fail);
+        listeners.push(p);
+
+        if (!running) {
+            start();
+        }
     },
 
     /**
@@ -3409,36 +3468,38 @@ var accelerometer = {
      * @return String                       The watch id that must be passed to #clearWatch to stop watching.
      */
     watchAcceleration: function(successCallback, errorCallback, options) {
-
         // Default interval (10 sec)
-        var frequency = (options !== undefined && options.frequency !== undefined)? options.frequency : 10000;
+        var frequency = (options && options.frequency && typeof options.frequency == 'number') ? options.frequency : 10000;
 
         // successCallback required
         if (typeof successCallback !== "function") {
-            console.log("Accelerometer Error: successCallback is not a function");
-            return;
+            throw "watchAcceleration must be called with at least a success callback function as first parameter.";
         }
 
-        // errorCallback optional
-        if (errorCallback && (typeof errorCallback !== "function")) {
-            console.log("Accelerometer Error: errorCallback is not a function");
-            return;
-        }
-
-        // Make sure accelerometer timeout > frequency + 10 sec
-        exec(
-            function(timeout) {
-                if (timeout < (frequency + 10000)) {
-                    exec(null, null, "Accelerometer", "setTimeout", [frequency + 10000]);
-                }
-            },
-            function(e) { }, "Accelerometer", "getTimeout", []);
-
-        // Start watch timer
+        // Keep reference to watch id, and report accel readings as often as defined in frequency
         var id = utils.createUUID();
-        timers[id] = window.setInterval(function() {
-            exec(successCallback, errorCallback, "Accelerometer", "getAcceleration", []);
-        }, (frequency ? frequency : 1));
+
+        var p = createCallbackPair(function(){}, function(e) {
+            errorCallback(e);
+            removeListeners(p);
+        });
+        listeners.push(p);
+
+        timers[id] = {
+            timer:window.setInterval(function() {
+                if (accel) {
+                    successCallback(accel);
+                }
+            }, frequency),
+            listeners:p
+        };
+
+        if (running) {
+            // If we're already running then immediately invoke the success callback
+            successCallback(accel);
+        } else {
+            start();
+        }
 
         return id;
     },
@@ -3449,16 +3510,17 @@ var accelerometer = {
      * @param {String} id       The id of the watch returned from #watchAcceleration.
      */
     clearWatch: function(id) {
-
         // Stop javascript timer & remove from timer list
-        if (id && timers[id] !== undefined) {
-            window.clearInterval(timers[id]);
+        if (id && timers[id]) {
+            window.clearInterval(timers[id].timer);
+            removeListeners(timers[id].listeners);
             delete timers[id];
         }
     }
 };
 
 module.exports = accelerometer;
+
 });
 
 // file: lib/android/plugin/android/app.js
@@ -4471,6 +4533,177 @@ 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");
+var utils  = require("cordova/utils");
+
+//------------------------------------------------------------------------------
+// object that we're exporting
+//------------------------------------------------------------------------------
+var console = module.exports;
+
+//------------------------------------------------------------------------------
+// copy of the original console object
+//------------------------------------------------------------------------------
+var WinConsole = window.console;
+
+//------------------------------------------------------------------------------
+// whether to use the logger
+//------------------------------------------------------------------------------
+var UseLogger = false;
+
+//------------------------------------------------------------------------------
+// Timers
+//------------------------------------------------------------------------------
+var Timers = {};
+
+//------------------------------------------------------------------------------
+// used for unimplemented methods
+//------------------------------------------------------------------------------
+function noop() {}
+
+//------------------------------------------------------------------------------
+// used for unimplemented methods
+//------------------------------------------------------------------------------
+console.useLogger = function (value) {
+    if (arguments.length) UseLogger = !!value;
+
+    if (UseLogger) {
+        if (logger.useConsole()) {
+            throw new Error("console and logger are too intertwingly");
+        }
+    }
+
+    return UseLogger;
+};
+
+//------------------------------------------------------------------------------
+console.log = function() {
+    if (logger.useConsole()) return;
+    logger.log.apply(logger, [].slice.call(arguments));
+};
+
+//------------------------------------------------------------------------------
+console.error = function() {
+    if (logger.useConsole()) return;
+    logger.error.apply(logger, [].slice.call(arguments));
+};
+
+//------------------------------------------------------------------------------
+console.warn = function() {
+    if (logger.useConsole()) return;
+    logger.warn.apply(logger, [].slice.call(arguments));
+};
+
+//------------------------------------------------------------------------------
+console.info = function() {
+    if (logger.useConsole()) return;
+    logger.info.apply(logger, [].slice.call(arguments));
+};
+
+//------------------------------------------------------------------------------
+console.debug = function() {
+    if (logger.useConsole()) return;
+    logger.debug.apply(logger, [].slice.call(arguments));
+};
+
+//------------------------------------------------------------------------------
+console.assert = function(expression) {
+    if (expression) return;
+
+    var message = utils.vformat(arguments[1], [].slice.call(arguments, 2));
+    console.log("ASSERT: " + message);
+};
+
+//------------------------------------------------------------------------------
+console.clear = function() {};
+
+//------------------------------------------------------------------------------
+console.dir = function(object) {
+    console.log("%o", object);
+};
+
+//------------------------------------------------------------------------------
+console.dirxml = function(node) {
+    console.log(node.innerHTML);
+};
+
+//------------------------------------------------------------------------------
+console.trace = noop;
+
+//------------------------------------------------------------------------------
+console.group = console.log;
+
+//------------------------------------------------------------------------------
+console.groupCollapsed = console.log;
+
+//------------------------------------------------------------------------------
+console.groupEnd = noop;
+
+//------------------------------------------------------------------------------
+console.time = function(name) {
+    Timers[name] = new Date().valueOf();
+};
+
+//------------------------------------------------------------------------------
+console.timeEnd = function(name) {
+    var timeStart = Timers[name];
+    if (!timeStart) {
+        console.warn("unknown timer: " + name);
+        return;
+    }
+
+    var timeElapsed = new Date().valueOf() - timeStart;
+    console.log(name + ": " + timeElapsed + "ms");
+};
+
+//------------------------------------------------------------------------------
+console.timeStamp = noop;
+
+//------------------------------------------------------------------------------
+console.profile = noop;
+
+//------------------------------------------------------------------------------
+console.profileEnd = noop;
+
+//------------------------------------------------------------------------------
+console.count = noop;
+
+//------------------------------------------------------------------------------
+console.exception = console.log;
+
+//------------------------------------------------------------------------------
+console.table = function(data, columns) {
+    console.log("%o", data);
+};
+
+//------------------------------------------------------------------------------
+// return a new function that calls both functions passed as args
+//------------------------------------------------------------------------------
+function wrapperedOrigCall(orgFunc, newFunc) {
+    return function() {
+        var args = [].slice.call(arguments);
+        try { orgFunc.apply(WinConsole, args); } catch (e) {}
+        try { newFunc.apply(console,    args); } catch (e) {}
+    };
+}
+
+//------------------------------------------------------------------------------
+// For every function that exists in the original console object, that
+// also exists in the new console object, wrap the new console method
+// with one that calls both
+//------------------------------------------------------------------------------
+for (var key in console) {
+    if (typeof WinConsole[key] == "function") {
+        console[key] = wrapperedOrigCall(WinConsole[key], console[key]);
+    }
+}
+
+});
+
 // file: lib/common/plugin/contacts.js
 define("cordova/plugin/contacts", function(require, exports, module) {
 var exec = require('cordova/exec'),
@@ -4732,6 +4965,233 @@ 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:
+//
+// LOG                          - constant for the level LOG
+// ERROR                        - constant for the level ERROR
+// WARN                         - constant for the level WARN
+// INFO                         - constant for the level INFO
+// DEBUG                        - constant for the level DEBUG
+// logLevel()                   - returns current log level
+// logLevel(value)              - sets and returns a new log level
+// useConsole()                 - returns whether logger is using console
+// useConsole(value)            - sets and returns whether logger is using console
+// log(message,...)             - logs a message at level LOG
+// error(message,...)           - logs a message at level ERROR
+// warn(message,...)            - logs a message at level WARN
+// info(message,...)            - logs a message at level INFO
+// debug(message,...)           - logs a message at level DEBUG
+// logLevel(level,message,...)  - logs a message specified level
+//
+//------------------------------------------------------------------------------
+
+var logger = exports;
+
+var exec    = require('cordova/exec');
+var utils   = require('cordova/utils');
+
+var UseConsole   = true;
+var Queued       = [];
+var DeviceReady  = false;
+var CurrentLevel;
+
+/**
+ * Logging levels
+ */
+
+var Levels = [
+    "LOG",
+    "ERROR",
+    "WARN",
+    "INFO",
+    "DEBUG"
+];
+
+/*
+ * add the logging levels to the logger object and
+ * to a separate levelsMap object for testing
+ */
+
+var LevelsMap = {};
+for (var i=0; i<Levels.length; i++) {
+    var level = Levels[i];
+    LevelsMap[level] = i;
+    logger[level]    = level;
+}
+
+CurrentLevel = LevelsMap.WARN;
+
+/**
+ * Getter/Setter for the logging level
+ *
+ * Returns the current logging level.
+ *
+ * When a value is passed, sets the logging level to that value.
+ * The values should be one of the following constants:
+ *    logger.LOG
+ *    logger.ERROR
+ *    logger.WARN
+ *    logger.INFO
+ *    logger.DEBUG
+ *
+ * The value used determines which messages get printed.  The logging
+ * values above are in order, and only messages logged at the logging
+ * level or above will actually be displayed to the user.  Eg, the
+ * default level is WARN, so only messages logged with LOG, ERROR, or
+ * WARN will be displayed; INFO and DEBUG messages will be ignored.
+ */
+logger.level = function (value) {
+    if (arguments.length) {
+        if (LevelsMap[value] === null) {
+            throw new Error("invalid logging level: " + value);
+        }
+        CurrentLevel = LevelsMap[value];
+    }
+
+    return Levels[CurrentLevel];
+};
+
+/**
+ * Getter/Setter for the useConsole functionality
+ *
+ * When useConsole is true, the logger will log via the
+ * browser 'console' object.  Otherwise, it will use the
+ * native Logger plugin.
+ */
+logger.useConsole = function (value) {
+    if (arguments.length) UseConsole = !!value;
+
+    if (UseConsole) {
+        if (typeof console == "undefined") {
+            throw new Error("global console object is not defined");
+        }
+
+        if (typeof console.log != "function") {
+            throw new Error("global console object does not have a log function");
+        }
+
+        if (typeof console.useLogger == "function") {
+            if (console.useLogger()) {
+                throw new Error("console and logger are too intertwingly");
+            }
+        }
+    }
+
+    return UseConsole;
+};
+
+/**
+ * Logs a message at the LOG level.
+ *
+ * Parameters passed after message are used applied to
+ * the message with utils.format()
+ */
+logger.log   = function(message) { logWithArgs("LOG",   arguments); };
+
+/**
+ * Logs a message at the ERROR level.
+ *
+ * Parameters passed after message are used applied to
+ * the message with utils.format()
+ */
+logger.error = function(message) { logWithArgs("ERROR", arguments); };
+
+/**
+ * Logs a message at the WARN level.
+ *
+ * Parameters passed after message are used applied to
+ * the message with utils.format()
+ */
+logger.warn  = function(message) { logWithArgs("WARN",  arguments); };
+
+/**
+ * Logs a message at the INFO level.
+ *
+ * Parameters passed after message are used applied to
+ * the message with utils.format()
+ */
+logger.info  = function(message) { logWithArgs("INFO",  arguments); };
+
+/**
+ * Logs a message at the DEBUG level.
+ *
+ * Parameters passed after message are used applied to
+ * the message with utils.format()
+ */
+logger.debug = function(message) { logWithArgs("DEBUG", arguments); };
+
+// log at the specified level with args
+function logWithArgs(level, args) {
+    args = [level].concat([].slice.call(args));
+    logger.logLevel.apply(logger, args);
+}
+
+/**
+ * Logs a message at the specified level.
+ *
+ * Parameters passed after message are used applied to
+ * the message with utils.format()
+ */
+logger.logLevel = function(level, message /* , ... */) {
+    // format the message with the parameters
+    var formatArgs = [].slice.call(arguments, 2);
+    message    = utils.vformat(message, formatArgs);
+
+    if (LevelsMap[level] === null) {
+        throw new Error("invalid logging level: " + level);
+    }
+
+    if (LevelsMap[level] > CurrentLevel) return;
+
+    // queue the message if not yet at deviceready
+    if (!DeviceReady && !UseConsole) {
+        Queued.push([level, message]);
+        return;
+    }
+
+    // if not using the console, use the native logger
+    if (!UseConsole) {
+        exec(null, null, "Logger", "logLevel", [level, message]);
+        return;
+    }
+
+    // make sure console is not using logger
+    if (console.__usingCordovaLogger) {
+        throw new Error("console and logger are too intertwingly");
+    }
+
+    // log to the console
+    switch (level) {
+        case logger.LOG:   console.log(message); break;
+        case logger.ERROR: console.log("ERROR: " + message); break;
+        case logger.WARN:  console.log("WARN: "  + message); break;
+        case logger.INFO:  console.log("INFO: "  + message); break;
+        case logger.DEBUG: console.log("DEBUG: " + message); break;
+    }
+};
+
+// when deviceready fires, log queued messages
+logger.__onDeviceReady = function() {
+    if (DeviceReady) return;
+
+    DeviceReady = true;
+
+    for (var i=0; i<Queued.length; i++) {
+        var messageArgs = Queued[i];
+        logger.logLevel(messageArgs[0], messageArgs[1]);
+    }
+
+    Queued = null;
+};
+
+// add a deviceready event to log queued messages
+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'),
@@ -5061,6 +5521,16 @@ utils.alert = function(msg) {
 /**
  * Formats a string and arguments following it ala sprintf()
  *
+ * see utils.vformat() for more information
+ */
+utils.format = function(formatString /* ,... */) {
+    var args = [].slice.call(arguments, 1);
+    return utils.vformat(formatString, args);
+};
+
+/**
+ * Formats a string and arguments following it ala vsprintf()
+ *
  * format chars:
  *   %j - format arg as JSON
  *   %o - format arg as JSON
@@ -5072,14 +5542,13 @@ utils.alert = function(msg) {
  * for rationale, see FireBug's Console API:
  *    http://getfirebug.com/wiki/index.php/Console_API
  */
-utils.format = function(formatString /* ,... */) {
+utils.vformat = function(formatString, args) {
     if (formatString === null || formatString === undefined) return "";
     if (arguments.length == 1) return formatString.toString();
 
     var pattern = /(.*?)%(.)(.*)/;
     var rest    = formatString.toString();
     var result  = [];
-    var args    = [].slice.call(arguments,1);
 
     while (args.length) {
         var arg   = args.shift();
diff --git a/framework/src/org/apache/cordova/AccelListener.java b/framework/src/org/apache/cordova/AccelListener.java
index dbda3fe6..1574f081 100755
--- a/framework/src/org/apache/cordova/AccelListener.java
+++ b/framework/src/org/apache/cordova/AccelListener.java
@@ -18,7 +18,11 @@
 */
 package org.apache.cordova;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.cordova.api.CordovaInterface;
 import org.apache.cordova.api.Plugin;
@@ -31,6 +35,8 @@ import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.location.Location;
+import android.util.Log;
 import android.content.Context;
 
 /**
@@ -43,16 +49,16 @@ public class AccelListener extends Plugin implements SensorEventListener {
     public static int STARTING = 1;
     public static int RUNNING = 2;
     public static int ERROR_FAILED_TO_START = 3;
+    
+    private float x,y,z;                                // most recent acceleration values
+    private long timestamp;                         // time of most recent value
+    private int status;                                 // status of listener
+    private int accuracy = SensorManager.SENSOR_STATUS_UNRELIABLE;
 
-    public float TIMEOUT = 30000;		// Timeout in msec to shut off listener
+    private SensorManager sensorManager;    // Sensor manager
+    private Sensor mSensor;                           // Acceleration sensor returned by sensor manager
 
-    float x, y, z;						// most recent acceleration values
-    long timestamp;						// time of most recent value
-    int status;							// status of listener
-    long lastAccessTime;				// time the value was last retrieved
-
-    private SensorManager sensorManager;// Sensor manager
-    Sensor mSensor;						// Acceleration sensor returned by sensor manager
+    private String callbackId;              // Keeps track of the single "start" callback ID passed in from JS
 
     /**
      * Create an accelerometer listener.
@@ -63,8 +69,8 @@ public class AccelListener extends Plugin implements SensorEventListener {
         this.z = 0;
         this.timestamp = 0;
         this.setStatus(AccelListener.STOPPED);
-    }
-
+     }
+    
     /**
      * Sets the context of the Command. This can then be used to do things like
      * get file paths associated with the Activity.
@@ -80,106 +86,36 @@ public class AccelListener extends Plugin implements SensorEventListener {
     /**
      * Executes the request and returns PluginResult.
      * 
-     * @param action 		The action to execute.
-     * @param args 			JSONArry of arguments for the plugin.
-     * @param callbackId	The callback id used when calling back into JavaScript.
-     * @return 				A PluginResult object with a status and message.
+     * @param action        The action to execute.
+     * @param args          JSONArry of arguments for the plugin.
+     * @param callbackId    The callback id used when calling back into JavaScript.
+     * @return              A PluginResult object with a status and message.
      */
     public PluginResult execute(String action, JSONArray args, String callbackId) {
-        PluginResult.Status status = PluginResult.Status.OK;
-        String result = "";
+        PluginResult.Status status = PluginResult.Status.NO_RESULT;
+        String message = "";
+        PluginResult result = new PluginResult(status, message);
+        result.setKeepCallback(true);   
 
-        try {
-            if (action.equals("getStatus")) {
-                int i = this.getStatus();
-                return new PluginResult(status, i);
-            }
-            else if (action.equals("start")) {
-                int i = this.start();
-                return new PluginResult(status, i);
-            }
-            else if (action.equals("stop")) {
-                this.stop();
-                return new PluginResult(status, 0);
-            }
-            else if (action.equals("getAcceleration")) {
+        if (action.equals("start")) {
+            this.callbackId = callbackId;
+            if (this.status != AccelListener.RUNNING) {
                 // If not running, then this is an async call, so don't worry about waiting
-                if (this.status != AccelListener.RUNNING) {
-                    int r = this.start();
-                    if (r == AccelListener.ERROR_FAILED_TO_START) {
-                        return new PluginResult(PluginResult.Status.IO_EXCEPTION, AccelListener.ERROR_FAILED_TO_START);
-                    }
-                    // Wait until running
-                    long timeout = 2000;
-                    while ((this.status == STARTING) && (timeout > 0)) {
-                        timeout = timeout - 100;
-                        try {
-                            Thread.sleep(100);
-                        } catch (InterruptedException e) {
-                            e.printStackTrace();
-                        }
-                    }
-                    if (timeout == 0) {
-                        return new PluginResult(PluginResult.Status.IO_EXCEPTION, AccelListener.ERROR_FAILED_TO_START);
-                    }
-                }
-                this.lastAccessTime = System.currentTimeMillis();
-                JSONObject r = new JSONObject();
-                r.put("x", this.x);
-                r.put("y", this.y);
-                r.put("z", this.z);
-                // TODO: Should timestamp be sent?
-                r.put("timestamp", this.timestamp);
-                return new PluginResult(status, r);
+                // We drop the callback onto our stack, call start, and let start and the sensor callback fire off the callback down the road
+                this.start();
             }
-            else if (action.equals("setTimeout")) {
-                try {
-                    float timeout = Float.parseFloat(args.getString(0));
-                    this.setTimeout(timeout);
-                    return new PluginResult(status, 0);
-                } catch (NumberFormatException e) {
-                    status = PluginResult.Status.INVALID_ACTION;
-                    e.printStackTrace();
-                } catch (JSONException e) {
-                    status = PluginResult.Status.JSON_EXCEPTION;
-                    e.printStackTrace();
-                }
-            }
-            else if (action.equals("getTimeout")) {
-                float f = this.getTimeout();
-                return new PluginResult(status, f);
-            } else {
-                // Unsupported action
-                return new PluginResult(PluginResult.Status.INVALID_ACTION);
-            }
-            return new PluginResult(status, result);
-        } catch (JSONException e) {
-            return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
         }
-    }
-
-    /**
-     * Identifies if action to be executed returns a value and should be run synchronously.
-     * 
-     * @param action	The action to execute
-     * @return			T=returns value
-     */
-    public boolean isSynch(String action) {
-        if (action.equals("getStatus")) {
-            return true;
-        }
-        else if (action.equals("getAcceleration")) {
-            // Can only return value if RUNNING
+        else if (action.equals("stop")) {
             if (this.status == AccelListener.RUNNING) {
-                return true;
+                this.stop();
             }
+        } else {
+          // Unsupported action
+            return new PluginResult(PluginResult.Status.INVALID_ACTION);
         }
-        else if (action.equals("getTimeout")) {
-            return true;
-        }
-        return false;
+        return result;
     }
-
+    
     /**
      * Called by AccelBroker when listener is to be shut down.
      * Stop listener.
@@ -191,46 +127,60 @@ public class AccelListener extends Plugin implements SensorEventListener {
     //--------------------------------------------------------------------------
     // LOCAL METHODS
     //--------------------------------------------------------------------------
-
+    //
     /**
      * Start listening for acceleration sensor.
      * 
-     * @return 			status of listener
+     * @return          status of listener
      */
-    public int start() {
-
+    private int start() {
         // If already starting or running, then just return
         if ((this.status == AccelListener.RUNNING) || (this.status == AccelListener.STARTING)) {
-            return this.status;
+          return this.status;
         }
-
+        
+        this.setStatus(AccelListener.STARTING);
+        
         // Get accelerometer from sensor manager
         List<Sensor> list = this.sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
 
         // If found, then register as listener
         if ((list != null) && (list.size() > 0)) {
-            this.mSensor = list.get(0);
-            this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_FASTEST);
-            this.setStatus(AccelListener.STARTING);
-            this.lastAccessTime = System.currentTimeMillis();
+          this.mSensor = list.get(0);
+          this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_UI);
+          this.setStatus(AccelListener.STARTING);
+        } else {
+          this.setStatus(AccelListener.ERROR_FAILED_TO_START);
+          this.fail(AccelListener.ERROR_FAILED_TO_START, "No sensors found to register accelerometer listening to.");
+          return this.status;
         }
-
-        // If error, then set status to error
-        else {
-            this.setStatus(AccelListener.ERROR_FAILED_TO_START);
+        
+        // Wait until running
+        long timeout = 2000;
+        while ((this.status == STARTING) && (timeout > 0)) {
+          timeout = timeout - 100;
+          try {
+              Thread.sleep(100);
+          } catch (InterruptedException e) {
+              e.printStackTrace();
+          }
+        }
+        if (timeout == 0) {
+          this.setStatus(AccelListener.ERROR_FAILED_TO_START);
+          this.fail(AccelListener.ERROR_FAILED_TO_START, "Accelerometer could not be started.");
         }
-
         return this.status;
     }
 
     /**
      * Stop listening to acceleration sensor.
      */
-    public void stop() {
+    private void stop() {
         if (this.status != AccelListener.STOPPED) {
             this.sensorManager.unregisterListener(this);
         }
         this.setStatus(AccelListener.STOPPED);
+        this.accuracy = SensorManager.SENSOR_STATUS_UNRELIABLE;
     }
 
     /**
@@ -240,6 +190,16 @@ public class AccelListener extends Plugin implements SensorEventListener {
      * @param accuracy
      */
     public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        // Only look at accelerometer events
+        if (sensor.getType() != Sensor.TYPE_ACCELEROMETER) {
+            return;
+        }
+        
+        // If not running, then just return
+        if (this.status == AccelListener.STOPPED) {
+            return;
+        }
+        this.accuracy = accuracy;
     }
 
     /**
@@ -248,7 +208,6 @@ public class AccelListener extends Plugin implements SensorEventListener {
      * @param SensorEvent event
      */
     public void onSensorChanged(SensorEvent event) {
-
         // Only look at accelerometer events
         if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) {
             return;
@@ -258,54 +217,58 @@ public class AccelListener extends Plugin implements SensorEventListener {
         if (this.status == AccelListener.STOPPED) {
             return;
         }
-
-        // Save time that event was received
-        this.timestamp = System.currentTimeMillis();
-        this.x = event.values[0];
-        this.y = event.values[1];
-        this.z = event.values[2];
-
         this.setStatus(AccelListener.RUNNING);
+        
+        if (this.accuracy >= SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM) {
 
-        // If values haven't been read for TIMEOUT time, then turn off accelerometer sensor to save power
-        if ((this.timestamp - this.lastAccessTime) > this.TIMEOUT) {
-            this.stop();
+            // Save time that event was received
+            this.timestamp = System.currentTimeMillis();
+            this.x = event.values[0];
+            this.y = event.values[1];
+            this.z = event.values[2];
+
+            this.win();
         }
     }
 
-    /**
-     * Get status of accelerometer sensor.
-     * 
-     * @return			status
-     */
-    public int getStatus() {
-        return this.status;
+    // Sends an error back to JS
+    private void fail(int code, String message) {
+        // Error object
+        JSONObject errorObj = new JSONObject();
+        try {
+            errorObj.put("code", code);
+            errorObj.put("message", message);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        PluginResult err = new PluginResult(PluginResult.Status.ERROR, errorObj);
+        err.setKeepCallback(true);
+
+        this.error(err, this.callbackId);
+    }
+    
+    private void win() {
+        // Success return object
+        PluginResult result = new PluginResult(PluginResult.Status.OK, this.getAccelerationJSON());
+        result.setKeepCallback(true);
+
+        this.success(result, this.callbackId);
     }
 
-    /**
-     * Set the timeout to turn off accelerometer sensor if getX() hasn't been called.
-     * 
-     * @param timeout		Timeout in msec.
-     */
-    public void setTimeout(float timeout) {
-        this.TIMEOUT = timeout;
-    }
-
-    /**
-     * Get the timeout to turn off accelerometer sensor if getX() hasn't been called.
-     * 
-     * @return timeout in msec
-     */
-    public float getTimeout() {
-        return this.TIMEOUT;
-    }
-
-    /**
-     * Set the status and send it to JavaScript.
-     * @param status
-     */
     private void setStatus(int status) {
         this.status = status;
     }
-
+    
+    private JSONObject getAccelerationJSON() {
+        JSONObject r = new JSONObject();
+        try {
+            r.put("x", this.x);
+            r.put("y", this.y);
+            r.put("z", this.z);
+            r.put("timestamp", this.timestamp);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return r;
+    }
 }
diff --git a/framework/src/org/apache/cordova/FileTransfer.java b/framework/src/org/apache/cordova/FileTransfer.java
index e006230f..a040f47d 100644
--- a/framework/src/org/apache/cordova/FileTransfer.java
+++ b/framework/src/org/apache/cordova/FileTransfer.java
@@ -27,6 +27,7 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
 import java.net.URL;
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
@@ -35,7 +36,6 @@ import java.util.Iterator;
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.TrustManager;
@@ -56,7 +56,7 @@ public class FileTransfer extends Plugin {
     private static final String LOG_TAG = "FileTransfer";
     private static final String LINE_START = "--";
     private static final String LINE_END = "\r\n";
-    private static final String BOUNDRY = "*****";
+    private static final String BOUNDARY =  "*****";
 
     public static int FILE_NOT_FOUND_ERR = 1;
     public static int INVALID_URL_ERR = 2;
@@ -80,49 +80,251 @@ public class FileTransfer extends Plugin {
             return new PluginResult(PluginResult.Status.JSON_EXCEPTION, "Missing source or target");
         }
 
-        try {
-            if (action.equals("upload")) {
-                // Setup the options
-                String fileKey = null;
-                String fileName = null;
-                String mimeType = null;
+        if (action.equals("upload")) {
+            return upload(source, target, args);
+        } else if (action.equals("download")) {
+            return download(source, target);
+        } else {
+            return new PluginResult(PluginResult.Status.INVALID_ACTION);
+        }
+    }
 
-                fileKey = getArgument(args, 2, "file");
-                fileName = getArgument(args, 3, "image.jpg");
-                mimeType = getArgument(args, 4, "image/jpeg");
-                JSONObject params = args.optJSONObject(5);
-                boolean trustEveryone = args.optBoolean(6);
-                boolean chunkedMode = args.optBoolean(7) || args.isNull(7); //Always use chunked mode unless set to false as per API
-                FileUploadResult r = upload(source, target, fileKey, fileName, mimeType, params, trustEveryone, chunkedMode);
-                Log.d(LOG_TAG, "****** About to return a result from upload");
-                return new PluginResult(PluginResult.Status.OK, r.toJSONObject());
-            } else if (action.equals("download")) {
-                JSONObject r = download(source, target);
-                Log.d(LOG_TAG, "****** About to return a result from download");
-                return new PluginResult(PluginResult.Status.OK, r);
-            } else {
-                return new PluginResult(PluginResult.Status.INVALID_ACTION);
+    /**
+     * Uploads the specified file to the server URL provided using an HTTP multipart request.
+     * @param source        Full path of the file on the file system
+     * @param target        URL of the server to receive the file
+     * @param args          JSON Array of args
+     *
+     * args[2] fileKey       Name of file request parameter
+     * args[3] fileName      File name to be used on server
+     * args[4] mimeType      Describes file content type
+     * args[5] params        key:value pairs of user-defined parameters
+     * @return FileUploadResult containing result of upload request
+     */
+    private PluginResult upload(String source, String target, JSONArray args) {
+        Log.d(LOG_TAG, "upload " + source + " to " +  target);
+
+        HttpURLConnection conn = null;
+        try {
+            // Setup the options
+            String fileKey = getArgument(args, 2, "file");
+            String fileName = getArgument(args, 3, "image.jpg");
+            String mimeType = getArgument(args, 4, "image/jpeg");
+            JSONObject params = args.optJSONObject(5);
+            if (params == null) params = new JSONObject();
+            boolean trustEveryone = args.optBoolean(6);
+            boolean chunkedMode = args.optBoolean(7) || args.isNull(7); //Always use chunked mode unless set to false as per API
+
+            Log.d(LOG_TAG, "fileKey: " + fileKey);
+            Log.d(LOG_TAG, "fileName: " + fileName);
+            Log.d(LOG_TAG, "mimeType: " + mimeType);
+            Log.d(LOG_TAG, "params: " + params);
+            Log.d(LOG_TAG, "trustEveryone: " + trustEveryone);
+            Log.d(LOG_TAG, "chunkedMode: " + chunkedMode);
+
+            // Create return object
+            FileUploadResult result = new FileUploadResult();
+
+            // Get a input stream of the file on the phone
+            FileInputStream fileInputStream = (FileInputStream) getPathFromUri(source);
+
+            DataOutputStream dos = null;
+
+            int bytesRead, bytesAvailable, bufferSize;
+            long totalBytes;
+            byte[] buffer;
+            int maxBufferSize = 8096;
+
+            //------------------ CLIENT REQUEST
+            // open a URL connection to the server
+            URL url = new URL(target);
+
+            // Open a HTTP connection to the URL based on protocol
+            if (url.getProtocol().toLowerCase().equals("https")) {
+                // Using standard HTTPS connection. Will not allow self signed certificate
+                if (!trustEveryone) {
+                    conn = (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);
+                    conn = https;
+                }
             }
+            // Return a standard HTTP connection
+            else {
+                conn = (HttpURLConnection) url.openConnection();
+            }
+
+            // Allow Inputs
+            conn.setDoInput(true);
+
+            // Allow Outputs
+            conn.setDoOutput(true);
+
+            // Don't use a cached copy.
+            conn.setUseCaches(false);
+
+            // Use a post method.
+            conn.setRequestMethod("POST");
+            conn.setRequestProperty("Connection", "Keep-Alive");
+            conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDARY);
+
+            // Handle the other headers
+            try {
+              JSONObject headers = params.getJSONObject("headers");
+              for (Iterator iter = headers.keys(); iter.hasNext();)
+              {
+                String headerKey = iter.next().toString();
+                conn.setRequestProperty(headerKey, headers.getString(headerKey));
+              }
+            } catch (JSONException e1) {
+              // No headers to be manipulated!
+            }
+
+            // Set the cookies on the response
+            String cookie = CookieManager.getInstance().getCookie(target);
+            if (cookie != null) {
+                conn.setRequestProperty("Cookie", cookie);
+            }
+
+
+            /*
+                * Store the non-file portions of the multipart data as a string, so that we can add it
+                * to the contentSize, since it is part of the body of the HTTP request.
+                */
+            String extraParams = "";
+            try {
+                for (Iterator iter = params.keys(); iter.hasNext();) {
+                    Object key = iter.next();
+                    if(!String.valueOf(key).equals("headers"))
+                    {
+                      extraParams += LINE_START + BOUNDARY + LINE_END;
+                      extraParams += "Content-Disposition: form-data; name=\"" +  key.toString() + "\";";
+                      extraParams += LINE_END + LINE_END;
+                      extraParams += params.getString(key.toString());
+                      extraParams += LINE_END;
+                    }
+                }
+            } catch (JSONException e) {
+                Log.e(LOG_TAG, e.getMessage(), e);
+            }
+
+            extraParams += LINE_START + BOUNDARY + LINE_END;
+            extraParams += "Content-Disposition: form-data; name=\"" + fileKey + "\";" + " filename=\"";
+
+            String midParams = "\"" + LINE_END + "Content-Type: " + mimeType + LINE_END + LINE_END;
+            String tailParams = LINE_END + LINE_START + BOUNDARY + LINE_START + LINE_END;
+
+            // Should set this up as an option
+            if (chunkedMode) {
+                conn.setChunkedStreamingMode(maxBufferSize);
+            }
+            else
+            {
+              int stringLength = extraParams.length() + midParams.length() + tailParams.length() + fileName.getBytes("UTF-8").length;
+              Log.d(LOG_TAG, "String Length: " + stringLength);
+              int fixedLength = (int) fileInputStream.getChannel().size() + stringLength;
+              Log.d(LOG_TAG, "Content Length: " + fixedLength);
+              conn.setFixedLengthStreamingMode(fixedLength);
+            }
+
+
+            dos = new DataOutputStream( conn.getOutputStream() );
+            dos.writeBytes(extraParams);
+            //We don't want to chagne encoding, we just want this to write for all Unicode.
+            dos.write(fileName.getBytes("UTF-8"));
+            dos.writeBytes(midParams);
+
+            // create a buffer of maximum size
+            bytesAvailable = fileInputStream.available();
+            bufferSize = Math.min(bytesAvailable, maxBufferSize);
+            buffer = new byte[bufferSize];
+
+            // read file and write it into form...
+            bytesRead = fileInputStream.read(buffer, 0, bufferSize);
+            totalBytes = 0;
+
+            while (bytesRead > 0) {
+                totalBytes += bytesRead;
+                result.setBytesSent(totalBytes);
+                dos.write(buffer, 0, bufferSize);
+                bytesAvailable = fileInputStream.available();
+                bufferSize = Math.min(bytesAvailable, maxBufferSize);
+                bytesRead = fileInputStream.read(buffer, 0, bufferSize);
+            }
+
+            // send multipart form data necesssary after file data...
+            dos.writeBytes(tailParams);
+
+            // close streams
+            fileInputStream.close();
+            dos.flush();
+            dos.close();
+
+            //------------------ read the SERVER RESPONSE
+            StringBuffer responseString = new StringBuffer("");
+            DataInputStream inStream;
+            try {
+                inStream = new DataInputStream ( conn.getInputStream() );
+            } catch(FileNotFoundException e) {
+                Log.e(LOG_TAG, e.toString(), e);
+                throw new IOException("Received error from server");
+            }
+
+            String line;
+            while (( line = inStream.readLine()) != null) {
+                responseString.append(line);
+            }
+            Log.d(LOG_TAG, "got response from server");
+            Log.d(LOG_TAG, responseString.toString());
+
+            // send request and retrieve response
+            result.setResponseCode(conn.getResponseCode());
+            result.setResponse(responseString.toString());
+
+            inStream.close();
+
+            // Revert back to the proper verifier and socket factories
+            if (trustEveryone && url.getProtocol().toLowerCase().equals("https")) {
+                ((HttpsURLConnection) conn).setHostnameVerifier(defaultHostnameVerifier);
+                HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
+            }
+
+            Log.d(LOG_TAG, "****** About to return a result from upload");
+            return new PluginResult(PluginResult.Status.OK, result.toJSONObject());
+
         } catch (FileNotFoundException e) {
-            Log.e(LOG_TAG, e.getMessage(), e);
-            JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target);
+            JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, conn);
+            Log.e(LOG_TAG, error.toString(), e);
             return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
-        } catch (IllegalArgumentException e) {
-            Log.e(LOG_TAG, e.getMessage(), e);
-            JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target);
-            return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
-        } catch (SSLException e) {
-            Log.e(LOG_TAG, e.getMessage(), e);
-            Log.d(LOG_TAG, "Got my ssl exception!!!");
-            JSONObject error = createFileTransferError(CONNECTION_ERR, source, target);
+        } catch (MalformedURLException e) {
+            JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, conn);
+            Log.e(LOG_TAG, error.toString(), e);
             return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
         } catch (IOException e) {
-            Log.e(LOG_TAG, e.getMessage(), e);
-            JSONObject error = createFileTransferError(CONNECTION_ERR, source, target);
+            JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn);
+            Log.e(LOG_TAG, error.toString(), e);
             return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
         } catch (JSONException e) {
             Log.e(LOG_TAG, e.getMessage(), e);
             return new PluginResult(PluginResult.Status.JSON_EXCEPTION);
+        } catch (Throwable t) {
+            // Shouldn't happen, but will
+            JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, conn);
+            Log.wtf(LOG_TAG, error.toString(), t);
+            return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+        } finally {
+            if (conn != null) {
+                conn.disconnect();
+            }
         }
     }
 
@@ -170,18 +372,36 @@ public class FileTransfer extends Plugin {
         }
     }
 
-    /**
-     * Create an error object based on the passed in errorCode
-     * @param errorCode 	the error
-     * @return JSONObject containing the error
-     */
-    private JSONObject createFileTransferError(int errorCode, String source, String target) {
+    private JSONObject createFileTransferError(int errorCode, String source, String target, HttpURLConnection connection) {
+
+        Integer httpStatus = null;
+
+        if (connection != null) {
+            try {
+                httpStatus = connection.getResponseCode();
+            } catch (IOException e) {
+                Log.w(LOG_TAG, "Error getting HTTP status code from connection.", e);
+            }
+        }
+
+        return createFileTransferError(errorCode, source, target, httpStatus);
+    }
+
+        /**
+        * Create an error object based on the passed in errorCode
+        * @param errorCode 	the error
+        * @return JSONObject containing the error
+        */
+    private JSONObject createFileTransferError(int errorCode, String source, String target, Integer httpStatus) {
         JSONObject error = null;
         try {
             error = new JSONObject();
             error.put("code", errorCode);
             error.put("source", source);
             error.put("target", target);
+            if (httpStatus != null) {
+                error.put("http_status", httpStatus);
+            }
         } catch (JSONException e) {
             Log.e(LOG_TAG, e.getMessage(), e);
         }
@@ -206,200 +426,6 @@ public class FileTransfer extends Plugin {
         return arg;
     }
 
-    /**
-     * Uploads the specified file to the server URL provided using an HTTP
-     * multipart request.
-     * @param file      Full path of the file on the file system
-     * @param server        URL of the server to receive the file
-     * @param fileKey       Name of file request parameter
-     * @param fileName      File name to be used on server
-     * @param mimeType      Describes file content type
-     * @param params        key:value pairs of user-defined parameters
-     * @return FileUploadResult containing result of upload request
-     */
-    @SuppressWarnings("deprecation")
-    public FileUploadResult upload(String file, String server, final String fileKey, final String fileName,
-            final String mimeType, JSONObject params, boolean trustEveryone, boolean chunkedMode) throws IOException, SSLException {
-        // Create return object
-        FileUploadResult result = new FileUploadResult();
-
-        // Get a input stream of the file on the phone
-        FileInputStream fileInputStream = (FileInputStream) getPathFromUri(file);
-
-        HttpURLConnection conn = null;
-        DataOutputStream dos = null;
-
-        int bytesRead, bytesAvailable, bufferSize;
-        long totalBytes;
-        byte[] buffer;
-        int maxBufferSize = 8096;
-
-        //------------------ CLIENT REQUEST
-        // open a URL connection to the server
-        URL url = new URL(server);
-
-        // Open a HTTP connection to the URL based on protocol
-        if (url.getProtocol().toLowerCase().equals("https")) {
-            // Using standard HTTPS connection. Will not allow self signed certificate
-            if (!trustEveryone) {
-                conn = (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);
-                conn = https;
-            }
-        }
-        // Return a standard HTTP connection
-        else {
-            conn = (HttpURLConnection) url.openConnection();
-        }
-
-        // Allow Inputs
-        conn.setDoInput(true);
-
-        // Allow Outputs
-        conn.setDoOutput(true);
-
-        // Don't use a cached copy.
-        conn.setUseCaches(false);
-
-        // Use a post method.
-        conn.setRequestMethod("POST");
-        conn.setRequestProperty("Connection", "Keep-Alive");
-        conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDRY);
-
-        // Handle the other headers
-        try {
-            JSONObject headers = params.getJSONObject("headers");
-            for (@SuppressWarnings("rawtypes")
-            Iterator iter = headers.keys(); iter.hasNext();)
-            {
-                String headerKey = iter.next().toString();
-                conn.setRequestProperty(headerKey, headers.getString(headerKey));
-            }
-        } catch (JSONException e1) {
-            // No headers to be manipulated!
-        }
-
-        // Set the cookies on the response
-        String cookie = CookieManager.getInstance().getCookie(server);
-        if (cookie != null) {
-            conn.setRequestProperty("Cookie", cookie);
-        }
-
-        /*
-         * Store the non-file portions of the multipart data as a string, so that we can add it 
-         * to the contentSize, since it is part of the body of the HTTP request.
-         */
-        String extraParams = "";
-        try {
-            for (@SuppressWarnings("rawtypes")
-            Iterator iter = params.keys(); iter.hasNext();) {
-                Object key = iter.next();
-                if (key.toString() != "headers")
-                {
-                    extraParams += LINE_START + BOUNDRY + LINE_END;
-                    extraParams += "Content-Disposition: form-data; name=\"" + key.toString() + "\";";
-                    extraParams += LINE_END + LINE_END;
-                    extraParams += params.getString(key.toString());
-                    extraParams += LINE_END;
-                }
-            }
-        } catch (JSONException e) {
-            Log.e(LOG_TAG, e.getMessage(), e);
-        }
-
-        extraParams += LINE_START + BOUNDRY + LINE_END;
-        extraParams += "Content-Disposition: form-data; name=\"" + fileKey + "\";" + " filename=\"";
-
-        String midParams = "\"" + LINE_END + "Content-Type: " + mimeType + LINE_END + LINE_END;
-        String tailParams = LINE_END + LINE_START + BOUNDRY + LINE_START + LINE_END;
-
-        // Should set this up as an option
-        if (chunkedMode) {
-            conn.setChunkedStreamingMode(maxBufferSize);
-        }
-        else
-        {
-            int stringLength = extraParams.length() + midParams.length() + tailParams.length() + fileName.getBytes("UTF-8").length;
-            Log.d(LOG_TAG, "String Length: " + stringLength);
-            int fixedLength = (int) fileInputStream.getChannel().size() + stringLength;
-            Log.d(LOG_TAG, "Content Length: " + fixedLength);
-            conn.setFixedLengthStreamingMode(fixedLength);
-        }
-
-        dos = new DataOutputStream(conn.getOutputStream());
-        dos.writeBytes(extraParams);
-        //We don't want to chagne encoding, we just want this to write for all Unicode.
-        dos.write(fileName.getBytes("UTF-8"));
-        dos.writeBytes(midParams);
-
-        // create a buffer of maximum size
-        bytesAvailable = fileInputStream.available();
-        bufferSize = Math.min(bytesAvailable, maxBufferSize);
-        buffer = new byte[bufferSize];
-
-        // read file and write it into form...
-        bytesRead = fileInputStream.read(buffer, 0, bufferSize);
-        totalBytes = 0;
-
-        while (bytesRead > 0) {
-            totalBytes += bytesRead;
-            result.setBytesSent(totalBytes);
-            dos.write(buffer, 0, bufferSize);
-            bytesAvailable = fileInputStream.available();
-            bufferSize = Math.min(bytesAvailable, maxBufferSize);
-            bytesRead = fileInputStream.read(buffer, 0, bufferSize);
-        }
-
-        // send multipart form data necesssary after file data...
-        dos.writeBytes(tailParams);
-
-        // close streams
-        fileInputStream.close();
-        dos.flush();
-        dos.close();
-
-        //------------------ read the SERVER RESPONSE
-        StringBuffer responseString = new StringBuffer("");
-        DataInputStream inStream;
-        try {
-            inStream = new DataInputStream(conn.getInputStream());
-        } catch (FileNotFoundException e) {
-            throw new IOException("Received error from server");
-        }
-
-        String line;
-        while ((line = inStream.readLine()) != null) {
-            responseString.append(line);
-        }
-        Log.d(LOG_TAG, "got response from server");
-        Log.d(LOG_TAG, responseString.toString());
-
-        // send request and retrieve response
-        result.setResponseCode(conn.getResponseCode());
-        result.setResponse(responseString.toString());
-
-        inStream.close();
-        conn.disconnect();
-
-        // Revert back to the proper verifier and socket factories
-        if (trustEveryone && url.getProtocol().toLowerCase().equals("https")) {
-            ((HttpsURLConnection) conn).setHostnameVerifier(defaultHostnameVerifier);
-            HttpsURLConnection.setDefaultSSLSocketFactory(defaultSSLSocketFactory);
-        }
-
-        return result;
-    }
-
     /**
      * Downloads a file form a given URL and saves it to the specified directory.
      *
@@ -407,7 +433,10 @@ public class FileTransfer extends Plugin {
      * @param target      	Full path of the file on the file system
      * @return JSONObject 	the downloaded file
      */
-    public JSONObject download(String source, String target) throws IOException {
+    private PluginResult download(String source, String target) {
+        Log.d(LOG_TAG, "download " + source + " to " +  target);
+
+        HttpURLConnection connection = null;
         try {
             File file = getFileFromPath(target);
 
@@ -417,16 +446,20 @@ public class FileTransfer extends Plugin {
             // connect to server
             if (webView.isUrlWhiteListed(source))
             {
-                URL url = new URL(source);
-                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
-                connection.setRequestMethod("GET");
+              URL url = new URL(source);
+              connection = (HttpURLConnection) url.openConnection();
+              connection.setRequestMethod("GET");
+              
+              //Add cookie support
+              String cookie = CookieManager.getInstance().getCookie(source);
+              if(cookie != null)
+              {
+                connection.setRequestProperty("cookie", cookie);
+              }
+              
+              connection.connect();
 
-                //Add cookie support
-                String cookie = CookieManager.getInstance().getCookie(source);
-                if (cookie != null)
-                {
-                    connection.setRequestProperty("cookie", cookie);
-                }
+              Log.d(LOG_TAG, "Download file: " + url);
 
                 connection.connect();
 
@@ -447,25 +480,42 @@ public class FileTransfer extends Plugin {
 
                 Log.d(LOG_TAG, "Saved file: " + target);
 
-                // create FileEntry object
-                FileUtils fileUtil = new FileUtils();
+              // create FileEntry object
+              FileUtils fileUtil = new FileUtils();
+              JSONObject fileEntry = fileUtil.getEntry(file);
 
-                return fileUtil.getEntry(file);
+              return new PluginResult(PluginResult.Status.OK, fileEntry);
             }
             else
             {
-                throw new IOException("Error: Unable to connect to domain");
+                Log.w(LOG_TAG, "Source URL is not in white list: '" + source + "'");
+                JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, 401);
+                return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+            }
+
+        } catch (FileNotFoundException e) {
+            JSONObject error = createFileTransferError(FILE_NOT_FOUND_ERR, source, target, connection);
+            Log.e(LOG_TAG, error.toString(), e);
+            return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+        } catch (MalformedURLException e) {
+            JSONObject error = createFileTransferError(INVALID_URL_ERR, source, target, connection);
+            Log.e(LOG_TAG, error.toString(), e);
+            return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+        } catch (Exception e) {  // IOException, JSONException, NullPointer
+            JSONObject error = createFileTransferError(CONNECTION_ERR, source, target, connection);
+            Log.e(LOG_TAG, error.toString(), e);
+            return new PluginResult(PluginResult.Status.IO_EXCEPTION, error);
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
             }
-        } catch (Exception e) {
-            Log.d(LOG_TAG, e.getMessage(), e);
-            throw new IOException("Error while downloading");
         }
     }
 
     /**
      * Get an input stream based on file path or content:// uri
      *
-     * @param path
+     * @param path foo
      * @return an input stream
      * @throws FileNotFoundException
      */
@@ -490,14 +540,23 @@ public class FileTransfer extends Plugin {
     /**
      * Get a File object from the passed in path
      * 
-     * @param path
-     * @return
+     * @param path file path
+     * @return file object
      */
-    private File getFileFromPath(String path) {
-        if (path.startsWith("file://")) {
-            return new File(path.substring(7));
+    private File getFileFromPath(String path) throws FileNotFoundException {
+        File file;
+        String prefix = "file://";
+
+        if (path.startsWith(prefix)) {
+            file = new File(path.substring(prefix.length()));
         } else {
-            return new File(path);
+            file = new File(path);
         }
+
+        if (file.getParent() == null) {
+            throw new FileNotFoundException();
+        }
+
+        return file;
     }
 }