initial commit of PhoneGap WRT framework, licensing and test code default tip
authorRyan Willoughby <ryan.willoughby@nitobi.com>
Tue, 06 Jul 2010 11:31:19 -0700
changeset 0 54063d8b0412
initial commit of PhoneGap WRT framework, licensing and test code
.DS_Store
LICENSE
Makefile
README.md
framework/www/Icon.png
framework/www/Info.plist
framework/www/index.html
framework/www/phonegap.js
js/acceleration.js
js/accelerometer.js
js/audio.js
js/camera.js
js/camera/com.nokia.device.camera.js
js/camera/com.nokia.device.framework.js
js/camera/com.nokia.device.utility.js
js/camera/s60_camera.js
js/contacts.js
js/debugconsole.js
js/device.js
js/geolocation.js
js/map.js
js/network.js
js/notification.js
js/orientation.js
js/phonegap.js.base
js/position.js
js/sms.js
js/storage.js
js/telephony.js
spec/LICENSE
spec/README.md
spec/index.html
spec/qunit.css
spec/qunit.js
spec/tests/accelerometer.tests.js
spec/tests/camera.tests.js
spec/tests/contacts.tests.js
spec/tests/device.tests.js
spec/tests/file.tests.js
spec/tests/geolocation.tests.js
spec/tests/map.tests.js
spec/tests/media.tests.js
spec/tests/network.tests.js
spec/tests/notification.tests.js
spec/tests/orientation.tests.js
spec/tests/sms.tests.js
spec/tests/storage.tests.js
spec/tests/telephony.tests.js
Binary file .DS_Store has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LICENSE	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,21 @@
+Copyright (c) 2010 Nitobi Software
+website: http://phonegap.com
+ 
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+Software), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+ 
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+ 
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,57 @@
+SHELL = /bin/sh
+CHMOD = chmod
+CP = cp
+MV = mv
+NOOP = $(SHELL) -c true
+RM_F = rm -f
+RM_RF = rm -rf
+TEST_F = test -f
+TOUCH = touch
+UMASK_NULL = umask 0
+DEV_NULL = > /dev/null 2>&1
+MKPATH = mkdir -p
+CAT = cat
+MAKE = make
+OPEN = open
+ECHO = echo
+ECHO_N = echo -n
+JAVA = java
+
+all :: js package
+
+clean :: clean_libs
+
+clean_libs:
+	-$(RM_RF) lib
+	
+package:
+	cp lib/phonegap.js framework/www/phonegap.js
+	cd framework/ && zip -r app.zip www/* -x www/wrt_preview_frame.html www/wrt_preview_main.html www/preview/ www/*.wgz
+	mv framework/app.zip app.wgz
+	
+js: lib/phonegap.js
+
+lib/phonegap.js: js/phonegap.js.base js/acceleration.js js/accelerometer.js js/audio.js js/camera.js js/camera/com.nokia.device.utility.js js/camera/com.nokia.device.framework.js js/camera/s60_camera.js js/camera/com.nokia.device.camera.js js/contacts.js js/debugconsole.js js/device.js js/geolocation.js js/map.js js/network.js js/notification.js js/orientation.js js/position.js js/sms.js js/storage.js js/telephony.js
+	$(MKPATH) lib
+	$(RM_F) $@
+	$(CAT) js/phonegap.js.base >> $@
+	$(CAT) js/acceleration.js >> $@
+	$(CAT) js/accelerometer.js >> $@
+	$(CAT) js/audio.js >> $@
+	$(CAT) js/camera.js >> $@
+	$(CAT) js/camera/com.nokia.device.utility.js >> $@
+	$(CAT) js/camera/com.nokia.device.framework.js >> $@
+	$(CAT) js/camera/s60_camera.js >> $@
+	$(CAT) js/camera/com.nokia.device.camera.js >> $@
+	$(CAT) js/contacts.js >> $@
+	$(CAT) js/debugconsole.js >> $@
+	$(CAT) js/device.js >> $@
+	$(CAT) js/geolocation.js >> $@
+	$(CAT) js/map.js >> $@
+	$(CAT) js/network.js >> $@
+	$(CAT) js/notification.js >> $@
+	$(CAT) js/orientation.js >> $@
+	$(CAT) js/position.js >> $@
+	$(CAT) js/sms.js >> $@
+	$(CAT) js/storage.js >> $@
+	$(CAT) js/telephony.js >> $@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.md	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,66 @@
+PhoneGap Symbian (WRT)
+=====================================================
+PhoneGap Symbian.WRT is a skeleton application for Nokia's Web RunTime, along with javascript wrapper libraries, which allow a developer to build a native application for a WRT Supported Symbian phone using web technologies. The same set of web application files can be ported to PhoneGap BlackBerry, iPhone, Palm, and more to come...
+
+
+Pre-requisites
+-----------------------------------------------------
+There are no real pre-requisites for PhoneGap Symbian.WRT development, as a WRT application consists simply of web files (html, css, js, etc) packaged and compressed into a .wgz file. No build process is necessary. However, for the purposes of development efficiency and testing, the following tools will make life MUCH easier:
+ - Aptana Studio and Nokia's WRT Plugin for Aptana Studio. Includes a limited but very handy browser-based device simulator.
+ - Optionally S60 5th Edition SDK, which includes the Symbian device emulator.
+
+
+Set up your dev environment
+---------------------------
+1. Place your web application in the phonegap_root/symbian.wrt/framework/www folder
+2. Ensure phonegap.js is included in your main html page
+3. Place your application icon in the www folder. It should be named Icon.png
+4. Modify info.plist (use the existing sample as a guide)
+5. Develop your application around this html file ... only this page will have access to the device api
+6. Compress the www folder into a zip file, and change the .zip extension to .wgz *
+7. Transfer this file to your S60 5th Edition device, and open it. The device should recognize and install the application
+
+Make targets:
+  - make js (compile the javascript source into lib/phonegap.js)
+  - make package (package the contents of framework/www/ into a .wgz package file, installable to the device)
+ 
+As you can see, you don't need to have any particular tools installed in your development environment to build WRT applications, as there is no building or compiling involved. However there are tools you can use to make development easier. The combination of Aptana Studio and Nokia's WRT Plug-in for Aptana worked nicely for developing and testing WRT applications. It includes an emulator (only for PC) and a browser-based javascript emulator, and can deploy applications directly to your device if bluetooth is enabled.
+ 
+A sample application resides in available at http://github.com/wildabeast/phonegap-demo which demonstrates the use of various device features through the phonegap API. It is essentially just a local website with a couple of rules: you must have an info.plist file and a main html page. To deploy this to your S60 5th Ed. device:
+ 
+1. Add the info.plist file to the application folder.
+2. Compress the application folder into a zip file.
+2. Change the .zip extension to .wgz.
+3. Transfer the file to your phone, via bluetooth, downloading from the web, or from email, etc.
+4. When you receive the message, your device should recognize the file type and install the application. 
+ 
+Note: a limitation of WRT is that you must define one main html page (defined in info.plist), and this page is the only one which will have access to the device functionality (geolocation, vibration, etc.). You can still use multiple pages, and pages not accessing device functionality will be accessible, but we recommend instead swapping views in and out of the main html page using a local xmlhttprequest, or building a javascript application (swapping views by showing and hiding divs).
+
+
+Debugging your Symbian.WRT PhoneGap application
+-----------------------------------------------
+Creating your PhoneGap Nokia project in Aptana Studio's WRT Plugin allows you to simulate the on device behaviour of your application in a browser.
+ 
+Get the Aptana WRT Plugin here.
+The simulator requires Firefox and Firebug.
+ 
+1. Install Aptana Studio and the Nokia WRT plug-in.
+2. Go to File -> New -> Project. Select Nokia Web Runtime -> New Nokia Web Runtime Widget.
+3. Select your desired project name and file locations. Click Finish. You should now see some additional internal application files/folders.
+4. Copy your web application pages into the project folder (unless starting from scratch).
+5. Ensure that the MainHTML property in info.plist refers to your application starting page.
+6. Right click on the file "wrt_preview_frame.html", and select Debug As -> Javascript Web Application.
+ 
+Your application should run inside Firefox, with the phonegap API functioning and returning test data for geolocation, contacts, acceleration, etc.
+
+Notes
+------------------------------------------------------
+ - The memory available to WRT apps is very limited. Loading large javascript files into memory, playing sound files, and excessive monitoring of sensors can fairly easily crash your application. Minify js files, and use sensor monitoring (accel, gps, etc) conservatively.
+ - If your symbian phone has contacts synced via PC Suite or Ovi Sync, and you attempt to query the contacts api, your app will crash. This is discussed here: http://discussion.forum.nokia.com/forum/showthread.php?t=170839&highlight=contacts+api+crashing. Hopefully nokia will fix it soon.
+ - Javascript animation in WRT is not great. I've tried dojo, scriptaculous, & emile, to no avail. You can leave it in ... your app will reach the animation end state ... but the animation itself won't be pretty.
+
+Helpful Links
+-----------------------------------------------------
+  - PhoneGap API Docs: 			docs.phonegap.com
+  - PhoneGap Wiki: 				phonegap.pbworks.com
+  - Nokia Web Runtime: 			http://www.forum.nokia.com/Technology_Topics/Web_Technologies/Web_Runtime/
Binary file framework/www/Icon.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/framework/www/Info.plist	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Nokia//DTD PLIST 1.0//EN" "http://www.nokia.com/DTDs/plist-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>DisplayName</key>
+	<string>phonegap.wrt</string>
+	<key>Identifier</key>
+	<string>com.nitobi.phonegap.demo</string>
+	<key>Version</key>
+	<string>1.0</string>
+	<key>AllowNetworkAccess</key>
+	<true/>
+	<key>MainHTML</key>
+	<string>index.html</string>
+	<key>MiniViewEnabled</key>
+	<false/>
+</dict>
+</plist>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/framework/www/index.html	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>PhoneGap Symbian.WRT</title>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+		<script language="javascript" type="text/javascript" src="phonegap.js"></script>
+	</head>
+<body>
+	Build your phonegap app here!
+</body>
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/framework/www/phonegap.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,1884 @@
+if (typeof(DeviceInfo) != 'object')
+    DeviceInfo = {};
+
+/**
+ * This represents the PhoneGap API itself, and provides a global namespace for accessing
+ * information about the state of PhoneGap.
+ * @class
+ */
+PhoneGap = {
+    queue: {
+        ready: true,
+        commands: [],
+        timer: null
+    },
+    _constructors: []
+};
+
+/**
+ * Boolean flag indicating if the PhoneGap API is available and initialized.
+ */
+PhoneGap.available = DeviceInfo.uuid != undefined;
+
+/**
+ * Execute a PhoneGap command in a queued fashion, to ensure commands do not
+ * execute with any race conditions, and only run when PhoneGap is ready to
+ * recieve them.
+ * @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
+ */
+PhoneGap.exec = function() {
+    PhoneGap.queue.commands.push(arguments);
+    if (PhoneGap.queue.timer == null)
+        PhoneGap.queue.timer = setInterval(PhoneGap.run_command, 10);
+};
+/**
+ * Internal function used to dispatch the request to PhoneGap.  This needs to be implemented per-platform to
+ * ensure that methods are called on the phone in a way appropriate for that device.
+ * @private
+ */
+PhoneGap.run_command = function() {
+};
+
+/**
+ * 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;
+}
+
+/**
+ * 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) {
+	// If the acceleration is available then call success
+	// If the acceleration is not available then call error
+	
+	try {
+		if (!this.serviceObj) 
+			this.serviceObj = this.getServiceObj();
+		
+		if (this.serviceObj == null) 
+			throw {
+				name: "DeviceErr",
+				message: "Could not initialize service object"
+			};
+		
+		//get the sensor channel
+		var SensorParams = {
+			SearchCriterion: "AccelerometerAxis"
+		};
+		var returnvalue = this.serviceObj.ISensor.FindSensorChannel(SensorParams);
+		var error = returnvalue["ErrorCode"];
+		var errmsg = returnvalue["ErrorMessage"];
+		if (!(error == 0 || error == 1012)) {
+			var ex = {
+				name: "Unable to find Sensor Channel: " + error,
+				message: errmsg
+			};
+			throw ex;
+		}
+		var channelInfoMap = returnvalue["ReturnValue"][0];
+		var criteria = {
+			ChannelInfoMap: channelInfoMap,
+			ListeningType: "ChannelData"
+		};
+		
+		if (typeof(successCallback) != 'function') 
+			successCallback = function(){
+			};
+		if (typeof(errorCallback) != 'function') 
+			errorCallback = function(){
+			};
+		
+		this.success_callback = successCallback;
+		this.error_callback = errorCallback;
+		//create a closure to persist this instance of Accelerometer into the RegisterForNofication callback
+		var obj = this;
+		
+		// TODO: this call crashes WRT, but there is no other way to read the accel sensor
+		// http://discussion.forum.nokia.com/forum/showthread.php?t=182151&highlight=memory+leak
+		this.serviceObj.ISensor.RegisterForNotification(criteria, function(transId, eventCode, result){
+			try {
+				var criteria = {
+					TransactionID: transId
+				};
+				obj.serviceObj.ISensor.Cancel(criteria);
+				
+				var accel = new Acceleration(result.ReturnValue.XAxisData, result.ReturnValue.YAxisData, result.ReturnValue.ZAxisData);
+				Accelerometer.lastAcceleration = accel;
+				
+				obj.success_callback(accel);
+				
+			} 
+			catch (ex) {
+				obj.serviceObj.ISensor.Cancel(criteria);
+				obj.error_callback(ex);
+			}
+			
+		});
+	} catch (ex) {
+		errorCallback(ex);
+	}
+
+};
+
+
+/**
+ * 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;
+	return setInterval(function() {
+		navigator.accelerometer.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);
+};
+
+//gets the Acceleration Service Object from WRT
+Accelerometer.prototype.getServiceObj = function() {
+	var so;
+	
+    try {
+        so = device.getServiceObject("Service.Sensor", "ISensor");
+    } catch (ex) {
+		throw {
+			name: "DeviceError",
+			message: "Could not initialize accel service object (" + ex.name + ": " + ex.message + ")"
+		};
+    }		
+	return so;
+};
+
+if (typeof navigator.accelerometer == "undefined") navigator.accelerometer = new Accelerometer();/**
+ * This class provides access to the device media, interfaces to both sound and video
+ * @constructor
+ */
+function Audio(src, successCallback, errorCallback) {
+	this.src = src;
+	this.successCallback = successCallback;
+	this.errorCallback = errorCallback;												
+}
+
+Audio.prototype.record = function() {
+};
+
+Audio.prototype.play = function() {
+try {
+	if (document.getElementById('gapsound'))
+		document.body.removeChild(document.getElementById('gapsound'));
+	var obj;
+	obj = document.createElement("embed");
+	obj.setAttribute("id", "gapsound");
+	obj.setAttribute("type", "audio/x-mpeg");
+	obj.setAttribute("width", "0");
+	obj.setAttribute("width", "0");
+	obj.setAttribute("hidden", "true");
+	obj.setAttribute("autostart", "true");
+	obj.setAttribute("src", this.src);
+	document.body.appendChild(obj);
+} catch (ex) { debug.log(ex.name + ": " + ex.message); }
+};
+
+Audio.prototype.pause = function() {
+};
+
+Audio.prototype.stop = function() {
+	document.body.removeChild(document.getElementById('gapsound'));
+};
+/**
+ * This class provides access to the device camera.
+ * @constructor
+ */
+function Camera() {
+	this.success_callback = null;
+	this.error_callback = null;
+}
+
+/**
+ * We use the Platform Services 2.0 API here. So we must include a portion of the
+ * PS 2.0 source code (camera API). 
+ * @param {Function} successCallback
+ * @param {Function} errorCallback
+ * @param {Object} options
+ */
+Camera.prototype.getPicture = function(successCallback, errorCallback, options){
+	try {
+		if (!this.serviceObj) {
+			this.serviceObj = com.nokia.device.load("", "com.nokia.device.camera", "");
+		}
+		if (!this.serviceObj) {
+			throw {
+				name: "CameraError",
+				message: "could not load camera service"
+			};
+		}
+		var obj = this;
+		
+		obj.success_callback = successCallback;
+		obj.error_callback = errorCallback;
+		this.serviceObj.startCamera( function(transactionID, errorCode, outPut) { 
+			//outPut should be an array of image urls (local), or an error code
+			if (errorCode == 0) {
+				obj.success_callback(outPut);
+			}
+			else {
+				obj.error_callback({
+					name: "CameraError",
+					message: errorCode
+				});
+			}
+		});
+		
+	} catch (ex) {
+		errorCallback.call(ex);
+	}
+	
+};
+
+if (typeof navigator.camera == "undefined") navigator.camera = new Camera();/*
+Copyright © 2009 Nokia. All rights reserved.
+Code licensed under the BSD License:
+Software License Agreement (BSD License) Copyright © 2009 Nokia.
+All rights reserved.
+Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
+Neither the name of Nokia Corporation. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Nokia Corporation. 
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+version: 1.0
+*/
+
+
+// utility.js
+//
+// This file contains some utility functions for S60 providers
+
+
+// Start an application and wait for it to exit
+
+//TBD: Get rid of this global, use closures instead
+
+DeviceError.prototype = new Error(); //inheritance occurs here
+DeviceError.prototype.constructor = DeviceError; //If this not present then, it uses default constructor of Error
+
+//constructor for DeviceError.
+function DeviceError(message,code) 
+{
+	this.toString = concatenate;
+	this.code = code;
+	this.name = "DeviceException";//we can even overwrite default name "Error"
+	this.message=message; 
+}
+
+function concatenate()
+{
+	return (this.name+":"+" "+this.message+" "+this.code);
+}
+
+function splitErrorMessage(errmessage)
+{
+	if(errmessage.search(/:/)!=-1)
+	{
+		if((errmessage.split(":").length)==2)
+		{
+			return errmessage.split(":")[1];
+		}
+		if((errmessage.split(":").length)>2)
+		{
+			return errmessage.split(":")[2];
+		}
+	}
+	return errmessage;
+}
+
+
+var __s60_start_and_wait_cb;
+
+function __s60_on_app_exit(){
+  widget.onshow = null;
+  if(__s60_start_and_wait_cb != null){
+    __s60_start_and_wait_cb();
+  }
+}
+
+function __s60_on_app_start(){
+  widget.onhide = null;
+  widget.onshow = __s60_on_app_exit;
+}
+
+// This function cannot actually force JS to wait,
+// but it does supply a callback the apps can use
+// to continue processing on return from the app.
+// Apps should take care not to reinvoke this and
+// should be careful about any other processing
+// that might happen while the app is running.
+
+function __s60_start_and_wait(id, args, app_exit_cb){
+  __s60_start_and_wait_cb = app_exit_cb;
+  widget.onhide = __s60_on_app_start;
+  widget.openApplication(id, args);
+}
+
+function __s60_api_not_supported(){
+  throw(err_ServiceNotSupported);
+}
+
+function __s60_enumerate_object(object, namespace, func, param){
+    var key;
+    for(key in object){
+       
+        var propname;
+       	if(namespace){
+	    propname = namespace + "." + key;
+	}
+	else{
+	    propname = key;
+	}
+        var value = object[key];
+        if(typeof value == "object"){
+	  __s60_enumerate_object(value, propname, func, param);
+	}
+	else {
+	  func(propname,value, param);
+	}
+    }
+}
+/*
+Copyright © 2009 Nokia. All rights reserved.
+Code licensed under the BSD License:
+Software License Agreement (BSD License) Copyright © 2009 Nokia.
+All rights reserved.
+Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
+Neither the name of Nokia Corporation. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Nokia Corporation. 
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+version: 1.0
+*/
+
+
+var __device_debug_on__ = true;
+var err_missing_argument = 1003;
+var event_cancelled = 3;
+var err_bad_argument = 1002;
+var err_InvalidService_Argument = 1000;
+var err_ServiceNotReady = 1006;
+var err_ServiceNotSupported = 1004;
+
+function __device_debug(text){
+  //if(__device_debug_on__) alert(text);
+}
+
+function __device_handle_exception(e, text){
+	__device_debug(text);
+	throw(e);
+}
+
+function __device_typeof(value)
+{
+	// First check to see if the value is undefined.
+	if (value == undefined) {
+        return "undefined";
+    }
+	// Check for objects created with the "new" keyword.
+	if (value instanceof Object) {
+		// Check whether it's a string object.
+		if (value instanceof String) {
+			return "String";
+		}
+		// Check whether it's an array object/array literal.
+		else 
+			if (value instanceof Array) {
+				return "Array";
+			}
+	}
+	// dealing with a literal.
+		if (typeof value) {
+			if (typeof value == "object") {
+				if (typeof value == "object" && !value) {
+					return "null";
+				}
+			}
+           // if not null check for other types
+			
+				// Check if it's a string literal.
+			else if (typeof value == "string") {
+					return "string";
+				}
+		}	 
+}
+
+
+// The top-level service object. It would be nice to use a namespace here 
+// (com.nokia.device.service), but emulating namespaces still allows name clashes.
+/*
+var sp_device = {
+        //services: null; // TBD: Make the services list a member of this object?
+	load: __device_service_load,
+        listServices: __device_service_list,
+        listInterfaces: __device_service_interfaces,
+        version: "0.1",
+        info: "device prototype"
+};
+*/
+
+if(undefined == com)
+    var com={};
+
+if( typeof com != "object")
+    throw("com defined as non object");
+
+if(undefined == com.nokia)
+    com.nokia = {};
+
+if( typeof com.nokia != "object")
+    throw("com.nokia defined as non object");
+
+if(undefined == com.nokia.device)
+    com.nokia.device = {
+        load: __device_service_load,
+        listServices: __device_service_list,
+        listInterfaces: __device_service_interfaces,
+        version: "0.1",
+        info: "device prototype"
+        };
+else
+    throw("com.nokia.device already defined");
+
+com.nokia.device.SORT_ASCENDING = 0;
+com.nokia.device.SORT_DESCENDING = 1;
+
+com.nokia.device.SORT_BY_DATE = 0;
+com.nokia.device.SORT_BY_SENDER = 1;
+
+com.nokia.device.STATUS_READ = 0;
+com.nokia.device.STATUS_UNREAD = 1;
+
+
+// Configure the services offered.
+
+var __device_services_inited = false;
+
+var __device_services = [
+
+  // For now, the only service is the base "device"" service
+  {
+    "name":"com.nokia.device",
+    "version": 0.1, 
+    "interfaces": []
+  }
+];
+
+// Initialize the configured services.
+
+function __device_services_init(){
+  if(__device_services_inited){
+    return;
+  }
+  __device_services_inited = true;
+
+  // Get the service-specific service entries. Note that these
+  // need to be individually wrapped by try/catch blocks so that the
+  // interpreter gracefully handles missing services. 
+
+  try {
+    __device_services[0].interfaces.push(__device_geolocation_service_entry);
+  }catch (e){
+    __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_camera_service_entry);
+  }catch (e){
+    __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_media_service_entry);
+  }catch (e){
+//    __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_contacts_service_entry);
+  }catch (e){
+//    __device_debug("Missing library implementation: " + e);
+  }
+ try {
+    __device_services[0].interfaces.push(__device_messaging_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_calendar_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_landmarks_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_event_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_sysinfo_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_sensors_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+
+}
+
+function __device_get_implementation(i){
+  //__device_debug("get_implementation: " + i);
+  return  new i.proto(new(i.providers[0].instance));
+}
+
+function __device_get_descriptor(i){
+  //__device_debug("get_descriptor: " + i);
+  return new i.descriptor(new(i.providers[0].descriptor));
+}
+
+function __device_get_interface(s, interfaceName, version){
+  //__device_debug("get_interface: " + s + " " + interfaceName);
+  var i = s.interfaces;
+  if((interfaceName == null) || (interfaceName == '')){
+    // Interface name not specified, get first interface, ignoring version
+    return __device_get_implementation(i[0]);
+  }
+
+  // Find first match of name and version
+  for (var d in i){
+  
+    if(i[d].name == null){
+      __device_update_descriptor(i[d]);
+    }
+    if(i[d].name == undefined){
+      continue;
+    }
+    if (i[d].name == interfaceName){
+      // Match version if specified
+      if ((version == null) || (version == '') || (i[d].version >= version)){
+	return __device_get_implementation(i[d]);
+      }
+    }
+  }
+  return null;
+}
+
+// Implemention of the load method
+
+function __device_service_load(serviceName, interfaceName, version){
+
+  __device_services_init();
+  
+  // Service name is specified
+   if ((serviceName != null) && (serviceName != '') &&(serviceName != "*")){
+    for(var s in __device_services){
+      if (serviceName == __device_services[s].name){
+	return __device_get_interface(__device_services[s], interfaceName, version);
+      }
+    }
+  // Service name not specified, get first implementation 
+  } else {
+    //__device_debug("Trying to get interface implementations: ");
+    for(var s in __device_services){
+      //__device_debug("service_load: " + s + ":" +  __device_services[s].name + ": " + interfaceName);
+      var i = __device_get_interface(__device_services[s], interfaceName, version);
+      if (i != null){
+	return i;
+      }
+    }
+  }
+  return null;
+}
+
+// Lazily fill in the descriptor table
+
+function __device_update_descriptor(i){
+  var d = __device_get_descriptor(i);
+  i.name = d.interfaceName;
+  i.version = d.version;  
+}
+// Get an array of interface descriptors for a service
+
+function __device_interface_list(s){
+  var retval = new Array();
+  for(var i in s.interfaces){
+    if(s.interfaces[i].name == null){
+      __device_update_descriptor(s.interfaces[i]);
+    }
+    if(s.interfaces[i].name == undefined){
+      continue;
+    }
+    retval[i] = new Object();
+    retval[i].name = s.interfaces[i].name;
+    retval[i].version = s.interfaces[i].version;
+  }  
+  return retval;
+}
+
+// Get a service description
+
+function __device_service_descriptor(s){
+  this.name = s.name;
+  this.version = s.version;
+  this.interfaces = __device_interface_list(s);
+  this.toString = __device_service_descriptor_to_string;
+}
+
+function __device_service_descriptor_to_string(){
+  var is = "\nInterfaces(s): ";
+
+  for (i in this.interfaces){
+    is += "\n" + this.interfaces[i].name + " " + this.interfaces[0].version;
+  }
+  return ("Service: " + this.name + is);
+}
+
+// Implement the listServices method 
+
+function __device_service_list(serviceName, interfaceName, version){
+  //__device_debug("__device_service_list: " + serviceName + " " + interfaceName);
+  __device_services_init();
+  var retval = new Array();
+  var n = 0;
+  
+  //Treat empty service and interface names as wildcards
+  if ((serviceName == null)|| (serviceName == '')/* || (serviceName == undefined)*/){
+    serviceName = ".*"; 
+  }
+  if ((interfaceName == null) || (interfaceName == '') /*|| (serviceName == undefined)*/){
+    interfaceName = ".*";
+  }
+ 
+  if ((typeof serviceName != "string") || (typeof interfaceName != "string")) {
+  	return retval;
+  }
+  
+  // This method does regular expression matching of service and interface
+
+  var sregx = new RegExp(serviceName);
+  var iregx = new RegExp(interfaceName);
+ 
+  for(var s in __device_services){
+   //__device_debug (serviceName + "==" + __device_services[s].name + "?:" + sregx.test(__device_services[s].name));
+    if (sregx.test(__device_services[s].name)){
+      // Find the first matching interface 
+        
+      for(var i in __device_services[s].interfaces){
+        if(__device_services[s].interfaces[i].name == null){
+          __device_update_descriptor(__device_services[s].interfaces[i]);
+	}
+        if(__device_services[s].interfaces[i].name == undefined){
+	  continue;
+	}
+	//__device_debug (interfaceName + "==" + __device_services[s].interfaces[i].name + "?:" + iregx.test(__device_services[s].interfaces[i].name));
+	if (iregx.test(__device_services[s].interfaces[i].name)){
+	  if ((version == null) || (version == '') || (__device_services[s].interfaces[i].version >= version)){
+            // An interface matched, we're done.
+            retval[n] = new __device_service_descriptor(__device_services[s]);
+            break; 
+	  }
+	}
+      }
+    }
+    ++n;
+  }
+  return retval;
+}
+
+// Implement the listInterfaces method
+    
+function __device_service_interfaces(serviceName){
+  __device_services_init();
+  if(serviceName==null||serviceName==undefined||serviceName==''){
+  	throw new DeviceError("Framework: listInterfaces: serviceName is missing", err_missing_argument);
+  }
+  for (var s in __device_services){
+    if(__device_services[s].name == serviceName){
+      return __device_interface_list(__device_services[s]);
+    }
+  }
+  return null;
+}
+
+function modifyObjectBaseProp(obj){
+  for (pro in obj) {
+    if(typeof obj[pro] == "function" )
+      obj[pro] = 0;
+    }
+};
+/*
+Copyright © 2009 Nokia. All rights reserved.
+Code licensed under the BSD License:
+Software License Agreement (BSD License) Copyright © 2009 Nokia.
+All rights reserved.
+Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
+Neither the name of Nokia Corporation. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Nokia Corporation. 
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+version: 1.0
+*/
+
+
+// S60 sp-based camera provider
+
+function __sp_camera_descriptor(){
+  //__device_debug("sp_camera_descriptor");
+  //Read-only properties
+  this.interfaceName = "com.nokia.device.camera";
+  this.version = "0.1";
+  //Class-static properties 
+}
+
+// TBD make local to closure funcs
+var __sp_camera_start_date;
+
+function __sp_camera_instance(){
+  //__device_debug("sp_camera_instance");
+  //Descriptor
+  this.descriptor = new __sp_camera_descriptor();
+  //Core methods
+  this.startCamera = __sp_startCamera;
+  this.stopViewfinder = __s60_api_not_supported;
+  //Extended methods
+  this.takePicture = __s60_api_not_supported;
+  //Private data
+}
+
+var CAMERA_APP_ID = 0x101f857a;
+
+//Apps should take care that this is not reinvoked
+//while the viewfinder is running. 
+
+function __sp_startCamera(camera_cb){
+
+	//If callback is null , then return missing argument error
+    if( camera_cb == null )
+        throw new DeviceError("Camera:startCamera:callback is missing", err_missing_argument);
+        
+	//If the callback is not a function, then return bad type error
+	if( typeof(camera_cb) != "function" )
+	    throw new DeviceError("Camera:startCamera:callback is a non-function", err_bad_argument);
+
+  var finished = function (){
+    var invoker = function (arg1, arg2, arg3){
+      //__device_debug("invoker with: " + camera_cb);
+      var it = arg3.ReturnValue;
+      var item;
+      var items = new Array();
+      while (( item = it.getNext()) != undefined){
+          var d = new Date(Date.parse(item.FileDate));
+          //__device_debug(item.FileName + " " + d );
+          // Items returned in reverse date order, so stop iterating before
+          // reaching initial date. (Should be able to do this more efficiently
+          // with sp filter, but that doesn't seem to work right now.)
+          if (d > __sp_camera_start_date) {
+              var pathname = item.FileNameAndPath.replace(/\\/g, "/");
+              var fileScheme = "file:///";
+              //Non-patched builds don't allow file scheme TBD: change this for patched builds
+              items.unshift(fileScheme + pathname);
+          }
+      }
+      var dummyTransID = 0;
+      var dummyStatusCode = 0;
+      camera_cb(dummyTransID, dummyStatusCode, items);
+    };
+
+    
+    //When camera returns, get the image(s) created
+    try {
+      var mso = device.getServiceObject("Service.MediaManagement", "IDataSource");
+    }
+    catch(e) {
+      __device_handle_exception (e, "media service not available : " + e);
+    }
+    
+    var criteria = new Object();
+	modifyObjectBaseProp(criteria);
+    criteria.Type = 'FileInfo';
+    criteria.Filter = new Object();
+	modifyObjectBaseProp(criteria.Filter);
+    criteria.Filter.FileType = 'Image';
+    //criteria.Filter.Key = 'FileDate';
+    //criteria.Filter.StartRange = null;
+    //criteria.Filter.EndRange = null;
+    criteria.Sort = new Object();
+	modifyObjectBaseProp(criteria.Sort);
+    criteria.Sort.Key = 'FileDate';
+    criteria.Sort.Order = 'Descending';
+    
+    try {
+      var rval = mso.IDataSource.GetList(criteria, invoker);
+    }
+    catch (e) {
+      __device_handle_exception (e, "media service GetList failed: " + e);
+    }
+  };
+
+  __sp_camera_start_date = new Date();
+  __s60_start_and_wait(CAMERA_APP_ID, "", finished);
+  var dummyTid = 0;
+  return dummyTid;
+}
+
+
+/*
+Copyright © 2009 Nokia. All rights reserved.
+Code licensed under the BSD License:
+Software License Agreement (BSD License) Copyright © 2009 Nokia.
+All rights reserved.
+Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
+Neither the name of Nokia Corporation. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Nokia Corporation. 
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+version: 1.0
+*/
+
+
+// Camera service interface
+
+var __device_camera_service_entry =  {"name": null, 
+					 "version": null,
+					 "proto": __device_camera,
+					 "descriptor": __device_camera_descriptor,
+					 "providers": [{"descriptor": __sp_camera_descriptor, "instance": __sp_camera_instance}]
+					};
+
+function __device_camera_descriptor(provider){
+  this.interfaceName = provider.interfaceName;
+  this.version = provider.version;
+}
+
+
+// Private camera  prototype: called from service factory
+function __device_camera(provider){
+  //Private properties
+  this.provider = provider;
+  //Read-only properties
+  this.interfaceName = provider.descriptor.interfaceName;
+  this.version = provider.descriptor.version;
+ // this.supportedMediaTypes = provider.supportedMediaTypes;
+ // this.supportedSizes = provider.supportedSizes;
+  //Core methods
+  this.startCamera = __device_camera_startCamera;
+  this.stopViewfinder = __device_camera_stopViewfinder;
+  //Extended methods
+  this.takePicture = __device_camera_takePicture;
+}
+
+
+//Why bother to define these methods? Because the camera
+//object defines the contract for providers!
+
+function __device_camera_startCamera(camera_cb){
+  return this.provider.startCamera(camera_cb);
+}
+
+function __device_camera_stopViewfinder(){
+  this.provider.stopViewfinder();
+}
+
+function __device_camera_takePicture(format){
+  this.provider.takePicture(format);
+}
+/**
+ * This class provides access to the device contacts.
+ * @constructor
+ */
+
+function Contacts() {
+	
+}
+
+function Contact() {
+	this.id = null;
+	this.name = { 
+		formatted: "",
+		givenName: "",
+		familyName: ""
+	};
+    this.phones = [];
+    this.emails = [];
+}
+
+Contact.prototype.displayName = function()
+{
+    // TODO: can be tuned according to prefs
+	return this.givenName + " " + this.familyName;
+};
+
+/*
+ * @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) {
+	try {
+		this.contactsService = device.getServiceObject("Service.Contact", "IDataSource");
+		if (typeof options == 'object')
+			this.options = options;
+		else
+			this.options = {};
+		
+		var criteria = new Object();
+		criteria.Type = "Contact";
+		if (filter && filter.name) {
+			var searchTerm = '';
+			if (filter.name.givenName && filter.name.givenName.length > 0) {
+				searchTerm += filter.name.givenName;
+			}
+			if (filter.name.familyName && filter.name.familyName.length > 0) {
+				searchTerm += searchTerm.length > 0 ? ' ' + filter.name.familyName : filter.name.familyName;
+			}
+			if (!filter.name.familyName && !filter.name.givenName && filter.name.formatted) {
+				searchTerm = filter.name.formatted;
+			}
+			criteria.Filter = { SearchVal: searchTerm };
+		}
+		
+		if (typeof(successCallback) != 'function') 
+			successCallback = function(){};
+		if (typeof(errorCallback) != 'function') 
+			errorCallback = function(){};
+		if (isNaN(this.options.limit))
+			this.options.limit = 200;
+		if (isNaN(this.options.page))
+			this.options.page = 1;
+		
+		//need a closure here to bind this method to this instance of the Contacts object
+		this.global_success = successCallback;
+		var obj = this;
+		
+		//WRT: result.ReturnValue is an iterator of contacts
+		this.contactsService.IDataSource.GetList(criteria, function(transId, eventCode, result){
+			obj.success_callback(result.ReturnValue);
+		});
+	} 
+	catch (ex) {
+		alert(ex.name + ": " + ex.message);
+		errorCallback(ex);
+	}
+};
+
+Contacts.prototype.success_callback = function(contacts_iterator) {
+	try {
+	var gapContacts = new Array();
+	contacts_iterator.reset();
+    var contact;
+	var i = 0;
+	var end = this.options.page * this.options.limit;
+	var start = end - this.options.limit;
+	while ((contact = contacts_iterator.getNext()) != undefined && i < end) {
+		try {
+			if (i >= start) {
+				var gapContact = new Contact();
+				gapContact.name.givenName = Contacts.GetValue(contact, "FirstName");
+				gapContact.name.familyName = Contacts.GetValue(contact, "LastName");
+				gapContact.name.formatted = gapContact.name.givenName + " " + gapContact.name.familyName;
+				gapContact.emails = Contacts.getEmailsList(contact);
+				gapContact.phones = Contacts.getPhonesList(contact);
+				gapContact.address = Contacts.getAddress(contact);
+				gapContact.id = Contacts.GetValue(contact, "id");
+				gapContacts.push(gapContact);
+			}
+			i++;
+		} catch (e) {
+			alert("ContactsError (" + e.name + ": " + e.message + ")");
+		}
+	}
+	this.contacts = gapContacts;
+	this.global_success(gapContacts);
+	} catch (ex) { alert(ex.name + ": " + ex.message); }
+};
+
+Contacts.getEmailsList = function(contact) {
+	var emails = new Array();
+	try {
+			emails[0] = { type:"General", address: Contacts.GetValue(contact, "EmailGen") };
+			emails[1] = { type:"Work", address: Contacts.GetValue(contact, "EmailWork") };		
+			emails[2] = { type:"Home", address: Contacts.GetValue(contact, "EmailHome") };
+	} catch (e) {
+		emails = [];
+	}
+	return emails;
+};
+
+Contacts.getPhonesList = function(contact) {
+	var phones = new Array();
+	try {
+			phones[0] = { type:"Mobile", number: Contacts.GetValue(contact, "MobilePhoneGen") };
+			phones[1] = { type:"Home", number: Contacts.GetValue(contact, "LandPhoneGen") };
+			phones[2] = { type:"Fax", number: Contacts.GetValue(contact, "FaxNumberGen") };
+			phones[3] = { type:"Work", number: Contacts.GetValue(contact, "LandPhoneWork") };
+			phones[4] = { type:"WorkMobile", number: Contacts.GetValue(contact, "MobilePhoneWork") };
+	} catch (e) {
+		phones = [];
+	}
+	return phones;
+};
+
+Contacts.getAddress = function(contact) {
+	var address = "";
+	try {
+		address = Contacts.GetValue(contact, "AddrLabelHome") + ", " + Contacts.GetValue(contact, "AddrStreetHome") + ", " +
+				Contacts.GetValue(contact, "AddrLocalHome") + ", " + Contacts.GetValue(contact, "AddrRegionHome") + ", " + 
+				Contacts.GetValue(contact, "AddrPostCodeHome") + ", " + Contacts.GetValue(contact, "AddrCountryHome");
+	} catch (e) {
+		address = "";
+	}
+	return address;
+};
+
+Contacts.GetValue = function(contactObj, key) {
+	try {
+		return contactObj[key]["Value"];
+	} catch (e) {
+		return "";
+	}
+};
+
+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) {
+	
+	//This ends up in C:\jslog_widget.log on the device
+	console.log(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) {
+};
+
+/**
+ * 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 window.debug == "undefined") window.debug = new DebugConsole();
+PhoneGap.ExtendWrtDeviceObj = function(){
+	
+	if (!window.device)
+		window.device = {};
+	navigator.device = window.device;
+
+	try {
+	
+		if (window.menu)
+	    	window.menu.hideSoftkeys();
+		
+		device.available = PhoneGap.available;
+		device.platform = null;
+		device.version = null;
+		device.name = null;
+		device.uuid = null;
+		
+		var so = device.getServiceObject("Service.SysInfo", "ISysInfo");
+		var pf = PhoneGap.GetWrtPlatformVersion(so);
+		device.platform = pf.platform;
+		device.version = pf.version;
+		device.uuid = PhoneGap.GetWrtDeviceProperty(so, "IMEI");
+		device.name = PhoneGap.GetWrtDeviceProperty(so, "PhoneModel");
+	} 
+	catch (e) {
+		device.available = false;
+	}
+};
+
+PhoneGap.GetWrtDeviceProperty = function(serviceObj, key) {
+	var criteria = { "Entity": "Device", "Key": key };
+	var result = serviceObj.ISysInfo.GetInfo(criteria);
+	if (result.ErrorCode == 0) {
+		return result.ReturnValue.StringData;
+	}
+	else {
+		return null;
+	}
+};
+
+PhoneGap.GetWrtPlatformVersion = function(serviceObj) {
+	var criteria = { "Entity": "Device", "Key": "PlatformVersion" };
+	var result = serviceObj.ISysInfo.GetInfo(criteria);
+	if (result.ErrorCode == 0) {
+		var version = {};
+		version.platform = result.ReturnValue.MajorVersion;
+		version.version = result.ReturnValue.MinorVersion;
+		return version;
+	}
+	else {
+		return null;
+	}
+};
+
+PhoneGap.ExtendWrtDeviceObj();/**
+ * 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;
+    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 position, call success and cancel the timer
+        if (dis.lastPosition && 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);
+};
+
+/**
+ * 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. 
+	this.getCurrentPosition(successCallback, errorCallback, options);
+	var frequency = 10000;
+        if (typeof options == 'object' && options.frequency)
+            frequency = options.frequency;
+	var that = this;
+	return setInterval(function() {
+		that.getCurrentPosition(successCallback, errorCallback, options);
+	}, frequency);
+};
+
+
+/**
+ * Clears the specified position watch.
+ * @param {String} watchId The ID of the watch returned from #watchPosition.
+ */
+Geolocation.prototype.clearWatch = function(watchId) {
+	clearInterval(watchId);
+};
+
+Geolocation.prototype.start = function(options) {
+	var so = device.getServiceObject("Service.Location", "ILocation");
+	
+	//construct the criteria for our location request
+	var updateOptions = new Object();
+	// Specify that location information need not be guaranteed. This helps in
+	// that the widget doesn't need to wait for that information possibly indefinitely.
+	updateOptions.PartialUpdates = true;
+	
+	//default 15 seconds
+	if (typeof(options) == 'object' && options.timeout) 
+		//options.timeout in in ms, updateOptions.UpdateTimeout in microsecs
+		updateOptions.UpdateTimeOut = options.timeout * 1000;
+
+	//default 1 second
+	if (typeof(options) == 'object' && options.interval) 
+		//options.timeout in in ms, updateOptions.UpdateTimeout in microsecs
+		updateOptions.UpdateInterval = options.interval * 1000;
+	
+	// Initialize the criteria for the GetLocation call
+	var trackCriteria = new Object();
+	// could use "BasicLocationInformation" or "GenericLocationInfo"
+	trackCriteria.LocationInformationClass = "GenericLocationInfo";
+	trackCriteria.Updateoptions = updateOptions;
+	
+	var dis = this;
+	so.ILocation.Trace(trackCriteria, function(transId, eventCode, result) {
+		var retVal = result.ReturnValue;
+
+		if (result.ErrorCode != 0 || isNaN(retVal.Latitude))
+			return;
+		
+		// heading options: retVal.TrueCourse, retVal.MagneticHeading, retVal.Heading, retVal.MagneticCourse
+		// but retVal.Heading was the only field being returned with data on the test device (Nokia 5800)
+		// WRT does not provide accuracy
+		var newCoords = new Coordinates(retVal.Latitude, retVal.Longitude, retVal.Altitude, null, retVal.Heading, retVal.HorizontalSpeed);
+		var positionObj = { coords: newCoords, timestamp: (new Date()).getTime() };
+
+		dis.lastPosition = positionObj;
+	});
+	
+};
+
+
+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 err = "map api is unimplemented on symbian.wrt";
+	debug.log(err);
+	return { name: "MapError", message: err };
+
+};
+
+if (typeof navigator.map == "undefined") navigator.map = new Map();
+function Network() {
+    /**
+     * The last known Network status.
+     */
+	this.lastReachability = null;
+};
+
+Network.prototype.isReachable = function(hostName, successCallback, options) {
+	var req = new XMLHttpRequest();  
+   	req.open('GET', hostName, true);  
+   	req.onreadystatechange = function (aEvt) {  
+     	if (req.readyState == 4) {  
+        	if(req.status == 200)  
+        		successCallback(NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK);
+         	else  
+          		successCallback(NetworkStatus.NOT_REACHABLE);
+ 		}  
+  	};  
+  	req.send(null);
+
+};
+
+/**
+ * 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() {
+	
+}
+
+Notification.prototype.vibrate = function(mills)
+{
+	
+	if (!Notification.getSysinfoObject())
+		Notification.embedSysinfoObject();
+	
+	this.sysinfo = Notification.getSysinfoObject();
+	this.sysinfo.startvibra(mills, 100);
+};
+
+//TODO: this is not beeping
+Notification.prototype.beep = function(count, volume)
+{
+	if (!Notification.getSysinfoObject())
+		Notification.embedSysinfoObject();
+	
+	this.sysinfo = Notification.getSysinfoObject();	
+	this.sysinfo.beep(220,2000);
+};
+
+
+/**
+ * Open a native alert dialog, with a customizable title and button text.
+ * @param {String} message Message to print in the body of the alert
+ * @param {String} [title="Alert"] Title of the alert dialog (default: Alert)
+ * @param {String} [buttonLabel="OK"] Label of the close button (default: OK)
+ */
+Notification.prototype.alert = function(message, title, buttonLabel) {
+    // Default is to use a browser alert; this will use "index.html" as the title though
+    alert(message);
+};
+
+/**
+ * Start spinning the activity indicator on the statusbar
+ */
+Notification.prototype.activityStart = function() {
+};
+
+/**
+ * Stop spinning the activity indicator on the statusbar, if it's currently spinning
+ */
+Notification.prototype.activityStop = function() {
+};
+
+/**
+ * Causes the device to blink a status LED.
+ * @param {Integer} count The number of blinks.
+ * @param {String} colour The colour of the light.
+ */
+Notification.prototype.blink = function(count, colour) {
+	
+};
+
+Notification.embedSysinfoObject = function() {
+	var el = document.createElement("embed");
+	el.setAttribute("type", "application/x-systeminfo-widget");
+	el.setAttribute("hidden", "yes");
+	document.getElementsByTagName("body")[0].appendChild(el);
+	return;
+};
+
+Notification.getSysinfoObject = function() {
+	return document.embeds[0];
+};
+
+if (typeof navigator.notification == "undefined") navigator.notification = new Notification();
+/**
+ * This class provides access to the device orientation.
+ * @constructor
+ */
+function Orientation() {
+	/**
+	 * The current orientation, or null if the orientation hasn't changed yet.
+	 */
+	this.currentOrientation = null;
+}
+
+/**
+ * Set the current orientation of the phone.  This is called from the device automatically.
+ * 
+ * When the orientation is changed, the DOMEvent \c orientationChanged is dispatched against
+ * the document element.  The event has the property \c orientation which can be used to retrieve
+ * the device's current orientation, in addition to the \c Orientation.currentOrientation class property.
+ *
+ * @param {Number} orientation The orientation to be set
+ */
+Orientation.prototype.setOrientation = function(orientation) {
+		if (orientation == this.currentOrientation) 
+			return;
+		var old = this.currentOrientation;
+
+		this.currentOrientation = orientation;
+		var e = document.createEvent('Events');
+		e.initEvent('orientationChanged', 'false', 'false');
+		e.orientation = orientation;
+		e.oldOrientation = old;
+		document.dispatchEvent(e);
+};
+
+/**
+ * Asynchronously aquires the current orientation.
+ * @param {Function} successCallback The function to call when the orientation
+ * is known.
+ * @param {Function} errorCallback The function to call when there is an error 
+ * getting the orientation.
+ */
+Orientation.prototype.getCurrentOrientation = function(successCallback, errorCallback) {
+	// If the orientation is available then call success
+	// If the orientation is not available then call error
+	try {
+		if (!this.serviceObj) 
+			this.serviceObj = this.getServiceObj();
+		
+		if (this.serviceObj == null) 
+			errorCallback({
+				name: "DeviceErr",
+				message: "Could not initialize service object"
+			});
+		
+		//get the sensor channel
+		var SensorParams = {
+			SearchCriterion: "Orientation"
+		};
+		var returnvalue = this.serviceObj.ISensor.FindSensorChannel(SensorParams);
+		
+		var error = returnvalue["ErrorCode"];
+		var errmsg = returnvalue["ErrorMessage"];
+		if (!(error == 0 || error == 1012)) {
+			var ex = {
+				name: "Unable to find Sensor Channel: " + error,
+				message: errmsg
+			};
+			errorCallback(ex);
+		}
+		var channelInfoMap = returnvalue["ReturnValue"][0];
+		var criteria = {
+			ChannelInfoMap: channelInfoMap,
+			ListeningType: "ChannelData"
+		};
+		
+		if (typeof(successCallback) != 'function') 
+			successCallback = function(){
+			};
+		if (typeof(errorCallback) != 'function') 
+			errorCallback = function(){
+			};
+		
+		this.success_callback = successCallback;
+		this.error_callback = errorCallback;
+		
+		//create a closure to persist this instance of orientation object into the RegisterForNofication callback
+		var obj = this;
+		
+		// TODO: this call crashes WRT, but there is no other way to read the orientation sensor
+		// http://discussion.forum.nokia.com/forum/showthread.php?t=182151&highlight=memory+leak
+		this.serviceObj.ISensor.RegisterForNotification(criteria, function(transId, eventCode, result){
+			var criteria = {
+				TransactionID: transId
+			};
+			try {
+				//var orientation = result.ReturnValue.DeviceOrientation;
+				obj.serviceObj.ISensor.Cancel(criteria);
+				
+				var orientation = null;
+				switch (result.ReturnValue.DeviceOrientation) {
+					case "DisplayUpwards": orientation = DisplayOrientation.FACE_UP; break;
+					case "DisplayDownwards": orientation = DisplayOrientation.FACE_DOWN; break;
+					case "DisplayUp": orientation = DisplayOrientation.PORTRAIT; break;
+					case "DisplayDown": orientation = DisplayOrientation.REVERSE_PORTRAIT; break;
+					case "DisplayRightUp": orientation = DisplayOrientation.LANDSCAPE_RIGHT_UP; break;
+					case "DisplayLeftUp": orientation = DisplayOrientation.LANDSCAPE_LEFT_UP; break;
+					
+				}
+				
+				obj.setOrientation(orientation);
+				
+				obj.success_callback(orientation);
+				
+			} 
+			catch (ex) {
+				obj.serviceObj.ISensor.Cancel(criteria);
+				obj.error_callback(ex);
+			}
+			
+		});
+	} catch (ex) {
+		errorCallback({ name: "OrientationError", message: ex.name + ": " + ex.message });
+	}
+};
+
+/**
+ * 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 frequency = (options != undefined)? options.frequency : 1000;
+	return setInterval(function() {
+		navigator.orientation.getCurrentOrientation(successCallback, errorCallback);
+	}, frequency);
+};
+
+/**
+ * Clears the specified orientation watch.
+ * @param {String} watchId The ID of the watch returned from #watchOrientation.
+ */
+Orientation.prototype.clearWatch = function(watchId) {
+	clearInterval(watchId);
+};
+
+//gets the Acceleration Service Object from WRT
+Orientation.prototype.getServiceObj = function() {
+	var so;
+	
+    try {
+        so = device.getServiceObject("Service.Sensor", "ISensor");
+    } catch (ex) {
+		throw {
+			name: "DeviceError",
+			message: ex.name + ": " + ex.message
+		};
+    }		
+	return so;
+};
+
+
+/**
+ * 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();
+/**
+ * This class contains position information.
+ * @param {Object} lat
+ * @param {Object} lng
+ * @param {Object} acc
+ * @param {Object} alt
+ * @param {Object} altacc
+ * @param {Object} head
+ * @param {Object} vel
+ * @constructor
+ */
+function Position(coords, timestamp) {
+	this.coords = coords;
+        this.timestamp = new Date().getTime();
+}
+
+function Coordinates(lat, lng, alt, acc, head, vel) {
+	/**
+	 * 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;
+}
+
+/**
+ * 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;
+/**
+ * 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 {
+		if (!this.serviceObj)
+			this.serviceObj = this.getServiceObj();
+			
+	    // Setup input params using dot syntax
+	    var criteria = new Object();
+	    criteria.MessageType = 'SMS';
+	    criteria.To = number;
+	    criteria.BodyText = message;
+
+      	var result = this.serviceObj.IMessaging.Send(criteria);
+    	if (result.ErrorCode != 0 && result.ErrorCode != "0")
+		{
+			var exception = { name: "SMSError", message: result.ErrorMessage };
+			throw exception;
+		} else {
+			successCallback.call();
+		}
+    }
+  	catch(ex)
+  	{
+		errorCallback.call({ name: "SmsError", message: ex.name + ": " + ex.message });
+  	}
+
+};
+
+
+//gets the Sms Service Object from WRT
+Sms.prototype.getServiceObj = function() {
+	var so;
+	
+    try {
+        so = device.getServiceObject("Service.Messaging", "IMessaging");
+    } catch (ex) {
+		throw {
+			name: "SmsError",
+			message: "Failed to load sms service (" + ex.name + ": " + ex.message + ")"
+		};
+    }		
+	return so;
+};
+
+if (typeof navigator.sms == "undefined") navigator.sms = new Sms();/**
+ * @author ryan
+ */
+
+function Storage() {
+	this.available = true;
+	this.serialized = null;
+	this.items = null;
+	
+	if (!window.widget) {
+		this.available = false;
+		return;
+	}
+	var pref = window.widget.preferenceForKey(Storage.PREFERENCE_KEY);
+	
+	//storage not yet created
+	if (pref == "undefined" || pref == undefined) {
+		this.length = 0;
+		this.serialized = "({})";
+		this.items = {};
+		window.widget.setPreferenceForKey(this.serialized, Storage.PREFERENCE_KEY);
+	} else {
+		this.serialized = pref;'({"store_test": { "key": "store_test", "data": "asdfasdfs" },})';
+		this.items = eval(this.serialized);
+	}
+}
+
+Storage.PREFERENCE_KEY = "phonegap_storage_pref_key";
+
+Storage.prototype.index = function (key) {
+	
+};
+
+Storage.prototype.getItem = function (key) {
+	try {
+		return this.items[key].data;
+	} catch (ex) {
+		return null;
+	}
+};
+
+Storage.prototype.setItem = function (key, data) {
+
+	this.items[key] = {
+		"key": key,
+		"data": data
+	};
+	
+	this.serialize();
+};
+
+Storage.prototype.removeItem = function (key) {
+
+	if (this.items[key]) {
+		this.items[key] = undefined;
+	}
+	this.serialize();
+};
+
+Storage.prototype.clear = function () {
+	this.serialized = "({})";
+	this.items = {};
+	this.serialize();
+};
+
+Storage.prototype.serialize = function() {
+	var json = "";
+	
+	for (key in this.items) {
+		var item = this.items[key];
+		if (typeof item != "undefined") {
+			json += "\"" + item.key + "\": { \"key\": \"" + item.key + "\", \"data\": \"" + item.data + "\" }, ";
+		}
+	}
+	this.serialized = "({" + json + "})";
+
+	window.widget.setPreferenceForKey( this.serialized, Storage.PREFERENCE_KEY);
+};
+
+if (typeof navigator.storage == "undefined" ) navigator.storage = new Storage();
+/**
+ * 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) {
+	widget.openURL('tel:+' + number);
+};
+
+if (typeof navigator.telephony == "undefined") navigator.telephony = new Telephony();
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/acceleration.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,37 @@
+/**
+ * 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;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/accelerometer.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,138 @@
+/**
+ * This class provides access to device accelerometer data.
+ * @constructor
+ */
+function Accelerometer() {
+	/**
+	 * The last known acceleration.
+	 */
+	this.lastAcceleration = null;
+}
+
+/**
+ * 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) {
+	// If the acceleration is available then call success
+	// If the acceleration is not available then call error
+	
+	try {
+		if (!this.serviceObj) 
+			this.serviceObj = this.getServiceObj();
+		
+		if (this.serviceObj == null) 
+			throw {
+				name: "DeviceErr",
+				message: "Could not initialize service object"
+			};
+		
+		//get the sensor channel
+		var SensorParams = {
+			SearchCriterion: "AccelerometerAxis"
+		};
+		var returnvalue = this.serviceObj.ISensor.FindSensorChannel(SensorParams);
+		var error = returnvalue["ErrorCode"];
+		var errmsg = returnvalue["ErrorMessage"];
+		if (!(error == 0 || error == 1012)) {
+			var ex = {
+				name: "Unable to find Sensor Channel: " + error,
+				message: errmsg
+			};
+			throw ex;
+		}
+		var channelInfoMap = returnvalue["ReturnValue"][0];
+		var criteria = {
+			ChannelInfoMap: channelInfoMap,
+			ListeningType: "ChannelData"
+		};
+		
+		if (typeof(successCallback) != 'function') 
+			successCallback = function(){
+			};
+		if (typeof(errorCallback) != 'function') 
+			errorCallback = function(){
+			};
+		
+		this.success_callback = successCallback;
+		this.error_callback = errorCallback;
+		//create a closure to persist this instance of Accelerometer into the RegisterForNofication callback
+		var obj = this;
+		
+		// TODO: this call crashes WRT, but there is no other way to read the accel sensor
+		// http://discussion.forum.nokia.com/forum/showthread.php?t=182151&highlight=memory+leak
+		this.serviceObj.ISensor.RegisterForNotification(criteria, function(transId, eventCode, result){
+			try {
+				var criteria = {
+					TransactionID: transId
+				};
+				obj.serviceObj.ISensor.Cancel(criteria);
+				
+				var accel = new Acceleration(result.ReturnValue.XAxisData, result.ReturnValue.YAxisData, result.ReturnValue.ZAxisData);
+				Accelerometer.lastAcceleration = accel;
+				
+				obj.success_callback(accel);
+				
+			} 
+			catch (ex) {
+				obj.serviceObj.ISensor.Cancel(criteria);
+				obj.error_callback(ex);
+			}
+			
+		});
+	} catch (ex) {
+		errorCallback(ex);
+	}
+
+};
+
+
+/**
+ * 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;
+	return setInterval(function() {
+		navigator.accelerometer.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);
+};
+
+//gets the Acceleration Service Object from WRT
+Accelerometer.prototype.getServiceObj = function() {
+	var so;
+	
+    try {
+        so = device.getServiceObject("Service.Sensor", "ISensor");
+    } catch (ex) {
+		throw {
+			name: "DeviceError",
+			message: "Could not initialize accel service object (" + ex.name + ": " + ex.message + ")"
+		};
+    }		
+	return so;
+};
+
+if (typeof navigator.accelerometer == "undefined") navigator.accelerometer = new Accelerometer();
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/audio.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,36 @@
+/**
+ * This class provides access to the device media, interfaces to both sound and video
+ * @constructor
+ */
+function Audio(src, successCallback, errorCallback) {
+	this.src = src;
+	this.successCallback = successCallback;
+	this.errorCallback = errorCallback;												
+}
+
+Audio.prototype.record = function() {
+};
+
+Audio.prototype.play = function() {
+try {
+	if (document.getElementById('gapsound'))
+		document.body.removeChild(document.getElementById('gapsound'));
+	var obj;
+	obj = document.createElement("embed");
+	obj.setAttribute("id", "gapsound");
+	obj.setAttribute("type", "audio/x-mpeg");
+	obj.setAttribute("width", "0");
+	obj.setAttribute("width", "0");
+	obj.setAttribute("hidden", "true");
+	obj.setAttribute("autostart", "true");
+	obj.setAttribute("src", this.src);
+	document.body.appendChild(obj);
+} catch (ex) { debug.log(ex.name + ": " + ex.message); }
+};
+
+Audio.prototype.pause = function() {
+};
+
+Audio.prototype.stop = function() {
+	document.body.removeChild(document.getElementById('gapsound'));
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/camera.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,51 @@
+/**
+ * This class provides access to the device camera.
+ * @constructor
+ */
+function Camera() {
+	this.success_callback = null;
+	this.error_callback = null;
+}
+
+/**
+ * We use the Platform Services 2.0 API here. So we must include a portion of the
+ * PS 2.0 source code (camera API). 
+ * @param {Function} successCallback
+ * @param {Function} errorCallback
+ * @param {Object} options
+ */
+Camera.prototype.getPicture = function(successCallback, errorCallback, options){
+	try {
+		if (!this.serviceObj) {
+			this.serviceObj = com.nokia.device.load("", "com.nokia.device.camera", "");
+		}
+		if (!this.serviceObj) {
+			throw {
+				name: "CameraError",
+				message: "could not load camera service"
+			};
+		}
+		var obj = this;
+		
+		obj.success_callback = successCallback;
+		obj.error_callback = errorCallback;
+		this.serviceObj.startCamera( function(transactionID, errorCode, outPut) { 
+			//outPut should be an array of image urls (local), or an error code
+			if (errorCode == 0) {
+				obj.success_callback(outPut);
+			}
+			else {
+				obj.error_callback({
+					name: "CameraError",
+					message: errorCode
+				});
+			}
+		});
+		
+	} catch (ex) {
+		errorCallback.call(ex);
+	}
+	
+};
+
+if (typeof navigator.camera == "undefined") navigator.camera = new Camera();
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/camera/com.nokia.device.camera.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,62 @@
+/*
+Copyright © 2009 Nokia. All rights reserved.
+Code licensed under the BSD License:
+Software License Agreement (BSD License) Copyright © 2009 Nokia.
+All rights reserved.
+Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
+Neither the name of Nokia Corporation. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Nokia Corporation. 
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+version: 1.0
+*/
+
+
+// Camera service interface
+
+var __device_camera_service_entry =  {"name": null, 
+					 "version": null,
+					 "proto": __device_camera,
+					 "descriptor": __device_camera_descriptor,
+					 "providers": [{"descriptor": __sp_camera_descriptor, "instance": __sp_camera_instance}]
+					};
+
+function __device_camera_descriptor(provider){
+  this.interfaceName = provider.interfaceName;
+  this.version = provider.version;
+}
+
+
+// Private camera  prototype: called from service factory
+function __device_camera(provider){
+  //Private properties
+  this.provider = provider;
+  //Read-only properties
+  this.interfaceName = provider.descriptor.interfaceName;
+  this.version = provider.descriptor.version;
+ // this.supportedMediaTypes = provider.supportedMediaTypes;
+ // this.supportedSizes = provider.supportedSizes;
+  //Core methods
+  this.startCamera = __device_camera_startCamera;
+  this.stopViewfinder = __device_camera_stopViewfinder;
+  //Extended methods
+  this.takePicture = __device_camera_takePicture;
+}
+
+
+//Why bother to define these methods? Because the camera
+//object defines the contract for providers!
+
+function __device_camera_startCamera(camera_cb){
+  return this.provider.startCamera(camera_cb);
+}
+
+function __device_camera_stopViewfinder(){
+  this.provider.stopViewfinder();
+}
+
+function __device_camera_takePicture(format){
+  this.provider.takePicture(format);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/camera/com.nokia.device.framework.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,373 @@
+/*
+Copyright © 2009 Nokia. All rights reserved.
+Code licensed under the BSD License:
+Software License Agreement (BSD License) Copyright © 2009 Nokia.
+All rights reserved.
+Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
+Neither the name of Nokia Corporation. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Nokia Corporation. 
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+version: 1.0
+*/
+
+
+var __device_debug_on__ = true;
+var err_missing_argument = 1003;
+var event_cancelled = 3;
+var err_bad_argument = 1002;
+var err_InvalidService_Argument = 1000;
+var err_ServiceNotReady = 1006;
+var err_ServiceNotSupported = 1004;
+
+function __device_debug(text){
+  //if(__device_debug_on__) alert(text);
+}
+
+function __device_handle_exception(e, text){
+	__device_debug(text);
+	throw(e);
+}
+
+function __device_typeof(value)
+{
+	// First check to see if the value is undefined.
+	if (value == undefined) {
+        return "undefined";
+    }
+	// Check for objects created with the "new" keyword.
+	if (value instanceof Object) {
+		// Check whether it's a string object.
+		if (value instanceof String) {
+			return "String";
+		}
+		// Check whether it's an array object/array literal.
+		else 
+			if (value instanceof Array) {
+				return "Array";
+			}
+	}
+	// dealing with a literal.
+		if (typeof value) {
+			if (typeof value == "object") {
+				if (typeof value == "object" && !value) {
+					return "null";
+				}
+			}
+           // if not null check for other types
+			
+				// Check if it's a string literal.
+			else if (typeof value == "string") {
+					return "string";
+				}
+		}	 
+}
+
+
+// The top-level service object. It would be nice to use a namespace here 
+// (com.nokia.device.service), but emulating namespaces still allows name clashes.
+/*
+var sp_device = {
+        //services: null; // TBD: Make the services list a member of this object?
+	load: __device_service_load,
+        listServices: __device_service_list,
+        listInterfaces: __device_service_interfaces,
+        version: "0.1",
+        info: "device prototype"
+};
+*/
+
+if(undefined == com)
+    var com={};
+
+if( typeof com != "object")
+    throw("com defined as non object");
+
+if(undefined == com.nokia)
+    com.nokia = {};
+
+if( typeof com.nokia != "object")
+    throw("com.nokia defined as non object");
+
+if(undefined == com.nokia.device)
+    com.nokia.device = {
+        load: __device_service_load,
+        listServices: __device_service_list,
+        listInterfaces: __device_service_interfaces,
+        version: "0.1",
+        info: "device prototype"
+        };
+else
+    throw("com.nokia.device already defined");
+
+com.nokia.device.SORT_ASCENDING = 0;
+com.nokia.device.SORT_DESCENDING = 1;
+
+com.nokia.device.SORT_BY_DATE = 0;
+com.nokia.device.SORT_BY_SENDER = 1;
+
+com.nokia.device.STATUS_READ = 0;
+com.nokia.device.STATUS_UNREAD = 1;
+
+
+// Configure the services offered.
+
+var __device_services_inited = false;
+
+var __device_services = [
+
+  // For now, the only service is the base "device"" service
+  {
+    "name":"com.nokia.device",
+    "version": 0.1, 
+    "interfaces": []
+  }
+];
+
+// Initialize the configured services.
+
+function __device_services_init(){
+  if(__device_services_inited){
+    return;
+  }
+  __device_services_inited = true;
+
+  // Get the service-specific service entries. Note that these
+  // need to be individually wrapped by try/catch blocks so that the
+  // interpreter gracefully handles missing services. 
+
+  try {
+    __device_services[0].interfaces.push(__device_geolocation_service_entry);
+  }catch (e){
+    __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_camera_service_entry);
+  }catch (e){
+    __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_media_service_entry);
+  }catch (e){
+//    __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_contacts_service_entry);
+  }catch (e){
+//    __device_debug("Missing library implementation: " + e);
+  }
+ try {
+    __device_services[0].interfaces.push(__device_messaging_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_calendar_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_landmarks_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_event_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_sysinfo_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+  try {
+    __device_services[0].interfaces.push(__device_sensors_service_entry);
+  }catch (e){
+      __device_debug("Missing library implementation: " + e);
+  }
+
+}
+
+function __device_get_implementation(i){
+  //__device_debug("get_implementation: " + i);
+  return  new i.proto(new(i.providers[0].instance));
+}
+
+function __device_get_descriptor(i){
+  //__device_debug("get_descriptor: " + i);
+  return new i.descriptor(new(i.providers[0].descriptor));
+}
+
+function __device_get_interface(s, interfaceName, version){
+  //__device_debug("get_interface: " + s + " " + interfaceName);
+  var i = s.interfaces;
+  if((interfaceName == null) || (interfaceName == '')){
+    // Interface name not specified, get first interface, ignoring version
+    return __device_get_implementation(i[0]);
+  }
+
+  // Find first match of name and version
+  for (var d in i){
+  
+    if(i[d].name == null){
+      __device_update_descriptor(i[d]);
+    }
+    if(i[d].name == undefined){
+      continue;
+    }
+    if (i[d].name == interfaceName){
+      // Match version if specified
+      if ((version == null) || (version == '') || (i[d].version >= version)){
+	return __device_get_implementation(i[d]);
+      }
+    }
+  }
+  return null;
+}
+
+// Implemention of the load method
+
+function __device_service_load(serviceName, interfaceName, version){
+
+  __device_services_init();
+  
+  // Service name is specified
+   if ((serviceName != null) && (serviceName != '') &&(serviceName != "*")){
+    for(var s in __device_services){
+      if (serviceName == __device_services[s].name){
+	return __device_get_interface(__device_services[s], interfaceName, version);
+      }
+    }
+  // Service name not specified, get first implementation 
+  } else {
+    //__device_debug("Trying to get interface implementations: ");
+    for(var s in __device_services){
+      //__device_debug("service_load: " + s + ":" +  __device_services[s].name + ": " + interfaceName);
+      var i = __device_get_interface(__device_services[s], interfaceName, version);
+      if (i != null){
+	return i;
+      }
+    }
+  }
+  return null;
+}
+
+// Lazily fill in the descriptor table
+
+function __device_update_descriptor(i){
+  var d = __device_get_descriptor(i);
+  i.name = d.interfaceName;
+  i.version = d.version;  
+}
+// Get an array of interface descriptors for a service
+
+function __device_interface_list(s){
+  var retval = new Array();
+  for(var i in s.interfaces){
+    if(s.interfaces[i].name == null){
+      __device_update_descriptor(s.interfaces[i]);
+    }
+    if(s.interfaces[i].name == undefined){
+      continue;
+    }
+    retval[i] = new Object();
+    retval[i].name = s.interfaces[i].name;
+    retval[i].version = s.interfaces[i].version;
+  }  
+  return retval;
+}
+
+// Get a service description
+
+function __device_service_descriptor(s){
+  this.name = s.name;
+  this.version = s.version;
+  this.interfaces = __device_interface_list(s);
+  this.toString = __device_service_descriptor_to_string;
+}
+
+function __device_service_descriptor_to_string(){
+  var is = "\nInterfaces(s): ";
+
+  for (i in this.interfaces){
+    is += "\n" + this.interfaces[i].name + " " + this.interfaces[0].version;
+  }
+  return ("Service: " + this.name + is);
+}
+
+// Implement the listServices method 
+
+function __device_service_list(serviceName, interfaceName, version){
+  //__device_debug("__device_service_list: " + serviceName + " " + interfaceName);
+  __device_services_init();
+  var retval = new Array();
+  var n = 0;
+  
+  //Treat empty service and interface names as wildcards
+  if ((serviceName == null)|| (serviceName == '')/* || (serviceName == undefined)*/){
+    serviceName = ".*"; 
+  }
+  if ((interfaceName == null) || (interfaceName == '') /*|| (serviceName == undefined)*/){
+    interfaceName = ".*";
+  }
+ 
+  if ((typeof serviceName != "string") || (typeof interfaceName != "string")) {
+  	return retval;
+  }
+  
+  // This method does regular expression matching of service and interface
+
+  var sregx = new RegExp(serviceName);
+  var iregx = new RegExp(interfaceName);
+ 
+  for(var s in __device_services){
+   //__device_debug (serviceName + "==" + __device_services[s].name + "?:" + sregx.test(__device_services[s].name));
+    if (sregx.test(__device_services[s].name)){
+      // Find the first matching interface 
+        
+      for(var i in __device_services[s].interfaces){
+        if(__device_services[s].interfaces[i].name == null){
+          __device_update_descriptor(__device_services[s].interfaces[i]);
+	}
+        if(__device_services[s].interfaces[i].name == undefined){
+	  continue;
+	}
+	//__device_debug (interfaceName + "==" + __device_services[s].interfaces[i].name + "?:" + iregx.test(__device_services[s].interfaces[i].name));
+	if (iregx.test(__device_services[s].interfaces[i].name)){
+	  if ((version == null) || (version == '') || (__device_services[s].interfaces[i].version >= version)){
+            // An interface matched, we're done.
+            retval[n] = new __device_service_descriptor(__device_services[s]);
+            break; 
+	  }
+	}
+      }
+    }
+    ++n;
+  }
+  return retval;
+}
+
+// Implement the listInterfaces method
+    
+function __device_service_interfaces(serviceName){
+  __device_services_init();
+  if(serviceName==null||serviceName==undefined||serviceName==''){
+  	throw new DeviceError("Framework: listInterfaces: serviceName is missing", err_missing_argument);
+  }
+  for (var s in __device_services){
+    if(__device_services[s].name == serviceName){
+      return __device_interface_list(__device_services[s]);
+    }
+  }
+  return null;
+}
+
+function modifyObjectBaseProp(obj){
+  for (pro in obj) {
+    if(typeof obj[pro] == "function" )
+      obj[pro] = 0;
+    }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/camera/com.nokia.device.utility.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,110 @@
+/*
+Copyright © 2009 Nokia. All rights reserved.
+Code licensed under the BSD License:
+Software License Agreement (BSD License) Copyright © 2009 Nokia.
+All rights reserved.
+Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
+Neither the name of Nokia Corporation. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Nokia Corporation. 
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+version: 1.0
+*/
+
+
+// utility.js
+//
+// This file contains some utility functions for S60 providers
+
+
+// Start an application and wait for it to exit
+
+//TBD: Get rid of this global, use closures instead
+
+DeviceError.prototype = new Error(); //inheritance occurs here
+DeviceError.prototype.constructor = DeviceError; //If this not present then, it uses default constructor of Error
+
+//constructor for DeviceError.
+function DeviceError(message,code) 
+{
+	this.toString = concatenate;
+	this.code = code;
+	this.name = "DeviceException";//we can even overwrite default name "Error"
+	this.message=message; 
+}
+
+function concatenate()
+{
+	return (this.name+":"+" "+this.message+" "+this.code);
+}
+
+function splitErrorMessage(errmessage)
+{
+	if(errmessage.search(/:/)!=-1)
+	{
+		if((errmessage.split(":").length)==2)
+		{
+			return errmessage.split(":")[1];
+		}
+		if((errmessage.split(":").length)>2)
+		{
+			return errmessage.split(":")[2];
+		}
+	}
+	return errmessage;
+}
+
+
+var __s60_start_and_wait_cb;
+
+function __s60_on_app_exit(){
+  widget.onshow = null;
+  if(__s60_start_and_wait_cb != null){
+    __s60_start_and_wait_cb();
+  }
+}
+
+function __s60_on_app_start(){
+  widget.onhide = null;
+  widget.onshow = __s60_on_app_exit;
+}
+
+// This function cannot actually force JS to wait,
+// but it does supply a callback the apps can use
+// to continue processing on return from the app.
+// Apps should take care not to reinvoke this and
+// should be careful about any other processing
+// that might happen while the app is running.
+
+function __s60_start_and_wait(id, args, app_exit_cb){
+  __s60_start_and_wait_cb = app_exit_cb;
+  widget.onhide = __s60_on_app_start;
+  widget.openApplication(id, args);
+}
+
+function __s60_api_not_supported(){
+  throw(err_ServiceNotSupported);
+}
+
+function __s60_enumerate_object(object, namespace, func, param){
+    var key;
+    for(key in object){
+       
+        var propname;
+       	if(namespace){
+	    propname = namespace + "." + key;
+	}
+	else{
+	    propname = key;
+	}
+        var value = object[key];
+        if(typeof value == "object"){
+	  __s60_enumerate_object(value, propname, func, param);
+	}
+	else {
+	  func(propname,value, param);
+	}
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/camera/s60_camera.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,118 @@
+/*
+Copyright © 2009 Nokia. All rights reserved.
+Code licensed under the BSD License:
+Software License Agreement (BSD License) Copyright © 2009 Nokia.
+All rights reserved.
+Redistribution and use of this software in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
+Neither the name of Nokia Corporation. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Nokia Corporation. 
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+version: 1.0
+*/
+
+
+// S60 sp-based camera provider
+
+function __sp_camera_descriptor(){
+  //__device_debug("sp_camera_descriptor");
+  //Read-only properties
+  this.interfaceName = "com.nokia.device.camera";
+  this.version = "0.1";
+  //Class-static properties 
+}
+
+// TBD make local to closure funcs
+var __sp_camera_start_date;
+
+function __sp_camera_instance(){
+  //__device_debug("sp_camera_instance");
+  //Descriptor
+  this.descriptor = new __sp_camera_descriptor();
+  //Core methods
+  this.startCamera = __sp_startCamera;
+  this.stopViewfinder = __s60_api_not_supported;
+  //Extended methods
+  this.takePicture = __s60_api_not_supported;
+  //Private data
+}
+
+var CAMERA_APP_ID = 0x101f857a;
+
+//Apps should take care that this is not reinvoked
+//while the viewfinder is running. 
+
+function __sp_startCamera(camera_cb){
+
+	//If callback is null , then return missing argument error
+    if( camera_cb == null )
+        throw new DeviceError("Camera:startCamera:callback is missing", err_missing_argument);
+        
+	//If the callback is not a function, then return bad type error
+	if( typeof(camera_cb) != "function" )
+	    throw new DeviceError("Camera:startCamera:callback is a non-function", err_bad_argument);
+
+  var finished = function (){
+    var invoker = function (arg1, arg2, arg3){
+      //__device_debug("invoker with: " + camera_cb);
+      var it = arg3.ReturnValue;
+      var item;
+      var items = new Array();
+      while (( item = it.getNext()) != undefined){
+          var d = new Date(Date.parse(item.FileDate));
+          //__device_debug(item.FileName + " " + d );
+          // Items returned in reverse date order, so stop iterating before
+          // reaching initial date. (Should be able to do this more efficiently
+          // with sp filter, but that doesn't seem to work right now.)
+          if (d > __sp_camera_start_date) {
+              var pathname = item.FileNameAndPath.replace(/\\/g, "/");
+              var fileScheme = "file:///";
+              //Non-patched builds don't allow file scheme TBD: change this for patched builds
+              items.unshift(fileScheme + pathname);
+          }
+      }
+      var dummyTransID = 0;
+      var dummyStatusCode = 0;
+      camera_cb(dummyTransID, dummyStatusCode, items);
+    };
+
+    
+    //When camera returns, get the image(s) created
+    try {
+      var mso = device.getServiceObject("Service.MediaManagement", "IDataSource");
+    }
+    catch(e) {
+      __device_handle_exception (e, "media service not available : " + e);
+    }
+    
+    var criteria = new Object();
+	modifyObjectBaseProp(criteria);
+    criteria.Type = 'FileInfo';
+    criteria.Filter = new Object();
+	modifyObjectBaseProp(criteria.Filter);
+    criteria.Filter.FileType = 'Image';
+    //criteria.Filter.Key = 'FileDate';
+    //criteria.Filter.StartRange = null;
+    //criteria.Filter.EndRange = null;
+    criteria.Sort = new Object();
+	modifyObjectBaseProp(criteria.Sort);
+    criteria.Sort.Key = 'FileDate';
+    criteria.Sort.Order = 'Descending';
+    
+    try {
+      var rval = mso.IDataSource.GetList(criteria, invoker);
+    }
+    catch (e) {
+      __device_handle_exception (e, "media service GetList failed: " + e);
+    }
+  };
+
+  __sp_camera_start_date = new Date();
+  __s60_start_and_wait(CAMERA_APP_ID, "", finished);
+  var dummyTid = 0;
+  return dummyTid;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/contacts.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,159 @@
+/**
+ * This class provides access to the device contacts.
+ * @constructor
+ */
+
+function Contacts() {
+	
+}
+
+function Contact() {
+	this.id = null;
+	this.name = { 
+		formatted: "",
+		givenName: "",
+		familyName: ""
+	};
+    this.phones = [];
+    this.emails = [];
+}
+
+Contact.prototype.displayName = function()
+{
+    // TODO: can be tuned according to prefs
+	return this.givenName + " " + this.familyName;
+};
+
+/*
+ * @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) {
+	try {
+		this.contactsService = device.getServiceObject("Service.Contact", "IDataSource");
+		if (typeof options == 'object')
+			this.options = options;
+		else
+			this.options = {};
+		
+		var criteria = new Object();
+		criteria.Type = "Contact";
+		if (filter && filter.name) {
+			var searchTerm = '';
+			if (filter.name.givenName && filter.name.givenName.length > 0) {
+				searchTerm += filter.name.givenName;
+			}
+			if (filter.name.familyName && filter.name.familyName.length > 0) {
+				searchTerm += searchTerm.length > 0 ? ' ' + filter.name.familyName : filter.name.familyName;
+			}
+			if (!filter.name.familyName && !filter.name.givenName && filter.name.formatted) {
+				searchTerm = filter.name.formatted;
+			}
+			criteria.Filter = { SearchVal: searchTerm };
+		}
+		
+		if (typeof(successCallback) != 'function') 
+			successCallback = function(){};
+		if (typeof(errorCallback) != 'function') 
+			errorCallback = function(){};
+		if (isNaN(this.options.limit))
+			this.options.limit = 200;
+		if (isNaN(this.options.page))
+			this.options.page = 1;
+		
+		//need a closure here to bind this method to this instance of the Contacts object
+		this.global_success = successCallback;
+		var obj = this;
+		
+		//WRT: result.ReturnValue is an iterator of contacts
+		this.contactsService.IDataSource.GetList(criteria, function(transId, eventCode, result){
+			obj.success_callback(result.ReturnValue);
+		});
+	} 
+	catch (ex) {
+		alert(ex.name + ": " + ex.message);
+		errorCallback(ex);
+	}
+};
+
+Contacts.prototype.success_callback = function(contacts_iterator) {
+	try {
+	var gapContacts = new Array();
+	contacts_iterator.reset();
+    var contact;
+	var i = 0;
+	var end = this.options.page * this.options.limit;
+	var start = end - this.options.limit;
+	while ((contact = contacts_iterator.getNext()) != undefined && i < end) {
+		try {
+			if (i >= start) {
+				var gapContact = new Contact();
+				gapContact.name.givenName = Contacts.GetValue(contact, "FirstName");
+				gapContact.name.familyName = Contacts.GetValue(contact, "LastName");
+				gapContact.name.formatted = gapContact.name.givenName + " " + gapContact.name.familyName;
+				gapContact.emails = Contacts.getEmailsList(contact);
+				gapContact.phones = Contacts.getPhonesList(contact);
+				gapContact.address = Contacts.getAddress(contact);
+				gapContact.id = Contacts.GetValue(contact, "id");
+				gapContacts.push(gapContact);
+			}
+			i++;
+		} catch (e) {
+			alert("ContactsError (" + e.name + ": " + e.message + ")");
+		}
+	}
+	this.contacts = gapContacts;
+	this.global_success(gapContacts);
+	} catch (ex) { alert(ex.name + ": " + ex.message); }
+};
+
+Contacts.getEmailsList = function(contact) {
+	var emails = new Array();
+	try {
+			emails[0] = { type:"General", address: Contacts.GetValue(contact, "EmailGen") };
+			emails[1] = { type:"Work", address: Contacts.GetValue(contact, "EmailWork") };		
+			emails[2] = { type:"Home", address: Contacts.GetValue(contact, "EmailHome") };
+	} catch (e) {
+		emails = [];
+	}
+	return emails;
+};
+
+Contacts.getPhonesList = function(contact) {
+	var phones = new Array();
+	try {
+			phones[0] = { type:"Mobile", number: Contacts.GetValue(contact, "MobilePhoneGen") };
+			phones[1] = { type:"Home", number: Contacts.GetValue(contact, "LandPhoneGen") };
+			phones[2] = { type:"Fax", number: Contacts.GetValue(contact, "FaxNumberGen") };
+			phones[3] = { type:"Work", number: Contacts.GetValue(contact, "LandPhoneWork") };
+			phones[4] = { type:"WorkMobile", number: Contacts.GetValue(contact, "MobilePhoneWork") };
+	} catch (e) {
+		phones = [];
+	}
+	return phones;
+};
+
+Contacts.getAddress = function(contact) {
+	var address = "";
+	try {
+		address = Contacts.GetValue(contact, "AddrLabelHome") + ", " + Contacts.GetValue(contact, "AddrStreetHome") + ", " +
+				Contacts.GetValue(contact, "AddrLocalHome") + ", " + Contacts.GetValue(contact, "AddrRegionHome") + ", " + 
+				Contacts.GetValue(contact, "AddrPostCodeHome") + ", " + Contacts.GetValue(contact, "AddrCountryHome");
+	} catch (e) {
+		address = "";
+	}
+	return address;
+};
+
+Contacts.GetValue = function(contactObj, key) {
+	try {
+		return contactObj[key]["Value"];
+	} catch (e) {
+		return "";
+	}
+};
+
+if (typeof navigator.contacts == "undefined") navigator.contacts = new Contacts();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/debugconsole.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,32 @@
+/**
+ * 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) {
+	
+	//This ends up in C:\jslog_widget.log on the device
+	console.log(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) {
+};
+
+/**
+ * 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 window.debug == "undefined") window.debug = new DebugConsole();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/device.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,55 @@
+PhoneGap.ExtendWrtDeviceObj = function(){
+	
+	if (!window.device)
+		window.device = {};
+	navigator.device = window.device;
+
+	try {
+	
+		if (window.menu)
+	    	window.menu.hideSoftkeys();
+		
+		device.available = PhoneGap.available;
+		device.platform = null;
+		device.version = null;
+		device.name = null;
+		device.uuid = null;
+		
+		var so = device.getServiceObject("Service.SysInfo", "ISysInfo");
+		var pf = PhoneGap.GetWrtPlatformVersion(so);
+		device.platform = pf.platform;
+		device.version = pf.version;
+		device.uuid = PhoneGap.GetWrtDeviceProperty(so, "IMEI");
+		device.name = PhoneGap.GetWrtDeviceProperty(so, "PhoneModel");
+	} 
+	catch (e) {
+		device.available = false;
+	}
+};
+
+PhoneGap.GetWrtDeviceProperty = function(serviceObj, key) {
+	var criteria = { "Entity": "Device", "Key": key };
+	var result = serviceObj.ISysInfo.GetInfo(criteria);
+	if (result.ErrorCode == 0) {
+		return result.ReturnValue.StringData;
+	}
+	else {
+		return null;
+	}
+};
+
+PhoneGap.GetWrtPlatformVersion = function(serviceObj) {
+	var criteria = { "Entity": "Device", "Key": "PlatformVersion" };
+	var result = serviceObj.ISysInfo.GetInfo(criteria);
+	if (result.ErrorCode == 0) {
+		var version = {};
+		version.platform = result.ReturnValue.MajorVersion;
+		version.version = result.ReturnValue.MinorVersion;
+		return version;
+	}
+	else {
+		return null;
+	}
+};
+
+PhoneGap.ExtendWrtDeviceObj();
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/geolocation.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,135 @@
+/**
+ * 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;
+    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 position, call success and cancel the timer
+        if (dis.lastPosition && 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);
+};
+
+/**
+ * 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. 
+	this.getCurrentPosition(successCallback, errorCallback, options);
+	var frequency = 10000;
+        if (typeof options == 'object' && options.frequency)
+            frequency = options.frequency;
+	var that = this;
+	return setInterval(function() {
+		that.getCurrentPosition(successCallback, errorCallback, options);
+	}, frequency);
+};
+
+
+/**
+ * Clears the specified position watch.
+ * @param {String} watchId The ID of the watch returned from #watchPosition.
+ */
+Geolocation.prototype.clearWatch = function(watchId) {
+	clearInterval(watchId);
+};
+
+Geolocation.prototype.start = function(options) {
+	var so = device.getServiceObject("Service.Location", "ILocation");
+	
+	//construct the criteria for our location request
+	var updateOptions = new Object();
+	// Specify that location information need not be guaranteed. This helps in
+	// that the widget doesn't need to wait for that information possibly indefinitely.
+	updateOptions.PartialUpdates = true;
+	
+	//default 15 seconds
+	if (typeof(options) == 'object' && options.timeout) 
+		//options.timeout in in ms, updateOptions.UpdateTimeout in microsecs
+		updateOptions.UpdateTimeOut = options.timeout * 1000;
+
+	//default 1 second
+	if (typeof(options) == 'object' && options.interval) 
+		//options.timeout in in ms, updateOptions.UpdateTimeout in microsecs
+		updateOptions.UpdateInterval = options.interval * 1000;
+	
+	// Initialize the criteria for the GetLocation call
+	var trackCriteria = new Object();
+	// could use "BasicLocationInformation" or "GenericLocationInfo"
+	trackCriteria.LocationInformationClass = "GenericLocationInfo";
+	trackCriteria.Updateoptions = updateOptions;
+	
+	var dis = this;
+	so.ILocation.Trace(trackCriteria, function(transId, eventCode, result) {
+		var retVal = result.ReturnValue;
+
+		if (result.ErrorCode != 0 || isNaN(retVal.Latitude))
+			return;
+		
+		// heading options: retVal.TrueCourse, retVal.MagneticHeading, retVal.Heading, retVal.MagneticCourse
+		// but retVal.Heading was the only field being returned with data on the test device (Nokia 5800)
+		// WRT does not provide accuracy
+		var newCoords = new Coordinates(retVal.Latitude, retVal.Longitude, retVal.Altitude, null, retVal.Heading, retVal.HorizontalSpeed);
+		var positionObj = { coords: newCoords, timestamp: (new Date()).getTime() };
+
+		dis.lastPosition = positionObj;
+	});
+	
+};
+
+
+if (typeof navigator.geolocation == "undefined") navigator.geolocation = new Geolocation();
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/map.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,20 @@
+/**
+ * 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 err = "map api is unimplemented on symbian.wrt";
+	debug.log(err);
+	return { name: "MapError", message: err };
+
+};
+
+if (typeof navigator.map == "undefined") navigator.map = new Map();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/network.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,36 @@
+function Network() {
+    /**
+     * The last known Network status.
+     */
+	this.lastReachability = null;
+};
+
+Network.prototype.isReachable = function(hostName, successCallback, options) {
+	var req = new XMLHttpRequest();  
+   	req.open('GET', hostName, true);  
+   	req.onreadystatechange = function (aEvt) {  
+     	if (req.readyState == 4) {  
+        	if(req.status == 200)  
+        		successCallback(NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK);
+         	else  
+          		successCallback(NetworkStatus.NOT_REACHABLE);
+ 		}  
+  	};  
+  	req.send(null);
+
+};
+
+/**
+ * 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();
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/notification.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,74 @@
+
+/**
+ * This class provides access to notifications on the device.
+ */
+function Notification() {
+	
+}
+
+Notification.prototype.vibrate = function(mills)
+{
+	
+	if (!Notification.getSysinfoObject())
+		Notification.embedSysinfoObject();
+	
+	this.sysinfo = Notification.getSysinfoObject();
+	this.sysinfo.startvibra(mills, 100);
+};
+
+//TODO: this is not beeping
+Notification.prototype.beep = function(count, volume)
+{
+	if (!Notification.getSysinfoObject())
+		Notification.embedSysinfoObject();
+	
+	this.sysinfo = Notification.getSysinfoObject();	
+	this.sysinfo.beep(220,2000);
+};
+
+
+/**
+ * Open a native alert dialog, with a customizable title and button text.
+ * @param {String} message Message to print in the body of the alert
+ * @param {String} [title="Alert"] Title of the alert dialog (default: Alert)
+ * @param {String} [buttonLabel="OK"] Label of the close button (default: OK)
+ */
+Notification.prototype.alert = function(message, title, buttonLabel) {
+    // Default is to use a browser alert; this will use "index.html" as the title though
+    alert(message);
+};
+
+/**
+ * Start spinning the activity indicator on the statusbar
+ */
+Notification.prototype.activityStart = function() {
+};
+
+/**
+ * Stop spinning the activity indicator on the statusbar, if it's currently spinning
+ */
+Notification.prototype.activityStop = function() {
+};
+
+/**
+ * Causes the device to blink a status LED.
+ * @param {Integer} count The number of blinks.
+ * @param {String} colour The colour of the light.
+ */
+Notification.prototype.blink = function(count, colour) {
+	
+};
+
+Notification.embedSysinfoObject = function() {
+	var el = document.createElement("embed");
+	el.setAttribute("type", "application/x-systeminfo-widget");
+	el.setAttribute("hidden", "yes");
+	document.getElementsByTagName("body")[0].appendChild(el);
+	return;
+};
+
+Notification.getSysinfoObject = function() {
+	return document.embeds[0];
+};
+
+if (typeof navigator.notification == "undefined") navigator.notification = new Notification();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/orientation.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,182 @@
+/**
+ * This class provides access to the device orientation.
+ * @constructor
+ */
+function Orientation() {
+	/**
+	 * The current orientation, or null if the orientation hasn't changed yet.
+	 */
+	this.currentOrientation = null;
+}
+
+/**
+ * Set the current orientation of the phone.  This is called from the device automatically.
+ * 
+ * When the orientation is changed, the DOMEvent \c orientationChanged is dispatched against
+ * the document element.  The event has the property \c orientation which can be used to retrieve
+ * the device's current orientation, in addition to the \c Orientation.currentOrientation class property.
+ *
+ * @param {Number} orientation The orientation to be set
+ */
+Orientation.prototype.setOrientation = function(orientation) {
+		if (orientation == this.currentOrientation) 
+			return;
+		var old = this.currentOrientation;
+
+		this.currentOrientation = orientation;
+		var e = document.createEvent('Events');
+		e.initEvent('orientationChanged', 'false', 'false');
+		e.orientation = orientation;
+		e.oldOrientation = old;
+		document.dispatchEvent(e);
+};
+
+/**
+ * Asynchronously aquires the current orientation.
+ * @param {Function} successCallback The function to call when the orientation
+ * is known.
+ * @param {Function} errorCallback The function to call when there is an error 
+ * getting the orientation.
+ */
+Orientation.prototype.getCurrentOrientation = function(successCallback, errorCallback) {
+	// If the orientation is available then call success
+	// If the orientation is not available then call error
+	try {
+		if (!this.serviceObj) 
+			this.serviceObj = this.getServiceObj();
+		
+		if (this.serviceObj == null) 
+			errorCallback({
+				name: "DeviceErr",
+				message: "Could not initialize service object"
+			});
+		
+		//get the sensor channel
+		var SensorParams = {
+			SearchCriterion: "Orientation"
+		};
+		var returnvalue = this.serviceObj.ISensor.FindSensorChannel(SensorParams);
+		
+		var error = returnvalue["ErrorCode"];
+		var errmsg = returnvalue["ErrorMessage"];
+		if (!(error == 0 || error == 1012)) {
+			var ex = {
+				name: "Unable to find Sensor Channel: " + error,
+				message: errmsg
+			};
+			errorCallback(ex);
+		}
+		var channelInfoMap = returnvalue["ReturnValue"][0];
+		var criteria = {
+			ChannelInfoMap: channelInfoMap,
+			ListeningType: "ChannelData"
+		};
+		
+		if (typeof(successCallback) != 'function') 
+			successCallback = function(){
+			};
+		if (typeof(errorCallback) != 'function') 
+			errorCallback = function(){
+			};
+		
+		this.success_callback = successCallback;
+		this.error_callback = errorCallback;
+		
+		//create a closure to persist this instance of orientation object into the RegisterForNofication callback
+		var obj = this;
+		
+		// TODO: this call crashes WRT, but there is no other way to read the orientation sensor
+		// http://discussion.forum.nokia.com/forum/showthread.php?t=182151&highlight=memory+leak
+		this.serviceObj.ISensor.RegisterForNotification(criteria, function(transId, eventCode, result){
+			var criteria = {
+				TransactionID: transId
+			};
+			try {
+				//var orientation = result.ReturnValue.DeviceOrientation;
+				obj.serviceObj.ISensor.Cancel(criteria);
+				
+				var orientation = null;
+				switch (result.ReturnValue.DeviceOrientation) {
+					case "DisplayUpwards": orientation = DisplayOrientation.FACE_UP; break;
+					case "DisplayDownwards": orientation = DisplayOrientation.FACE_DOWN; break;
+					case "DisplayUp": orientation = DisplayOrientation.PORTRAIT; break;
+					case "DisplayDown": orientation = DisplayOrientation.REVERSE_PORTRAIT; break;
+					case "DisplayRightUp": orientation = DisplayOrientation.LANDSCAPE_RIGHT_UP; break;
+					case "DisplayLeftUp": orientation = DisplayOrientation.LANDSCAPE_LEFT_UP; break;
+					
+				}
+				
+				obj.setOrientation(orientation);
+				
+				obj.success_callback(orientation);
+				
+			} 
+			catch (ex) {
+				obj.serviceObj.ISensor.Cancel(criteria);
+				obj.error_callback(ex);
+			}
+			
+		});
+	} catch (ex) {
+		errorCallback({ name: "OrientationError", message: ex.name + ": " + ex.message });
+	}
+};
+
+/**
+ * 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 frequency = (options != undefined)? options.frequency : 1000;
+	return setInterval(function() {
+		navigator.orientation.getCurrentOrientation(successCallback, errorCallback);
+	}, frequency);
+};
+
+/**
+ * Clears the specified orientation watch.
+ * @param {String} watchId The ID of the watch returned from #watchOrientation.
+ */
+Orientation.prototype.clearWatch = function(watchId) {
+	clearInterval(watchId);
+};
+
+//gets the Acceleration Service Object from WRT
+Orientation.prototype.getServiceObj = function() {
+	var so;
+	
+    try {
+        so = device.getServiceObject("Service.Sensor", "ISensor");
+    } catch (ex) {
+		throw {
+			name: "DeviceError",
+			message: ex.name + ": " + ex.message
+		};
+    }		
+	return so;
+};
+
+
+/**
+ * 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();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/phonegap.js.base	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,42 @@
+if (typeof(DeviceInfo) != 'object')
+    DeviceInfo = {};
+
+/**
+ * This represents the PhoneGap API itself, and provides a global namespace for accessing
+ * information about the state of PhoneGap.
+ * @class
+ */
+PhoneGap = {
+    queue: {
+        ready: true,
+        commands: [],
+        timer: null
+    },
+    _constructors: []
+};
+
+/**
+ * Boolean flag indicating if the PhoneGap API is available and initialized.
+ */
+PhoneGap.available = DeviceInfo.uuid != undefined;
+
+/**
+ * Execute a PhoneGap command in a queued fashion, to ensure commands do not
+ * execute with any race conditions, and only run when PhoneGap is ready to
+ * recieve them.
+ * @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
+ */
+PhoneGap.exec = function() {
+    PhoneGap.queue.commands.push(arguments);
+    if (PhoneGap.queue.timer == null)
+        PhoneGap.queue.timer = setInterval(PhoneGap.run_command, 10);
+};
+/**
+ * Internal function used to dispatch the request to PhoneGap.  This needs to be implemented per-platform to
+ * ensure that methods are called on the phone in a way appropriate for that device.
+ * @private
+ */
+PhoneGap.run_command = function() {
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/position.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,72 @@
+/**
+ * This class contains position information.
+ * @param {Object} lat
+ * @param {Object} lng
+ * @param {Object} acc
+ * @param {Object} alt
+ * @param {Object} altacc
+ * @param {Object} head
+ * @param {Object} vel
+ * @constructor
+ */
+function Position(coords, timestamp) {
+	this.coords = coords;
+        this.timestamp = new Date().getTime();
+}
+
+function Coordinates(lat, lng, alt, acc, head, vel) {
+	/**
+	 * 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;
+}
+
+/**
+ * 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;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/sms.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,60 @@
+/**
+ * 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 {
+		if (!this.serviceObj)
+			this.serviceObj = this.getServiceObj();
+			
+	    // Setup input params using dot syntax
+	    var criteria = new Object();
+	    criteria.MessageType = 'SMS';
+	    criteria.To = number;
+	    criteria.BodyText = message;
+
+      	var result = this.serviceObj.IMessaging.Send(criteria);
+    	if (result.ErrorCode != 0 && result.ErrorCode != "0")
+		{
+			var exception = { name: "SMSError", message: result.ErrorMessage };
+			throw exception;
+		} else {
+			successCallback.call();
+		}
+    }
+  	catch(ex)
+  	{
+		errorCallback.call({ name: "SmsError", message: ex.name + ": " + ex.message });
+  	}
+
+};
+
+
+//gets the Sms Service Object from WRT
+Sms.prototype.getServiceObj = function() {
+	var so;
+	
+    try {
+        so = device.getServiceObject("Service.Messaging", "IMessaging");
+    } catch (ex) {
+		throw {
+			name: "SmsError",
+			message: "Failed to load sms service (" + ex.name + ": " + ex.message + ")"
+		};
+    }		
+	return so;
+};
+
+if (typeof navigator.sms == "undefined") navigator.sms = new Sms();
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/storage.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,80 @@
+/**
+ * @author ryan
+ */
+
+function Storage() {
+	this.available = true;
+	this.serialized = null;
+	this.items = null;
+	
+	if (!window.widget) {
+		this.available = false;
+		return;
+	}
+	var pref = window.widget.preferenceForKey(Storage.PREFERENCE_KEY);
+	
+	//storage not yet created
+	if (pref == "undefined" || pref == undefined) {
+		this.length = 0;
+		this.serialized = "({})";
+		this.items = {};
+		window.widget.setPreferenceForKey(this.serialized, Storage.PREFERENCE_KEY);
+	} else {
+		this.serialized = pref;'({"store_test": { "key": "store_test", "data": "asdfasdfs" },})';
+		this.items = eval(this.serialized);
+	}
+}
+
+Storage.PREFERENCE_KEY = "phonegap_storage_pref_key";
+
+Storage.prototype.index = function (key) {
+	
+};
+
+Storage.prototype.getItem = function (key) {
+	try {
+		return this.items[key].data;
+	} catch (ex) {
+		return null;
+	}
+};
+
+Storage.prototype.setItem = function (key, data) {
+
+	this.items[key] = {
+		"key": key,
+		"data": data
+	};
+	
+	this.serialize();
+};
+
+Storage.prototype.removeItem = function (key) {
+
+	if (this.items[key]) {
+		this.items[key] = undefined;
+	}
+	this.serialize();
+};
+
+Storage.prototype.clear = function () {
+	this.serialized = "({})";
+	this.items = {};
+	this.serialize();
+};
+
+Storage.prototype.serialize = function() {
+	var json = "";
+	
+	for (key in this.items) {
+		var item = this.items[key];
+		if (typeof item != "undefined") {
+			json += "\"" + item.key + "\": { \"key\": \"" + item.key + "\", \"data\": \"" + item.data + "\" }, ";
+		}
+	}
+	this.serialized = "({" + json + "})";
+
+	window.widget.setPreferenceForKey( this.serialized, Storage.PREFERENCE_KEY);
+};
+
+if (typeof navigator.storage == "undefined" ) navigator.storage = new Storage();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/js/telephony.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,17 @@
+/**
+ * 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) {
+	widget.openURL('tel:+' + number);
+};
+
+if (typeof navigator.telephony == "undefined") navigator.telephony = new Telephony();
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/LICENSE	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2009 Rob Ellis, Brock Whitten, Brian LeRoux
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/README.md	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,37 @@
+## Mobile Spec Suite ##
+
+These specs are designed to run inside the mobile device that implements it - _it will fail in the DESKTOP browser_.
+
+These set of tests is designed to be used with PhoneGap. You should initialize a fresh PhoneGap repository (git clone 
+git://github.com/phonegap/phonegap.git) and then toss these files into the www or assets folder, replacing the
+contents. Make sure you include phonegap.js - each platform directory in the PhoneGap repository has a build or make
+file, which will create a phonegap.js file for you (and in most cases copy it into the www or assets folder).
+
+The goal is to test mobile device functionality inside a mobile browser.
+Where possible, the PhoneGap API lines up with HTML 5 spec. Maybe down
+the road we could use this spec for parts of HTML 5, too :)
+
+
+LICENSE
+---
+
+_Copyright (c) 2009 Rob Ellis, Brian LeRoux, Brock Whitten, Nitobi Software_
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/index.html	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,61 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+  <head>
+  	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
+  	<title>QUnit results for PhoneGap JS API</title>
+    <!-- You need the following two files in the same directory for QUnit to run. -->
+    <link rel="stylesheet" type="text/css" href="qunit.css" />
+	<script type="text/javascript" src="qunit.js"></script>
+	<!-- Make sure you copy in phonegap.js from the PhoneGap repository (that's what you're testing, right?) -->
+    <script type="text/javascript" src="phonegap.js"></script>
+	<script type="text/javascript">
+		function Tests() {
+			this.TEST_TIMEOUT = 15000;
+		};
+		var tests = new Tests();
+		
+		// Runs each function in Tests that contains 'Tests' in the name.
+		function run() {
+			for (var t in tests) {
+				if (t.indexOf('Tests') > -1) {
+					tests[t]();
+				}
+			}
+			alert('Test run complete.');
+		}
+		function loadTests() {
+			// Shitty hack here: check for BlackBerry browser and timeout the test runner, or simply attach test runner
+			// to the deviceready event if we have any other platform that can handle events.
+			if (navigator.userAgent.indexOf('Browzr') > -1) {
+				setTimeout(run, 250);
+			} else {
+				var state = document.readyState;
+				if (state == "loaded" || state == "complete") {
+					run();
+				} else {
+					document.addEventListener('deviceready', run, false);
+				}
+			}
+		}
+	</script>
+	<script type="text/javascript" src="tests/accelerometer.tests.js"></script>
+	<script type="text/javascript" src="tests/contacts.tests.js"></script>
+	<script type="text/javascript" src="tests/camera.tests.js"></script>
+	<script type="text/javascript" src="tests/device.tests.js"></script>
+	<script type="text/javascript" src="tests/file.tests.js"></script>
+	<script type="text/javascript" src="tests/geolocation.tests.js"></script>
+	<script type="text/javascript" src="tests/map.tests.js"></script>
+	<script type="text/javascript" src="tests/network.tests.js"></script>
+	<script type="text/javascript" src="tests/notification.tests.js"></script>
+	<script type="text/javascript" src="tests/orientation.tests.js"></script>
+	<script type="text/javascript" src="tests/sms.tests.js"></script>
+	<script type="text/javascript" src="tests/telephony.tests.js"></script>
+	<script type="text/javascript" src="tests/storage.tests.js"></script>
+  </head>
+	<body onload="loadTests();">
+		<h1 id="qunit-header">Mobile Spec (PhoneGap) Tests</h1>
+		<h2 id="qunit-banner"></h2>
+		<h2 id="qunit-userAgent"></h2>
+		<ol id="qunit-tests"></ol>
+	</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/qunit.css	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,119 @@
+
+ol#qunit-tests {
+	font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
+	margin:0;
+	padding:0;
+	list-style-position:inside;
+
+	font-size: smaller;
+}
+ol#qunit-tests li{
+	padding:0.4em 0.5em 0.4em 2.5em;
+	border-bottom:1px solid #fff;
+	font-size:small;
+	list-style-position:inside;
+}
+ol#qunit-tests li ol{
+	box-shadow: inset 0px 2px 13px #999;
+	-moz-box-shadow: inset 0px 2px 13px #999;
+	-webkit-box-shadow: inset 0px 2px 13px #999;
+	margin-top:0.5em;
+	margin-left:0;
+	padding:0.5em;
+	background-color:#fff;
+	border-radius:15px;
+	-moz-border-radius: 15px;
+	-webkit-border-radius: 15px;
+}
+ol#qunit-tests li li{
+	border-bottom:none;
+	margin:0.5em;
+	background-color:#fff;
+	list-style-position: inside;
+	padding:0.4em 0.5em 0.4em 0.5em;
+}
+
+ol#qunit-tests li li.pass{
+	border-left:26px solid #C6E746;
+	background-color:#fff;
+	color:#5E740B;
+	}
+ol#qunit-tests li li.fail{
+	border-left:26px solid #EE5757;
+	background-color:#fff;
+	color:#710909;
+}
+ol#qunit-tests li.pass{
+	background-color:#D2E0E6;
+	color:#528CE0;
+}
+ol#qunit-tests li.fail{
+	background-color:#EE5757;
+	color:#000;
+}
+ol#qunit-tests li strong {
+	cursor:pointer;
+}
+h1#qunit-header{
+	background-color:#0d3349;
+	margin:0;
+	padding:0.5em 0 0.5em 1em;
+	color:#fff;
+	font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
+	border-top-right-radius:15px;
+	border-top-left-radius:15px;
+	-moz-border-radius-topright:15px;
+	-moz-border-radius-topleft:15px;
+	-webkit-border-top-right-radius:15px;
+	-webkit-border-top-left-radius:15px;
+	text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px;
+}
+h2#qunit-banner{
+	font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
+	height:5px;
+	margin:0;
+	padding:0;
+}
+h2#qunit-banner.qunit-pass{
+	background-color:#C6E746;
+}
+h2#qunit-banner.qunit-fail, #qunit-testrunner-toolbar {
+	background-color:#EE5757;
+}
+#qunit-testrunner-toolbar {
+	font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
+	padding:0;
+	/*width:80%;*/
+	padding:0em 0 0.5em 2em;
+	font-size: small;
+}
+h2#qunit-userAgent {
+	font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
+	background-color:#2b81af;
+	margin:0;
+	padding:0;
+	color:#fff;
+	font-size: small;
+	padding:0.5em 0 0.5em 2.5em;
+	text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+}
+p#qunit-testresult{
+	font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
+	margin:0;
+	font-size: small;
+	color:#2b81af;
+	border-bottom-right-radius:15px;
+	border-bottom-left-radius:15px;
+	-moz-border-radius-bottomright:15px;
+	-moz-border-radius-bottomleft:15px;
+	-webkit-border-bottom-right-radius:15px;
+	-webkit-border-bottom-left-radius:15px;
+	background-color:#D2E0E6;
+	padding:0.5em 0.5em 0.5em 2.5em;
+}
+strong b.fail{
+	color:#710909;
+	}
+strong b.pass{
+	color:#5E740B;
+	}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/qunit.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,1042 @@
+/*
+ * QUnit - A JavaScript Unit Testing Framework
+ * 
+ * http://docs.jquery.com/QUnit
+ *
+ * Copyright (c) 2009 John Resig, Jörn Zaefferer
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ */
+
+(function(window) {
+
+var QUnit = {
+
+	// Initialize the configuration options
+	init: function() {
+		config = {
+			stats: { all: 0, bad: 0 },
+			moduleStats: { all: 0, bad: 0 },
+			started: +new Date,
+			blocking: false,
+			autorun: false,
+			assertions: [],
+			filters: [],
+			queue: []
+		};
+
+		var tests = id("qunit-tests"),
+			banner = id("qunit-banner"),
+			result = id("qunit-testresult");
+
+		if ( tests ) {
+			tests.innerHTML = "";
+		}
+
+		if ( banner ) {
+			banner.className = "";
+		}
+
+		if ( result ) {
+			result.parentNode.removeChild( result );
+		}
+	},
+	
+	// call on start of module test to prepend name to all tests
+	module: function(name, testEnvironment) {
+		config.currentModule = name;
+
+		synchronize(function() {
+			if ( config.currentModule ) {
+				QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
+			}
+
+			config.currentModule = name;
+			config.moduleTestEnvironment = testEnvironment;
+			config.moduleStats = { all: 0, bad: 0 };
+
+			QUnit.moduleStart( name, testEnvironment );
+		});
+	},
+
+	asyncTest: function(testName, expected, callback) {
+		if ( arguments.length === 2 ) {
+			callback = expected;
+			expected = 0;
+		}
+
+		QUnit.test(testName, expected, callback, true);
+	},
+	
+	test: function(testName, expected, callback, async) {
+		var name = testName, testEnvironment, testEnvironmentArg;
+
+		if ( arguments.length === 2 ) {
+			callback = expected;
+			expected = null;
+		}
+		// is 2nd argument a testEnvironment?
+		if ( expected && typeof expected === 'object') {
+			testEnvironmentArg =  expected;
+			expected = null;
+		}
+
+		if ( config.currentModule ) {
+			name = config.currentModule + " module: " + name;
+		}
+
+		if ( !validTest(name) ) {
+			return;
+		}
+
+		synchronize(function() {
+			QUnit.testStart( testName );
+
+			testEnvironment = extend({
+				setup: function() {},
+				teardown: function() {}
+			}, config.moduleTestEnvironment);
+			if (testEnvironmentArg) {
+				extend(testEnvironment,testEnvironmentArg);
+			}
+
+			// allow utility functions to access the current test environment
+			QUnit.current_testEnvironment = testEnvironment;
+			
+			config.assertions = [];
+			config.expected = expected;
+
+			try {
+				if ( !config.pollution ) {
+					saveGlobal();
+				}
+
+				testEnvironment.setup.call(testEnvironment);
+			} catch(e) {
+				QUnit.ok( false, "Setup failed on " + name + ": " + e.message );
+			}
+
+			if ( async ) {
+				QUnit.stop();
+			}
+
+			try {
+				callback.call(testEnvironment);
+			} catch(e) {
+				fail("Test " + name + " died, exception and test follows", e, callback);
+				QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message );
+				// else next test will carry the responsibility
+				saveGlobal();
+
+				// Restart the tests if they're blocking
+				if ( config.blocking ) {
+					start();
+				}
+			}
+		});
+
+		synchronize(function() {
+			try {
+				checkPollution();
+				testEnvironment.teardown.call(testEnvironment);
+			} catch(e) {
+				QUnit.ok( false, "Teardown failed on " + name + ": " + e.message );
+			}
+
+			try {
+				QUnit.reset();
+			} catch(e) {
+				fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset);
+			}
+
+			if ( config.expected && config.expected != config.assertions.length ) {
+				QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" );
+			}
+
+			var good = 0, bad = 0,
+				tests = id("qunit-tests");
+
+			config.stats.all += config.assertions.length;
+			config.moduleStats.all += config.assertions.length;
+
+			if ( tests ) {
+				var ol  = document.createElement("ol");
+				ol.style.display = "none";
+
+				for ( var i = 0; i < config.assertions.length; i++ ) {
+					var assertion = config.assertions[i];
+
+					var li = document.createElement("li");
+					li.className = assertion.result ? "pass" : "fail";
+					li.innerHTML = assertion.message || "(no message)";
+					ol.appendChild( li );
+
+					if ( assertion.result ) {
+						good++;
+					} else {
+						bad++;
+						config.stats.bad++;
+						config.moduleStats.bad++;
+					}
+				}
+
+				var b = document.createElement("strong");
+				b.innerHTML = name + " <b style='color:black;'>(<b class='fail'>" + bad + "</b>, <b class='pass'>" + good + "</b>, " + config.assertions.length + ")</b>";
+				
+				addEvent(b, "click", function() {
+					var next = b.nextSibling, display = next.style.display;
+					next.style.display = display === "none" ? "block" : "none";
+				});
+				
+				addEvent(b, "dblclick", function(e) {
+					var target = e && e.target ? e.target : window.event.srcElement;
+					if ( target.nodeName.toLowerCase() === "strong" ) {
+						var text = "", node = target.firstChild;
+
+						while ( node.nodeType === 3 ) {
+							text += node.nodeValue;
+							node = node.nextSibling;
+						}
+
+						text = text.replace(/(^\s*|\s*$)/g, "");
+
+						if ( window.location ) {
+							window.location.href = window.location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(text);
+						}
+					}
+				});
+
+				var li = document.createElement("li");
+				li.className = bad ? "fail" : "pass";
+				li.appendChild( b );
+				li.appendChild( ol );
+				tests.appendChild( li );
+
+				if ( bad ) {
+					var toolbar = id("qunit-testrunner-toolbar");
+					if ( toolbar ) {
+						toolbar.style.display = "block";
+						id("qunit-filter-pass").disabled = null;
+						id("qunit-filter-missing").disabled = null;
+					}
+				}
+
+			} else {
+				for ( var i = 0; i < config.assertions.length; i++ ) {
+					if ( !config.assertions[i].result ) {
+						bad++;
+						config.stats.bad++;
+						config.moduleStats.bad++;
+					}
+				}
+			}
+
+			QUnit.testDone( testName, bad, config.assertions.length );
+
+			if ( !window.setTimeout && !config.queue.length ) {
+				done();
+			}
+		});
+
+		if ( window.setTimeout && !config.doneTimer ) {
+			config.doneTimer = window.setTimeout(function(){
+				if ( !config.queue.length ) {
+					done();
+				} else {
+					synchronize( done );
+				}
+			}, 13);
+		}
+	},
+	
+	/**
+	 * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
+	 */
+	expect: function(asserts) {
+		config.expected = asserts;
+	},
+
+	/**
+	 * Asserts true.
+	 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
+	 */
+	ok: function(a, msg) {
+		QUnit.log(a, msg);
+
+		config.assertions.push({
+			result: !!a,
+			message: msg
+		});
+	},
+
+	/**
+	 * Checks that the first two arguments are equal, with an optional message.
+	 * Prints out both actual and expected values.
+	 *
+	 * Prefered to ok( actual == expected, message )
+	 *
+	 * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
+	 *
+	 * @param Object actual
+	 * @param Object expected
+	 * @param String message (optional)
+	 */
+	equal: function(actual, expected, message) {
+		push(expected == actual, actual, expected, message);
+	},
+
+	notEqual: function(actual, expected, message) {
+		push(expected != actual, actual, expected, message);
+	},
+	
+	deepEqual: function(a, b, message) {
+		push(QUnit.equiv(a, b), a, b, message);
+	},
+
+	notDeepEqual: function(a, b, message) {
+		push(!QUnit.equiv(a, b), a, b, message);
+	},
+
+	strictEqual: function(actual, expected, message) {
+		push(expected === actual, actual, expected, message);
+	},
+
+	notStrictEqual: function(actual, expected, message) {
+		push(expected !== actual, actual, expected, message);
+	},
+	
+	start: function() {
+		// A slight delay, to avoid any current callbacks
+		if ( window.setTimeout ) {
+			window.setTimeout(function() {
+				if ( config.timeout ) {
+					clearTimeout(config.timeout);
+				}
+
+				config.blocking = false;
+				process();
+			}, 13);
+		} else {
+			config.blocking = false;
+			process();
+		}
+	},
+	
+	stop: function(timeout) {
+		config.blocking = true;
+
+		if ( timeout && window.setTimeout ) {
+			config.timeout = window.setTimeout(function() {
+				QUnit.ok( false, "Test timed out" );
+				QUnit.start();
+			}, timeout);
+		}
+	},
+	
+	/**
+	 * Resets the test setup. Useful for tests that modify the DOM.
+	 */
+	reset: function() {
+		if ( window.jQuery ) {
+			jQuery("#main").html( config.fixture );
+			jQuery.event.global = {};
+			jQuery.ajaxSettings = extend({}, config.ajaxSettings);
+		}
+	},
+	
+	/**
+	 * Trigger an event on an element.
+	 *
+	 * @example triggerEvent( document.body, "click" );
+	 *
+	 * @param DOMElement elem
+	 * @param String type
+	 */
+	triggerEvent: function( elem, type, event ) {
+		if ( document.createEvent ) {
+			event = document.createEvent("MouseEvents");
+			event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
+				0, 0, 0, 0, 0, false, false, false, false, 0, null);
+			elem.dispatchEvent( event );
+
+		} else if ( elem.fireEvent ) {
+			elem.fireEvent("on"+type);
+		}
+	},
+	
+	// Safe object type checking
+	is: function( type, obj ) {
+		return Object.prototype.toString.call( obj ) === "[object "+ type +"]";
+	},
+	
+	// Logging callbacks
+	done: function(failures, total) {},
+	log: function(result, message) {},
+	testStart: function(name) {},
+	testDone: function(name, failures, total) {},
+	moduleStart: function(name, testEnvironment) {},
+	moduleDone: function(name, failures, total) {}
+};
+
+// Backwards compatibility, deprecated
+QUnit.equals = QUnit.equal;
+QUnit.same = QUnit.deepEqual;
+
+// Maintain internal state
+var config = {
+	// The queue of tests to run
+	queue: [],
+
+	// block until document ready
+	blocking: true
+};
+
+// Load paramaters
+(function() {
+	var location = window.location || { search: "", protocol: "file:" },
+		GETParams = location.search.slice(1).split('&');
+
+	for ( var i = 0; i < GETParams.length; i++ ) {
+		GETParams[i] = decodeURIComponent( GETParams[i] );
+		if ( GETParams[i] === "noglobals" ) {
+			GETParams.splice( i, 1 );
+			i--;
+			config.noglobals = true;
+		} else if ( GETParams[i].search('=') > -1 ) {
+			GETParams.splice( i, 1 );
+			i--;
+		}
+	}
+	
+	// restrict modules/tests by get parameters
+	config.filters = GETParams;
+	
+	// Figure out if we're running the tests from a server or not
+	QUnit.isLocal = !!(location.protocol === 'file:');
+})();
+
+// Expose the API as global variables, unless an 'exports'
+// object exists, in that case we assume we're in CommonJS
+if ( typeof exports === "undefined" || typeof require === "undefined" ) {
+	extend(window, QUnit);
+	window.QUnit = QUnit;
+} else {
+	extend(exports, QUnit);
+	exports.QUnit = QUnit;
+}
+
+if ( typeof document === "undefined" || document.readyState === "complete" ) {
+	config.autorun = true;
+}
+
+addEvent(window, "load", function() {
+	// Initialize the config, saving the execution queue
+	var oldconfig = extend({}, config);
+	QUnit.init();
+	extend(config, oldconfig);
+
+	config.blocking = false;
+
+	var userAgent = id("qunit-userAgent");
+	if ( userAgent ) {
+		userAgent.innerHTML = navigator.userAgent;
+	}
+	
+	var toolbar = id("qunit-testrunner-toolbar");
+	if ( toolbar ) {
+		toolbar.style.display = "none";
+		
+		var filter = document.createElement("input");
+		filter.type = "checkbox";
+		filter.id = "qunit-filter-pass";
+		filter.disabled = true;
+		addEvent( filter, "click", function() {
+			var li = document.getElementsByTagName("li");
+			for ( var i = 0; i < li.length; i++ ) {
+				if ( li[i].className.indexOf("pass") > -1 ) {
+					li[i].style.display = filter.checked ? "none" : "";
+				}
+			}
+		});
+		toolbar.appendChild( filter );
+
+		var label = document.createElement("label");
+		label.setAttribute("for", "qunit-filter-pass");
+		label.innerHTML = "Hide passed tests";
+		toolbar.appendChild( label );
+
+		var missing = document.createElement("input");
+		missing.type = "checkbox";
+		missing.id = "qunit-filter-missing";
+		missing.disabled = true;
+		addEvent( missing, "click", function() {
+			var li = document.getElementsByTagName("li");
+			for ( var i = 0; i < li.length; i++ ) {
+				if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) {
+					li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block";
+				}
+			}
+		});
+		toolbar.appendChild( missing );
+
+		label = document.createElement("label");
+		label.setAttribute("for", "qunit-filter-missing");
+		label.innerHTML = "Hide missing tests (untested code is broken code)";
+		toolbar.appendChild( label );
+	}
+
+	var main = id('main');
+	if ( main ) {
+		config.fixture = main.innerHTML;
+	}
+
+	if ( window.jQuery ) {
+		config.ajaxSettings = window.jQuery.ajaxSettings;
+	}
+
+	QUnit.start();
+});
+
+function done() {
+	if ( config.doneTimer && window.clearTimeout ) {
+		window.clearTimeout( config.doneTimer );
+		config.doneTimer = null;
+	}
+
+	if ( config.queue.length ) {
+		config.doneTimer = window.setTimeout(function(){
+			if ( !config.queue.length ) {
+				done();
+			} else {
+				synchronize( done );
+			}
+		}, 13);
+
+		return;
+	}
+
+	config.autorun = true;
+
+	// Log the last module results
+	if ( config.currentModule ) {
+		QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
+	}
+
+	var banner = id("qunit-banner"),
+		tests = id("qunit-tests"),
+		html = ['Tests completed in ',
+		+new Date - config.started, ' milliseconds.<br/>',
+		'<span class="passed">', config.stats.all - config.stats.bad, '</span> tests of <span class="total">', config.stats.all, '</span> passed, <span class="failed">', config.stats.bad,'</span> failed.'].join('');
+
+	if ( banner ) {
+		banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
+	}
+
+	if ( tests ) {	
+		var result = id("qunit-testresult");
+
+		if ( !result ) {
+			result = document.createElement("p");
+			result.id = "qunit-testresult";
+			result.className = "result";
+			tests.parentNode.insertBefore( result, tests.nextSibling );
+		}
+
+		result.innerHTML = html;
+	}
+
+	QUnit.done( config.stats.bad, config.stats.all );
+}
+
+function validTest( name ) {
+	var i = config.filters.length,
+		run = false;
+
+	if ( !i ) {
+		return true;
+	}
+	
+	while ( i-- ) {
+		var filter = config.filters[i],
+			not = filter.charAt(0) == '!';
+
+		if ( not ) {
+			filter = filter.slice(1);
+		}
+
+		if ( name.indexOf(filter) !== -1 ) {
+			return !not;
+		}
+
+		if ( not ) {
+			run = true;
+		}
+	}
+
+	return run;
+}
+
+function push(result, actual, expected, message) {
+	message = message || (result ? "okay" : "failed");
+	QUnit.ok( result, result ? message + ": " + expected : message + ", expected: " + QUnit.jsDump.parse(expected) + " result: " + QUnit.jsDump.parse(actual) );
+}
+
+function synchronize( callback ) {
+	config.queue.push( callback );
+
+	if ( config.autorun && !config.blocking ) {
+		process();
+	}
+}
+
+function process() {
+	while ( config.queue.length && !config.blocking ) {
+		config.queue.shift()();
+	}
+}
+
+function saveGlobal() {
+	config.pollution = [];
+	
+	if ( config.noglobals ) {
+		for ( var key in window ) {
+			config.pollution.push( key );
+		}
+	}
+}
+
+function checkPollution( name ) {
+	var old = config.pollution;
+	saveGlobal();
+	
+	var newGlobals = diff( old, config.pollution );
+	if ( newGlobals.length > 0 ) {
+		ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
+		config.expected++;
+	}
+
+	var deletedGlobals = diff( config.pollution, old );
+	if ( deletedGlobals.length > 0 ) {
+		ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
+		config.expected++;
+	}
+}
+
+// returns a new Array with the elements that are in a but not in b
+function diff( a, b ) {
+	var result = a.slice();
+	for ( var i = 0; i < result.length; i++ ) {
+		for ( var j = 0; j < b.length; j++ ) {
+			if ( result[i] === b[j] ) {
+				result.splice(i, 1);
+				i--;
+				break;
+			}
+		}
+	}
+	return result;
+}
+
+function fail(message, exception, callback) {
+	if ( typeof console !== "undefined" && console.error && console.warn ) {
+		console.error(message);
+		console.error(exception);
+		console.warn(callback.toString());
+
+	} else if ( window.opera && opera.postError ) {
+		opera.postError(message, exception, callback.toString);
+	}
+}
+
+function extend(a, b) {
+	for ( var prop in b ) {
+		a[prop] = b[prop];
+	}
+
+	return a;
+}
+
+function addEvent(elem, type, fn) {
+	if ( elem.addEventListener ) {
+		elem.addEventListener( type, fn, false );
+	} else if ( elem.attachEvent ) {
+		elem.attachEvent( "on" + type, fn );
+	} else {
+		fn();
+	}
+}
+
+function id(name) {
+	return !!(typeof document !== "undefined" && document && document.getElementById) &&
+		document.getElementById( name );
+}
+
+// Test for equality any JavaScript type.
+// Discussions and reference: http://philrathe.com/articles/equiv
+// Test suites: http://philrathe.com/tests/equiv
+// Author: Philippe Rathé <prathe@gmail.com>
+QUnit.equiv = function () {
+
+    var innerEquiv; // the real equiv function
+    var callers = []; // stack to decide between skip/abort functions
+
+
+    // Determine what is o.
+    function hoozit(o) {
+        if (QUnit.is("String", o)) {
+            return "string";
+            
+        } else if (QUnit.is("Boolean", o)) {
+            return "boolean";
+
+        } else if (QUnit.is("Number", o)) {
+
+            if (isNaN(o)) {
+                return "nan";
+            } else {
+                return "number";
+            }
+
+        } else if (typeof o === "undefined") {
+            return "undefined";
+
+        // consider: typeof null === object
+        } else if (o === null) {
+            return "null";
+
+        // consider: typeof [] === object
+        } else if (QUnit.is( "Array", o)) {
+            return "array";
+        
+        // consider: typeof new Date() === object
+        } else if (QUnit.is( "Date", o)) {
+            return "date";
+
+        // consider: /./ instanceof Object;
+        //           /./ instanceof RegExp;
+        //          typeof /./ === "function"; // => false in IE and Opera,
+        //                                          true in FF and Safari
+        } else if (QUnit.is( "RegExp", o)) {
+            return "regexp";
+
+        } else if (typeof o === "object") {
+            return "object";
+
+        } else if (QUnit.is( "Function", o)) {
+            return "function";
+        } else {
+            return undefined;
+        }
+    }
+
+    // Call the o related callback with the given arguments.
+    function bindCallbacks(o, callbacks, args) {
+        var prop = hoozit(o);
+        if (prop) {
+            if (hoozit(callbacks[prop]) === "function") {
+                return callbacks[prop].apply(callbacks, args);
+            } else {
+                return callbacks[prop]; // or undefined
+            }
+        }
+    }
+    
+    var callbacks = function () {
+
+        // for string, boolean, number and null
+        function useStrictEquality(b, a) {
+            if (b instanceof a.constructor || a instanceof b.constructor) {
+                // to catch short annotaion VS 'new' annotation of a declaration
+                // e.g. var i = 1;
+                //      var j = new Number(1);
+                return a == b;
+            } else {
+                return a === b;
+            }
+        }
+
+        return {
+            "string": useStrictEquality,
+            "boolean": useStrictEquality,
+            "number": useStrictEquality,
+            "null": useStrictEquality,
+            "undefined": useStrictEquality,
+
+            "nan": function (b) {
+                return isNaN(b);
+            },
+
+            "date": function (b, a) {
+                return hoozit(b) === "date" && a.valueOf() === b.valueOf();
+            },
+
+            "regexp": function (b, a) {
+                return hoozit(b) === "regexp" &&
+                    a.source === b.source && // the regex itself
+                    a.global === b.global && // and its modifers (gmi) ...
+                    a.ignoreCase === b.ignoreCase &&
+                    a.multiline === b.multiline;
+            },
+
+            // - skip when the property is a method of an instance (OOP)
+            // - abort otherwise,
+            //   initial === would have catch identical references anyway
+            "function": function () {
+                var caller = callers[callers.length - 1];
+                return caller !== Object &&
+                        typeof caller !== "undefined";
+            },
+
+            "array": function (b, a) {
+                var i;
+                var len;
+
+                // b could be an object literal here
+                if ( ! (hoozit(b) === "array")) {
+                    return false;
+                }
+
+                len = a.length;
+                if (len !== b.length) { // safe and faster
+                    return false;
+                }
+                for (i = 0; i < len; i++) {
+                    if ( ! innerEquiv(a[i], b[i])) {
+                        return false;
+                    }
+                }
+                return true;
+            },
+
+            "object": function (b, a) {
+                var i;
+                var eq = true; // unless we can proove it
+                var aProperties = [], bProperties = []; // collection of strings
+
+                // comparing constructors is more strict than using instanceof
+                if ( a.constructor !== b.constructor) {
+                    return false;
+                }
+
+                // stack constructor before traversing properties
+                callers.push(a.constructor);
+
+                for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
+
+                    aProperties.push(i); // collect a's properties
+
+                    if ( ! innerEquiv(a[i], b[i])) {
+                        eq = false;
+                    }
+                }
+
+                callers.pop(); // unstack, we are done
+
+                for (i in b) {
+                    bProperties.push(i); // collect b's properties
+                }
+
+                // Ensures identical properties name
+                return eq && innerEquiv(aProperties.sort(), bProperties.sort());
+            }
+        };
+    }();
+
+    innerEquiv = function () { // can take multiple arguments
+        var args = Array.prototype.slice.apply(arguments);
+        if (args.length < 2) {
+            return true; // end transition
+        }
+
+        return (function (a, b) {
+            if (a === b) {
+                return true; // catch the most you can
+            } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || hoozit(a) !== hoozit(b)) {
+                return false; // don't lose time with error prone cases
+            } else {
+                return bindCallbacks(a, callbacks, [b, a]);
+            }
+
+        // apply transition with (1..n) arguments
+        })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
+    };
+
+    return innerEquiv;
+
+}();
+
+/**
+ * jsDump
+ * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
+ * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
+ * Date: 5/15/2008
+ * @projectDescription Advanced and extensible data dumping for Javascript.
+ * @version 1.0.0
+ * @author Ariel Flesler
+ * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
+ */
+QUnit.jsDump = (function() {
+	function quote( str ) {
+		return '"' + str.toString().replace(/"/g, '\\"') + '"';
+	};
+	function literal( o ) {
+		return o + '';	
+	};
+	function join( pre, arr, post ) {
+		var s = jsDump.separator(),
+			base = jsDump.indent(),
+			inner = jsDump.indent(1);
+		if ( arr.join )
+			arr = arr.join( ',' + s + inner );
+		if ( !arr )
+			return pre + post;
+		return [ pre, inner + arr, base + post ].join(s);
+	};
+	function array( arr ) {
+		var i = arr.length,	ret = Array(i);					
+		this.up();
+		while ( i-- )
+			ret[i] = this.parse( arr[i] );				
+		this.down();
+		return join( '[', ret, ']' );
+	};
+	
+	var reName = /^function (\w+)/;
+	
+	var jsDump = {
+		parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
+			var	parser = this.parsers[ type || this.typeOf(obj) ];
+			type = typeof parser;			
+			
+			return type == 'function' ? parser.call( this, obj ) :
+				   type == 'string' ? parser :
+				   this.parsers.error;
+		},
+		typeOf:function( obj ) {
+			var type;
+			if ( obj === null ) {
+				type = "null";
+			} else if (typeof obj === "undefined") {
+				type = "undefined";
+			} else if (QUnit.is("RegExp", obj)) {
+				type = "regexp";
+			} else if (QUnit.is("Date", obj)) {
+				type = "date";
+			} else if (QUnit.is("Function", obj)) {
+				type = "function";
+			} else if (QUnit.is("Array", obj)) {
+				type = "array";
+			} else if (QUnit.is("Window", obj) || QUnit.is("global", obj)) {
+				type = "window";
+			} else if (QUnit.is("HTMLDocument", obj)) {
+				type = "document";
+			} else if (QUnit.is("HTMLCollection", obj) || QUnit.is("NodeList", obj)) {
+				type = "nodelist";
+			} else if (/^\[object HTML/.test(Object.prototype.toString.call( obj ))) {
+				type = "node";
+			} else {
+				type = typeof obj;
+			}
+			return type;
+		},
+		separator:function() {
+			return this.multiline ?	this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
+		},
+		indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
+			if ( !this.multiline )
+				return '';
+			var chr = this.indentChar;
+			if ( this.HTML )
+				chr = chr.replace(/\t/g,'   ').replace(/ /g,'&nbsp;');
+			return Array( this._depth_ + (extra||0) ).join(chr);
+		},
+		up:function( a ) {
+			this._depth_ += a || 1;
+		},
+		down:function( a ) {
+			this._depth_ -= a || 1;
+		},
+		setParser:function( name, parser ) {
+			this.parsers[name] = parser;
+		},
+		// The next 3 are exposed so you can use them
+		quote:quote, 
+		literal:literal,
+		join:join,
+		//
+		_depth_: 1,
+		// This is the list of parsers, to modify them, use jsDump.setParser
+		parsers:{
+			window: '[Window]',
+			document: '[Document]',
+			error:'[ERROR]', //when no parser is found, shouldn't happen
+			unknown: '[Unknown]',
+			'null':'null',
+			undefined:'undefined',
+			'function':function( fn ) {
+				var ret = 'function',
+					name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
+				if ( name )
+					ret += ' ' + name;
+				ret += '(';
+				
+				ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join('');
+				return join( ret, this.parse(fn,'functionCode'), '}' );
+			},
+			array: array,
+			nodelist: array,
+			arguments: array,
+			object:function( map ) {
+				var ret = [ ];
+				this.up();
+				for ( var key in map )
+					ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) );
+				this.down();
+				return join( '{', ret, '}' );
+			},
+			node:function( node ) {
+				var open = this.HTML ? '&lt;' : '<',
+					close = this.HTML ? '&gt;' : '>';
+					
+				var tag = node.nodeName.toLowerCase(),
+					ret = open + tag;
+					
+				for ( var a in this.DOMAttrs ) {
+					var val = node[this.DOMAttrs[a]];
+					if ( val )
+						ret += ' ' + a + '=' + this.parse( val, 'attribute' );
+				}
+				return ret + close + open + '/' + tag + close;
+			},
+			functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
+				var l = fn.length;
+				if ( !l ) return '';				
+				
+				var args = Array(l);
+				while ( l-- )
+					args[l] = String.fromCharCode(97+l);//97 is 'a'
+				return ' ' + args.join(', ') + ' ';
+			},
+			key:quote, //object calls it internally, the key part of an item in a map
+			functionCode:'[code]', //function calls it internally, it's the content of the function
+			attribute:quote, //node calls it internally, it's an html attribute value
+			string:quote,
+			date:quote,
+			regexp:literal, //regex
+			number:literal,
+			'boolean':literal
+		},
+		DOMAttrs:{//attributes to dump from nodes, name=>realName
+			id:'id',
+			name:'name',
+			'class':'className'
+		},
+		HTML:true,//if true, entities are escaped ( <, >, \t, space and \n )
+		indentChar:'   ',//indentation unit
+		multiline:true //if true, items in a collection, are separated by a \n, else just a space.
+	};
+
+	return jsDump;
+})();
+
+})(this);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/tests/accelerometer.tests.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,55 @@
+Tests.prototype.AccelerometerTests = function() {
+	module('Accelerometer (navigator.accelerometer)');
+	test("should exist", function() {
+  		expect(1);
+  		ok(navigator.accelerometer != null, "navigator.accelerometer should not be null.");
+	});
+	test("should contain a getCurrentAcceleration function", function() {
+		expect(2);
+		ok(typeof navigator.accelerometer.getCurrentAcceleration != 'undefined' && navigator.accelerometer.getCurrentAcceleration != null, "navigator.accelerometer.getCurrentAcceleration should not be null.");
+		ok(typeof navigator.accelerometer.getCurrentAcceleration == 'function', "navigator.accelerometer.getCurrentAcceleration should be a function.");
+	});
+	test("getCurrentAcceleration success callback should be called with an Acceleration object", function() {
+		expect(7);
+		stop(tests.TEST_TIMEOUT);
+		var win = function(a) {
+			ok(typeof a == 'object', "Acceleration object returned in getCurrentAcceleration success callback should be of type 'object'.");
+			ok(a.x != null, "Acceleration object returned in getCurrentAcceleration success callback should have an 'x' property.");
+			ok(typeof a.x == 'number', "Acceleration object's 'x' property returned in getCurrentAcceleration success callback should be of type 'number'.");
+			ok(a.y != null, "Acceleration object returned in getCurrentAcceleration success callback should have a 'y' property.");
+			ok(typeof a.y == 'number', "Acceleration object's 'y' property returned in getCurrentAcceleration success callback should be of type 'number'.");
+			ok(a.z != null, "Acceleration object returned in getCurrentAcceleration success callback should have a 'z' property.");
+			ok(typeof a.z == 'number', "Acceleration object's 'z' property returned in getCurrentAcceleration success callback should be of type 'number'.");
+			start();
+		};
+		var fail = function() { start(); };
+		navigator.accelerometer.getCurrentAcceleration(win, fail);
+	});
+	test("should contain a watchAcceleration function", function() {
+		expect(2);
+		ok(typeof navigator.accelerometer.watchAcceleration != 'undefined' && navigator.accelerometer.watchAcceleration != null, "navigator.accelerometer.watchAcceleration should not be null.");
+		ok(typeof navigator.accelerometer.watchAcceleration == 'function', "navigator.accelerometer.watchAcceleration should be a function.");
+	});
+	test("should contain a clearWatch function", function() {
+		expect(2);
+		ok(typeof navigator.accelerometer.clearWatch != 'undefined' && navigator.accelerometer.clearWatch != null, "navigator.accelerometer.clearWatch should not be null.");
+		ok(typeof navigator.accelerometer.clearWatch == 'function', "navigator.accelerometer.clearWatch should be a function!");
+	});
+	module('Acceleration model');
+	test("should be able to define a new Acceleration object with x, y, z and timestamp properties.", function () {
+		expect(9);
+		var x = 1;
+		var y = 2;
+		var z = 3;
+		var a = new Acceleration(x, y, z);
+		ok(a != null, "new Acceleration object should not be null.");
+		ok(typeof a == 'object', "new Acceleration object should be of type 'object'.");
+		ok(a.x != null, "new Acceleration object should have an 'x' property.");
+		equals(a.x, x, "new Acceleration object should have 'x' property equal to first parameter passed in Acceleration constructor.");
+		ok(a.y != null, "new Acceleration object should have a 'y' property.");
+		equals(a.y, y, "new Acceleration object should have 'y' property equal to second parameter passed in Acceleration constructor.");
+		ok(a.z != null, "new Acceleration object should have a 'z' property.");
+		equals(a.z, z, "new Acceleration object should have 'z' property equal to third parameter passed in Acceleration constructor.");
+		ok(a.timestamp != null, "new Acceleration object should have a 'timestamp' property.");
+	});
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/tests/camera.tests.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,12 @@
+Tests.prototype.CameraTests = function() {	
+	module('Camera (navigator.camera)');
+	test("should exist", function() {
+  		expect(1);
+  		ok(navigator.camera != null, "navigator.camera should not be null.");
+	});
+	test("should contain a getPicture function", function() {
+		expect(2);
+		ok(typeof navigator.camera.getPicture != 'undefined' && navigator.camera.getPicture != null, "navigator.camera.getPicture should not be null.");
+		ok(typeof navigator.camera.getPicture == 'function', "navigator.camera.getPicture should be a function.");
+	});
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/tests/contacts.tests.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,38 @@
+Tests.prototype.ContactsTests = function() {
+	module("Contacts model");
+	test("should be able to define a Contacts object with name, emails, and phones attributes.", function() {
+		expect(5);
+		var con = new Contact();
+		ok(con != null, "new Contact() should not be null.");
+		ok(con.name != null, "new Contact() should include a 'name' property.");
+		ok(con.name.formatted != null, "new Contact()'s 'name' property should, at the very least, contain a 'formatted' property.");
+		ok(con.emails != null, "new Contact() should include an 'emails' array.");
+		ok(con.phones != null, "new Contact() should include a 'phones' array.");
+	});
+	module('Contacts (navigator.contacts)');
+	test("should exist", function() {
+  		expect(1);
+  		ok(navigator.contacts != null, "navigator.contacts should not be null.");
+	});
+	test("should contain a find function", function() {
+		expect(2);
+		ok(typeof navigator.contacts.find != 'undefined' && navigator.contacts.find != null, "navigator.contacts.find should not be null.");
+		ok(typeof navigator.contacts.find == 'function', "navigator.contacts.find should be a function.");
+	});
+	// TODO: Need to add tests for the find function. Need to be able to include test data, but how? How do we add a contact 
+	// 		 to the phone before running the test?
+	// TODO: Need to include tests that check error-handling, doubt there is any in the framework code.
+	test("contacts.find success callback should be called with an array", function() {
+		expect(2);
+		stop(tests.TEST_TIMEOUT);
+		var win = function(result) {
+			ok(typeof result == 'object', "Object returned in contacts.find success callback is of type 'object' (actually array).");
+			ok(typeof result.length == 'number', "Object returned in contacts.find success callback has a length property which is numerical.");
+			start();
+		};
+		var fail = function() { start(); };
+		var filter = new Contact();
+		filter.name.formatted = '';
+		navigator.contacts.find(filter,win, fail);
+	});
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/tests/device.tests.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,31 @@
+Tests.prototype.DeviceTests = function() {
+	module('Device Information (window.device)');
+	test("should exist", function() {
+  		expect(1);
+  		ok(window.device != null, "window.device should not be null.");
+	});
+	test("should contain a platform specification that is a string", function() {
+		expect(2);
+		ok(typeof window.device.platform != 'undefined' && window.device.platform != null, "window.device.platform should not be null.")
+		ok((new String(window.device.platform)).length > 0, "window.device.platform should contain some sort of description.")
+	});
+	test("should contain a version specification that is a string", function() {
+		expect(2);
+		ok(typeof window.device.version != 'undefined' && window.device.version != null, "window.device.version should not be null.")
+		ok((new String(window.device.version)).length > 0, "window.device.version should contain some kind of description.")
+	});
+	test("should contain a name specification that is a string", function() {
+		expect(2);
+		ok(typeof window.device.name != 'undefined' && window.device.name != null, "window.device.name should not be null.")
+		ok((new String(window.device.name)).length > 0, "window.device.name should contain some kind of description.")
+	});
+	test("should contain a UUID specification that is a string or a number", function() {
+		expect(2);
+		ok(typeof window.device.uuid != 'undefined' && window.device.uuid != null, "window.device.uuid should not be null.")
+		if (typeof window.device.uuid == 'string' || typeof window.device.uuid == 'object') {
+			ok((new String(window.device.uuid)).length > 0, "window.device.uuid, as a string, should have at least one character.")
+		} else {
+			ok(window.device.uuid > 0, "window.device.uuid, as a number, should be greater than 0. (should it, even?)")
+		}
+	});
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/tests/file.tests.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,17 @@
+Tests.prototype.FileTests = function() {	
+	module('File I/O (navigator.file)');
+	test("should exist", function() {
+  		expect(1);
+  		ok(navigator.file != null, "navigator.file should not be null.");
+	});
+	test("should contain a read function", function() {
+		expect(2);
+		ok(typeof navigator.file.read != 'undefined' && navigator.file.read != null, "navigator.file.read should not be null.");
+		ok(typeof navigator.file.read == 'function', "navigator.file.read should be a function.");
+	});
+	test("should contain a write function", function() {
+		expect(2);
+		ok(typeof navigator.file.write != 'undefined' && navigator.file.write != null, "navigator.file.write should not be null.");
+		ok(typeof navigator.file.write == 'function', "navigator.file.write should be a function.");
+	});
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/tests/geolocation.tests.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,56 @@
+Tests.prototype.GeoLocationTests = function() {	
+	module('Geolocation (navigator.geolocation)');
+	test("should exist", function() {
+  		expect(1);
+  		ok(navigator.geolocation != null, "navigator.geolocation should not be null.");
+	});
+	test("should have an initially null lastPosition property", function() {
+  		expect(1);
+  		ok(typeof navigator.geolocation.lastPosition != 'undefined' && navigator.geolocation.lastPosition == null, "navigator.geolocation.lastPosition should be initially null.");
+	});
+	test("should contain a getCurrentPosition function", function() {
+		expect(2);
+		ok(typeof navigator.geolocation.getCurrentPosition != 'undefined' && navigator.geolocation.getCurrentPosition != null, "navigator.geolocation.getCurrentPosition should not be null.");
+		ok(typeof navigator.geolocation.getCurrentPosition == 'function', "navigator.geolocation.getCurrentPosition should be a function.");
+	});
+	test("should contain a watchPosition function", function() {
+		expect(2);
+		ok(typeof navigator.geolocation.watchPosition != 'undefined' && navigator.geolocation.watchPosition != null, "navigator.geolocation.watchPosition should not be null.");
+		ok(typeof navigator.geolocation.watchPosition == 'function', "navigator.geolocation.watchPosition should be a function.");
+	});
+	test("getCurrentPosition success callback should be called with a Position object", function() {
+		expect(3);
+		stop(tests.TEST_TIMEOUT);
+		var win = function(p) {
+			ok(p.coords != null, "Position object returned in getCurrentPosition success callback has a 'coords' property.");
+			ok(p.timestamp != null, "Position object returned in getCurrentPosition success callback has a 'timestamp' property.");
+			equals(p, navigator.geolocation.lastPosition, "Position object returned in getCurrentPosition success callback equals navigator.geolocation.lastPosition.");
+			start();
+		};
+		var fail = function() { start(); };
+		navigator.geolocation.getCurrentPosition(win, fail);
+	});
+	// TODO: Need to test error callback... how?
+	// TODO: Need to test watchPosition success callback, test that makes sure clearPosition works (how to test that a timer is getting cleared?)
+	// TODO: Need to test options object passed in. Members that need to be tested so far include:
+	//				- options.frequency: this is also labelled differently on some implementations (internval on iPhone/BlackBerry currently). 
+	module('Geolocation model');
+	test("should be able to define a Position object with coords and timestamp properties", function() {
+		expect(3);
+		var pos = new Position({});
+		ok(pos != null, "new Position() should not be null.");
+		ok(typeof pos.coords != 'undefined' && pos.coords != null, "new Position() should include a 'coords' property.");
+		ok(typeof pos.timestamp != 'undefined' && pos.timestamp != null, "new Position() should include a 'timestamp' property.");
+	});
+	test("should be able to define a Coordinates object with latitude, longitude, accuracy, altitude, heading and speed properties", function() {
+		expect(7);
+		var coords = new Coordinates(1,2,3,4,5,6,7);
+		ok(coords != null, "new Coordinates() should not be null.");
+		ok(typeof coords.latitude != 'undefined' && coords.latitude != null, "new Coordinates() should include a 'latitude' property.");
+		ok(typeof coords.longitude != 'undefined' && coords.longitude != null, "new Coordinates() should include a 'longitude' property.");
+		ok(typeof coords.accuracy != 'undefined' && coords.accuracy != null, "new Coordinates() should include a 'accuracy' property.");
+		ok(typeof coords.altitude != 'undefined' && coords.altitude != null, "new Coordinates() should include a 'altitude' property.");
+		ok(typeof coords.heading != 'undefined' && coords.heading != null, "new Coordinates() should include a 'heading' property.");
+		ok(typeof coords.speed != 'undefined' && coords.speed != null, "new Coordinates() should include a 'speed' property.");
+	});
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/tests/map.tests.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,12 @@
+Tests.prototype.MapTests = function() {	
+	module('Map (navigator.map)');
+	test("should exist", function() {
+  		expect(1);
+  		ok(navigator.map != null, "navigator.map should not be null.");
+	});
+	test("should contain a show function", function() {
+		expect(2);
+		ok(navigator.map.show != null, "navigator.map.show should not be null.");
+		ok(typeof navigator.map.show == 'function', "navigator.map.show should be a function.");
+	});
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/tests/media.tests.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,26 @@
+Tests.prototype.MediaTests = function() {	
+	module('Media (Audio)');
+	test("should exist", function() {
+  		expect(1);
+  		ok(typeof Audio == "function", "'Audio' should be defined as a function in global scope.");
+	});
+	test("should define constants for Media errors", function() {
+		expect(5);
+		ok(MediaError != null && typeof MediaError != 'undefined', "MediaError object exists in global scope.");
+		equals(MediaError.MEDIA_ERR_ABORTED, 1, "MediaError.MEDIA_ERR_ABORTED is equal to 1.");
+		equals(MediaError.MEDIA_ERR_NETWORK, 2, "MediaError.MEDIA_ERR_NETWORK is equal to 2.");
+		equals(MediaError.MEDIA_ERR_DECODE, 3, "MediaError.MEDIA_ERR_DECODE is equal to 3.");
+		equals(MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, 4, "MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED is equal to 4.");
+	});
+	test("should contain 'src', 'loop' and 'error' properties", function() {
+  		expect(7);
+		var audioSrc = '/test.mp3';
+		var audio = new Audio(audioSrc);
+  		ok(typeof audio == "object", "Instantiated 'Audio' object instance should be of type 'object.'");
+		ok(audio.src != null && typeof audio.src != 'undefined', "Instantiated 'Audio' object's 'src' property should not be null or undefined.");
+		ok(audio.src == audioSrc, "Instantiated 'Audio' object's 'src' property should match constructor parameter.");
+		ok(audio.loop != null && typeof audio.loop != 'undefined', "Instantiated 'Audio' object's 'loop' property should not be null or undefined.");
+		ok(audio.loop == false, "Instantiated 'Audio' object's 'loop' property should initially be false.");
+		ok(typeof audio.error != 'undefined', "Instantiated 'Audio' object's 'error' property should not undefined.");
+		ok(audio.error == null, "Instantiated 'Audio' object's 'error' should initially be null.");
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/tests/network.tests.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,29 @@
+Tests.prototype.NetworkTests = function() {
+	module('Network (navigator.network)');
+	test("should exist", function() {
+  		expect(1);
+  		ok(navigator.network != null, "navigator.network should not be null.");
+	});
+	test("should contain an isReachable function", function() {
+		expect(2);
+		ok(typeof navigator.network.isReachable != 'undefined' && navigator.network.isReachable != null, "navigator.network.isReachable should not be null.");
+		ok(typeof navigator.network.isReachable == 'function', "navigator.network.isReachable should be a function.");
+	});
+	test("should define constants for network status", function() {
+		expect(4);
+		ok(NetworkStatus != null, "NetworkStatus object exists in global scope.");
+		equals(NetworkStatus.NOT_REACHABLE, 0, "NetworkStatus.NOT_REACHABLE is equal to 0.");
+		equals(NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK, 1, "NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK is equal to 1.");
+		equals(NetworkStatus.REACHABLE_VIA_WIFI_NETWORK, 2, "NetworkStatus.REACHABLE_VIA_WIFI_NETWORK is equal to 2.");
+	});
+	test("isReachable function should return an object with a 'code' member as a NetworkStatus constant in its success callback", function() {
+		expect(1);
+		stop(Tests.TEST_TIMEOUT);
+		var hostname = "http://www.google.com";
+		var win = function(p) {
+			ok(p.code == NetworkStatus.NOT_REACHABLE || p.code == NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK || p.code == NetworkStatus.REACHABLE_VIA_WIFI_NETWORK, "Success callback in isReachable returns a proper object with a 'code' member equal to a NetworkStatus constant.");
+			start();
+		};
+		navigator.network.isReachable(hostname, win);
+	});
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/tests/notification.tests.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,17 @@
+Tests.prototype.NotificationTests = function() {	
+	module('Notification (navigator.notification)');
+	test("should exist", function() {
+  		expect(1);
+  		ok(navigator.notification != null, "navigator.notification should not be null.");
+	});
+	test("should contain a vibrate function", function() {
+		expect(2);
+		ok(typeof navigator.notification.vibrate != 'undefined' && navigator.notification.vibrate != null, "navigator.notification.vibrate should not be null.");
+		ok(typeof navigator.notification.vibrate == 'function', "navigator.notification.vibrate should be a function.");
+	});
+	test("should contain a beep function", function() {
+		expect(2);
+		ok(typeof navigator.notification.beep != 'undefined' && navigator.notification.beep != null, "navigator.notification.beep should not be null.");
+		ok(typeof navigator.notification.beep == 'function', "navigator.notification.beep should be a function.");
+	});
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/tests/orientation.tests.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,34 @@
+Tests.prototype.OrientationTests = function() {	
+	module('Orientation (navigator.orientation)');
+	test("should exist", function() {
+  		expect(1);
+  		ok(navigator.orientation != null, "navigator.orientation should not be null.");
+	});
+	test("should have an initially null lastPosition property", function() {
+  		expect(1);
+  		ok(typeof navigator.orientation.currentOrientation != 'undefined' && navigator.orientation.currentOrientation == null, "navigator.orientation.currentOrientation should be initially null.");
+	});
+	test("should contain a getCurrentOrientation function", function() {
+		expect(2);
+		ok(typeof navigator.orientation.getCurrentOrientation != 'undefined' && navigator.orientation.getCurrentOrientation != null, "navigator.orientation.getCurrentOrientation should not be null.");
+		ok(typeof navigator.orientation.getCurrentOrientation == 'function', "navigator.orientation.getCurrentOrientation should be a function.");
+	});
+	test("should contain a watchOrientation function", function() {
+		expect(2);
+		ok(typeof navigator.orientation.watchOrientation != 'undefined' && navigator.orientation.watchOrientation != null, "navigator.orientation.watchOrientation should not be null.");
+		ok(typeof navigator.orientation.watchOrientation == 'function', "navigator.orientation.watchOrientation should be a function.");
+	});
+	// TODO: add tests for DisplayOrientation constants?
+	test("getCurrentOrientation success callback should be called with an Orientation enumeration", function() {
+		expect(2);
+		stop(tests.TEST_TIMEOUT);
+		var win = function(orient) {
+			ok(0 <= orient <= 6, "Position object returned in getCurrentPosition success callback is a valid DisplayOrientation value.");
+			equals(orient, navigator.orientation.currentOrientation, "Orientation value returned in getCurrentOrientation success callback equals navigator.orientation.currentOrientation.");
+			start();
+		};
+		var fail = function() { start(); };
+		navigator.orientation.getCurrentOrientation(win, fail);
+	});
+	// TODO: Need to test watchPosition success callback, test that makes sure clearPosition works (how to test that a timer is getting cleared?)
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/tests/sms.tests.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,12 @@
+Tests.prototype.SMSTests = function() {	
+	module('SMS (navigator.sms)');
+	test("should exist", function() {
+  		expect(1);
+  		ok(navigator.sms != null, "navigator.sms should not be null.");
+	});
+	test("should contain a send function", function() {
+		expect(2);
+		ok(typeof navigator.sms.send != 'undefined' && navigator.sms.send != null, "navigator.sms.send should not be null.");
+		ok(typeof navigator.sms.send == 'function', "navigator.sms.send should be a function.");
+	});
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/tests/storage.tests.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,12 @@
+Tests.prototype.StorageTests = function() 
+{
+  module("HTML 5 Storage");
+  test("should exist", function() {
+    expect(1);
+    ok(typeof(window.openDatabase) == "function", "Database is defined");
+  });
+  test("Should open a database", function() {
+    var db = openDatabase("Database", "1.0", "HTML5 Database API example", 200000);
+    ok(db != null, "Database should be opened");
+  });
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/tests/telephony.tests.js	Tue Jul 06 11:31:19 2010 -0700
@@ -0,0 +1,12 @@
+Tests.prototype.TelephonyTests = function() {	
+	module('Telephony (navigator.telephony)');
+	test("should exist", function() {
+  		expect(1);
+  		ok(navigator.telephony != null, "navigator.telephony should not be null.");
+	});
+	test("should contain a send function", function() {
+		expect(2);
+		ok(typeof navigator.telephony.send != 'undefined' && navigator.telephony.send != null, "navigator.telephony.send should not be null.");
+		ok(typeof navigator.telephony.send == 'function', "navigator.telephony.send should be a function.");
+	});
+};
\ No newline at end of file