http://git-wip-us.apache.org/repos/asf/incubator-cordova-webos/blob/246123e9/framework/phonegap-1.5.0.js ---------------------------------------------------------------------- diff --git a/framework/phonegap-1.5.0.js b/framework/phonegap-1.5.0.js new file mode 100644 index 0000000..1571443 --- /dev/null +++ b/framework/phonegap-1.5.0.js @@ -0,0 +1,1828 @@ +if (typeof(DeviceInfo) != 'object') + DeviceInfo = {}; + +function PhoneGap() { + ready = true; + available = true; + sceneController = null; +}; + +PhoneGap.exec = function(win, fail, clazz, action, args) { + + setTimeout(function() { + PhoneGap.plugins[clazz].execute(action, args, win, fail); + }, 0); + +} + +PhoneGap.checkArgs = function(args, func) { + if (typeof args == 'object') + func.apply(null, args); + else + func(args); +} + +PhoneGap.callback = function(success, win, fail) { + if (success) + win(); + else + fail(); +} + +// translates the action into an API call +accelerometerAPI = { + execute: function(action, args, win, fail) { + var actionFound = false; + switch(action) { + case 'setFastAccelerometer': + PhoneGap.checkArgs(args, navigator.accelerometer.setFastAccelerometer); + actionFound = true; + break; + case 'getCurrentAcceleration': + PhoneGap.checkArgs(args, navigator.accelerometer.getCurrentAcceleration); + actionFound = true; + break; + case 'watchAcceleration': + PhoneGap.checkArgs(args, navigator.accelerometer.watchAcceleration); + actionFound = true; + break; + case 'clearWatch': + PhoneGap.checkArgs(args, navigator.accelerometer.clearWatch); + actionFound = true; + break; + case 'start': + PhoneGap.checkArgs(args, navigator.accelerometer.start); + actionFound = true; + break; + } + + PhoneGap.callback(actionFound, win, fail); + } +} + +applicationAPI = { + execute: function(action, args, win, fail) { + var actionFound = false; + switch(action) { + case 'activate': + PhoneGap.checkArgs(args, navigator.application.activate); + actionFound = true; + break; + case 'deactivate': + PhoneGap.checkArgs(args, navigator.application.deactivate); + actionFound = true; + break; + case 'getIdentifier': + PhoneGap.checkArgs(args, navigator.application.getIdentifier); + actionFound = true; + break; + } + + PhoneGap.callback(actionFound, win, fail); + } +} + +cameraAPI = { + execute: function(action, args, win, fail) { + var actionFound = false; + switch(action) { + case 'getPicture': + console.log("in here"); + PhoneGap.checkArgs(args, navigator.camera.getPicture); + actionFound = true; + break; + } + + PhoneGap.callback(actionFound, win, fail); + } +} + +debugAPI = { + execute: function(action, args, win, fail) { + var actionFound = false; + switch(action) { + case 'log': + PhoneGap.checkArgs(args, window.debug.log); + actionFound = true; + break; + case 'warn': + PhoneGap.checkArgs(args, window.debug.warn); + actionFound = true; + break; + case 'error': + PhoneGap.checkArgs(args, window.debug.error); + actionFound = true; + break; + } + + PhoneGap.callback(actionFound, win, fail); + } +} + +deviceAPI = { + execute: function(action, args, win, fail) { + var actionFound = false; + switch(action) { + case 'getDeviceInfo': + PhoneGap.checkArgs(args, navigator.device.getDeviceInfo); + actionFound = true; + break; + } + + PhoneGap.callback(actionFound, win, fail); + } +} + +geolocationAPI = { + execute: function(action, args, win, fail) { + var actionFound = false; + switch(action) { + case 'getCurrentPosition': + PhoneGap.checkArgs(args, navigator.geolocation.getCurrentPosition); + actionFound = true; + break; + case 'watchPosition': + PhoneGap.checkArgs(args, navigator.geolocation.watchPosition); + actionFound = true; + break; + case 'clearWatch': + PhoneGap.checkArgs(args, navigator.geolocation.clearWatch); + actionFound = true; + break; + case 'start': + PhoneGap.checkArgs(args, navigator.geolocation.start); + actionFound = true; + break; + case 'stop': + PhoneGap.checkArgs(args, navigator.geolocation.stop); + actionFound = true; + break; + } + + PhoneGap.callback(actionFound, win, fail); + } +} + +mapAPI = { + execute: function(action, args, win, fail) { + var actionFound = false; + switch(action) { + case 'show': + PhoneGap.checkArgs(args, navigator.map.show); + actionFound = true; + break; + } + + PhoneGap.callback(actionFound, win, fail); + } +} + +mouseAPI = { + execute: function(action, args, win, fail) { + var actionFound = false; + switch(action) { + case 'simulateMouseClick': + PhoneGap.checkArgs(args, navigator.mouse.simulateMouseClick); + actionFound = true; + break; + } + + PhoneGap.callback(actionFound, win, fail); + } +} + +networkAPI = { + execute: function(action, args, win, fail) { + var actionFound = false; + switch(action) { + case 'isReachable': + PhoneGap.checkArgs(args, navigator.network.isReachable); + actionFound = true; + break; + } + + PhoneGap.callback(actionFound, win, fail); + } +} + +notificationAPI = { + execute: function(action, args, win, fail) { + var actionFound = false; + switch(action) { + case 'alert': + PhoneGap.checkArgs(args, navigator.notification.alert); + actionFound = true; + break; + case 'showBanner': + PhoneGap.checkArgs(args, navigator.notification.showBanner); + actionFound = true; + break; + case 'newDashboard': + PhoneGap.checkArgs(args, navigator.notification.newDashboard); + actionFound = true; + break; + case 'removeBannerMessage': + PhoneGap.checkArgs(args, navigator.notification.removeBannerMessage); + actionFound = true; + break; + case 'clearBannerMessage': + PhoneGap.checkArgs(args, navigator.notification.clearBannerMessage); + actionFound = true; + break; + case 'vibrate': + PhoneGap.checkArgs(args, navigator.notification.vibrate); + actionFound = true; + break; + case 'beep': + PhoneGap.checkArgs(args, navigator.notification.beep); + actionFound = true; + break; + } + + PhoneGap.callback(actionFound, win, fail); + } +} + +orientationAPI = { + execute: function(action, args, win, fail) { + var actionFound = false; + switch(action) { + case 'setOrientation': + PhoneGap.checkArgs(args, navigator.orientation.setOrientation); + actionFound = true; + break; + case 'getOrientation': + PhoneGap.checkArgs(args, navigator.orientation.getOrientation); + actionFound = true; + break; + case 'start': + PhoneGap.checkArgs(args, navigator.orientation.start); + actionFound = true; + break; + case 'watchOrientation': + PhoneGap.checkArgs(args, navigator.orientation.watchOrientation); + actionFound = true; + break; + case 'clearWatch': + PhoneGap.checkArgs(args, navigator.orientation.clearWatch); + actionFound = true; + break; + } + + PhoneGap.callback(actionFound, win, fail); + } +} + +smsAPI = { + execute: function(action, args, win, fail) { + var actionFound = false; + switch(action) { + case 'send': + PhoneGap.checkArgs(args, navigator.sms.send); + actionFound = true; + break; + } + + PhoneGap.callback(actionFound, win, fail); + } +} + +telephonyAPI = { + execute: function(action, args, win, fail) { + var actionFound = false; + switch(action) { + case 'send': + PhoneGap.checkArgs(args, navigator.telephony.send); + actionFound = true; + break; + } + + PhoneGap.callback(actionFound, win, fail); + } +} + +windowAPI = { + execute: function(action, args, win, fail) { + var actionFound = false; + switch(action) { + case 'newCard': + PhoneGap.checkArgs(args, navigator.window.newCard); + actionFound = true; + break; + case 'setFullScreen': + PhoneGap.checkArgs(args, navigator.window.setFullScreen); + actionFound = true; + break; + case 'setWindowProperties': + PhoneGap.checkArgs(args, navigator.window.setWindowProperties); + actionFound = true; + break; + case 'blockScreenTimeout': + PhoneGap.checkArgs(args, navigator.window.blockScreenTimeout); + actionFound = true; + break; + case 'setSubtleLightbar': + PhoneGap.checkArgs(args, navigator.window.setSubtleLightbar); + actionFound = true; + break; + + } + + PhoneGap.callback(actionFound, win, fail); + } +} + +// this mapping acts as a shim to the webOS APIs +PhoneGap.plugins = {}; +PhoneGap.plugins['navigator.accelerometer'] = accelerometerAPI; +PhoneGap.plugins['navigator.application'] = applicationAPI; +PhoneGap.plugins['navigator.camera'] = cameraAPI; +PhoneGap.plugins['window.debug'] = debugAPI; +PhoneGap.plugins['navigator.device'] = deviceAPI; +PhoneGap.plugins['navigator.geolocation'] = geolocationAPI; +PhoneGap.plugins['navigator.map'] = mapAPI; +PhoneGap.plugins['navigator.mouse'] = mouseAPI; +PhoneGap.plugins['navigator.network'] = networkAPI; +PhoneGap.plugins['navigator.notification'] = notificationAPI; +PhoneGap.plugins['navigator.orientation'] = orientationAPI; +PhoneGap.plugins['navigator.sms'] = smsAPI; +PhoneGap.plugins['navigator.telephony'] = telephonyAPI; +PhoneGap.plugins['navigator.window'] = windowAPI; + + +document.addEventListener('DOMContentLoaded', function () { + window.phonegap = new PhoneGap(); + navigator.device.deviceReady(); +}); +/* + * This class contains acceleration information + * @constructor + * @param {Number} x The force applied by the device in the x-axis. + * @param {Number} y The force applied by the device in the y-axis. + * @param {Number} z The force applied by the device in the z-axis. + */ +function Acceleration(x, y, z) { + /* + * The force applied by the device in the x-axis. + */ + this.x = x; + /* + * The force applied by the device in the y-axis. + */ + this.y = y; + /* + * The force applied by the device in the z-axis. + */ + this.z = z; + /* + * The time that the acceleration was obtained. + */ + this.timestamp = new Date().getTime(); +}; + +/* + * This class specifies the options for requesting acceleration data. + * @constructor + */ +function AccelerationOptions() { + /* + * The timeout after which if acceleration data cannot be obtained the errorCallback + * is called. + */ + this.timeout = 10000; +}; + +/* + * This class provides access to device accelerometer data. + * @constructor + */ +function Accelerometer() { + /* + * The last known acceleration. + */ + this.lastAcceleration = null; +}; + +/* + * Tells WebOS to put higher priority on accelerometer resolution. Also relaxes the internal garbage collection events. + * @param {Boolean} state + * Dependencies: Mojo.windowProperties + * Example: + * navigator.accelerometer.setFastAccelerometer(true) + */ +Accelerometer.prototype.setFastAccelerometer = function(state) { + navigator.windowProperties.fastAccelerometer = state; + navigator.window.setWindowProperties(); +}; + +/* + * 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. + */ + +Accelerometer.prototype.getCurrentAcceleration = function(successCallback, errorCallback, options) { + + var referenceTime = 0; + if (this.lastAcceleration) + referenceTime = this.lastAcceleration.timestamp; + else + this.start(); + + var timeout = 20000; + var interval = 500; + if (typeof(options) == 'object' && options.interval) + interval = options.interval; + + if (typeof(successCallback) != 'function') + successCallback = function() {}; + if (typeof(errorCallback) != 'function') + errorCallback = function() {}; + + var dis = this; + var delay = 0; + var timer = setInterval(function() { + delay += interval; + + //if we have a new acceleration, call success and cancel the timer + if (typeof(dis.lastAcceleration) == 'object' && dis.lastAcceleration != null && dis.lastAcceleration.timestamp > referenceTime) { + successCallback(dis.lastAcceleration); + clearInterval(timer); + } else if (delay >= timeout) { //else if timeout has occured then call error and cancel the timer + errorCallback(); + clearInterval(timer); + } + //else the interval gets called again + }, interval); +}; + + +/* + * 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. + */ + +Accelerometer.prototype.watchAcceleration = function(successCallback, errorCallback, options) { + this.getCurrentAcceleration(successCallback, errorCallback, options); + // TODO: add the interval id to a list so we can clear all watches + var frequency = (options != undefined)? options.frequency : 10000; + var that = this; + return setInterval(function() { + that.getCurrentAcceleration(successCallback, errorCallback, options); + }, frequency); +}; + +/* + * Clears the specified accelerometer watch. + * @param {String} watchId The ID of the watch returned from #watchAcceleration. + */ +Accelerometer.prototype.clearWatch = function(watchId) { + clearInterval(watchId); +}; + +/* + * Starts the native acceleration listener. + */ + +Accelerometer.prototype.start = function() { + var that = this; + //Mojo.Event.listen(document, "acceleration", function(event) { + document.addEventListener("acceleration", function(event) { + var accel = new Acceleration(event.accelX, event.accelY, event.accelZ); + that.lastAcceleration = accel; + }); +}; + +if (typeof navigator.accelerometer == "undefined") navigator.accelerometer = new Accelerometer(); + +function Application() { + +}; + +/* + * Tell webOS to activate the current page of your app, bringing it into focus. + * Example: + * navigator.application.activate(); + */ +Application.prototype.activate = function() { + PalmSystem.activate(); +}; + +/* + * Tell webOS to deactivate your app. + * Example: + * navigator.application.deactivate(); + */ +Application.prototype.deactivate = function() { + PalmSystem.deactivate(); +}; + +/* + * Returns the identifier of the current running application (e.g. com.yourdomain.yourapp). + * Example: + * navigator.application.getIdentifier(); + */ +Application.prototype.getIdentifier = function() { + return PalmSystem.identifier; +}; + +if (typeof navigator.application == "undefined") navigator.application = new Application(); + +/* + * This class provides access to the device audio + * @constructor + */ + +PhoneGap.overrideAudio = function() { + + PhoneGap.MojoAudio = Audio; + + Audio = function(src) { + this.src = src; + }; + + Audio.prototype.play = function() { + // this.src = src; + // The 'end' event listener doesn't seem to work, so we have to call stop before playing + // otherwise, we'll never be able to play again + if (this.paused && !this.stopped) { + this.paused = false; + this.playing = true; + this.audioPlayer.play(); + } else { + if (this.audioPlayer) + this.stop(); + if (!this.playing) { + this.paused = false; + this.playing = true; + this.stopped = false; + this.audioPlayer = new PhoneGap.MojoAudio(); + var file = Mojo.appPath + this.src; + if (this.audioPlayer.palm) { + this.audioPlayer.mojo.audioClass = "media"; + } + this.audioPlayer.src = file; + + //event doesn't work, see above + this.audioPlayer.addEventListener('end', this.endHandler, false); + this.audioPlayer.play(); + } + } + }; + + Audio.prototype.pause = function() { + if (this.stopped) + return; + this.paused = true; + if (this.playing) { + this.playing = false; + this.stopped = false; + this.audioPlayer.pause(); + } else { + this.playing = false; + this.paused = false; + this.stopped = true; + } + }; + + Audio.prototype.stop = function() { + this.audioPlayer.pause(); + this.audioPlayer.src = null; + this.playing = false; + this.paused = false; + this.stopped = true; + }; + + // End event handler not working (see comment in Audio.prototype.play) + Audio.prototype.endHandler = function () { + this.audioPlayer.removeEventListener('end', endHandler, false); + this.audioPlayer.pause(); + this.audioPlayer.src = null; + this.paused = false; + this.stopped = true; + }; + + /* + * This class contains information about any Media errors. + * @constructor + */ + MediaError = function() { + this.code = null, + this.message = ""; + }; + + MediaError.MEDIA_ERR_ABORTED = 1; + MediaError.MEDIA_ERR_NETWORK = 2; + MediaError.MEDIA_ERR_DECODE = 3; + MediaError.MEDIA_ERR_NONE_SUPPORTED = 4; + +} + +document.addEventListener("deviceready", PhoneGap.overrideAudio, false); + +/* + * This class provides access to the device camera. + * @constructor + */ +function Camera() { + +}; + +/* + * @param {Function} successCallback + * @param {Function} errorCallback + * @param {Object} options + */ +Camera.prototype.getPicture = function(successCallback, errorCallback, options) { + + var filename = ""; + + if (typeof options != 'undefined' && typeof options.filename != 'undefined') { + filename = options.filename; + } + + this.service = navigator.service.Request('palm://com.palm.applicationManager', { + method: 'launch', + parameters: { + id: 'com.palm.app.camera', + params: { + appId: 'com.palm.app.camera', + name: 'capture', + sublaunch: true, + filename: filename + } + }, + onSuccess: successCallback, + onFailure: errorCallback + }); +}; + +if (typeof navigator.camera == 'undefined') navigator.camera = new Camera(); + +/* + * This class provides access to the device contacts. + * @constructor + */ + +function Contacts() { + +}; + +function Contact() { + this.phones = []; + this.emails = []; + this.name = { + givenName: "", + familyName: "", + formatted: "" + }; + this.id = ""; +}; + +Contact.prototype.displayName = function() +{ + // TODO: can be tuned according to prefs + return this.givenName + " " + this.familyName; +}; + +function ContactsFilter(name) { + if (name) + this.name = name; + else + this.name = ""; +}; + +/* + * @param {ContactsFilter} filter Object with filter properties. filter.name only for now. + * @param {function} successCallback Callback function on success + * @param {function} errorCallback Callback function on failure + * @param {object} options Object with properties .page and .limit for paging + */ + +Contacts.prototype.find = function(filter, successCallback, errorCallback, options) { + errorCallback({ name: "ContactsError", message: "PhoneGap Palm contacts not implemented" }); +}; + +Contacts.prototype.success_callback = function(contacts_iterator) { +}; + +if (typeof navigator.contacts == "undefined") navigator.contacts = new Contacts(); +/* + * This class provides access to the debugging console. + * @constructor + */ +function DebugConsole() { +}; + +/* + * Print a normal log message to the console + * @param {Object|String} message Message or object to print to the console + */ +DebugConsole.prototype.log = function(message) { + if (typeof message == 'object') + message = Object.toJSON(message); + + this.error(message); +}; + +/* + * Print a warning message to the console + * @param {Object|String} message Message or object to print to the console + */ +DebugConsole.prototype.warn = function(message) { + if (typeof message == 'object') + message = Object.toJSON(message); + + this.error(message); +}; + +/** + * Print an error message to the console + * @param {Object|String} message Message or object to print to the console + */ +DebugConsole.prototype.error = function(message) { + if (typeof message == 'object') + message = Object.toJSON(message); + + console.log(JSON.stringify(message)); +}; + +if (typeof window.debug == "undefined") window.debug = new DebugConsole(); +/* + * this represents the mobile device, and provides properties for inspecting the model, version, UUID of the + * phone, etc. + * @constructor + */ +function Device() { + this.platform = "palm"; + this.version = null; + this.name = null; + this.uuid = null; + this.deviceInfo = null; +}; + +/* + * A direct call to return device information. + * Example: + * var deviceinfo = JSON.stringify(navigator.device.getDeviceInfo()).replace(/,/g, ', '); + */ +Device.prototype.getDeviceInfo = function() { + return this.deviceInfo;//JSON.parse(PalmSystem.deviceInfo); +}; + +/* + * needs to be invoked in a <script> nested within the <body> it tells WebOS that the app is ready + TODO: see if we can get this added as in a document.write so that the user doesn't have to explicitly call this method + * Dependencies: Mojo.onKeyUp + * Example: + * navigator.device.deviceReady(); + */ +Device.prototype.deviceReady = function() { + + // tell webOS this app is ready to show + if (window.PalmSystem) { + // setup keystroke events for forward and back gestures + document.body.addEventListener("keyup", Mojo.onKeyUp, true); + + setTimeout(function() { PalmSystem.stageReady(); PalmSystem.activate(); }, 1); + alert = this.showBanner; + } + + // fire deviceready event; taken straight from phonegap-iphone + // put on a different stack so it always fires after DOMContentLoaded + window.setTimeout(function () { + var e = document.createEvent('Events'); + e.initEvent('deviceready'); + document.dispatchEvent(e); + }, 10); + + this.setUUID(); + this.setDeviceInfo(); +}; + +Device.prototype.setDeviceInfo = function() { + var parsedData = JSON.parse(PalmSystem.deviceInfo); + + this.deviceInfo = parsedData; + this.version = parsedData.platformVersion; + this.name = parsedData.modelName; +}; + +Device.prototype.setUUID = function() { + //this is the only system property webos provides (may change?) + var that = this; + this.service = navigator.service.Request('palm://com.palm.preferences/systemProperties', { + method:"Get", + parameters:{"key": "com.palm.properties.nduid" }, + onSuccess: function(result) { + that.uuid = result["com.palm.properties.nduid"]; + } + }); + + +}; + + +if (typeof window.device == 'undefined') window.device = navigator.device = new Device(); + +/* + * This class provides generic read and write access to the mobile device file system. + */ +function File() { + /** + * The data of a file. + */ + this.data = ""; + /** + * The name of the file. + */ + this.name = ""; +}; + +/* + * Reads a file from the mobile device. This function is asyncronous. + * @param {String} fileName The name (including the path) to the file on the mobile device. + * The file name will likely be device dependant. + * @param {Function} successCallback The function to call when the file is successfully read. + * @param {Function} errorCallback The function to call when there is an error reading the file from the device. + */ +File.prototype.read = function(fileName, successCallback, errorCallback) { + //Mojo has no file i/o yet, so we use an xhr. very limited + var path = fileName; //incomplete + //Mojo.Log.error(path); + navigator.debug.error(path); + + if (typeof successCallback != 'function') + successCallback = function () {}; + if (typeof errorCallback != 'function') + errorCallback = function () {}; + + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + if (xhr.status == 200 && xhr.responseText != null) { + this.data = xhr.responseText; + this.name = path; + successCallback(this.data); + } else { + errorCallback({ name: xhr.status, message: "could not read file: " + path }); + } + } + }; + xhr.open("GET", path, true); + xhr.send(); +}; + +/* + * Writes a file to the mobile device. + * @param {File} file The file to write to the device. + */ +File.prototype.write = function(file) { + //Palm does not provide file i/o +}; + +if (typeof navigator.file == "undefined") navigator.file = new File(); + +/* + * This class provides access to device GPS data. + * @constructor + */ +function Geolocation() { + /** + * The last known GPS position. + */ + this.lastPosition = null; + this.lastError = null; + this.callbacks = { + onLocationChanged: [], + onError: [] + }; +}; + +/* + * 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 position data. + * @param {PositionOptions} options The options for getting the position data + * such as timeout. + */ +Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallback, options) { + /* + var referenceTime = 0; + if (this.lastPosition) + referenceTime = this.lastPosition.timestamp; + else + this.start(options); + */ + + var timeout = 20000; + if (typeof(options) == 'object' && options.timeout) + timeout = options.timeout; + + if (typeof(successCallback) != 'function') + successCallback = function() {}; + if (typeof(errorCallback) != 'function') + errorCallback = function() {}; + + /* + var dis = this; + var delay = 0; + var timer = setInterval(function() { + delay += interval; + + //if we have a new position, call success and cancel the timer + if (dis.lastPosition && typeof(dis.lastPosition) == 'object' && dis.lastPosition.timestamp > referenceTime) { + successCallback(dis.lastPosition); + clearInterval(timer); + } else if (delay >= timeout) { //else if timeout has occured then call error and cancel the timer + errorCallback(); + clearInterval(timer); + } + //else the interval gets called again + }, interval); + */ + + var responseTime; + if (timeout <= 5000) + responseTime = 1; + else if (5000 < timeout <= 20000) + responseTime = 2; + else + responseTime = 3; + + var timer = setTimeout(function() { + errorCallback({ + message: "timeout" + }); + }, + timeout); + + var startTime = (new Date()).getTime(); + + var alias = this; + + // It may be that getCurrentPosition is less reliable than startTracking ... but + // not sure if we want to be starting and stopping the tracker if we're not watching. + //new Mojo.Service.Request('palm://com.palm.location', { + navigator.service.Request('palm://com.palm.location', { + method: "getCurrentPosition", + parameters: { + responseTime: responseTime + }, + onSuccess: function(event) { + alias.lastPosition = { + coords: { + latitude: event.latitude, + longitude: event.longitude, + altitude: (event.altitude >= 0 ? event.altitude: null), + speed: (event.velocity >= 0 ? event.velocity: null), + heading: (event.heading >= 0 ? event.heading: null), + accuracy: (event.horizAccuracy >= 0 ? event.horizAccuracy: null), + altitudeAccuracy: (event.vertAccuracy >= 0 ? event.vertAccuracy: null) + }, + timestamp: new Date().getTime() + }; + + var responseTime = alias.lastPosition.timestamp - startTime; + if (responseTime <= timeout) + { + clearTimeout(timer); + successCallback(alias.lastPosition); + } + }, + onFailure: function() { + errorCallback(); + } + }); + +}; + +/* + * Asynchronously aquires the position repeatedly at a given interval. + * @param {Function} successCallback The function to call each time the position + * data is available + * @param {Function} errorCallback The function to call when there is an error + * getting the position data. + * @param {PositionOptions} options The options for getting the position data + * such as timeout and the frequency of the watch. + */ +Geolocation.prototype.watchPosition = function(successCallback, errorCallback, options) { + // Invoke the appropriate callback with a new Position object every time the implementation + // determines that the position of the hosting device has changed. + var frequency = 10000; + if (typeof(options) == 'object' && options.frequency) + frequency = options.frequency; + + this.start(options, errorCallback); + + var referenceTime = 0; + if (this.lastPosition) + referenceTime = this.lastPosition.timestamp; + + var alias = this; + return setInterval(function() { + // check if we have a new position, if so call our successcallback + if (!alias.lastPosition) + return; + + if (alias.lastPosition.timestamp > referenceTime) + successCallback(alias.lastPosition); + }, + frequency); +}; + + +/* + * Clears the specified position watch. + * @param {String} watchId The ID of the watch returned from #watchPosition. + */ +Geolocation.prototype.clearWatch = function(watchId) { + clearInterval(watchId); + this.stop(); +}; + +Geolocation.prototype.start = function(options, errorCallback) { + //options.timeout; + //options.interval; + if (typeof(errorCallback) != 'function') + errorCallback = function() {}; + + var that = this; + var frequency = 10000; + if (typeof(options) == 'object' && options.frequency) + frequency = options.frequency; + + var responseTime; + if (frequency <= 5000) + responseTime = 1; + else if (5000 < frequency <= 20000) + responseTime = 2; + else + responseTime = 3; + + //location tracking does not support setting a custom interval :P + this.trackingHandle = navigator.service.Request('palm://com.palm.location', { + method: 'startTracking', + parameters: { + subscribe: true + }, + onSuccess: function(event) { + that.lastPosition = { + coords: { + latitude: event.latitude, + longitude: event.longitude, + altitude: (event.altitude >= 0 ? event.altitude: null), + speed: (event.velocity >= 0 ? event.velocity: null), + heading: (event.heading >= 0 ? event.heading: null), + accuracy: (event.horizAccuracy >= 0 ? event.horizAccuracy: null), + altitudeAccuracy: (event.vertAccuracy >= 0 ? event.vertAccuracy: null) + }, + timestamp: new Date().getTime() + }; + }, + onFailure: function() { + errorCallback(); + } + }); +}; + +Geolocation.prototype.stop = function() { + this.trackingHandle.cancel(); +}; + +if (typeof navigator.geolocation == "undefined") navigator.geolocation = new Geolocation(); + +/* + * This class provides access to native mapping applications on the device. + */ +function Map() { + +}; + +/* + * Shows a native map on the device with pins at the given positions. + * @param {Array} positions + */ +Map.prototype.show = function(positions) { + + var jsonPos = {}; + var pos = null; + if (typeof positions == 'object') { + // If positions is an array, then get the first only, since google's query + // can't take more than one marker (believe it or not). + // Otherwise we assume its a single position object. + if (positions.length) { + pos = positions[0]; + } else { + pos = positions; + } + } + else if (navigator.geolocation.lastPosition) { + pos = navigator.geolocation.lastPosition; + } else { + // If we don't have a position, lets use nitobi! + pos = { coords: { latitude: 49.28305, longitude: -123.10689 } }; + } + + this.service = navigator.service.Request('palm://com.palm.applicationManager', { + method: 'open', + parameters: { + id: 'com.palm.app.maps', + params: { + query: "@" + pos.coords.latitude + "," + pos.coords.longitude + } + } + }); + +}; + +if (typeof navigator.map == "undefined") navigator.map = new Map(); + +//=========================== +// Mojo Dependencies - we still need to rely on these minimal parts of the Mojo framework - should try to find if we can get access to lower level APIs +// so that we can remove dependence of Mojo +//=========================== + +Mojo = { + contentIndicator: false, + + // called by webOS in certain cases + relaunch: function() { + var launch = JSON.parse(PalmSystem.launchParams); + + if (launch['palm-command'] && launch['palm-command'] == 'open-app-menu') + this.fireEvent(window, "appmenuopen"); + else + this.fireEvent(window, "palmsystem", launch); + }, + + // called by webOS when your app gets focus + stageActivated: function() { + this.fireEvent(window, "activate"); + }, + + // called by webOS when your app loses focus + stageDeactivated: function() { + this.fireEvent(window, "deactivate"); + }, + + // this is a stub -- called by webOS when orientation changes + // but the preferred method is to use the orientationchanged + // DOM event + screenOrientationChanged: function(dir) { + }, + + // used to redirect keyboard events to DOM event "back" + onKeyUp: function(e) { + if (e.keyCode == 27) + this.fireEvent(window, "back"); + }, + + // private method, used to fire off DOM events + fireEvent: function(element, event, data) { + var e = document.createEvent("Event"); + e.initEvent(event, false, true); + + if (data) + e.data = data; + + element.dispatchEvent(e); + }, + + /* + not sure if these stubs are still needed since the Log object is encapsulated in debugconsole class + and the Service object is encapsulated in the Service class + */ + // stubs to make v8 happier + Service: {}, + Log: {} + +};function Mouse() { + +}; + +/* + * Possibly useful for automated testing, this call to PalmSystem triggers a mouse click (i.e. touch event). + * x coordinate & y coordinate of where the screen was touched and also a true/false flag to tell WebOS if it should simulate the mouse click + * @param {Number} x + * @param {Number} y + * @param {Boolean} state + * Example: + * navigator.mouse.simulateMouseClick(10, 10, true); + */ +Mouse.prototype.simulateMouseClick = function(x, y, state) { + PalmSystem.simulateMouseClick(x, y, state || true); +}; + +if (typeof navigator.mouse == "undefined") navigator.mouse = new Mouse(); + +function Network() { + /* + * The last known Network status. + */ + this.lastReachability = null; +}; + +Network.prototype.isReachable = function(hostName, successCallback, options) { + this.request = navigator.service.Request('palm://com.palm.connectionmanager', { + method: 'getstatus', + parameters: {}, + onSuccess: function(result) { + var status = NetworkStatus.NOT_REACHABLE; + if (result.isInternetConnectionAvailable == true) + { + // don't know whether its via wifi or carrier ... so return the worst case + status = NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK; + } + successCallback(status); + }, + onFailure: function() {} + }); + +}; + +/* + * This class contains information about any NetworkStatus. + * @constructor + */ +function NetworkStatus() { + this.code = null; + this.message = ""; +}; + +NetworkStatus.NOT_REACHABLE = 0; +NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK = 1; +NetworkStatus.REACHABLE_VIA_WIFI_NETWORK = 2; + +if (typeof navigator.network == "undefined") navigator.network = new Network(); + +/* + * This class provides access to notifications on the device. + */ +function Notification() { + + }; + +/* + * adds a dashboard to the WebOS app + * @param {String} url + * @param {String} html + * Example: + * navigator.notification.newDashboard("dashboard.html"); + */ +Notification.prototype.newDashboard = function(url, html) { + var win = window.open(url, "_blank", "attributes={\"window\":\"dashboard\"}"); + html && win.document.write(html); + win.PalmSystem.stageReady(); +}; + +/* + * Displays a banner notification. If specified, will send your 'response' object as data via the 'palmsystem' DOM event. + * If no 'icon' filename is specified, will use a small version of your application icon. + * @param {String} message + * @param {Object} response + * @param {String} icon + * @param {String} soundClass class of the sound; supported classes are: "ringtones", "alerts", "alarm", "calendar", "notification" + * @param {String} soundFile partial or full path to the sound file + * @param {String} soundDurationMs of sound in ms + * Example: + * navigator.notification.showBanner('test message'); + */ +Notification.prototype.showBanner = function(message, response, icon, soundClass, soundFile, soundDurationMs) { + var response = response || { + banner: true + }; + PalmSystem.addBannerMessage(message, JSON.stringify(response), icon, soundClass, soundFile, soundDurationMs); +}; + +/** + * Remove a banner from the banner area. The category parameter defaults to 'banner'. Will not remove + * messages that are already displayed. + * @param {String} category + Value defined by the application and usually same one used in {@link showBanner}. + It is used if you have more than one kind of banner message. + */ +Notification.prototype.removeBannerMessage = function(category) { + var bannerKey = category || 'banner'; + var bannerId = this.banners.get(bannerKey); + if (bannerId) { + try { + PalmSystem.removeBannerMessage(bannerId); + } catch(removeBannerException) { + window.debug.error(removeBannerException.toString()); + } + } +}; + +/* + * Remove all pending banner messages from the banner area. Will not remove messages that are already displayed. + */ +Notification.prototype.clearBannerMessage = function() { + PalmSystem.clearBannerMessage(); +}; + +/* + * This function vibrates the device + * @param {number} duration The duration in ms to vibrate for. + * @param {number} intensity The intensity of the vibration + */ +Notification.prototype.vibrate = function(duration, intensity) { + //the intensity for palm is inverted; 0=high intensity, 100=low intensity + //this is opposite from our api, so we invert + if (isNaN(intensity) || intensity > 100 || intensity <= 0) + intensity = 0; + else + intensity = 100 - intensity; + + // if the app id does not have the namespace "com.palm.", an error will be thrown here + //this.vibhandle = new Mojo.Service.Request("palm://com.palm.vibrate", { + this.vibhandle = navigator.service.Request("palm://com.palm.vibrate", { + method: 'vibrate', + parameters: { + 'period': intensity, + 'duration': duration + } + }, + false); +}; + +/* + * Plays the specified sound + * @param {String} soundClass class of the sound; supported classes are: "ringtones", "alerts", "alarm", "calendar", "notification" + * @param {String} soundFile partial or full path to the sound file + * @param {String} soundDurationMs of sound in ms + */ +Notification.prototype.beep = function(soundClass, soundFile, soundDurationMs) { + PalmSystem.playSoundNotification(soundClass, soundFile, soundDurationMs); +}; + +/* + * displays a notification + * @param {String} message + * @param {Object} response + * @param {String} icon + */ +Notification.prototype.alert = function(message, response, icon) { + var response = response || { + banner: true + }; + navigator.notification.showBanner(message, response, icon); +}; + +if (typeof navigator.notification == 'undefined') { + navigator.notification = new Notification(); + alert = navigator.notification.alert; +} + +/* + * This class provides access to the device orientation. + * @constructor + */ +function Orientation() { + this.started = false; +}; + +/* + * Manually sets the orientation of the application window. + * 'up', 'down', 'left' or 'right' used to specify fixed window orientation + * 'free' WebOS will change the window orientation to match the device orientation + * @param {String} orientation + * Example: + * navigator.orientation.setOrientation('up'); + */ +Orientation.prototype.setOrientation = function(orientation) { + PalmSystem.setWindowOrientation(orientation); +}; + +/* + * Returns the current window orientation + */ +Orientation.prototype.getCurrentOrientation = function() { + return PalmSystem.windowOrientation; +}; + +/* + * Starts the native orientationchange event listener. + */ +Orientation.prototype.start = function (successCallback) { + var that = this; + // This subscribes the callback once for the successCallback function + that.callback = function (e) { + document.removeEventListener("orientationChanged", that.callback); + successCallback(e.orientation); + } + + document.addEventListener("orientationChanged", that.callback); + + // This subscribes setOrientation to be constantly updating the currentOrientation property + document.addEventListener("orientationchange", function(event) { + var orient = null; + switch (event.position) { + case 0: orient = DisplayOrientation.FACE_UP; break; + case 1: orient = DisplayOrientation.FACE_DOWN; break; + case 2: orient = DisplayOrientation.PORTRAIT; break; + case 3: orient = DisplayOrientation.REVERSE_PORTRAIT; break; + case 4: orient = DisplayOrientation.LANDSCAPE_RIGHT_UP; break; + case 5: orient = DisplayOrientation.LANDSCAPE_LEFT_UP; break; + default: return; //orientationchange event seems to get thrown sometimes with a null event position + } + that.setOrientation(orient); + }); + this.started = true; +}; + +/* + * Asynchronously aquires the orientation repeatedly at a given interval. + * @param {Function} successCallback The function to call each time the orientation + * data is available. + * @param {Function} errorCallback The function to call when there is an error + * getting the orientation data. + */ +Orientation.prototype.watchOrientation = function(successCallback, errorCallback, options) { + // Invoke the appropriate callback with a new Position object every time the implementation + // determines that the position of the hosting device has changed. + this.getCurrentOrientation(successCallback, errorCallback); + var interval = 1000; + if (options && !isNaN(options.interval)) + interval = options.interval; + var that = this; + return setInterval(function() { + that.getCurrentOrientation(successCallback, errorCallback); + }, interval); +}; + +/* + * Clears the specified orientation watch. + * @param {String} watchId The ID of the watch returned from #watchOrientation. + */ +Orientation.prototype.clearWatch = function(watchId) { + clearInterval(watchId); +}; + +/* + * This class encapsulates the possible orientation values. + * @constructor + */ +function DisplayOrientation() { + this.code = null; + this.message = ""; +}; + +DisplayOrientation.PORTRAIT = 0; +DisplayOrientation.REVERSE_PORTRAIT = 1; +DisplayOrientation.LANDSCAPE_LEFT_UP = 2; +DisplayOrientation.LANDSCAPE_RIGHT_UP = 3; +DisplayOrientation.FACE_UP = 4; +DisplayOrientation.FACE_DOWN = 5; + +if (typeof navigator.orientation == "undefined") navigator.orientation = new Orientation(); + +function Position(coords) { + this.coords = coords; + this.timestamp = new Date().getTime(); +}; + +function Coordinates(lat, lng, alt, acc, head, vel, altacc) { + /* + * The latitude of the position. + */ + this.latitude = lat; + /* + * The longitude of the position, + */ + this.longitude = lng; + /* + * The accuracy of the position. + */ + this.accuracy = acc; + /* + * The altitude of the position. + */ + this.altitude = alt; + /* + * The direction the device is moving at the position. + */ + this.heading = head; + /* + * The velocity with which the device is moving at the position. + */ + this.speed = vel; + /* + * The altitude accuracy of the position. + */ + this.altitudeAccuracy = (typeof(altacc) != 'undefined') ? altacc : null; +}; + +/* + * This class specifies the options for requesting position data. + * @constructor + */ +function PositionOptions() { + /* + * Specifies the desired position accuracy. + */ + this.enableHighAccuracy = true; + /* + * The timeout after which if position data cannot be obtained the errorCallback + * is called. + */ + this.timeout = 10000; +}; + +/* + * This class contains information about any GSP errors. + * @constructor + */ +function PositionError() { + this.code = null; + this.message = ""; +}; + +PositionError.UNKNOWN_ERROR = 0; +PositionError.PERMISSION_DENIED = 1; +PositionError.POSITION_UNAVAILABLE = 2; +PositionError.TIMEOUT = 3; + +function Service() { + +}; + +Service.prototype.Request = function (uri, params) { + var req = new PalmServiceBridge(); + var url = uri + "/" + (params.method || ""); + req.url = url; + + this.req = req; + this.url = url; + this.params = params || {}; + + this.call(params); + + return this; +}; + +Service.prototype.call = function(params) { + var onsuccess = null; + var onfailure = null; + var oncomplete = null; + + if (typeof params.onSuccess === 'function') + onsuccess = params.onSuccess; + + if (typeof params.onFailure === 'function') + onerror = params.onFailure; + + if (typeof params.onComplete === 'function') + oncomplete = params.onComplete; + + this.req.onservicecallback = callback; + + function callback(msg) { + var response = JSON.parse(msg); + + if ((response.errorCode) && onfailure) + onfailure(response); + else if (onsuccess) + onsuccess(response); + + if (oncomplete) + oncomplete(response); + } + + this.data = (typeof params.parameters === 'object') ? JSON.stringify(params.parameters) : '{}'; + + this.req.call(this.url, this.data); +} + +if (typeof navigator.service == "undefined") navigator.service = new Service(); + +/* + * This class provides access to the device SMS functionality. + * @constructor + */ +function Sms() { + + }; + +/* + * Sends an SMS message. + * @param {Integer} number The phone number to send the message to. + * @param {String} message The contents of the SMS message to send. + * @param {Function} successCallback The function to call when the SMS message is sent. + * @param {Function} errorCallback The function to call when there is an error sending the SMS message. + * @param {PositionOptions} options The options for accessing the GPS location such as timeout and accuracy. + */ +Sms.prototype.send = function(number, message, successCallback, errorCallback, options) { + try { + this.service = navigator.service.Request('palm://com.palm.applicationManager', { + method: 'launch', + parameters: { + id: "com.palm.app.messaging", + params: { + composeAddress: number, + messageText: message + } + } + }); + successCallback(); + } catch(ex) { + errorCallback({ + name: "SMSerror", + message: ex.name + ": " + ex.message + }); + } +}; + +if (typeof navigator.sms == "undefined") navigator.sms = new Sms(); + +/* + * This class provides access to the telephony features of the device. + * @constructor + */ +function Telephony() { + this.number = ""; +}; + +/* + * Calls the specifed number. + * @param {Integer} number The number to be called. + */ +Telephony.prototype.send = function(number) { + this.number = number; + this.service = navigator.service.Request('palm://com.palm.applicationManager', { + method: 'open', + parameters: { + target: "tel://" + number + } + }); +}; + +if (typeof navigator.telephony == "undefined") navigator.telephony = new Telephony(); + +function Window() { + + }; + +/* + * This is a thin wrapper for 'window.open()' which optionally sets document contents to 'html', and calls 'PalmSystem.stageReady()' + * on your new card. Note that this new card will not come with your framework (if any) or anything for that matter. + * @param {String} url + * @param {String} html + * Example: + * navigator.window.newCard('about:blank', '<html><body>Hello again!</body></html>'); + */ +Window.prototype.newCard = function(url, html) { + var win = window.open(url || ""); + if (html) + win.document.write(html); + win.PalmSystem.stageReady(); +}; + +/* + * Enable or disable full screen display (full screen removes the app menu bar and the rounded corners of the screen). + * @param {Boolean} state + * Example: + * navigator.window.setFullScreen(true); + */ +Window.prototype.setFullScreen = function(state) { + // valid state values are: true or false + PalmSystem.enableFullScreenMode(state); +}; + +/* + * used to set the window properties of the WebOS app + * @param {Object} props + * Example: + * private method used by other member functions - ideally we shouldn't call this method + */ +Window.prototype.setWindowProperties = function(props) { + if (typeof props === 'object') + navigator.windowProperties = props; + + PalmSystem.setWindowProperties(props || this.windowProperties); +}; + +/* + * Enable or disable screen timeout. When enabled, the device screen will not dim. This is useful for navigation, clocks or other "dock" apps. + * @param {Boolean} state + * Example: + * navigator.window.blockScreenTimeout(true); + */ +Window.prototype.blockScreenTimeout = function(state) { + navigator.windowProperties.blockScreenTimeout = state; + this.setWindowProperties(); +}; + +/* + * Sets the lightbar to be a little dimmer for screen locked notifications. + * @param {Boolean} state + * Example: + * navigator.window.setSubtleLightbar(true); + */ +Window.prototype.setSubtleLightbar = function(state) { + navigator.windowProperties.setSubtleLightbar = state; + this.setWindowProperties(); +}; + +if (typeof navigator.window == 'undefined') navigator.window = new Window(); + +/* + * Object for storing WebOS window properties + */ +function WindowProperties() { + blockScreenTimeout = false; + setSubtleLightbar = false; + fastAccelerometer = false; +}; + +if (typeof navigator.windowProperties == 'undefined') navigator.windowProperties = new WindowProperties();(function(window) { + + /** + * Do not use thumbs.js on touch-enabled devices + * + * Thanks to Jesse MacFadyen (purplecabbage): + * https://gist.github.com/850593#gistcomment-22484 + */ + try { + document.createEvent('TouchEvent'); + return; + } + catch(e) { + } + + /** + * Map touch events to mouse events + */ + var eventMap = { + 'mousedown': 'touchstart', + 'mouseup': 'touchend', + 'mousemove': 'touchmove' + }; + + /** + * Fire touch events + * + * Monitor mouse events and fire a touch event on the + * object broadcasting the mouse event. This approach + * likely has poorer performance than hijacking addEventListener + * but it is a little more browser friendly. + */ + window.addEventListener('load', function() { + for (var key in eventMap) { + document.body.addEventListener(key, function(e) { + // Supports: + // - addEventListener + // - setAttribute + var event = createTouchEvent(eventMap[e.type], e); + e.target.dispatchEvent(event); + + // Supports: + // - element.ontouchstart + var fn = e.target['on' + eventMap[e.type]]; + if (typeof fn === 'function') fn(e); + }, false); + } + }, false); + + /** + * Utility function to create a touch event. + * + * @param name {String} of the event + * @return event {Object} + */ + var createTouchEvent = function(name, e) { + var event = document.createEvent('MouseEvents'); + + event.initMouseEvent( + name, + e.bubbles, + e.cancelable, + e.view, + e.detail, + e.screenX, + e.screenY, + e.clientX, + e.clientY, + e.ctrlKey, + e.altKey, + e.shiftKey, + e.metaKey, + e.button, + e.relatedTarget + ); + + return event; + }; + +})(window);
