diff --git a/framework/assets/js/accelerometer.js b/framework/assets/js/accelerometer.js index fac351b8..3923a56a 100755 --- a/framework/assets/js/accelerometer.js +++ b/framework/assets/js/accelerometer.js @@ -1,35 +1,3 @@ -com.phonegap.AccelListenerProxy = function() { - this.className = "com.phonegap.AccelListener"; - this.status = -1; // not set yet -}; -com.phonegap.AccelListenerProxy.prototype.getStatus = function() { - if (this.status == -1) { // if not set, then request status - this.status = PhoneGap.exec(this.className, "getStatus", []); - } - return this.status; -}; -com.phonegap.AccelListenerProxy.prototype.onStatus = function(status) { - console.log("AccelListener.onStatus("+status+")"); - this.status = status; -}; -com.phonegap.AccelListenerProxy.prototype.getAcceleration = function() { - var r = PhoneGap.exec(this.className, "getAcceleration", []); - var a = new Acceleration(r.x,r.y,r.z); - return a; -}; -com.phonegap.AccelListenerProxy.prototype.start = function() { - return PhoneGap.exec(this.className, "start", []); -}; -com.phonegap.AccelListenerProxy.prototype.stop = function() { - return PhoneGap.exec(this.className, "stop", []); -}; -com.phonegap.AccelListenerProxy.prototype.setTimeout = function(timeout) { - return PhoneGap.exec(this.className, "setTimeout", [timeout]); -}; -com.phonegap.AccelListenerProxy.prototype.getTimeout = function() { - return PhoneGap.exec(this.className, "getTimeout", []); -}; -com.phonegap.AccelListener = new com.phonegap.AccelListenerProxy(); function Acceleration(x, y, z) { this.x = x; @@ -55,20 +23,17 @@ function Accelerometer() { this.timers = {}; }; -Accelerometer.STOPPED = 0; -Accelerometer.STARTING = 1; -Accelerometer.RUNNING = 2; -Accelerometer.ERROR_FAILED_TO_START = 3; Accelerometer.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"]; /** * Asynchronously aquires the current acceleration. * * @param {Function} successCallback The function to call when the acceleration data is available - * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. - * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. + * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL) + * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL) */ Accelerometer.prototype.getCurrentAcceleration = function(successCallback, errorCallback, options) { + console.log("Accelerometer.getCurrentAcceleration()"); // successCallback required if (typeof successCallback != "function") { @@ -82,60 +47,16 @@ Accelerometer.prototype.getCurrentAcceleration = function(successCallback, error return; } - // Get current acceleration status - var status = com.phonegap.AccelListener.getStatus(); - - // If running, then call successCallback - if (status == Accelerometer.RUNNING) { - try { - var accel = com.phonegap.AccelListener.getAcceleration(); - successCallback(accel); - } catch (e) { - console.log("Accelerometer Error in successCallback: " + e); - } - } - - // If not running, then start it - else if (status >= 0) { - com.phonegap.AccelListener.start(); - - // Wait until started - var timer = setInterval(function() { - var status = com.phonegap.AccelListener.getStatus(); - - // If accelerometer is running - if (status == Accelerometer.RUNNING) { - clearInterval(timer); - try { - var accel = com.phonegap.AccelListener.getAcceleration(); - successCallback(accel); - } catch (e) { - console.log("Accelerometer Error in successCallback: " + e); - } - } - - // If accelerometer error - else if (status == Accelerometer.ERROR_FAILED_TO_START) { - clearInterval(timer); - console.log("Accelerometer Error: "+ Accelerometer.ERROR_MSG[status]); - try { - if (errorCallback) { - errorCallback(status); - } - } catch (e) { - console.log("Accelerometer Error in errorCallback: " + e); - } - } - }, 10); - } + // Get acceleration + PhoneGap.execAsync(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); }; /** * Asynchronously aquires the acceleration repeatedly at a given interval. * * @param {Function} successCallback The function to call each time the acceleration data is available - * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. - * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. + * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL) + * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL) * @return String The watch id that must be passed to #clearWatch to stop watching. */ Accelerometer.prototype.watchAcceleration = function(successCallback, errorCallback, options) { @@ -156,40 +77,18 @@ Accelerometer.prototype.watchAcceleration = function(successCallback, errorCallb } // Make sure accelerometer timeout > frequency + 10 sec - var timeout = com.phonegap.AccelListener.getTimeout(); - if (timeout < (frequency + 10000)) { - com.phonegap.AccelListener.setTimeout(frequency + 10000); // set to frequency + 10 sec - } - - var id = PhoneGap.createUUID(); - com.phonegap.AccelListener.start(); + PhoneGap.execAsync( + function(timeout) { + if (timeout < (frequency + 10000)) { + PhoneGap.execAsync(null, null, "Accelerometer", "setTimeout", [frequency + 10000]); + } + }, + function(e) { }, "Accelerometer", "getTimeout", []); // Start watch timer + var id = PhoneGap.createUUID(); navigator.accelerometer.timers[id] = setInterval(function() { - var status = com.phonegap.AccelListener.getStatus(); - - // If accelerometer is running - if (status == Accelerometer.RUNNING) { - try { - var accel = com.phonegap.AccelListener.getAcceleration(); - successCallback(accel); - } catch (e) { - console.log("Accelerometer Error in successCallback: " + e); - } - } - - // If accelerometer had error - else if (status == Accelerometer.ERROR_FAILED_TO_START) { - console.log("Accelerometer Error: "+ Accelerometer.ERROR_MSG[status]); - try { - navigator.accelerometer.clearWatch(id); - if (errorCallback) { - errorCallback(status); - } - } catch (e) { - console.log("Accelerometer Error in errorCallback: " + e); - } - } + PhoneGap.execAsync(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); }, (frequency ? frequency : 1)); return id; diff --git a/framework/assets/js/camera.js b/framework/assets/js/camera.js old mode 100644 new mode 100755 index ba3668ac..0933c9da --- a/framework/assets/js/camera.js +++ b/framework/assets/js/camera.js @@ -1,27 +1,49 @@ -com.phonegap.CameraLauncherProxy = function() { - this.className = "com.phonegap.CameraLauncher"; -}; -com.phonegap.CameraLauncherProxy.prototype.setBase64 = function(b) { - return PhoneGap.exec(this.className, "setBase64", [b]); -}; -com.phonegap.CameraLauncherProxy.prototype.takePicture = function(quality) { - return PhoneGap.exec(this.className, "takePicture", [quality]); -}; -com.phonegap.CameraLauncher = new com.phonegap.CameraLauncherProxy(); /** * This class provides access to the device camera. * * @constructor */ -function Camera() { +Camera = function() { this.successCallback = null; this.errorCallback = null; this.options = null; }; /** - * Takes a photo and returns the image as a base64 encoded `String`. + * Format of image that returned from getPicture. + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) + */ +Camera.DestinationType = { + DATA_URL: 0, // Return base64 encoded string + FILE_URI: 1 // Return file uri (content://media/external/images/media/2 for Android) +}; +Camera.prototype.DestinationType = Camera.DestinationType; + +/** + * Source to getPicture from. + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) + */ +Camera.PictureSourceType = { + PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) + CAMERA : 1, // Take picture from camera + SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android) +}; +Camera.prototype.PictureSourceType = Camera.PictureSourceType; + +/** + * 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=DATA_URL. * * @param {Function} successCallback * @param {Function} errorCallback @@ -44,12 +66,19 @@ Camera.prototype.getPicture = function(successCallback, errorCallback, options) this.successCallback = successCallback; this.errorCallback = errorCallback; this.options = options; + var quality = 80; if (options.quality) { - com.phonegap.CameraLauncher.takePicture(options.quality); + quality = this.options.quality; } - else { - com.phonegap.CameraLauncher.takePicture(80); + var destinationType = Camera.DestinationType.DATA_URL; + if (this.options.destinationType) { + destinationType = this.options.destinationType; } + var sourceType = Camera.PictureSourceType.CAMERA; + if (typeof this.options.sourceType == "number") { + sourceType = this.options.sourceType; + } + PhoneGap.execAsync(null, null, "Camera", "takePicture", [quality, destinationType, sourceType]); }; /** diff --git a/framework/assets/js/compass.js b/framework/assets/js/compass.js index 54aebdbf..27b25f02 100644 --- a/framework/assets/js/compass.js +++ b/framework/assets/js/compass.js @@ -1,25 +1,3 @@ -com.phonegap.CompassListenerProxy = function() { - this.className = "com.phonegap.CompassListener"; -}; -com.phonegap.CompassListenerProxy.prototype.start = function() { - return PhoneGap.exec(this.className, "start", []); -}; -com.phonegap.CompassListenerProxy.prototype.stop = function() { - return PhoneGap.exec(this.className, "stop", []); -}; -com.phonegap.CompassListenerProxy.prototype.getStatus = function() { - return PhoneGap.exec(this.className, "getStatus", []); -}; -com.phonegap.CompassListenerProxy.prototype.getHeading = function() { - return PhoneGap.exec(this.className, "getHeading", []); -}; -com.phonegap.CompassListenerProxy.prototype.setTimeout = function(timeout) { - return PhoneGap.exec(this.className, "setTimeout", [timeout]); -}; -com.phonegap.CompassListenerProxy.prototype.getTimeout = function() { - return PhoneGap.exec(this.className, "getTimeout", []); -}; -com.phonegap.CompassListener = new com.phonegap.CompassListenerProxy(); /** * This class provides access to device Compass data. @@ -37,18 +15,14 @@ function Compass() { this.timers = {}; }; -Compass.STOPPED = 0; -Compass.STARTING = 1; -Compass.RUNNING = 2; -Compass.ERROR_FAILED_TO_START = 3; Compass.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"]; /** * Asynchronously aquires the current heading. * * @param {Function} successCallback The function to call when the heading data is available - * @param {Function} errorCallback The function to call when there is an error getting the heading data. - * @param {PositionOptions} options The options for getting the heading data such as timeout. + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) + * @param {PositionOptions} options The options for getting the heading data such as timeout. (OPTIONAL) */ Compass.prototype.getCurrentHeading = function(successCallback, errorCallback, options) { @@ -64,61 +38,16 @@ Compass.prototype.getCurrentHeading = function(successCallback, errorCallback, o return; } - // Get current compass status - var status = com.phonegap.CompassListener.getStatus(); - - // If running, then call successCallback - if (status == Compass.RUNNING) { - try { - var heading = com.phonegap.CompassListener.getHeading(); - successCallback(heading); - } catch (e) { - console.log("Compass Error in successCallback: " + e); - } - } - - // If not running, then start it - else { - com.phonegap.CompassListener.start(); - - // Wait until started - var timer = setInterval(function() { - var status = com.phonegap.CompassListener.getStatus(); - if (status != Compass.STARTING) { - clearInterval(timer); - - // If compass is running - if (status == Compass.RUNNING) { - try { - var heading = com.phonegap.CompassListener.getHeading(); - successCallback(heading); - } catch (e) { - console.log("Compass Error in successCallback: " + e); - } - } - - // If compass error - else { - console.log("Compass Error: "+ Compass.ERROR_MSG[status]); - try { - if (errorCallback) { - errorCallback(status); - } - } catch (e) { - console.log("Compass Error in errorCallback: " + e); - } - } - } - }, 10); - } + // Get heading + PhoneGap.execAsync(successCallback, errorCallback, "Compass", "getHeading", []); }; /** * Asynchronously aquires the heading repeatedly at a given interval. * * @param {Function} successCallback The function to call each time the heading data is available - * @param {Function} errorCallback The function to call when there is an error getting the heading data. - * @param {HeadingOptions} options The options for getting the heading data such as timeout and the frequency of the watch. + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) + * @param {HeadingOptions} options The options for getting the heading data such as timeout and the frequency of the watch. (OPTIONAL) * @return String The watch id that must be passed to #clearWatch to stop watching. */ Compass.prototype.watchHeading= function(successCallback, errorCallback, options) { @@ -139,41 +68,20 @@ Compass.prototype.watchHeading= function(successCallback, errorCallback, options } // Make sure compass timeout > frequency + 10 sec - var timeout = com.phonegap.CompassListener.getTimeout(); - if (timeout < (frequency + 10000)) { - com.phonegap.CompassListener.setTimeout(frequency + 10000); // set to frequency + 10 sec - } + PhoneGap.execAsync( + function(timeout) { + if (timeout < (frequency + 10000)) { + PhoneGap.execAsync(null, null, "Compass", "setTimeout", [frequency + 10000]); + } + }, + function(e) { }, "Compass", "getTimeout", []); + // Start watch timer to get headings var id = PhoneGap.createUUID(); - com.phonegap.CompassListener.start(); - - // Start watch timer - navigator.compass.timers[id] = setInterval(function() { - var status = com.phonegap.CompassListener.getStatus(); - - // If compass is running - if (status == Compass.RUNNING) { - try { - var heading = com.phonegap.CompassListener.getHeading(); - successCallback(heading); - } catch (e) { - console.log("Compass Error in successCallback: " + e); - } - } - - // If compass had error - else if (status != Compass.STARTING) { - console.log("Compass Error: "+ Compass.ERROR_MSG[status]); - try { - navigator.compass.clearWatch(id); - if (errorCallback) { - errorCallback(status); - } - } catch (e) { - console.log("Compass Error in errorCallback: " + e); - } - } - }, (frequency ? frequency : 1)); + navigator.compass.timers[id] = setInterval( + function() { + PhoneGap.execAsync(successCallback, errorCallback, "Compass", "getHeading", []); + }, (frequency ? frequency : 1)); return id; }; diff --git a/framework/assets/js/contact.js b/framework/assets/js/contact.js index f178eb96..7debac8d 100644 --- a/framework/assets/js/contact.js +++ b/framework/assets/js/contact.js @@ -1,13 +1,3 @@ -com.phonegap.ContactManagerProxy = function() { - this.className = "com.phonegap.ContactManager"; -}; -com.phonegap.ContactManagerProxy.prototype.getContactsAndSendBack = function() { - return PhoneGap.exec(this.className, "getContactsAndSendBack", []); -}; -com.phonegap.ContactManagerProxy.prototype.search = function(name, npa, mail) { - return PhoneGap.exec(this.className, "search", [name, npa, mail]); -}; -com.phonegap.ContactManager = new com.phonegap.ContactManagerProxy(); var Contact = function() { this.name = new ContactName(); @@ -39,6 +29,8 @@ var Contacts = function() { }; Contacts.prototype.find = function(obj, win, fail) { + this.win = win; + this.fail = fail; if(obj.name != null) { // Build up the search term that we'll use in SQL, based on the structure/contents of the contact object passed into find. var searchTerm = ''; @@ -51,10 +43,8 @@ Contacts.prototype.find = function(obj, win, fail) { if (!obj.name.familyName && !obj.name.givenName && obj.name.formatted) { searchTerm = obj.name.formatted; } - com.phonegap.ContactManager.search(searchTerm, "", ""); + PhoneGap.execAsync(null, null, "Contacts", "search", [searchTerm, "", ""]); } - this.win = win; - this.fail = fail; }; Contacts.prototype.droidFoundContact = function(name, npa, email) { diff --git a/framework/assets/js/crypto.js b/framework/assets/js/crypto.js index 3218a0c5..79512731 100644 --- a/framework/assets/js/crypto.js +++ b/framework/assets/js/crypto.js @@ -1,25 +1,15 @@ -com.phonegap.CryptoHandlerProxy = function() { - this.className = "com.phonegap.CryptoHandler"; -}; -com.phonegap.CryptoHandlerProxy.prototype.encrypt = function(pass, text) { - return PhoneGap.exec(this.className, "encrypt", [pass, text]); -}; -com.phonegap.CryptoHandlerProxy.prototype.decrypt = function(pass, text) { - return PhoneGap.exec(this.className, "decrypt", [pass, text]); -}; -com.phonegap.CryptoHandler = new com.phonegap.CryptoHandlerProxy(); var Crypto = function() { }; Crypto.prototype.encrypt = function(seed, string, callback) { - com.phonegap.CryptoHandler.encrypt(seed, string); this.encryptWin = callback; + PhoneGap.execAsync(null, null, "Crypto", "encrypt", [seed, string]); }; Crypto.prototype.decrypt = function(seed, string, callback) { - com.phonegap.CryptoHandler.decrypt(seed, string); this.decryptWin = callback; + PhoneGap.execAsync(null, null, "Crypto", "decrypt", [seed, string]); }; Crypto.prototype.gotCryptedString = function(string) { diff --git a/framework/assets/js/device.js b/framework/assets/js/device.js old mode 100644 new mode 100755 index 17bd9bb8..a4f754f2 --- a/framework/assets/js/device.js +++ b/framework/assets/js/device.js @@ -1,59 +1,84 @@ /** - * this represents the mobile device, and provides properties for inspecting the model, version, UUID of the + * This represents the mobile device, and provides properties for inspecting the model, version, UUID of the * phone, etc. * @constructor */ function Device() { this.available = PhoneGap.available; this.platform = null; - this.version = null; - this.name = null; - this.gap = null; - this.uuid = null; - try { - if (window.DroidGap) { - this.available = true; - this.uuid = window.DroidGap.getUuid(); - this.version = window.DroidGap.getOSVersion(); - this.gapVersion = window.DroidGap.getVersion(); - this.platform = window.DroidGap.getPlatform(); - this.name = window.DroidGap.getProductName(); - this.line1Number = window.DroidGap.getLine1Number(); - this.deviceId = window.DroidGap.getDeviceId(); - this.simSerialNumber = window.DroidGap.getSimSerialNumber(); - this.subscriberId = window.DroidGap.getSubscriberId(); - } - } catch(e) { - this.available = false; - } + this.version = null; + this.name = null; + this.uuid = null; + this.phonegap = null; + + var me = this; + this.getInfo( + function(info) { + me.available = true; + me.platform = info.platform; + me.version = info.version; + me.uuid = info.uuid; + me.phonegap = info.phonegap; + PhoneGap.onPhoneGapInfoReady.fire(); + }, + function(e) { + me.available = false; + console.log("Error initializing PhoneGap: " + e); + alert("Error initializing PhoneGap: "+e); + }); } -/* - * You must explicitly override the back button. +/** + * Get device info + * + * @param {Function} successCallback The function to call when the heading data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) */ +Device.prototype.getInfo = function(successCallback, errorCallback) { -Device.prototype.overrideBackButton = function() -{ - BackButton.override(); + // successCallback required + if (typeof successCallback != "function") { + console.log("Device Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback != "function")) { + console.log("Device Error: errorCallback is not a function"); + return; + } + + // Get info + PhoneGap.execAsync(successCallback, errorCallback, "Device", "getDeviceInfo", []); +}; + +/* + * This is only for Android. + * + * You must explicitly override the back button. + */ +Device.prototype.overrideBackButton = function() { + BackButton.override(); } /* + * This is only for Android. + * * This resets the back button to the default behaviour */ - -Device.prototype.resetBackButton = function() -{ - BackButton.reset(); +Device.prototype.resetBackButton = function() { + BackButton.reset(); } /* + * This is only for Android. + * * This terminates the activity! */ -Device.prototype.exitApp = function() -{ - BackButton.exitApp(); +Device.prototype.exitApp = function() { + BackButton.exitApp(); } PhoneGap.addConstructor(function() { navigator.device = window.device = new Device(); -}); \ No newline at end of file +}); diff --git a/framework/assets/js/file.js b/framework/assets/js/file.js old mode 100644 new mode 100755 index e32bbfd7..b4037a9c --- a/framework/assets/js/file.js +++ b/framework/assets/js/file.js @@ -1,226 +1,416 @@ +/** + * This class provides generic read and write access to the mobile device file system. + * They are not used to read files from a server. + */ +/** + * List of files + */ +function FileList() { + this.files = {}; +}; +/** + * Describes a single file in a FileList + */ +function File() { + this.name = null; + this.type = null; + this.urn = null; +}; -PhoneGap.addConstructor(function() { if (typeof navigator.fileMgr == "undefined") navigator.fileMgr = new FileMgr();}); +/** + * Create an event object since we can't set target on DOM event. + * + * @param type + * @param target + * + */ +File._createEvent = function(type, target) { + // Can't create event object, since we can't set target (its readonly) + //var evt = document.createEvent('Events'); + //evt.initEvent("onload", false, false); + var evt = {"type": type}; + evt.target = target; + return evt; +}; + +function FileError() { + // File error codes + // Found in DOMException + this.NOT_FOUND_ERR = 8; + this.SECURITY_ERR = 18; + this.ABORT_ERR = 20; + + // Added by this specification + this.NOT_READABLE_ERR = 24; + this.ENCODING_ERR = 26; + + this.code = null; +}; + +//----------------------------------------------------------------------------- +// File manager +//----------------------------------------------------------------------------- + +function FileMgr() { +}; + +FileMgr.prototype.getFileBasePaths = function() { +}; + +FileMgr.prototype.testSaveLocationExists = function(successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "testSaveLocationExists", []); +}; + +FileMgr.prototype.testFileExists = function(fileName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "testFileExists", [fileName]); +}; + +FileMgr.prototype.testDirectoryExists = function(dirName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "testDirectoryExists", [dirName]); +}; + +FileMgr.prototype.createDirectory = function(dirName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "createDirectory", [dirName]); +}; + +FileMgr.prototype.deleteDirectory = function(dirName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "deleteDirectory", [dirName]); +}; + +FileMgr.prototype.deleteFile = function(fileName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "deleteFile", [fileName]); +}; + +FileMgr.prototype.getFreeDiskSpace = function(successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "getFreeDiskSpace", []); +}; + +FileMgr.prototype.writeAsText = function(fileName, data, append, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "writeAsText", [fileName, data, append]); +}; + +FileMgr.prototype.readAsText = function(fileName, encoding, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "readAsText", [fileName, encoding]); +}; + +FileMgr.prototype.readAsDataURL = function(fileName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "readAsDataURL", [fileName]); +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.fileMgr == "undefined") navigator.fileMgr = new FileMgr(); +}); + +//----------------------------------------------------------------------------- +// File Reader +//----------------------------------------------------------------------------- +// TODO: All other FileMgr function operate on the SD card as root. However, +// for FileReader & FileWriter the root is not SD card. Should this be changed? + +/** + * This class reads the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To read from the SD card, the file name is "sdcard/my_file.txt" + */ +function FileReader() { + this.fileName = ""; + + this.readyState = 0; + + // File data + this.result = null; + + // Error + this.error = null; + + // Event handlers + this.onloadstart = null; // When the read starts. + this.onprogress = null; // While reading (and decoding) file or fileBlob data, and reporting partial file data (progess.loaded/progress.total) + this.onload = null; // When the read has successfully completed. + this.onerror = null; // When the read has failed (see errors). + this.onloadend = null; // When the request has completed (either in success or failure). + this.onabort = null; // When the read has been aborted. For instance, by invoking the abort() method. +}; + +// States +FileReader.EMPTY = 0; +FileReader.LOADING = 1; +FileReader.DONE = 2; + +/** + * Abort reading file. + */ +FileReader.prototype.abort = function() { + this.readyState = FileReader.DONE; + + // If abort callback + if (typeof this.onabort == "function") { + var evt = File._createEvent("abort", this); + this.onabort(evt); + } + + // TODO: Anything else to do? Maybe sent to native? +}; + +/** + * Read text file. + * + * @param file The name of the file + * @param encoding [Optional] (see http://www.iana.org/assignments/character-sets) + */ +FileReader.prototype.readAsText = function(file, encoding) { + this.fileName = file; + + // LOADING state + this.readyState = FileReader.LOADING; + + // If loadstart callback + if (typeof this.onloadstart == "function") { + var evt = File._createEvent("loadstart", this); + this.onloadstart(evt); + } + + // Default encoding is UTF-8 + var enc = encoding ? encoding : "UTF-8"; + + var me = this; + + // Read file + navigator.fileMgr.readAsText(file, enc, + + // Success callback + function(r) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileReader.DONE) { + return; + } + + // Save result + me.result = r; + + // DONE state + me.readyState = FileReader.DONE; + + // If onload callback + if (typeof me.onload == "function") { + var evt = File._createEvent("load", me); + me.onload(evt); + } + + // If onloadend callback + if (typeof me.onloadend == "function") { + var evt = File._createEvent("loadend", me); + me.onloadend(evt); + } + }, + + // Error callback + function(e) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileReader.DONE) { + return; + } + + // Save error + me.error = e; + + // DONE state + me.readyState = FileReader.DONE; + + // If onerror callback + if (typeof me.onerror == "function") { + var evt = File._createEvent("error", me); + me.onerror(evt); + } + + // If onloadend callback + if (typeof me.onloadend == "function") { + var evt = File._createEvent("loadend", me); + me.onloadend(evt); + } + } + ); +}; /** - * This class provides iPhone read and write access to the mobile device file system. - * Based loosely on http://www.w3.org/TR/2009/WD-FileAPI-20091117/#dfn-empty - */ -function FileMgr() -{ - this.fileWriters = {}; // empty maps - this.fileReaders = {}; - - this.docsFolderPath = "../../Documents"; - this.tempFolderPath = "../../tmp"; - this.freeDiskSpace = -1; - this.getFileBasePaths(); -} - -// private, called from Native Code -FileMgr.prototype._setPaths = function(docs,temp) -{ - this.docsFolderPath = docs; - this.tempFolderPath = temp; -} - -// private, called from Native Code -FileMgr.prototype._setFreeDiskSpace = function(val) -{ - this.freeDiskSpace = val; -} - - -// FileWriters add/remove -// called internally by writers -FileMgr.prototype.addFileWriter = function(filePath,fileWriter) -{ - this.fileWriters[filePath] = fileWriter; -} - -FileMgr.prototype.removeFileWriter = function(filePath) -{ - this.fileWriters[filePath] = null; -} - -// File readers add/remove -// called internally by readers -FileMgr.prototype.addFileReader = function(filePath,fileReader) -{ - this.fileReaders[filePath] = fileReader; -} - -FileMgr.prototype.removeFileReader = function(filePath) -{ - this.fileReaders[filePath] = null; -} - -/******************************************* + * Read file and return data as a base64 encoded data url. + * A data url is of the form: + * data:[][;base64], * - * private reader callback delegation - * called from native code + * @param file The name of the file */ -FileMgr.prototype.reader_onloadstart = function(filePath,result) -{ - this.fileReaders[filePath].onloadstart(result); -} +FileReader.prototype.readAsDataURL = function(file) { + this.fileName = file; -FileMgr.prototype.reader_onprogress = function(filePath,result) -{ - this.fileReaders[filePath].onprogress(result); -} + // LOADING state + this.readyState = FileReader.LOADING; -FileMgr.prototype.reader_onload = function(filePath,result) -{ - this.fileReaders[filePath].result = unescape(result); - this.fileReaders[filePath].onload(this.fileReaders[filePath].result); -} + // If loadstart callback + if (typeof this.onloadstart == "function") { + var evt = File._createEvent("loadstart", this); + this.onloadstart(evt); + } -FileMgr.prototype.reader_onerror = function(filePath,err) -{ - this.fileReaders[filePath].result = err; - this.fileReaders[filePath].onerror(err); -} + var me = this; -FileMgr.prototype.reader_onloadend = function(filePath,result) -{ - this.fileReaders[filePath].onloadend(result); -} + // Read file + navigator.fileMgr.readAsDataURL(file, -/******************************************* + // Success callback + function(r) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileReader.DONE) { + return; + } + + // Save result + me.result = r; + + // DONE state + me.readyState = FileReader.DONE; + + // If onload callback + if (typeof me.onload == "function") { + var evt = File._createEvent("load", me); + me.onload(evt); + } + + // If onloadend callback + if (typeof me.onloadend == "function") { + var evt = File._createEvent("loadend", me); + me.onloadend(evt); + } + }, + + // Error callback + function(e) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileReader.DONE) { + return; + } + + // Save error + me.error = e; + + // DONE state + me.readyState = FileReader.DONE; + + // If onerror callback + if (typeof me.onerror == "function") { + var evt = File._createEvent("error", me); + me.onerror(evt); + } + + // If onloadend callback + if (typeof me.onloadend == "function") { + var evt = File._createEvent("loadend", me); + me.onloadend(evt); + } + } + ); +}; + +/** + * Read file and return data as a binary data. * - * private writer callback delegation - * called from native code -*/ -FileMgr.prototype.writer_onerror = function(filePath,err) -{ - this.fileWriters[filePath].onerror(err); -} - -FileMgr.prototype.writer_oncomplete = function(filePath,result) -{ - this.fileWriters[filePath].oncomplete(result); // result contains bytes written -} - - -FileMgr.prototype.getFileBasePaths = function() -{ - //PhoneGap.exec("File.getFileBasePaths"); -} - -FileMgr.prototype.testFileExists = function(fileName, successCallback, errorCallback) -{ - var test = FileUtil.testFileExists(fileName); - test ? successCallback() : errorCallback(); -} - -FileMgr.prototype.testDirectoryExists = function(dirName, successCallback, errorCallback) -{ - this.successCallback = successCallback; - this.errorCallback = errorCallback; - var test = FileUtil.testDirectoryExists(dirName); - test ? successCallback() : errorCallback(); -} - -FileMgr.prototype.createDirectory = function(dirName, successCallback, errorCallback) -{ - this.successCallback = successCallback; - this.errorCallback = errorCallback; - var test = FileUtil.createDirectory(dirName); - test ? successCallback() : errorCallback(); -} - -FileMgr.prototype.deleteDirectory = function(dirName, successCallback, errorCallback) -{ - this.successCallback = successCallback; - this.errorCallback = errorCallback; - var test = FileUtil.deleteDirectory(dirName); - test ? successCallback() : errorCallback(); -} - -FileMgr.prototype.deleteFile = function(fileName, successCallback, errorCallback) -{ - this.successCallback = successCallback; - this.errorCallback = errorCallback; - FileUtil.deleteFile(fileName); - test ? successCallback() : errorCallback(); -} - -FileMgr.prototype.getFreeDiskSpace = function(successCallback, errorCallback) -{ - if(this.freeDiskSpace > 0) - { - return this.freeDiskSpace; - } - else - { - this.successCallback = successCallback; - this.errorCallback = errorCallback; - this.freeDiskSpace = FileUtil.getFreeDiskSpace(); - (this.freeDiskSpace > 0) ? successCallback() : errorCallback(); - } -} - - -// File Reader - - -function FileReader() -{ - this.fileName = ""; - this.result = null; - this.onloadstart = null; - this.onprogress = null; - this.onload = null; - this.onerror = null; - this.onloadend = null; -} - - -FileReader.prototype.abort = function() -{ - // Not Implemented -} - -FileReader.prototype.readAsText = function(file) -{ - if(this.fileName && this.fileName.length > 0) - { - navigator.fileMgr.removeFileReader(this.fileName,this); - } - this.fileName = file; - navigator.fileMgr.addFileReader(this.fileName,this); - - return FileUtil.read(this.fileName); -} + * @param file The name of the file + */ +FileReader.prototype.readAsBinaryString = function(file) { + // TODO - Can't return binary data to browser. + this.fileName = file; +}; +//----------------------------------------------------------------------------- // File Writer +//----------------------------------------------------------------------------- -function FileWriter() -{ - this.fileName = ""; - this.result = null; - this.readyState = 0; // EMPTY - this.result = null; - this.onerror = null; - this.oncomplete = null; -} +/** + * This class writes to the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To write to the SD card, the file name is "sdcard/my_file.txt" + */ +function FileWriter() { + this.fileName = ""; + this.result = null; + this.readyState = 0; // EMPTY + this.result = null; + this.onerror = null; + this.oncomplete = null; +}; + +// States +FileWriter.EMPTY = 0; +FileWriter.LOADING = 1; +FileWriter.DONE = 2; + +FileWriter.prototype.writeAsText = function(file, text, bAppend) { + if (bAppend != true) { + bAppend = false; // for null values + } + + this.fileName = file; + + // LOADING state + this.readyState = FileWriter.LOADING; + + var me = this; + + // Read file + navigator.fileMgr.writeAsText(file, text, bAppend, + + // Success callback + function(r) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileWriter.DONE) { + return; + } + + // Save result + me.result = r; + + // DONE state + me.readyState = FileWriter.DONE; + + // If oncomplete callback + if (typeof me.oncomplete == "function") { + var evt = File._createEvent("complete", me); + me.oncomplete(evt); + } + }, + + // Error callback + function(e) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileWriter.DONE) { + return; + } + + // Save error + me.error = e; + + // DONE state + me.readyState = FileWriter.DONE; + + // If onerror callback + if (typeof me.onerror == "function") { + var evt = File._createEvent("error", me); + me.onerror(evt); + } + } + ); + +}; -FileWriter.prototype.writeAsText = function(file,text,bAppend) -{ - if(this.fileName && this.fileName.length > 0) - { - navigator.fileMgr.removeFileWriter(this.fileName,this); - } - this.fileName = file; - if(bAppend != true) - { - bAppend = false; // for null values - } - navigator.fileMgr.addFileWriter(file,this); - this.readyState = 0; // EMPTY - var call = FileUtil.write(file, text, bAppend); - this.result = null; -} diff --git a/framework/assets/js/geolocation.js b/framework/assets/js/geolocation.js old mode 100644 new mode 100755 index fec7b884..e063f075 --- a/framework/assets/js/geolocation.js +++ b/framework/assets/js/geolocation.js @@ -3,85 +3,184 @@ * @constructor */ function Geolocation() { - /** - * The last known GPS position. - */ + + // The last known GPS position. this.lastPosition = null; - this.lastError = null; - this.listeners = null; + + // Geolocation listeners + this.listeners = {}; }; -var geoListeners = []; - -Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallback, options) -{ - var position = Geo.getCurrentLocation(); - this.global_success = successCallback; - this.fail = errorCallback; -} - -// Run the global callback -Geolocation.prototype.gotCurrentPosition = function(lat, lng, alt, altacc, head, vel, stamp) -{ - if (lat == "undefined" || lng == "undefined") - { - this.fail(); - } - else - { - coords = new Coordinates(lat, lng, alt, acc, head, vel); - loc = new Position(coords, stamp); - this.lastPosition = loc; - this.global_success(loc); - } -} - -/* -* This turns on the GeoLocator class, which has two listeners. -* The listeners have their own timeouts, and run independently of this process -* In this case, we return the key to the watch hash -*/ - -Geolocation.prototype.watchPosition = function(successCallback, errorCallback, options) -{ - var frequency = (options != undefined)? options.frequency : 10000; - - var key = geoListeners.push( {"success" : successCallback, "fail" : errorCallback }) - 1; - - // TO-DO: Get the names of the method and pass them as strings to the Java. - return Geo.start(frequency, key); -} - -/* - * Retrieve and stop this listener from listening to the GPS +/** + * Position error object * + * @param code + * @param message */ -Geolocation.prototype.success = function(key, lat, lng, alt, altacc, head, vel, stamp) -{ - var coords = new Coordinates(lat, lng, alt, acc, head, vel); - var loc = new Position(coords, stamp); - geoListeners[key].success(loc); +function PositionError(code, message) { + this.code = code; + this.message = message; +}; + +PositionError.PERMISSION_DENIED = 1; +PositionError.POSITION_UNAVAILABLE = 2; +PositionError.TIMEOUT = 3; + +/** + * Asynchronously aquires the current position. + * + * @param {Function} successCallback The function to call when the position data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading position. (OPTIONAL) + * @param {PositionOptions} options The options for getting the position data. (OPTIONAL) + */ +Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallback, options) { + if (navigator._geo.listeners["global"]) { + console.log("Geolocation Error: Still waiting for previous getCurrentPosition() request."); + try { + errorCallback(new PositionError(PositionError.TIMEOUT, "Geolocation Error: Still waiting for previous getCurrentPosition() request.")); + } catch (e) { + } + return; + } + var maximumAge = 10000; + var enableHighAccuracy = false; + var timeout = 10000; + if (typeof options != "undefined") { + if (typeof options.maximumAge != "undefined") { + maximumAge = options.maximumAge; + } + if (typeof options.enableHighAccuracy != "undefined") { + enableHighAccuracy = options.enableHighAccuracy; + } + if (typeof options.timeout != "undefined") { + timeout = options.timeout; + } + } + navigator._geo.listeners["global"] = {"success" : successCallback, "fail" : errorCallback }; + PhoneGap.execAsync(null, null, "Geolocation", "getCurrentLocation", [enableHighAccuracy, timeout, maximumAge]); } -Geolocation.prototype.fail = function(key) -{ - geoListeners[key].fail(); -} - -Geolocation.prototype.clearWatch = function(watchId) -{ - Geo.stop(watchId); -} +/** + * Asynchronously watches the geolocation for changes to geolocation. When a change occurs, + * the successCallback is called with the new location. + * + * @param {Function} successCallback The function to call each time the location data is available + * @param {Function} errorCallback The function to call when there is an error getting the location data. (OPTIONAL) + * @param {PositionOptions} options The options for getting the location data such as frequency. (OPTIONAL) + * @return String The watch id that must be passed to #clearWatch to stop watching. + */ +Geolocation.prototype.watchPosition = function(successCallback, errorCallback, options) { + var maximumAge = 10000; + var enableHighAccuracy = false; + var timeout = 10000; + if (typeof options != "undefined") { + if (typeof options.frequency != "undefined") { + maximumAge = options.frequency; + } + if (typeof options.maximumAge != "undefined") { + maximumAge = options.maximumAge; + } + if (typeof options.enableHighAccuracy != "undefined") { + enableHighAccuracy = options.enableHighAccuracy; + } + if (typeof options.timeout != "undefined") { + timeout = options.timeout; + } + } + var id = PhoneGap.createUUID(); + navigator._geo.listeners[id] = {"success" : successCallback, "fail" : errorCallback }; + PhoneGap.execAsync(null, null, "Geolocation", "start", [id, enableHighAccuracy, timeout, maximumAge]); + return id; +}; + +/* + * Native callback when watch position has a new position. + * PRIVATE METHOD + * + * @param {String} id + * @param {Number} lat + * @param {Number} lng + * @param {Number} alt + * @param {Number} altacc + * @param {Number} head + * @param {Number} vel + * @param {Number} stamp + */ +Geolocation.prototype.success = function(id, lat, lng, alt, altacc, head, vel, stamp) { + var coords = new Coordinates(lat, lng, alt, altacc, head, vel); + var loc = new Position(coords, stamp); + try { + if (lat == "undefined" || lng == "undefined") { + navigator._geo.listeners[id].fail(new PositionError(PositionError.POSITION_UNAVAILABLE, "Lat/Lng are undefined.")); + } + else { + navigator._geo.lastPosition = loc; + navigator._geo.listeners[id].success(loc); + } + } + catch (e) { + console.log("Geolocation Error: Error calling success callback function."); + } + + if (id == "global") { + delete navigator._geo.listeners["global"]; + } +}; + +/** + * Native callback when watch position has an error. + * PRIVATE METHOD + * + * @param {String} id The ID of the watch + * @param {Number} code The error code + * @param {String} msg The error message + */ +Geolocation.prototype.fail = function(id, code, msg) { + try { + navigator._geo.listeners[id].fail(new PositionError(code, msg)); + } + catch (e) { + console.log("Geolocation Error: Error calling error callback function."); + } +}; + +/** + * Clears the specified heading watch. + * + * @param {String} id The ID of the watch returned from #watchPosition + */ +Geolocation.prototype.clearWatch = function(id) { + PhoneGap.execAsync(null, null, "Geolocation", "stop", [id]); + delete navigator._geo.listeners[id]; +}; + +/** + * Force the PhoneGap geolocation to be used instead of built-in. + */ +Geolocation.usingPhoneGap = false; +Geolocation.usePhoneGap = function() { + if (Geolocation.usingPhoneGap) { + return; + } + Geolocation.usingPhoneGap = true; + + // Set built-in geolocation methods to our own implementations + // (Cannot replace entire geolocation, but can replace individual methods) + navigator.geolocation.setLocation = navigator._geo.setLocation; + navigator.geolocation.getCurrentPosition = navigator._geo.getCurrentPosition; + navigator.geolocation.watchPosition = navigator._geo.watchPosition; + navigator.geolocation.clearWatch = navigator._geo.clearWatch; + navigator.geolocation.start = navigator._geo.start; + navigator.geolocation.stop = navigator._geo.stop; +}; PhoneGap.addConstructor(function() { - // Taken from Jesse's geo fix (similar problem) in PhoneGap iPhone. Go figure, same browser! - function __proxyObj(origObj, proxyObj, funkList) { - for (var v in funkList) { - origObj[funkList[v]] = proxyObj[funkList[v]]; - } - } - // In the case of Android, we can use the Native Geolocation Object if it exists, so only load this on 1.x devices - if (typeof navigator.geolocation == 'undefined') { - navigator.geolocation = new Geolocation(); - } + navigator._geo = new Geolocation(); + + // No native geolocation object for Android 1.x, so use PhoneGap geolocation + if (typeof navigator.geolocation == 'undefined') { + navigator.geolocation = navigator._geo; + Geolocation.usingPhoneGap = true; + } }); + diff --git a/framework/assets/js/media.js b/framework/assets/js/media.js index 345f1bb2..b3e57ab1 100755 --- a/framework/assets/js/media.js +++ b/framework/assets/js/media.js @@ -1,28 +1,3 @@ -com.phonegap.AudioHandlerProxy = function() { - this.className = "com.phonegap.AudioHandler"; -}; -com.phonegap.AudioHandlerProxy.prototype.startRecordingAudio = function(id, file) { - return PhoneGap.exec(this.className, "startRecordingAudio", [id, file]); -}; -com.phonegap.AudioHandlerProxy.prototype.stopRecordingAudio = function(id) { - return PhoneGap.exec(this.className, "stopRecordingAudio", [id]); -}; -com.phonegap.AudioHandlerProxy.prototype.startPlayingAudio = function(id, file) { - return PhoneGap.exec(this.className, "startPlayingAudio", [id, file]); -}; -com.phonegap.AudioHandlerProxy.prototype.pausePlayingAudio = function(id) { - return PhoneGap.exec(this.className, "pausePlayingAudio", [id]); -}; -com.phonegap.AudioHandlerProxy.prototype.stopPlayingAudio = function(id) { - return PhoneGap.exec(this.className, "stopPlayingAudio", [id]); -}; -com.phonegap.AudioHandlerProxy.prototype.getCurrentPositionAudio = function(id) { - return PhoneGap.exec(this.className, "getCurrentPositionAudio", [id]); -}; -com.phonegap.AudioHandlerProxy.prototype.getDurationAudio = function(id, file) { - return PhoneGap.exec(this.className, "getDurationAudio", [id, file]); -}; -com.phonegap.AudioHandler = new com.phonegap.AudioHandlerProxy(); /** * List of media objects. @@ -88,8 +63,10 @@ PhoneGap.Media.onStatus = function(id, msg, value) { * errorCallback(int errorCode) - OPTIONAL * @param statusCallback The callback to be called when media status has changed. * statusCallback(int statusCode) - OPTIONAL + * @param positionCallback The callback to be called when media position has changed. + * positionCallback(long position) - OPTIONAL */ -Media = function(src, successCallback, errorCallback, statusCallback) { +Media = function(src, successCallback, errorCallback, statusCallback, positionCallback) { // successCallback optional if (successCallback && (typeof successCallback != "function")) { @@ -109,19 +86,27 @@ Media = function(src, successCallback, errorCallback, statusCallback) { return; } + // statusCallback optional + if (positionCallback && (typeof positionCallback != "function")) { + console.log("Media Error: positionCallback is not a function"); + return; + } + this.id = PhoneGap.createUUID(); PhoneGap.mediaObjects[this.id] = this; this.src = src; this.successCallback = successCallback; this.errorCallback = errorCallback; this.statusCallback = statusCallback; + this.positionCallback = positionCallback; this._duration = -1; + this._position = -1; }; // Media messages Media.MEDIA_STATE = 1; Media.MEDIA_DURATION = 2; -Media.MEDIA_ERROR = 3; +Media.MEDIA_ERROR = 9; // Media states Media.MEDIA_NONE = 0; @@ -150,21 +135,21 @@ MediaError.MEDIA_ERR_NONE_SUPPORTED = 4; * Start or resume playing audio file. */ Media.prototype.play = function() { - com.phonegap.AudioHandler.startPlayingAudio(this.id, this.src); + PhoneGap.execAsync(null, null, "Media", "startPlayingAudio", [this.id, this.src]); }; /** * Stop playing audio file. */ Media.prototype.stop = function() { - com.phonegap.AudioHandler.stopPlayingAudio(this.id); + return PhoneGap.execAsync(null, null, "Media", "stopPlayingAudio", [this.id]); }; /** * Pause playing audio file. */ Media.prototype.pause = function() { - com.phonegap.AudioHandler.pausePlayingAudio(this.id); + PhoneGap.execAsync(null, null, "Media", "pausePlayingAudio", [this.id]); }; /** @@ -182,21 +167,21 @@ Media.prototype.getDuration = function() { * * @return */ -Media.prototype.getCurrentPosition = function() { - return com.phonegap.AudioHandler.getCurrentPositionAudio(this.id); +Media.prototype.getCurrentPosition = function(success, fail) { + PhoneGap.execAsync(success, fail, "Media", "getCurrentPositionAudio", [this.id]); }; /** * Start recording audio file. */ Media.prototype.startRecord = function() { - com.phonegap.AudioHandler.startRecordingAudio(this.id, this.src); + PhoneGap.execAsync(null, null, "Media", "startRecordingAudio", [this.id, this.src]); }; /** * Stop recording audio file. */ Media.prototype.stopRecord = function() { - com.phonegap.AudioHandler.stopRecordingAudio(this.id); + PhoneGap.execAsync(null, null, "Media", "stopRecordingAudio", [this.id]); }; diff --git a/framework/assets/js/network.js b/framework/assets/js/network.js index 7328b9b0..b3fb1f40 100755 --- a/framework/assets/js/network.js +++ b/framework/assets/js/network.js @@ -1,24 +1,11 @@ -com.phonegap.NetworkManagerProxy = function() { - this.className = "com.phonegap.NetworkManager"; -}; -com.phonegap.NetworkManagerProxy.prototype.isAvailable = function() { - return PhoneGap.exec(this.className, "isAvailable", []); -}; -com.phonegap.NetworkManagerProxy.prototype.isWifiActive = function() { - return PhoneGap.exec(this.className, "isWifiActive", []); -}; -com.phonegap.NetworkManagerProxy.prototype.isReachable = function(uri) { - return PhoneGap.exec(this.className, "isReachable", [uri]); -}; -com.phonegap.NetworkManager = new com.phonegap.NetworkManagerProxy(); /** * This class contains information about any NetworkStatus. * @constructor */ function NetworkStatus() { - this.code = null; - this.message = ""; + //this.code = null; + //this.message = ""; }; NetworkStatus.NOT_REACHABLE = 0; @@ -42,32 +29,27 @@ function Network() { * Called by the geolocation framework when the reachability status has changed. * @param {Reachibility} reachability The current reachability status. */ +// TODO: Callback from native code not implemented for Android Network.prototype.updateReachability = function(reachability) { this.lastReachability = reachability; }; /** - * + * Determine if a URI is reachable over the network. + * @param {Object} uri - * @param {Function} win + * @param {Function} callback * @param {Object} options (isIpAddress:boolean) */ -Network.prototype.isReachable = function(uri, win, options) { - var status = new NetworkStatus(); - if(com.phonegap.NetworkManager.isReachable(uri)) { - if (com.phonegap.NetworkManager.isWifiActive()) { - status.code = NetworkStatus.REACHABLE_VIA_WIFI_NETWORK; - } - else { - status.code = NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK; - } +Network.prototype.isReachable = function(uri, callback, options) { + var isIpAddress = false; + if (options && options.isIpAddress) { + isIpAddress = options.isIpAddress; } - else { - status.code = NetworkStatus.NOT_REACHABLE; - } - win(status); + PhoneGap.execAsync(callback, null, "Network Status", "isReachable", [uri, isIpAddress]); }; PhoneGap.addConstructor(function() { if (typeof navigator.network == "undefined") navigator.network = new Network(); -}); \ No newline at end of file +}); + diff --git a/framework/assets/js/notification.js b/framework/assets/js/notification.js index 5a7c733b..e80c3e40 100644 --- a/framework/assets/js/notification.js +++ b/framework/assets/js/notification.js @@ -2,7 +2,6 @@ * This class provides access to notifications on the device. */ function Notification() { - } /** @@ -34,7 +33,7 @@ Notification.prototype.activityStop = function() { * @param {String} colour The colour of the light. */ Notification.prototype.blink = function(count, colour) { - + }; /** @@ -42,16 +41,17 @@ Notification.prototype.blink = function(count, colour) { * @param {Integer} mills The number of milliseconds to vibrate for. */ Notification.prototype.vibrate = function(mills) { - + PhoneGap.execAsync(null, null, "Device", "vibrate", [mills]); }; /** * Causes the device to beep. + * On Android, the default notification ringtone is played. + * * @param {Integer} count The number of beeps. - * @param {Integer} volume The volume of the beep. */ -Notification.prototype.beep = function(count, volume) { - +Notification.prototype.beep = function(count) { + PhoneGap.execAsync(null, null, "Device", "beep", [count]); }; // TODO: of course on Blackberry and Android there notifications in the UI as well @@ -60,18 +60,3 @@ PhoneGap.addConstructor(function() { if (typeof navigator.notification == "undefined") navigator.notification = new Notification(); }); -Notification.prototype.vibrate = function(mills) -{ - DroidGap.vibrate(mills); -} - -/* - * On the Android, we don't beep, we notify you with your - * notification! We shouldn't keep hammering on this, and should - * review what we want beep to do. - */ - -Notification.prototype.beep = function(count, volume) -{ - DroidGap.beep(count); -} diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base index cd8819c6..dbfe0343 100755 --- a/framework/assets/js/phonegap.js.base +++ b/framework/assets/js/phonegap.js.base @@ -1,10 +1,28 @@ + +/** + * The order of events during page load and PhoneGap startup is as follows: + * + * onDOMContentLoaded Internal event that is received when the web page is loaded and parsed. + * window.onload Body onload event. + * onNativeReady Internal event that indicates the PhoneGap native side is ready. + * onPhoneGapInit Internal event that kicks off creation of all PhoneGap JavaScript objects (runs constructors). + * onPhoneGapReady Internal event fired when all PhoneGap JavaScript objects have been created + * onPhoneGapInfoReady Internal event fired when device properties are available + * onDeviceReady User event fired to indicate that PhoneGap is ready + * onResume User event fired to indicate a start/resume lifecycle event + * + * The only PhoneGap events that user code should register for are: + * onDeviceReady + * onResume + * + * Listeners can be registered as: + * document.addEventListener("deviceready", myDeviceReadyListener, false); + * document.addEventListener("resume", myResumeListener, false); + */ + if (typeof(DeviceInfo) != 'object') DeviceInfo = {}; -var com = {}; - -com.phonegap = {}; - /** * This represents the PhoneGap API itself, and provides a global namespace for accessing * information about the state of PhoneGap. @@ -127,7 +145,7 @@ PhoneGap.available = DeviceInfo.uuid != undefined; * @param {Function} func The function callback you want run once PhoneGap is initialized */ PhoneGap.addConstructor = function(func) { - PhoneGap.onDeviceReady.subscribeOnce(function() { + PhoneGap.onPhoneGapInit.subscribeOnce(function() { try { func(); } catch(e) { @@ -165,6 +183,23 @@ PhoneGap.onDOMContentLoaded = new PhoneGap.Channel('onDOMContentLoaded'); */ PhoneGap.onNativeReady = new PhoneGap.Channel('onNativeReady'); +/** + * onPhoneGapInit channel is fired when the web page is fully loaded and + * PhoneGap native code has been initialized. + */ +PhoneGap.onPhoneGapInit = new PhoneGap.Channel('onPhoneGapInit'); + +/** + * onPhoneGapReady channel is fired when the JS PhoneGap objects have been created. + */ +PhoneGap.onPhoneGapReady = new PhoneGap.Channel('onPhoneGapReady'); + +/** + * onPhoneGapInfoReady channel is fired when the PhoneGap device properties + * has been set. + */ +PhoneGap.onPhoneGapInfoReady = new PhoneGap.Channel('onPhoneGapInfoReady'); + /** * onResume channel is fired when the PhoneGap native code * resumes. @@ -183,29 +218,44 @@ PhoneGap.onPause = new PhoneGap.Channel('onPause'); if (typeof _nativeReady !== 'undefined') { PhoneGap.onNativeReady.fire(); } /** - * onDeviceReady is fired only after both onDOMContentLoaded and - * onNativeReady have fired. + * onDeviceReady is fired only after all PhoneGap objects are created and + * the device properties are set. */ PhoneGap.onDeviceReady = new PhoneGap.Channel('onDeviceReady'); -PhoneGap.onDeviceReady.subscribeOnce(function() { - PhoneGap.JSCallback(); -}); +/** + * Create all PhoneGap objects once page has fully loaded and native side is ready. + */ +PhoneGap.Channel.join(function() { + + // Start listening for XHR callbacks + PhoneGap.JSCallback(); + + // Run PhoneGap constructors + PhoneGap.onPhoneGapInit.fire(); + + // Fire event to notify that all objects are created + PhoneGap.onPhoneGapReady.fire(); + +}, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]); + +/** + * Fire onDeviceReady event once all constructors have run and PhoneGap info has been + * received from native side. + */ PhoneGap.Channel.join(function() { PhoneGap.onDeviceReady.fire(); // Fire the onresume event, since first one happens before JavaScript is loaded PhoneGap.onResume.fire(); -}, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]); - +}, [ PhoneGap.onPhoneGapReady, PhoneGap.onPhoneGapInfoReady]); // Listen for DOMContentLoaded and notify our channel subscribers document.addEventListener('DOMContentLoaded', function() { PhoneGap.onDOMContentLoaded.fire(); }, false); - // Intercept calls to document.addEventListener and watch for deviceready PhoneGap.m_document_addEventListener = document.addEventListener; @@ -222,6 +272,36 @@ document.addEventListener = function(evt, handler, capture) { } }; +/** + * If JSON not included, use our own stringify. (Android 1.6) + * The restriction on ours is that it must be an array of simple types. + * + * @param args + * @return + */ +PhoneGap.stringify = function(args) { + if (typeof JSON == "undefined") { + var s = "["; + for (var i=0; i 0) { + s = s + ","; + } + var type = typeof args[i]; + if ((type == "number") || (type == "boolean")) { + s = s + args[i]; + } + else { + s = s + '"' + args[i] + '"'; + } + } + s = s + "]"; + return s; + } + else { + return JSON.stringify(args); + } +}; + PhoneGap.callbackId = 0; PhoneGap.callbacks = {}; @@ -232,10 +312,11 @@ PhoneGap.callbacks = {}; * @param {String} command Command to be run in PhoneGap, e.g. "ClassName.method" * @param {String[]} [args] Zero or more arguments to pass to the method */ +// TODO: Not used anymore, should be removed. PhoneGap.exec = function(clazz, action, args) { try { var callbackId = 0; - var r = PluginManager.exec(clazz, action, callbackId, JSON.stringify(args), false); + var r = PluginManager.exec(clazz, action, callbackId, this.stringify(args), false); eval("var v="+r+";"); // If status is OK, then return value back to caller @@ -253,22 +334,54 @@ PhoneGap.exec = function(clazz, action, args) { } }; -PhoneGap.execAsync = function(success, fail, clazz, action, args) { +/** + * Execute a PhoneGap command. It is up to the native side whether this action is synch or async. + * The native side can return: + * Synchronous: PluginResult object as a JSON string + * Asynchrounous: Empty string "" + * If async, the native side will PhoneGap.callbackSuccess or PhoneGap.callbackError, + * depending upon the result of the action. + * + * @param {Function} success The success callback + * @param {Function} fail The fail callback + * @param {String} service The name of the service to use + * @param {String} action Action to be run in PhoneGap + * @param {String[]} [args] Zero or more arguments to pass to the method + */ +PhoneGap.execAsync = function(success, fail, service, action, args) { try { - var callbackId = clazz + PhoneGap.callbackId++; - PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; - var r = PluginManager.exec(clazz, action, callbackId, JSON.stringify(args), true); - if (r) { + var callbackId = service + PhoneGap.callbackId++; + if (success || fail) { + PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; + } + + // Note: Device returns string, but for some reason emulator returns object - so convert to string. + var r = ""+PluginManager.exec(service, action, callbackId, this.stringify(args), true); + + // If a result was returned + if (r.length > 0) { eval("var v="+r+";"); // If status is OK, then return value back to caller if (v.status == 0) { + + // If there is a success callback, then call it now with returned value + if (success) { + success(v.message); + delete PhoneGap.callbacks[callbackId]; + } return v.message; } // If error, then display error else { console.log("Error: Status="+r.status+" Message="+v.message); + + // If there is a fail callback, then call it now with returned value + if (fail) { + fail(v.message); + delete PhoneGap.callbacks[callbackId]; + } return null; } } @@ -277,24 +390,44 @@ PhoneGap.execAsync = function(success, fail, clazz, action, args) { } }; +/** + * Called by native code when returning successful result from an action. + * + * @param callbackId + * @param args + */ PhoneGap.callbackSuccess = function(callbackId, args) { - try { - PhoneGap.callbacks[callbackId].success(args.message); + if (PhoneGap.callbacks[callbackId]) { + try { + if (PhoneGap.callbacks[callbackId].success) { + PhoneGap.callbacks[callbackId].success(args.message); + } + } + catch (e) { + console.log("Error in success callback: "+callbackId+" = "+e); + } + delete PhoneGap.callbacks[callbackId]; } - catch (e) { - console.log("Error in success callback: "+callbackId+" = "+e); - } - delete PhoneGap.callbacks[callbackId]; }; +/** + * Called by native code when returning error result from an action. + * + * @param callbackId + * @param args + */ PhoneGap.callbackError = function(callbackId, args) { - try { - PhoneGap.callbacks[callbackId].fail(args.message); + if (PhoneGap.callbacks[callbackId]) { + try { + if (PhoneGap.callbacks[callbackId].fail) { + PhoneGap.callbacks[callbackId].fail(args.message); + } + } + catch (e) { + console.log("Error in error callback: "+callbackId+" = "+e); + } + delete PhoneGap.callbacks[callbackId]; } - catch (e) { - console.log("Error in error callback: "+callbackId+" = "+e); - } - delete PhoneGap.callbacks[callbackId]; }; @@ -305,6 +438,7 @@ PhoneGap.callbackError = function(callbackId, args) { * url, which will be turned into a dictionary on the other end. * @private */ +// TODO: Is this used? PhoneGap.run_command = function() { if (!PhoneGap.available || !PhoneGap.queue.ready) return; @@ -344,6 +478,8 @@ PhoneGap.run_command = function() { }; /** + * This is only for Android. + * * Internal function that uses XHR to call into PhoneGap Java code and retrieve * any JavaScript code that needs to be run. This is used for callbacks from * Java to JavaScript. diff --git a/framework/assets/js/storage.js b/framework/assets/js/storage.js index 7930f3cf..ba234035 100644 --- a/framework/assets/js/storage.js +++ b/framework/assets/js/storage.js @@ -1,13 +1,3 @@ -com.phonegap.StorageProxy = function() { - this.className = "com.phonegap.Storage"; -}; -com.phonegap.StorageProxy.prototype.executeSql = function(query, params, id) { - return PhoneGap.exec(this.className, "executeSql", [query, params, id]); -}; -com.phonegap.StorageProxy.prototype.openDatabase = function(name, version, display_name, size) { - return PhoneGap.exec(this.className, "openDatabase", [name, version, display_name, size]); -}; -com.phonegap.Storage = new com.phonegap.StorageProxy(); /* * This is purely for the Android 1.5/1.6 HTML 5 Storage @@ -53,7 +43,7 @@ var Tx = function() { }; Tx.prototype.executeSql = function(query, params, win, fail) { - com.phonegap.Storage.executeSql(query, params, this.id); + PhoneGap.execAsync(null, null, "Storage", "executeSql", [query, params, this.id]); tx.win = win; tx.fail = fail; }; @@ -72,7 +62,7 @@ Rows.prototype.item = function(row_id) { }; var dbSetup = function(name, version, display_name, size) { - com.phonegap.Storage.openDatabase(name, version, display_name, size) + PhoneGap.execAsync(null, null, "Storage", "openDatabase", [name, version, display_name, size]); db_object = new DatabaseShell(); return db_object; }; diff --git a/framework/src/com/phonegap/AccelListener.java b/framework/src/com/phonegap/AccelListener.java index f0000849..4f4cae62 100755 --- a/framework/src/com/phonegap/AccelListener.java +++ b/framework/src/com/phonegap/AccelListener.java @@ -98,9 +98,25 @@ public class AccelListener implements SensorEventListener, Plugin{ return new PluginResult(status, 0); } else if (action.equals("getAcceleration")) { - // Start if not already running - if (this.status == AccelListener.STOPPED) { - this.start(); + // 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); + } } JSONObject r = new JSONObject(); r.put("x", this.x); @@ -133,6 +149,28 @@ public class AccelListener implements SensorEventListener, Plugin{ } } + /** + * 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 + if (this.status == RUNNING) { + return true; + } + } + else if (action.equals("getTimeout")) { + return true; + } + return false; + } + /** * Called when the system is about to start resuming a previous activity. */ @@ -282,9 +320,6 @@ public class AccelListener implements SensorEventListener, Plugin{ * @param status */ private void setStatus(int status) { - if (this.status != status) { - ctx.sendJavascript("com.phonegap.AccelListener.onStatus("+status+")"); - } this.status = status; } diff --git a/framework/src/com/phonegap/AudioHandler.java b/framework/src/com/phonegap/AudioHandler.java index 79e2349e..e2ca4b83 100755 --- a/framework/src/com/phonegap/AudioHandler.java +++ b/framework/src/com/phonegap/AudioHandler.java @@ -101,6 +101,22 @@ public class AudioHandler implements Plugin { 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("getCurrentPositionAudio")) { + return true; + } + else if (action.equals("getDurationAudio")) { + return true; + } + return false; + } /** * Called when the system is about to start resuming a previous activity. diff --git a/framework/src/com/phonegap/AudioPlayer.java b/framework/src/com/phonegap/AudioPlayer.java index c3360aed..36aa62f9 100755 --- a/framework/src/com/phonegap/AudioPlayer.java +++ b/framework/src/com/phonegap/AudioPlayer.java @@ -30,7 +30,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On // AudioPlayer message ids private static int MEDIA_STATE = 1; private static int MEDIA_DURATION = 2; - private static int MEDIA_ERROR = 3; + private static int MEDIA_ERROR = 9; // AudioPlayer error codes private static int MEDIA_ERROR_PLAY_MODE_SET = 1; @@ -109,8 +109,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On try { this.recorder.prepare(); this.recorder.start(); - this.state = MEDIA_RUNNING; - this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); + this.setState(MEDIA_RUNNING); return; } catch (IllegalStateException e) { e.printStackTrace(); @@ -144,11 +143,8 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On if (this.recorder != null) { try{ if (this.state == MEDIA_RUNNING) { - this.state = MEDIA_STOPPED; this.recorder.stop(); - - // Send status notification to JavaScript - this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); + this.setState(MEDIA_STOPPED); } this.moveFile(this.audioFile); } @@ -205,10 +201,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On this.duration = this.mPlayer.getDuration(); } this.mPlayer.setOnPreparedListener(this); - this.state = MEDIA_STARTING; - - // Send status notification to JavaScript - this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); + this.setState(MEDIA_STARTING); } catch (Exception e) { e.printStackTrace(); @@ -222,10 +215,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On // If player has been paused, then resume playback if ((this.state == MEDIA_PAUSED) || (this.state == MEDIA_STARTING)) { this.mPlayer.start(); - this.state = MEDIA_RUNNING; - - // Send status notification to JavaScript - this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); + this.setState(MEDIA_RUNNING); } else { System.out.println("AudioPlayer Error: startPlaying() called during invalid state: "+this.state); @@ -242,10 +232,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On // If playing, then pause if (this.state == MEDIA_RUNNING) { this.mPlayer.pause(); - this.state = MEDIA_PAUSED; - - // Send status notification to JavaScript - this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); + this.setState(MEDIA_PAUSED); } else { System.out.println("AudioPlayer Error: pausePlaying() called during invalid state: "+this.state); @@ -258,11 +245,8 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On */ public void stopPlaying() { if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) { - this.state = MEDIA_STOPPED; this.mPlayer.stop(); - - // Send status notification to JavaScript - this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); + this.setState(MEDIA_STOPPED); } else { System.out.println("AudioPlayer Error: stopPlaying() called during invalid state: "+this.state); @@ -276,10 +260,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * @param mPlayer The MediaPlayer that reached the end of the file */ public void onCompletion(MediaPlayer mPlayer) { - this.state = MEDIA_STOPPED; - - // Send status notification to JavaScript - this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); + this.setState(MEDIA_STOPPED); } /** @@ -359,10 +340,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On this.mPlayer.start(); // Set player init flag - this.state = MEDIA_RUNNING; - - // Send status notification to JavaScript - this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); + this.setState(MEDIA_RUNNING); } // Save off duration @@ -392,5 +370,19 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On // Send error notification to JavaScript this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_ERROR+", "+arg1+");"); return false; - } + } + + /** + * Set the state and send it to JavaScript. + * + * @param state + */ + private void setState(int state) { + if (this.state != state) { + this.handler.ctx.sendJavascript("PhoneGap.Media.onStatus('" + this.id + "', "+MEDIA_STATE+", "+this.state+");"); + } + + this.state = state; + } + } diff --git a/framework/src/com/phonegap/CameraLauncher.java b/framework/src/com/phonegap/CameraLauncher.java index aa838763..cb05721b 100755 --- a/framework/src/com/phonegap/CameraLauncher.java +++ b/framework/src/com/phonegap/CameraLauncher.java @@ -2,6 +2,7 @@ package com.phonegap; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; @@ -10,18 +11,17 @@ import org.json.JSONArray; import org.json.JSONException; import com.phonegap.api.Plugin; -import com.phonegap.api.PluginManager; import com.phonegap.api.PluginResult; import android.app.Activity; import android.content.ContentValues; -import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.net.Uri; import android.os.Environment; import android.webkit.WebView; +import android.provider.MediaStore; /** * This class launches the camera view, allows the user to take a picture, closes the camera view, @@ -30,12 +30,18 @@ import android.webkit.WebView; */ public class CameraLauncher implements Plugin { - WebView webView; // WebView object - DroidGap ctx; // DroidGap object + private static final int DATA_URL = 0; // Return base64 encoded string + private static final int FILE_URI = 1; // Return file uri (content://media/external/images/media/2 for Android) + + private static final int PHOTOLIBRARY = 0; // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) + private static final int CAMERA = 1; // Take picture from camera + private static final int SAVEDPHOTOALBUM = 2; // Choose image from picture library (same as PHOTOLIBRARY for Android) + + WebView webView; // WebView object + DroidGap ctx; // DroidGap object - private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) - private Uri imageUri; // Uri of captured image - private boolean base64 = true; + private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) + private Uri imageUri; // Uri of captured image /** * Constructor. @@ -75,11 +81,21 @@ public class CameraLauncher implements Plugin { String result = ""; try { - if (action.equals("setBase64")) { - this.setBase64(args.getBoolean(0)); - } - else if (action.equals("takePicture")) { - this.takePicture(args.getInt(0)); + if (action.equals("takePicture")) { + int destType = DATA_URL; + if (args.length() > 1) { + destType = args.getInt(1); + } + int srcType = CAMERA; + if (args.length() > 2) { + srcType = args.getInt(2); + } + if (srcType == CAMERA) { + this.takePicture(args.getInt(0), destType); + } + else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) { + this.getImage(srcType, destType); + } } return new PluginResult(status, result); } catch (JSONException e) { @@ -88,6 +104,16 @@ public class CameraLauncher implements Plugin { } } + /** + * 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) { + return false; + } + /** * Called when the system is about to start resuming a previous activity. */ @@ -112,27 +138,20 @@ public class CameraLauncher implements Plugin { //-------------------------------------------------------------------------- /** - * Set the type of data to return. The data can either be returned - * as a base64 string or a URI that points to the file. + * Take a picture with the camera. + * When an image is captured or the camera view is cancelled, the result is returned + * in DroidGap.onActivityResult, which forwards the result to this.onActivityResult. + * + * The image can either be returned as a base64 string or a URI that points to the file. * To display base64 string in an img tag, set the source to: * img.src="data:image/jpeg;base64,"+result; * or to display URI in an img tag * img.src=result; * - * @param b T=return base64 string (default), F=return URI - */ - public void setBase64(boolean b) { - this.base64 = b; - } - - /** - * Take a picture with the camera. - * When an image is captured or the camera view is cancelled, the result is returned - * in DroidGap.onActivityResult, which forwards the result to this.onActivityResult. - * * @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) + * @param returnType Set the type of image to return. */ - public void takePicture(int quality) { + public void takePicture(int quality, int returnType) { this.mQuality = quality; // Display camera @@ -144,7 +163,22 @@ public class CameraLauncher implements Plugin { intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo)); this.imageUri = Uri.fromFile(photo); - this.ctx.startActivityForResult((Plugin) this, intent); + this.ctx.startActivityForResult((Plugin) this, intent, (CAMERA+1)*16 + returnType+1); + } + + /** + * Get image from photo library. + * + * @param returnType + */ + // TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do! + public void getImage(int srcType, int returnType) { + Intent intent = new Intent(); + intent.setType("image/*"); + intent.setAction(Intent.ACTION_GET_CONTENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + this.ctx.startActivityForResult((Plugin) this, Intent.createChooser(intent, + new String("Get Picture")), (srcType+1)*16 + returnType + 1); } /** @@ -156,61 +190,98 @@ public class CameraLauncher implements Plugin { * @param intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). */ public void onActivityResult(int requestCode, int resultCode, Intent intent) { + + // Get src and dest types from request code + int srcType = (requestCode/16) - 1; + int destType = (requestCode % 16) - 1; - // If image available - if (resultCode == Activity.RESULT_OK) { - try { - // Read in bitmap of captured image - Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); - + // If CAMERA + if (srcType == CAMERA) { + + // If image available + if (resultCode == Activity.RESULT_OK) { + try { + // Read in bitmap of captured image + Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); + + // If sending base64 image back + if (destType == DATA_URL) { + this.processPicture(bitmap); + } + + // If sending filename back + else if (destType == FILE_URI){ + // Create entry in media store for image + // (Don't use insertImage() because it uses default compression setting of 50 - no way to change it) + ContentValues values = new ContentValues(); + values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); + Uri uri = null; + try { + uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + } catch (UnsupportedOperationException e) { + System.out.println("Can't write to external media storage."); + try { + uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values); + } catch (UnsupportedOperationException ex) { + System.out.println("Can't write to internal media storage."); + this.failPicture("Error capturing image - no media storage found."); + return; + } + } + + // Add compressed version of captured image to returned media store Uri + OutputStream os = this.ctx.getContentResolver().openOutputStream(uri); + bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os); + os.close(); + + // Send Uri back to JavaScript for viewing image + this.ctx.sendJavascript("navigator.camera.success('" + uri.toString() + "');"); + } + } catch (IOException e) { + e.printStackTrace(); + this.failPicture("Error capturing image."); + } + } + + // If cancelled + else if (resultCode == Activity.RESULT_CANCELED) { + this.failPicture("Camera cancelled."); + } + + // If something else + else { + this.failPicture("Did not complete!"); + } + } + + // If retrieving photo from library + else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) { + if (resultCode == Activity.RESULT_OK) { + Uri uri = intent.getData(); + android.content.ContentResolver resolver = this.ctx.getContentResolver(); // If sending base64 image back - if (this.base64) { - this.processPicture(bitmap); + if (destType == DATA_URL) { + try { + Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri)); + this.processPicture(bitmap); + } catch (FileNotFoundException e) { + e.printStackTrace(); + this.failPicture("Error retrieving image."); + } } // If sending filename back - else { - // Create entry in media store for image - // (Don't use insertImage() because it uses default compression setting of 50 - no way to change it) - ContentValues values = new ContentValues(); - values.put(android.provider.MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); - Uri uri = null; - try { - uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); - } catch (UnsupportedOperationException e) { - System.out.println("Can't write to external media storage."); - try { - uri = this.ctx.getContentResolver().insert(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, values); - } catch (UnsupportedOperationException ex) { - System.out.println("Can't write to internal media storage."); - this.failPicture("Error capturing image - no media storage found."); - return; - } - } - - // Add compressed version of captured image to returned media store Uri - OutputStream os = this.ctx.getContentResolver().openOutputStream(uri); - bitmap.compress(Bitmap.CompressFormat.JPEG, this.mQuality, os); - os.close(); - - // Send Uri back to JavaScript for viewing image - this.ctx.sendJavascript("navigator.camera.success('" + uri.toString() + "');"); + else if (destType == FILE_URI) { + this.ctx.sendJavascript("navigator.camera.success('" + uri + "');"); } - } catch (IOException e) { - e.printStackTrace(); - this.failPicture("Error capturing image."); - } + } + else if (resultCode == Activity.RESULT_CANCELED) { + this.failPicture("Selection cancelled."); + } + else { + this.failPicture("Selection did not complete!"); + } } - - // If cancelled - else if (resultCode == Activity.RESULT_CANCELED) { - this.failPicture("Camera cancelled."); - } - - // If something else - else { - this.failPicture("Did not complete!"); - } } /** diff --git a/framework/src/com/phonegap/CompassListener.java b/framework/src/com/phonegap/CompassListener.java index 35f8ad09..6ff6efe1 100755 --- a/framework/src/com/phonegap/CompassListener.java +++ b/framework/src/com/phonegap/CompassListener.java @@ -44,7 +44,7 @@ public class CompassListener implements SensorEventListener, Plugin{ */ public CompassListener() { this.timeStamp = 0; - this.status = CompassListener.STOPPED; + this.setStatus(CompassListener.STOPPED); } /** @@ -91,6 +91,26 @@ public class CompassListener implements SensorEventListener, Plugin{ return new PluginResult(status, i); } else if (action.equals("getHeading")) { + // If not running, then this is an async call, so don't worry about waiting + if (this.status != RUNNING) { + int r = this.start(); + if (r == ERROR_FAILED_TO_START) { + return new PluginResult(PluginResult.Status.IO_EXCEPTION, 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); + } + } float f = this.getHeading(); return new PluginResult(status, f); } @@ -108,6 +128,28 @@ public class CompassListener implements SensorEventListener, Plugin{ } } + /** + * 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("getHeading")) { + // Can only return value if RUNNING + if (this.status == RUNNING) { + return true; + } + } + else if (action.equals("getTimeout")) { + return true; + } + return false; + } + /** * Called when the system is about to start resuming a previous activity. */ @@ -162,13 +204,13 @@ public class CompassListener implements SensorEventListener, Plugin{ if (list.size() > 0) { this.mSensor = list.get(0); this.sensorManager.registerListener(this, this.mSensor, SensorManager.SENSOR_DELAY_NORMAL); - this.status = CompassListener.STARTING; this.lastAccessTime = System.currentTimeMillis(); + this.setStatus(CompassListener.STARTING); } // If error, then set status to error else { - this.status = CompassListener.ERROR_FAILED_TO_START; + this.setStatus(CompassListener.ERROR_FAILED_TO_START); } return this.status; @@ -181,7 +223,7 @@ public class CompassListener implements SensorEventListener, Plugin{ if (this.status != CompassListener.STOPPED) { this.sensorManager.unregisterListener(this); } - this.status = CompassListener.STOPPED; + this.setStatus(CompassListener.STOPPED); } @@ -202,7 +244,7 @@ public class CompassListener implements SensorEventListener, Plugin{ // Save heading this.timeStamp = System.currentTimeMillis(); this.heading = heading; - this.status = CompassListener.RUNNING; + this.setStatus(CompassListener.RUNNING); // If heading hasn't been read for TIMEOUT time, then turn off compass sensor to save power if ((this.timeStamp - this.lastAccessTime) > this.TIMEOUT) { @@ -246,4 +288,13 @@ public class CompassListener implements SensorEventListener, Plugin{ public long getTimeout() { return this.TIMEOUT; } + + /** + * Set the status and send it to JavaScript. + * @param status + */ + private void setStatus(int status) { + this.status = status; + } + } diff --git a/framework/src/com/phonegap/ContactManager.java b/framework/src/com/phonegap/ContactManager.java index 01c6ef18..8924ccf9 100755 --- a/framework/src/com/phonegap/ContactManager.java +++ b/framework/src/com/phonegap/ContactManager.java @@ -83,6 +83,16 @@ public class ContactManager implements Plugin { } } + /** + * 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) { + return false; + } + /** * Called when the system is about to start resuming a previous activity. */ @@ -246,14 +256,14 @@ public class ContactManager implements Plugin { // Code for backwards compatibility with the OLD Contacts API if (all) { - this.ctx.sendJavascript("navigator.ContactManager.droidAddContact('" + name + "','" + phoneNumber + "','" + email +"');"); + this.ctx.sendJavascript("navigator.contacts.droidFoundContact('" + name + "','" + phoneNumber + "','" + email +"');"); } else { this.ctx.sendJavascript("navigator.contacts.droidFoundContact('" + name + "','" + phoneNumber + "','" + email +"');"); } } while (cur.moveToNext()); if (all) { - this.ctx.sendJavascript("navigator.ContactManager.droidDone();"); + this.ctx.sendJavascript("navigator.contacts.droidDone();"); } else { this.ctx.sendJavascript("navigator.contacts.droidDone();"); @@ -262,7 +272,7 @@ public class ContactManager implements Plugin { else { if (all) { - this.ctx.sendJavascript("navigator.ContactManager.fail();"); + this.ctx.sendJavascript("navigator.contacts.fail('Error');"); } else { this.ctx.sendJavascript("navigator.contacts.fail('None found!');"); @@ -289,7 +299,7 @@ public class ContactManager implements Plugin { if(data != null) { data.email = email; - this.ctx.sendJavascript("navigator.Contacts.droidFoundContact('" + data.name + "','" + data.phone + "','" + data.email +"');"); + this.ctx.sendJavascript("navigator.contacts.droidFoundContact('" + data.name + "','" + data.phone + "','" + data.email +"');"); } } while (cur.moveToNext()); this.ctx.sendJavascript("navigator.contacts.droidDoneContacts();"); diff --git a/framework/src/com/phonegap/CryptoHandler.java b/framework/src/com/phonegap/CryptoHandler.java index 9c68a3c1..96ca274a 100755 --- a/framework/src/com/phonegap/CryptoHandler.java +++ b/framework/src/com/phonegap/CryptoHandler.java @@ -64,6 +64,16 @@ public class CryptoHandler implements Plugin { } } + /** + * 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) { + return false; + } + /** * Called when the system is about to start resuming a previous activity. */ diff --git a/framework/src/com/phonegap/Device.java b/framework/src/com/phonegap/Device.java index 922fd01f..babd09fe 100644 --- a/framework/src/com/phonegap/Device.java +++ b/framework/src/com/phonegap/Device.java @@ -24,7 +24,15 @@ package com.phonegap; import java.util.TimeZone; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import com.phonegap.api.Plugin; +import com.phonegap.api.PluginResult; + import android.content.Context; +import android.content.Intent; import android.net.Uri; import android.os.Vibrator; import android.provider.Settings; @@ -33,76 +41,210 @@ import android.webkit.WebView; import android.media.Ringtone; import android.media.RingtoneManager; -public class Device{ +public class Device implements Plugin { - private static final String LOG_TAG = "PhoneGap"; - /* - * UUID, version and availability - */ - public boolean droid = true; - public static String version = "0.91"; - public static String platform = "Android"; - public static String uuid; - private Context mCtx; - private WebView mAppView; - AudioPlayer audio; + public static String phonegapVersion = "pre-0.92 EDGE"; // PhoneGap version + public static String platform = "Android"; // Device OS + public static String uuid; // Device UUID + private DroidGap ctx; // DroidGap object + @SuppressWarnings("unused") + private WebView webView; // Webview object - public Device(WebView appView, Context ctx) { - this.mCtx = ctx; - this.mAppView = appView; - uuid = getUuid(); + /** + * Constructor. + */ + public Device() { } - public void beep(long pattern) - { + /** + * Sets the context of the Command. This can then be used to do things like + * get file paths associated with the Activity. + * + * @param ctx The context of the main Activity. + */ + public void setContext(DroidGap ctx) { + this.ctx = ctx; + Device.uuid = getUuid(); + } + + /** + * Sets the main View of the application, this is the WebView within which + * a PhoneGap app runs. + * + * @param webView The PhoneGap WebView + */ + public void setView(WebView webView) { + this.webView = webView; + } + + /** + * Executes the request and returns CommandResult. + * + * @param action The command to execute. + * @param args JSONArry of arguments for the command. + * @return A CommandResult object with a status and message. + */ + public PluginResult execute(String action, JSONArray args) { + PluginResult.Status status = PluginResult.Status.OK; + String result = ""; + + try { + if (action.equals("getDeviceInfo")) { + JSONObject r = new JSONObject(); + r.put("uuid", Device.uuid); + r.put("version", this.getOSVersion()); + r.put("platform", Device.platform); + r.put("name", this.getProductName()); + r.put("phonegap", Device.phonegapVersion); + //JSONObject pg = new JSONObject(); + //pg.put("version", Device.phonegapVersion); + //r.put("phonegap", pg); + return new PluginResult(status, r); + } + else if (action.equals("beep")) { + this.beep(args.getLong(0)); + } + else if (action.equals("vibrate")) { + this.vibrate(args.getLong(0)); + } + 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("getDeviceInfo")) { + return true; + } + return false; + } + + /** + * Called when the system is about to start resuming a previous activity. + */ + public void onPause() { + } + + /** + * Called when the activity will start interacting with the user. + */ + public void onResume() { + } + + /** + * Called when the activity is to be shut down. + * Stop listener. + */ + public void onDestroy() { + } + + /** + * Called when an activity you launched exits, giving you the requestCode you started it with, + * the resultCode it returned, and any additional data from it. + * + * @param requestCode The request code originally supplied to startActivityForResult(), + * allowing you to identify who this result came from. + * @param resultCode The integer result code returned by the child activity through its setResult(). + * @param data An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + */ + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + } + + //-------------------------------------------------------------------------- + // LOCAL METHODS + //-------------------------------------------------------------------------- + + /** + * Beep plays the default notification ringtone. + * + * @param count Number of times to play notification + */ + public void beep(long count) { Uri ringtone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); - Ringtone notification = RingtoneManager.getRingtone(mCtx, ringtone); - if (notification != null) { // This will be the case when the phone is set to silent for example - for (long i = 0; i < pattern; ++i) - { + Ringtone notification = RingtoneManager.getRingtone(this.ctx, ringtone); + + // If phone is not set to silent mode + if (notification != null) { + for (long i = 0; i < count; ++i) { notification.play(); + long timeout = 5000; + while (notification.isPlaying() && (timeout > 0)) { + timeout = timeout - 100; + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + } } } } - public void vibrate(long pattern){ + /** + * Vibrates the device for the specified amount of time. + * + * @param time Time to vibrate in ms. + */ + public void vibrate(long time){ // Start the vibration, 0 defaults to half a second. - if (pattern == 0) - pattern = 500; - Vibrator vibrator = (Vibrator) mCtx.getSystemService(Context.VIBRATOR_SERVICE); - vibrator.vibrate(pattern); + if (time == 0) { + time = 500; + } + Vibrator vibrator = (Vibrator) this.ctx.getSystemService(Context.VIBRATOR_SERVICE); + vibrator.vibrate(time); } - public String getPlatform() - { - return this.platform; + /** + * Get the OS name. + * + * @return + */ + public String getPlatform() { + return Device.platform; } - public String getUuid() - { - //TelephonyManager operator = (TelephonyManager) mCtx.getSystemService(Context.TELEPHONY_SERVICE); - //String uuid = operator.getDeviceId(); - String uuid = Settings.Secure.getString(mCtx.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID); + /** + * Get the device's Universally Unique Identifier (UUID). + * + * @return + */ + public String getUuid() { + String uuid = Settings.Secure.getString(this.ctx.getContentResolver(), android.provider.Settings.Secure.ANDROID_ID); return uuid; } + /** + * Get the PhoneGap version. + * + * @return + */ + public String getPhonegapVersion() { + return Device.phonegapVersion; + } + public String getLine1Number(){ - TelephonyManager operator = (TelephonyManager)mCtx.getSystemService(Context.TELEPHONY_SERVICE); + TelephonyManager operator = (TelephonyManager)this.ctx.getSystemService(Context.TELEPHONY_SERVICE); return operator.getLine1Number(); } public String getDeviceId(){ - TelephonyManager operator = (TelephonyManager)mCtx.getSystemService(Context.TELEPHONY_SERVICE); + TelephonyManager operator = (TelephonyManager)this.ctx.getSystemService(Context.TELEPHONY_SERVICE); return operator.getDeviceId(); } public String getSimSerialNumber(){ - TelephonyManager operator = (TelephonyManager)mCtx.getSystemService(Context.TELEPHONY_SERVICE); + TelephonyManager operator = (TelephonyManager)this.ctx.getSystemService(Context.TELEPHONY_SERVICE); return operator.getSimSerialNumber(); } public String getSubscriberId(){ - TelephonyManager operator = (TelephonyManager)mCtx.getSystemService(Context.TELEPHONY_SERVICE); + TelephonyManager operator = (TelephonyManager)this.ctx.getSystemService(Context.TELEPHONY_SERVICE); return operator.getSubscriberId(); } @@ -116,21 +258,23 @@ public class Device{ String productname = android.os.Build.PRODUCT; return productname; } - public String getOSVersion() - { + + /** + * Get the OS version. + * + * @return + */ + public String getOSVersion() { String osversion = android.os.Build.VERSION.RELEASE; return osversion; } + public String getSDKVersion() { String sdkversion = android.os.Build.VERSION.SDK; return sdkversion; } - public String getVersion() - { - return version; - } public String getTimeZoneID() { TimeZone tz = TimeZone.getDefault(); diff --git a/framework/src/com/phonegap/DirectoryManager.java b/framework/src/com/phonegap/DirectoryManager.java index 2b411a6a..6ee0d46e 100644 --- a/framework/src/com/phonegap/DirectoryManager.java +++ b/framework/src/com/phonegap/DirectoryManager.java @@ -6,26 +6,47 @@ import android.os.Environment; import android.os.StatFs; import android.util.Log; +/** + * This class provides file directory utilities. + * All file operations are performed on the SD card. + * + * It is used by the FileUtils class. + */ public class DirectoryManager { - protected static boolean testFileExists (String name){ + /** + * Determine if a file or directory exists. + * + * @param name The name of the file to check. + * @return T=exists, F=not found + */ + protected static boolean testFileExists(String name) { boolean status; - if ((testSaveLocationExists())&&(!name.equals(""))){ + + // If SD card exists + if ((testSaveLocationExists()) && (!name.equals(""))) { File path = Environment.getExternalStorageDirectory(); File newPath = constructFilePaths(path.toString(), name); status = newPath.exists(); - }else{ + } + + // If no SD card + else{ status = false; } return status; } - protected static long getFreeDiskSpace(){ - /* - * gets the available SD card free space or returns -1 if the SD card is not mounted. - */ + /** + * Get the free disk space on the SD card + * + * @return Size in KB or -1 if not available + */ + protected static long getFreeDiskSpace() { String status = Environment.getExternalStorageState(); long freeSpace = 0; + + // If SD card exists if (status.equals(Environment.MEDIA_MOUNTED)) { try { File path = Environment.getExternalStorageDirectory(); @@ -34,44 +55,82 @@ public class DirectoryManager { long availableBlocks = stat.getAvailableBlocks(); freeSpace = availableBlocks*blockSize/1024; } catch (Exception e) {e.printStackTrace(); } - } else { return -1; } + } + + // If no SD card, then return -1 + else { + return -1; + } + return (freeSpace); } - protected static boolean createDirectory(String directoryName){ + /** + * Create directory on SD card. + * + * @param directoryName The name of the directory to create. + * @return T=successful, F=failed + */ + protected static boolean createDirectory(String directoryName) { boolean status; - if ((testSaveLocationExists())&&(!directoryName.equals(""))){ + + // Make sure SD card exists + if ((testSaveLocationExists()) && (!directoryName.equals(""))) { File path = Environment.getExternalStorageDirectory(); File newPath = constructFilePaths(path.toString(), directoryName); status = newPath.mkdir(); status = true; - }else + } + + // If no SD card or invalid dir name + else { status = false; + } return status; } - protected static boolean testSaveLocationExists(){ + /** + * Determine if SD card exists. + * + * @return T=exists, F=not found + */ + protected static boolean testSaveLocationExists() { String sDCardStatus = Environment.getExternalStorageState(); boolean status; - if (sDCardStatus.equals(Environment.MEDIA_MOUNTED)){ + + // If SD card is mounted + if (sDCardStatus.equals(Environment.MEDIA_MOUNTED)) { status = true; - }else + } + + // If no SD card + else { status = false; + } return status; } - protected static boolean deleteDirectory(String fileName){ + /** + * Delete directory. + * + * @param fileName The name of the directory to delete + * @return T=deleted, F=could not delete + */ + protected static boolean deleteDirectory(String fileName) { boolean status; SecurityManager checker = new SecurityManager(); - if ((testSaveLocationExists())&&(!fileName.equals(""))){ - + // Make sure SD card exists + if ((testSaveLocationExists()) && (!fileName.equals(""))) { File path = Environment.getExternalStorageDirectory(); File newPath = constructFilePaths(path.toString(), fileName); checker.checkDelete(newPath.toString()); - if(newPath.isDirectory()){ + + // If dir to delete is really a directory + if (newPath.isDirectory()) { String[] listfile = newPath.list(); - // delete all files within the specified directory and then delete the directory + + // Delete all files within the specified directory and then delete the directory try{ for (int i=0; i < listfile.length; i++){ File deletedFile = new File (newPath.toString()+"/"+listfile[i].toString()); @@ -80,27 +139,43 @@ public class DirectoryManager { newPath.delete(); Log.i("DirectoryManager deleteDirectory", fileName); status = true; - }catch (Exception e){ + } + catch (Exception e){ e.printStackTrace(); status = false; } - - }else + } + + // If dir not a directory, then error + else { status = false; - }else + } + } + + // If no SD card + else { status = false; + } return status; } - protected static boolean deleteFile(String fileName){ + /** + * Delete file. + * + * @param fileName The name of the file to delete + * @return T=deleted, F=not deleted + */ + protected static boolean deleteFile(String fileName) { boolean status; SecurityManager checker = new SecurityManager(); - if ((testSaveLocationExists())&&(!fileName.equals(""))){ - + // Make sure SD card exists + if ((testSaveLocationExists()) && (!fileName.equals(""))) { File path = Environment.getExternalStorageDirectory(); File newPath = constructFilePaths(path.toString(), fileName); checker.checkDelete(newPath.toString()); + + // If file to delete is really a file if (newPath.isFile()){ try { Log.i("DirectoryManager deleteFile", fileName); @@ -110,14 +185,28 @@ public class DirectoryManager { se.printStackTrace(); status = false; } - }else + } + // If not a file, then error + else { status = false; - }else + } + } + + // If no SD card + else { status = false; + } return status; } - private static File constructFilePaths (String file1, String file2){ + /** + * Create a new file object from two file paths. + * + * @param file1 Base file path + * @param file2 Remaining file path + * @return File object + */ + private static File constructFilePaths (String file1, String file2) { File newPath; newPath = new File(file1+"/"+file2); return newPath; diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 52968e72..70d15328 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -25,7 +25,6 @@ package com.phonegap; import com.phonegap.api.Plugin; -import java.util.HashMap; import com.phonegap.api.PluginManager; import android.app.Activity; @@ -83,8 +82,6 @@ public class DroidGap extends Activity { protected Boolean loadInWebView = false; private LinearLayout root; - private Device gap; - private FileUtils fs; private BrowserKey mKey; public CallbackServer callbackServer; private PluginManager pluginManager; @@ -92,10 +89,8 @@ public class DroidGap extends Activity { private String url; // The initial URL for our app private String baseUrl; // The base of the initial URL for our app - // Variables to manage ActivityResultCallbacks - private int activityResultCallbackCounter = 1000; - private HashMap activityResultCallbacks = new HashMap(); - + private Plugin activityResultCallback = null; // Plugin to call when activity result is received + /** * Called when the activity is first created. * @@ -252,6 +247,16 @@ public class DroidGap extends Activity { } } + /** + * Add a class that implements a service. + * + * @param serviceType + * @param className + */ + public void addService(String serviceType, String className) { + this.pluginManager.addService(serviceType, className); + } + /** * Bind PhoneGap objects to JavaScript. * @@ -260,15 +265,11 @@ public class DroidGap extends Activity { private void bindBrowser(WebView appView) { this.callbackServer = new CallbackServer(); this.pluginManager = new PluginManager(appView, this); - this.gap = new Device(appView, this); - this.fs = new FileUtils(appView, this); this.mKey = new BrowserKey(appView, this); // This creates the new javascript interfaces for PhoneGap appView.addJavascriptInterface(this.pluginManager, "PluginManager"); - appView.addJavascriptInterface(this.gap, "DroidGap"); - appView.addJavascriptInterface(this.fs, "FileUtil"); appView.addJavascriptInterface(this.mKey, "BackButton"); appView.addJavascriptInterface(this.callbackServer, "CallbackServer"); @@ -281,9 +282,22 @@ public class DroidGap extends Activity { Storage cupcakeStorage = (Storage)this.pluginManager.addPlugin("com.phonegap.Storage"); cupcakeStorage.setStorage(appPackage); - this.pluginManager.addPlugin("com.phonegap.GeoBroker"); } + + this.addService("Geolocation", "com.phonegap.GeoBroker"); + this.addService("Device", "com.phonegap.Device"); + this.addService("Accelerometer", "com.phonegap.AccelListener"); + this.addService("Compass", "com.phonegap.CompassListener"); + this.addService("Media", "com.phonegap.AudioHandler"); + this.addService("Camera", "com.phonegap.CameraLauncher"); + this.addService("Contacts", "com.phonegap.ContactManager"); + this.addService("Crypto", "com.phonegap.CryptoHandler"); + this.addService("File", "com.phonegap.FileUtils"); + this.addService("Location", "com.phonegap.GeoBroker"); + this.addService("Network Status", "com.phonegap.NetworkManager"); + this.addService("Storage", "com.phonegap.Storage"); + this.addService("Temperature", "com.phonegap.TempListener"); } /** @@ -658,13 +672,11 @@ public class DroidGap extends Activity { * * @param command The command object * @param intent The intent to start - * @return The request code to use for the callback + * @param requestCode The request code that is passed to callback to identify the activity */ - public int startActivityForResult(Plugin command, Intent intent) { - int requestCode = this.activityResultCallbackCounter++; - this.activityResultCallbacks.put(requestCode, command); + public void startActivityForResult(Plugin command, Intent intent, int requestCode) { + this.activityResultCallback = command; super.startActivityForResult(intent, requestCode); - return requestCode; } @Override @@ -679,8 +691,7 @@ public class DroidGap extends Activity { */ protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); - - Plugin callback = this.activityResultCallbacks.remove(requestCode); + Plugin callback = this.activityResultCallback; if (callback != null) { callback.onActivityResult(requestCode, resultCode, intent); } diff --git a/framework/src/com/phonegap/FileUtils.java b/framework/src/com/phonegap/FileUtils.java old mode 100644 new mode 100755 index 3f54f9b6..598c23e9 --- a/framework/src/com/phonegap/FileUtils.java +++ b/framework/src/com/phonegap/FileUtils.java @@ -2,123 +2,264 @@ package com.phonegap; import java.io.*; +import org.apache.commons.codec.binary.Base64; +import org.json.JSONArray; +import org.json.JSONException; + +import com.phonegap.api.Plugin; +import com.phonegap.api.PluginResult; + +import android.content.Intent; import android.webkit.WebView; -public class FileUtils { +/** + * This class provides SD card file and directory services to JavaScript. + * Only files on the SD card can be accessed. + */ +public class FileUtils implements Plugin { + + public static int NOT_FOUND_ERR = 8; + public static int SECURITY_ERR = 18; + public static int ABORT_ERR = 20; + + public static int NOT_READABLE_ERR = 24; + public static int ENCODING_ERR = 26; - WebView mView; + WebView webView; // WebView object + DroidGap ctx; // DroidGap object + FileReader f_in; FileWriter f_out; - public FileUtils(WebView view, DroidGap gap) - { - mView = view; + /** + * Constructor. + */ + public FileUtils() { + System.out.println("FileUtils()"); } - - public int testSaveLocationExists(){ - if (DirectoryManager.testSaveLocationExists()) - return 0; - else - return 1; - } - - public long getFreeDiskSpace(){ - long freeDiskSpace=DirectoryManager.getFreeDiskSpace(); - return freeDiskSpace; - } - public int testFileExists(String file){ - if (DirectoryManager.testFileExists(file)) - return 0; - else - return 1; - } - - public int testDirectoryExists(String file){ - if (DirectoryManager.testFileExists(file)) - return 0; - else - return 1; - } - - /** - * Delete a specific directory. - * Everyting in side the directory would be gone. - * TODO: JavaScript Call backs for success and error handling + /** + * Sets the context of the Command. This can then be used to do things like + * get file paths associated with the Activity. + * + * @param ctx The context of the main Activity. */ - public int deleteDirectory (String dir){ - if (DirectoryManager.deleteDirectory(dir)) - return 0; - else - return 1; - } - + public void setContext(DroidGap ctx) { + this.ctx = ctx; + } - /** - * Delete a specific file. - * TODO: JavaScript Call backs for success and error handling + /** + * Sets the main View of the application, this is the WebView within which + * a PhoneGap app runs. + * + * @param webView The PhoneGap WebView */ - public int deleteFile (String file){ - if (DirectoryManager.deleteFile(file)) - return 0; - else - return 1; - } - + public void setView(WebView webView) { + this.webView = webView; + } - /** - * Create a new directory. - * TODO: JavaScript Call backs for success and error handling + /** + * Executes the request and returns CommandResult. + * + * @param action The command to execute. + * @param args JSONArry of arguments for the command. + * @return A CommandResult object with a status and message. */ - public int createDirectory(String dir){ - if (DirectoryManager.createDirectory(dir)) - return 0; - else - return 1; - } - - public String read(String filename) - { - String data = ""; - String output = ""; - try { - FileInputStream fstream = new FileInputStream(filename); - DataInputStream in = new DataInputStream(fstream); - while (in.available() !=0) - { - data += in.readLine(); - } - - } catch (FileNotFoundException e) { - data = "FAIL: File not found"; - } catch (IOException e) { - data = "FAIL: IO ERROR"; - } + public PluginResult execute(String action, JSONArray args) { + PluginResult.Status status = PluginResult.Status.OK; + String result = ""; + //System.out.println("FileUtils.execute("+action+")"); - //mView.loadUrl("javascript:navigator.FileReader.hasRead('" + data + "')"); + try { + if (action.equals("testSaveLocationExists")) { + boolean b = DirectoryManager.testSaveLocationExists(); + return new PluginResult(status, b); + } + else if (action.equals("getFreeDiskSpace")) { + long l = DirectoryManager.getFreeDiskSpace(); + return new PluginResult(status, l); + } + else if (action.equals("testFileExists")) { + boolean b = DirectoryManager.testFileExists(args.getString(0)); + return new PluginResult(status, b); + } + else if (action.equals("testDirectoryExists")) { + boolean b = DirectoryManager.testFileExists(args.getString(0)); + return new PluginResult(status, b); + } + else if (action.equals("deleteDirectory")) { + boolean b = DirectoryManager.deleteDirectory(args.getString(0)); + return new PluginResult(status, b); + } + else if (action.equals("deleteFile")) { + boolean b = DirectoryManager.deleteFile(args.getString(0)); + return new PluginResult(status, b); + } + else if (action.equals("createDirectory")) { + boolean b = DirectoryManager.createDirectory(args.getString(0)); + return new PluginResult(status, b); + } + else if (action.equals("readAsText")) { + try { + String s = this.readAsText(args.getString(0), args.getString(1)); + return new PluginResult(status, s); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR); + } catch (IOException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); + } + } + else if (action.equals("readAsDataURL")) { + try { + String s = this.readAsDataURL(args.getString(0)); + return new PluginResult(status, s); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR); + } catch (IOException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); + } + } + else if (action.equals("writeAsText")) { + try { + this.writeAsText(args.getString(0), args.getString(1), args.getBoolean(2)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR); + } catch (IOException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_READABLE_ERR); + } + } + return new PluginResult(status, result); + } catch (JSONException e) { + e.printStackTrace(); + 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("readAsText")) { + return false; + } + else if (action.equals("readAsDataURL")) { + return false; + } + else if (action.equals("writeAsText")) { + return false; + } + return true; + } + + /** + * Called when the system is about to start resuming a previous activity. + */ + public void onPause() { + } + + /** + * Called when the activity will start interacting with the user. + */ + public void onResume() { + } + + /** + * Called by AccelBroker when listener is to be shut down. + * Stop listener. + */ + public void onDestroy() { + } + + /** + * Called when an activity you launched exits, giving you the requestCode you started it with, + * the resultCode it returned, and any additional data from it. + * + * @param requestCode The request code originally supplied to startActivityForResult(), + * allowing you to identify who this result came from. + * @param resultCode The integer result code returned by the child activity through its setResult(). + * @param data An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + */ + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + } + + //-------------------------------------------------------------------------- + // LOCAL METHODS + //-------------------------------------------------------------------------- + + /** + * Read content of text file. + * + * @param filename The name of the file. + * @param encoding The encoding to return contents as. Typical value is UTF-8. + * (see http://www.iana.org/assignments/character-sets) + * @return Contents of file. + * @throws FileNotFoundException, IOException + */ + public String readAsText(String filename, String encoding) throws FileNotFoundException, IOException { + System.out.println("FileUtils.readAsText("+filename+", "+encoding+")"); + StringBuilder data = new StringBuilder(); + FileInputStream fis = new FileInputStream(filename); + BufferedReader reader = new BufferedReader(new InputStreamReader(fis, encoding), 1024); + String line; + while ((line = reader.readLine()) != null) { + data.append(line); + } + return data.toString(); + } + + /** + * Read content of text file and return as base64 encoded data url. + * + * @param filename The name of the file. + * @return Contents of file = data:;base64, + * @throws FileNotFoundException, IOException + */ + public String readAsDataURL(String filename) throws FileNotFoundException, IOException { + byte[] bytes = new byte[1000]; + BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filename), 1024); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int numRead = 0; + while ((numRead = bis.read(bytes, 0, 1000)) >= 0) { + bos.write(bytes, 0, numRead); + } + + // Determine content type from file name + // TODO + String contentType = ""; + + byte[] base64 = Base64.encodeBase64(bos.toByteArray()); + String data = "data:" + contentType + ";base64," + new String(base64); return data; } - public int write(String filename, String data, boolean append) - { - String FilePath= filename; - try { - byte [] rawData = data.getBytes(); - ByteArrayInputStream in = new ByteArrayInputStream(rawData); - FileOutputStream out= new FileOutputStream(FilePath, append); - byte buff[] = new byte[rawData.length]; - in.read(buff, 0, buff.length); - out.write(buff, 0, rawData.length); - out.flush(); - out.close(); - //mView.loadUrl("javascript:navigator.FileReader.onsuccess('File written')"); - } catch (Exception e) { - //mView.loadUrl("javascript:navigator.FileReader.onerror('Fail')"); - // So, do we just return -1 at this point! - return -1; - } - return 0; + /** + * Write contents of file. + * + * @param filename The name of the file. + * @param data The contents of the file. + * @param append T=append, F=overwrite + * @throws FileNotFoundException, IOException + */ + public void writeAsText(String filename, String data, boolean append) throws FileNotFoundException, IOException { + String FilePath= filename; + byte [] rawData = data.getBytes(); + ByteArrayInputStream in = new ByteArrayInputStream(rawData); + FileOutputStream out= new FileOutputStream(FilePath, append); + byte buff[] = new byte[rawData.length]; + in.read(buff, 0, buff.length); + out.write(buff, 0, rawData.length); + out.flush(); + out.close(); } diff --git a/framework/src/com/phonegap/GeoBroker.java b/framework/src/com/phonegap/GeoBroker.java old mode 100644 new mode 100755 index b3e7ca9b..45281d76 --- a/framework/src/com/phonegap/GeoBroker.java +++ b/framework/src/com/phonegap/GeoBroker.java @@ -1,6 +1,7 @@ package com.phonegap; import java.util.HashMap; +import java.util.Map.Entry; import org.json.JSONArray; import org.json.JSONException; @@ -22,6 +23,7 @@ public class GeoBroker implements Plugin { WebView webView; // WebView object DroidGap ctx; // DroidGap object + // List of gGeolocation listeners private HashMap geoListeners; private GeoListener global; @@ -65,10 +67,10 @@ public class GeoBroker implements Plugin { try { if (action.equals("getCurrentLocation")) { - this.getCurrentLocation(); + this.getCurrentLocation(args.getBoolean(0), args.getInt(1), args.getInt(2)); } else if (action.equals("start")) { - String s = this.start(args.getInt(0), args.getString(1)); + String s = this.start(args.getString(0), args.getBoolean(1), args.getInt(2), args.getInt(3)); return new PluginResult(status, s); } else if (action.equals("stop")) { @@ -80,6 +82,17 @@ public class GeoBroker implements Plugin { } } + /** + * 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) { + // Starting listeners is easier to run on main thread, so don't run async. + return true; + } + /** * Called when the system is about to start resuming a previous activity. */ @@ -93,10 +106,22 @@ public class GeoBroker implements Plugin { } /** - * Called by AccelBroker when listener is to be shut down. + * Called when the activity is to be shut down. * Stop listener. */ - public void onDestroy() { + public void onDestroy() { + java.util.Set> s = this.geoListeners.entrySet(); + java.util.Iterator> it = s.iterator(); + while (it.hasNext()) { + Entry entry = it.next(); + GeoListener listener = entry.getValue(); + listener.destroy(); + } + this.geoListeners.clear(); + if (this.global != null) { + this.global.destroy(); + } + this.global = null; } /** @@ -115,23 +140,57 @@ public class GeoBroker implements Plugin { // LOCAL METHODS //-------------------------------------------------------------------------- - public void getCurrentLocation() { - //It's supposed to run async! - if (global == null) { - global = new GeoListener("global", this.ctx, 10000, this.webView); + /** + * Get current location. + * The result is returned to JavaScript via a callback. + * + * @param enableHighAccuracy + * @param timeout + * @param maximumAge + */ + public void getCurrentLocation(boolean enableHighAccuracy, int timeout, int maximumAge) { + + // Create a geolocation listener just for getCurrentLocation and call it "global" + if (this.global == null) { + this.global = new GeoListener("global", this.ctx, maximumAge, this.webView); } else { - global.start(10000); + this.global.start(maximumAge); } } - public String start(int freq, String key) { - GeoListener listener = new GeoListener(key, this.ctx, freq, this.webView); - geoListeners.put(key, listener); + /** + * Start geolocation listener and add to listener list. + * + * @param key The listener id + * @param enableHighAccuracy + * @param timeout + * @param maximumAge + * @return + */ + public String start(String key, boolean enableHighAccuracy, int timeout, int maximumAge) { + + // Make sure this listener doesn't already exist + GeoListener listener = geoListeners.get(key); + if (listener == null) { + listener = new GeoListener(key, this.ctx, maximumAge, this.webView); + geoListeners.put(key, listener); + } + + // Start it + listener.start(maximumAge); return key; } + /** + * Stop geolocation listener and remove from listener list. + * + * @param key The listener id + */ public void stop(String key) { - GeoListener geo = geoListeners.get(key); + GeoListener listener = geoListeners.remove(key); + if (listener != null) { + listener.stop(); + } } } diff --git a/framework/src/com/phonegap/GeoListener.java b/framework/src/com/phonegap/GeoListener.java old mode 100644 new mode 100755 index 86d79c8f..b78f6fb1 --- a/framework/src/com/phonegap/GeoListener.java +++ b/framework/src/com/phonegap/GeoListener.java @@ -6,83 +6,112 @@ import android.location.LocationManager; import android.webkit.WebView; public class GeoListener { - String id; - String successCallback; + public static int PERMISSION_DENIED = 1; + public static int POSITION_UNAVAILABLE = 2; + public static int TIMEOUT = 3; + + String id; // Listener ID + String successCallback; // String failCallback; - GpsListener mGps; - NetworkListener mNetwork; - LocationManager mLocMan; - private DroidGap mCtx; - private WebView mAppView; + GpsListener mGps; // GPS listener + NetworkListener mNetwork; // Network listener + LocationManager mLocMan; // Location manager + + private DroidGap ctx; // DroidGap object + @SuppressWarnings("unused") + private WebView mAppView; // Webview object int interval; - GeoListener(String i, DroidGap ctx, int time, WebView appView) { - id = i; - interval = time; - mCtx = ctx; - mGps = null; - mNetwork = null; - mLocMan = (LocationManager) mCtx.getSystemService(Context.LOCATION_SERVICE); + /** + * Constructor. + * + * @param id Listener id + * @param ctx + * @param time Sampling period in msec + * @param appView + */ + GeoListener(String id, DroidGap ctx, int time, WebView appView) { + this.id = id; + this.interval = time; + this.ctx = ctx; + this.mGps = null; + this.mNetwork = null; + this.mLocMan = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE); - if (mLocMan.getProvider(LocationManager.GPS_PROVIDER) != null) { - mGps = new GpsListener(mCtx, interval, this); + // If GPS provider, then create and start GPS listener + if (this.mLocMan.getProvider(LocationManager.GPS_PROVIDER) != null) { + this.mGps = new GpsListener(ctx, time, this); } - if (mLocMan.getProvider(LocationManager.NETWORK_PROVIDER) != null) { - mNetwork = new NetworkListener(mCtx, interval, this); + + // If network provider, then create and start network listener + if (this.mLocMan.getProvider(LocationManager.NETWORK_PROVIDER) != null) { + this.mNetwork = new NetworkListener(ctx, time, this); } - mAppView = appView; + this.mAppView = appView; } + /** + * Destroy listener. + */ + public void destroy() { + this.stop(); + } + + /** + * Location found. Send location back to JavaScript. + * + * @param loc + */ void success(Location loc) { - /* - * We only need to figure out what we do when we succeed! - */ - String params; - /* - * Build the giant string to send back to Javascript! - */ - params = loc.getLatitude() + "," + loc.getLongitude() + ", " + loc.getAltitude() + "," + loc.getAccuracy() + "," + loc.getBearing(); - params += "," + loc.getSpeed() + "," + loc.getTime(); - if (id != "global") { - mCtx.sendJavascript("navigator._geo.success(" + id + "," + params + ");"); - } - else { - mCtx.sendJavascript("navigator.geolocation.gotCurrentPosition(" + params + ");"); + String params = loc.getLatitude() + "," + loc.getLongitude() + ", " + loc.getAltitude() + + "," + loc.getAccuracy() + "," + loc.getBearing() + + "," + loc.getSpeed() + "," + loc.getTime(); + + if (id == "global") { this.stop(); } + this.ctx.sendJavascript("navigator._geo.success('" + id + "'," + params + ");"); } - void fail() { - // Do we need to know why? How would we handle this? - if (id != "global") { - mCtx.sendJavascript("navigator._geo.fail(" + id + ");"); - } else { - mCtx.sendJavascript("navigator._geo.fail();"); - } + /** + * Location failed. Send error back to JavaScript. + * + * @param code The error code + * @param msg The error message + */ + void fail(int code, String msg) { + this.ctx.sendJavascript("navigator._geo.fail('" + this.id + "', " + ", " + code + ", '" + msg + "');"); + this.stop(); } + /** + * Start retrieving location. + * + * @param interval + */ void start(int interval) { - if (mGps != null) { - mGps.start(interval); + if (this.mGps != null) { + this.mGps.start(interval); } - if (mNetwork != null) { - mNetwork.start(interval); + if (this.mNetwork != null) { + this.mNetwork.start(interval); } - if (mNetwork == null && mGps == null) { - // Really, how were you going to get the location??? - mCtx.sendJavascript("navigator._geo.fail();"); + if (this.mNetwork == null && this.mGps == null) { + this.fail(POSITION_UNAVAILABLE, "No location providers available."); } } - // This stops the listener + /** + * Stop listening for location. + */ void stop() { - if (mGps != null) { - mGps.stop(); + if (this.mGps != null) { + this.mGps.stop(); } - if (mNetwork != null) { - mNetwork.stop(); + if (this.mNetwork != null) { + this.mNetwork.stop(); } } diff --git a/framework/src/com/phonegap/GpsListener.java b/framework/src/com/phonegap/GpsListener.java old mode 100644 new mode 100755 index a236f563..96326f4e --- a/framework/src/com/phonegap/GpsListener.java +++ b/framework/src/com/phonegap/GpsListener.java @@ -26,81 +26,138 @@ import android.location.Location; import android.location.LocationManager; import android.location.LocationListener; import android.os.Bundle; -import android.util.Log; +/** + * This class handles requests for GPS location services. + * + */ public class GpsListener implements LocationListener { - private Context mCtx; - private Location cLoc; - private LocationManager mLocMan; - private static final String LOG_TAG = "PhoneGap"; - private GeoListener owner; - private boolean hasData = false; + private DroidGap mCtx; // DroidGap object - public GpsListener(Context ctx, int interval, GeoListener m) - { - owner = m; - mCtx = ctx; + private LocationManager mLocMan; // Location manager object + private GeoListener owner; // Geolistener object (parent) + private boolean hasData = false; // Flag indicates if location data is available in cLoc + private Location cLoc; // Last recieved location + private boolean running = false; // Flag indicates if listener is running + + /** + * Constructor. + * Automatically starts listening. + * + * @param ctx + * @param interval + * @param m + */ + public GpsListener(DroidGap ctx, int interval, GeoListener m) { + this.owner = m; + this.mCtx = ctx; + this.mLocMan = (LocationManager) this.mCtx.getSystemService(Context.LOCATION_SERVICE); + this.running = false; this.start(interval); } - public Location getLocation() - { - cLoc = mLocMan.getLastKnownLocation(LocationManager.GPS_PROVIDER); - hasData = true; - return cLoc; + /** + * Get last location. + * + * @return Location object + */ + public Location getLocation() { + this.cLoc = this.mLocMan.getLastKnownLocation(LocationManager.GPS_PROVIDER); + if (this.cLoc != null) { + this.hasData = true; + } + return this.cLoc; } + /** + * Called when the provider is disabled by the user. + * + * @param provider + */ public void onProviderDisabled(String provider) { - // TODO Auto-generated method stub - Log.d(LOG_TAG, "The provider " + provider + " is disabled"); - owner.fail(); + this.owner.fail(GeoListener.POSITION_UNAVAILABLE, "GPS provider disabled."); } + /** + * Called when the provider is enabled by the user. + * + * @param provider + */ public void onProviderEnabled(String provider) { - // TODO Auto-generated method stub - Log.d(LOG_TAG, "The provider "+ provider + " is enabled"); + System.out.println("GpsListener: The provider "+ provider + " is enabled"); } - + /** + * Called when the provider status changes. This method is called when a + * provider is unable to fetch a location or if the provider has recently + * become available after a period of unavailability. + * + * @param provider + * @param status + * @param extras + */ public void onStatusChanged(String provider, int status, Bundle extras) { - // TODO Auto-generated method stub - Log.d(LOG_TAG, "The status of the provider " + provider + " has changed"); - if(status == 0) - { - Log.d(LOG_TAG, provider + " is OUT OF SERVICE"); - owner.fail(); + System.out.println("GpsListener: The status of the provider " + provider + " has changed"); + if (status == 0) { + System.out.println("GpsListener: " + provider + " is OUT OF SERVICE"); + this.owner.fail(GeoListener.POSITION_UNAVAILABLE, "GPS out of service."); } - else if(status == 1) - { - Log.d(LOG_TAG, provider + " is TEMPORARILY_UNAVAILABLE"); + else if (status == 1) { + System.out.println("GpsListener: " + provider + " is TEMPORARILY_UNAVAILABLE"); } - else - { - Log.d(LOG_TAG, provider + " is Available"); + else { + System.out.println("GpsListener: " + provider + " is Available"); } } - + /** + * Called when the location has changed. + * + * @param location + */ public void onLocationChanged(Location location) { - Log.d(LOG_TAG, "The location has been updated!"); - owner.success(location); + System.out.println("GpsListener: The location has been updated!"); + this.hasData = true; + this.cLoc = location; + this.owner.success(location); } + /** + * Determine if location data is available. + * + * @return + */ public boolean hasLocation() { - return hasData; + return this.hasData; } - public void start(int interval) - { - mLocMan = (LocationManager) mCtx.getSystemService(Context.LOCATION_SERVICE); - mLocMan.requestLocationUpdates(LocationManager.GPS_PROVIDER, interval, 0, this); - cLoc = mLocMan.getLastKnownLocation(LocationManager.GPS_PROVIDER); + /** + * Start requesting location updates. + * + * @param interval + */ + public void start(int interval) { + if (!this.running) { + this.running = true; + this.mLocMan.requestLocationUpdates(LocationManager.GPS_PROVIDER, interval, 0, this); + this.getLocation(); + + // If GPS provider has data, then send now + if (this.hasData) { + this.owner.success(this.cLoc); + } + } } - public void stop() - { - mLocMan.removeUpdates(this); + /** + * Stop receiving location updates. + */ + public void stop() { + if (this.running) { + this.mLocMan.removeUpdates(this); + } + this.running = false; } } diff --git a/framework/src/com/phonegap/NetworkListener.java b/framework/src/com/phonegap/NetworkListener.java old mode 100644 new mode 100755 index 4b0ecda4..0941f08b --- a/framework/src/com/phonegap/NetworkListener.java +++ b/framework/src/com/phonegap/NetworkListener.java @@ -26,82 +26,129 @@ import android.location.Location; import android.location.LocationManager; import android.location.LocationListener; import android.os.Bundle; -import android.util.Log; public class NetworkListener implements LocationListener { - - private Context mCtx; - private Location cLoc; - private LocationManager mLocMan; - private static final String LOG_TAG = "PhoneGap"; - GeoListener owner; - public NetworkListener(Context ctx, int interval, GeoListener m) - { - owner = m; - mCtx = ctx; + private DroidGap mCtx; // DroidGap object + + private LocationManager mLocMan; // Location manager object + private GeoListener owner; // Geolistener object (parent) + private boolean hasData = false; // Flag indicates if location data is available in cLoc + private Location cLoc; // Last recieved location + private boolean running = false; // Flag indicates if listener is running + + /** + * Constructor. + * Automatically starts listening. + * + * @param ctx + * @param interval + * @param m + */ + public NetworkListener(DroidGap ctx, int interval, GeoListener m) { + this.owner = m; + this.mCtx = ctx; + this.mLocMan = (LocationManager) this.mCtx.getSystemService(Context.LOCATION_SERVICE); + this.running = false; this.start(interval); } - public Location getLocation() - { - cLoc = mLocMan.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); - return cLoc; + /** + * Get last location. + * + * @return Location object + */ + public Location getLocation() { + this.cLoc = this.mLocMan.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); + if (this.cLoc != null) { + this.hasData = true; + } + return this.cLoc; } + /** + * Called when the provider is disabled by the user. + * + * @param provider + */ public void onProviderDisabled(String provider) { - // TODO Auto-generated method stub - Log.d(LOG_TAG, "The provider " + provider + " is disabled"); + System.out.println("NetworkListener: The provider " + provider + " is disabled"); } - + /** + * Called when the provider is enabled by the user. + * + * @param provider + */ public void onProviderEnabled(String provider) { - // TODO Auto-generated method stub - Log.d(LOG_TAG, "The provider "+ provider + " is enabled"); + System.out.println("NetworkListener: The provider "+ provider + " is enabled"); } - + /** + * Called when the provider status changes. This method is called when a + * provider is unable to fetch a location or if the provider has recently + * become available after a period of unavailability. + * + * @param provider + * @param status + * @param extras + */ public void onStatusChanged(String provider, int status, Bundle extras) { - // TODO Auto-generated method stub - Log.d(LOG_TAG, "The status of the provider " + provider + " has changed"); - if(status == 0) - { - Log.d(LOG_TAG, provider + " is OUT OF SERVICE"); + System.out.println("NetworkListener: The status of the provider " + provider + " has changed"); + if (status == 0) { + System.out.println("NetworkListener: " + provider + " is OUT OF SERVICE"); } - else if(status == 1) - { - Log.d(LOG_TAG, provider + " is TEMPORARILY_UNAVAILABLE"); + else if (status == 1) { + System.out.println("NetworkListener: " + provider + " is TEMPORARILY_UNAVAILABLE"); } - else - { - Log.d(LOG_TAG, provider + " is Available"); + else { + System.out.println("NetworkListener: " + provider + " is Available"); } } - - /* - * The GPS is the primary form of Geolocation in PhoneGap. Only fire the success variables if the GPS is down - * for some reason + /** + * Called when the location has changed. + * + * @param location */ public void onLocationChanged(Location location) { - Log.d(LOG_TAG, "The location has been updated!"); - if (!owner.mGps.hasLocation()) - { - owner.success(location); + System.out.println("NetworkListener: The location has been updated!"); + this.hasData = true; + this.cLoc = location; + + // The GPS is the primary form of Geolocation in PhoneGap. + // Only fire the success variables if the GPS is down for some reason. + if (!this.owner.mGps.hasLocation()) { + this.owner.success(location); } - cLoc = location; } - public void start(int interval) - { - mLocMan = (LocationManager) mCtx.getSystemService(Context.LOCATION_SERVICE); - mLocMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, interval, 0, this); - cLoc = mLocMan.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); + /** + * Start requesting location updates. + * + * @param interval + */ + public void start(int interval) { + if (!this.running) { + this.running = true; + this.mLocMan.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, interval, 0, this); + this.getLocation(); + + // If Network provider has data but GPS provider doesn't, then send ours + if (this.hasData && !this.owner.mGps.hasLocation()) { + this.owner.success(this.cLoc); + } + } } - public void stop() - { - mLocMan.removeUpdates(this); + /** + * Stop receiving location updates. + */ + public void stop() { + if (this.running) { + this.mLocMan.removeUpdates(this); + } + this.running = false; } } diff --git a/framework/src/com/phonegap/NetworkManager.java b/framework/src/com/phonegap/NetworkManager.java old mode 100644 new mode 100755 index bf847f8d..82c75fe5 --- a/framework/src/com/phonegap/NetworkManager.java +++ b/framework/src/com/phonegap/NetworkManager.java @@ -14,6 +14,11 @@ import android.net.*; import android.webkit.WebView; public class NetworkManager implements Plugin { + + public static int NOT_REACHABLE = 0; + public static int REACHABLE_VIA_CARRIER_DATA_NETWORK = 1; + public static int REACHABLE_VIA_WIFI_NETWORK = 2; + WebView webView; // WebView object DroidGap ctx; // DroidGap object @@ -67,8 +72,8 @@ public class NetworkManager implements Plugin { return new PluginResult(status, b); } else if (action.equals("isReachable")) { - boolean b = this.isReachable(args.getString(0)); - return new PluginResult(status, b); + int i = this.isReachable(args.getString(0), args.getBoolean(1)); + return new PluginResult(status, i); } return new PluginResult(status, result); } catch (JSONException e) { @@ -76,6 +81,17 @@ public class NetworkManager implements Plugin { } } + /** + * 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) { + // All methods take a while, so always use async + return false; + } + /** * Called when the system is about to start resuming a previous activity. */ @@ -111,6 +127,11 @@ public class NetworkManager implements Plugin { // LOCAL METHODS //-------------------------------------------------------------------------- + /** + * Determine if a network connection exists. + * + * @return + */ public boolean isAvailable() { NetworkInfo info = sockMan.getActiveNetworkInfo(); boolean conn = false; @@ -120,6 +141,11 @@ public class NetworkManager implements Plugin { return conn; } + /** + * Determine if a WIFI connection exists. + * + * @return + */ public boolean isWifiActive() { NetworkInfo info = sockMan.getActiveNetworkInfo(); if (info != null) { @@ -129,18 +155,37 @@ public class NetworkManager implements Plugin { return false; } - public boolean isReachable(String uri) { + /** + * Determine if a URI is reachable over the network. + * + * @param uri + * @param isIpAddress + * @return + */ + public int isReachable(String uri, boolean isIpAddress) { + int reachable = NOT_REACHABLE; + if (uri.indexOf("http://") == -1) { uri = "http://" + uri; } - boolean reached = isAvailable(); - try { - DefaultHttpClient httpclient = new DefaultHttpClient(); - HttpGet httpget = new HttpGet(uri); - httpclient.execute(httpget); - } catch (Exception e) { - reached = false; + + if (isAvailable()) { + try { + DefaultHttpClient httpclient = new DefaultHttpClient(); + HttpGet httpget = new HttpGet(uri); + httpclient.execute(httpget); + + if (isWifiActive()) { + reachable = REACHABLE_VIA_WIFI_NETWORK; + } + else { + reachable = REACHABLE_VIA_CARRIER_DATA_NETWORK; + } + } catch (Exception e) { + reachable = NOT_REACHABLE; + } } - return reached; + + return reachable; } } diff --git a/framework/src/com/phonegap/Storage.java b/framework/src/com/phonegap/Storage.java index 9bd3e3b8..46a8c63c 100644 --- a/framework/src/com/phonegap/Storage.java +++ b/framework/src/com/phonegap/Storage.java @@ -82,6 +82,16 @@ public class Storage implements Plugin { } } + /** + * 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) { + return false; + } + /** * Called when the system is about to start resuming a previous activity. */ diff --git a/framework/src/com/phonegap/TempListener.java b/framework/src/com/phonegap/TempListener.java index b7a13288..5b79d3ab 100644 --- a/framework/src/com/phonegap/TempListener.java +++ b/framework/src/com/phonegap/TempListener.java @@ -70,6 +70,16 @@ public class TempListener implements SensorEventListener, Plugin { return new PluginResult(status, result); } + /** + * 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) { + return false; + } + /** * Called when the system is about to start resuming a previous activity. */ diff --git a/framework/src/com/phonegap/api/Plugin.java b/framework/src/com/phonegap/api/Plugin.java index 9ca7281c..78a97ca3 100755 --- a/framework/src/com/phonegap/api/Plugin.java +++ b/framework/src/com/phonegap/api/Plugin.java @@ -17,12 +17,20 @@ public interface Plugin { /** * Executes the request and returns PluginResult. * - * @param action The plugin to execute. - * @param args JSONArry of arguments for the plugin. - * @return A PluginResult object with a status and message. + * @param action The action to execute. + * @param args JSONArry of arguments for the plugin. + * @return A PluginResult object with a status and message. */ PluginResult execute(String action, JSONArray args); + /** + * 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); + /** * Sets the context of the Plugin. This can then be used to do things like * get file paths associated with the Activity. diff --git a/framework/src/com/phonegap/api/PluginManager.java b/framework/src/com/phonegap/api/PluginManager.java index 3edf68a6..00b1358c 100755 --- a/framework/src/com/phonegap/api/PluginManager.java +++ b/framework/src/com/phonegap/api/PluginManager.java @@ -18,6 +18,7 @@ import com.phonegap.DroidGap; public final class PluginManager { private HashMap plugins = new HashMap(); + private HashMap services = new HashMap(); private final DroidGap ctx; private final WebView app; @@ -42,29 +43,36 @@ public final class PluginManager { * string is returned that will indicate if any errors have occurred when trying to find * or execute the class denoted by the clazz argument. * - * @param clazz String containing the fully qualified class name. e.g. com.phonegap.FooBar - * @param action String containt the action that the class is supposed to perform. This is - * passed to the plugin execute method and it is up to the plugin developer - * how to deal with it. - * @param callbackId String containing the id of the callback that is execute in JavaScript if - * this is an async plugin call. - * @param args An Array literal string containing any arguments needed in the - * plugin execute method. - * @param async Boolean indicating whether the calling JavaScript code is expecting an - * immediate return value. If true, either PhoneGap.callbackSuccess(...) or PhoneGap.callbackError(...) - * is called once the plugin code has executed. - * @return JSON encoded string with a response message and status. + * @param service String containing the service to run + * @param action String containt the action that the class is supposed to perform. This is + * passed to the plugin execute method and it is up to the plugin developer + * how to deal with it. + * @param callbackId String containing the id of the callback that is execute in JavaScript if + * this is an async plugin call. + * @param args An Array literal string containing any arguments needed in the + * plugin execute method. + * @param async Boolean indicating whether the calling JavaScript code is expecting an + * immediate return value. If true, either PhoneGap.callbackSuccess(...) or + * PhoneGap.callbackError(...) is called once the plugin code has executed. + * + * @return JSON encoded string with a response message and status. */ - public String exec(final String clazz, final String action, final String callbackId, final String jsonArgs, final boolean async) { - System.out.println("PluginManager.exec("+clazz+", "+action+", "+callbackId+", "+jsonArgs+", "+async+")"); + public String exec(final String service, final String action, final String callbackId, final String jsonArgs, final boolean async) { + System.out.println("PluginManager.exec("+service+", "+action+", "+callbackId+", "+jsonArgs+", "+async+")"); PluginResult cr = null; + boolean runAsync = async; try { final JSONArray args = new JSONArray(jsonArgs); - Class c = getClassByName(clazz); - if (isPhoneGapPlugin(c)) { + String clazz = this.services.get(service); + Class c = null; + if (clazz != null) { + c = getClassByName(clazz); + } + if ((c == null) || isPhoneGapPlugin(c)) { final Plugin plugin = this.addPlugin(clazz); final DroidGap ctx = this.ctx; - if (async) { + runAsync = async && !plugin.isSynch(action); + if (async && !plugin.isSynch(action)) { // Run this on a different thread so that this one can return back to JS Thread thread = new Thread(new Runnable() { public void run() { @@ -92,9 +100,8 @@ public final class PluginManager { cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION); } // if async we have already returned at this point unless there was an error... - if (async) { + if (runAsync) { ctx.sendJavascript(cr.toErrorCallbackString(callbackId)); - //app.loadUrl(cr.toErrorCallbackString(callbackId)); } if (cr != null) { System.out.println(" -- returning result: "+cr.getJSONString()); @@ -103,7 +110,7 @@ public final class PluginManager { } /** - * + * Get the class. * * @param clazz * @return @@ -168,6 +175,26 @@ public final class PluginManager { Plugin plugin = this.plugins.get(className); return plugin; } + + /** + * Add a class that implements a service. + * + * @param serviceType + * @param className + */ + public void addService(String serviceType, String className) { + this.services.put(serviceType, className); + } + + /** + * Get the class that implements a service. + * + * @param serviceType + * @return + */ + public String getClassForService(String serviceType) { + return this.services.get(serviceType); + } /** * Called when the system is about to start resuming a previous activity. diff --git a/framework/src/com/phonegap/api/PluginResult.java b/framework/src/com/phonegap/api/PluginResult.java index fbfb281f..777f2605 100755 --- a/framework/src/com/phonegap/api/PluginResult.java +++ b/framework/src/com/phonegap/api/PluginResult.java @@ -63,7 +63,8 @@ public class PluginResult { "Malformed url", "IO error", "Invalid action", - "JSON error" + "JSON error", + "Error" }; public enum Status { @@ -74,6 +75,7 @@ public class PluginResult { MALFORMED_URL_EXCEPTION, IO_EXCEPTION, INVALID_ACTION, - JSON_EXCEPTION + JSON_EXCEPTION, + ERROR } }