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 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 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 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; } }