From 0e316321f92714de03a2558f181a9c8b1f733c0a Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Sun, 21 Aug 2011 20:50:57 -0500 Subject: [PATCH 01/11] Fix Issue #203: Prompt crashes on Android 3.2 tablet. --- framework/src/com/phonegap/DroidGap.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 1ceb52fc..559be52e 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -914,13 +914,13 @@ public class DroidGap extends PhonegapActivity { } // Polling for JavaScript messages - else if (reqOk && defaultValue.equals("gap_poll:")) { + else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) { String r = callbackServer.getJavascript(); result.confirm(r); } // Calling into CallbackServer - else if (reqOk && defaultValue.equals("gap_callbackServer:")) { + else if (reqOk && defaultValue != null && defaultValue.equals("gap_callbackServer:")) { String r = ""; if (message.equals("usePolling")) { r = ""+callbackServer.usePolling(); @@ -939,7 +939,7 @@ public class DroidGap extends PhonegapActivity { // PhoneGap JS has initialized, so show webview // (This solves white flash seen when rendering HTML) - else if (reqOk && defaultValue.equals("gap_init:")) { + else if (reqOk && defaultValue != null && defaultValue.equals("gap_init:")) { appView.setVisibility(View.VISIBLE); ctx.spinnerStop(); result.confirm("OK"); From 8a4737947b95d0304e35fc58a0f4c787262ae3e1 Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Mon, 22 Aug 2011 09:50:42 -0500 Subject: [PATCH 02/11] Remove old phonegap.js file. --- framework/assets/www/phonegap.js | 2861 ------------------------------ 1 file changed, 2861 deletions(-) delete mode 100644 framework/assets/www/phonegap.js diff --git a/framework/assets/www/phonegap.js b/framework/assets/www/phonegap.js deleted file mode 100644 index 69cf16f7..00000000 --- a/framework/assets/www/phonegap.js +++ /dev/null @@ -1,2861 +0,0 @@ -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - - -/** - * 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 = {}; - -/** - * This represents the PhoneGap API itself, and provides a global namespace for accessing - * information about the state of PhoneGap. - * @class - */ -var PhoneGap = { - queue: { - ready: true, - commands: [], - timer: null - } -}; - - -/** - * Custom pub-sub channel that can have functions subscribed to it - */ -PhoneGap.Channel = function(type) -{ - this.type = type; - this.handlers = {}; - this.guid = 0; - this.fired = false; - this.enabled = true; -}; - -/** - * Subscribes the given function to the channel. Any time that - * Channel.fire is called so too will the function. - * Optionally specify an execution context for the function - * and a guid that can be used to stop subscribing to the channel. - * Returns the guid. - */ -PhoneGap.Channel.prototype.subscribe = function(f, c, g) { - // need a function to call - if (f == null) { return; } - - var func = f; - if (typeof c == "object" && f instanceof Function) { func = PhoneGap.close(c, f); } - - g = g || func.observer_guid || f.observer_guid || this.guid++; - func.observer_guid = g; - f.observer_guid = g; - this.handlers[g] = func; - return g; -}; - -/** - * Like subscribe but the function is only called once and then it - * auto-unsubscribes itself. - */ -PhoneGap.Channel.prototype.subscribeOnce = function(f, c) { - var g = null; - var _this = this; - var m = function() { - f.apply(c || null, arguments); - _this.unsubscribe(g); - } - if (this.fired) { - if (typeof c == "object" && f instanceof Function) { f = PhoneGap.close(c, f); } - f.apply(this, this.fireArgs); - } else { - g = this.subscribe(m); - } - return g; -}; - -/** - * Unsubscribes the function with the given guid from the channel. - */ -PhoneGap.Channel.prototype.unsubscribe = function(g) { - if (g instanceof Function) { g = g.observer_guid; } - this.handlers[g] = null; - delete this.handlers[g]; -}; - -/** - * Calls all functions subscribed to this channel. - */ -PhoneGap.Channel.prototype.fire = function(e) { - if (this.enabled) { - var fail = false; - for (var item in this.handlers) { - var handler = this.handlers[item]; - if (handler instanceof Function) { - var rv = (handler.apply(this, arguments)==false); - fail = fail || rv; - } - } - this.fired = true; - this.fireArgs = arguments; - return !fail; - } - return true; -}; - -/** - * Calls the provided function only after all of the channels specified - * have been fired. - */ -PhoneGap.Channel.join = function(h, c) { - var i = c.length; - var f = function() { - if (!(--i)) h(); - } - for (var j=0; j 0) { - s = s + ","; - } - var type = typeof args[i]; - if ((type == "number") || (type == "boolean")) { - s = s + args[i]; - } - else if (args[i] instanceof Array) { - s = s + "[" + args[i] + "]"; - } - else if (args[i] instanceof Object) { - var start = true; - s = s + '{'; - for (var name in args[i]) { - if (!start) { - s = s + ','; - } - s = s + '"' + name + '":'; - var nameType = typeof args[i][name]; - if ((nameType == "number") || (nameType == "boolean")) { - s = s + args[i][name]; - } - else { - s = s + '"' + args[i][name] + '"'; - } - start=false; - } - s = s + '}'; - } - else { - var a = args[i].replace(/\\/g, '\\\\'); - a = a.replace(/"/g, '\\"'); - s = s + '"' + a + '"'; - } - } - s = s + "]"; - return s; - } - else { - return JSON.stringify(args); - } -}; - -/** - * Does a deep clone of the object. - * - * @param obj - * @return - */ -PhoneGap.clone = function(obj) { - if(!obj) { - return obj; - } - - if(obj instanceof Array){ - var retVal = new Array(); - for(var i = 0; i < obj.length; ++i){ - retVal.push(PhoneGap.clone(obj[i])); - } - return retVal; - } - - if (obj instanceof Function) { - return obj; - } - - if(!(obj instanceof Object)){ - return obj; - } - - retVal = new Object(); - for(i in obj){ - if(!(i in retVal) || retVal[i] != obj[i]) { - retVal[i] = PhoneGap.clone(obj[i]); - } - } - return retVal; -}; - -PhoneGap.callbackId = 0; -PhoneGap.callbacks = {}; -PhoneGap.callbackStatus = { - NO_RESULT: 0, - OK: 1, - CLASS_NOT_FOUND_EXCEPTION: 2, - ILLEGAL_ACCESS_EXCEPTION: 3, - INSTANTIATION_EXCEPTION: 4, - MALFORMED_URL_EXCEPTION: 5, - IO_EXCEPTION: 6, - INVALID_ACTION: 7, - JSON_EXCEPTION: 8, - ERROR: 9 - }; - - -/** - * 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.exec = function(success, fail, service, action, args) { - try { - 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 == PhoneGap.callbackStatus.OK) { - - // If there is a success callback, then call it now with returned value - if (success) { - try { - success(v.message); - } - catch (e) { - console.log("Error in success callback: "+callbackId+" = "+e); - } - - // Clear callback if not expecting any more results - if (!v.keepCallback) { - delete PhoneGap.callbacks[callbackId]; - } - } - return v.message; - } - - // If no result - else if (v.status == PhoneGap.callbackStatus.NO_RESULT) { - - // Clear callback if not expecting any more results - if (!v.keepCallback) { - delete PhoneGap.callbacks[callbackId]; - } - } - - // 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) { - try { - fail(v.message); - } - catch (e) { - console.log("Error in error callback: "+callbackId+" = "+e); - } - - // Clear callback if not expecting any more results - if (!v.keepCallback) { - delete PhoneGap.callbacks[callbackId]; - } - } - return null; - } - } - } catch (e) { - console.log("Error: "+e); - } -}; - -/** - * Called by native code when returning successful result from an action. - * - * @param callbackId - * @param args - */ -PhoneGap.callbackSuccess = function(callbackId, args) { - if (PhoneGap.callbacks[callbackId]) { - - // If result is to be sent to callback - if (args.status == PhoneGap.callbackStatus.OK) { - try { - if (PhoneGap.callbacks[callbackId].success) { - PhoneGap.callbacks[callbackId].success(args.message); - } - } - catch (e) { - console.log("Error in success callback: "+callbackId+" = "+e); - } - } - - // Clear callback if not expecting any more results - if (!args.keepCallback) { - delete PhoneGap.callbacks[callbackId]; - } - } -}; - -/** - * Called by native code when returning error result from an action. - * - * @param callbackId - * @param args - */ -PhoneGap.callbackError = function(callbackId, args) { - 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); - } - - // Clear callback if not expecting any more results - if (!args.keepCallback) { - delete PhoneGap.callbacks[callbackId]; - } - } -}; - - -/** - * Internal function used to dispatch the request to PhoneGap. It processes the - * command queue and executes the next command on the list. If one of the - * arguments is a JavaScript object, it will be passed on the QueryString of the - * 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; - - PhoneGap.queue.ready = false; - - var args = PhoneGap.queue.commands.shift(); - if (PhoneGap.queue.commands.length == 0) { - clearInterval(PhoneGap.queue.timer); - PhoneGap.queue.timer = null; - } - - var uri = []; - var dict = null; - for (var i = 1; i < args.length; i++) { - var arg = args[i]; - if (arg == undefined || arg == null) - arg = ''; - if (typeof(arg) == 'object') - dict = arg; - else - uri.push(encodeURIComponent(arg)); - } - var url = "gap://" + args[0] + "/" + uri.join("/"); - if (dict != null) { - var query_args = []; - for (var name in dict) { - if (typeof(name) != 'string') - continue; - query_args.push(encodeURIComponent(name) + "=" + encodeURIComponent(dict[name])); - } - if (query_args.length > 0) - url += "?" + query_args.join("&"); - } - document.location = url; - -}; - -/** - * 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. - */ -PhoneGap.JSCallback = function() { - var xmlhttp = new XMLHttpRequest(); - - // Callback function when XMLHttpRequest is ready - xmlhttp.onreadystatechange=function(){ - if(xmlhttp.readyState == 4){ - - // If callback has JavaScript statement to execute - if (xmlhttp.status == 200) { - - var msg = xmlhttp.responseText; - setTimeout(function() { - try { - var t = eval(msg); - } - catch (e) { - console.log("JSCallback Error: "+e); - } - }, 1); - setTimeout(PhoneGap.JSCallback, 1); - } - - // If callback ping (used to keep XHR request from timing out) - else if (xmlhttp.status == 404) { - setTimeout(PhoneGap.JSCallback, 10); - } - - // If error, restart callback server - else { - console.log("JSCallback Error: Request failed."); - CallbackServer.restartServer(); - setTimeout(PhoneGap.JSCallback, 100); - } - } - } - - xmlhttp.open("GET", "http://127.0.0.1:"+CallbackServer.getPort()+"/" , true); - xmlhttp.send(); -}; - -/** - * The polling period to use with JSCallbackPolling. - * This can be changed by the application. The default is 50ms. - */ -PhoneGap.JSCallbackPollingPeriod = 50; - -/** - * This is only for Android. - * - * Internal function that uses polling 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. - */ -PhoneGap.JSCallbackPolling = function() { - var msg = CallbackServer.getJavascript(); - if (msg) { - setTimeout(function() { - try { - var t = eval(""+msg); - } - catch (e) { - console.log("JSCallbackPolling Error: "+e); - } - }, 1); - setTimeout(PhoneGap.JSCallbackPolling, 1); - } - else { - setTimeout(PhoneGap.JSCallbackPolling, PhoneGap.JSCallbackPollingPeriod); - } -}; - -/** - * Create a UUID - * - * @return - */ -PhoneGap.createUUID = function() { - return PhoneGap.UUIDcreatePart(4) + '-' + - PhoneGap.UUIDcreatePart(2) + '-' + - PhoneGap.UUIDcreatePart(2) + '-' + - PhoneGap.UUIDcreatePart(2) + '-' + - PhoneGap.UUIDcreatePart(6); -}; - -PhoneGap.UUIDcreatePart = function(length) { - var uuidpart = ""; - for (var i=0; i frequency + 10 sec - PhoneGap.exec( - function(timeout) { - if (timeout < (frequency + 10000)) { - PhoneGap.exec(null, null, "Accelerometer", "setTimeout", [frequency + 10000]); - } - }, - function(e) { }, "Accelerometer", "getTimeout", []); - - // Start watch timer - var id = PhoneGap.createUUID(); - navigator.accelerometer.timers[id] = setInterval(function() { - PhoneGap.exec(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); - }, (frequency ? frequency : 1)); - - return id; -}; - -/** - * Clears the specified accelerometer watch. - * - * @param {String} id The id of the watch returned from #watchAcceleration. - */ -Accelerometer.prototype.clearWatch = function(id) { - - // Stop javascript timer & remove from timer list - if (id && navigator.accelerometer.timers[id] != undefined) { - clearInterval(navigator.accelerometer.timers[id]); - delete navigator.accelerometer.timers[id]; - } -}; - -PhoneGap.addConstructor(function() { - if (typeof navigator.accelerometer == "undefined") navigator.accelerometer = new Accelerometer(); -}); -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * This class provides access to the device camera. - * - * @constructor - */ -Camera = function() { - this.successCallback = null; - this.errorCallback = null; - this.options = null; -}; - -/** - * 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 - * @param {Object} options - */ -Camera.prototype.getPicture = function(successCallback, errorCallback, options) { - - // successCallback required - if (typeof successCallback != "function") { - console.log("Camera Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback != "function")) { - console.log("Camera Error: errorCallback is not a function"); - return; - } - - this.options = options; - var quality = 80; - if (options.quality) { - quality = this.options.quality; - } - 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.exec(successCallback, errorCallback, "Camera", "takePicture", [quality, destinationType, sourceType]); -}; - -PhoneGap.addConstructor(function() { - if (typeof navigator.camera == "undefined") navigator.camera = new Camera(); -}); -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * This class provides access to device Compass data. - * @constructor - */ -function Compass() { - /** - * The last known Compass position. - */ - this.lastHeading = null; - - /** - * List of compass watch timers - */ - this.timers = {}; -}; - -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. (OPTIONAL) - * @param {PositionOptions} options The options for getting the heading data such as timeout. (OPTIONAL) - */ -Compass.prototype.getCurrentHeading = function(successCallback, errorCallback, options) { - - // successCallback required - if (typeof successCallback != "function") { - console.log("Compass Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback != "function")) { - console.log("Compass Error: errorCallback is not a function"); - return; - } - - // Get heading - PhoneGap.exec(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. (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) { - - // Default interval (100 msec) - var frequency = (options != undefined) ? options.frequency : 100; - - // successCallback required - if (typeof successCallback != "function") { - console.log("Compass Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback != "function")) { - console.log("Compass Error: errorCallback is not a function"); - return; - } - - // Make sure compass timeout > frequency + 10 sec - PhoneGap.exec( - function(timeout) { - if (timeout < (frequency + 10000)) { - PhoneGap.exec(null, null, "Compass", "setTimeout", [frequency + 10000]); - } - }, - function(e) { }, "Compass", "getTimeout", []); - - // Start watch timer to get headings - var id = PhoneGap.createUUID(); - navigator.compass.timers[id] = setInterval( - function() { - PhoneGap.exec(successCallback, errorCallback, "Compass", "getHeading", []); - }, (frequency ? frequency : 1)); - - return id; -}; - - -/** - * Clears the specified heading watch. - * - * @param {String} id The ID of the watch returned from #watchHeading. - */ -Compass.prototype.clearWatch = function(id) { - - // Stop javascript timer & remove from timer list - if (id && navigator.compass.timers[id]) { - clearInterval(navigator.compass.timers[id]); - delete navigator.compass.timers[id]; - } -}; - -PhoneGap.addConstructor(function() { - if (typeof navigator.compass == "undefined") navigator.compass = new Compass(); -}); -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** -* Contains information about a single contact. -* @param {DOMString} id unique identifier -* @param {DOMString} displayName -* @param {ContactName} name -* @param {DOMString} nickname -* @param {ContactField[]} phoneNumbers array of phone numbers -* @param {ContactField[]} emails array of email addresses -* @param {ContactAddress[]} addresses array of addresses -* @param {ContactField[]} ims instant messaging user ids -* @param {ContactOrganization[]} organizations -* @param {DOMString} published date contact was first created -* @param {DOMString} updated date contact was last updated -* @param {DOMString} birthday contact's birthday -* @param (DOMString} anniversary contact's anniversary -* @param {DOMString} gender contact's gender -* @param {DOMString} note user notes about contact -* @param {DOMString} preferredUsername -* @param {ContactField[]} photos -* @param {ContactField[]} tags -* @param {ContactField[]} relationships -* @param {ContactField[]} urls contact's web sites -* @param {ContactAccounts[]} accounts contact's online accounts -* @param {DOMString} utcOffset UTC time zone offset -* @param {DOMString} connected -*/ -var Contact = function(id, displayName, name, nickname, phoneNumbers, emails, addresses, - ims, organizations, published, updated, birthday, anniversary, gender, note, - preferredUsername, photos, tags, relationships, urls, accounts, utcOffset, connected) { - this.id = id || null; - this.displayName = displayName || null; - this.name = name || null; // ContactName - this.nickname = nickname || null; - this.phoneNumbers = phoneNumbers || null; // ContactField[] - this.emails = emails || null; // ContactField[] - this.addresses = addresses || null; // ContactAddress[] - this.ims = ims || null; // ContactField[] - this.organizations = organizations || null; // ContactOrganization[] - this.published = published || null; - this.updated = updated || null; - this.birthday = birthday || null; - this.anniversary = anniversary || null; - this.gender = gender || null; - this.note = note || null; - this.preferredUsername = preferredUsername || null; - this.photos = photos || null; // ContactField[] - this.tags = tags || null; // ContactField[] - this.relationships = relationships || null; // ContactField[] - this.urls = urls || null; // ContactField[] - this.accounts = accounts || null; // ContactAccount[] - this.utcOffset = utcOffset || null; - this.connected = connected || null; -}; - -/** -* Removes contact from device storage. -* @param successCB success callback -* @param errorCB error callback -*/ -Contact.prototype.remove = function(successCB, errorCB) { - if (this.id == null) { - var errorObj = new ContactError(); - errorObj.code = ContactError.NOT_FOUND_ERROR; - errorCB(errorObj); - } - - PhoneGap.exec(successCB, errorCB, "Contacts", "remove", [this.id]); -}; - -/** -* Creates a deep copy of this Contact. -* With the contact ID set to null. -* @return copy of this Contact -*/ -Contact.prototype.clone = function() { - var clonedContact = PhoneGap.clone(this); - clonedContact.id = null; - return clonedContact; -}; - -/** -* Persists contact to device storage. -* @param successCB success callback -* @param errorCB error callback -*/ -Contact.prototype.save = function(successCB, errorCB) { -}; - -/** -* Contact name. -* @param formatted -* @param familyName -* @param givenName -* @param middle -* @param prefix -* @param suffix -*/ -var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) { - this.formatted = formatted || null; - this.familyName = familyName || null; - this.givenName = givenName || null; - this.middleName = middle || null; - this.honorificPrefix = prefix || null; - this.honorificSuffix = suffix || null; -}; - -/** -* Generic contact field. -* @param type -* @param value -* @param primary -*/ -var ContactField = function(type, value, primary) { - this.type = type || null; - this.value = value || null; - this.primary = primary || null; -}; - -/** -* Contact address. -* @param formatted -* @param streetAddress -* @param locality -* @param region -* @param postalCode -* @param country -*/ -var ContactAddress = function(formatted, streetAddress, locality, region, postalCode, country) { - this.formatted = formatted || null; - this.streetAddress = streetAddress || null; - this.locality = locality || null; - this.region = region || null; - this.postalCode = postalCode || null; - this.country = country || null; -}; - -/** -* Contact organization. -* @param name -* @param dept -* @param title -* @param startDate -* @param endDate -* @param location -* @param desc -*/ -var ContactOrganization = function(name, dept, title, startDate, endDate, location, desc) { - this.name = name || null; - this.department = dept || null; - this.title = title || null; - this.startDate = startDate || null; - this.endDate = endDate || null; - this.location = location || null; - this.description = desc || null; -}; - -/** -* Contact account. -* @param domain -* @param username -* @param userid -*/ -var ContactAccount = function(domain, username, userid) { - this.domain = domain || null; - this.username = username || null; - this.userid = userid || null; -} - -/** -* Represents a group of Contacts. -*/ -var Contacts = function() { - this.inProgress = false; - this.records = new Array(); -} -/** -* Returns an array of Contacts matching the search criteria. -* @param fields that should be searched -* @param successCB success callback -* @param errorCB error callback -* @param {ContactFindOptions} options that can be applied to contact searching -* @return array of Contacts matching search criteria -*/ -Contacts.prototype.find = function(fields, successCB, errorCB, options) { - PhoneGap.exec(successCB, errorCB, "Contacts", "search", [fields, options]); -}; - -/** -* This function creates a new contact, but it does not persist the contact -* to device storage. To persist the contact to device storage, invoke -* contact.save(). -* @param properties an object who's properties will be examined to create a new Contact -* @returns new Contact object -*/ -Contacts.prototype.create = function(properties) { - var contact = new Contact(); - for (i in properties) { - if (contact[i]!='undefined') { - contact[i]=properties[i]; - } - } - return contact; -}; - -/** - * ContactFindOptions. - * @param filter used to match contacts against - * @param multiple boolean used to determine if more than one contact should be returned - * @param limit maximum number of results to return from the contacts search - * @param updatedSince return only contact records that have been updated on or after the given time - */ -var ContactFindOptions = function(filter, multiple, limit, updatedSince) { - this.filter = filter || ''; - this.multiple = multiple || false; - this.limit = limit || 1; - this.updatedSince = updatedSince || ''; -}; - -/** - * ContactError. - * An error code assigned by an implementation when an error has occurred - */ -var ContactError = function() { - this.code=null; -}; - -/** - * Error codes - */ -ContactError.UNKNOWN_ERROR = 0; -ContactError.INVALID_ARGUMENT_ERROR = 1; -ContactError.NOT_FOUND_ERROR = 2; -ContactError.TIMEOUT_ERROR = 3; -ContactError.PENDING_OPERATION_ERROR = 4; -ContactError.IO_ERROR = 5; -ContactError.NOT_SUPPORTED_ERROR = 6; -ContactError.PERMISSION_DENIED_ERROR = 20; - -/** - * Add the contact interface into the browser. - */ -PhoneGap.addConstructor(function() { - if(typeof navigator.service == "undefined") navigator.service = new Object(); - if(typeof navigator.service.contacts == "undefined") navigator.service.contacts = new Contacts(); -}); -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -// TODO: Needs to be commented - -var Crypto = function() { -}; - -Crypto.prototype.encrypt = function(seed, string, callback) { - this.encryptWin = callback; - PhoneGap.exec(null, null, "Crypto", "encrypt", [seed, string]); -}; - -Crypto.prototype.decrypt = function(seed, string, callback) { - this.decryptWin = callback; - PhoneGap.exec(null, null, "Crypto", "decrypt", [seed, string]); -}; - -Crypto.prototype.gotCryptedString = function(string) { - this.encryptWin(string); -}; - -Crypto.prototype.getPlainString = function(string) { - this.decryptWin(string); -}; - -PhoneGap.addConstructor(function() { - if (typeof navigator.Crypto == "undefined") navigator.Crypto = new Crypto(); -}); - -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * 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.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); - }); -} - -/** - * 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) { - - // 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.exec(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(); -} - -/* - * This is only for Android. - * - * This terminates the activity! - */ -Device.prototype.exitApp = function() { - BackButton.exitApp(); -} - -PhoneGap.addConstructor(function() { - navigator.device = window.device = new Device(); -}); -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * 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; -}; - -/** - * 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.exec(successCallback, errorCallback, "File", "testSaveLocationExists", []); -}; - -FileMgr.prototype.testFileExists = function(fileName, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "testFileExists", [fileName]); -}; - -FileMgr.prototype.testDirectoryExists = function(dirName, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "testDirectoryExists", [dirName]); -}; - -FileMgr.prototype.createDirectory = function(dirName, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "createDirectory", [dirName]); -}; - -FileMgr.prototype.deleteDirectory = function(dirName, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "deleteDirectory", [dirName]); -}; - -FileMgr.prototype.deleteFile = function(fileName, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "deleteFile", [fileName]); -}; - -FileMgr.prototype.getFreeDiskSpace = function(successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "getFreeDiskSpace", []); -}; - -FileMgr.prototype.writeAsText = function(fileName, data, append, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "writeAsText", [fileName, data, append]); -}; - -FileMgr.prototype.readAsText = function(fileName, encoding, successCallback, errorCallback) { - PhoneGap.exec(successCallback, errorCallback, "File", "readAsText", [fileName, encoding]); -}; - -FileMgr.prototype.readAsDataURL = function(fileName, successCallback, errorCallback) { - PhoneGap.exec(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); - } - } - ); -}; - - -/** - * Read file and return data as a base64 encoded data url. - * A data url is of the form: - * data:[][;base64], - * - * @param file The name of the file - */ -FileReader.prototype.readAsDataURL = function(file) { - 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); - } - - var me = this; - - // 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. - * - * @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 -//----------------------------------------------------------------------------- - -/** - * 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.readyState = 0; // EMPTY - - this.result = null; - - // Error - this.error = null; - - // Event handlers - this.onwritestart = null; // When writing starts - this.onprogress = null; // While writing the file, and reporting partial file data - this.onwrite = null; // When the write has successfully completed. - this.onwriteend = null; // When the request has completed (either in success or failure). - this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method. - this.onerror = null; // When the write has failed (see errors). -}; - -// States -FileWriter.INIT = 0; -FileWriter.WRITING = 1; -FileWriter.DONE = 2; - -/** - * Abort writing file. - */ -FileWriter.prototype.abort = function() { - this.readyState = FileWriter.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? -}; - -FileWriter.prototype.writeAsText = function(file, text, bAppend) { - if (bAppend != true) { - bAppend = false; // for null values - } - - this.fileName = file; - - // WRITING state - this.readyState = FileWriter.WRITING; - - var me = this; - - // Write 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 onwrite callback - if (typeof me.onwrite == "function") { - var evt = File._createEvent("write", me); - me.onwrite(evt); - } - - // If onwriteend callback - if (typeof me.onwriteend == "function") { - var evt = File._createEvent("writeend", me); - me.onwriteend(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); - } - - // If onwriteend callback - if (typeof me.onwriteend == "function") { - var evt = File._createEvent("writeend", me); - me.onwriteend(evt); - } - } - ); - -}; - -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * This class provides access to device GPS data. - * @constructor - */ -function Geolocation() { - - // The last known GPS position. - this.lastPosition = null; - - // Geolocation listeners - this.listeners = {}; -}; - -/** - * Position error object - * - * @param code - * @param message - */ -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.exec(null, null, "Geolocation", "getCurrentLocation", [enableHighAccuracy, timeout, maximumAge]); -} - -/** - * 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.exec(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.exec(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() { - 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; - } -}); - -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -function KeyEvent() -{ -} - -KeyEvent.prototype.backTrigger = function() -{ - var e = document.createEvent('Events'); - e.initEvent('backKeyDown'); - document.dispatchEvent(e); -} - -if (document.keyEvent == null || typeof document.keyEvent == 'undefined') -{ - window.keyEvent = document.keyEvent = new KeyEvent(); -} -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * List of media objects. - * PRIVATE - */ -PhoneGap.mediaObjects = {}; - -/** - * Object that receives native callbacks. - * PRIVATE - */ -PhoneGap.Media = function() {}; - -/** - * Get the media object. - * PRIVATE - * - * @param id The media object id (string) - */ -PhoneGap.Media.getMediaObject = function(id) { - return PhoneGap.mediaObjects[id]; -}; - -/** - * Audio has status update. - * PRIVATE - * - * @param id The media object id (string) - * @param status The status code (int) - * @param msg The status message (string) - */ -PhoneGap.Media.onStatus = function(id, msg, value) { - var media = PhoneGap.mediaObjects[id]; - - // If state update - if (msg == Media.MEDIA_STATE) { - if (value == Media.MEDIA_STOPPED) { - if (media.successCallback) { - media.successCallback(); - } - } - if (media.statusCallback) { - media.statusCallback(value); - } - } - else if (msg == Media.MEDIA_DURATION) { - media._duration = value; - } - else if (msg == Media.MEDIA_ERROR) { - if (media.errorCallback) { - media.errorCallback(value); - } - } -}; - -/** - * This class provides access to the device media, interfaces to both sound and video - * - * @param src The file name or url to play - * @param successCallback The callback to be called when the file is done playing or recording. - * successCallback() - OPTIONAL - * @param errorCallback The callback to be called if there is an error. - * 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, positionCallback) { - - // successCallback optional - if (successCallback && (typeof successCallback != "function")) { - console.log("Media Error: successCallback is not a function"); - return; - } - - // errorCallback optional - if (errorCallback && (typeof errorCallback != "function")) { - console.log("Media Error: errorCallback is not a function"); - return; - } - - // statusCallback optional - if (statusCallback && (typeof statusCallback != "function")) { - console.log("Media Error: statusCallback is not a function"); - 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 = 9; - -// Media states -Media.MEDIA_NONE = 0; -Media.MEDIA_STARTING = 1; -Media.MEDIA_RUNNING = 2; -Media.MEDIA_PAUSED = 3; -Media.MEDIA_STOPPED = 4; -Media.MEDIA_MSG = ["None", "Starting", "Running", "Paused", "Stopped"]; - -// TODO: Will MediaError be used? -/** - * This class contains information about any Media errors. - * @constructor - */ -function MediaError() { - this.code = null, - this.message = ""; -}; - -MediaError.MEDIA_ERR_ABORTED = 1; -MediaError.MEDIA_ERR_NETWORK = 2; -MediaError.MEDIA_ERR_DECODE = 3; -MediaError.MEDIA_ERR_NONE_SUPPORTED = 4; - -/** - * Start or resume playing audio file. - */ -Media.prototype.play = function() { - PhoneGap.exec(null, null, "Media", "startPlayingAudio", [this.id, this.src]); -}; - -/** - * Stop playing audio file. - */ -Media.prototype.stop = function() { - return PhoneGap.exec(null, null, "Media", "stopPlayingAudio", [this.id]); -}; - -/** - * Pause playing audio file. - */ -Media.prototype.pause = function() { - PhoneGap.exec(null, null, "Media", "pausePlayingAudio", [this.id]); -}; - -/** - * Get duration of an audio file. - * The duration is only set for audio that is playing, paused or stopped. - * - * @return duration or -1 if not known. - */ -Media.prototype.getDuration = function() { - return this._duration; -}; - -/** - * Get position of audio. - * - * @return - */ -Media.prototype.getCurrentPosition = function(success, fail) { - PhoneGap.exec(success, fail, "Media", "getCurrentPositionAudio", [this.id]); -}; - -/** - * Start recording audio file. - */ -Media.prototype.startRecord = function() { - PhoneGap.exec(null, null, "Media", "startRecordingAudio", [this.id, this.src]); -}; - -/** - * Stop recording audio file. - */ -Media.prototype.stopRecord = function() { - PhoneGap.exec(null, null, "Media", "stopRecordingAudio", [this.id]); -}; - -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * This class contains information about any NetworkStatus. - * @constructor - */ -function NetworkStatus() { - //this.code = null; - //this.message = ""; -}; - -NetworkStatus.NOT_REACHABLE = 0; -NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK = 1; -NetworkStatus.REACHABLE_VIA_WIFI_NETWORK = 2; - -/** - * This class provides access to device Network data (reachability). - * @constructor - */ -function Network() { - /** - * The last known Network status. - * { hostName: string, ipAddress: string, - remoteHostStatus: int(0/1/2), internetConnectionStatus: int(0/1/2), localWiFiConnectionStatus: int (0/2) } - */ - this.lastReachability = null; -}; - -/** - * 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} callback - * @param {Object} options (isIpAddress:boolean) - */ -Network.prototype.isReachable = function(uri, callback, options) { - var isIpAddress = false; - if (options && options.isIpAddress) { - isIpAddress = options.isIpAddress; - } - PhoneGap.exec(callback, null, "Network Status", "isReachable", [uri, isIpAddress]); -}; - -PhoneGap.addConstructor(function() { - if (typeof navigator.network == "undefined") navigator.network = new Network(); -}); - -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * This class provides access to notifications on the device. - */ -function Notification() { -} - -/** - * Open a native alert dialog, with a customizable title and button text. - * - * @param {String} message Message to print in the body of the alert - * @param {Function} completeCallback The callback that is called when user clicks on a button. - * @param {String} title Title of the alert dialog (default: Alert) - * @param {String} buttonLabel Label of the close button (default: OK) - */ -Notification.prototype.alert = function(message, completeCallback, title, buttonLabel) { - var _title = (title || "Alert"); - var _buttonLabel = (buttonLabel || "OK"); - PhoneGap.exec(completeCallback, null, "Notification", "alert", [message,_title,_buttonLabel]); -}; - -/** - * Open a native confirm dialog, with a customizable title and button text. - * The result that the user selects is returned to the result callback. - * - * @param {String} message Message to print in the body of the alert - * @param {Function} resultCallback The callback that is called when user clicks on a button. - * @param {String} title Title of the alert dialog (default: Confirm) - * @param {String} buttonLabels Comma separated list of the labels of the buttons (default: 'OK,Cancel') - */ -Notification.prototype.confirm = function(message, resultCallback, title, buttonLabels) { - var _title = (title || "Confirm"); - var _buttonLabels = (buttonLabels || "OK,Cancel"); - PhoneGap.exec(resultCallback, null, "Notification", "confirm", [message,_title,_buttonLabels]); -}; - -/** - * Start spinning the activity indicator on the statusbar - */ -Notification.prototype.activityStart = function() { - PhoneGap.exec(null, null, "Notification", "activityStart", ["Busy","Please wait..."]); -}; - -/** - * Stop spinning the activity indicator on the statusbar, if it's currently spinning - */ -Notification.prototype.activityStop = function() { - PhoneGap.exec(null, null, "Notification", "activityStop", []); -}; - -/** - * Display a progress dialog with progress bar that goes from 0 to 100. - * - * @param {String} title Title of the progress dialog. - * @param {String} message Message to display in the dialog. - */ -Notification.prototype.progressStart = function(title, message) { - PhoneGap.exec(null, null, "Notification", "progressStart", [title, message]); -}; - -/** - * Set the progress dialog value. - * - * @param {Number} value 0-100 - */ -Notification.prototype.progressValue = function(value) { - PhoneGap.exec(null, null, "Notification", "progressValue", [value]); -}; - -/** - * Close the progress dialog. - */ -Notification.prototype.progressStop = function() { - PhoneGap.exec(null, null, "Notification", "progressStop", []); -}; - -/** - * Causes the device to blink a status LED. - * - * @param {Integer} count The number of blinks. - * @param {String} colour The colour of the light. - */ -Notification.prototype.blink = function(count, colour) { - // NOT IMPLEMENTED -}; - -/** - * Causes the device to vibrate. - * - * @param {Integer} mills The number of milliseconds to vibrate for. - */ -Notification.prototype.vibrate = function(mills) { - PhoneGap.exec(null, null, "Notification", "vibrate", [mills]); -}; - -/** - * Causes the device to beep. - * On Android, the default notification ringtone is played "count" times. - * - * @param {Integer} count The number of beeps. - */ -Notification.prototype.beep = function(count) { - PhoneGap.exec(null, null, "Notification", "beep", [count]); -}; - -PhoneGap.addConstructor(function() { - if (typeof navigator.notification == "undefined") navigator.notification = new Notification(); -}); - -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/** - * This class contains position information. - * @param {Object} lat - * @param {Object} lng - * @param {Object} acc - * @param {Object} alt - * @param {Object} altacc - * @param {Object} head - * @param {Object} vel - * @constructor - */ -function Position(coords, timestamp) { - this.coords = coords; - this.timestamp = new Date().getTime(); -} - -function Coordinates(lat, lng, alt, acc, head, vel, altacc) { - /** - * The latitude of the position. - */ - this.latitude = lat; - /** - * The longitude of the position, - */ - this.longitude = lng; - /** - * The accuracy of the position. - */ - this.accuracy = acc; - /** - * The altitude of the position. - */ - this.altitude = alt; - /** - * The direction the device is moving at the position. - */ - this.heading = head; - /** - * The velocity with which the device is moving at the position. - */ - this.speed = vel; - /** - * The altitude accuracy of the position. - */ - this.altitudeAccuracy = (altacc != 'undefined') ? altacc : null; -} - -/** - * This class specifies the options for requesting position data. - * @constructor - */ -function PositionOptions() { - /** - * Specifies the desired position accuracy. - */ - this.enableHighAccuracy = true; - /** - * The timeout after which if position data cannot be obtained the errorCallback - * is called. - */ - this.timeout = 10000; -} - -/** - * This class contains information about any GSP errors. - * @constructor - */ -function PositionError() { - this.code = null; - this.message = ""; -} - -PositionError.UNKNOWN_ERROR = 0; -PositionError.PERMISSION_DENIED = 1; -PositionError.POSITION_UNAVAILABLE = 2; -PositionError.TIMEOUT = 3; -/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -PhoneGap.addConstructor(function() { - if (typeof navigator.splashScreen == "undefined") { - navigator.splashScreen = SplashScreen; // SplashScreen object come from native side through addJavaScriptInterface - } -});/* - * PhoneGap is available under *either* the terms of the modified BSD license *or* the - * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. - * - * Copyright (c) 2005-2010, Nitobi Software Inc. - * Copyright (c) 2010, IBM Corporation - */ - -/* - * This is purely for the Android 1.5/1.6 HTML 5 Storage - * I was hoping that Android 2.0 would deprecate this, but given the fact that - * most manufacturers ship with Android 1.5 and do not do OTA Updates, this is required - */ - -/** - * Storage object that is called by native code when performing queries. - * PRIVATE METHOD - */ -var DroidDB = function() { - this.queryQueue = {}; -}; - -/** - * Callback from native code when result from a query is available. - * PRIVATE METHOD - * - * @param rawdata JSON string of the row data - * @param id Query id - */ -DroidDB.prototype.addResult = function(rawdata, id) { - try { - eval("var data = " + rawdata + ";"); - var query = this.queryQueue[id]; - query.resultSet.push(data); - } catch (e) { - console.log("DroidDB.addResult(): Error="+e); - } -}; - -/** - * Callback from native code when query is complete. - * PRIVATE METHOD - * - * @param id Query id - */ -DroidDB.prototype.completeQuery = function(id) { - var query = this.queryQueue[id]; - if (query) { - try { - delete this.queryQueue[id]; - - // Get transaction - var tx = query.tx; - - // If transaction hasn't failed - // Note: We ignore all query results if previous query - // in the same transaction failed. - if (tx && tx.queryList[id]) { - - // Save query results - var r = new DroidDB_Result(); - r.rows.resultSet = query.resultSet; - r.rows.length = query.resultSet.length; - try { - if (typeof query.successCallback == 'function') { - query.successCallback(query.tx, r); - } - } catch (ex) { - console.log("executeSql error calling user success callback: "+ex); - } - - tx.queryComplete(id); - } - } catch (e) { - console.log("executeSql error: "+e); - } - } -}; - -/** - * Callback from native code when query fails - * PRIVATE METHOD - * - * @param reason Error message - * @param id Query id - */ -DroidDB.prototype.fail = function(reason, id) { - var query = this.queryQueue[id]; - if (query) { - try { - delete this.queryQueue[id]; - - // Get transaction - var tx = query.tx; - - // If transaction hasn't failed - // Note: We ignore all query results if previous query - // in the same transaction failed. - if (tx && tx.queryList[id]) { - tx.queryList = {}; - - try { - if (typeof query.errorCallback == 'function') { - query.errorCallback(query.tx, reason); - } - } catch (ex) { - console.log("executeSql error calling user error callback: "+ex); - } - - tx.queryFailed(id, reason); - } - - } catch (e) { - console.log("executeSql error: "+e); - } - } -}; - -var DatabaseShell = function() { -}; - -/** - * Start a transaction. - * Does not support rollback in event of failure. - * - * @param process {Function} The transaction function - * @param successCallback {Function} - * @param errorCallback {Function} - */ -DatabaseShell.prototype.transaction = function(process, successCallback, errorCallback) { - var tx = new DroidDB_Tx(); - tx.successCallback = successCallback; - tx.errorCallback = errorCallback; - try { - process(tx); - } catch (e) { - console.log("Transaction error: "+e); - if (tx.errorCallback) { - try { - tx.errorCallback(e); - } catch (ex) { - console.log("Transaction error calling user error callback: "+e); - } - } - } -}; - -/** - * Transaction object - * PRIVATE METHOD - */ -var DroidDB_Tx = function() { - - // Set the id of the transaction - this.id = PhoneGap.createUUID(); - - // Callbacks - this.successCallback = null; - this.errorCallback = null; - - // Query list - this.queryList = {}; -}; - -/** - * Mark query in transaction as complete. - * If all queries are complete, call the user's transaction success callback. - * - * @param id Query id - */ -DroidDB_Tx.prototype.queryComplete = function(id) { - delete this.queryList[id]; - - // If no more outstanding queries, then fire transaction success - if (this.successCallback) { - var count = 0; - for (var i in this.queryList) { - count++; - } - if (count == 0) { - try { - this.successCallback(); - } catch(e) { - console.log("Transaction error calling user success callback: " + e); - } - } - } -}; - -/** - * Mark query in transaction as failed. - * - * @param id Query id - * @param reason Error message - */ -DroidDB_Tx.prototype.queryFailed = function(id, reason) { - - // The sql queries in this transaction have already been run, since - // we really don't have a real transaction implemented in native code. - // However, the user callbacks for the remaining sql queries in transaction - // will not be called. - this.queryList = {}; - - if (this.errorCallback) { - try { - this.errorCallback(reason); - } catch(e) { - console.log("Transaction error calling user error callback: " + e); - } - } -}; - -/** - * SQL query object - * PRIVATE METHOD - * - * @param tx The transaction object that this query belongs to - */ -var DroidDB_Query = function(tx) { - - // Set the id of the query - this.id = PhoneGap.createUUID(); - - // Add this query to the queue - droiddb.queryQueue[this.id] = this; - - // Init result - this.resultSet = []; - - // Set transaction that this query belongs to - this.tx = tx; - - // Add this query to transaction list - this.tx.queryList[this.id] = this; - - // Callbacks - this.successCallback = null; - this.errorCallback = null; - -} - -/** - * Execute SQL statement - * - * @param sql SQL statement to execute - * @param params Statement parameters - * @param successCallback Success callback - * @param errorCallback Error callback - */ -DroidDB_Tx.prototype.executeSql = function(sql, params, successCallback, errorCallback) { - - // Init params array - if (typeof params == 'undefined') { - params = []; - } - - // Create query and add to queue - var query = new DroidDB_Query(this); - droiddb.queryQueue[query.id] = query; - - // Save callbacks - query.successCallback = successCallback; - query.errorCallback = errorCallback; - - // Call native code - PhoneGap.exec(null, null, "Storage", "executeSql", [sql, params, query.id]); -}; - -/** - * SQL result set that is returned to user. - * PRIVATE METHOD - */ -DroidDB_Result = function() { - this.rows = new DroidDB_Rows(); -}; - -/** - * SQL result set object - * PRIVATE METHOD - */ -DroidDB_Rows = function() { - this.resultSet = []; // results array - this.length = 0; // number of rows -}; - -/** - * Get item from SQL result set - * - * @param row The row number to return - * @return The row object - */ -DroidDB_Rows.prototype.item = function(row) { - return this.resultSet[row]; -}; - -/** - * Open database - * - * @param name Database name - * @param version Database version - * @param display_name Database display name - * @param size Database size in bytes - * @return Database object - */ -DroidDB_openDatabase = function(name, version, display_name, size) { - PhoneGap.exec(null, null, "Storage", "openDatabase", [name, version, display_name, size]); - var db = new DatabaseShell(); - return db; -}; - -PhoneGap.addConstructor(function() { - if (typeof window.openDatabase == "undefined") { - navigator.openDatabase = window.openDatabase = DroidDB_openDatabase; - window.droiddb = new DroidDB(); - } -}); From a9c34e65fbdcfa73e8f560d197093867b26af637 Mon Sep 17 00:00:00 2001 From: macdonst Date: Tue, 23 Aug 2011 01:38:00 +0800 Subject: [PATCH 03/11] Fix for issue #141: EXIF data stripped from captured photos in android In order to fix this issue I needed to read the EXIF data. Save it to a temporary object then after the bitmap is compressed I open the file and write the saved EXIF data. Supports the following EXIF fields if they are set in your image: APERTURE DATETIME EXPOSURE_TIME FLASH FOCAL_LENGTH GPS_ALTITUDE GPS_ALTITUDE_REF GPS_DATESTAMP GPS_LATITUDE GPS_LATITUDE_REF GPS_LONGITUDE GPS_LONGITUDE_REF GPS_PROCESSING_METHOD GPS_TIMESTAMP ISO MAKE MODEL ORIENTATION WHITE_BALANCE --- .../src/com/phonegap/CameraLauncher.java | 531 ++--- framework/src/com/phonegap/Capture.java | 564 +++-- framework/src/com/phonegap/DroidGap.java | 1824 ++++++++--------- framework/src/com/phonegap/ExifHelper.java | 153 ++ framework/src/com/phonegap/FileUtils.java | 20 +- 5 files changed, 1634 insertions(+), 1458 deletions(-) create mode 100644 framework/src/com/phonegap/ExifHelper.java diff --git a/framework/src/com/phonegap/CameraLauncher.java b/framework/src/com/phonegap/CameraLauncher.java index 6ca85c43..58526653 100755 --- a/framework/src/com/phonegap/CameraLauncher.java +++ b/framework/src/com/phonegap/CameraLauncher.java @@ -35,100 +35,102 @@ import android.util.Log; */ public class CameraLauncher extends Plugin { - 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) - - private static final int JPEG = 0; // Take a picture of type JPEG - private static final int PNG = 1; // Take a picture of type PNG - - private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) - private int targetWidth; // desired width of the image - private int targetHeight; // desired height of the image - private Uri imageUri; // Uri of captured image - public String callbackId; - + 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) + + private static final int JPEG = 0; // Take a picture of type JPEG + private static final int PNG = 1; // Take a picture of type PNG + + private int mQuality; // Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) + private int targetWidth; // desired width of the image + private int targetHeight; // desired height of the image + private Uri imageUri; + private int encodingType; + // Uri of captured image + public String callbackId; + /** * Constructor. */ - public CameraLauncher() { - } + public CameraLauncher() { + } - /** - * Executes the request and returns PluginResult. - * - * @param action The action to execute. - * @param args JSONArry of arguments for the plugin. - * @param callbackId The callback id used when calling back into JavaScript. - * @return A PluginResult object with a status and message. - */ - public PluginResult execute(String action, JSONArray args, String callbackId) { - PluginResult.Status status = PluginResult.Status.OK; - String result = ""; - this.callbackId = callbackId; - - try { - 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); - } + /** + * Executes the request and returns PluginResult. + * + * @param action The action to execute. + * @param args JSONArry of arguments for the plugin. + * @param callbackId The callback id used when calling back into JavaScript. + * @return A PluginResult object with a status and message. + */ + public PluginResult execute(String action, JSONArray args, String callbackId) { + PluginResult.Status status = PluginResult.Status.OK; + String result = ""; + this.callbackId = callbackId; + + try { + 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 (args.length() > 3) { this.targetWidth = args.getInt(3); } if (args.length() > 4) { this.targetHeight = args.getInt(4); } - int encodingType = JPEG; + this.encodingType = JPEG; if (args.length() > 5) { - encodingType = args.getInt(5); + this.encodingType = args.getInt(5); } - if (srcType == CAMERA) { - this.takePicture(args.getInt(0), destType, encodingType); - } - else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) { - this.getImage(args.getInt(0), srcType, destType); - } - PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); - r.setKeepCallback(true); - return r; - } - return new PluginResult(status, result); - } catch (JSONException e) { - e.printStackTrace(); - return new PluginResult(PluginResult.Status.JSON_EXCEPTION); - } - } - + if (srcType == CAMERA) { + this.takePicture(args.getInt(0), destType, encodingType); + } + else if ((srcType == PHOTOLIBRARY) || (srcType == SAVEDPHOTOALBUM)) { + this.getImage(args.getInt(0), srcType, destType); + } + PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); + r.setKeepCallback(true); + return r; + } + return new PluginResult(status, result); + } catch (JSONException e) { + e.printStackTrace(); + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } + } + //-------------------------------------------------------------------------- // LOCAL METHODS //-------------------------------------------------------------------------- - /** - * Take a picture with the camera. - * When an image is captured or the camera view is cancelled, the result is returned - * in PhonegapActivity.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 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, int returnType, int encodingType) { - this.mQuality = quality; - - // Display camera + /** + * Take a picture with the camera. + * When an image is captured or the camera view is cancelled, the result is returned + * in PhonegapActivity.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 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, int returnType, int encodingType) { + this.mQuality = quality; + + // Display camera Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); // Specify file so that large image is captured and returned @@ -138,14 +140,14 @@ public class CameraLauncher extends Plugin { this.imageUri = Uri.fromFile(photo); this.ctx.startActivityForResult((Plugin) this, intent, (CAMERA+1)*16 + returnType+1); - } + } - /** - * Create a file in the applications temporary directory based upon the supplied encoding. - * - * @param encodingType of the image to be taken - * @return a File object pointing to the temporary picture - */ + /** + * Create a file in the applications temporary directory based upon the supplied encoding. + * + * @param encodingType of the image to be taken + * @return a File object pointing to the temporary picture + */ private File createCaptureFile(int encodingType) { File photo = null; if (encodingType == JPEG) { @@ -157,46 +159,46 @@ public class CameraLauncher extends Plugin { } /** - * Get image from photo library. - * - * @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) - * @param srcType The album to get image from. - * @param returnType Set the type of image to return. - */ - // TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do! - public void getImage(int quality, int srcType, int returnType) { - this.mQuality = quality; + * Get image from photo library. + * + * @param quality Compression quality hint (0-100: 0=low quality & high compression, 100=compress of max quality) + * @param srcType The album to get image from. + * @param returnType Set the type of image to return. + */ + // TODO: Images selected from SDCARD don't display correctly, but from CAMERA ALBUM do! + public void getImage(int quality, int srcType, int returnType) { + this.mQuality = quality; - 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); - } + 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); + } - /** - * Scales the bitmap according to the requested size. - * - * @param bitmap The bitmap to scale. - * @return Bitmap A new Bitmap object of the same bitmap after scaling. - */ - public Bitmap scaleBitmap(Bitmap bitmap) { + /** + * Scales the bitmap according to the requested size. + * + * @param bitmap The bitmap to scale. + * @return Bitmap A new Bitmap object of the same bitmap after scaling. + */ + public Bitmap scaleBitmap(Bitmap bitmap) { int newWidth = this.targetWidth; int newHeight = this.targetHeight; int origWidth = bitmap.getWidth(); int origHeight = bitmap.getHeight(); // If no new width or height were specified return the original bitmap - if (newWidth <= 0 && newHeight <= 0) { - return bitmap; - } - // Only the width was specified - else if (newWidth > 0 && newHeight <= 0) { + if (newWidth <= 0 && newHeight <= 0) { + return bitmap; + } + // Only the width was specified + else if (newWidth > 0 && newHeight <= 0) { newHeight = (newWidth * origHeight) / origWidth; } - // only the height was specified - else if (newWidth <= 0 && newHeight > 0) { + // only the height was specified + else if (newWidth <= 0 && newHeight > 0) { newWidth = (newHeight * origWidth) / origHeight; } // If the user specified both a positive width and height @@ -205,7 +207,7 @@ public class CameraLauncher extends Plugin { // Alternatively, the specified width and height could have been // kept and Bitmap.SCALE_TO_FIT specified when scaling, but this // would result in whitespace in the new image. - else { + else { double newRatio = newWidth / (double)newHeight; double origRatio = origWidth / (double)origHeight; @@ -217,156 +219,169 @@ public class CameraLauncher extends Plugin { } return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true); - } - + } + /** * Called when the camera view exits. * - * @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 intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + * @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 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; + 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 CAMERA - if (srcType == CAMERA) { - // If image available - if (resultCode == Activity.RESULT_OK) { - try { - // Read in bitmap of captured image - Bitmap bitmap; - try { - bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); - } catch (FileNotFoundException e) { - Uri uri = intent.getData(); - android.content.ContentResolver resolver = this.ctx.getContentResolver(); - bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri)); - } + // If CAMERA + if (srcType == CAMERA) { + // If image available + if (resultCode == Activity.RESULT_OK) { + try { + // Create an ExifHelper to save the exif data that is lost during compression + ExifHelper exif = new ExifHelper(); + if (this.encodingType == JPEG) { + exif.createInFile(DirectoryManager.getTempDirectoryPath(ctx) + "/Pic.jpg"); + exif.readExifData(); + } - bitmap = scaleBitmap(bitmap); - - // If sending base64 image back - if (destType == DATA_URL) { - this.processPicture(bitmap); - } + // Read in bitmap of captured image + Bitmap bitmap; + try { + bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); + } catch (FileNotFoundException e) { + Uri uri = intent.getData(); + android.content.ContentResolver resolver = this.ctx.getContentResolver(); + bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri)); + } - // 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; - } - } + bitmap = scaleBitmap(bitmap); + + // If sending base64 image back + if (destType == DATA_URL) { + this.processPicture(bitmap); + } - // 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(); + // 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; + } + } - // Send Uri back to JavaScript for viewing image - this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); - } - bitmap.recycle(); - bitmap = null; - System.gc(); - } catch (IOException e) { - e.printStackTrace(); - this.failPicture("Error capturing image."); - } - } + // 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(); + + // Restore exif data to file + if (this.encodingType == JPEG) { + exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.ctx)); + exif.writeExifData(); + } - // If cancelled - else if (resultCode == Activity.RESULT_CANCELED) { - this.failPicture("Camera cancelled."); - } + // Send Uri back to JavaScript for viewing image + this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); + } + bitmap.recycle(); + bitmap = null; + System.gc(); + } catch (IOException e) { + e.printStackTrace(); + this.failPicture("Error capturing image."); + } + } - // 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 (destType == DATA_URL) { - try { - Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri)); - bitmap = scaleBitmap(bitmap); - this.processPicture(bitmap); - bitmap.recycle(); - bitmap = null; - System.gc(); - } catch (FileNotFoundException e) { - e.printStackTrace(); - this.failPicture("Error retrieving image."); - } - } - - // If sending filename back - else if (destType == FILE_URI) { - this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); - } - } - else if (resultCode == Activity.RESULT_CANCELED) { - this.failPicture("Selection cancelled."); - } - else { - this.failPicture("Selection did not complete!"); - } - } - } - - /** - * Compress bitmap using jpeg, convert to Base64 encoded string, and return to JavaScript. - * - * @param bitmap - */ - public void processPicture(Bitmap bitmap) { - ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream(); - try { - if (bitmap.compress(CompressFormat.JPEG, mQuality, jpeg_data)) { - byte[] code = jpeg_data.toByteArray(); - byte[] output = Base64.encodeBase64(code); - String js_out = new String(output); - this.success(new PluginResult(PluginResult.Status.OK, js_out), this.callbackId); - js_out = null; - output = null; - code = null; - } - } - catch(Exception e) { - this.failPicture("Error compressing image."); - } - jpeg_data = null; - } - - /** - * Send error message to JavaScript. - * - * @param err - */ - public void failPicture(String err) { - this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId); - } -} + // 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 (destType == DATA_URL) { + try { + Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri)); + bitmap = scaleBitmap(bitmap); + this.processPicture(bitmap); + bitmap.recycle(); + bitmap = null; + System.gc(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + this.failPicture("Error retrieving image."); + } + } + + // If sending filename back + else if (destType == FILE_URI) { + this.success(new PluginResult(PluginResult.Status.OK, uri.toString()), this.callbackId); + } + } + else if (resultCode == Activity.RESULT_CANCELED) { + this.failPicture("Selection cancelled."); + } + else { + this.failPicture("Selection did not complete!"); + } + } + } + + /** + * Compress bitmap using jpeg, convert to Base64 encoded string, and return to JavaScript. + * + * @param bitmap + */ + public void processPicture(Bitmap bitmap) { + ByteArrayOutputStream jpeg_data = new ByteArrayOutputStream(); + try { + if (bitmap.compress(CompressFormat.JPEG, mQuality, jpeg_data)) { + byte[] code = jpeg_data.toByteArray(); + byte[] output = Base64.encodeBase64(code); + String js_out = new String(output); + this.success(new PluginResult(PluginResult.Status.OK, js_out), this.callbackId); + js_out = null; + output = null; + code = null; + } + } + catch(Exception e) { + this.failPicture("Error compressing image."); + } + jpeg_data = null; + } + + /** + * Send error message to JavaScript. + * + * @param err + */ + public void failPicture(String err) { + this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId); + } +} \ No newline at end of file diff --git a/framework/src/com/phonegap/Capture.java b/framework/src/com/phonegap/Capture.java index b078e5df..e9261442 100644 --- a/framework/src/com/phonegap/Capture.java +++ b/framework/src/com/phonegap/Capture.java @@ -18,12 +18,10 @@ import org.json.JSONObject; import android.app.Activity; import android.content.ContentValues; import android.content.Intent; -import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.MediaPlayer; import android.net.Uri; -import android.os.Environment; import android.util.Log; import com.phonegap.api.Plugin; @@ -31,146 +29,145 @@ import com.phonegap.api.PluginResult; public class Capture extends Plugin { - private static final String _DATA = "_data"; // The column name where the file path is stored - private static final int CAPTURE_AUDIO = 0; // Constant for capture audio - private static final int CAPTURE_IMAGE = 1; // Constant for capture image - private static final int CAPTURE_VIDEO = 2; // Constant for capture video - private static final String LOG_TAG = "Capture"; - private String callbackId; // The ID of the callback to be invoked with our result - private long limit; // the number of pics/vids/clips to take - private double duration; // optional duration parameter for video recording - private JSONArray results; // The array of results to be returned to the user - private Uri imageUri; // Uri of captured image + private static final int CAPTURE_AUDIO = 0; // Constant for capture audio + private static final int CAPTURE_IMAGE = 1; // Constant for capture image + private static final int CAPTURE_VIDEO = 2; // Constant for capture video + private static final String LOG_TAG = "Capture"; + private String callbackId; // The ID of the callback to be invoked with our result + private long limit; // the number of pics/vids/clips to take + private double duration; // optional duration parameter for video recording + private JSONArray results; // The array of results to be returned to the user + private Uri imageUri; // Uri of captured image - @Override - public PluginResult execute(String action, JSONArray args, String callbackId) { - this.callbackId = callbackId; - this.limit = 1; - this.duration = 0.0f; - this.results = new JSONArray(); - - JSONObject options = args.optJSONObject(0); - if (options != null) { - limit = options.optLong("limit", 1); - duration = options.optDouble("duration", 0.0f); - } + @Override + public PluginResult execute(String action, JSONArray args, String callbackId) { + this.callbackId = callbackId; + this.limit = 1; + this.duration = 0.0f; + this.results = new JSONArray(); + + JSONObject options = args.optJSONObject(0); + if (options != null) { + limit = options.optLong("limit", 1); + duration = options.optDouble("duration", 0.0f); + } - if (action.equals("getFormatData")) { - try { - JSONObject obj = getFormatData(args.getString(0), args.getString(1)); - return new PluginResult(PluginResult.Status.OK, obj); - } catch (JSONException e) { - return new PluginResult(PluginResult.Status.ERROR); - } - } - else if (action.equals("captureAudio")) { - this.captureAudio(); - } - else if (action.equals("captureImage")) { - this.captureImage(); - } - else if (action.equals("captureVideo")) { - this.captureVideo(duration); - } - - PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); - r.setKeepCallback(true); - return r; - } + if (action.equals("getFormatData")) { + try { + JSONObject obj = getFormatData(args.getString(0), args.getString(1)); + return new PluginResult(PluginResult.Status.OK, obj); + } catch (JSONException e) { + return new PluginResult(PluginResult.Status.ERROR); + } + } + else if (action.equals("captureAudio")) { + this.captureAudio(); + } + else if (action.equals("captureImage")) { + this.captureImage(); + } + else if (action.equals("captureVideo")) { + this.captureVideo(duration); + } + + PluginResult r = new PluginResult(PluginResult.Status.NO_RESULT); + r.setKeepCallback(true); + return r; + } - /** - * Provides the media data file data depending on it's mime type - * - * @param filePath path to the file - * @param mimeType of the file - * @return a MediaFileData object - */ - private JSONObject getFormatData(String filePath, String mimeType) { - JSONObject obj = new JSONObject(); - try { - // setup defaults - obj.put("height", 0); - obj.put("width", 0); - obj.put("bitrate", 0); - obj.put("duration", 0); - obj.put("codecs", ""); + /** + * Provides the media data file data depending on it's mime type + * + * @param filePath path to the file + * @param mimeType of the file + * @return a MediaFileData object + */ + private JSONObject getFormatData(String filePath, String mimeType) { + JSONObject obj = new JSONObject(); + try { + // setup defaults + obj.put("height", 0); + obj.put("width", 0); + obj.put("bitrate", 0); + obj.put("duration", 0); + obj.put("codecs", ""); - // If the mimeType isn't set the rest will fail - // so let's see if we can determine it. - if (mimeType == null || mimeType.equals("")) { - mimeType = FileUtils.getMimeType(filePath); - } - - if (mimeType.equals("image/jpeg") || filePath.endsWith(".jpg")) { - obj = getImageData(filePath, obj); - } - else if (filePath.endsWith("audio/3gpp")) { - obj = getAudioVideoData(filePath, obj, false); - } - else if (mimeType.equals("video/3gpp")) { - obj = getAudioVideoData(filePath, obj, true); - } - } - catch (JSONException e) { - Log.d(LOG_TAG, "Error: setting media file data object"); - } - return obj; - } + // If the mimeType isn't set the rest will fail + // so let's see if we can determine it. + if (mimeType == null || mimeType.equals("")) { + mimeType = FileUtils.getMimeType(filePath); + } + + if (mimeType.equals("image/jpeg") || filePath.endsWith(".jpg")) { + obj = getImageData(filePath, obj); + } + else if (filePath.endsWith("audio/3gpp")) { + obj = getAudioVideoData(filePath, obj, false); + } + else if (mimeType.equals("video/3gpp")) { + obj = getAudioVideoData(filePath, obj, true); + } + } + catch (JSONException e) { + Log.d(LOG_TAG, "Error: setting media file data object"); + } + return obj; + } - /** - * Get the Image specific attributes - * - * @param filePath path to the file - * @param obj represents the Media File Data - * @return a JSONObject that represents the Media File Data - * @throws JSONException - */ - private JSONObject getImageData(String filePath, JSONObject obj) throws JSONException { - Bitmap bitmap = BitmapFactory.decodeFile(filePath); - obj.put("height", bitmap.getHeight()); - obj.put("width", bitmap.getWidth()); - return obj; - } + /** + * Get the Image specific attributes + * + * @param filePath path to the file + * @param obj represents the Media File Data + * @return a JSONObject that represents the Media File Data + * @throws JSONException + */ + private JSONObject getImageData(String filePath, JSONObject obj) throws JSONException { + Bitmap bitmap = BitmapFactory.decodeFile(filePath); + obj.put("height", bitmap.getHeight()); + obj.put("width", bitmap.getWidth()); + return obj; + } - /** - * Get the Image specific attributes - * - * @param filePath path to the file - * @param obj represents the Media File Data - * @param video if true get video attributes as well - * @return a JSONObject that represents the Media File Data - * @throws JSONException - */ - private JSONObject getAudioVideoData(String filePath, JSONObject obj, boolean video) throws JSONException { - MediaPlayer player = new MediaPlayer(); - try { - player.setDataSource(filePath); - player.prepare(); - obj.put("duration", player.getDuration()); - if (video) { - obj.put("height", player.getVideoHeight()); - obj.put("width", player.getVideoWidth()); - } - } - catch (IOException e) { - Log.d(LOG_TAG, "Error: loading video file"); - } - return obj; - } + /** + * Get the Image specific attributes + * + * @param filePath path to the file + * @param obj represents the Media File Data + * @param video if true get video attributes as well + * @return a JSONObject that represents the Media File Data + * @throws JSONException + */ + private JSONObject getAudioVideoData(String filePath, JSONObject obj, boolean video) throws JSONException { + MediaPlayer player = new MediaPlayer(); + try { + player.setDataSource(filePath); + player.prepare(); + obj.put("duration", player.getDuration()); + if (video) { + obj.put("height", player.getVideoHeight()); + obj.put("width", player.getVideoWidth()); + } + } + catch (IOException e) { + Log.d(LOG_TAG, "Error: loading video file"); + } + return obj; + } - /** - * Sets up an intent to capture audio. Result handled by onActivityResult() - */ - private void captureAudio() { + /** + * Sets up an intent to capture audio. Result handled by onActivityResult() + */ + private void captureAudio() { Intent intent = new Intent(android.provider.MediaStore.Audio.Media.RECORD_SOUND_ACTION); this.ctx.startActivityForResult((Plugin) this, intent, CAPTURE_AUDIO); } - /** - * Sets up an intent to capture images. Result handled by onActivityResult() - */ - private void captureImage() { + /** + * Sets up an intent to capture images. Result handled by onActivityResult() + */ + private void captureImage() { Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); // Specify file so that large image is captured and returned @@ -181,179 +178,174 @@ public class Capture extends Plugin { this.ctx.startActivityForResult((Plugin) this, intent, CAPTURE_IMAGE); } - /** - * Sets up an intent to capture video. Result handled by onActivityResult() - */ - private void captureVideo(double duration) { + /** + * Sets up an intent to capture video. Result handled by onActivityResult() + */ + private void captureVideo(double duration) { Intent intent = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE); // Introduced in API 8 //intent.putExtra(android.provider.MediaStore.EXTRA_DURATION_LIMIT, duration); this.ctx.startActivityForResult((Plugin) this, intent, CAPTURE_VIDEO); } - + /** * Called when the video view exits. * - * @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 intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + * @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 intent An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). * @throws JSONException */ - public void onActivityResult(int requestCode, int resultCode, Intent intent) { + public void onActivityResult(int requestCode, int resultCode, Intent intent) { - // Result received okay - if (resultCode == Activity.RESULT_OK) { - // An audio clip was requested - if (requestCode == CAPTURE_AUDIO) { - // Get the uri of the audio clip - Uri data = intent.getData(); - // create a file object from the uri - results.put(createMediaFile(data)); + // Result received okay + if (resultCode == Activity.RESULT_OK) { + // An audio clip was requested + if (requestCode == CAPTURE_AUDIO) { + // Get the uri of the audio clip + Uri data = intent.getData(); + // create a file object from the uri + results.put(createMediaFile(data)); - if (results.length() >= limit) { - // Send Uri back to JavaScript for listening to audio - this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); - } else { - // still need to capture more audio clips - captureAudio(); - } - } else if (requestCode == CAPTURE_IMAGE) { - // For some reason if I try to do: - // Uri data = intent.getData(); - // It crashes in the emulator and on my phone with a null pointer exception - // To work around it I had to grab the code from CameraLauncher.java - try { - // Read in bitmap of captured image - Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); + if (results.length() >= limit) { + // Send Uri back to JavaScript for listening to audio + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } else { + // still need to capture more audio clips + captureAudio(); + } + } else if (requestCode == CAPTURE_IMAGE) { + // For some reason if I try to do: + // Uri data = intent.getData(); + // It crashes in the emulator and on my phone with a null pointer exception + // To work around it I had to grab the code from CameraLauncher.java + try { + // Create an ExifHelper to save the exif data that is lost during compression + ExifHelper exif = new ExifHelper(); + exif.createInFile(DirectoryManager.getTempDirectoryPath(ctx) + "/Capture.jpg"); + exif.readExifData(); + + // Read in bitmap of captured image + Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); - // 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.fail("Error capturing image - no media storage found."); - return; - } - } + // 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.fail("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, 100, os); - os.close(); + // Add compressed version of captured image to returned media store Uri + OutputStream os = this.ctx.getContentResolver().openOutputStream(uri); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); + os.close(); - bitmap.recycle(); - bitmap = null; - System.gc(); - - // Add image to results - results.put(createMediaFile(uri)); - - if (results.length() >= limit) { - // Send Uri back to JavaScript for viewing image - this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); - } else { - // still need to capture more images - captureImage(); - } - } catch (IOException e) { - e.printStackTrace(); - this.fail("Error capturing image."); - } - } else if (requestCode == CAPTURE_VIDEO) { - // Get the uri of the video clip - Uri data = intent.getData(); - // create a file object from the uri - results.put(createMediaFile(data)); + bitmap.recycle(); + bitmap = null; + System.gc(); + + // Restore exif data to file + exif.createOutFile(FileUtils.getRealPathFromURI(uri, this.ctx)); + exif.writeExifData(); + + // Add image to results + results.put(createMediaFile(uri)); + + if (results.length() >= limit) { + // Send Uri back to JavaScript for viewing image + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } else { + // still need to capture more images + captureImage(); + } + } catch (IOException e) { + e.printStackTrace(); + this.fail("Error capturing image."); + } + } else if (requestCode == CAPTURE_VIDEO) { + // Get the uri of the video clip + Uri data = intent.getData(); + // create a file object from the uri + results.put(createMediaFile(data)); - if (results.length() >= limit) { - // Send Uri back to JavaScript for viewing video - this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); - } else { - // still need to capture more video clips - captureVideo(duration); - } - } - } - // If canceled - else if (resultCode == Activity.RESULT_CANCELED) { - // If we have partial results send them back to the user - if (results.length() > 0) { - this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); - } - // user canceled the action - else { - this.fail("Canceled."); - } - } - // If something else - else { - // If we have partial results send them back to the user - if (results.length() > 0) { - this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); - } - // something bad happened - else { - this.fail("Did not complete!"); - } - } - } - - /** - * Creates a JSONObject that represents a File from the Uri - * - * @param data the Uri of the audio/image/video - * @return a JSONObject that represents a File - */ - private JSONObject createMediaFile(Uri data) { - File fp = new File(getRealPathFromURI(data)); - - JSONObject obj = new JSONObject(); - - try { - // File properties - obj.put("name", fp.getName()); - obj.put("fullPath", fp.getAbsolutePath()); - obj.put("type", FileUtils.getMimeType(fp.getAbsolutePath())); - obj.put("lastModifiedDate", fp.lastModified()); - obj.put("size", fp.length()); - } catch (JSONException e) { - // this will never happen - e.printStackTrace(); - } - - return obj; - } - - /** - * Queries the media store to find out what the file path is for the Uri we supply - * - * @param contentUri the Uri of the audio/image/video - * @return the full path to the file - */ - private String getRealPathFromURI(Uri contentUri) { - String[] proj = { _DATA }; - Cursor cursor = this.ctx.managedQuery(contentUri, proj, null, null, null); - int column_index = cursor.getColumnIndexOrThrow(_DATA); - cursor.moveToFirst(); - return cursor.getString(column_index); + if (results.length() >= limit) { + // Send Uri back to JavaScript for viewing video + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } else { + // still need to capture more video clips + captureVideo(duration); + } + } + } + // If canceled + else if (resultCode == Activity.RESULT_CANCELED) { + // If we have partial results send them back to the user + if (results.length() > 0) { + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } + // user canceled the action + else { + this.fail("Canceled."); + } + } + // If something else + else { + // If we have partial results send them back to the user + if (results.length() > 0) { + this.success(new PluginResult(PluginResult.Status.OK, results, "navigator.device.capture._castMediaFile"), this.callbackId); + } + // something bad happened + else { + this.fail("Did not complete!"); + } + } } - /** - * Send error message to JavaScript. - * - * @param err - */ - public void fail(String err) { - this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId); - } + /** + * Creates a JSONObject that represents a File from the Uri + * + * @param data the Uri of the audio/image/video + * @return a JSONObject that represents a File + * @throws IOException + */ + private JSONObject createMediaFile(Uri data){ + File fp = new File(FileUtils.getRealPathFromURI(data, this.ctx)); + JSONObject obj = new JSONObject(); + + try { + // File properties + obj.put("name", fp.getName()); + obj.put("fullPath", fp.getAbsolutePath()); + obj.put("type", FileUtils.getMimeType(fp.getAbsolutePath())); + obj.put("lastModifiedDate", fp.lastModified()); + obj.put("size", fp.length()); + } catch (JSONException e) { + // this will never happen + e.printStackTrace(); + } + + return obj; + } + + /** + * Send error message to JavaScript. + * + * @param err + */ + public void fail(String err) { + this.error(new PluginResult(PluginResult.Status.ERROR, err), this.callbackId); + } } \ No newline at end of file diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 559be52e..fc210341 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -91,90 +91,90 @@ import com.phonegap.api.PluginManager; * * Properties: The application can be configured using the following properties: * - * // Display a native loading dialog. Format for value = "Title,Message". - * // (String - default=null) - * super.setStringProperty("loadingDialog", "Wait,Loading Demo..."); + * // Display a native loading dialog. Format for value = "Title,Message". + * // (String - default=null) + * super.setStringProperty("loadingDialog", "Wait,Loading Demo..."); * - * // Display a native loading dialog when loading sub-pages. Format for value = "Title,Message". - * // (String - default=null) - * super.setStringProperty("loadingPageDialog", "Loading page..."); + * // Display a native loading dialog when loading sub-pages. Format for value = "Title,Message". + * // (String - default=null) + * super.setStringProperty("loadingPageDialog", "Loading page..."); * - * // Cause all links on web page to be loaded into existing web view, - * // instead of being loaded into new browser. (Boolean - default=false) - * super.setBooleanProperty("loadInWebView", true); + * // Cause all links on web page to be loaded into existing web view, + * // instead of being loaded into new browser. (Boolean - default=false) + * super.setBooleanProperty("loadInWebView", true); * - * // Load a splash screen image from the resource drawable directory. - * // (Integer - default=0) - * super.setIntegerProperty("splashscreen", R.drawable.splash); + * // Load a splash screen image from the resource drawable directory. + * // (Integer - default=0) + * super.setIntegerProperty("splashscreen", R.drawable.splash); * - * // Set the background color. - * // (Integer - default=0 or BLACK) - * super.setIntegerProperty("backgroundColor", Color.WHITE); + * // Set the background color. + * // (Integer - default=0 or BLACK) + * super.setIntegerProperty("backgroundColor", Color.WHITE); * - * // Time in msec to wait before triggering a timeout error when loading - * // with super.loadUrl(). (Integer - default=20000) - * super.setIntegerProperty("loadUrlTimeoutValue", 60000); + * // Time in msec to wait before triggering a timeout error when loading + * // with super.loadUrl(). (Integer - default=20000) + * super.setIntegerProperty("loadUrlTimeoutValue", 60000); * - * // URL to load if there's an error loading specified URL with loadUrl(). - * // Should be a local URL starting with file://. (String - default=null) - * super.setStringProperty("errorUrl", "file:///android_asset/www/error.html"); + * // URL to load if there's an error loading specified URL with loadUrl(). + * // Should be a local URL starting with file://. (String - default=null) + * super.setStringProperty("errorUrl", "file:///android_asset/www/error.html"); * - * // Enable app to keep running in background. (Boolean - default=true) - * super.setBooleanProperty("keepRunning", false); + * // Enable app to keep running in background. (Boolean - default=true) + * super.setBooleanProperty("keepRunning", false); */ public class DroidGap extends PhonegapActivity { - // The webview for our app - protected WebView appView; - protected WebViewClient webViewClient; + // The webview for our app + protected WebView appView; + protected WebViewClient webViewClient; - protected LinearLayout root; - public boolean bound = false; - public CallbackServer callbackServer; - protected PluginManager pluginManager; - protected boolean cancelLoadUrl = false; - protected boolean clearHistory = false; - protected ProgressDialog spinnerDialog = null; + protected LinearLayout root; + public boolean bound = false; + public CallbackServer callbackServer; + protected PluginManager pluginManager; + protected boolean cancelLoadUrl = false; + protected boolean clearHistory = false; + protected ProgressDialog spinnerDialog = null; - // The initial URL for our app - // ie http://server/path/index.html#abc?query - private String url; - - // The base of the initial URL for our app. - // Does not include file name. Ends with / - // ie http://server/path/ - private String baseUrl = null; + // The initial URL for our app + // ie http://server/path/index.html#abc?query + private String url; + + // The base of the initial URL for our app. + // Does not include file name. Ends with / + // ie http://server/path/ + private String baseUrl = null; - // Plugin to call when activity result is received - protected IPlugin activityResultCallback = null; - protected boolean activityResultKeepRunning; + // Plugin to call when activity result is received + protected IPlugin activityResultCallback = null; + protected boolean activityResultKeepRunning; - // Flag indicates that a loadUrl timeout occurred - private int loadUrlTimeout = 0; - - // Default background color for activity - // (this is not the color for the webview, which is set in HTML) - private int backgroundColor = Color.BLACK; - - /* - * The variables below are used to cache some of the activity properties. - */ + // Flag indicates that a loadUrl timeout occurred + private int loadUrlTimeout = 0; + + // Default background color for activity + // (this is not the color for the webview, which is set in HTML) + private int backgroundColor = Color.BLACK; + + /* + * The variables below are used to cache some of the activity properties. + */ - // Flag indicates that a URL navigated to from PhoneGap app should be loaded into same webview - // instead of being loaded into the web browser. - protected boolean loadInWebView = false; + // Flag indicates that a URL navigated to from PhoneGap app should be loaded into same webview + // instead of being loaded into the web browser. + protected boolean loadInWebView = false; - // Draw a splash screen using an image located in the drawable resource directory. - // This is not the same as calling super.loadSplashscreen(url) - protected int splashscreen = 0; + // Draw a splash screen using an image located in the drawable resource directory. + // This is not the same as calling super.loadSplashscreen(url) + protected int splashscreen = 0; - // LoadUrl timeout value in msec (default of 20 sec) - protected int loadUrlTimeoutValue = 20000; - - // Keep app running when pause is received. (default = true) - // If true, then the JavaScript and native code continue to run in the background - // when another application (activity) is started. - protected boolean keepRunning = true; + // LoadUrl timeout value in msec (default of 20 sec) + protected int loadUrlTimeoutValue = 20000; + + // Keep app running when pause is received. (default = true) + // If true, then the JavaScript and native code continue to run in the background + // when another application (activity) is started. + protected boolean keepRunning = true; /** * Called when the activity is first created. @@ -183,55 +183,55 @@ public class DroidGap extends PhonegapActivity { */ @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().requestFeature(Window.FEATURE_NO_TITLE); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); - // This builds the view. We could probably get away with NOT having a LinearLayout, but I like having a bucket! + super.onCreate(savedInstanceState); + getWindow().requestFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + // This builds the view. We could probably get away with NOT having a LinearLayout, but I like having a bucket! - Display display = getWindowManager().getDefaultDisplay(); - int width = display.getWidth(); - int height = display.getHeight(); - - root = new LinearLayoutSoftKeyboardDetect(this, width, height); - root.setOrientation(LinearLayout.VERTICAL); - root.setBackgroundColor(this.backgroundColor); - root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, - ViewGroup.LayoutParams.FILL_PARENT, 0.0F)); + Display display = getWindowManager().getDefaultDisplay(); + int width = display.getWidth(); + int height = display.getHeight(); + + root = new LinearLayoutSoftKeyboardDetect(this, width, height); + root.setOrientation(LinearLayout.VERTICAL); + root.setBackgroundColor(this.backgroundColor); + root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.FILL_PARENT, 0.0F)); - // If url was passed in to intent, then init webview, which will load the url - Bundle bundle = this.getIntent().getExtras(); - if (bundle != null) { - String url = bundle.getString("url"); - if (url != null) { - this.init(); - } - } - // Setup the hardware volume controls to handle volume control - setVolumeControlStream(AudioManager.STREAM_MUSIC); + // If url was passed in to intent, then init webview, which will load the url + Bundle bundle = this.getIntent().getExtras(); + if (bundle != null) { + String url = bundle.getString("url"); + if (url != null) { + this.init(); + } + } + // Setup the hardware volume controls to handle volume control + setVolumeControlStream(AudioManager.STREAM_MUSIC); } /** * Create and initialize web container. */ - public void init() { - - // Create web container - this.appView = new WebView(DroidGap.this); - this.appView.setId(100); - - this.appView.setLayoutParams(new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, - ViewGroup.LayoutParams.FILL_PARENT, - 1.0F)); + public void init() { + + // Create web container + this.appView = new WebView(DroidGap.this); + this.appView.setId(100); + + this.appView.setLayoutParams(new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.FILL_PARENT, + 1.0F)); WebViewReflect.checkCompatibility(); if (android.os.Build.VERSION.RELEASE.startsWith("1.")) { - this.appView.setWebChromeClient(new GapClient(DroidGap.this)); + this.appView.setWebChromeClient(new GapClient(DroidGap.this)); } else { - this.appView.setWebChromeClient(new EclairClient(DroidGap.this)); + this.appView.setWebChromeClient(new EclairClient(DroidGap.this)); } this.setWebViewClient(this.appView, new GapViewClient(this)); @@ -271,209 +271,209 @@ public class DroidGap extends PhonegapActivity { // If url specified, then load it String url = this.getStringProperty("url", null); if (url != null) { - System.out.println("Loading initial URL="+url); - this.loadUrl(url); + System.out.println("Loading initial URL="+url); + this.loadUrl(url); } - } - - /** - * Set the WebViewClient. - * - * @param appView - * @param client - */ - protected void setWebViewClient(WebView appView, WebViewClient client) { - this.webViewClient = client; - appView.setWebViewClient(client); - } + } + + /** + * Set the WebViewClient. + * + * @param appView + * @param client + */ + protected void setWebViewClient(WebView appView, WebViewClient client) { + this.webViewClient = client; + appView.setWebViewClient(client); + } /** * Bind PhoneGap objects to JavaScript. * * @param appView */ - private void bindBrowser(WebView appView) { - this.callbackServer = new CallbackServer(); - this.pluginManager = new PluginManager(appView, this); + private void bindBrowser(WebView appView) { + this.callbackServer = new CallbackServer(); + this.pluginManager = new PluginManager(appView, this); - } + } - /** - * Look at activity parameters and process them. - * This must be called from the main UI thread. - */ - private void handleActivityParameters() { + /** + * Look at activity parameters and process them. + * This must be called from the main UI thread. + */ + private void handleActivityParameters() { - // Init web view if not already done - if (this.appView == null) { - this.init(); - } + // Init web view if not already done + if (this.appView == null) { + this.init(); + } - // If backgroundColor - this.backgroundColor = this.getIntegerProperty("backgroundColor", Color.BLACK); - this.root.setBackgroundColor(this.backgroundColor); + // If backgroundColor + this.backgroundColor = this.getIntegerProperty("backgroundColor", Color.BLACK); + this.root.setBackgroundColor(this.backgroundColor); - // If spashscreen - this.splashscreen = this.getIntegerProperty("splashscreen", 0); - if (this.splashscreen != 0) { - root.setBackgroundResource(this.splashscreen); - } + // If spashscreen + this.splashscreen = this.getIntegerProperty("splashscreen", 0); + if (this.splashscreen != 0) { + root.setBackgroundResource(this.splashscreen); + } - // If loadInWebView - this.loadInWebView = this.getBooleanProperty("loadInWebView", false); + // If loadInWebView + this.loadInWebView = this.getBooleanProperty("loadInWebView", false); - // If loadUrlTimeoutValue - int timeout = this.getIntegerProperty("loadUrlTimeoutValue", 0); - if (timeout > 0) { - this.loadUrlTimeoutValue = timeout; - } - - // If keepRunning - this.keepRunning = this.getBooleanProperty("keepRunning", true); - } - + // If loadUrlTimeoutValue + int timeout = this.getIntegerProperty("loadUrlTimeoutValue", 0); + if (timeout > 0) { + this.loadUrlTimeoutValue = timeout; + } + + // If keepRunning + this.keepRunning = this.getBooleanProperty("keepRunning", true); + } + /** * Load the url into the webview. * * @param url */ - public void loadUrl(final String url) { - System.out.println("loadUrl("+url+")"); - this.url = url; - if (this.baseUrl == null) { - int i = url.lastIndexOf('/'); - if (i > 0) { - this.baseUrl = url.substring(0, i+1); - } - else { - this.baseUrl = this.url + "/"; - } - } - System.out.println("url="+url+" baseUrl="+baseUrl); + public void loadUrl(final String url) { + System.out.println("loadUrl("+url+")"); + this.url = url; + if (this.baseUrl == null) { + int i = url.lastIndexOf('/'); + if (i > 0) { + this.baseUrl = url.substring(0, i+1); + } + else { + this.baseUrl = this.url + "/"; + } + } + System.out.println("url="+url+" baseUrl="+baseUrl); - // Load URL on UI thread - final DroidGap me = this; - this.runOnUiThread(new Runnable() { - public void run() { + // Load URL on UI thread + final DroidGap me = this; + this.runOnUiThread(new Runnable() { + public void run() { - // Handle activity parameters - me.handleActivityParameters(); + // Handle activity parameters + me.handleActivityParameters(); - // Initialize callback server - me.callbackServer.init(url); + // Initialize callback server + me.callbackServer.init(url); - // If loadingDialog, then show the App loading dialog - String loading = me.getStringProperty("loadingDialog", null); - if (loading != null) { + // If loadingDialog, then show the App loading dialog + String loading = me.getStringProperty("loadingDialog", null); + if (loading != null) { - String title = ""; - String message = "Loading Application..."; + String title = ""; + String message = "Loading Application..."; - if (loading.length() > 0) { - int comma = loading.indexOf(','); - if (comma > 0) { - title = loading.substring(0, comma); - message = loading.substring(comma+1); - } - else { - title = ""; - message = loading; - } - } - me.spinnerStart(title, message); - } + if (loading.length() > 0) { + int comma = loading.indexOf(','); + if (comma > 0) { + title = loading.substring(0, comma); + message = loading.substring(comma+1); + } + else { + title = ""; + message = loading; + } + } + me.spinnerStart(title, message); + } - // Create a timeout timer for loadUrl - final int currentLoadUrlTimeout = me.loadUrlTimeout; - Runnable runnable = new Runnable() { - public void run() { - try { - synchronized(this) { - wait(me.loadUrlTimeoutValue); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } + // Create a timeout timer for loadUrl + final int currentLoadUrlTimeout = me.loadUrlTimeout; + Runnable runnable = new Runnable() { + public void run() { + try { + synchronized(this) { + wait(me.loadUrlTimeoutValue); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } - // If timeout, then stop loading and handle error - if (me.loadUrlTimeout == currentLoadUrlTimeout) { - me.appView.stopLoading(); - me.webViewClient.onReceivedError(me.appView, -6, "The connection to the server was unsuccessful.", url); - } - } - }; - Thread thread = new Thread(runnable); - thread.start(); - me.appView.loadUrl(url); - } - }); - } - - /** - * Load the url into the webview after waiting for period of time. - * This is used to display the splashscreen for certain amount of time. - * - * @param url - * @param time The number of ms to wait before loading webview - */ - public void loadUrl(final String url, final int time) { - System.out.println("loadUrl("+url+","+time+")"); - final DroidGap me = this; + // If timeout, then stop loading and handle error + if (me.loadUrlTimeout == currentLoadUrlTimeout) { + me.appView.stopLoading(); + me.webViewClient.onReceivedError(me.appView, -6, "The connection to the server was unsuccessful.", url); + } + } + }; + Thread thread = new Thread(runnable); + thread.start(); + me.appView.loadUrl(url); + } + }); + } + + /** + * Load the url into the webview after waiting for period of time. + * This is used to display the splashscreen for certain amount of time. + * + * @param url + * @param time The number of ms to wait before loading webview + */ + public void loadUrl(final String url, final int time) { + System.out.println("loadUrl("+url+","+time+")"); + final DroidGap me = this; - // Handle activity parameters - this.runOnUiThread(new Runnable() { - public void run() { - me.handleActivityParameters(); - } - }); + // Handle activity parameters + this.runOnUiThread(new Runnable() { + public void run() { + me.handleActivityParameters(); + } + }); - Runnable runnable = new Runnable() { - public void run() { - try { - synchronized(this) { - this.wait(time); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - if (!me.cancelLoadUrl) { - me.loadUrl(url); - } - else{ - me.cancelLoadUrl = false; - System.out.println("Aborting loadUrl("+url+"): Another URL was loaded before timer expired."); - } - } - }; - Thread thread = new Thread(runnable); - thread.start(); - } - - /** - * Cancel loadUrl before it has been loaded. - */ - public void cancelLoadUrl() { - this.cancelLoadUrl = true; - } - - /** - * Clear the resource cache. - */ - public void clearCache() { - if (this.appView == null) { - this.init(); - } - this.appView.clearCache(true); - } + Runnable runnable = new Runnable() { + public void run() { + try { + synchronized(this) { + this.wait(time); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (!me.cancelLoadUrl) { + me.loadUrl(url); + } + else{ + me.cancelLoadUrl = false; + System.out.println("Aborting loadUrl("+url+"): Another URL was loaded before timer expired."); + } + } + }; + Thread thread = new Thread(runnable); + thread.start(); + } + + /** + * Cancel loadUrl before it has been loaded. + */ + public void cancelLoadUrl() { + this.cancelLoadUrl = true; + } + + /** + * Clear the resource cache. + */ + public void clearCache() { + if (this.appView == null) { + this.init(); + } + this.appView.clearCache(true); + } /** * Clear web history in this web view. */ public void clearHistory() { - this.clearHistory = true; - if (this.appView != null) { - this.appView.clearHistory(); - } + this.clearHistory = true; + if (this.appView != null) { + this.appView.clearHistory(); + } } @Override @@ -483,8 +483,8 @@ public class DroidGap extends PhonegapActivity { * @param Configuration newConfig */ public void onConfigurationChanged(Configuration newConfig) { - //don't reload the current page when the orientation is changed - super.onConfigurationChanged(newConfig); + //don't reload the current page when the orientation is changed + super.onConfigurationChanged(newConfig); } /** @@ -495,15 +495,15 @@ public class DroidGap extends PhonegapActivity { * @return */ public boolean getBooleanProperty(String name, boolean defaultValue) { - Bundle bundle = this.getIntent().getExtras(); - if (bundle == null) { - return defaultValue; - } - Boolean p = (Boolean)bundle.get(name); - if (p == null) { - return defaultValue; - } - return p.booleanValue(); + Bundle bundle = this.getIntent().getExtras(); + if (bundle == null) { + return defaultValue; + } + Boolean p = (Boolean)bundle.get(name); + if (p == null) { + return defaultValue; + } + return p.booleanValue(); } /** @@ -514,15 +514,15 @@ public class DroidGap extends PhonegapActivity { * @return */ public int getIntegerProperty(String name, int defaultValue) { - Bundle bundle = this.getIntent().getExtras(); - if (bundle == null) { - return defaultValue; - } - Integer p = (Integer)bundle.get(name); - if (p == null) { - return defaultValue; - } - return p.intValue(); + Bundle bundle = this.getIntent().getExtras(); + if (bundle == null) { + return defaultValue; + } + Integer p = (Integer)bundle.get(name); + if (p == null) { + return defaultValue; + } + return p.intValue(); } /** @@ -533,15 +533,15 @@ public class DroidGap extends PhonegapActivity { * @return */ public String getStringProperty(String name, String defaultValue) { - Bundle bundle = this.getIntent().getExtras(); - if (bundle == null) { - return defaultValue; - } - String p = bundle.getString(name); - if (p == null) { - return defaultValue; - } - return p; + Bundle bundle = this.getIntent().getExtras(); + if (bundle == null) { + return defaultValue; + } + String p = bundle.getString(name); + if (p == null) { + return defaultValue; + } + return p; } /** @@ -552,15 +552,15 @@ public class DroidGap extends PhonegapActivity { * @return */ public double getDoubleProperty(String name, double defaultValue) { - Bundle bundle = this.getIntent().getExtras(); - if (bundle == null) { - return defaultValue; - } - Double p = (Double)bundle.get(name); - if (p == null) { - return defaultValue; - } - return p.doubleValue(); + Bundle bundle = this.getIntent().getExtras(); + if (bundle == null) { + return defaultValue; + } + Double p = (Double)bundle.get(name); + if (p == null) { + return defaultValue; + } + return p.doubleValue(); } /** @@ -570,7 +570,7 @@ public class DroidGap extends PhonegapActivity { * @param value */ public void setBooleanProperty(String name, boolean value) { - this.getIntent().putExtra(name, value); + this.getIntent().putExtra(name, value); } /** @@ -580,7 +580,7 @@ public class DroidGap extends PhonegapActivity { * @param value */ public void setIntegerProperty(String name, int value) { - this.getIntent().putExtra(name, value); + this.getIntent().putExtra(name, value); } /** @@ -590,7 +590,7 @@ public class DroidGap extends PhonegapActivity { * @param value */ public void setStringProperty(String name, String value) { - this.getIntent().putExtra(name, value); + this.getIntent().putExtra(name, value); } /** @@ -600,7 +600,7 @@ public class DroidGap extends PhonegapActivity { * @param value */ public void setDoubleProperty(String name, double value) { - this.getIntent().putExtra(name, value); + this.getIntent().putExtra(name, value); } @Override @@ -610,20 +610,20 @@ public class DroidGap extends PhonegapActivity { protected void onPause() { super.onPause(); if (this.appView == null) { - return; + return; } - // Send pause event to JavaScript - this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};"); + // Send pause event to JavaScript + this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};"); - // Forward to plugins - this.pluginManager.onPause(this.keepRunning); + // Forward to plugins + this.pluginManager.onPause(this.keepRunning); // If app doesn't want to run in background if (!this.keepRunning) { - // Pause JavaScript timers (including setInterval) - this.appView.pauseTimers(); + // Pause JavaScript timers (including setInterval) + this.appView.pauseTimers(); } } @@ -632,10 +632,10 @@ public class DroidGap extends PhonegapActivity { * Called when the activity receives a new intent **/ protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); + super.onNewIntent(intent); - //Forward to plugins - this.pluginManager.onNewIntent(intent); + //Forward to plugins + this.pluginManager.onNewIntent(intent); } @Override @@ -645,26 +645,26 @@ public class DroidGap extends PhonegapActivity { protected void onResume() { super.onResume(); if (this.appView == null) { - return; + return; } - // Send resume event to JavaScript - this.appView.loadUrl("javascript:try{PhoneGap.onResume.fire();}catch(e){};"); + // Send resume event to JavaScript + this.appView.loadUrl("javascript:try{PhoneGap.onResume.fire();}catch(e){};"); - // Forward to plugins - this.pluginManager.onResume(this.keepRunning || this.activityResultKeepRunning); + // Forward to plugins + this.pluginManager.onResume(this.keepRunning || this.activityResultKeepRunning); // If app doesn't want to run in background if (!this.keepRunning || this.activityResultKeepRunning) { - // Restore multitasking state - if (this.activityResultKeepRunning) { - this.keepRunning = this.activityResultKeepRunning; - this.activityResultKeepRunning = false; - } + // Restore multitasking state + if (this.activityResultKeepRunning) { + this.keepRunning = this.activityResultKeepRunning; + this.activityResultKeepRunning = false; + } - // Resume JavaScript timers (including setInterval) - this.appView.resumeTimers(); + // Resume JavaScript timers (including setInterval) + this.appView.resumeTimers(); } } @@ -673,21 +673,21 @@ public class DroidGap extends PhonegapActivity { * The final call you receive before your activity is destroyed. */ public void onDestroy() { - super.onDestroy(); - + super.onDestroy(); + if (this.appView != null) { - - // Make sure pause event is sent if onPause hasn't been called before onDestroy - this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};"); + + // Make sure pause event is sent if onPause hasn't been called before onDestroy + this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};"); - // Send destroy event to JavaScript - this.appView.loadUrl("javascript:try{PhoneGap.onDestroy.fire();}catch(e){};"); + // Send destroy event to JavaScript + this.appView.loadUrl("javascript:try{PhoneGap.onDestroy.fire();}catch(e){};"); - // Load blank page so that JavaScript onunload is called - this.appView.loadUrl("about:blank"); - - // Forward to plugins - this.pluginManager.onDestroy(); + // Load blank page so that JavaScript onunload is called + this.appView.loadUrl("about:blank"); + + // Forward to plugins + this.pluginManager.onDestroy(); } } @@ -699,7 +699,7 @@ public class DroidGap extends PhonegapActivity { * @param className */ public void addService(String serviceType, String className) { - this.pluginManager.addService(serviceType, className); + this.pluginManager.addService(serviceType, className); } /** @@ -709,7 +709,7 @@ public class DroidGap extends PhonegapActivity { * @param message */ public void sendJavascript(String statement) { - this.callbackServer.sendJavascript(statement); + this.callbackServer.sendJavascript(statement); } @@ -721,85 +721,85 @@ public class DroidGap extends PhonegapActivity { * * @param url The url to load. * @param usePhoneGap Load url in PhoneGap webview. - * @param clearPrev Clear the activity stack, so new app becomes top of stack - * @param params DroidGap parameters for new app + * @param clearPrev Clear the activity stack, so new app becomes top of stack + * @param params DroidGap parameters for new app * @throws android.content.ActivityNotFoundException */ public void showWebPage(String url, boolean usePhoneGap, boolean clearPrev, HashMap params) throws android.content.ActivityNotFoundException { - Intent intent = null; - if (usePhoneGap) { - try { - intent = new Intent().setClass(this, Class.forName(this.getComponentName().getClassName())); - intent.putExtra("url", url); + Intent intent = null; + if (usePhoneGap) { + try { + intent = new Intent().setClass(this, Class.forName(this.getComponentName().getClassName())); + intent.putExtra("url", url); - // Add parameters - if (params != null) { - java.util.Set> s = params.entrySet(); - java.util.Iterator> it = s.iterator(); - while(it.hasNext()) { - Entry entry = it.next(); - String key = entry.getKey(); - Object value = entry.getValue(); - if (value == null) { - } - else if (value.getClass().equals(String.class)) { - intent.putExtra(key, (String)value); - } - else if (value.getClass().equals(Boolean.class)) { - intent.putExtra(key, (Boolean)value); - } - else if (value.getClass().equals(Integer.class)) { - intent.putExtra(key, (Integer)value); - } - } + // Add parameters + if (params != null) { + java.util.Set> s = params.entrySet(); + java.util.Iterator> it = s.iterator(); + while(it.hasNext()) { + Entry entry = it.next(); + String key = entry.getKey(); + Object value = entry.getValue(); + if (value == null) { + } + else if (value.getClass().equals(String.class)) { + intent.putExtra(key, (String)value); + } + else if (value.getClass().equals(Boolean.class)) { + intent.putExtra(key, (Boolean)value); + } + else if (value.getClass().equals(Integer.class)) { + intent.putExtra(key, (Integer)value); + } + } - } - } catch (ClassNotFoundException e) { - e.printStackTrace(); - intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - } - } - else { - intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - } - this.startActivity(intent); - - // Finish current activity - if (clearPrev) { - this.finish(); - } + } + } catch (ClassNotFoundException e) { + e.printStackTrace(); + intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + } + } + else { + intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + } + this.startActivity(intent); + + // Finish current activity + if (clearPrev) { + this.finish(); + } } /** * Show the spinner. Must be called from the UI thread. * - * @param title Title of the dialog - * @param message The message of the dialog + * @param title Title of the dialog + * @param message The message of the dialog */ public void spinnerStart(final String title, final String message) { - if (this.spinnerDialog != null) { - this.spinnerDialog.dismiss(); - this.spinnerDialog = null; - } - final DroidGap me = this; - this.spinnerDialog = ProgressDialog.show(DroidGap.this, title , message, true, true, - new DialogInterface.OnCancelListener() { - public void onCancel(DialogInterface dialog) { - me.spinnerDialog = null; - } - }); + if (this.spinnerDialog != null) { + this.spinnerDialog.dismiss(); + this.spinnerDialog = null; + } + final DroidGap me = this; + this.spinnerDialog = ProgressDialog.show(DroidGap.this, title , message, true, true, + new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface dialog) { + me.spinnerDialog = null; + } + }); } /** * Stop spinner. */ public void spinnerStop() { - if (this.spinnerDialog != null) { - this.spinnerDialog.dismiss(); - this.spinnerDialog = null; - } + if (this.spinnerDialog != null) { + this.spinnerDialog.dismiss(); + this.spinnerDialog = null; + } } /** @@ -834,11 +834,11 @@ public class DroidGap extends PhonegapActivity { dlg.setTitle("Alert"); dlg.setCancelable(false); dlg.setPositiveButton(android.R.string.ok, - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - result.confirm(); - } - }); + new AlertDialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + result.confirm(); + } + }); dlg.create(); dlg.show(); return true; @@ -859,15 +859,15 @@ public class DroidGap extends PhonegapActivity { dlg.setTitle("Confirm"); dlg.setCancelable(false); dlg.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - result.confirm(); + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + result.confirm(); } }); dlg.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - result.cancel(); + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + result.cancel(); } }); dlg.create(); @@ -888,91 +888,91 @@ public class DroidGap extends PhonegapActivity { */ @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { - - // Security check to make sure any requests are coming from the page initially - // loaded in webview and not another loaded in an iframe. - boolean reqOk = false; - if (url.indexOf(this.ctx.baseUrl) == 0) { - reqOk = true; - } - - // Calling PluginManager.exec() to call a native service using - // prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true])); - if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) { - JSONArray array; - try { - array = new JSONArray(defaultValue.substring(4)); - String service = array.getString(0); - String action = array.getString(1); - String callbackId = array.getString(2); - boolean async = array.getBoolean(3); - String r = pluginManager.exec(service, action, callbackId, message, async); - result.confirm(r); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - // Polling for JavaScript messages - else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) { - String r = callbackServer.getJavascript(); - result.confirm(r); - } - - // Calling into CallbackServer - else if (reqOk && defaultValue != null && defaultValue.equals("gap_callbackServer:")) { - String r = ""; - if (message.equals("usePolling")) { - r = ""+callbackServer.usePolling(); - } - else if (message.equals("restartServer")) { - callbackServer.restartServer(); - } - else if (message.equals("getPort")) { - r = Integer.toString(callbackServer.getPort()); - } - else if (message.equals("getToken")) { - r = callbackServer.getToken(); - } - result.confirm(r); - } - - // PhoneGap JS has initialized, so show webview - // (This solves white flash seen when rendering HTML) - else if (reqOk && defaultValue != null && defaultValue.equals("gap_init:")) { - appView.setVisibility(View.VISIBLE); - ctx.spinnerStop(); - result.confirm("OK"); - } + + // Security check to make sure any requests are coming from the page initially + // loaded in webview and not another loaded in an iframe. + boolean reqOk = false; + if (url.indexOf(this.ctx.baseUrl) == 0) { + reqOk = true; + } + + // Calling PluginManager.exec() to call a native service using + // prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true])); + if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) { + JSONArray array; + try { + array = new JSONArray(defaultValue.substring(4)); + String service = array.getString(0); + String action = array.getString(1); + String callbackId = array.getString(2); + boolean async = array.getBoolean(3); + String r = pluginManager.exec(service, action, callbackId, message, async); + result.confirm(r); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + // Polling for JavaScript messages + else if (reqOk && defaultValue.equals("gap_poll:")) { + String r = callbackServer.getJavascript(); + result.confirm(r); + } + + // Calling into CallbackServer + else if (reqOk && defaultValue.equals("gap_callbackServer:")) { + String r = ""; + if (message.equals("usePolling")) { + r = ""+callbackServer.usePolling(); + } + else if (message.equals("restartServer")) { + callbackServer.restartServer(); + } + else if (message.equals("getPort")) { + r = Integer.toString(callbackServer.getPort()); + } + else if (message.equals("getToken")) { + r = callbackServer.getToken(); + } + result.confirm(r); + } + + // PhoneGap JS has initialized, so show webview + // (This solves white flash seen when rendering HTML) + else if (reqOk && defaultValue.equals("gap_init:")) { + appView.setVisibility(View.VISIBLE); + ctx.spinnerStop(); + result.confirm("OK"); + } - // Show dialog - else { - final JsPromptResult res = result; - AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx); - dlg.setMessage(message); - final EditText input = new EditText(this.ctx); - if (defaultValue != null) { - input.setText(defaultValue); - } - dlg.setView(input); - dlg.setCancelable(false); - dlg.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - String usertext = input.getText().toString(); - res.confirm(usertext); - } - }); - dlg.setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - res.cancel(); - } - }); - dlg.create(); - dlg.show(); - } - return true; + // Show dialog + else { + final JsPromptResult res = result; + AlertDialog.Builder dlg = new AlertDialog.Builder(this.ctx); + dlg.setMessage(message); + final EditText input = new EditText(this.ctx); + if (defaultValue != null) { + input.setText(defaultValue); + } + dlg.setView(input); + dlg.setCancelable(false); + dlg.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + String usertext = input.getText().toString(); + res.confirm(usertext); + } + }); + dlg.setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + res.cancel(); + } + }); + dlg.create(); + dlg.show(); + } + return true; } } @@ -982,69 +982,69 @@ public class DroidGap extends PhonegapActivity { */ public class EclairClient extends GapClient { - private String TAG = "PhoneGapLog"; - private long MAX_QUOTA = 100 * 1024 * 1024; + private String TAG = "PhoneGapLog"; + private long MAX_QUOTA = 100 * 1024 * 1024; - /** - * Constructor. - * - * @param ctx - */ - public EclairClient(Context ctx) { - super(ctx); - } + /** + * Constructor. + * + * @param ctx + */ + public EclairClient(Context ctx) { + super(ctx); + } - /** - * Handle database quota exceeded notification. - * - * @param url - * @param databaseIdentifier - * @param currentQuota - * @param estimatedSize - * @param totalUsedQuota - * @param quotaUpdater - */ - @Override - public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize, - long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) - { - Log.d(TAG, "event raised onExceededDatabaseQuota estimatedSize: " + Long.toString(estimatedSize) + " currentQuota: " + Long.toString(currentQuota) + " totalUsedQuota: " + Long.toString(totalUsedQuota)); + /** + * Handle database quota exceeded notification. + * + * @param url + * @param databaseIdentifier + * @param currentQuota + * @param estimatedSize + * @param totalUsedQuota + * @param quotaUpdater + */ + @Override + public void onExceededDatabaseQuota(String url, String databaseIdentifier, long currentQuota, long estimatedSize, + long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) + { + Log.d(TAG, "event raised onExceededDatabaseQuota estimatedSize: " + Long.toString(estimatedSize) + " currentQuota: " + Long.toString(currentQuota) + " totalUsedQuota: " + Long.toString(totalUsedQuota)); - if( estimatedSize < MAX_QUOTA) - { - //increase for 1Mb - long newQuota = estimatedSize; - Log.d(TAG, "calling quotaUpdater.updateQuota newQuota: " + Long.toString(newQuota) ); - quotaUpdater.updateQuota(newQuota); - } - else - { - // Set the quota to whatever it is and force an error - // TODO: get docs on how to handle this properly - quotaUpdater.updateQuota(currentQuota); - } - } + if( estimatedSize < MAX_QUOTA) + { + //increase for 1Mb + long newQuota = estimatedSize; + Log.d(TAG, "calling quotaUpdater.updateQuota newQuota: " + Long.toString(newQuota) ); + quotaUpdater.updateQuota(newQuota); + } + else + { + // Set the quota to whatever it is and force an error + // TODO: get docs on how to handle this properly + quotaUpdater.updateQuota(currentQuota); + } + } - // console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html - @Override - public void onConsoleMessage(String message, int lineNumber, String sourceID) - { - // This is a kludgy hack!!!! - Log.d(TAG, sourceID + ": Line " + Integer.toString(lineNumber) + " : " + message); - } + // console.log in api level 7: http://developer.android.com/guide/developing/debug-tasks.html + @Override + public void onConsoleMessage(String message, int lineNumber, String sourceID) + { + // This is a kludgy hack!!!! + Log.d(TAG, sourceID + ": Line " + Integer.toString(lineNumber) + " : " + message); + } - @Override - /** - * Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin. - * - * @param origin - * @param callback - */ - public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) { - // TODO Auto-generated method stub - super.onGeolocationPermissionsShowPrompt(origin, callback); - callback.invoke(origin, true, false); - } + @Override + /** + * Instructs the client to show a prompt to ask the user to set the Geolocation permission state for the specified origin. + * + * @param origin + * @param callback + */ + public void onGeolocationPermissionsShowPrompt(String origin, Callback callback) { + // TODO Auto-generated method stub + super.onGeolocationPermissionsShowPrompt(origin, callback); + callback.invoke(origin, true, false); + } } @@ -1068,221 +1068,221 @@ public class DroidGap extends PhonegapActivity { * Give the host application a chance to take over the control when a new url * is about to be loaded in the current WebView. * - * @param view The WebView that is initiating the callback. - * @param url The url to be loaded. - * @return true to override, false for default behavior + * @param view The WebView that is initiating the callback. + * @param url The url to be loaded. + * @return true to override, false for default behavior */ @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { - // If dialing phone (tel:5551212) - if (url.startsWith(WebView.SCHEME_TEL)) { - try { - Intent intent = new Intent(Intent.ACTION_DIAL); - intent.setData(Uri.parse(url)); - startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error dialing "+url+": "+ e.toString()); - } - return true; - } - - // If displaying map (geo:0,0?q=address) - else if (url.startsWith("geo:")) { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error showing map "+url+": "+ e.toString()); - } - return true; - } - - // If sending email (mailto:abc@corp.com) - else if (url.startsWith(WebView.SCHEME_MAILTO)) { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error sending email "+url+": "+ e.toString()); - } - return true; - } - - // If sms:5551212?body=This is the message - else if (url.startsWith("sms:")) { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); + // If dialing phone (tel:5551212) + if (url.startsWith(WebView.SCHEME_TEL)) { + try { + Intent intent = new Intent(Intent.ACTION_DIAL); + intent.setData(Uri.parse(url)); + startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error dialing "+url+": "+ e.toString()); + } + return true; + } + + // If displaying map (geo:0,0?q=address) + else if (url.startsWith("geo:")) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error showing map "+url+": "+ e.toString()); + } + return true; + } + + // If sending email (mailto:abc@corp.com) + else if (url.startsWith(WebView.SCHEME_MAILTO)) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error sending email "+url+": "+ e.toString()); + } + return true; + } + + // If sms:5551212?body=This is the message + else if (url.startsWith("sms:")) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); - // Get address - String address = null; - int parmIndex = url.indexOf('?'); - if (parmIndex == -1) { - address = url.substring(4); - } - else { - address = url.substring(4, parmIndex); + // Get address + String address = null; + int parmIndex = url.indexOf('?'); + if (parmIndex == -1) { + address = url.substring(4); + } + else { + address = url.substring(4, parmIndex); - // If body, then set sms body - Uri uri = Uri.parse(url); - String query = uri.getQuery(); - if (query != null) { - if (query.startsWith("body=")) { - intent.putExtra("sms_body", query.substring(5)); - } - } - } - intent.setData(Uri.parse("sms:"+address)); - intent.putExtra("address", address); - intent.setType("vnd.android-dir/mms-sms"); - startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error sending sms "+url+":"+ e.toString()); - } - return true; - } + // If body, then set sms body + Uri uri = Uri.parse(url); + String query = uri.getQuery(); + if (query != null) { + if (query.startsWith("body=")) { + intent.putExtra("sms_body", query.substring(5)); + } + } + } + intent.setData(Uri.parse("sms:"+address)); + intent.putExtra("address", address); + intent.setType("vnd.android-dir/mms-sms"); + startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error sending sms "+url+":"+ e.toString()); + } + return true; + } - // All else - else { + // All else + else { - // If our app or file:, then load into our webview - // NOTE: This replaces our app with new URL. When BACK is pressed, - // our app is reloaded and restarted. All state is lost. - if (this.ctx.loadInWebView || url.startsWith("file://") || url.indexOf(this.ctx.baseUrl) == 0) { - try { - // Init parameters to new DroidGap activity and propagate existing parameters - HashMap params = new HashMap(); - String loadingPage = this.ctx.getStringProperty("loadingPageDialog", null); - if (loadingPage != null) { - params.put("loadingDialog", loadingPage); - params.put("loadingPageDialog", loadingPage); - } - if (this.ctx.loadInWebView) { - params.put("loadInWebView", true); - } - params.put("keepRunning", this.ctx.keepRunning); - params.put("loadUrlTimeoutValue", this.ctx.loadUrlTimeoutValue); - String errorUrl = this.ctx.getStringProperty("errorUrl", null); - if (errorUrl != null) { - params.put("errorUrl", errorUrl); - } - params.put("backgroundColor", this.ctx.backgroundColor); + // If our app or file:, then load into our webview + // NOTE: This replaces our app with new URL. When BACK is pressed, + // our app is reloaded and restarted. All state is lost. + if (this.ctx.loadInWebView || url.startsWith("file://") || url.indexOf(this.ctx.baseUrl) == 0) { + try { + // Init parameters to new DroidGap activity and propagate existing parameters + HashMap params = new HashMap(); + String loadingPage = this.ctx.getStringProperty("loadingPageDialog", null); + if (loadingPage != null) { + params.put("loadingDialog", loadingPage); + params.put("loadingPageDialog", loadingPage); + } + if (this.ctx.loadInWebView) { + params.put("loadInWebView", true); + } + params.put("keepRunning", this.ctx.keepRunning); + params.put("loadUrlTimeoutValue", this.ctx.loadUrlTimeoutValue); + String errorUrl = this.ctx.getStringProperty("errorUrl", null); + if (errorUrl != null) { + params.put("errorUrl", errorUrl); + } + params.put("backgroundColor", this.ctx.backgroundColor); - this.ctx.showWebPage(url, true, false, params); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error loading url into DroidGap - "+url+":"+ e.toString()); - } - } - - // If not our application, let default viewer handle - else { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - startActivity(intent); - } catch (android.content.ActivityNotFoundException e) { - System.out.println("Error loading url "+url+":"+ e.toString()); - } - } - return true; - } + this.ctx.showWebPage(url, true, false, params); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error loading url into DroidGap - "+url+":"+ e.toString()); + } + } + + // If not our application, let default viewer handle + else { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + startActivity(intent); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error loading url "+url+":"+ e.toString()); + } + } + return true; + } } - + /** * Notify the host application that a page has finished loading. * - * @param view The webview initiating the callback. - * @param url The url of the page. + * @param view The webview initiating the callback. + * @param url The url of the page. */ @Override public void onPageFinished(WebView view, String url) { - super.onPageFinished(view, url); + super.onPageFinished(view, url); - // Clear timeout flag - this.ctx.loadUrlTimeout++; + // Clear timeout flag + this.ctx.loadUrlTimeout++; - // Try firing the onNativeReady event in JS. If it fails because the JS is - // not loaded yet then just set a flag so that the onNativeReady can be fired - // from the JS side when the JS gets to that code. - if (!url.equals("about:blank")) { - appView.loadUrl("javascript:try{ PhoneGap.onNativeReady.fire();}catch(e){_nativeReady = true;}"); - } + // Try firing the onNativeReady event in JS. If it fails because the JS is + // not loaded yet then just set a flag so that the onNativeReady can be fired + // from the JS side when the JS gets to that code. + if (!url.equals("about:blank")) { + appView.loadUrl("javascript:try{ PhoneGap.onNativeReady.fire();}catch(e){_nativeReady = true;}"); + } - // Make app visible after 2 sec in case there was a JS error and PhoneGap JS never initialized correctly - Thread t = new Thread(new Runnable() { - public void run() { - try { - Thread.sleep(2000); - ctx.runOnUiThread(new Runnable() { - public void run() { - appView.setVisibility(View.VISIBLE); - ctx.spinnerStop(); - } - }); - } catch (InterruptedException e) { - } - } - }); - t.start(); - + // Make app visible after 2 sec in case there was a JS error and PhoneGap JS never initialized correctly + Thread t = new Thread(new Runnable() { + public void run() { + try { + Thread.sleep(2000); + ctx.runOnUiThread(new Runnable() { + public void run() { + appView.setVisibility(View.VISIBLE); + ctx.spinnerStop(); + } + }); + } catch (InterruptedException e) { + } + } + }); + t.start(); + - // Clear history, so that previous screen isn't there when Back button is pressed - if (this.ctx.clearHistory) { - this.ctx.clearHistory = false; - this.ctx.appView.clearHistory(); - } - - // Shutdown if blank loaded - if (url.equals("about:blank")) { - if (this.ctx.callbackServer != null) { - this.ctx.callbackServer.destroy(); - } - } + // Clear history, so that previous screen isn't there when Back button is pressed + if (this.ctx.clearHistory) { + this.ctx.clearHistory = false; + this.ctx.appView.clearHistory(); + } + + // Shutdown if blank loaded + if (url.equals("about:blank")) { + if (this.ctx.callbackServer != null) { + this.ctx.callbackServer.destroy(); + } + } } /** * Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable). * The errorCode parameter corresponds to one of the ERROR_* constants. * - * @param view The WebView that is initiating the callback. - * @param errorCode The error code corresponding to an ERROR_* value. - * @param description A String describing the error. - * @param failingUrl The url that failed to load. + * @param view The WebView that is initiating the callback. + * @param errorCode The error code corresponding to an ERROR_* value. + * @param description A String describing the error. + * @param failingUrl The url that failed to load. */ @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { - System.out.println("onReceivedError: Error code="+errorCode+" Description="+description+" URL="+failingUrl); + System.out.println("onReceivedError: Error code="+errorCode+" Description="+description+" URL="+failingUrl); - // Clear timeout flag - this.ctx.loadUrlTimeout++; + // Clear timeout flag + this.ctx.loadUrlTimeout++; - // Stop "app loading" spinner if showing - this.ctx.spinnerStop(); + // Stop "app loading" spinner if showing + this.ctx.spinnerStop(); - // Handle error - this.ctx.onReceivedError(errorCode, description, failingUrl); + // Handle error + this.ctx.onReceivedError(errorCode, description, failingUrl); } public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { - final String packageName = this.ctx.getPackageName(); - final PackageManager pm = this.ctx.getPackageManager(); - ApplicationInfo appInfo; - try { - appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); - if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { - // debug = true - handler.proceed(); - return; - } else { - // debug = false - super.onReceivedSslError(view, handler, error); - } - } catch (NameNotFoundException e) { - // When it doubt, lock it out! - super.onReceivedSslError(view, handler, error); - } + final String packageName = this.ctx.getPackageName(); + final PackageManager pm = this.ctx.getPackageManager(); + ApplicationInfo appInfo; + try { + appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); + if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { + // debug = true + handler.proceed(); + return; + } else { + // debug = false + super.onReceivedSslError(view, handler, error); + } + } catch (NameNotFoundException e) { + // When it doubt, lock it out! + super.onReceivedSslError(view, handler, error); + } } } @@ -1295,47 +1295,47 @@ public class DroidGap extends PhonegapActivity { @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (this.appView == null) { - return super.onKeyDown(keyCode, event); + return super.onKeyDown(keyCode, event); } - // If back key - if (keyCode == KeyEvent.KEYCODE_BACK) { + // If back key + if (keyCode == KeyEvent.KEYCODE_BACK) { - // If back key is bound, then send event to JavaScript - if (this.bound) { - this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('backbutton');"); - return true; - } + // If back key is bound, then send event to JavaScript + if (this.bound) { + this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('backbutton');"); + return true; + } - // If not bound - else { + // If not bound + else { - // Go to previous page in webview if it is possible to go back - if (this.appView.canGoBack()) { - this.appView.goBack(); - return true; - } + // Go to previous page in webview if it is possible to go back + if (this.appView.canGoBack()) { + this.appView.goBack(); + return true; + } - // If not, then invoke behavior of super class - else { - return super.onKeyDown(keyCode, event); - } - } - } + // If not, then invoke behavior of super class + else { + return super.onKeyDown(keyCode, event); + } + } + } - // If menu key - else if (keyCode == KeyEvent.KEYCODE_MENU) { - this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('menubutton');"); - return true; - } + // If menu key + else if (keyCode == KeyEvent.KEYCODE_MENU) { + this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('menubutton');"); + return true; + } - // If search key - else if (keyCode == KeyEvent.KEYCODE_SEARCH) { - this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('searchbutton');"); - return true; - } + // If search key + else if (keyCode == KeyEvent.KEYCODE_SEARCH) { + this.appView.loadUrl("javascript:PhoneGap.fireDocumentEvent('searchbutton');"); + return true; + } - return false; + return false; } /** @@ -1344,36 +1344,36 @@ public class DroidGap extends PhonegapActivity { * * This is done to eliminate the need to modify DroidGap.java to receive activity results. * - * @param intent The intent to start - * @param requestCode Identifies who to send the result to + * @param intent The intent to start + * @param requestCode Identifies who to send the result to * * @throws RuntimeException */ @Override public void startActivityForResult(Intent intent, int requestCode) throws RuntimeException { - System.out.println("startActivityForResult(intent,"+requestCode+")"); - super.startActivityForResult(intent, requestCode); + System.out.println("startActivityForResult(intent,"+requestCode+")"); + super.startActivityForResult(intent, requestCode); } /** * Launch an activity for which you would like a result when it finished. When this activity exits, * your onActivityResult() method will be called. * - * @param command The command object - * @param intent The intent to start - * @param requestCode The request code that is passed to callback to identify the activity + * @param command The command object + * @param intent The intent to start + * @param requestCode The request code that is passed to callback to identify the activity */ public void startActivityForResult(IPlugin command, Intent intent, int requestCode) { - this.activityResultCallback = command; - this.activityResultKeepRunning = this.keepRunning; - - // If multitasking turned on, then disable it for activities that return results - if (command != null) { - this.keepRunning = false; - } - - // Start activity - super.startActivityForResult(intent, requestCode); + this.activityResultCallback = command; + this.activityResultKeepRunning = this.keepRunning; + + // If multitasking turned on, then disable it for activities that return results + if (command != null) { + this.keepRunning = false; + } + + // Start activity + super.startActivityForResult(intent, requestCode); } @Override @@ -1381,52 +1381,52 @@ public class DroidGap extends PhonegapActivity { * 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"). + * @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"). */ protected void onActivityResult(int requestCode, int resultCode, Intent intent) { - super.onActivityResult(requestCode, resultCode, intent); - IPlugin callback = this.activityResultCallback; - if (callback != null) { - callback.onActivityResult(requestCode, resultCode, intent); - } + super.onActivityResult(requestCode, resultCode, intent); + IPlugin callback = this.activityResultCallback; + if (callback != null) { + callback.onActivityResult(requestCode, resultCode, intent); + } } @Override public void setActivityResultCallback(IPlugin plugin) { - this.activityResultCallback = plugin; + this.activityResultCallback = plugin; } /** * Report an error to the host application. These errors are unrecoverable (i.e. the main resource is unavailable). * The errorCode parameter corresponds to one of the ERROR_* constants. * - * @param errorCode The error code corresponding to an ERROR_* value. - * @param description A String describing the error. - * @param failingUrl The url that failed to load. + * @param errorCode The error code corresponding to an ERROR_* value. + * @param description A String describing the error. + * @param failingUrl The url that failed to load. */ public void onReceivedError(int errorCode, String description, String failingUrl) { - final DroidGap me = this; + final DroidGap me = this; - // If errorUrl specified, then load it - final String errorUrl = me.getStringProperty("errorUrl", null); - if ((errorUrl != null) && errorUrl.startsWith("file://") && (!failingUrl.equals(errorUrl))) { + // If errorUrl specified, then load it + final String errorUrl = me.getStringProperty("errorUrl", null); + if ((errorUrl != null) && errorUrl.startsWith("file://") && (!failingUrl.equals(errorUrl))) { - // Load URL on UI thread - me.runOnUiThread(new Runnable() { - public void run() { - me.appView.loadUrl(errorUrl); - } - }); - } + // Load URL on UI thread + me.runOnUiThread(new Runnable() { + public void run() { + me.appView.loadUrl(errorUrl); + } + }); + } - // If not, then display error dialog - else { - me.appView.loadUrl("about:blank"); - me.displayError("Application Error", description + " ("+failingUrl+")", "OK", true); - } + // If not, then display error dialog + else { + me.appView.loadUrl("about:blank"); + me.displayError("Application Error", description + " ("+failingUrl+")", "OK", true); + } } /** @@ -1438,26 +1438,26 @@ public class DroidGap extends PhonegapActivity { * @param exit */ public void displayError(final String title, final String message, final String button, final boolean exit) { - final DroidGap me = this; - me.runOnUiThread(new Runnable() { - public void run() { - AlertDialog.Builder dlg = new AlertDialog.Builder(me); - dlg.setMessage(message); - dlg.setTitle(title); - dlg.setCancelable(false); - dlg.setPositiveButton(button, - new AlertDialog.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - if (exit) { - me.finish(); - } - } - }); - dlg.create(); - dlg.show(); - } - }); + final DroidGap me = this; + me.runOnUiThread(new Runnable() { + public void run() { + AlertDialog.Builder dlg = new AlertDialog.Builder(me); + dlg.setMessage(message); + dlg.setTitle(title); + dlg.setCancelable(false); + dlg.setPositiveButton(button, + new AlertDialog.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + if (exit) { + me.finish(); + } + } + }); + dlg.create(); + dlg.show(); + } + }); } /** @@ -1466,77 +1466,77 @@ public class DroidGap extends PhonegapActivity { */ class LinearLayoutSoftKeyboardDetect extends LinearLayout { - private static final String LOG_TAG = "SoftKeyboardDetect"; - - private int oldHeight = 0; // Need to save the old height as not to send redundant events - private int oldWidth = 0; // Need to save old width for orientation change - private int screenWidth = 0; - private int screenHeight = 0; - - public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) { - super(context); - screenWidth = width; - screenHeight = height; - } + private static final String LOG_TAG = "SoftKeyboardDetect"; + + private int oldHeight = 0; // Need to save the old height as not to send redundant events + private int oldWidth = 0; // Need to save old width for orientation change + private int screenWidth = 0; + private int screenHeight = 0; + + public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) { + super(context); + screenWidth = width; + screenHeight = height; + } - @Override - /** - * Start listening to new measurement events. Fire events when the height - * gets smaller fire a show keyboard event and when height gets bigger fire - * a hide keyboard event. - * - * Note: We are using callbackServer.sendJavascript() instead of - * this.appView.loadUrl() as changing the URL of the app would cause the - * soft keyboard to go away. - * - * @param widthMeasureSpec - * @param heightMeasureSpec - */ - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - Log.d(LOG_TAG, "We are in our onMeasure method"); + @Override + /** + * Start listening to new measurement events. Fire events when the height + * gets smaller fire a show keyboard event and when height gets bigger fire + * a hide keyboard event. + * + * Note: We are using callbackServer.sendJavascript() instead of + * this.appView.loadUrl() as changing the URL of the app would cause the + * soft keyboard to go away. + * + * @param widthMeasureSpec + * @param heightMeasureSpec + */ + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + Log.d(LOG_TAG, "We are in our onMeasure method"); - // Get the current height of the visible part of the screen. - // This height will not included the status bar. - int height = MeasureSpec.getSize(heightMeasureSpec); - int width = MeasureSpec.getSize(widthMeasureSpec); - - Log.d(LOG_TAG, "Old Height = " + oldHeight); - Log.d(LOG_TAG, "Height = " + height); - Log.d(LOG_TAG, "Old Width = " + oldWidth); - Log.d(LOG_TAG, "Width = " + width); - - - // If the oldHeight = 0 then this is the first measure event as the app starts up. - // If oldHeight == height then we got a measurement change that doesn't affect us. - if (oldHeight == 0 || oldHeight == height) { - Log.d(LOG_TAG, "Ignore this event"); - } - // Account for orientation change and ignore this event/Fire orientation change - else if(screenHeight == width) - { - int tmp_var = screenHeight; - screenHeight = screenWidth; - screenWidth = tmp_var; - Log.d(LOG_TAG, "Orientation Change"); - } - // If the height as gotten bigger then we will assume the soft keyboard has - // gone away. - else if (height > oldHeight) { - Log.d(LOG_TAG, "Throw hide keyboard event"); - callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('hidekeyboard');"); - } - // If the height as gotten smaller then we will assume the soft keyboard has - // been displayed. - else if (height < oldHeight) { - Log.d(LOG_TAG, "Throw show keyboard event"); - callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('showkeyboard');"); - } + // Get the current height of the visible part of the screen. + // This height will not included the status bar. + int height = MeasureSpec.getSize(heightMeasureSpec); + int width = MeasureSpec.getSize(widthMeasureSpec); + + Log.d(LOG_TAG, "Old Height = " + oldHeight); + Log.d(LOG_TAG, "Height = " + height); + Log.d(LOG_TAG, "Old Width = " + oldWidth); + Log.d(LOG_TAG, "Width = " + width); + + + // If the oldHeight = 0 then this is the first measure event as the app starts up. + // If oldHeight == height then we got a measurement change that doesn't affect us. + if (oldHeight == 0 || oldHeight == height) { + Log.d(LOG_TAG, "Ignore this event"); + } + // Account for orientation change and ignore this event/Fire orientation change + else if(screenHeight == width) + { + int tmp_var = screenHeight; + screenHeight = screenWidth; + screenWidth = tmp_var; + Log.d(LOG_TAG, "Orientation Change"); + } + // If the height as gotten bigger then we will assume the soft keyboard has + // gone away. + else if (height > oldHeight) { + Log.d(LOG_TAG, "Throw hide keyboard event"); + callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('hidekeyboard');"); + } + // If the height as gotten smaller then we will assume the soft keyboard has + // been displayed. + else if (height < oldHeight) { + Log.d(LOG_TAG, "Throw show keyboard event"); + callbackServer.sendJavascript("PhoneGap.fireDocumentEvent('showkeyboard');"); + } - // Update the old height for the next event - oldHeight = height; - oldWidth = width; - } + // Update the old height for the next event + oldHeight = height; + oldWidth = width; + } } -} +} \ No newline at end of file diff --git a/framework/src/com/phonegap/ExifHelper.java b/framework/src/com/phonegap/ExifHelper.java new file mode 100644 index 00000000..cd9e0a0c --- /dev/null +++ b/framework/src/com/phonegap/ExifHelper.java @@ -0,0 +1,153 @@ +/* + * PhoneGap is available under *either* the terms of the modified BSD license *or* the + * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. + * + * Copyright (c) 2011, IBM Corporation + */ +package com.phonegap; + +import java.io.IOException; + +import android.media.ExifInterface; + +public class ExifHelper { + private String aperature = null; + private String datetime = null; + private String exposureTime = null; + private String flash = null; + private String focalLength = null; + private String gpsAltitude = null; + private String gpsAltitudeRef = null; + private String gpsDateStamp = null; + private String gpsLatitude = null; + private String gpsLatitudeRef = null; + private String gpsLongitude = null; + private String gpsLongitudeRef = null; + private String gpsProcessingMethod = null; + private String gpsTimestamp = null; + private String iso = null; + private String make = null; + private String model = null; + private String orientation = null; + private String whiteBalance = null; + + private ExifInterface inFile = null; + private ExifInterface outFile = null; + + /** + * The file before it is compressed + * + * @param filePath + * @throws IOException + */ + public void createInFile(String filePath) throws IOException { + this.inFile = new ExifInterface(filePath); + } + + /** + * The file after it has been compressed + * + * @param filePath + * @throws IOException + */ + public void createOutFile(String filePath) throws IOException { + this.outFile = new ExifInterface(filePath); + } + + /** + * Reads all the EXIF data from the input file. + */ + public void readExifData() { + this.aperature = inFile.getAttribute(ExifInterface.TAG_APERTURE); + this.datetime = inFile.getAttribute(ExifInterface.TAG_DATETIME); + this.exposureTime = inFile.getAttribute(ExifInterface.TAG_EXPOSURE_TIME); + this.flash = inFile.getAttribute(ExifInterface.TAG_FLASH); + this.focalLength = inFile.getAttribute(ExifInterface.TAG_FOCAL_LENGTH); + this.gpsAltitude = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE); + this.gpsAltitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF); + this.gpsDateStamp = inFile.getAttribute(ExifInterface.TAG_GPS_DATESTAMP); + this.gpsLatitude = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE); + this.gpsLatitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF); + this.gpsLongitude = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE); + this.gpsLongitudeRef = inFile.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF); + this.gpsProcessingMethod = inFile.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD); + this.gpsTimestamp = inFile.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP); + this.iso = inFile.getAttribute(ExifInterface.TAG_ISO); + this.make = inFile.getAttribute(ExifInterface.TAG_MAKE); + this.model = inFile.getAttribute(ExifInterface.TAG_MODEL); + this.orientation = inFile.getAttribute(ExifInterface.TAG_ORIENTATION); + this.whiteBalance = inFile.getAttribute(ExifInterface.TAG_WHITE_BALANCE); + } + + /** + * Writes the previously stored EXIF data to the output file. + * + * @throws IOException + */ + public void writeExifData() throws IOException { + // Don't try to write to a null file + if (this.outFile == null) { + return; + } + + if (this.aperature != null) { + this.outFile.setAttribute(ExifInterface.TAG_APERTURE, this.aperature); + } + if (this.datetime != null) { + this.outFile.setAttribute(ExifInterface.TAG_DATETIME, this.datetime); + } + if (this.exposureTime != null) { + this.outFile.setAttribute(ExifInterface.TAG_EXPOSURE_TIME, this.exposureTime); + } + if (this.flash != null) { + this.outFile.setAttribute(ExifInterface.TAG_FLASH, this.flash); + } + if (this.focalLength != null) { + this.outFile.setAttribute(ExifInterface.TAG_FOCAL_LENGTH, this.focalLength); + } + if (this.gpsAltitude != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE, this.gpsAltitude); + } + if (this.gpsAltitudeRef != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, this.gpsAltitudeRef); + } + if (this.gpsDateStamp != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_DATESTAMP, this.gpsDateStamp); + } + if (this.gpsLatitude != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE, this.gpsLatitude); + } + if (this.gpsLatitudeRef != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, this.gpsLatitudeRef); + } + if (this.gpsLongitude != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, this.gpsLongitude); + } + if (this.gpsLongitudeRef != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, this.gpsLongitudeRef); + } + if (this.gpsProcessingMethod != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD, this.gpsProcessingMethod); + } + if (this.gpsTimestamp != null) { + this.outFile.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, this.gpsTimestamp); + } + if (this.iso != null) { + this.outFile.setAttribute(ExifInterface.TAG_ISO, this.iso); + } + if (this.make != null) { + this.outFile.setAttribute(ExifInterface.TAG_MAKE, this.make); + } + if (this.model != null) { + this.outFile.setAttribute(ExifInterface.TAG_MODEL, this.model); + } + if (this.orientation != null) { + this.outFile.setAttribute(ExifInterface.TAG_ORIENTATION, this.orientation); + } + if (this.whiteBalance != null) { + this.outFile.setAttribute(ExifInterface.TAG_WHITE_BALANCE, this.whiteBalance); + } + + this.outFile.saveAttributes(); + } +} \ No newline at end of file diff --git a/framework/src/com/phonegap/FileUtils.java b/framework/src/com/phonegap/FileUtils.java index 240a507e..6d6ad655 100755 --- a/framework/src/com/phonegap/FileUtils.java +++ b/framework/src/com/phonegap/FileUtils.java @@ -25,6 +25,7 @@ import android.provider.MediaStore; import android.util.Log; import android.webkit.MimeTypeMap; +import com.phonegap.api.PhonegapActivity; import com.phonegap.api.Plugin; import com.phonegap.api.PluginResult; import com.phonegap.file.EncodingException; @@ -39,7 +40,8 @@ import com.phonegap.file.TypeMismatchException; */ public class FileUtils extends Plugin { private static final String LOG_TAG = "FileUtils"; - + private static final String _DATA = "_data"; // The column name where the file path is stored + public static int NOT_FOUND_ERR = 1; public static int SECURITY_ERR = 2; public static int ABORT_ERR = 3; @@ -988,5 +990,19 @@ public class FileUtils extends Plugin { return new FileInputStream(path); } } + + /** + * Queries the media store to find out what the file path is for the Uri we supply + * + * @param contentUri the Uri of the audio/image/video + * @param ctx the current applicaiton context + * @return the full path to the file + */ + protected static String getRealPathFromURI(Uri contentUri, PhonegapActivity ctx) { + String[] proj = { _DATA }; + Cursor cursor = ctx.managedQuery(contentUri, proj, null, null, null); + int column_index = cursor.getColumnIndexOrThrow(_DATA); + cursor.moveToFirst(); + return cursor.getString(column_index); + } } - From 8a5dec8d8b8610fc09c351be44d70a77cefb3d7e Mon Sep 17 00:00:00 2001 From: Bryce Curtis Date: Mon, 22 Aug 2011 16:22:22 -0500 Subject: [PATCH 04/11] Re-checkin commit for "Fix Issue #203: Prompt crashes on Android 3.2 tablet." --- framework/src/com/phonegap/DroidGap.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index fc210341..0b6b4d50 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -914,13 +914,13 @@ public class DroidGap extends PhonegapActivity { } // Polling for JavaScript messages - else if (reqOk && defaultValue.equals("gap_poll:")) { + else if (reqOk && defaultValue != null && defaultValue.equals("gap_poll:")) { String r = callbackServer.getJavascript(); result.confirm(r); } // Calling into CallbackServer - else if (reqOk && defaultValue.equals("gap_callbackServer:")) { + else if (reqOk && defaultValue != null && defaultValue.equals("gap_callbackServer:")) { String r = ""; if (message.equals("usePolling")) { r = ""+callbackServer.usePolling(); @@ -939,7 +939,7 @@ public class DroidGap extends PhonegapActivity { // PhoneGap JS has initialized, so show webview // (This solves white flash seen when rendering HTML) - else if (reqOk && defaultValue.equals("gap_init:")) { + else if (reqOk && defaultValue != null && defaultValue.equals("gap_init:")) { appView.setVisibility(View.VISIBLE); ctx.spinnerStop(); result.confirm("OK"); From facb752cc7a43d83354026ab4cb6f6501a433fbf Mon Sep 17 00:00:00 2001 From: macdonst Date: Fri, 26 Aug 2011 00:14:50 +0800 Subject: [PATCH 05/11] Fix for Issue #208: Media.release() accidentally makes a call to the Media error callback --- framework/src/com/phonegap/AudioPlayer.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/src/com/phonegap/AudioPlayer.java b/framework/src/com/phonegap/AudioPlayer.java index df90795a..0f8c0606 100755 --- a/framework/src/com/phonegap/AudioPlayer.java +++ b/framework/src/com/phonegap/AudioPlayer.java @@ -86,7 +86,10 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On // Stop any play or record if (this.mPlayer != null) { - this.stopPlaying(); + if ((this.state == MEDIA_RUNNING) || (this.state == MEDIA_PAUSED)) { + this.mPlayer.stop(); + this.setState(MEDIA_STOPPED); + } this.mPlayer.release(); this.mPlayer = null; } From 8d35b1aeef40b400d677283866d695fc7639cce9 Mon Sep 17 00:00:00 2001 From: macdonst Date: Sat, 27 Aug 2011 04:04:57 +0800 Subject: [PATCH 06/11] Fix for Issue #210: devready event never fires if we can't get network connection info --- framework/assets/js/network.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/framework/assets/js/network.js b/framework/assets/js/network.js index f71bad50..f174b6da 100755 --- a/framework/assets/js/network.js +++ b/framework/assets/js/network.js @@ -47,6 +47,12 @@ var Connection = function() { } }, function(e) { + // If we can't get the network info we should still tell PhoneGap + // to fire the deviceready event. + if (me._firstRun) { + me._firstRun = false; + PhoneGap.onPhoneGapConnectionReady.fire(); + } console.log("Error initializing Network Connection: " + e); }); }; From 3e5a52ceeedc18804a6d11650c748ef4ee991cb1 Mon Sep 17 00:00:00 2001 From: Dave Johnson Date: Tue, 30 Aug 2011 16:52:56 -0700 Subject: [PATCH 07/11] Add overrideUrlLoading overriding ... yeah ... to plugins by the plugin adding a and implmenting the onOverrideUrlLoading(...) method --- framework/src/com/phonegap/api/IPlugin.java | 9 +- .../com/phonegap/api/PhonegapActivity.java | 7 ++ framework/src/com/phonegap/api/Plugin.java | 4 + .../src/com/phonegap/api/PluginManager.java | 88 ++++++++++++------- 4 files changed, 77 insertions(+), 31 deletions(-) diff --git a/framework/src/com/phonegap/api/IPlugin.java b/framework/src/com/phonegap/api/IPlugin.java index 5da8e563..7edb30a2 100755 --- a/framework/src/com/phonegap/api/IPlugin.java +++ b/framework/src/com/phonegap/api/IPlugin.java @@ -87,4 +87,11 @@ public interface IPlugin { */ void onActivityResult(int requestCode, int resultCode, Intent intent); -} + /** + * By specifying a in plugins.xml you can map a URL (using startsWith atm) to this method. + * + * @param url The URL that is trying to be loaded in the PhoneGap webview. + * @return Return true to prevent the URL from loading. Default is false. + */ + boolean onOverrideUrlLoading(String url); +} \ No newline at end of file diff --git a/framework/src/com/phonegap/api/PhonegapActivity.java b/framework/src/com/phonegap/api/PhonegapActivity.java index 558bad40..30e3c615 100755 --- a/framework/src/com/phonegap/api/PhonegapActivity.java +++ b/framework/src/com/phonegap/api/PhonegapActivity.java @@ -47,4 +47,11 @@ public abstract class PhonegapActivity extends Activity { * @param plugin The plugin on which onActivityResult is to be called */ abstract public void setActivityResultCallback(IPlugin plugin); + + /** + * Load the specified URL in the PhoneGap webview. + * + * @param url The URL to load. + */ + abstract public void loadUrl(String url); } diff --git a/framework/src/com/phonegap/api/Plugin.java b/framework/src/com/phonegap/api/Plugin.java index 282f60f4..7efd2ed2 100755 --- a/framework/src/com/phonegap/api/Plugin.java +++ b/framework/src/com/phonegap/api/Plugin.java @@ -104,6 +104,10 @@ public abstract class Plugin implements IPlugin { public void onActivityResult(int requestCode, int resultCode, Intent intent) { } + public boolean onOverrideUrlLoading(String url) { + return false; + } + /** * Send generic JavaScript statement back to JavaScript. * success(...) and error(...) should be used instead where possible. diff --git a/framework/src/com/phonegap/api/PluginManager.java b/framework/src/com/phonegap/api/PluginManager.java index 90de9778..04080072 100755 --- a/framework/src/com/phonegap/api/PluginManager.java +++ b/framework/src/com/phonegap/api/PluginManager.java @@ -9,6 +9,7 @@ package com.phonegap.api; import java.io.IOException; import java.util.HashMap; +import java.util.Iterator; import java.util.Map.Entry; import org.json.JSONArray; @@ -17,6 +18,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.content.Intent; import android.content.res.XmlResourceParser; +import android.util.Log; import android.webkit.WebView; /** @@ -25,7 +27,7 @@ import android.webkit.WebView; * Calling native plugin code can be done by calling PluginManager.exec(...) * from JavaScript. */ -public final class PluginManager { +public final class PluginManager { private HashMap plugins = new HashMap(); private HashMap services = new HashMap(); @@ -33,6 +35,10 @@ public final class PluginManager { private final PhonegapActivity ctx; private final WebView app; + // Map URL schemes like foo: to plugins that want to handle those schemes + // This would allow how all URLs are handled to be offloaded to a plugin + protected HashMap urlMap = new HashMap(); + /** * Constructor. * @@ -53,14 +59,17 @@ public final class PluginManager { if (id == 0) { pluginConfigurationMissing(); } XmlResourceParser xml = ctx.getResources().getXml(id); int eventType = -1; + String pluginClass = "", pluginName = ""; while (eventType != XmlResourceParser.END_DOCUMENT) { if (eventType == XmlResourceParser.START_TAG) { String strNode = xml.getName(); if (strNode.equals("plugin")) { - String name = xml.getAttributeValue(null, "name"); - String value = xml.getAttributeValue(null, "value"); + pluginClass = xml.getAttributeValue(null, "value"); + pluginName = xml.getAttributeValue(null, "name"); //System.out.println("Plugin: "+name+" => "+value); - this.addService(name, value); + this.addService(pluginName, pluginClass); + } else if (strNode.equals("url-filter")) { + this.urlMap.put(xml.getAttributeValue(null, "value"), pluginName); } } try { @@ -101,14 +110,9 @@ public final class PluginManager { boolean runAsync = async; try { final JSONArray args = new JSONArray(jsonArgs); - String clazz = this.services.get(service); - Class c = null; - if (clazz != null) { - c = getClassByName(clazz); - } - if (isPhoneGapPlugin(c)) { - final IPlugin plugin = this.addPlugin(clazz, c); - final PhonegapActivity ctx = this.ctx; + final IPlugin plugin = this.getPlugin(service); + final PhonegapActivity ctx = this.ctx; + if (plugin != null) { runAsync = async && !plugin.isSynch(action); if (runAsync) { // Run this on a different thread so that this one can return back to JS @@ -150,8 +154,6 @@ public final class PluginManager { } } } - } catch (ClassNotFoundException e) { - cr = new PluginResult(PluginResult.Status.CLASS_NOT_FOUND_EXCEPTION); } catch (JSONException e) { System.out.println("ERROR: "+e.toString()); cr = new PluginResult(PluginResult.Status.JSON_EXCEPTION); @@ -175,7 +177,11 @@ public final class PluginManager { */ @SuppressWarnings("unchecked") private Class getClassByName(final String clazz) throws ClassNotFoundException { - return Class.forName(clazz); + Class c = null; + if (clazz != null) { + c = Class.forName(clazz); + } + return c; } /** @@ -203,18 +209,17 @@ public final class PluginManager { * @return The plugin */ @SuppressWarnings("unchecked") - private IPlugin addPlugin(String className, Class clazz) { - if (this.plugins.containsKey(className)) { - return this.getPlugin(className); - } - try { - IPlugin plugin = (IPlugin)clazz.newInstance(); - this.plugins.put(className, plugin); - plugin.setContext(this.ctx); - plugin.setView(this.app); - return plugin; - } - catch (Exception e) { + private IPlugin addPlugin(String pluginName, String className) { + try { + Class c = getClassByName(className); + if (isPhoneGapPlugin(c)) { + IPlugin plugin = (IPlugin)c.newInstance(); + this.plugins.put(className, plugin); + plugin.setContext(this.ctx); + plugin.setView(this.app); + return plugin; + } + } catch (Exception e) { e.printStackTrace(); System.out.println("Error adding plugin "+className+"."); } @@ -224,12 +229,18 @@ public final class PluginManager { /** * Get the loaded plugin. * + * If the plugin is not already loaded then load it. + * * @param className The class of the loaded plugin. * @return */ - private IPlugin getPlugin(String className) { - IPlugin plugin = this.plugins.get(className); - return plugin; + private IPlugin getPlugin(String pluginName) { + String className = this.services.get(pluginName); + if (this.plugins.containsKey(className)) { + return this.plugins.get(className); + } else { + return this.addPlugin(pluginName, className); + } } /** @@ -299,6 +310,23 @@ public final class PluginManager { } } + /** + * Called when the URL of the webview changes. + * + * @param url The URL that is being changed to. + * @return Return false to allow the URL to load, return true to prevent the URL from loading. + */ + public boolean onOverrideUrlLoading(String url) { + Iterator> it = this.urlMap.entrySet().iterator(); + while (it.hasNext()) { + HashMap.Entry pairs = it.next(); + if (url.startsWith(pairs.getKey())) { + return this.getPlugin(pairs.getValue()).onOverrideUrlLoading(url); + } + } + return false; + } + private void pluginConfigurationMissing() { System.err.println("====================================================================================="); System.err.println("ERROR: plugin.xml is missing. Add res/xml/plugins.xml to your project."); From d9ec6df5a846ae079b8d70004615e3371f5fdb95 Mon Sep 17 00:00:00 2001 From: macdonst Date: Fri, 2 Sep 2011 02:35:52 +0800 Subject: [PATCH 08/11] Fix for Issue #218: audio capture audio/3gpp mimetype getting set to video/3gpp Works around an issue where MimeTypeMap.getMimeTypeFromExtension() always returns video/3gpp when the file extension is .3gp or .3gpp. --- framework/src/com/phonegap/Capture.java | 28 ++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/framework/src/com/phonegap/Capture.java b/framework/src/com/phonegap/Capture.java index e9261442..0a843564 100644 --- a/framework/src/com/phonegap/Capture.java +++ b/framework/src/com/phonegap/Capture.java @@ -29,6 +29,10 @@ import com.phonegap.api.PluginResult; public class Capture extends Plugin { + private static final String VIDEO_3GPP = "video/3gpp"; + private static final String AUDIO_3GPP = "audio/3gpp"; + private static final String IMAGE_JPEG = "image/jpeg"; + private static final int CAPTURE_AUDIO = 0; // Constant for capture audio private static final int CAPTURE_IMAGE = 1; // Constant for capture image private static final int CAPTURE_VIDEO = 2; // Constant for capture video @@ -97,14 +101,15 @@ public class Capture extends Plugin { if (mimeType == null || mimeType.equals("")) { mimeType = FileUtils.getMimeType(filePath); } + Log.d(LOG_TAG, "Mime type = " + mimeType); - if (mimeType.equals("image/jpeg") || filePath.endsWith(".jpg")) { + if (mimeType.equals(IMAGE_JPEG) || filePath.endsWith(".jpg")) { obj = getImageData(filePath, obj); } - else if (filePath.endsWith("audio/3gpp")) { + else if (mimeType.endsWith(AUDIO_3GPP)) { obj = getAudioVideoData(filePath, obj, false); } - else if (mimeType.equals("video/3gpp")) { + else if (mimeType.equals(VIDEO_3GPP)) { obj = getAudioVideoData(filePath, obj, true); } } @@ -233,7 +238,7 @@ public class Capture extends Plugin { // 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"); + 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); @@ -329,7 +334,20 @@ public class Capture extends Plugin { // File properties obj.put("name", fp.getName()); obj.put("fullPath", fp.getAbsolutePath()); - obj.put("type", FileUtils.getMimeType(fp.getAbsolutePath())); + + // Because of an issue with MimeTypeMap.getMimeTypeFromExtension() all .3gpp files + // are reported as video/3gpp. I'm doing this hacky check of the URI to see if it + // is stored in the audio or video content store. + if (fp.getAbsoluteFile().toString().endsWith(".3gp") || fp.getAbsoluteFile().toString().endsWith(".3gpp")) { + if (data.toString().contains("/audio/")) { + obj.put("type", AUDIO_3GPP); + } else { + obj.put("type", VIDEO_3GPP); + } + } else { + obj.put("type", FileUtils.getMimeType(fp.getAbsolutePath())); + } + obj.put("lastModifiedDate", fp.lastModified()); obj.put("size", fp.length()); } catch (JSONException e) { From a6ae85b4ea586a14a006c5e0589978b46eb476e6 Mon Sep 17 00:00:00 2001 From: macdonst Date: Fri, 2 Sep 2011 08:37:33 +0800 Subject: [PATCH 09/11] Fix for Issue #220: Android audio streaming doesn't work for https --- framework/src/com/phonegap/AudioPlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/com/phonegap/AudioPlayer.java b/framework/src/com/phonegap/AudioPlayer.java index 0f8c0606..e2f10680 100755 --- a/framework/src/com/phonegap/AudioPlayer.java +++ b/framework/src/com/phonegap/AudioPlayer.java @@ -313,7 +313,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * @return T=streaming, F=local */ public boolean isStreaming(String file) { - if (file.contains("http://")) { + if (file.contains("http://") || file.contains("https://")) { return true; } else { From 4f121aa07d21702d2b834ca78afabcc092a43e54 Mon Sep 17 00:00:00 2001 From: macdonst Date: Sat, 3 Sep 2011 01:32:29 +0800 Subject: [PATCH 10/11] Fix bad tel: link in example/index.html --- example/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/index.html b/example/index.html index 094754fb..2ed97039 100644 --- a/example/index.html +++ b/example/index.html @@ -25,7 +25,7 @@ Toggle Accelerometer Get Location - Call 411 + Call 411 Beep Vibrate Get a Picture From 91f4097fd8815358ea6dbf0157d9f094d4e7825a Mon Sep 17 00:00:00 2001 From: macdonst Date: Tue, 6 Sep 2011 23:23:50 +0800 Subject: [PATCH 11/11] Fix for Issue #222: Android plugin FileUploader with UTF-8 in params --- framework/src/com/phonegap/FileTransfer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/com/phonegap/FileTransfer.java b/framework/src/com/phonegap/FileTransfer.java index b73961c7..6a0059b6 100644 --- a/framework/src/com/phonegap/FileTransfer.java +++ b/framework/src/com/phonegap/FileTransfer.java @@ -274,7 +274,7 @@ public class FileTransfer extends Plugin { dos.writeBytes(LINE_START + BOUNDRY + LINE_END); dos.writeBytes("Content-Disposition: form-data; name=\"" + key.toString() + "\";"); dos.writeBytes(LINE_END + LINE_END); - dos.writeBytes(params.getString(key.toString())); + dos.write(params.getString(key.toString()).getBytes()); dos.writeBytes(LINE_END); } } catch (JSONException e) {