diff --git a/example/index.html b/example/index.html index e12e895b..bdfee7c3 100644 --- a/example/index.html +++ b/example/index.html @@ -101,7 +101,10 @@ function get_contacts() { - navigator.ContactManager.getAllContacts(count_contacts, fail, null); + var obj = new ContactFindOptions(); + obj.filter=""; + obj.multiple=true; + navigator.service.contacts.find(["displayName", "phoneNumbers", "emails"], count_contacts, fail, obj); } function count_contacts(contacts) diff --git a/framework/assets/js/contact.js b/framework/assets/js/contact.js index 7debac8d..1d5eb4c0 100644 --- a/framework/assets/js/contact.js +++ b/framework/assets/js/contact.js @@ -1,72 +1,127 @@ -var Contact = function() { - this.name = new ContactName(); - this.emails = []; - this.phones = []; +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; }; -var ContactName = function() { - this.formatted = ""; - this.familyName = ""; - this.givenName = ""; - this.additionalNames = []; - this.prefixes = []; - this.suffixes = []; +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; }; -var ContactEmail = function() { - this.types = []; - this.address = ""; +var ContactField = function(type, value, primary) { + this.type = type || null; + this.value = value || null; + this.primary = primary || null; }; -var ContactPhoneNumber = function() { - this.types = []; - this.number = ""; +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; }; +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; +}; + +var ContactAccount = function(domain, username, userid) { + this.domain = domain || null; + this.username = username || null; + this.userid = userid || null; +} + var Contacts = function() { - this.records = []; -}; + this.inProgress = false; + this.records = new Array(); +} -Contacts.prototype.find = function(obj, win, fail) { +// Contacts.prototype.find = function(obj, win, fail) { +Contacts.prototype.find = function(fields, win, fail, options) { this.win = win; this.fail = fail; - if(obj.name != null) { - // Build up the search term that we'll use in SQL, based on the structure/contents of the contact object passed into find. - var searchTerm = ''; - if (obj.name.givenName && obj.name.givenName.length > 0) { - searchTerm = obj.name.givenName.split(' ').join('%'); - } - if (obj.name.familyName && obj.name.familyName.length > 0) { - searchTerm += obj.name.familyName.split(' ').join('%'); - } - if (!obj.name.familyName && !obj.name.givenName && obj.name.formatted) { - searchTerm = obj.name.formatted; - } - PhoneGap.execAsync(null, null, "Contacts", "search", [searchTerm, "", ""]); - } + + PhoneGap.execAsync(null, null, "Contacts", "search", [fields, options]); }; -Contacts.prototype.droidFoundContact = function(name, npa, email) { - var contact = new Contact(); - contact.name = new ContactName(); - contact.name.formatted = name; - contact.name.givenName = name; - var mail = new ContactEmail(); - mail.types.push("home"); - mail.address = email; - contact.emails.push(mail); - phone = new ContactPhoneNumber(); - phone.types.push("home"); - phone.number = npa; - contact.phones.push(phone); - this.records.push(contact); +Contacts.prototype.droidDone = function(contacts) { + this.win(eval('(' + contacts + ')')); }; -Contacts.prototype.droidDone = function() { - this.win(this.records); +Contacts.prototype.remove = function(contact) { + }; +Contacts.prototype.save = function(contact) { + +}; + +Contacts.prototype.create = function(contact) { + +}; + +Contacts.prototype.m_foundContacts = function(win, contacts) { + this.inProgress = false; + win(contacts); +}; + +var ContactFindOptions = function(filter, multiple, limit, updatedSince) { + this.filter = filter || ''; + this.multiple = multiple || true; + this.limit = limit || Number.MAX_VALUE; + this.updatedSince = updatedSince || ''; +}; + +var ContactError = function() { + this.code=null; +}; + +ContactError.INVALID_ARGUMENT_ERROR = 0; +ContactError.IO_ERROR = 1; +ContactError.NOT_FOUND_ERROR = 2; +ContactError.NOT_SUPPORTED_ERROR = 3; +ContactError.PENDING_OPERATION_ERROR = 4; +ContactError.PERMISSION_DENIED_ERROR = 5; +ContactError.TIMEOUT_ERROR = 6; +ContactError.UNKNOWN_ERROR = 7; + PhoneGap.addConstructor(function() { - if(typeof navigator.contacts == "undefined") navigator.contacts = new Contacts(); + if(typeof navigator.service == "undefined") navigator.service = new Object(); + if(typeof navigator.service.contacts == "undefined") navigator.service.contacts = new Contacts(); }); diff --git a/framework/assets/js/notification.js b/framework/assets/js/notification.js index e80c3e40..e78e9a5e 100644 --- a/framework/assets/js/notification.js +++ b/framework/assets/js/notification.js @@ -41,7 +41,7 @@ Notification.prototype.blink = function(count, colour) { * @param {Integer} mills The number of milliseconds to vibrate for. */ Notification.prototype.vibrate = function(mills) { - PhoneGap.execAsync(null, null, "Device", "vibrate", [mills]); + PhoneGap.execAsync(null, null, "Notification", "vibrate", [mills]); }; /** @@ -51,7 +51,7 @@ Notification.prototype.vibrate = function(mills) { * @param {Integer} count The number of beeps. */ Notification.prototype.beep = function(count) { - PhoneGap.execAsync(null, null, "Device", "beep", [count]); + PhoneGap.execAsync(null, null, "Notification", "beep", [count]); }; // TODO: of course on Blackberry and Android there notifications in the UI as well diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base index dbfe0343..0c31d018 100755 --- a/framework/assets/js/phonegap.js.base +++ b/framework/assets/js/phonegap.js.base @@ -290,6 +290,28 @@ PhoneGap.stringify = function(args) { 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 { s = s + '"' + args[i] + '"'; } diff --git a/framework/assets/www/phonegap.js b/framework/assets/www/phonegap.js index 4b417f95..0964a8eb 100644 --- a/framework/assets/www/phonegap.js +++ b/framework/assets/www/phonegap.js @@ -1,10 +1,28 @@ + +/** + * The order of events during page load and PhoneGap startup is as follows: + * + * onDOMContentLoaded Internal event that is received when the web page is loaded and parsed. + * window.onload Body onload event. + * onNativeReady Internal event that indicates the PhoneGap native side is ready. + * onPhoneGapInit Internal event that kicks off creation of all PhoneGap JavaScript objects (runs constructors). + * onPhoneGapReady Internal event fired when all PhoneGap JavaScript objects have been created + * onPhoneGapInfoReady Internal event fired when device properties are available + * onDeviceReady User event fired to indicate that PhoneGap is ready + * onResume User event fired to indicate a start/resume lifecycle event + * + * The only PhoneGap events that user code should register for are: + * onDeviceReady + * onResume + * + * Listeners can be registered as: + * document.addEventListener("deviceready", myDeviceReadyListener, false); + * document.addEventListener("resume", myResumeListener, false); + */ + if (typeof(DeviceInfo) != 'object') DeviceInfo = {}; -var com = {}; - -com.phonegap = {}; - /** * This represents the PhoneGap API itself, and provides a global namespace for accessing * information about the state of PhoneGap. @@ -127,7 +145,7 @@ PhoneGap.available = DeviceInfo.uuid != undefined; * @param {Function} func The function callback you want run once PhoneGap is initialized */ PhoneGap.addConstructor = function(func) { - PhoneGap.onDeviceReady.subscribeOnce(function() { + PhoneGap.onPhoneGapInit.subscribeOnce(function() { try { func(); } catch(e) { @@ -165,6 +183,23 @@ PhoneGap.onDOMContentLoaded = new PhoneGap.Channel('onDOMContentLoaded'); */ PhoneGap.onNativeReady = new PhoneGap.Channel('onNativeReady'); +/** + * onPhoneGapInit channel is fired when the web page is fully loaded and + * PhoneGap native code has been initialized. + */ +PhoneGap.onPhoneGapInit = new PhoneGap.Channel('onPhoneGapInit'); + +/** + * onPhoneGapReady channel is fired when the JS PhoneGap objects have been created. + */ +PhoneGap.onPhoneGapReady = new PhoneGap.Channel('onPhoneGapReady'); + +/** + * onPhoneGapInfoReady channel is fired when the PhoneGap device properties + * has been set. + */ +PhoneGap.onPhoneGapInfoReady = new PhoneGap.Channel('onPhoneGapInfoReady'); + /** * onResume channel is fired when the PhoneGap native code * resumes. @@ -183,29 +218,44 @@ PhoneGap.onPause = new PhoneGap.Channel('onPause'); if (typeof _nativeReady !== 'undefined') { PhoneGap.onNativeReady.fire(); } /** - * onDeviceReady is fired only after both onDOMContentLoaded and - * onNativeReady have fired. + * onDeviceReady is fired only after all PhoneGap objects are created and + * the device properties are set. */ PhoneGap.onDeviceReady = new PhoneGap.Channel('onDeviceReady'); -PhoneGap.onDeviceReady.subscribeOnce(function() { - PhoneGap.JSCallback(); -}); +/** + * Create all PhoneGap objects once page has fully loaded and native side is ready. + */ +PhoneGap.Channel.join(function() { + + // Start listening for XHR callbacks + PhoneGap.JSCallback(); + + // Run PhoneGap constructors + PhoneGap.onPhoneGapInit.fire(); + + // Fire event to notify that all objects are created + PhoneGap.onPhoneGapReady.fire(); + +}, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]); + +/** + * Fire onDeviceReady event once all constructors have run and PhoneGap info has been + * received from native side. + */ PhoneGap.Channel.join(function() { PhoneGap.onDeviceReady.fire(); // Fire the onresume event, since first one happens before JavaScript is loaded PhoneGap.onResume.fire(); -}, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]); - +}, [ PhoneGap.onPhoneGapReady, PhoneGap.onPhoneGapInfoReady]); // Listen for DOMContentLoaded and notify our channel subscribers document.addEventListener('DOMContentLoaded', function() { PhoneGap.onDOMContentLoaded.fire(); }, false); - // Intercept calls to document.addEventListener and watch for deviceready PhoneGap.m_document_addEventListener = document.addEventListener; @@ -222,6 +272,58 @@ document.addEventListener = function(evt, handler, capture) { } }; +/** + * If JSON not included, use our own stringify. (Android 1.6) + * The restriction on ours is that it must be an array of simple types. + * + * @param args + * @return + */ +PhoneGap.stringify = function(args) { + if (typeof JSON == "undefined") { + var s = "["; + for (var i=0; i 0) { + s = s + ","; + } + var type = typeof args[i]; + if ((type == "number") || (type == "boolean")) { + s = s + args[i]; + } + else 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 { + s = s + '"' + args[i] + '"'; + } + } + s = s + "]"; + return s; + } + else { + return JSON.stringify(args); + } +}; + PhoneGap.callbackId = 0; PhoneGap.callbacks = {}; @@ -232,10 +334,11 @@ PhoneGap.callbacks = {}; * @param {String} command Command to be run in PhoneGap, e.g. "ClassName.method" * @param {String[]} [args] Zero or more arguments to pass to the method */ +// TODO: Not used anymore, should be removed. PhoneGap.exec = function(clazz, action, args) { try { var callbackId = 0; - var r = PluginManager.exec(clazz, action, callbackId, JSON.stringify(args), false); + var r = PluginManager.exec(clazz, action, callbackId, this.stringify(args), false); eval("var v="+r+";"); // If status is OK, then return value back to caller @@ -253,22 +356,54 @@ PhoneGap.exec = function(clazz, action, args) { } }; -PhoneGap.execAsync = function(success, fail, clazz, action, args) { +/** + * Execute a PhoneGap command. It is up to the native side whether this action is synch or async. + * The native side can return: + * Synchronous: PluginResult object as a JSON string + * Asynchrounous: Empty string "" + * If async, the native side will PhoneGap.callbackSuccess or PhoneGap.callbackError, + * depending upon the result of the action. + * + * @param {Function} success The success callback + * @param {Function} fail The fail callback + * @param {String} service The name of the service to use + * @param {String} action Action to be run in PhoneGap + * @param {String[]} [args] Zero or more arguments to pass to the method + */ +PhoneGap.execAsync = function(success, fail, service, action, args) { try { - var callbackId = clazz + PhoneGap.callbackId++; - PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; - var r = PluginManager.exec(clazz, action, callbackId, JSON.stringify(args), true); - if (r) { + var callbackId = service + PhoneGap.callbackId++; + if (success || fail) { + PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; + } + + // Note: Device returns string, but for some reason emulator returns object - so convert to string. + var r = ""+PluginManager.exec(service, action, callbackId, this.stringify(args), true); + + // If a result was returned + if (r.length > 0) { eval("var v="+r+";"); // If status is OK, then return value back to caller if (v.status == 0) { + + // If there is a success callback, then call it now with returned value + if (success) { + success(v.message); + delete PhoneGap.callbacks[callbackId]; + } return v.message; } // If error, then display error else { console.log("Error: Status="+r.status+" Message="+v.message); + + // If there is a fail callback, then call it now with returned value + if (fail) { + fail(v.message); + delete PhoneGap.callbacks[callbackId]; + } return null; } } @@ -277,24 +412,44 @@ PhoneGap.execAsync = function(success, fail, clazz, action, args) { } }; +/** + * Called by native code when returning successful result from an action. + * + * @param callbackId + * @param args + */ PhoneGap.callbackSuccess = function(callbackId, args) { - try { - PhoneGap.callbacks[callbackId].success(args.message); + if (PhoneGap.callbacks[callbackId]) { + try { + if (PhoneGap.callbacks[callbackId].success) { + PhoneGap.callbacks[callbackId].success(args.message); + } + } + catch (e) { + console.log("Error in success callback: "+callbackId+" = "+e); + } + delete PhoneGap.callbacks[callbackId]; } - catch (e) { - console.log("Error in success callback: "+callbackId+" = "+e); - } - delete PhoneGap.callbacks[callbackId]; }; +/** + * Called by native code when returning error result from an action. + * + * @param callbackId + * @param args + */ PhoneGap.callbackError = function(callbackId, args) { - try { - PhoneGap.callbacks[callbackId].fail(args.message); + if (PhoneGap.callbacks[callbackId]) { + try { + if (PhoneGap.callbacks[callbackId].fail) { + PhoneGap.callbacks[callbackId].fail(args.message); + } + } + catch (e) { + console.log("Error in error callback: "+callbackId+" = "+e); + } + delete PhoneGap.callbacks[callbackId]; } - catch (e) { - console.log("Error in error callback: "+callbackId+" = "+e); - } - delete PhoneGap.callbacks[callbackId]; }; @@ -305,6 +460,7 @@ PhoneGap.callbackError = function(callbackId, args) { * url, which will be turned into a dictionary on the other end. * @private */ +// TODO: Is this used? PhoneGap.run_command = function() { if (!PhoneGap.available || !PhoneGap.queue.ready) return; @@ -344,6 +500,8 @@ PhoneGap.run_command = function() { }; /** + * This is only for Android. + * * Internal function that uses XHR to call into PhoneGap Java code and retrieve * any JavaScript code that needs to be run. This is used for callbacks from * Java to JavaScript. @@ -425,38 +583,6 @@ PhoneGap.close = function(context, func, params) { } }; -com.phonegap.AccelListenerProxy = function() { - this.className = "com.phonegap.AccelListener"; - this.status = -1; // not set yet -}; -com.phonegap.AccelListenerProxy.prototype.getStatus = function() { - if (this.status == -1) { // if not set, then request status - this.status = PhoneGap.exec(this.className, "getStatus", []); - } - return this.status; -}; -com.phonegap.AccelListenerProxy.prototype.onStatus = function(status) { - console.log("AccelListener.onStatus("+status+")"); - this.status = status; -}; -com.phonegap.AccelListenerProxy.prototype.getAcceleration = function() { - var r = PhoneGap.exec(this.className, "getAcceleration", []); - var a = new Acceleration(r.x,r.y,r.z); - return a; -}; -com.phonegap.AccelListenerProxy.prototype.start = function() { - return PhoneGap.exec(this.className, "start", []); -}; -com.phonegap.AccelListenerProxy.prototype.stop = function() { - return PhoneGap.exec(this.className, "stop", []); -}; -com.phonegap.AccelListenerProxy.prototype.setTimeout = function(timeout) { - return PhoneGap.exec(this.className, "setTimeout", [timeout]); -}; -com.phonegap.AccelListenerProxy.prototype.getTimeout = function() { - return PhoneGap.exec(this.className, "getTimeout", []); -}; -com.phonegap.AccelListener = new com.phonegap.AccelListenerProxy(); function Acceleration(x, y, z) { this.x = x; @@ -482,20 +608,17 @@ function Accelerometer() { this.timers = {}; }; -Accelerometer.STOPPED = 0; -Accelerometer.STARTING = 1; -Accelerometer.RUNNING = 2; -Accelerometer.ERROR_FAILED_TO_START = 3; Accelerometer.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"]; /** * Asynchronously aquires the current acceleration. * * @param {Function} successCallback The function to call when the acceleration data is available - * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. - * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. + * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL) + * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL) */ Accelerometer.prototype.getCurrentAcceleration = function(successCallback, errorCallback, options) { + console.log("Accelerometer.getCurrentAcceleration()"); // successCallback required if (typeof successCallback != "function") { @@ -509,60 +632,16 @@ Accelerometer.prototype.getCurrentAcceleration = function(successCallback, error return; } - // Get current acceleration status - var status = com.phonegap.AccelListener.getStatus(); - - // If running, then call successCallback - if (status == Accelerometer.RUNNING) { - try { - var accel = com.phonegap.AccelListener.getAcceleration(); - successCallback(accel); - } catch (e) { - console.log("Accelerometer Error in successCallback: " + e); - } - } - - // If not running, then start it - else if (status >= 0) { - com.phonegap.AccelListener.start(); - - // Wait until started - var timer = setInterval(function() { - var status = com.phonegap.AccelListener.getStatus(); - - // If accelerometer is running - if (status == Accelerometer.RUNNING) { - clearInterval(timer); - try { - var accel = com.phonegap.AccelListener.getAcceleration(); - successCallback(accel); - } catch (e) { - console.log("Accelerometer Error in successCallback: " + e); - } - } - - // If accelerometer error - else if (status == Accelerometer.ERROR_FAILED_TO_START) { - clearInterval(timer); - console.log("Accelerometer Error: "+ Accelerometer.ERROR_MSG[status]); - try { - if (errorCallback) { - errorCallback(status); - } - } catch (e) { - console.log("Accelerometer Error in errorCallback: " + e); - } - } - }, 10); - } + // Get acceleration + PhoneGap.execAsync(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); }; /** * Asynchronously aquires the acceleration repeatedly at a given interval. * * @param {Function} successCallback The function to call each time the acceleration data is available - * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. - * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. + * @param {Function} errorCallback The function to call when there is an error getting the acceleration data. (OPTIONAL) + * @param {AccelerationOptions} options The options for getting the accelerometer data such as timeout. (OPTIONAL) * @return String The watch id that must be passed to #clearWatch to stop watching. */ Accelerometer.prototype.watchAcceleration = function(successCallback, errorCallback, options) { @@ -583,40 +662,18 @@ Accelerometer.prototype.watchAcceleration = function(successCallback, errorCallb } // Make sure accelerometer timeout > frequency + 10 sec - var timeout = com.phonegap.AccelListener.getTimeout(); - if (timeout < (frequency + 10000)) { - com.phonegap.AccelListener.setTimeout(frequency + 10000); // set to frequency + 10 sec - } - - var id = PhoneGap.createUUID(); - com.phonegap.AccelListener.start(); + PhoneGap.execAsync( + function(timeout) { + if (timeout < (frequency + 10000)) { + PhoneGap.execAsync(null, null, "Accelerometer", "setTimeout", [frequency + 10000]); + } + }, + function(e) { }, "Accelerometer", "getTimeout", []); // Start watch timer + var id = PhoneGap.createUUID(); navigator.accelerometer.timers[id] = setInterval(function() { - var status = com.phonegap.AccelListener.getStatus(); - - // If accelerometer is running - if (status == Accelerometer.RUNNING) { - try { - var accel = com.phonegap.AccelListener.getAcceleration(); - successCallback(accel); - } catch (e) { - console.log("Accelerometer Error in successCallback: " + e); - } - } - - // If accelerometer had error - else if (status == Accelerometer.ERROR_FAILED_TO_START) { - console.log("Accelerometer Error: "+ Accelerometer.ERROR_MSG[status]); - try { - navigator.accelerometer.clearWatch(id); - if (errorCallback) { - errorCallback(status); - } - } catch (e) { - console.log("Accelerometer Error in errorCallback: " + e); - } - } + PhoneGap.execAsync(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); }, (frequency ? frequency : 1)); return id; @@ -639,30 +696,52 @@ Accelerometer.prototype.clearWatch = function(id) { PhoneGap.addConstructor(function() { if (typeof navigator.accelerometer == "undefined") navigator.accelerometer = new Accelerometer(); }); -com.phonegap.CameraLauncherProxy = function() { - this.className = "com.phonegap.CameraLauncher"; -}; -com.phonegap.CameraLauncherProxy.prototype.setBase64 = function(b) { - return PhoneGap.exec(this.className, "setBase64", [b]); -}; -com.phonegap.CameraLauncherProxy.prototype.takePicture = function(quality) { - return PhoneGap.exec(this.className, "takePicture", [quality]); -}; -com.phonegap.CameraLauncher = new com.phonegap.CameraLauncherProxy(); /** * This class provides access to the device camera. * * @constructor */ -function Camera() { +Camera = function() { this.successCallback = null; this.errorCallback = null; this.options = null; }; /** - * Takes a photo and returns the image as a base64 encoded `String`. + * Format of image that returned from getPicture. + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) + */ +Camera.DestinationType = { + DATA_URL: 0, // Return base64 encoded string + FILE_URI: 1 // Return file uri (content://media/external/images/media/2 for Android) +}; +Camera.prototype.DestinationType = Camera.DestinationType; + +/** + * Source to getPicture from. + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) + */ +Camera.PictureSourceType = { + PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) + CAMERA : 1, // Take picture from camera + SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android) +}; +Camera.prototype.PictureSourceType = Camera.PictureSourceType; + +/** + * Gets a picture from source defined by "options.sourceType", and returns the + * image as defined by the "options.destinationType" option. + + * The defaults are sourceType=CAMERA and destinationType=DATA_URL. * * @param {Function} successCallback * @param {Function} errorCallback @@ -685,12 +764,19 @@ Camera.prototype.getPicture = function(successCallback, errorCallback, options) this.successCallback = successCallback; this.errorCallback = errorCallback; this.options = options; + var quality = 80; if (options.quality) { - com.phonegap.CameraLauncher.takePicture(options.quality); + quality = this.options.quality; } - else { - com.phonegap.CameraLauncher.takePicture(80); + var destinationType = Camera.DestinationType.DATA_URL; + if (this.options.destinationType) { + destinationType = this.options.destinationType; } + var sourceType = Camera.PictureSourceType.CAMERA; + if (typeof this.options.sourceType == "number") { + sourceType = this.options.sourceType; + } + PhoneGap.execAsync(null, null, "Camera", "takePicture", [quality, destinationType, sourceType]); }; /** @@ -719,28 +805,6 @@ Camera.prototype.error = function(err) { PhoneGap.addConstructor(function() { if (typeof navigator.camera == "undefined") navigator.camera = new Camera(); }); -com.phonegap.CompassListenerProxy = function() { - this.className = "com.phonegap.CompassListener"; -}; -com.phonegap.CompassListenerProxy.prototype.start = function() { - return PhoneGap.exec(this.className, "start", []); -}; -com.phonegap.CompassListenerProxy.prototype.stop = function() { - return PhoneGap.exec(this.className, "stop", []); -}; -com.phonegap.CompassListenerProxy.prototype.getStatus = function() { - return PhoneGap.exec(this.className, "getStatus", []); -}; -com.phonegap.CompassListenerProxy.prototype.getHeading = function() { - return PhoneGap.exec(this.className, "getHeading", []); -}; -com.phonegap.CompassListenerProxy.prototype.setTimeout = function(timeout) { - return PhoneGap.exec(this.className, "setTimeout", [timeout]); -}; -com.phonegap.CompassListenerProxy.prototype.getTimeout = function() { - return PhoneGap.exec(this.className, "getTimeout", []); -}; -com.phonegap.CompassListener = new com.phonegap.CompassListenerProxy(); /** * This class provides access to device Compass data. @@ -758,18 +822,14 @@ function Compass() { this.timers = {}; }; -Compass.STOPPED = 0; -Compass.STARTING = 1; -Compass.RUNNING = 2; -Compass.ERROR_FAILED_TO_START = 3; Compass.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"]; /** * Asynchronously aquires the current heading. * * @param {Function} successCallback The function to call when the heading data is available - * @param {Function} errorCallback The function to call when there is an error getting the heading data. - * @param {PositionOptions} options The options for getting the heading data such as timeout. + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) + * @param {PositionOptions} options The options for getting the heading data such as timeout. (OPTIONAL) */ Compass.prototype.getCurrentHeading = function(successCallback, errorCallback, options) { @@ -785,61 +845,16 @@ Compass.prototype.getCurrentHeading = function(successCallback, errorCallback, o return; } - // Get current compass status - var status = com.phonegap.CompassListener.getStatus(); - - // If running, then call successCallback - if (status == Compass.RUNNING) { - try { - var heading = com.phonegap.CompassListener.getHeading(); - successCallback(heading); - } catch (e) { - console.log("Compass Error in successCallback: " + e); - } - } - - // If not running, then start it - else { - com.phonegap.CompassListener.start(); - - // Wait until started - var timer = setInterval(function() { - var status = com.phonegap.CompassListener.getStatus(); - if (status != Compass.STARTING) { - clearInterval(timer); - - // If compass is running - if (status == Compass.RUNNING) { - try { - var heading = com.phonegap.CompassListener.getHeading(); - successCallback(heading); - } catch (e) { - console.log("Compass Error in successCallback: " + e); - } - } - - // If compass error - else { - console.log("Compass Error: "+ Compass.ERROR_MSG[status]); - try { - if (errorCallback) { - errorCallback(status); - } - } catch (e) { - console.log("Compass Error in errorCallback: " + e); - } - } - } - }, 10); - } + // Get heading + PhoneGap.execAsync(successCallback, errorCallback, "Compass", "getHeading", []); }; /** * Asynchronously aquires the heading repeatedly at a given interval. * * @param {Function} successCallback The function to call each time the heading data is available - * @param {Function} errorCallback The function to call when there is an error getting the heading data. - * @param {HeadingOptions} options The options for getting the heading data such as timeout and the frequency of the watch. + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) + * @param {HeadingOptions} options The options for getting the heading data such as timeout and the frequency of the watch. (OPTIONAL) * @return String The watch id that must be passed to #clearWatch to stop watching. */ Compass.prototype.watchHeading= function(successCallback, errorCallback, options) { @@ -860,41 +875,20 @@ Compass.prototype.watchHeading= function(successCallback, errorCallback, options } // Make sure compass timeout > frequency + 10 sec - var timeout = com.phonegap.CompassListener.getTimeout(); - if (timeout < (frequency + 10000)) { - com.phonegap.CompassListener.setTimeout(frequency + 10000); // set to frequency + 10 sec - } + PhoneGap.execAsync( + function(timeout) { + if (timeout < (frequency + 10000)) { + PhoneGap.execAsync(null, null, "Compass", "setTimeout", [frequency + 10000]); + } + }, + function(e) { }, "Compass", "getTimeout", []); + // Start watch timer to get headings var id = PhoneGap.createUUID(); - com.phonegap.CompassListener.start(); - - // Start watch timer - navigator.compass.timers[id] = setInterval(function() { - var status = com.phonegap.CompassListener.getStatus(); - - // If compass is running - if (status == Compass.RUNNING) { - try { - var heading = com.phonegap.CompassListener.getHeading(); - successCallback(heading); - } catch (e) { - console.log("Compass Error in successCallback: " + e); - } - } - - // If compass had error - else if (status != Compass.STARTING) { - console.log("Compass Error: "+ Compass.ERROR_MSG[status]); - try { - navigator.compass.clearWatch(id); - if (errorCallback) { - errorCallback(status); - } - } catch (e) { - console.log("Compass Error in errorCallback: " + e); - } - } - }, (frequency ? frequency : 1)); + navigator.compass.timers[id] = setInterval( + function() { + PhoneGap.execAsync(successCallback, errorCallback, "Compass", "getHeading", []); + }, (frequency ? frequency : 1)); return id; }; @@ -917,110 +911,145 @@ Compass.prototype.clearWatch = function(id) { PhoneGap.addConstructor(function() { if (typeof navigator.compass == "undefined") navigator.compass = new Compass(); }); -com.phonegap.ContactManagerProxy = function() { - this.className = "com.phonegap.ContactManager"; -}; -com.phonegap.ContactManagerProxy.prototype.getContactsAndSendBack = function() { - return PhoneGap.exec(this.className, "getContactsAndSendBack", []); -}; -com.phonegap.ContactManagerProxy.prototype.search = function(name, npa, mail) { - return PhoneGap.exec(this.className, "search", [name, npa, mail]); -}; -com.phonegap.ContactManager = new com.phonegap.ContactManagerProxy(); -var Contact = function() { - this.name = new ContactName(); - this.emails = []; - this.phones = []; +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; }; -var ContactName = function() { - this.formatted = ""; - this.familyName = ""; - this.givenName = ""; - this.additionalNames = []; - this.prefixes = []; - this.suffixes = []; +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; }; -var ContactEmail = function() { - this.types = []; - this.address = ""; +var ContactField = function(type, value, primary) { + this.type = type || null; + this.value = value || null; + this.primary = primary || null; }; -var ContactPhoneNumber = function() { - this.types = []; - this.number = ""; +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; }; +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; +}; + +var ContactAccount = function(domain, username, userid) { + this.domain = domain || null; + this.username = username || null; + this.userid = userid || null; +} + var Contacts = function() { - this.records = []; -}; + this.inProgress = false; + this.records = new Array(); +} -Contacts.prototype.find = function(obj, win, fail) { - if(obj.name != null) { - // Build up the search term that we'll use in SQL, based on the structure/contents of the contact object passed into find. - var searchTerm = ''; - if (obj.name.givenName && obj.name.givenName.length > 0) { - searchTerm = obj.name.givenName.split(' ').join('%'); - } - if (obj.name.familyName && obj.name.familyName.length > 0) { - searchTerm += obj.name.familyName.split(' ').join('%'); - } - if (!obj.name.familyName && !obj.name.givenName && obj.name.formatted) { - searchTerm = obj.name.formatted; - } - com.phonegap.ContactManager.search(searchTerm, "", ""); - } +// Contacts.prototype.find = function(obj, win, fail) { +Contacts.prototype.find = function(fields, win, fail, options) { this.win = win; this.fail = fail; + + PhoneGap.execAsync(null, null, "Contacts", "search", [fields, options]); }; -Contacts.prototype.droidFoundContact = function(name, npa, email) { - var contact = new Contact(); - contact.name = new ContactName(); - contact.name.formatted = name; - contact.name.givenName = name; - var mail = new ContactEmail(); - mail.types.push("home"); - mail.address = email; - contact.emails.push(mail); - phone = new ContactPhoneNumber(); - phone.types.push("home"); - phone.number = npa; - contact.phones.push(phone); - this.records.push(contact); +Contacts.prototype.droidDone = function(contacts) { + this.win(eval('(' + contacts + ')')); }; -Contacts.prototype.droidDone = function() { - this.win(this.records); +Contacts.prototype.remove = function(contact) { + }; +Contacts.prototype.save = function(contact) { + +}; + +Contacts.prototype.create = function(contact) { + +}; + +Contacts.prototype.m_foundContacts = function(win, contacts) { + this.inProgress = false; + win(contacts); +}; + +var ContactFindOptions = function(filter, multiple, limit, updatedSince) { + this.filter = filter || ''; + this.multiple = multiple || true; + this.limit = limit || Number.MAX_VALUE; + this.updatedSince = updatedSince || ''; +}; + +var ContactError = function() { + this.code=null; +}; + +ContactError.INVALID_ARGUMENT_ERROR = 0; +ContactError.IO_ERROR = 1; +ContactError.NOT_FOUND_ERROR = 2; +ContactError.NOT_SUPPORTED_ERROR = 3; +ContactError.PENDING_OPERATION_ERROR = 4; +ContactError.PERMISSION_DENIED_ERROR = 5; +ContactError.TIMEOUT_ERROR = 6; +ContactError.UNKNOWN_ERROR = 7; + PhoneGap.addConstructor(function() { - if(typeof navigator.contacts == "undefined") navigator.contacts = new Contacts(); + if(typeof navigator.service == "undefined") navigator.service = new Object(); + if(typeof navigator.service.contacts == "undefined") navigator.service.contacts = new Contacts(); }); -com.phonegap.CryptoHandlerProxy = function() { - this.className = "com.phonegap.CryptoHandler"; -}; -com.phonegap.CryptoHandlerProxy.prototype.encrypt = function(pass, text) { - return PhoneGap.exec(this.className, "encrypt", [pass, text]); -}; -com.phonegap.CryptoHandlerProxy.prototype.decrypt = function(pass, text) { - return PhoneGap.exec(this.className, "decrypt", [pass, text]); -}; -com.phonegap.CryptoHandler = new com.phonegap.CryptoHandlerProxy(); var Crypto = function() { }; Crypto.prototype.encrypt = function(seed, string, callback) { - com.phonegap.CryptoHandler.encrypt(seed, string); this.encryptWin = callback; + PhoneGap.execAsync(null, null, "Crypto", "encrypt", [seed, string]); }; Crypto.prototype.decrypt = function(seed, string, callback) { - com.phonegap.CryptoHandler.decrypt(seed, string); this.decryptWin = callback; + PhoneGap.execAsync(null, null, "Crypto", "decrypt", [seed, string]); }; Crypto.prototype.gotCryptedString = function(string) { @@ -1036,376 +1065,691 @@ PhoneGap.addConstructor(function() { }); /** - * this represents the mobile device, and provides properties for inspecting the model, version, UUID of the + * This represents the mobile device, and provides properties for inspecting the model, version, UUID of the * phone, etc. * @constructor */ function Device() { this.available = PhoneGap.available; this.platform = null; - this.version = null; - this.name = null; - this.gap = null; - this.uuid = null; - try { - if (window.DroidGap) { - this.available = true; - this.uuid = window.DroidGap.getUuid(); - this.version = window.DroidGap.getOSVersion(); - this.gapVersion = window.DroidGap.getVersion(); - this.platform = window.DroidGap.getPlatform(); - this.name = window.DroidGap.getProductName(); - this.line1Number = window.DroidGap.getLine1Number(); - this.deviceId = window.DroidGap.getDeviceId(); - this.simSerialNumber = window.DroidGap.getSimSerialNumber(); - this.subscriberId = window.DroidGap.getSubscriberId(); - } - } catch(e) { - this.available = false; - } + this.version = null; + this.name = null; + this.uuid = null; + this.phonegap = null; + + var me = this; + this.getInfo( + function(info) { + me.available = true; + me.platform = info.platform; + me.version = info.version; + me.uuid = info.uuid; + me.phonegap = info.phonegap; + PhoneGap.onPhoneGapInfoReady.fire(); + }, + function(e) { + me.available = false; + console.log("Error initializing PhoneGap: " + e); + alert("Error initializing PhoneGap: "+e); + }); } -/* - * You must explicitly override the back button. +/** + * Get device info + * + * @param {Function} successCallback The function to call when the heading data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) */ +Device.prototype.getInfo = function(successCallback, errorCallback) { -Device.prototype.overrideBackButton = function() -{ - BackButton.override(); + // successCallback required + if (typeof successCallback != "function") { + console.log("Device Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback != "function")) { + console.log("Device Error: errorCallback is not a function"); + return; + } + + // Get info + PhoneGap.execAsync(successCallback, errorCallback, "Device", "getDeviceInfo", []); +}; + +/* + * This is only for Android. + * + * You must explicitly override the back button. + */ +Device.prototype.overrideBackButton = function() { + BackButton.override(); } /* + * This is only for Android. + * * This resets the back button to the default behaviour */ - -Device.prototype.resetBackButton = function() -{ - BackButton.reset(); +Device.prototype.resetBackButton = function() { + BackButton.reset(); } /* + * This is only for Android. + * * This terminates the activity! */ -Device.prototype.exitApp = function() -{ - BackButton.exitApp(); +Device.prototype.exitApp = function() { + BackButton.exitApp(); } PhoneGap.addConstructor(function() { navigator.device = window.device = new Device(); }); +/** + * 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 = {}; +}; -PhoneGap.addConstructor(function() { if (typeof navigator.fileMgr == "undefined") navigator.fileMgr = new FileMgr();}); +/** + * 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.execAsync(successCallback, errorCallback, "File", "testSaveLocationExists", []); +}; + +FileMgr.prototype.testFileExists = function(fileName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "testFileExists", [fileName]); +}; + +FileMgr.prototype.testDirectoryExists = function(dirName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "testDirectoryExists", [dirName]); +}; + +FileMgr.prototype.createDirectory = function(dirName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "createDirectory", [dirName]); +}; + +FileMgr.prototype.deleteDirectory = function(dirName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "deleteDirectory", [dirName]); +}; + +FileMgr.prototype.deleteFile = function(fileName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "deleteFile", [fileName]); +}; + +FileMgr.prototype.getFreeDiskSpace = function(successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "getFreeDiskSpace", []); +}; + +FileMgr.prototype.writeAsText = function(fileName, data, append, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "writeAsText", [fileName, data, append]); +}; + +FileMgr.prototype.readAsText = function(fileName, encoding, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "readAsText", [fileName, encoding]); +}; + +FileMgr.prototype.readAsDataURL = function(fileName, successCallback, errorCallback) { + PhoneGap.execAsync(successCallback, errorCallback, "File", "readAsDataURL", [fileName]); +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.fileMgr == "undefined") navigator.fileMgr = new FileMgr(); +}); + +//----------------------------------------------------------------------------- +// File Reader +//----------------------------------------------------------------------------- +// TODO: All other FileMgr function operate on the SD card as root. However, +// for FileReader & FileWriter the root is not SD card. Should this be changed? + +/** + * This class reads the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To read from the SD card, the file name is "sdcard/my_file.txt" + */ +function FileReader() { + this.fileName = ""; + + this.readyState = 0; + + // File data + this.result = null; + + // Error + this.error = null; + + // Event handlers + this.onloadstart = null; // When the read starts. + this.onprogress = null; // While reading (and decoding) file or fileBlob data, and reporting partial file data (progess.loaded/progress.total) + this.onload = null; // When the read has successfully completed. + this.onerror = null; // When the read has failed (see errors). + this.onloadend = null; // When the request has completed (either in success or failure). + this.onabort = null; // When the read has been aborted. For instance, by invoking the abort() method. +}; + +// States +FileReader.EMPTY = 0; +FileReader.LOADING = 1; +FileReader.DONE = 2; + +/** + * Abort reading file. + */ +FileReader.prototype.abort = function() { + this.readyState = FileReader.DONE; + + // If abort callback + if (typeof this.onabort == "function") { + var evt = File._createEvent("abort", this); + this.onabort(evt); + } + + // TODO: Anything else to do? Maybe sent to native? +}; + +/** + * Read text file. + * + * @param file The name of the file + * @param encoding [Optional] (see http://www.iana.org/assignments/character-sets) + */ +FileReader.prototype.readAsText = function(file, encoding) { + this.fileName = file; + + // LOADING state + this.readyState = FileReader.LOADING; + + // If loadstart callback + if (typeof this.onloadstart == "function") { + var evt = File._createEvent("loadstart", this); + this.onloadstart(evt); + } + + // Default encoding is UTF-8 + var enc = encoding ? encoding : "UTF-8"; + + var me = this; + + // Read file + navigator.fileMgr.readAsText(file, enc, + + // Success callback + function(r) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileReader.DONE) { + return; + } + + // Save result + me.result = r; + + // DONE state + me.readyState = FileReader.DONE; + + // If onload callback + if (typeof me.onload == "function") { + var evt = File._createEvent("load", me); + me.onload(evt); + } + + // If onloadend callback + if (typeof me.onloadend == "function") { + var evt = File._createEvent("loadend", me); + me.onloadend(evt); + } + }, + + // Error callback + function(e) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileReader.DONE) { + return; + } + + // Save error + me.error = e; + + // DONE state + me.readyState = FileReader.DONE; + + // If onerror callback + if (typeof me.onerror == "function") { + var evt = File._createEvent("error", me); + me.onerror(evt); + } + + // If onloadend callback + if (typeof me.onloadend == "function") { + var evt = File._createEvent("loadend", me); + me.onloadend(evt); + } + } + ); +}; /** - * This class provides iPhone read and write access to the mobile device file system. - * Based loosely on http://www.w3.org/TR/2009/WD-FileAPI-20091117/#dfn-empty - */ -function FileMgr() -{ - this.fileWriters = {}; // empty maps - this.fileReaders = {}; - - this.docsFolderPath = "../../Documents"; - this.tempFolderPath = "../../tmp"; - this.freeDiskSpace = -1; - this.getFileBasePaths(); -} - -// private, called from Native Code -FileMgr.prototype._setPaths = function(docs,temp) -{ - this.docsFolderPath = docs; - this.tempFolderPath = temp; -} - -// private, called from Native Code -FileMgr.prototype._setFreeDiskSpace = function(val) -{ - this.freeDiskSpace = val; -} - - -// FileWriters add/remove -// called internally by writers -FileMgr.prototype.addFileWriter = function(filePath,fileWriter) -{ - this.fileWriters[filePath] = fileWriter; -} - -FileMgr.prototype.removeFileWriter = function(filePath) -{ - this.fileWriters[filePath] = null; -} - -// File readers add/remove -// called internally by readers -FileMgr.prototype.addFileReader = function(filePath,fileReader) -{ - this.fileReaders[filePath] = fileReader; -} - -FileMgr.prototype.removeFileReader = function(filePath) -{ - this.fileReaders[filePath] = null; -} - -/******************************************* + * Read file and return data as a base64 encoded data url. + * A data url is of the form: + * data:[][;base64], * - * private reader callback delegation - * called from native code + * @param file The name of the file */ -FileMgr.prototype.reader_onloadstart = function(filePath,result) -{ - this.fileReaders[filePath].onloadstart(result); -} +FileReader.prototype.readAsDataURL = function(file) { + this.fileName = file; -FileMgr.prototype.reader_onprogress = function(filePath,result) -{ - this.fileReaders[filePath].onprogress(result); -} + // LOADING state + this.readyState = FileReader.LOADING; -FileMgr.prototype.reader_onload = function(filePath,result) -{ - this.fileReaders[filePath].result = unescape(result); - this.fileReaders[filePath].onload(this.fileReaders[filePath].result); -} + // If loadstart callback + if (typeof this.onloadstart == "function") { + var evt = File._createEvent("loadstart", this); + this.onloadstart(evt); + } -FileMgr.prototype.reader_onerror = function(filePath,err) -{ - this.fileReaders[filePath].result = err; - this.fileReaders[filePath].onerror(err); -} + var me = this; -FileMgr.prototype.reader_onloadend = function(filePath,result) -{ - this.fileReaders[filePath].onloadend(result); -} + // Read file + navigator.fileMgr.readAsDataURL(file, -/******************************************* + // Success callback + function(r) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileReader.DONE) { + return; + } + + // Save result + me.result = r; + + // DONE state + me.readyState = FileReader.DONE; + + // If onload callback + if (typeof me.onload == "function") { + var evt = File._createEvent("load", me); + me.onload(evt); + } + + // If onloadend callback + if (typeof me.onloadend == "function") { + var evt = File._createEvent("loadend", me); + me.onloadend(evt); + } + }, + + // Error callback + function(e) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileReader.DONE) { + return; + } + + // Save error + me.error = e; + + // DONE state + me.readyState = FileReader.DONE; + + // If onerror callback + if (typeof me.onerror == "function") { + var evt = File._createEvent("error", me); + me.onerror(evt); + } + + // If onloadend callback + if (typeof me.onloadend == "function") { + var evt = File._createEvent("loadend", me); + me.onloadend(evt); + } + } + ); +}; + +/** + * Read file and return data as a binary data. * - * private writer callback delegation - * called from native code -*/ -FileMgr.prototype.writer_onerror = function(filePath,err) -{ - this.fileWriters[filePath].onerror(err); -} - -FileMgr.prototype.writer_oncomplete = function(filePath,result) -{ - this.fileWriters[filePath].oncomplete(result); // result contains bytes written -} - - -FileMgr.prototype.getFileBasePaths = function() -{ - //PhoneGap.exec("File.getFileBasePaths"); -} - -FileMgr.prototype.testFileExists = function(fileName, successCallback, errorCallback) -{ - var test = FileUtil.testFileExists(fileName); - test ? successCallback() : errorCallback(); -} - -FileMgr.prototype.testDirectoryExists = function(dirName, successCallback, errorCallback) -{ - this.successCallback = successCallback; - this.errorCallback = errorCallback; - var test = FileUtil.testDirectoryExists(dirName); - test ? successCallback() : errorCallback(); -} - -FileMgr.prototype.createDirectory = function(dirName, successCallback, errorCallback) -{ - this.successCallback = successCallback; - this.errorCallback = errorCallback; - var test = FileUtil.createDirectory(dirName); - test ? successCallback() : errorCallback(); -} - -FileMgr.prototype.deleteDirectory = function(dirName, successCallback, errorCallback) -{ - this.successCallback = successCallback; - this.errorCallback = errorCallback; - var test = FileUtil.deleteDirectory(dirName); - test ? successCallback() : errorCallback(); -} - -FileMgr.prototype.deleteFile = function(fileName, successCallback, errorCallback) -{ - this.successCallback = successCallback; - this.errorCallback = errorCallback; - FileUtil.deleteFile(fileName); - test ? successCallback() : errorCallback(); -} - -FileMgr.prototype.getFreeDiskSpace = function(successCallback, errorCallback) -{ - if(this.freeDiskSpace > 0) - { - return this.freeDiskSpace; - } - else - { - this.successCallback = successCallback; - this.errorCallback = errorCallback; - this.freeDiskSpace = FileUtil.getFreeDiskSpace(); - (this.freeDiskSpace > 0) ? successCallback() : errorCallback(); - } -} - - -// File Reader - - -function FileReader() -{ - this.fileName = ""; - this.result = null; - this.onloadstart = null; - this.onprogress = null; - this.onload = null; - this.onerror = null; - this.onloadend = null; -} - - -FileReader.prototype.abort = function() -{ - // Not Implemented -} - -FileReader.prototype.readAsText = function(file) -{ - if(this.fileName && this.fileName.length > 0) - { - navigator.fileMgr.removeFileReader(this.fileName,this); - } - this.fileName = file; - navigator.fileMgr.addFileReader(this.fileName,this); - - return FileUtil.read(this.fileName); -} + * @param file The name of the file + */ +FileReader.prototype.readAsBinaryString = function(file) { + // TODO - Can't return binary data to browser. + this.fileName = file; +}; +//----------------------------------------------------------------------------- // File Writer +//----------------------------------------------------------------------------- -function FileWriter() -{ - this.fileName = ""; - this.result = null; - this.readyState = 0; // EMPTY - this.result = null; - this.onerror = null; - this.oncomplete = null; -} +/** + * This class writes to the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To write to the SD card, the file name is "sdcard/my_file.txt" + */ +function FileWriter() { + this.fileName = ""; + this.result = null; + this.readyState = 0; // EMPTY + this.result = null; + this.onerror = null; + this.oncomplete = null; +}; + +// States +FileWriter.EMPTY = 0; +FileWriter.LOADING = 1; +FileWriter.DONE = 2; + +FileWriter.prototype.writeAsText = function(file, text, bAppend) { + if (bAppend != true) { + bAppend = false; // for null values + } + + this.fileName = file; + + // LOADING state + this.readyState = FileWriter.LOADING; + + var me = this; + + // Read file + navigator.fileMgr.writeAsText(file, text, bAppend, + + // Success callback + function(r) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileWriter.DONE) { + return; + } + + // Save result + me.result = r; + + // DONE state + me.readyState = FileWriter.DONE; + + // If oncomplete callback + if (typeof me.oncomplete == "function") { + var evt = File._createEvent("complete", me); + me.oncomplete(evt); + } + }, + + // Error callback + function(e) { + + // If DONE (cancelled), then don't do anything + if (me.readyState == FileWriter.DONE) { + return; + } + + // Save error + me.error = e; + + // DONE state + me.readyState = FileWriter.DONE; + + // If onerror callback + if (typeof me.onerror == "function") { + var evt = File._createEvent("error", me); + me.onerror(evt); + } + } + ); + +}; -FileWriter.prototype.writeAsText = function(file,text,bAppend) -{ - if(this.fileName && this.fileName.length > 0) - { - navigator.fileMgr.removeFileWriter(this.fileName,this); - } - this.fileName = file; - if(bAppend != true) - { - bAppend = false; // for null values - } - navigator.fileMgr.addFileWriter(file,this); - this.readyState = 0; // EMPTY - var call = FileUtil.write(file, text, bAppend); - this.result = null; -} /** * This class provides access to device GPS data. * @constructor */ function Geolocation() { - /** - * The last known GPS position. - */ + + // The last known GPS position. this.lastPosition = null; - this.lastError = null; - this.listeners = null; + + // Geolocation listeners + this.listeners = {}; }; -var geoListeners = []; - -Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallback, options) -{ - var position = Geo.getCurrentLocation(); - this.global_success = successCallback; - this.fail = errorCallback; -} - -// Run the global callback -Geolocation.prototype.gotCurrentPosition = function(lat, lng, alt, altacc, head, vel, stamp) -{ - if (lat == "undefined" || lng == "undefined") - { - this.fail(); - } - else - { - coords = new Coordinates(lat, lng, alt, acc, head, vel); - loc = new Position(coords, stamp); - this.lastPosition = loc; - this.global_success(loc); - } -} - -/* -* This turns on the GeoLocator class, which has two listeners. -* The listeners have their own timeouts, and run independently of this process -* In this case, we return the key to the watch hash -*/ - -Geolocation.prototype.watchPosition = function(successCallback, errorCallback, options) -{ - var frequency = (options != undefined)? options.frequency : 10000; - - var key = geoListeners.push( {"success" : successCallback, "fail" : errorCallback }) - 1; - - // TO-DO: Get the names of the method and pass them as strings to the Java. - return Geo.start(frequency, key); -} - -/* - * Retrieve and stop this listener from listening to the GPS +/** + * Position error object * + * @param code + * @param message */ -Geolocation.prototype.success = function(key, lat, lng, alt, altacc, head, vel, stamp) -{ - var coords = new Coordinates(lat, lng, alt, acc, head, vel); - var loc = new Position(coords, stamp); - geoListeners[key].success(loc); +function PositionError(code, message) { + this.code = code; + this.message = message; +}; + +PositionError.PERMISSION_DENIED = 1; +PositionError.POSITION_UNAVAILABLE = 2; +PositionError.TIMEOUT = 3; + +/** + * Asynchronously aquires the current position. + * + * @param {Function} successCallback The function to call when the position data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading position. (OPTIONAL) + * @param {PositionOptions} options The options for getting the position data. (OPTIONAL) + */ +Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallback, options) { + if (navigator._geo.listeners["global"]) { + console.log("Geolocation Error: Still waiting for previous getCurrentPosition() request."); + try { + errorCallback(new PositionError(PositionError.TIMEOUT, "Geolocation Error: Still waiting for previous getCurrentPosition() request.")); + } catch (e) { + } + return; + } + var maximumAge = 10000; + var enableHighAccuracy = false; + var timeout = 10000; + if (typeof options != "undefined") { + if (typeof options.maximumAge != "undefined") { + maximumAge = options.maximumAge; + } + if (typeof options.enableHighAccuracy != "undefined") { + enableHighAccuracy = options.enableHighAccuracy; + } + if (typeof options.timeout != "undefined") { + timeout = options.timeout; + } + } + navigator._geo.listeners["global"] = {"success" : successCallback, "fail" : errorCallback }; + PhoneGap.execAsync(null, null, "Geolocation", "getCurrentLocation", [enableHighAccuracy, timeout, maximumAge]); } -Geolocation.prototype.fail = function(key) -{ - geoListeners[key].fail(); -} - -Geolocation.prototype.clearWatch = function(watchId) -{ - Geo.stop(watchId); -} +/** + * Asynchronously watches the geolocation for changes to geolocation. When a change occurs, + * the successCallback is called with the new location. + * + * @param {Function} successCallback The function to call each time the location data is available + * @param {Function} errorCallback The function to call when there is an error getting the location data. (OPTIONAL) + * @param {PositionOptions} options The options for getting the location data such as frequency. (OPTIONAL) + * @return String The watch id that must be passed to #clearWatch to stop watching. + */ +Geolocation.prototype.watchPosition = function(successCallback, errorCallback, options) { + var maximumAge = 10000; + var enableHighAccuracy = false; + var timeout = 10000; + if (typeof options != "undefined") { + if (typeof options.frequency != "undefined") { + maximumAge = options.frequency; + } + if (typeof options.maximumAge != "undefined") { + maximumAge = options.maximumAge; + } + if (typeof options.enableHighAccuracy != "undefined") { + enableHighAccuracy = options.enableHighAccuracy; + } + if (typeof options.timeout != "undefined") { + timeout = options.timeout; + } + } + var id = PhoneGap.createUUID(); + navigator._geo.listeners[id] = {"success" : successCallback, "fail" : errorCallback }; + PhoneGap.execAsync(null, null, "Geolocation", "start", [id, enableHighAccuracy, timeout, maximumAge]); + return id; +}; + +/* + * Native callback when watch position has a new position. + * PRIVATE METHOD + * + * @param {String} id + * @param {Number} lat + * @param {Number} lng + * @param {Number} alt + * @param {Number} altacc + * @param {Number} head + * @param {Number} vel + * @param {Number} stamp + */ +Geolocation.prototype.success = function(id, lat, lng, alt, altacc, head, vel, stamp) { + var coords = new Coordinates(lat, lng, alt, altacc, head, vel); + var loc = new Position(coords, stamp); + try { + if (lat == "undefined" || lng == "undefined") { + navigator._geo.listeners[id].fail(new PositionError(PositionError.POSITION_UNAVAILABLE, "Lat/Lng are undefined.")); + } + else { + navigator._geo.lastPosition = loc; + navigator._geo.listeners[id].success(loc); + } + } + catch (e) { + console.log("Geolocation Error: Error calling success callback function."); + } + + if (id == "global") { + delete navigator._geo.listeners["global"]; + } +}; + +/** + * Native callback when watch position has an error. + * PRIVATE METHOD + * + * @param {String} id The ID of the watch + * @param {Number} code The error code + * @param {String} msg The error message + */ +Geolocation.prototype.fail = function(id, code, msg) { + try { + navigator._geo.listeners[id].fail(new PositionError(code, msg)); + } + catch (e) { + console.log("Geolocation Error: Error calling error callback function."); + } +}; + +/** + * Clears the specified heading watch. + * + * @param {String} id The ID of the watch returned from #watchPosition + */ +Geolocation.prototype.clearWatch = function(id) { + PhoneGap.execAsync(null, null, "Geolocation", "stop", [id]); + delete navigator._geo.listeners[id]; +}; + +/** + * Force the PhoneGap geolocation to be used instead of built-in. + */ +Geolocation.usingPhoneGap = false; +Geolocation.usePhoneGap = function() { + if (Geolocation.usingPhoneGap) { + return; + } + Geolocation.usingPhoneGap = true; + + // Set built-in geolocation methods to our own implementations + // (Cannot replace entire geolocation, but can replace individual methods) + navigator.geolocation.setLocation = navigator._geo.setLocation; + navigator.geolocation.getCurrentPosition = navigator._geo.getCurrentPosition; + navigator.geolocation.watchPosition = navigator._geo.watchPosition; + navigator.geolocation.clearWatch = navigator._geo.clearWatch; + navigator.geolocation.start = navigator._geo.start; + navigator.geolocation.stop = navigator._geo.stop; +}; PhoneGap.addConstructor(function() { - // Taken from Jesse's geo fix (similar problem) in PhoneGap iPhone. Go figure, same browser! - function __proxyObj(origObj, proxyObj, funkList) { - for (var v in funkList) { - origObj[funkList[v]] = proxyObj[funkList[v]]; - } - } - // In the case of Android, we can use the Native Geolocation Object if it exists, so only load this on 1.x devices - if (typeof navigator.geolocation == 'undefined') { - navigator.geolocation = new Geolocation(); - } + navigator._geo = new Geolocation(); + + // No native geolocation object for Android 1.x, so use PhoneGap geolocation + if (typeof navigator.geolocation == 'undefined') { + navigator.geolocation = navigator._geo; + Geolocation.usingPhoneGap = true; + } }); + function KeyEvent() { } @@ -1421,31 +1765,6 @@ if (document.keyEvent == null || typeof document.keyEvent == 'undefined') { window.keyEvent = document.keyEvent = new KeyEvent(); } -com.phonegap.AudioHandlerProxy = function() { - this.className = "com.phonegap.AudioHandler"; -}; -com.phonegap.AudioHandlerProxy.prototype.startRecordingAudio = function(id, file) { - return PhoneGap.exec(this.className, "startRecordingAudio", [id, file]); -}; -com.phonegap.AudioHandlerProxy.prototype.stopRecordingAudio = function(id) { - return PhoneGap.exec(this.className, "stopRecordingAudio", [id]); -}; -com.phonegap.AudioHandlerProxy.prototype.startPlayingAudio = function(id, file) { - return PhoneGap.exec(this.className, "startPlayingAudio", [id, file]); -}; -com.phonegap.AudioHandlerProxy.prototype.pausePlayingAudio = function(id) { - return PhoneGap.exec(this.className, "pausePlayingAudio", [id]); -}; -com.phonegap.AudioHandlerProxy.prototype.stopPlayingAudio = function(id) { - return PhoneGap.exec(this.className, "stopPlayingAudio", [id]); -}; -com.phonegap.AudioHandlerProxy.prototype.getCurrentPositionAudio = function(id) { - return PhoneGap.exec(this.className, "getCurrentPositionAudio", [id]); -}; -com.phonegap.AudioHandlerProxy.prototype.getDurationAudio = function(id, file) { - return PhoneGap.exec(this.className, "getDurationAudio", [id, file]); -}; -com.phonegap.AudioHandler = new com.phonegap.AudioHandlerProxy(); /** * List of media objects. @@ -1511,8 +1830,10 @@ PhoneGap.Media.onStatus = function(id, msg, value) { * errorCallback(int errorCode) - OPTIONAL * @param statusCallback The callback to be called when media status has changed. * statusCallback(int statusCode) - OPTIONAL + * @param positionCallback The callback to be called when media position has changed. + * positionCallback(long position) - OPTIONAL */ -Media = function(src, successCallback, errorCallback, statusCallback) { +Media = function(src, successCallback, errorCallback, statusCallback, positionCallback) { // successCallback optional if (successCallback && (typeof successCallback != "function")) { @@ -1532,19 +1853,27 @@ Media = function(src, successCallback, errorCallback, statusCallback) { return; } + // statusCallback optional + if (positionCallback && (typeof positionCallback != "function")) { + console.log("Media Error: positionCallback is not a function"); + return; + } + this.id = PhoneGap.createUUID(); PhoneGap.mediaObjects[this.id] = this; this.src = src; this.successCallback = successCallback; this.errorCallback = errorCallback; this.statusCallback = statusCallback; + this.positionCallback = positionCallback; this._duration = -1; + this._position = -1; }; // Media messages Media.MEDIA_STATE = 1; Media.MEDIA_DURATION = 2; -Media.MEDIA_ERROR = 3; +Media.MEDIA_ERROR = 9; // Media states Media.MEDIA_NONE = 0; @@ -1573,21 +1902,21 @@ MediaError.MEDIA_ERR_NONE_SUPPORTED = 4; * Start or resume playing audio file. */ Media.prototype.play = function() { - com.phonegap.AudioHandler.startPlayingAudio(this.id, this.src); + PhoneGap.execAsync(null, null, "Media", "startPlayingAudio", [this.id, this.src]); }; /** * Stop playing audio file. */ Media.prototype.stop = function() { - com.phonegap.AudioHandler.stopPlayingAudio(this.id); + return PhoneGap.execAsync(null, null, "Media", "stopPlayingAudio", [this.id]); }; /** * Pause playing audio file. */ Media.prototype.pause = function() { - com.phonegap.AudioHandler.pausePlayingAudio(this.id); + PhoneGap.execAsync(null, null, "Media", "pausePlayingAudio", [this.id]); }; /** @@ -1605,45 +1934,32 @@ Media.prototype.getDuration = function() { * * @return */ -Media.prototype.getCurrentPosition = function() { - return com.phonegap.AudioHandler.getCurrentPositionAudio(this.id); +Media.prototype.getCurrentPosition = function(success, fail) { + PhoneGap.execAsync(success, fail, "Media", "getCurrentPositionAudio", [this.id]); }; /** * Start recording audio file. */ Media.prototype.startRecord = function() { - com.phonegap.AudioHandler.startRecordingAudio(this.id, this.src); + PhoneGap.execAsync(null, null, "Media", "startRecordingAudio", [this.id, this.src]); }; /** * Stop recording audio file. */ Media.prototype.stopRecord = function() { - com.phonegap.AudioHandler.stopRecordingAudio(this.id); + PhoneGap.execAsync(null, null, "Media", "stopRecordingAudio", [this.id]); }; -com.phonegap.NetworkManagerProxy = function() { - this.className = "com.phonegap.NetworkManager"; -}; -com.phonegap.NetworkManagerProxy.prototype.isAvailable = function() { - return PhoneGap.exec(this.className, "isAvailable", []); -}; -com.phonegap.NetworkManagerProxy.prototype.isWifiActive = function() { - return PhoneGap.exec(this.className, "isWifiActive", []); -}; -com.phonegap.NetworkManagerProxy.prototype.isReachable = function(uri) { - return PhoneGap.exec(this.className, "isReachable", [uri]); -}; -com.phonegap.NetworkManager = new com.phonegap.NetworkManagerProxy(); /** * This class contains information about any NetworkStatus. * @constructor */ function NetworkStatus() { - this.code = null; - this.message = ""; + //this.code = null; + //this.message = ""; }; NetworkStatus.NOT_REACHABLE = 0; @@ -1667,39 +1983,34 @@ function Network() { * Called by the geolocation framework when the reachability status has changed. * @param {Reachibility} reachability The current reachability status. */ +// TODO: Callback from native code not implemented for Android Network.prototype.updateReachability = function(reachability) { this.lastReachability = reachability; }; /** - * + * Determine if a URI is reachable over the network. + * @param {Object} uri - * @param {Function} win + * @param {Function} callback * @param {Object} options (isIpAddress:boolean) */ -Network.prototype.isReachable = function(uri, win, options) { - var status = new NetworkStatus(); - if(com.phonegap.NetworkManager.isReachable(uri)) { - if (com.phonegap.NetworkManager.isWifiActive()) { - status.code = NetworkStatus.REACHABLE_VIA_WIFI_NETWORK; - } - else { - status.code = NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK; - } +Network.prototype.isReachable = function(uri, callback, options) { + var isIpAddress = false; + if (options && options.isIpAddress) { + isIpAddress = options.isIpAddress; } - else { - status.code = NetworkStatus.NOT_REACHABLE; - } - win(status); + PhoneGap.execAsync(callback, null, "Network Status", "isReachable", [uri, isIpAddress]); }; PhoneGap.addConstructor(function() { if (typeof navigator.network == "undefined") navigator.network = new Network(); -});/** +}); + +/** * This class provides access to notifications on the device. */ function Notification() { - } /** @@ -1731,7 +2042,7 @@ Notification.prototype.activityStop = function() { * @param {String} colour The colour of the light. */ Notification.prototype.blink = function(count, colour) { - + }; /** @@ -1739,16 +2050,17 @@ Notification.prototype.blink = function(count, colour) { * @param {Integer} mills The number of milliseconds to vibrate for. */ Notification.prototype.vibrate = function(mills) { - + PhoneGap.execAsync(null, null, "Notification", "vibrate", [mills]); }; /** * Causes the device to beep. + * On Android, the default notification ringtone is played. + * * @param {Integer} count The number of beeps. - * @param {Integer} volume The volume of the beep. */ -Notification.prototype.beep = function(count, volume) { - +Notification.prototype.beep = function(count) { + PhoneGap.execAsync(null, null, "Notification", "beep", [count]); }; // TODO: of course on Blackberry and Android there notifications in the UI as well @@ -1757,21 +2069,6 @@ PhoneGap.addConstructor(function() { if (typeof navigator.notification == "undefined") navigator.notification = new Notification(); }); -Notification.prototype.vibrate = function(mills) -{ - DroidGap.vibrate(mills); -} - -/* - * On the Android, we don't beep, we notify you with your - * notification! We shouldn't keep hammering on this, and should - * review what we want beep to do. - */ - -Notification.prototype.beep = function(count, volume) -{ - DroidGap.beep(count); -} /** * This class contains position information. * @param {Object} lat @@ -1852,17 +2149,7 @@ PhoneGap.addConstructor(function() { if (typeof navigator.splashScreen == "undefined") { navigator.splashScreen = SplashScreen; // SplashScreen object come from native side through addJavaScriptInterface } -});com.phonegap.StorageProxy = function() { - this.className = "com.phonegap.Storage"; -}; -com.phonegap.StorageProxy.prototype.executeSql = function(query, params, id) { - return PhoneGap.exec(this.className, "executeSql", [query, params, id]); -}; -com.phonegap.StorageProxy.prototype.openDatabase = function(name, version, display_name, size) { - return PhoneGap.exec(this.className, "openDatabase", [name, version, display_name, size]); -}; -com.phonegap.Storage = new com.phonegap.StorageProxy(); - +}); /* * This is purely for the Android 1.5/1.6 HTML 5 Storage * I was hoping that Android 2.0 would deprecate this, but given the fact that @@ -1907,7 +2194,7 @@ var Tx = function() { }; Tx.prototype.executeSql = function(query, params, win, fail) { - com.phonegap.Storage.executeSql(query, params, this.id); + PhoneGap.execAsync(null, null, "Storage", "executeSql", [query, params, this.id]); tx.win = win; tx.fail = fail; }; @@ -1926,7 +2213,7 @@ Rows.prototype.item = function(row_id) { }; var dbSetup = function(name, version, display_name, size) { - com.phonegap.Storage.openDatabase(name, version, display_name, size) + PhoneGap.execAsync(null, null, "Storage", "openDatabase", [name, version, display_name, size]); db_object = new DatabaseShell(); return db_object; }; diff --git a/framework/src/com/phonegap/AccelListener.java b/framework/src/com/phonegap/AccelListener.java index 4f4cae62..b24261d0 100755 --- a/framework/src/com/phonegap/AccelListener.java +++ b/framework/src/com/phonegap/AccelListener.java @@ -21,7 +21,7 @@ import android.webkit.WebView; * This class listens to the accelerometer sensor and stores the latest * acceleration values x,y,z. */ -public class AccelListener implements SensorEventListener, Plugin{ +public class AccelListener implements SensorEventListener, Plugin { public static int STOPPED = 0; public static int STARTING = 1; @@ -118,6 +118,7 @@ public class AccelListener implements SensorEventListener, Plugin{ return new PluginResult(PluginResult.Status.IO_EXCEPTION, AccelListener.ERROR_FAILED_TO_START); } } + this.lastAccessTime = System.currentTimeMillis(); JSONObject r = new JSONObject(); r.put("x", this.x); r.put("y", this.y); diff --git a/framework/src/com/phonegap/ContactAccessor.java b/framework/src/com/phonegap/ContactAccessor.java new file mode 100644 index 00000000..ac03c414 --- /dev/null +++ b/framework/src/com/phonegap/ContactAccessor.java @@ -0,0 +1,90 @@ +// Taken from Android Tutorials + +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.phonegap; + +import java.lang.reflect.Constructor; + +import android.app.Activity; +import android.webkit.WebView; + +import org.json.JSONArray; +import org.json.JSONObject; + +/** + * This abstract class defines SDK-independent API for communication with + * Contacts Provider. The actual implementation used by the application depends + * on the level of API available on the device. If the API level is Cupcake or + * Donut, we want to use the {@link ContactAccessorSdk3_4} class. If it is + * Eclair or higher, we want to use {@link ContactAccessorSdk5}. + */ +public abstract class ContactAccessor { + + /** + * Static singleton instance of {@link ContactAccessor} holding the + * SDK-specific implementation of the class. + */ + private static ContactAccessor sInstance; + protected final String LOG_TAG = "ContactsAccessor"; + protected Activity mApp; + protected WebView mView; + + public static ContactAccessor getInstance(WebView view, Activity app) { + if (sInstance == null) { + String className; + + /* + * Check the version of the SDK we are running on. Choose an + * implementation class designed for that version of the SDK. + * + * Unfortunately we have to use strings to represent the class + * names. If we used the conventional ContactAccessorSdk5.class.getName() + * syntax, we would get a ClassNotFoundException at runtime on pre-Eclair SDKs. + * Using the above syntax would force Dalvik to load the class and try to + * resolve references to all other classes it uses. Since the pre-Eclair + * does not have those classes, the loading of ContactAccessorSdk5 would fail. + */ + + if (android.os.Build.VERSION.RELEASE.startsWith("1.")) { + className = "com.phonegap.ContactAccessorSdk3_4"; + } else { + className = "com.phonegap.ContactAccessorSdk5"; + } + + /* + * Find the required class by name and instantiate it. + */ + try { + Class clazz = + Class.forName(className).asSubclass(ContactAccessor.class); + // Grab constructor for contactsmanager class dynamically. + Constructor classConstructor = clazz.getConstructor(Class.forName("android.webkit.WebView"), Class.forName("android.app.Activity")); + sInstance = classConstructor.newInstance(view, app); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + return sInstance; + } + + /** + * Handles searching through SDK-specific contacts API. + */ + public abstract void search(JSONArray filter, JSONObject options); +} \ No newline at end of file diff --git a/framework/src/com/phonegap/ContactAccessorSdk3_4.java b/framework/src/com/phonegap/ContactAccessorSdk3_4.java new file mode 100644 index 00000000..086e1052 --- /dev/null +++ b/framework/src/com/phonegap/ContactAccessorSdk3_4.java @@ -0,0 +1,362 @@ +// Taken from Android tutorials +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.phonegap; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import android.app.Activity; +import android.content.ContentResolver; +import android.database.Cursor; +import android.net.Uri; +import android.provider.Contacts.ContactMethods; +import android.provider.Contacts.ContactMethodsColumns; +import android.provider.Contacts.Organizations; +import android.provider.Contacts.People; +import android.provider.Contacts.Phones; +import android.util.Log; +import android.webkit.WebView; + +/** + * An implementation of {@link ContactAccessor} that uses legacy Contacts API. + * These APIs are deprecated and should not be used unless we are running on a + * pre-Eclair SDK. + *

+ * There are several reasons why we wouldn't want to use this class on an Eclair device: + *

    + *
  • It would see at most one account, namely the first Google account created on the device. + *
  • It would work through a compatibility layer, which would make it inherently less efficient. + *
  • Not relevant to this particular example, but it would not have access to new kinds + * of data available through current APIs. + *
+ */ +@SuppressWarnings("deprecation") +public class ContactAccessorSdk3_4 extends ContactAccessor { + private static final Map dbMap = new HashMap(); + static { + dbMap.put("id", People._ID); + dbMap.put("displayName", People.DISPLAY_NAME); + dbMap.put("phoneNumbers", Phones.NUMBER); + dbMap.put("phoneNumbers.value", Phones.NUMBER); + dbMap.put("emails", ContactMethods.DATA); + dbMap.put("emails.value", ContactMethods.DATA); + dbMap.put("addresses", ContactMethodsColumns.DATA); + dbMap.put("addresses.formatted", ContactMethodsColumns.DATA); + dbMap.put("ims", ContactMethodsColumns.DATA); + dbMap.put("ims.value", ContactMethodsColumns.DATA); + dbMap.put("organizations", Organizations.COMPANY); + dbMap.put("organizations.name", Organizations.COMPANY); + dbMap.put("organizations.title", Organizations.TITLE); + dbMap.put("note", People.NOTES); + } + + public ContactAccessorSdk3_4(WebView view, Activity app) + { + mApp = app; + mView = view; + } + + @Override + public void search(JSONArray filter, JSONObject options) { + String searchTerm = ""; + int limit = Integer.MAX_VALUE; + boolean multiple = true; + try { + searchTerm = options.getString("filter"); + if (searchTerm.length()==0) { + searchTerm = "%"; + } + else { + searchTerm = "%" + searchTerm + "%"; + } + multiple = options.getBoolean("multiple"); + if (multiple) { + limit = options.getInt("limit"); + } + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + + ContentResolver cr = mApp.getContentResolver(); + + Set contactIds = buildSetOfContactIds(filter, searchTerm); + + Iterator it = contactIds.iterator(); + + JSONArray contacts = new JSONArray(); + JSONObject contact; + String contactId; + int pos = 0; + while (it.hasNext() && (pos < limit)) { + contact = new JSONObject(); + try { + contactId = it.next(); + contact.put("id", contactId); + + // Do query for name and note + // Right now we are just querying the displayName + Cursor cur = cr.query(People.CONTENT_URI, + null, + "people._id = ?", + new String[] {contactId}, + null); + cur.moveToFirst(); + + // name + contact.put("displayName", cur.getString(cur.getColumnIndex(People.DISPLAY_NAME))); + // phone number + contact.put("phoneNumbers", phoneQuery(cr, contactId)); + // email + contact.put("emails", emailQuery(cr, contactId)); + // addresses + contact.put("addresses", addressQuery(cr, contactId)); + // organizations + contact.put("organizations", organizationQuery(cr, contactId)); + // ims + contact.put("ims", imQuery(cr, contactId)); + // note + contact.put("note", cur.getString(cur.getColumnIndex(People.NOTES))); + // nickname + // urls + // relationship + // birthdays + // anniversary + + pos++; + cur.close(); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + contacts.put(contact); + } + mView.loadUrl("javascript:navigator.service.contacts.droidDone('" + contacts.toString() + "');"); + } + + private Set buildSetOfContactIds(JSONArray filter, String searchTerm) { + Set contactIds = new HashSet(); + + String key; + try { + for (int i=0; i contactIds, + Uri uri, String projection, String selection, String[] selectionArgs) { + ContentResolver cr = mApp.getContentResolver(); + + Cursor cursor = cr.query( + uri, + null, + selection, + selectionArgs, + null); + + while (cursor.moveToNext()) { + contactIds.add(cursor.getString(cursor.getColumnIndex(projection))); + } + cursor.close(); + } + + private JSONArray imQuery(ContentResolver cr, String contactId) { + String imWhere = ContactMethods.PERSON_ID + + " = ? AND " + ContactMethods.KIND + " = ?"; + String[] imWhereParams = new String[]{contactId, ContactMethods.CONTENT_IM_ITEM_TYPE}; + Cursor cursor = cr.query(ContactMethods.CONTENT_URI, + null, imWhere, imWhereParams, null); + JSONArray ims = new JSONArray(); + JSONObject im; + while (cursor.moveToNext()) { + im = new JSONObject(); + try{ + im.put("primary", false); + im.put("value", cursor.getString( + cursor.getColumnIndex(ContactMethodsColumns.DATA))); + im.put("type", cursor.getString( + cursor.getColumnIndex(ContactMethodsColumns.TYPE))); + ims.put(im); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + } + cursor.close(); + return null; + } + + private JSONArray organizationQuery(ContentResolver cr, String contactId) { + String orgWhere = ContactMethods.PERSON_ID + " = ?"; + String[] orgWhereParams = new String[]{contactId}; + Cursor cursor = cr.query(Organizations.CONTENT_URI, + null, orgWhere, orgWhereParams, null); + JSONArray organizations = new JSONArray(); + JSONObject organization; + while (cursor.moveToNext()) { + organization = new JSONObject(); + try{ + organization.put("name", cursor.getString(cursor.getColumnIndex(Organizations.COMPANY))); + organization.put("title", cursor.getString(cursor.getColumnIndex(Organizations.TITLE))); + // organization.put("department", cursor.getString(cursor.getColumnIndex(Organizations))); + // organization.put("description", cursor.getString(cursor.getColumnIndex(Organizations))); + // organization.put("endDate", cursor.getString(cursor.getColumnIndex(Organizations))); + // organization.put("location", cursor.getString(cursor.getColumnIndex(Organizations))); + // organization.put("startDate", cursor.getString(cursor.getColumnIndex(Organizations))); + organizations.put(organization); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + } + return organizations; + } + + private JSONArray addressQuery(ContentResolver cr, String contactId) { + String addrWhere = ContactMethods.PERSON_ID + + " = ? AND " + ContactMethods.KIND + " = ?"; + String[] addrWhereParams = new String[]{contactId, + ContactMethods.CONTENT_POSTAL_ITEM_TYPE}; + Cursor cursor = cr.query(ContactMethods.CONTENT_URI, + null, addrWhere, addrWhereParams, null); + JSONArray addresses = new JSONArray(); + JSONObject address; + while (cursor.moveToNext()) { + address = new JSONObject(); + try{ + address.put("formatted", cursor.getString(cursor.getColumnIndex(ContactMethodsColumns.DATA))); + addresses.put(address); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + } + return addresses; + } + + private JSONArray phoneQuery(ContentResolver cr, String contactId) { + Cursor cursor = cr.query( + Phones.CONTENT_URI, + null, + Phones.PERSON_ID +" = ?", + new String[]{contactId}, null); + JSONArray phones = new JSONArray(); + JSONObject phone; + while (cursor.moveToNext()) { + phone = new JSONObject(); + try{ + phone.put("primary", false); + phone.put("value", cursor.getString(cursor.getColumnIndex(Phones.NUMBER))); + phone.put("type", cursor.getString(cursor.getColumnIndex(Phones.TYPE))); + phones.put(phone); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + } + return phones; + } + + private JSONArray emailQuery(ContentResolver cr, String contactId) { + Cursor cursor = cr.query( + ContactMethods.CONTENT_EMAIL_URI, + null, + ContactMethods.PERSON_ID +" = ?", + new String[]{contactId}, null); + JSONArray emails = new JSONArray(); + JSONObject email; + while (cursor.moveToNext()) { + email = new JSONObject(); + try{ + email.put("primary", false); + email.put("value", cursor.getString(cursor.getColumnIndex(ContactMethods.DATA))); + // TODO Find out why adding an email type throws and exception + //email.put("type", cursor.getString(cursor.getColumnIndex(ContactMethods.TYPE))); + emails.put(email); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + } + return emails; + } +} \ No newline at end of file diff --git a/framework/src/com/phonegap/ContactAccessorSdk5.java b/framework/src/com/phonegap/ContactAccessorSdk5.java new file mode 100644 index 00000000..bcad148f --- /dev/null +++ b/framework/src/com/phonegap/ContactAccessorSdk5.java @@ -0,0 +1,553 @@ +// Taken from Android tutorials +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.phonegap; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import android.app.Activity; +import android.content.ContentResolver; +import android.database.Cursor; +import android.net.Uri; +import android.provider.ContactsContract; +import android.util.Log; +import android.webkit.WebView; + +/** + * An implementation of {@link ContactAccessor} that uses current Contacts API. + * This class should be used on Eclair or beyond, but would not work on any earlier + * release of Android. As a matter of fact, it could not even be loaded. + *

+ * This implementation has several advantages: + *

    + *
  • It sees contacts from multiple accounts. + *
  • It works with aggregated contacts. So for example, if the contact is the result + * of aggregation of two raw contacts from different accounts, it may return the name from + * one and the phone number from the other. + *
  • It is efficient because it uses the more efficient current API. + *
  • Not obvious in this particular example, but it has access to new kinds + * of data available exclusively through the new APIs. Exercise for the reader: add support + * for nickname (see {@link android.provider.ContactsContract.CommonDataKinds.Nickname}) or + * social status updates (see {@link android.provider.ContactsContract.StatusUpdates}). + *
+ */ +public class ContactAccessorSdk5 extends ContactAccessor { + + private static final String WHERE_STRING = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?"; + private static final Map dbMap = new HashMap(); + static { + dbMap.put("id", ContactsContract.Contacts._ID); + dbMap.put("displayName", ContactsContract.Contacts.DISPLAY_NAME); + dbMap.put("name", ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME); + dbMap.put("name.formatted", ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME); + dbMap.put("name.familyName", ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME); + dbMap.put("name.givenName", ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME); + dbMap.put("name.middleName", ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME); + dbMap.put("name.honorificPrefix", ContactsContract.CommonDataKinds.StructuredName.PREFIX); + dbMap.put("name.honorificSuffix", ContactsContract.CommonDataKinds.StructuredName.SUFFIX); + dbMap.put("nickname", ContactsContract.CommonDataKinds.Nickname.NAME); + dbMap.put("phoneNumbers", ContactsContract.CommonDataKinds.Phone.NUMBER); + dbMap.put("phoneNumbers.value", ContactsContract.CommonDataKinds.Phone.NUMBER); + dbMap.put("emails", ContactsContract.CommonDataKinds.Email.DATA); + dbMap.put("emails.value", ContactsContract.CommonDataKinds.Email.DATA); + dbMap.put("addresses", ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS); + dbMap.put("addresses.formatted", ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS); + dbMap.put("addresses.streetAddress", ContactsContract.CommonDataKinds.StructuredPostal.STREET); + dbMap.put("addresses.locality", ContactsContract.CommonDataKinds.StructuredPostal.CITY); + dbMap.put("addresses.region", ContactsContract.CommonDataKinds.StructuredPostal.REGION); + dbMap.put("addresses.postalCode", ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE); + dbMap.put("addresses.country", ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY); + dbMap.put("ims", ContactsContract.CommonDataKinds.Im.DATA); + dbMap.put("ims.value", ContactsContract.CommonDataKinds.Im.DATA); + dbMap.put("organizations", ContactsContract.CommonDataKinds.Organization.COMPANY); + dbMap.put("organizations.name", ContactsContract.CommonDataKinds.Organization.COMPANY); + dbMap.put("organizations.department", ContactsContract.CommonDataKinds.Organization.DEPARTMENT); + dbMap.put("organizations.title", ContactsContract.CommonDataKinds.Organization.TITLE); + dbMap.put("organizations.location", ContactsContract.CommonDataKinds.Organization.OFFICE_LOCATION); + dbMap.put("organizations.description", ContactsContract.CommonDataKinds.Organization.JOB_DESCRIPTION); + //dbMap.put("published", null); + //dbMap.put("updated", null); + dbMap.put("birthday", ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE); + dbMap.put("anniversary", ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE); + //dbMap.put("gender", null); + dbMap.put("note", ContactsContract.CommonDataKinds.Note.NOTE); + //dbMap.put("preferredUsername", null); + //dbMap.put("photos.value", null); + //dbMap.put("tags.value", null); + dbMap.put("relationships", ContactsContract.CommonDataKinds.Relation.NAME); + dbMap.put("relationships.value", ContactsContract.CommonDataKinds.Relation.NAME); + dbMap.put("urls", ContactsContract.CommonDataKinds.Website.URL); + dbMap.put("urls.value", ContactsContract.CommonDataKinds.Website.URL); + //dbMap.put("accounts.domain", null); + //dbMap.put("accounts.username", null); + //dbMap.put("accounts.userid", null); + //dbMap.put("utcOffset", null); + //dbMap.put("connected", null); + } + + public ContactAccessorSdk5(WebView view, Activity app) + { + mApp = app; + mView = view; + } + + @Override + public void search(JSONArray filter, JSONObject options) { + String searchTerm = ""; + int limit = Integer.MAX_VALUE; + boolean multiple = true; + try { + searchTerm = options.getString("filter"); + if (searchTerm.length()==0) { + searchTerm = "%"; + } + else { + searchTerm = "%" + searchTerm + "%"; + } + multiple = options.getBoolean("multiple"); + if (multiple) { + limit = options.getInt("limit"); + } + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + // Get a cursor by creating the query. + ContentResolver cr = mApp.getContentResolver(); + + Set contactIds = buildSetOfContactIds(filter, searchTerm); + + Iterator it = contactIds.iterator(); + + JSONArray contacts = new JSONArray(); + JSONObject contact; + String contactId; + int pos = 0; + while (it.hasNext() && (pos < limit)) { + contact = new JSONObject(); + contactId = it.next(); + + try { + contact.put("id", contactId); + contact.put("displayName", displayNameQuery(cr, contactId)); + contact.put("name", nameQuery(cr, contactId)); + contact.put("phoneNumbers", phoneQuery(cr, contactId)); + contact.put("emails", emailQuery(cr, contactId)); + contact.put("addresses", addressQuery(cr, contactId)); + contact.put("organizations", organizationQuery(cr, contactId)); + contact.put("ims",imQuery(cr, contactId)); + contact.put("note",noteQuery(cr, contactId)); + contact.put("nickname",nicknameQuery(cr, contactId)); + contact.put("urls",websiteQuery(cr, contactId)); + contact.put("relationships",relationshipQuery(cr, contactId)); + contact.put("birthday",birthdayQuery(cr, contactId)); + contact.put("anniversary",anniversaryQuery(cr, contactId)); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + + contacts.put(contact); + pos++; + } + mView.loadUrl("javascript:navigator.service.contacts.droidDone('" + contacts.toString() + "');"); + } + + private Set buildSetOfContactIds(JSONArray filter, String searchTerm) { + Set contactIds = new HashSet(); + + String key; + try { + for (int i=0; i contactIds, + Uri uri, String projection, String selection, String[] selectionArgs) { + // Get a cursor by creating the query. + ContentResolver cr = mApp.getContentResolver(); + + Cursor cursor = cr.query( + uri, + new String[] {projection}, + selection, + selectionArgs, + null); + + while (cursor.moveToNext()) { + contactIds.add(cursor.getString(cursor.getColumnIndex(projection))); + } + cursor.close(); + } + + private String displayNameQuery(ContentResolver cr, String contactId) { + Cursor cursor = cr.query( + ContactsContract.Contacts.CONTENT_URI, + new String[] {ContactsContract.Contacts.DISPLAY_NAME}, + ContactsContract.Contacts._ID + " = ?", + new String[] {contactId}, + null); + cursor.moveToFirst(); + String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)); + cursor.close(); + return displayName; + } + + private JSONArray organizationQuery(ContentResolver cr, String contactId) { + String[] orgWhereParams = new String[]{contactId, + ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE}; + Cursor cursor = cr.query(ContactsContract.Data.CONTENT_URI, + null, WHERE_STRING, orgWhereParams, null); + JSONArray organizations = new JSONArray(); + JSONObject organization = new JSONObject(); + while (cursor.moveToNext()) { + try { + organization.put("department", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization.DEPARTMENT))); + organization.put("description", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization.JOB_DESCRIPTION))); + // TODO No endDate + // organization.put("endDate", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization))); + organization.put("location", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization.OFFICE_LOCATION))); + organization.put("name", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization.COMPANY))); + // TODO no startDate + // organization.put("startDate", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization))); + organization.put("title", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Organization.TITLE))); + organizations.put(organization); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + } + cursor.close(); + return organizations; + } + + private JSONArray addressQuery(ContentResolver cr, String contactId) { + String[] addrWhereParams = new String[]{contactId, + ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE}; + Cursor cursor = cr.query(ContactsContract.Data.CONTENT_URI, + null, WHERE_STRING, addrWhereParams, null); + JSONArray addresses = new JSONArray(); + JSONObject address = new JSONObject(); + while (cursor.moveToNext()) { + try { + address.put("formatted", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS))); + address.put("streetAddress", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.STREET))); + address.put("locality", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.CITY))); + address.put("region", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.REGION))); + address.put("postalCode", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE))); + address.put("country", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY))); + addresses.put(address); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + } + cursor.close(); + return addresses; + } + + private JSONObject nameQuery(ContentResolver cr, String contactId) { + String[] addrWhereParams = new String[]{contactId, + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}; + Cursor name = cr.query(ContactsContract.Data.CONTENT_URI, + null, WHERE_STRING, addrWhereParams, null); + JSONObject contactName = new JSONObject(); + if (name.moveToFirst()) { + try { + String familyName = name.getString(name.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME)); + String givenName = name.getString(name.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME)); + String middleName = name.getString(name.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME)); + String honorificPrefix = name.getString(name.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.PREFIX)); + String honorificSuffix = name.getString(name.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.SUFFIX)); + + // Create the formatted name + StringBuffer formatted = new StringBuffer(""); + if (honorificPrefix != null) { formatted.append(honorificPrefix + " "); } + if (givenName != null) { formatted.append(givenName + " "); } + if (middleName != null) { formatted.append(middleName + " "); } + if (familyName != null) { formatted.append(familyName + " "); } + if (honorificSuffix != null) { formatted.append(honorificSuffix + " "); } + + contactName.put("familyName", familyName); + contactName.put("givenName", givenName); + contactName.put("middleName", middleName); + contactName.put("honorificPrefix", honorificPrefix); + contactName.put("honorificSuffix", honorificSuffix); + contactName.put("formatted", formatted); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + } + name.close(); + return contactName; + } + + private JSONArray phoneQuery(ContentResolver cr, String contactId) { + Cursor phones = cr.query( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + null, + ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + contactId, + null, null); + JSONArray phoneNumbers = new JSONArray(); + JSONObject phoneNumber = new JSONObject(); + while (phones.moveToNext()) { + try { + phoneNumber.put("primary", false); // Android does not store primary attribute + phoneNumber.put("value", phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))); + phoneNumber.put("type", phones.getInt(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE))); + phoneNumbers.put(phoneNumber); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + } + phones.close(); + return phoneNumbers; + } + + private JSONArray emailQuery(ContentResolver cr, String contactId) { + Cursor emails = cr.query( + ContactsContract.CommonDataKinds.Email.CONTENT_URI, + null, + ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = " + contactId, + null, null); + JSONArray emailAddresses = new JSONArray(); + JSONObject email = new JSONObject(); + while (emails.moveToNext()) { + try { + email.put("primary", false); // Android does not store primary attribute + email.put("value", emails.getString(emails.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA))); + email.put("type", emails.getInt(emails.getColumnIndex(ContactsContract.CommonDataKinds.Email.TYPE))); + emailAddresses.put(email); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + } + emails.close(); + return emailAddresses; + } + + private JSONArray imQuery(ContentResolver cr, String contactId) { + String[] addrWhereParams = new String[]{contactId, + ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE}; + Cursor cursor = cr.query(ContactsContract.Data.CONTENT_URI, + null, WHERE_STRING, addrWhereParams, null); + JSONArray ims = new JSONArray(); + JSONObject im = new JSONObject(); + while (cursor.moveToNext()) { + try { + im.put("primary", false); // Android does not store primary attribute + im.put("value", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA))); + im.put("type", cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.TYPE))); + ims.put(im); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + } + cursor.close(); + return ims; + } + + private String noteQuery(ContentResolver cr, String contactId) { + String[] noteWhereParams = new String[]{contactId, + ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE}; + Cursor cursor = cr.query(ContactsContract.Data.CONTENT_URI, + null, WHERE_STRING, noteWhereParams, null); + String note = new String(""); + if (cursor.moveToFirst()) { + note = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Note.NOTE)); + } + cursor.close(); + return note; + } + + private String nicknameQuery(ContentResolver cr, String contactId) { + String[] nicknameWhereParams = new String[]{contactId, + ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE}; + Cursor cursor = cr.query(ContactsContract.Data.CONTENT_URI, + null, WHERE_STRING, nicknameWhereParams, null); + String nickname = new String(""); + if (cursor.moveToFirst()) { + nickname = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Nickname.NAME)); + } + cursor.close(); + return nickname; + } + + private JSONArray websiteQuery(ContentResolver cr, String contactId) { + String[] websiteWhereParams = new String[]{contactId, + ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE}; + Cursor cursor = cr.query(ContactsContract.Data.CONTENT_URI, + null, WHERE_STRING, websiteWhereParams, null); + JSONArray websites = new JSONArray(); + JSONObject website = new JSONObject(); + while (cursor.moveToNext()) { + try { + website.put("primary", false); // Android does not store primary attribute + website.put("value", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Website.URL))); + website.put("type", cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Website.TYPE))); + websites.put(website); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + } + cursor.close(); + return websites; + } + + private JSONArray relationshipQuery(ContentResolver cr, String contactId) { + String[] relationshipWhereParams = new String[]{contactId, + ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE}; + Cursor cursor = cr.query(ContactsContract.Data.CONTENT_URI, + null, WHERE_STRING, relationshipWhereParams, null); + JSONArray relationships = new JSONArray(); + JSONObject relationship = new JSONObject(); + while (cursor.moveToNext()) { + try { + relationship.put("primary", false); // Android does not store primary attribute + relationship.put("value", cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Relation.NAME))); + relationship.put("type", cursor.getInt(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Relation.TYPE))); + relationships.put(relationship); + } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); + } + } + cursor.close(); + return relationships; + } + + private String birthdayQuery(ContentResolver cr, String contactId) { + String birthday = conditionalStringQuery(cr, contactId, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE, + ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY, ContactsContract.CommonDataKinds.Event.TYPE, + ContactsContract.CommonDataKinds.Event.START_DATE); + return birthday; + } + + private String anniversaryQuery(ContentResolver cr, String contactId) { + String anniversary = conditionalStringQuery(cr, contactId, ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE, + ContactsContract.CommonDataKinds.Event.TYPE_ANNIVERSARY, ContactsContract.CommonDataKinds.Event.TYPE, + ContactsContract.CommonDataKinds.Event.START_DATE); + return anniversary; + } + + private String conditionalStringQuery(ContentResolver cr, String contactId, String dataType, int type, String label, String data) { + String[] whereParams = new String[]{contactId, dataType}; + Cursor cursor = cr.query(ContactsContract.Data.CONTENT_URI, + null, WHERE_STRING, whereParams, null); + String retVal = new String(""); + while (cursor.moveToNext()) { + if (type == cursor.getInt(cursor.getColumnIndex(label))) { + retVal = cursor.getString(cursor.getColumnIndex(data)); + } + } + cursor.close(); + return retVal; + } +} \ No newline at end of file diff --git a/framework/src/com/phonegap/ContactManager.java b/framework/src/com/phonegap/ContactManager.java index 8924ccf9..9cf6a891 100755 --- a/framework/src/com/phonegap/ContactManager.java +++ b/framework/src/com/phonegap/ContactManager.java @@ -6,32 +6,17 @@ import org.json.JSONException; import com.phonegap.api.Plugin; import com.phonegap.api.PluginResult; -import android.provider.Contacts.ContactMethods; -import android.provider.Contacts.People; import android.util.Log; import android.webkit.WebView; import android.content.Intent; -import android.net.Uri; -import android.database.Cursor; -import android.database.sqlite.SQLiteException; -@SuppressWarnings("deprecation") public class ContactManager implements Plugin { - public class ContactTriplet - { - public String name = ""; - public String email = ""; - public String phone = ""; - } - + private static ContactAccessor contactAccessor; WebView webView; // WebView object DroidGap ctx; // DroidGap object private static final String LOG_TAG = "Contact Query"; - Uri mPeople = android.provider.Contacts.People.CONTENT_URI; - Uri mPhone = android.provider.Contacts.Phones.CONTENT_URI; - Uri mEmail = android.provider.Contacts.ContactMethods.CONTENT_URI; /** * Constructor. @@ -67,18 +52,28 @@ public class ContactManager implements Plugin { * @return A CommandResult object with a status and message. */ public PluginResult execute(String action, JSONArray args) { + if (contactAccessor == null) { + contactAccessor = ContactAccessor.getInstance(webView, ctx); + } PluginResult.Status status = PluginResult.Status.OK; String result = ""; try { - if (action.equals("getContactsAndSendBack")) { - this.getContactsAndSendBack(); + if (action.equals("search")) { + contactAccessor.search(args.getJSONArray(0), args.getJSONObject(1)); } - else if (action.equals("search")) { - this.search(args.getString(0), args.getString(1), args.getString(2)); + else if (action.equals("create")) { + // TODO Coming soon! + } + else if (action.equals("save")) { + // TODO Coming soon! + } + else if (action.equals("remove")) { + // TODO Coming soon! } return new PluginResult(status, result); } catch (JSONException e) { + Log.e(LOG_TAG, e.getMessage(), e); return new PluginResult(PluginResult.Status.JSON_EXCEPTION); } } @@ -123,269 +118,4 @@ public class ContactManager implements Plugin { */ public void onActivityResult(int requestCode, int resultCode, Intent intent) { } - - //-------------------------------------------------------------------------- - // LOCAL METHODS - //-------------------------------------------------------------------------- - - // This is to add backwards compatibility to the OLD Contacts API\ - public void getContactsAndSendBack() - { - String[] projection = new String[] { - People._ID, - People.NAME, - People.NUMBER, - People.PRIMARY_EMAIL_ID - }; - - try{ - Cursor myCursor = this.ctx.managedQuery(mPeople, projection, - null, null , People.NAME + " ASC"); - processResults(myCursor, true); - } - catch (SQLiteException ex) - { - Log.d(LOG_TAG, ex.getMessage()); - } - } - - public void search(String name, String npa, String email) - { - - if (email.length() > 0) - searchByEmail(email); - else - searchPeople(name, npa); - } - - private void searchByEmail(String email) - { - String[] projection = new String[] { - ContactMethods._ID, - ContactMethods.DATA, - ContactMethods.KIND, - ContactMethods.PERSON_ID - }; - String[] variables = new String[] { - email - }; - - try{ - Cursor myCursor = this.ctx.managedQuery(mEmail, projection, - "contact_methods." + ContactMethods.DATA + " = ?" + "AND contact_methods.kind = 1", variables , ContactMethods.DATA + " ASC"); - getMethodData(myCursor); - - } - catch (SQLiteException ex) - { - Log.d(LOG_TAG, ex.getMessage()); - } - - } - - private void searchPeople(String name, String number) - { - String conditions = ""; - - if (name.length() == 0) - { - name = "%"; - conditions += People.NAME + " LIKE ? AND "; - } - else - { - conditions += People.NAME + " = ? AND "; - } - - if (number.length() == 0) - number = "%"; - else - { - number = number.replace('+', '%'); - number = number.replace('.', '%'); - number = number.replace('-', '%'); - } - - conditions += People.NUMBER + " LIKE ? "; - - String[] projection = new String[] { - People._ID, - People.NAME, - People.NUMBER, - People.PRIMARY_EMAIL_ID - }; - - String[] variables = new String[] { - name, number - }; - - try{ - Cursor myCursor = this.ctx.managedQuery(mPeople, projection, - conditions, variables , People.NAME + " ASC"); - processResults(myCursor, false); - } - catch (SQLiteException ex) - { - Log.d(LOG_TAG, ex.getMessage()); - } - - } - - private void processResults(Cursor cur, boolean all){ - - if (cur.moveToFirst()) { - - String name; - String phoneNumber; - String email_id; - String email; - - int nameColumn = cur.getColumnIndex(People.NAME); - int phoneColumn = cur.getColumnIndex(People.NUMBER); - int emailIdColumn = cur.getColumnIndex(People.PRIMARY_EMAIL_ID); - - do { - // Get the field values - name = cur.getString(nameColumn); - phoneNumber = cur.getString(phoneColumn); - email_id = cur.getString(emailIdColumn); - if (email_id != null && email_id.length() > 0) - email = getEmail(email_id); - else - email = ""; - - // Code for backwards compatibility with the OLD Contacts API - if (all) { - this.ctx.sendJavascript("navigator.contacts.droidFoundContact('" + name + "','" + phoneNumber + "','" + email +"');"); - } - else { - this.ctx.sendJavascript("navigator.contacts.droidFoundContact('" + name + "','" + phoneNumber + "','" + email +"');"); - } - } while (cur.moveToNext()); - if (all) { - this.ctx.sendJavascript("navigator.contacts.droidDone();"); - } - else { - this.ctx.sendJavascript("navigator.contacts.droidDone();"); - } - } - else - { - if (all) { - this.ctx.sendJavascript("navigator.contacts.fail('Error');"); - } - else { - this.ctx.sendJavascript("navigator.contacts.fail('None found!');"); - } - } - } - - private void getMethodData(Cursor cur) - { - ContactTriplet data = new ContactTriplet(); - String id; - String email; - - if (cur.moveToFirst()) { - - int idColumn = cur.getColumnIndex(ContactMethods._ID); - int emailColumn = cur.getColumnIndex(ContactMethods.DATA); - do { - // Get the field values - id = cur.getString(idColumn); - email = cur.getString(emailColumn); - - data = getContactData(id); - if(data != null) - { - data.email = email; - this.ctx.sendJavascript("navigator.contacts.droidFoundContact('" + data.name + "','" + data.phone + "','" + data.email +"');"); - } - } while (cur.moveToNext()); - this.ctx.sendJavascript("navigator.contacts.droidDoneContacts();"); - } - } - - private ContactTriplet getContactData(String id) { - ContactTriplet data = null; - String[] projection = new String[] { - People._ID, - People.NAME, - People.NUMBER, - People.PRIMARY_EMAIL_ID - }; - - String[] variables = new String[] { - id - }; - - try{ - Cursor myCursor = this.ctx.managedQuery(mPeople, projection, - People.PRIMARY_EMAIL_ID + " = ?", variables , People.NAME + " ASC"); - data = getTriplet(myCursor); - } - catch (SQLiteException ex) - { - Log.d(LOG_TAG, ex.getMessage()); - } - - return data; - } - - private ContactTriplet getTriplet(Cursor cur) { - ContactTriplet data = new ContactTriplet(); - if (cur.moveToFirst()) { - - int nameColumn = cur.getColumnIndex(People.NAME); - int numberColumn = cur.getColumnIndex(People.NUMBER); - do { - - data.name = cur.getString(nameColumn); - data.phone = cur.getString(numberColumn); - - } while (cur.moveToNext()); - } - return data; - } - - private String getEmailColumnData(Cursor cur) - { - String email = ""; - if (cur != null && cur.moveToFirst()) { - int emailColumn = cur.getColumnIndex(ContactMethods.DATA); - do { - // Get the field values - email = cur.getString(emailColumn); - } while (cur.moveToNext()); - } - return email; - } - - private String getEmail(String id) - { - String email = ""; - String[] projection = new String[] { - ContactMethods._ID, - ContactMethods.DATA, - ContactMethods.KIND - }; - String[] variables = new String[] { - id - }; - - try - { - Cursor myCursor = this.ctx.managedQuery(mEmail, projection, - "contact_methods." + ContactMethods._ID + " = ?" + " AND contact_methods.kind = 1", variables , ContactMethods.DATA + " ASC"); - email = getEmailColumnData(myCursor); - } - catch (SQLiteException ex) - { - Log.d(LOG_TAG, ex.getMessage()); - } - - return email; - } - - } diff --git a/framework/src/com/phonegap/Device.java b/framework/src/com/phonegap/Device.java old mode 100644 new mode 100755 index babd09fe..80c1281b --- a/framework/src/com/phonegap/Device.java +++ b/framework/src/com/phonegap/Device.java @@ -23,23 +23,16 @@ package com.phonegap; */ import java.util.TimeZone; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; - import com.phonegap.api.Plugin; import com.phonegap.api.PluginResult; - import android.content.Context; import android.content.Intent; -import android.net.Uri; -import android.os.Vibrator; import android.provider.Settings; import android.telephony.TelephonyManager; import android.webkit.WebView; -import android.media.Ringtone; -import android.media.RingtoneManager; public class Device implements Plugin { @@ -101,12 +94,6 @@ public class Device implements Plugin { //r.put("phonegap", pg); return new PluginResult(status, r); } - else if (action.equals("beep")) { - this.beep(args.getLong(0)); - } - else if (action.equals("vibrate")) { - this.vibrate(args.getLong(0)); - } return new PluginResult(status, result); } catch (JSONException e) { return new PluginResult(PluginResult.Status.JSON_EXCEPTION); @@ -160,46 +147,7 @@ public class Device implements Plugin { //-------------------------------------------------------------------------- // LOCAL METHODS //-------------------------------------------------------------------------- - - /** - * Beep plays the default notification ringtone. - * - * @param count Number of times to play notification - */ - public void beep(long count) { - Uri ringtone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); - Ringtone notification = RingtoneManager.getRingtone(this.ctx, ringtone); - // If phone is not set to silent mode - if (notification != null) { - for (long i = 0; i < count; ++i) { - notification.play(); - long timeout = 5000; - while (notification.isPlaying() && (timeout > 0)) { - timeout = timeout - 100; - try { - Thread.sleep(100); - } catch (InterruptedException e) { - } - } - } - } - } - - /** - * Vibrates the device for the specified amount of time. - * - * @param time Time to vibrate in ms. - */ - public void vibrate(long time){ - // Start the vibration, 0 defaults to half a second. - if (time == 0) { - time = 500; - } - Vibrator vibrator = (Vibrator) this.ctx.getSystemService(Context.VIBRATOR_SERVICE); - vibrator.vibrate(time); - } - /** * Get the OS name. * diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 70d15328..a861732c 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -296,6 +296,7 @@ public class DroidGap extends Activity { this.addService("File", "com.phonegap.FileUtils"); this.addService("Location", "com.phonegap.GeoBroker"); this.addService("Network Status", "com.phonegap.NetworkManager"); + this.addService("Notification", "com.phonegap.Notification"); this.addService("Storage", "com.phonegap.Storage"); this.addService("Temperature", "com.phonegap.TempListener"); } diff --git a/framework/src/com/phonegap/Notification.java b/framework/src/com/phonegap/Notification.java new file mode 100755 index 00000000..68a18397 --- /dev/null +++ b/framework/src/com/phonegap/Notification.java @@ -0,0 +1,157 @@ +package com.phonegap; + +import org.json.JSONArray; +import org.json.JSONException; +import com.phonegap.api.Plugin; +import com.phonegap.api.PluginResult; +import android.content.Context; +import android.content.Intent; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Vibrator; +import android.webkit.WebView; + +/** + * This class provides access to notifications on the device. + */ +public class Notification implements Plugin { + + WebView webView; // WebView object + DroidGap ctx; // DroidGap object + + /** + * Constructor. + */ + public Notification() { + } + + /** + * Sets the context of the Command. This can then be used to do things like + * get file paths associated with the Activity. + * + * @param ctx The context of the main Activity. + */ + public void setContext(DroidGap ctx) { + this.ctx = ctx; + } + + /** + * Sets the main View of the application, this is the WebView within which + * a PhoneGap app runs. + * + * @param webView The PhoneGap WebView + */ + public void setView(WebView webView) { + this.webView = webView; + } + + /** + * Executes the request and returns CommandResult. + * + * @param action The command to execute. + * @param args JSONArry of arguments for the command. + * @return A CommandResult object with a status and message. + */ + public PluginResult execute(String action, JSONArray args) { + PluginResult.Status status = PluginResult.Status.OK; + String result = ""; + + try { + if (action.equals("beep")) { + this.beep(args.getLong(0)); + } + else if (action.equals("vibrate")) { + this.vibrate(args.getLong(0)); + } + return new PluginResult(status, result); + } catch (JSONException e) { + return new PluginResult(PluginResult.Status.JSON_EXCEPTION); + } + } + + /** + * Identifies if action to be executed returns a value and should be run synchronously. + * + * @param action The action to execute + * @return T=returns value + */ + public boolean isSynch(String action) { + return false; + } + + /** + * Called when the system is about to start resuming a previous activity. + */ + public void onPause() { + } + + /** + * Called when the activity will start interacting with the user. + */ + public void onResume() { + } + + /** + * Called by AccelBroker when listener is to be shut down. + * Stop listener. + */ + public void onDestroy() { + } + + /** + * Called when an activity you launched exits, giving you the requestCode you started it with, + * the resultCode it returned, and any additional data from it. + * + * @param requestCode The request code originally supplied to startActivityForResult(), + * allowing you to identify who this result came from. + * @param resultCode The integer result code returned by the child activity through its setResult(). + * @param data An Intent, which can return result data to the caller (various data can be attached to Intent "extras"). + */ + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + } + + //-------------------------------------------------------------------------- + // LOCAL METHODS + //-------------------------------------------------------------------------- + + /** + * Beep plays the default notification ringtone. + * + * @param count Number of times to play notification + */ + public void beep(long count) { + Uri ringtone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); + Ringtone notification = RingtoneManager.getRingtone(this.ctx, ringtone); + + // If phone is not set to silent mode + if (notification != null) { + for (long i = 0; i < count; ++i) { + notification.play(); + long timeout = 5000; + while (notification.isPlaying() && (timeout > 0)) { + timeout = timeout - 100; + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + } + } + } + } + + /** + * Vibrates the device for the specified amount of time. + * + * @param time Time to vibrate in ms. + */ + public void vibrate(long time){ + // Start the vibration, 0 defaults to half a second. + if (time == 0) { + time = 500; + } + Vibrator vibrator = (Vibrator) this.ctx.getSystemService(Context.VIBRATOR_SERVICE); + vibrator.vibrate(time); + } + +}