Merge branch 'next'
diff --git a/.gitignore b/.gitignore
index 4e14638..8d7f400 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@
 www/*
 build_output/
 pkg
+plugins
diff --git a/.jshintrc b/.jshintrc
index d8b8028..556accb 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -64,7 +64,8 @@
         "sinon",
         "setInterval", "clearInterval", "setTimeout", "clearTimeout",
         "require", "Contact", "Position", "Coordinates", "Node",
-        "OpenLayers"
+        "OpenLayers",
+        "cordova"
     ],
  
     "node" : true,
diff --git a/Jakefile b/Jakefile
index 137e693..5a39f14 100644
--- a/Jakefile
+++ b/Jakefile
@@ -22,10 +22,29 @@
 desc("test and lint before building (with js compression)");
 task('deploy', [], require('./build/deploy'));
 
+// TODO: put this functionality into its own module (same with code in build/deploy).
 desc("run all tests in node with an emulated dom - jake test [path,path2]");
 task('test', [], function () {
-    require('./build/test')(null, process.argv.length >= 4 ? process.argv[3] : null);
-});
+    var childProcess = require('child_process'),
+        env = process.env,
+        lib = process.cwd() + '/lib',
+        script = process.cwd() + '/build/scripts/runTestsInNode',
+        child;
+
+    env.NODE_PATH = lib;
+    child = childProcess.spawn(process.execPath, [script], {'env': env});
+
+    function log(data) {
+        process.stdout.write(new Buffer(data).toString('utf-8'));
+    }
+
+    child.stdout.on('data', log);
+    child.stderr.on('data', log);
+    child.on('exit', function (code) {
+        complete();
+        process.exit(code);
+    });
+}, true);
 
 desc("boot test server for running all tests in the browser");
 task('btest', [], require('./build/btest'));
diff --git a/README.md b/README.md
index 905d8cb..26ec006 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,17 @@
 
 This will describe all the available commands for building and running the tests
 
+## Running as a Chrome Extension
+
+- go to the extension management page (chrome://chrome/extensions/) in chrome.
+- Ensure that you have selected the developer mode checkbox
+- click the Load Unpacked extension button
+- select the chromestore folders in the pkg/ folder.
+
+NOTE: for development you should be fine to just build with jake and refresh your browser.  If
+you end up editing anything in the ext folder you will need to refresh the extension from
+the extension management page.
+
 ## Running Inside Other Web Browsers
 
 Ripple is (by-design) browser agnostic, and is able to run inside any web browser (with disabled web security).
@@ -45,6 +56,8 @@
     --disable-web-security
     --user-data-dir=/path/to/dummy/profile
 
+This has only really be tested in chrome.
+
 ## Code Guidelines
 
 * 4 spaces per editor tab
diff --git a/build/btest.js b/build/btest.js
index 1a8df75..438ab46 100644
--- a/build/btest.js
+++ b/build/btest.js
@@ -24,27 +24,21 @@
         doc,
         modules,
         specs,
-        openlayers,
-        app = connect(
-            connect.static(__dirname + "/../lib/"),
-            connect.static(__dirname + "/../"),
-            connect.router(function (app) {
-                app.get('/', function (req, res) {
-                    res.writeHead(200, {
-                        "Cache-Control": "no-cache",
-                        "Content-Type": "text/html"
-                    });
-                    res.end(doc);
+        app = connect()
+            .use(connect.static(__dirname + "/../lib/"))
+            .use(connect.static(__dirname + "/../"))
+            .use('/', function (req, res) {
+                res.writeHead(200, {
+                    "Cache-Control": "max-age=0",
+                    "Content-Type": "text/html"
                 });
-            })
-        );
+                res.end(doc);
+            });
 
-    //HACK: Openlayers causes weird stuff with the browser runner, so lets pop it off the list until we fix it
-    openlayers = conf.thirdpartyIncludes.pop();
-    if (openlayers !== "OpenLayers.js") {
-        //HACK: just a safe check to make sure our hack is still valid
-        console.log("HACK: we wanted to pop OpenLayers off but it looks like it wasn't the last one anymore");
-    }
+    //HACK: Openlayers causes weird stuff with the browser runner, so lets remove it from the list until we fix it
+    conf.thirdpartyIncludes = conf.thirdpartyIncludes.filter(function (filename) {
+        return !filename.match(/openlayers\.js/i);
+    });
 
     modules = pack();
 
diff --git a/build/build/chromestore.js b/build/build/chromestore.js
index f9441e2..7875b10 100644
--- a/build/build/chromestore.js
+++ b/build/build/chromestore.js
@@ -24,7 +24,8 @@
                'cp -r ' + _c.ASSETS + "images " + _c.DEPLOY + "chromestore/ &&" +
                'cp -r ' + _c.ASSETS + "themes " + _c.DEPLOY + "chromestore/ &&" +
                'cp ' + _c.EXT + "chromestore/manifest.json " + _c.DEPLOY + "chromestore/manifest.json &&" +
-               'cp ' + _c.EXT + "chromestore/controllers/Background.js " + _c.DEPLOY + "chromestore/controllers/Background.js"; 
+               'cp ' + _c.EXT + "chromestore/controllers/Background.js " + _c.DEPLOY + "chromestore/controllers/Background.js" + 
+               'cp ' + _c.EXT + "chromestore/views/background.html " + _c.DEPLOY + "chromestore/views/background.html"; 
 
     childProcess.exec(copy, function () {
         var css = _c.ASSETS + "ripple.css",
diff --git a/build/build/chromium.js b/build/build/chromium.js
index 331a725..91aac0b 100644
--- a/build/build/chromium.js
+++ b/build/build/chromium.js
@@ -22,7 +22,8 @@
 
     var copy = 'cp -r ' + _c.EXT + "chromium " + _c.DEPLOY + " && " +
                'cp -r ' + _c.ASSETS + "images " + _c.DEPLOY + "chromium/ &&" +
-               'cp -r ' + _c.ASSETS + "themes " + _c.DEPLOY + "chromium/";
+               'cp -r ' + _c.ASSETS + "themes " + _c.DEPLOY + "chromium/" +
+               'cp -r ' + _c.ROOT + "plugins " + _c.DEPLOY + "chromium/";
 
     childProcess.exec(copy, function () {
         var css = _c.ASSETS + "ripple.css",
diff --git a/build/deploy.js b/build/deploy.js
index 6dd68cc..6ed1e3f 100644
--- a/build/deploy.js
+++ b/build/deploy.js
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-var test = require('./test'),
-    lint = require('./lint'),
+var lint = require('./lint'),
     build = require('./build'),
+    childProcess = require('child_process'),
     fs = require('fs'),
     fail = fs.readFileSync(__dirname + "/../thirdparty/fail.txt", "utf-8");
 
@@ -26,6 +26,26 @@
     }
 }
 
+function test(callback) {
+    var env = process.env,
+        lib = process.cwd() + '/lib',
+        script = process.cwd() + '/build/scripts/runTestsInNode',
+        child;
+
+    env.NODE_PATH = lib;
+    child = childProcess.spawn(process.execPath, [script], {'env': env});
+
+    function log(data) {
+        process.stdout.write(new Buffer(data).toString('utf-8'));
+    }
+
+    child.stdout.on('data', log);
+    child.stderr.on('data', log);
+    child.on('exit', function (code) {
+        callback(code);
+    });
+}
+
 module.exports = function () {
     test(function (code) {
         ok(code, "red tests");
diff --git a/build/lint.js b/build/lint.js
index 32917d1..f02b99e 100644
--- a/build/lint.js
+++ b/build/lint.js
@@ -38,7 +38,7 @@
 
 function _lintCSS(files, done) {
     var rules = JSON.parse(fs.readFileSync(__dirname + "/../.csslintrc", "utf-8")),
-        options = ["--rules=" + rules, "--format=compact"];
+        options = ["--errors=" + rules, "--format=compact", "--quiet"];
     _spawn('csslint', files.concat(options), done);
 }
 
diff --git a/build/scripts/runTestsInNode b/build/scripts/runTestsInNode
new file mode 100755
index 0000000..c34ae30
--- /dev/null
+++ b/build/scripts/runTestsInNode
@@ -0,0 +1,5 @@
+// This is here for a reason.
+// This is so you can do things like `NODE_PATH=/path/to/ripple/lib node external_script`.
+// This gets around that pesky forcing of relative requires in Node (these days).
+// See the `jakefile` for how this is used in this project.
+require('./../test')();
diff --git a/build/test.js b/build/test.js
index c0a9a22..8474423 100644
--- a/build/test.js
+++ b/build/test.js
@@ -66,13 +66,11 @@
 
         _extraMocks();
 
-        childProcess.exec('rm -rf node_modules/ripple* && ' +
-                          'cp -rf lib/ripple node_modules/ripple && ' +
-                          'cp -f lib/ripple.js node_modules/ripple.js', ready);
+        ready();
     });
 }
 
-module.exports = function (done, custom) {
+module.exports = function (done) {
     //HACK: this should be  taken out if our pull request in jasmine is accepted.
     jasmine.core.Matchers.prototype.toThrow = function (expected) {
         var result = false,
@@ -108,14 +106,11 @@
     };
 
     _setupEnv(function () {
-        var targets = __dirname + "/../" + (custom ? custom : "test");
+        var targets = __dirname + "/../test";
 
         jasmine.run(targets.split(' '), function (runner) {
             var failed = runner.results().failedCount === 0 ? 0 : 1;
-            //Nuke everything out of node_modules since it was just in there to run the tests
-            childProcess.exec('rm -rf node_modules/ripple*', function () {
-                (typeof done !== "function" ? process.exit : done)(failed);
-            });
+            (typeof done !== "function" ? process.exit : done)(failed);
         });
     });
 };
diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md
index 81131a5..e21ddd8 100644
--- a/doc/CHANGELOG.md
+++ b/doc/CHANGELOG.md
@@ -1,3 +1,26 @@
+## v0.9.7 - July 19, 2012
+
+* Fixed an issue with Ripple booting on Chrome 21 dev channel
+* Webworks BB10 support: (https://github.com/blackberry/Ripple-UI/issues?milestone=11&page=1&state=closed)
+ * blackberry.app.exit
+ * device settings for software version and hardware ID
+ * support for consumer and enterprise parameters
+ * support for the swipe down event
+ * support for invoke
+* Fixed a caching issue.
+* Cleaned up browser test failures
+* updated build tooling to work in latest node
+* updated README docs for running as a plugin
+* added support for selecting the platform and version when launching from the querystring
+* Updated Cordova support for 2.0.0 (https://github.com/blackberry/Ripple-UI/issues?milestone=13&page=1&state=closed)
+ * updated the version numbers for phonegap and cordova
+ * navigator camera
+ * Media
+ * File API
+ * cordova specific event support
+ * updated and fixed support for navigator.contacts
+ * added partial support for navigator.device.capture
+
 ## v0.9.6.1 (HOTFIX) - June 21, 2012
 
 * Fixed bug with Chrome Version 21.0.1180.0 dev where Ripple will not boot
diff --git a/ext/assets/index.html b/ext/assets/index.html
index b922c21..ad7a547 100644
--- a/ext/assets/index.html
+++ b/ext/assets/index.html
@@ -16,6 +16,7 @@
 <!DOCTYPE html>
 <html manifest="cache.manifest">
     <head>
+        <META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">
         <link href="#URL_PREFIX#ripple.css" type="text/css" rel="stylesheet" />
     </head>
     <body>
diff --git a/ext/chromestore/controllers/Background.js b/ext/chromestore/controllers/Background.js
index 4a44c2c..39d3963 100644
--- a/ext/chromestore/controllers/Background.js
+++ b/ext/chromestore/controllers/Background.js
@@ -173,7 +173,7 @@
             chrome.tabs.getSelected(null, function (tab) {
                 console.log("enable ==> " + tab.url);
                 _persistEnabled(tab.url);
-                chrome.tabs.sendRequest(tab.id, {"action": "enable", "mode": "widget", "tabURL": tab.url }, function (response) {});
+                chrome.tabs.sendRequest(tab.id, {"action": "enable", "mode": "widget", "tabURL": tab.url });
             });
         },
 
@@ -194,7 +194,7 @@
 
                 localStorage["tinyhippos-enabled-uri"] = JSON.stringify(jsonObject);
 
-                chrome.tabs.sendRequest(tab.id, {"action": "disable", "tabURL": tab.url }, function (response) {});
+                chrome.tabs.sendRequest(tab.id, {"action": "disable", "tabURL": tab.url });
             });
         },
 
diff --git a/lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js b/ext/chromestore/views/background.html
similarity index 71%
copy from lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js
copy to ext/chromestore/views/background.html
index 38aa21a..96379ac 100644
--- a/lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js
+++ b/ext/chromestore/views/background.html
@@ -1,4 +1,4 @@
-/*
+<!--
  *  Copyright 2011 Research In Motion Limited.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,10 +12,12 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- */
-//blackberry.invoke.BrowserArguments ( url : String ,  [transport : blackberry.identity.Transport ] )
-module.exports = function (url, transport) {
-    return {
-        url: url
-    };
-};
+-->
+<html>
+    <head>
+        <script type="text/javascript" src="../controllers/jquery.js"></script>
+        <script type="text/javascript" src="../controllers/Background.js"></script>
+    </head>
+    <body>
+    </body>
+</html>
diff --git a/ext/chromium/controllers/Background.js b/ext/chromium/controllers/Background.js
index 0d43bee..0b29e7f 100644
--- a/ext/chromium/controllers/Background.js
+++ b/ext/chromium/controllers/Background.js
@@ -56,6 +56,8 @@
         xhr.send();
 
         chrome.extension.onRequest.addListener(function (request, sender, sendResponse) {
+            var xhr, postData, data, plugin;
+console.log(request);
             switch (request.action) {
             case "isEnabled":
                 console.log("isEnabled? ==> " + request.tabURL);
@@ -67,16 +69,16 @@
                 sendResponse();
                 break;
             case "userAgent":
-                console.log("user agent ==> " + userAgent);
+                console.log("user agent ==> " + request.data);
                 userAgent = request.data;
                 break;
             case "version":
                 sendResponse(version);
                 break;
             case "xhr":
-                var xhr = new XMLHttpRequest(),
-                    postData = new FormData(),
-                    data = JSON.parse(request.data);
+                xhr = new XMLHttpRequest();
+                postData = new FormData();
+                data = JSON.parse(request.data);
 
                 console.log("xhr ==> " + data.url);
 
@@ -99,8 +101,32 @@
                     }
                 });
                 break;
+            case "services":
+                console.log("services", request.data);
+                if (request.data === '"start"') {
+                    plugin = document.getElementById("pluginRippleBD");
+                    if (plugin) {
+                        console.log("return from startBD", plugin.startBD(9910));
+                        sendResponse();
+                    }
+                }
+                else if (request.data === '"stop"') {
+                    xhr = new XMLHTTPRequest();
+                    try {
+                        xhr.open("GET", "http://127.0.0.1:9910/ripple/shutdown", false);
+                        xhr.send();
+                    }
+                    catch (e) {
+                        console.log(e);
+                    }
+                }
+                break;
+            case "lag":
+            case "network":
+                // methods to be implemented at a later date
+                break;
             default:
-                throw {name: "MethodNotImplemented", message: "Requested action is not supported!"};
+                throw {name: "MethodNotImplemented", message: "Requested action is not supported! "};
                 break;
             };
         });
@@ -186,7 +212,7 @@
             chrome.tabs.getSelected(null, function (tab) {
                 console.log("enable ==> " + tab.url);
                 _persistEnabled(tab.url);
-                chrome.tabs.sendRequest(tab.id, {"action": "enable", "mode": "widget", "tabURL": tab.url }, function (response) {});
+                chrome.tabs.sendRequest(tab.id, {"action": "enable", "mode": "widget", "tabURL": tab.url});
             });
         },
 
@@ -207,12 +233,12 @@
 
                 localStorage["tinyhippos-enabled-uri"] = JSON.stringify(jsonObject);
 
-                chrome.tabs.sendRequest(tab.id, {"action": "disable", "tabURL": tab.url }, function (response) {});
+                chrome.tabs.sendRequest(tab.id, {"action": "disable", "tabURL": tab.url });
             });
         },
 
         isEnabled: function (url, enabledURIs) {
-            if (url.match(/enableripple=true/i)) {
+            if (url.match(/enableripple=/i)) {
                 _persistEnabled(url);
                 return true;
             }
diff --git a/ext/chromium/controllers/Insertion.js b/ext/chromium/controllers/Insertion.js
index 5b2457e..6ccbe8a 100644
--- a/ext/chromium/controllers/Insertion.js
+++ b/ext/chromium/controllers/Insertion.js
@@ -21,16 +21,20 @@
             case "enable":
                 break;
             case "disable":
-                localStorage.removeItem("tinyhippos-enabled-uri");
-                uri = uri.replace(/\?enableripple\=true/, "").replace(/\&enableripple\=true/, "");
+                //HACK: ummm .... I am sorry
+                uri = uri.replace(/enableripple=[^&]*[&]?/i, "").replace(/[\?&]*$/, "");
                 break;
 
             default:
                 throw {name: "MethodNotImplemented", message: "Requested action is not supported!"};
             }
 
-            sendResponse({});
-            location.href = uri;
+            if (location.href !== uri) {
+                location.href = uri;
+            }
+            else {
+                location.reload();
+            }
         });
     }
 
diff --git a/ext/chromium/controllers/frame.js b/ext/chromium/controllers/frame.js
index b0d4289..eb3a854 100644
--- a/ext/chromium/controllers/frame.js
+++ b/ext/chromium/controllers/frame.js
@@ -1,6 +1,21 @@
+/*
+ *  Copyright 2011 Research In Motion Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 if (!document.getElementById("emulator-booting") && !document.getElementById("tinyhippos-injected")) {
     var script = document.createElement("script");
     script.id = "tinyhippos-injected";
-    script.src = chrome.extension.getURL("controllers/injector.js");
+    script.innerText = 'if (window.top.require) { window.top.require("ripple/bootstrap").inject(window, document); }'; 
     document.documentElement.appendChild(script);
 }
diff --git a/ext/chromium/controllers/injector.js b/ext/chromium/controllers/injector.js
deleted file mode 100644
index 4cc51cf..0000000
--- a/ext/chromium/controllers/injector.js
+++ /dev/null
@@ -1,9 +0,0 @@
-if (window.top.require) {
-    //HACK: make this work better and handle iframes in the app!
-    window.top.require('ripple/bootstrap').inject(window, document);
-    window.top.onbeforeunload = function () { 
-        if (!window.top.tinyHipposReload) {
-            return "Are you sure you want to exit Ripple?";
-        }
-    };
-}
diff --git a/ext/chromium/manifest.json b/ext/chromium/manifest.json
index 67e1a5e..f54a673 100644
--- a/ext/chromium/manifest.json
+++ b/ext/chromium/manifest.json
@@ -2,6 +2,10 @@
     "version": "",
     "name": "Ripple Emulator (Beta)",
     "background_page":"views/background.html",
+    "plugins": [
+        { "path": "plugins/npRippleBD.dll" },
+        { "path": "plugins/RippleBD.plugin" }
+    ],
     "icons":{
         "16":"images/Icon_16x16.png",
         "128":"images/Icon_128x128.png",
@@ -23,7 +27,7 @@
         "matches": ["http://*/*","https://*/*","file:///*"],
         "all_frames": true
     }],
-    "permissions": ["tabs", "unlimitedStorage", "notifications", "contextMenus", "webRequest", "webRequestBlocking", "http://*/*", "https://*/*", "file:///*/*"],
+    "permissions": ["tabs", "unlimitedStorage", "notifications", "contextMenus", "webRequest", "webRequestBlocking", "<all_urls>"],
     "description": "A browser based html5 mobile application development and testing tool",
     "update_url": "http://developer.blackberry.com/ripple/updates.xml"
 }
diff --git a/ext/chromium/views/background.html b/ext/chromium/views/background.html
index 96379ac..2794dff 100644
--- a/ext/chromium/views/background.html
+++ b/ext/chromium/views/background.html
@@ -19,5 +19,6 @@
         <script type="text/javascript" src="../controllers/Background.js"></script>
     </head>
     <body>
+        <embed type="application/x-ripplebd" id="pluginRippleBD" hidden="true" width="0" height="0" /> 
     </body>
 </html>
diff --git a/lib/ripple.js b/lib/ripple.js
index 28b707a..602883a 100644
--- a/lib/ripple.js
+++ b/lib/ripple.js
@@ -57,6 +57,25 @@
                 }
             });
 
+            window.onbeforeunload = function () { 
+                if (!window.tinyHipposReload) {
+                    return "Are you sure you want to exit Ripple?";
+                }
+            };
+
+            //HACK: need to find a better way to do this since it's
+            //WebWorks specific!!!
+            window.onunload = function () {
+                var bus = require('ripple/bus');
+                bus.ajax(
+                    "GET",
+                    "http://127.0.0.1:9910/ripple/shutdown",
+                    null,
+                    null,
+                    null
+                );
+            };
+
             jWorkflow.order(omgwtf.initialize, omgwtf)
                  .andThen(appcache.initialize, appcache)
                  .andThen(db.initialize, db)
diff --git a/lib/ripple/bootstrap.js b/lib/ripple/bootstrap.js
index 0525ee1..9c806d2 100644
--- a/lib/ripple/bootstrap.js
+++ b/lib/ripple/bootstrap.js
@@ -118,7 +118,7 @@
     tinyHippos.boot(function () {
         var uri = ui.registered('omnibar') ?
                 db.retrieve(_CURRENT_URL) || "about:blank" :
-                document.documentURI.replace(/enableripple=true[&]/i, "").replace(/[\?&]$/, "");
+                document.documentURI.replace(/enableripple=[^&]*[&]?/i, "").replace(/[\?&]*$/, "");
 
         _post(uri);
         delete tinyHippos.boot;
diff --git a/lib/ripple/devices/Colt.js b/lib/ripple/devices/Colt.js
index ea664a7..e4be734 100644
--- a/lib/ripple/devices/Colt.js
+++ b/lib/ripple/devices/Colt.js
@@ -20,9 +20,10 @@
     "model": "Colt",
     "osName": "BlackBerry",
     "uuid": "42",
-    "osVersion": "10.0.4.178",
+    "osVersion": "10.0.6.99",
     "firmware": "6",
     "manufacturer": "Research In Motion",
+    "hardwareId": "0x8500240a",
 
     "skin": "Colt",
 
diff --git a/lib/ripple/devices/NexusS.js b/lib/ripple/devices/NexusS.js
index 311c882..37be6a4 100644
--- a/lib/ripple/devices/NexusS.js
+++ b/lib/ripple/devices/NexusS.js
@@ -45,7 +45,7 @@
     "ppi": 235,
     "userAgent": "Mozilla/5.0 (Linux; U; Android 2.3.2; en-us; Nexus S Build/GRH78C) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
     "browser": ["Webkit", "Presto"],
-    "platforms": ["web", "phonegap"],
+    "platforms": ["web", "phonegap", "cordova"],
 
     "notes": {
         "1": "<a href=\"http://www.google.com/nexus/#/tech-specs\" target=\"_blank\">Specs</a>"
diff --git a/lib/ripple/devices/iPad.js b/lib/ripple/devices/iPad.js
index 909b489..05ecdcd 100644
--- a/lib/ripple/devices/iPad.js
+++ b/lib/ripple/devices/iPad.js
@@ -46,7 +46,7 @@
     "ppi": 132,
     "userAgent": "Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B367 Safari/531.21.10",
     "browser": ["Webkit"],
-    "platforms": ["web", "phonegap"],
+    "platforms": ["web", "phonegap", "cordova"],
 
     "notes": {
         "1": "<a href=\"http://www.apple.com/ipad/specs/\" target=\"_blank\">Specs</a>"
diff --git a/lib/ripple/devices/iPhone3.js b/lib/ripple/devices/iPhone3.js
index fb57df9..d5dfd62 100644
--- a/lib/ripple/devices/iPhone3.js
+++ b/lib/ripple/devices/iPhone3.js
@@ -46,5 +46,5 @@
     "ppi": 164.8,
     "userAgent": "Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/1A542a Safari/419.3",
     "browser": ["Webkit"],
-    "platforms": ["web", "phonegap"]
+    "platforms": ["web", "phonegap", "cordova"]
 };
diff --git a/lib/ripple/emulatorBridge.js b/lib/ripple/emulatorBridge.js
index a8bdea3..42e7b77 100644
--- a/lib/ripple/emulatorBridge.js
+++ b/lib/ripple/emulatorBridge.js
@@ -78,12 +78,15 @@
         var marshal = function (obj, key) {
                 window[key] = win[key] = obj;
             },
+            currentPlatform = platform.current(),
             sandbox = {};
 
         marshal(window.tinyHippos, "tinyHippos");
         marshal(window.XMLHttpRequest, "XMLHttpRequest");
 
-        platform.current().initialize(win);
+        if (currentPlatform.initialize) {
+            currentPlatform.initialize(win);
+        }
 
         builder.build(platform.current().objects).into(sandbox);
         utils.forEach(sandbox, marshal);
diff --git a/lib/ripple/platform.js b/lib/ripple/platform.js
index f22adec..3139ee1 100644
--- a/lib/ripple/platform.js
+++ b/lib/ripple/platform.js
@@ -23,13 +23,30 @@
     spec = require('ripple/platform/spec'),
     _self;
 
-function _checkForDeprecatedPlatforms(replacement) {
-    if (!spec[_current.name] ||
-        !spec[_current.name][_current.version] ||
-        (spec[_current.name][_current.version] && !spec[_current.name][_current.version].objects)) {
-        _current = replacement;
-        db.saveObject("api-key", _current);
+function _getRequestedPlatform() {
+    var requestedPlatform = null,
+        enableRippleArg = utils.queryString().enableripple,
+        platform;
+
+    if (enableRippleArg) {
+        enableRippleArg = enableRippleArg.split('-');
+        platform = spec.get(enableRippleArg[0], enableRippleArg[1]);
+        if (platform) {
+            requestedPlatform = { name: platform.id, version: platform.version };
+        }
     }
+
+    return requestedPlatform;
+}
+
+function _validatePlatform(platform, defaultPlatform) {
+    if (!platform ||
+        !spec[platform.name] ||
+        !spec[platform.name][platform.version] ||
+        (spec[platform.name][platform.version] && !spec[platform.name][platform.version].objects)) {
+        return defaultPlatform;
+    }
+    return platform;
 }
 
 function _getPlatform() {
@@ -39,20 +56,16 @@
 _self = {
     initialize: function () {
         var firstAvailablePlatform = utils.map(this.getList(), function (platform) {
-                    return utils.map(platform, function (details, version) {
-                        return {name: details.id, version: version};
-                    })[0];
-                })[0];
+            return utils.map(platform, function (details, version) {
+                return {name: details.id, version: version};
+            })[0];
+        })[0];
 
-        _current = db.retrieveObject("api-key");
+        _current = _getRequestedPlatform() || db.retrieveObject("api-key") || firstAvailablePlatform;
+        _current = _validatePlatform(_current, firstAvailablePlatform);
+        db.saveObject("api-key", _current);
 
-        if (_current) {
-            _checkForDeprecatedPlatforms(firstAvailablePlatform);
-        } else {
-            _current = firstAvailablePlatform;
-        }
-
-        _console.prefix = _getPlatform().name;
+        _console.prefix = _current.name;
     },
 
     getList: function () {
diff --git a/lib/ripple/platform/cordova/1.6/bridge.js b/lib/ripple/platform/cordova/1.6/bridge.js
deleted file mode 100644
index 03a9e6b..0000000
--- a/lib/ripple/platform/cordova/1.6/bridge.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- *  Copyright 2011 Research In Motion Limited.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-var _prompt = require('ripple/ui/plugins/exec-dialog');
-
-module.exports = {
-    exec: function (success, fail, service, action, args) {
-        var emulator = {
-            "Network Status": require('ripple/platform/cordova/1.6/bridge/network'),
-            "NetworkStatus": require('ripple/platform/cordova/1.6/bridge/network'),
-            "Device": require('ripple/platform/cordova/1.6/bridge/device'),
-            "Contacts": require('ripple/platform/cordova/1.6/bridge/contacts'),
-            "Accelerometer": require('ripple/platform/cordova/1.6/bridge/accelerometer'),
-            "Compass": require('ripple/platform/cordova/1.6/bridge/compass'),
-            "Notification": require('ripple/platform/cordova/1.6/bridge/notification')
-        };
-
-        try {
-            emulator[service][action](success, fail, args);
-        }
-        catch (e) {
-            console.log("missing exec:" + service + "." + action);
-            console.log(args);
-            console.log(e);
-            console.log(e.stack);
-            //TODO: this should really not log the above lines, but they are very nice for development right now
-            _prompt.show(service, action, success, fail);
-        }
-    }
-};
diff --git a/lib/ripple/platform/cordova/1.6/spec.js b/lib/ripple/platform/cordova/1.6/spec.js
deleted file mode 100644
index 6dfccf6..0000000
--- a/lib/ripple/platform/cordova/1.6/spec.js
+++ /dev/null
@@ -1,85 +0,0 @@
-function loadWebworks(win, device) {
-    var builder = require('ripple/platform/builder'),
-        platform = device.id === "Playbook" || device.id === "Colt" ? "tablet" : "handset",
-        webworks = require('ripple/platform/webworks.' + platform + '/2.0.0/spec');
-
-    builder.build(webworks.objects).into(win);
-    builder.build(webworks.objects).into(window);
-}
-
-module.exports = {
-    id: "cordova",
-    version: "1.6",
-    name: "Apache Cordova",
-    type: "platform",
-
-    config: require('ripple/platform/phonegap/1.0/spec/config'),
-    device: require('ripple/platform/phonegap/1.0/spec/device'),
-    ui: require('ripple/platform/phonegap/1.0/spec/ui'),
-    events: require('ripple/platform/phonegap/1.0/spec/events'),
-
-    initialize: function (win) {
-        var honeypot = require('ripple/honeypot'),
-            devices = require('ripple/devices'),
-            device = devices.getCurrentDevice(),
-            bridge = require('ripple/platform/cordova/1.6/bridge'),
-            cordova,
-            get = function () {
-                return cordova;
-            },
-            set = function (orig) {
-                if (cordova) {
-                    return;
-                }
-
-                cordova = orig;
-
-                cordova.define.remove("cordova/exec");
-                cordova.define("cordova/exec", function (require, exports, module) {
-                    module.exports = bridge.exec;
-                });
-
-                cordova.UsePolling = true;
-
-                //do nothing here as we will just call the callbacks ourselves
-                cordova.define.remove("cordova/plugin/android/polling");
-                cordova.define("cordova/plugin/android/polling", function (require, exports, module) {
-                    module.exports = function () {};
-                });
-
-                var builder = cordova.require('cordova/builder'),
-                    base = cordova.require('cordova/common');
-
-                //HACK: Overwrite all the things, handles when cordova.js executes before we start booting
-                builder.build(base.objects).intoAndClobber(window);
-            };
-
-        if (device.manufacturer === "Research In Motion") {
-            loadWebworks(win, device);
-        }
-
-        honeypot.monitor(win, "cordova").andRun(get, set);
-        win._nativeReady = true;
-    },
-
-    objects: {
-        org: {
-            children: {
-                apache: {
-                    children: {
-                        cordova: {
-                            children: {
-                                Logger: {
-                                    path: "cordova/1.6/logger"
-                                },
-                                JavaPluginManager: {
-                                    path: "cordova/1.6/JavaPluginManager"
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-};
diff --git a/lib/ripple/platform/cordova/1.6/JavaPluginManager.js b/lib/ripple/platform/cordova/2.0.0/JavaPluginManager.js
similarity index 100%
rename from lib/ripple/platform/cordova/1.6/JavaPluginManager.js
rename to lib/ripple/platform/cordova/2.0.0/JavaPluginManager.js
diff --git a/lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js b/lib/ripple/platform/cordova/2.0.0/MediaError.js
similarity index 62%
copy from lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js
copy to lib/ripple/platform/cordova/2.0.0/MediaError.js
index 38aa21a..51cebea 100644
--- a/lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js
+++ b/lib/ripple/platform/cordova/2.0.0/MediaError.js
@@ -13,9 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-//blackberry.invoke.BrowserArguments ( url : String ,  [transport : blackberry.identity.Transport ] )
-module.exports = function (url, transport) {
-    return {
-        url: url
-    };
+var MediaError = function (code, msg) {
+    this.code = (code !== undefined ? code : null);
+    this.message = msg || "";
 };
+
+MediaError.MEDIA_ERR_NONE_ACTIVE    = 0;
+MediaError.MEDIA_ERR_ABORTED        = 1;
+MediaError.MEDIA_ERR_NETWORK        = 2;
+MediaError.MEDIA_ERR_DECODE         = 3;
+MediaError.MEDIA_ERR_NONE_SUPPORTED = 4;
+
+module.exports = MediaError;
diff --git a/lib/ripple/platform/cordova/2.0.0/bridge.js b/lib/ripple/platform/cordova/2.0.0/bridge.js
new file mode 100644
index 0000000..2542452
--- /dev/null
+++ b/lib/ripple/platform/cordova/2.0.0/bridge.js
@@ -0,0 +1,49 @@
+/*
+ *  Copyright 2011 Research In Motion Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var _prompt = require('ripple/ui/plugins/exec-dialog');
+
+module.exports = {
+    exec: function (success, fail, service, action, args) {
+        var emulator = {
+            "App": require('ripple/platform/cordova/2.0.0/bridge/app'),
+            "Accelerometer": require('ripple/platform/cordova/2.0.0/bridge/accelerometer'),
+            "Compass": require('ripple/platform/cordova/2.0.0/bridge/compass'),
+            "Camera": require('ripple/platform/cordova/2.0.0/bridge/camera'),
+            "Capture": require('ripple/platform/cordova/2.0.0/bridge/capture'),
+            "Contacts": require('ripple/platform/cordova/2.0.0/bridge/contacts'),
+            "Debug Console": require('ripple/platform/cordova/2.0.0/bridge/console'),
+            "Device": require('ripple/platform/cordova/2.0.0/bridge/device'),
+            "File": require('ripple/platform/cordova/2.0.0/bridge/file'),
+            "Geolocation": require('ripple/platform/cordova/2.0.0/bridge/geolocation'),
+            "Media": require('ripple/platform/cordova/2.0.0/bridge/media'),
+            "Network Status": require('ripple/platform/cordova/2.0.0/bridge/network'),
+            "NetworkStatus": require('ripple/platform/cordova/2.0.0/bridge/network'),
+            "Notification": require('ripple/platform/cordova/2.0.0/bridge/notification')
+        };
+
+        try {
+            emulator[service][action](success, fail, args);
+        }
+        catch (e) {
+            console.log("missing exec:" + service + "." + action);
+            console.log(args);
+            console.log(e);
+            console.log(e.stack);
+            //TODO: this should really not log the above lines, but they are very nice for development right now
+            _prompt.show(service, action, success, fail);
+        }
+    }
+};
diff --git a/lib/ripple/platform/cordova/1.6/bridge/accelerometer.js b/lib/ripple/platform/cordova/2.0.0/bridge/accelerometer.js
similarity index 65%
rename from lib/ripple/platform/cordova/1.6/bridge/accelerometer.js
rename to lib/ripple/platform/cordova/2.0.0/bridge/accelerometer.js
index bbfec57..434d4d0 100644
--- a/lib/ripple/platform/cordova/1.6/bridge/accelerometer.js
+++ b/lib/ripple/platform/cordova/2.0.0/bridge/accelerometer.js
@@ -13,30 +13,33 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-var utils = require('ripple/utils'),
-    event = require('ripple/event'),
-    _current = {x: 0, y: 0, z: 0, timestamp: (new Date()).getTime()};
+var event = require('ripple/event'),
+    _success,
+    _error,
+    _current = {x: 0, y: 0, z: 0, timestamp: (new Date()).getTime()},
+    _interval;
 
 event.on("AccelerometerInfoChangedEvent", function (accelerometerInfo) {
-    _current.x = accelerometerInfo.accelerationIncludingGravity.x / 9.8;
-    _current.y = accelerometerInfo.accelerationIncludingGravity.y / 9.8;
-    _current.z = accelerometerInfo.accelerationIncludingGravity.z / 9.8;
+    _current.x = accelerometerInfo.accelerationIncludingGravity.x;
+    _current.y = accelerometerInfo.accelerationIncludingGravity.y;
+    _current.z = accelerometerInfo.accelerationIncludingGravity.z;
     _current.timestamp = (new Date()).getTime();
 });
 
 module.exports = {
-    getTimeout: function (success, error, args) {
-        return success && success(1);
+    start: function (success, error, args) {
+        _success = success;
+        _error = error;
+        // Possible HACK? update the timestamp of the last data to something current
+        _interval = window.setInterval(function () {
+            _current.timestamp = (new Date()).getTime();
+            _success(_current);
+        }, 50);
     },
 
-    setTimeout: function (success, error, args) {
-        //do nothing
-        return success && success();
-    },
-
-    getAcceleration: function (success, error, args) {
-        // TODO: build facility to trigger onError() from emulator
-        // see pivotal item: https://www.pivotaltracker.com/story/show/7040343
-        success(utils.copy(_current));
+    stop: function () {
+        _success = null;
+        _error = null;
+        window.clearInterval(_interval);
     }
 };
diff --git a/lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js b/lib/ripple/platform/cordova/2.0.0/bridge/app.js
similarity index 75%
copy from lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js
copy to lib/ripple/platform/cordova/2.0.0/bridge/app.js
index 38aa21a..ea6f86f 100644
--- a/lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js
+++ b/lib/ripple/platform/cordova/2.0.0/bridge/app.js
@@ -13,9 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-//blackberry.invoke.BrowserArguments ( url : String ,  [transport : blackberry.identity.Transport ] )
-module.exports = function (url, transport) {
-    return {
-        url: url
-    };
+var camera = require('ripple/ui/plugins/camera'),
+    event = require('ripple/event');
+
+module.exports = {
+    show: function (success, error, args) {
+        return success && success();
+    }
 };
diff --git a/lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js b/lib/ripple/platform/cordova/2.0.0/bridge/camera.js
similarity index 62%
copy from lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js
copy to lib/ripple/platform/cordova/2.0.0/bridge/camera.js
index 38aa21a..41b5a3e 100644
--- a/lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js
+++ b/lib/ripple/platform/cordova/2.0.0/bridge/camera.js
@@ -13,9 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-//blackberry.invoke.BrowserArguments ( url : String ,  [transport : blackberry.identity.Transport ] )
-module.exports = function (url, transport) {
-    return {
-        url: url
-    };
+var camera = require('ripple/ui/plugins/camera'),
+    event = require('ripple/event');
+
+module.exports = {
+    takePicture: function (success, error, args) {
+        event.once("captured-image", function (uri, file) {
+            success(uri);
+        });
+        camera.show();
+    },
+    cleanup: function (success, error, args) {
+        success();
+    }
 };
diff --git a/lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js b/lib/ripple/platform/cordova/2.0.0/bridge/capture.js
similarity index 64%
copy from lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js
copy to lib/ripple/platform/cordova/2.0.0/bridge/capture.js
index 38aa21a..54251e3 100644
--- a/lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js
+++ b/lib/ripple/platform/cordova/2.0.0/bridge/capture.js
@@ -13,9 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-//blackberry.invoke.BrowserArguments ( url : String ,  [transport : blackberry.identity.Transport ] )
-module.exports = function (url, transport) {
-    return {
-        url: url
-    };
+
+var camera = require('ripple/ui/plugins/camera'),
+    event = require('ripple/event');
+
+module.exports = {
+    captureImage: function (success, error, args) {
+        event.once("captured-image", function (uri, file) {
+            file.fullPath = uri;
+            success([file]);
+        });
+        camera.show();
+    }
 };
diff --git a/lib/ripple/platform/cordova/1.6/bridge/compass.js b/lib/ripple/platform/cordova/2.0.0/bridge/compass.js
similarity index 77%
rename from lib/ripple/platform/cordova/1.6/bridge/compass.js
rename to lib/ripple/platform/cordova/2.0.0/bridge/compass.js
index 10c5354..63e0f2a 100644
--- a/lib/ripple/platform/cordova/1.6/bridge/compass.js
+++ b/lib/ripple/platform/cordova/2.0.0/bridge/compass.js
@@ -16,18 +16,18 @@
 var geo = require('ripple/geo');
 
 module.exports = {
-    getTimeout: function (success, error, args) {
-        return success && success(1);
-    },
-
-    setTimeout: function (success, error, args) {
-        //do nothing
-        return success && success();
-    },
-
     getHeading: function (success, error, args) {
         // TODO: build facility to trigger onError() from emulator
         // see pivotal item: https://www.pivotaltracker.com/story/show/7040343
-        success(geo.getPositionInfo().heading);
+        success({
+            magneticHeading: geo.getPositionInfo().heading, 
+            trueHeading: geo.getPositionInfo().heading, 
+            headingAccuracy: 100, 
+            timestamp: new Date().getSeconds()
+        });
+    },
+
+    stopHeading: function () { 
+        //do nothing
     }
 };
diff --git a/lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js b/lib/ripple/platform/cordova/2.0.0/bridge/console.js
similarity index 76%
rename from lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js
rename to lib/ripple/platform/cordova/2.0.0/bridge/console.js
index 38aa21a..ef7aa54 100644
--- a/lib/ripple/platform/webworks.bb10/1.0.0/BrowserArguments.js
+++ b/lib/ripple/platform/cordova/2.0.0/bridge/console.js
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-//blackberry.invoke.BrowserArguments ( url : String ,  [transport : blackberry.identity.Transport ] )
-module.exports = function (url, transport) {
-    return {
-        url: url
-    };
+
+module.exports = {
+    log: function (win, fail, args) {
+        console.log(args[0]);
+    }
 };
diff --git a/lib/ripple/platform/cordova/1.6/bridge/contacts.js b/lib/ripple/platform/cordova/2.0.0/bridge/contacts.js
similarity index 100%
rename from lib/ripple/platform/cordova/1.6/bridge/contacts.js
rename to lib/ripple/platform/cordova/2.0.0/bridge/contacts.js
diff --git a/lib/ripple/platform/cordova/1.6/bridge/device.js b/lib/ripple/platform/cordova/2.0.0/bridge/device.js
similarity index 100%
rename from lib/ripple/platform/cordova/1.6/bridge/device.js
rename to lib/ripple/platform/cordova/2.0.0/bridge/device.js
diff --git a/lib/ripple/platform/cordova/2.0.0/bridge/file.js b/lib/ripple/platform/cordova/2.0.0/bridge/file.js
new file mode 100644
index 0000000..7a2d4a4
--- /dev/null
+++ b/lib/ripple/platform/cordova/2.0.0/bridge/file.js
@@ -0,0 +1,349 @@
+// HACK: fs keeps a reference to the last-used FileSystem requested via requestFileSystem
+// this is a hack because if you keep switching between TEMPORARY vs. PERSISTENT file systems requested,
+// and run Cordova File API methods, no parameter is passed into exec specifying the underlying File System.
+// This should be fixed in Cordova!
+var fs, 
+    topCordova = window.top.require('ripple/platform/cordova/2.0.0/spec'),
+    BlobBuilder = (window.BlobBuilder || window.WebKitBlobBuilder),
+    rlfsu = window.webkitResolveLocalFileSystemURL;
+
+function cleanPath(path) {
+    while (path[0] && path[0] === '/') {
+        path = path.substr(1);
+    }
+    return path;
+}
+
+module.exports = {
+    requestFileSystem: function (win, fail, args) {
+        // HACK: may not be webkit
+        var rfs = window.webkitRequestFileSystem,
+            type = args[0],
+            size = args[1];
+
+        // HACK: assume any FS requested over a gig in size will throw an error
+        if (size > (1024 * 1024 * 1024 /* gigabyte */)) {
+            if (fail) fail(FileError.QUOTA_EXCEEDED_ERR);
+        } else {
+            return rfs(type, size, function (effes) {
+                fs = effes;
+                win(effes);
+            }, fail);
+        }
+    },
+    resolveLocalFileSystemURI: function (win, fail, args) {
+        var uri = args[0],
+            fulluri = fs.root.toURL();
+
+        // HACK: iOS-specific bs right here. See lib/ios/plugin/ios/Entry.js in cordova.js for details
+        // Cordova badly needs a unified File System abstraction.
+        if (uri.indexOf("file://localhost") === 0) {
+            uri = uri.substr(16);
+        }
+        uri = cleanPath(uri);
+
+        fulluri += uri;
+
+        return rlfsu(fulluri, function (entry) {
+            if (win) win(entry);
+        }, function (error) {
+            if (fail) fail(error.code);
+        });
+    },
+    getFile: function (win, fail, args) {
+        var path = args[0],
+            filename = args[1],
+            options = args[2],
+            file = '';
+
+        path = cleanPath(path);
+        filename = cleanPath(filename);
+
+        if (path) {
+            file = path + '/';
+        }
+        file += filename;
+
+        fs.root.getFile(file, options, function (entry) {
+            if (win) win(entry);
+        }, function (err) {
+            if (fail) fail(err.code);
+        });
+    },
+    remove: function (win, fail, args) {
+        var file = args[0];
+        rlfsu(fs.root.toURL() + file, function (entry) {
+            entry.remove(function () {
+                if (win) win();
+            }, function (err) {
+                if (fail) fail(err.code);
+            });
+        }, fail);
+    },
+    readEntries: function (win, fail, args) {
+        var root = fs.root.toURL(),
+            path = args[0],
+            reader;
+
+        path = cleanPath(path);
+        path = root + path;
+
+        rlfsu(path, function (entry) {
+            reader = entry.createReader();
+            reader.readEntries(function (entries) {
+                if (win) win(entries);
+            }, function (err) {
+                if (fail) fail(err.code);
+            });
+        }, function (err) {
+            if (fail) fail(err.code);
+        });
+    },
+    getDirectory: function (win, fail, args) {
+        var path = args[0],
+            filename = args[1],
+            options = args[2],
+            file = '';
+
+        path = cleanPath(path);
+        filename = cleanPath(filename);
+
+        if (path) {
+            file = path + '/';
+        }
+        file += filename;
+
+        fs.root.getDirectory(file, options, function (entry) {
+            if (win) win(entry);
+        }, function (err) {
+            if (fail) fail(err.code);
+        });
+    },
+    removeRecursively: function (win, fail, args) {
+        var root = fs.root.toURL(),
+            path = args[0];
+
+        path = cleanPath(path);
+
+        rlfsu(root + path, function (dirEntry) {
+            dirEntry.removeRecursively(function () {
+                if (win) win();
+            }, function (err) {
+                if (fail) fail(err.code);
+            });
+        }, function (err) {
+            if (fail) fail(err.code);
+        });
+    },
+    getFileMetadata: function (win, fail, args) {
+        var path = args[0],
+            root = fs.root.toURL();
+
+        path = cleanPath(path);
+
+        rlfsu(root + path, function (entry) {
+            entry.file(function (file) {
+                if (win) win(file);
+            }, function (err) {
+                if (fail) fail(err.code);
+            });
+        }, function (err) {
+            if (fail) fail(err.code);
+        });
+    },
+    getMetadata: function (win, fail, args) {
+        var path = args[0],
+            root = fs.root.toURL();
+
+        path = cleanPath(path);
+        
+        rlfsu(root + path, function (entry) {
+            entry.getMetadata(function (data) {
+                if (win) win(data);
+            }, function (err) {
+                if (fail) fail(err.code);
+            });
+        }, function (err) {
+            if (fail) fail(err.code);
+        });
+    },
+    getParent: function (win, fail, args) {
+        var path = args[0],
+            root = fs.root.toURL();
+
+        path = cleanPath(path);
+
+        rlfsu(root + path, function (entry) {
+            entry.getParent(function (dirEntry) {
+                if (win) win(dirEntry);
+            }, function (err) {
+                if (fail) fail(err.code);
+            });
+        }, function (err) {
+            if (fail) fail(err.code);
+        });
+    },
+    copyTo: function (win, fail, args) {
+        var src = args[0],
+            parent = args[1],
+            name = args[2],
+            root = fs.root.toURL();
+
+        parent = cleanPath(parent);
+        src = cleanPath(src);
+
+        // get the directoryentry that we will copy TO
+        rlfsu(root + parent, function (parentDirToCopyTo) {
+            rlfsu(root + src, function (sourceDir) {
+                sourceDir.copyTo(parentDirToCopyTo, name, function (newEntry) {
+                    if (win) win(newEntry);
+                }, function (err) {
+                    if (fail) fail(err.code);
+                });
+            }, function (err) {
+                if (fail) fail(err.code);
+            });
+        }, function (err) {
+            if (fail) fail(err.code);
+        });
+    },
+    moveTo: function (win, fail, args) {
+        var src = args[0],
+            parent = args[1],
+            name = args[2],
+            root = fs.root.toURL();
+
+        parent = cleanPath(parent);
+        src = cleanPath(src);
+
+        // get the directoryentry that we will move TO
+        rlfsu(root + parent, function (parentDirToMoveTo) {
+            rlfsu(root + src, function (sourceDir) {
+                sourceDir.moveTo(parentDirToMoveTo, name, function (newEntry) {
+                    if (win) win(newEntry);
+                }, function (err) {
+                    if (fail) fail(err.code);
+                });
+            }, function (err) {
+                if (fail) fail(err.code);
+            });
+        }, function (err) {
+            if (fail) fail(err.code);
+        });
+    },
+    write: function (win, fail, args) {
+        var file = args[0],
+            text = args[1],
+            position = args[2],
+            sourcepath,
+            bb = new BlobBuilder();
+
+        // Format source path
+        sourcepath = (file.fullPath ? file.fullPath : '') + file.name;
+        sourcepath = cleanPath(sourcepath);
+
+        // Create a blob for the text to be written
+        bb.append(text);
+
+        // Get the FileEntry, create if necessary
+        fs.root.getFile(sourcepath, {create: true}, function (entry) {
+            // Create a FileWriter for this entry
+            entry.createWriter(function (writer) {
+                writer.onwriteend = function (progressEvt) {
+                    if (win) win(progressEvt.total);
+                };
+                writer.onerror = function (err) {
+                    if (fail) fail(err.code);
+                };
+
+                if (position && position > 0) {
+                    writer.seek(position);
+                }
+                writer.write(bb.getBlob('text/plain'));
+            }, function (err) {
+                if (fail) fail(err.code);
+            });
+        }, function (err) {
+            if (fail) fail(err.code);
+        });
+    },
+    readAsText: function (win, fail, args) {
+        var path = args[0],
+            encoding = args[1],
+            FileReader = topCordova.nativeMethods.FileReader,
+            fr = new FileReader();
+
+        // Set up FileReader events
+        fr.onerror = function (err) {
+            if (fail) fail(err.code);
+        };
+        fr.onload = function (evt) {
+            if (win) win(evt.target.result);
+        };
+
+        path = cleanPath(path);
+
+        fs.root.getFile(path, {create: false}, function (entry) {
+            entry.file(function (blob) {
+                fr.readAsText(blob, encoding);
+            }, function (err) {
+                if (fail) fail(err.code);
+            });
+        }, function (err) {
+            if (fail) fail(err.code);
+        });
+    },
+    readAsDataURL: function (win, fail, args) {
+        var path = args[0],
+            FileReader = topCordova.nativeMethods.FileReader,
+            fr = new FileReader();
+
+        // Set up FileReader events
+        fr.onerror = function (err) {
+            if (fail) fail(err.code);
+        };
+        fr.onload = function (evt) {
+            if (win) win(evt.target.result);
+        };
+
+        path = cleanPath(path);
+
+        fs.root.getFile(path, {create: false}, function (entry) {
+            entry.file(function (blob) {
+                fr.readAsDataURL(blob);
+            }, function (err) {
+                if (fail) fail(err.code);
+            });
+        }, function (err) {
+            if (fail) fail(err.code);
+        });
+    },
+    truncate: function (win, fail, args) {
+        var file = args[0],
+            position = args[1],
+            sourcepath;
+
+        // Format source path
+        sourcepath = (file.fullPath ? file.fullPath : '') + file.name;
+        sourcepath = cleanPath(sourcepath);
+
+        // Get the FileEntry, create if necessary
+        fs.root.getFile(sourcepath, {create: false}, function (entry) {
+            // Create a FileWriter for this entry
+            entry.createWriter(function (writer) {
+                writer.onwriteend = function (progressEvt) {
+                    if (win) win(progressEvt.target.length);
+                };
+                writer.onerror = function (err) {
+                    if (fail) fail(err.code);
+                };
+
+                writer.truncate(position);
+            }, function (err) {
+                if (fail) fail(err.code);
+            });
+        }, function (err) {
+            if (fail) fail(err.code);
+        });
+    }
+};
diff --git a/lib/ripple/platform/cordova/2.0.0/bridge/geolocation.js b/lib/ripple/platform/cordova/2.0.0/bridge/geolocation.js
new file mode 100644
index 0000000..b6219f4
--- /dev/null
+++ b/lib/ripple/platform/cordova/2.0.0/bridge/geolocation.js
@@ -0,0 +1,81 @@
+/*
+ *  Copyright 2011 Research In Motion Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var event = require('ripple/event'),
+    geo = require('ripple/geo'),
+    utils = require('ripple/utils'),
+    PositionError = require('ripple/platform/w3c/1.0/PositionError'),
+    _watches = {},
+    _current = {
+        "latitude": 43.465187,
+        "longitude": -80.522372,
+        "altitude": 100,
+        "accuracy": 150,
+        "altitudeAccuracy": 80,
+        "heading": 0,
+        "velocity": 0,
+    },
+    _error;
+
+function _getCurrentPosition(win, fail) {
+    if (geo.timeout) {
+        if (fail) {
+            var positionError = new PositionError();
+
+            positionError.code = PositionError.TIMEOUT;
+            positionError.message = "postion timed out";
+            fail(positionError);
+        }
+    }
+    else {
+        win(geo.getPositionInfo());
+    }
+}
+
+event.on("PositionInfoUpdatedEvent", function (pi) {
+    _current.latitude = pi.latitude;
+    _current.longitude = pi.longitude;
+    _current.altitude = pi.altitude;
+    _current.accuracy = pi.accuracy;
+    _current.altitudeAccuracy = pi.altitudeAccuracy;
+    _current.heading = pi.heading;
+    _current.velocity = pi.speed;
+
+    utils.forEach(_watches, function (watch) {
+        try {
+            _getCurrentPosition(watch.win, watch.fail);
+        } catch (e) {
+            console.log(e);
+        }
+    });
+});
+
+module.exports = {
+    getLocation: function (success, error, args) {
+        _getCurrentPosition(success, error);
+    },
+
+    addWatch: function (success, error, args) {
+        _watches[args[0]] = {
+            win: success,
+            fail: error
+        };
+        _getCurrentPosition(success, error);
+    },
+
+    clearWatch: function (id) {
+        delete _watches[id];
+    }
+};
diff --git a/lib/ripple/platform/cordova/2.0.0/bridge/media.js b/lib/ripple/platform/cordova/2.0.0/bridge/media.js
new file mode 100644
index 0000000..e185451
--- /dev/null
+++ b/lib/ripple/platform/cordova/2.0.0/bridge/media.js
@@ -0,0 +1,205 @@
+/*
+ *  Copyright 2011 Research In Motion Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var audioObjects = {},
+    noop = function () {};
+
+function createAudio(id, src, error) {
+    var a = new Audio();
+    a.addEventListener("error", function (e) {
+        error(new window.MediaError(1, e.toString()));
+    });
+    a.addEventListener("durationchange", function () {
+        //HACK: I don't like this but best way for us to update the duration
+        cordova.require("cordova/plugin/Media").onStatus(id, 2, this.duration);
+    });
+    a.src = src;
+
+    return a;
+}
+
+module.exports = {
+    create: function (success, error, args) {
+        if (!args.length) {
+            error("Media Object id was not sent in arguments");
+            return;
+        }
+
+        var id = args[0],
+            src = args[1];
+
+        error = error || noop;
+        success = success || noop;
+
+        audioObjects[id] = createAudio(id, src, error);
+        success();
+    },
+    startPlayingAudio: function (success, error, args) {
+        error = error || noop;
+        success = success || noop;
+        if (!args.length) {
+            error("Media Object id was not sent in arguments");
+            return;
+        }
+
+        var id = args[0],
+            audio = audioObjects[id];
+
+        if (args.length === 1) {
+            error("Media source argument not found");
+            return;
+        }
+
+        if (audio) {
+            audio.pause();
+            audioObjects[id] = undefined;
+        }
+
+        audio = audioObjects[id] = createAudio(id, args[1], error);
+        audio.play();
+
+        success();
+    },
+    stopPlayingAudio: function (success, error, args) {
+        error = error || noop;
+        success = success || noop;
+        if (!args.length) {
+            error("Media Object id was not sent in arguments");
+            return;
+        }
+
+        var id = args[0],
+            audio = audioObjects[id];
+
+        if (!audio) {
+            error("Audio Object has not been initialized");
+            return;
+        }
+
+        audio.pause();
+        audioObjects[id] = undefined;
+
+        success();
+    },
+    seekToAudio: function (success, error, args) {
+        error = error || noop;
+        success = success || noop;
+        if (!args.length) {
+            error("Media Object id was not sent in arguments");
+            return;
+        }
+
+        var id = args[0],
+            audio = audioObjects[id];
+
+        if (!audio) {
+            error("Audio Object has not been initialized");
+            return;
+        } else if (args.length === 1) {
+            error("Media seek time argument not found");
+            return;
+        } else {
+            try {
+                audio.currentTime = args[1];
+            } catch (e) {
+                error("Error seeking audio: " + e);
+            }
+        }
+
+        success();
+    },
+    pausePlayingAudio: function (success, error, args) {
+        error = error || noop;
+        success = success || noop;
+        if (!args.length) {
+            error("Media Object id was not sent in arguments");
+            return;
+        }
+
+        var id = args[0],
+            audio = audioObjects[id];
+
+        if (!audio) {
+            error("Audio Object has not been initialized");
+            return;
+        }
+
+        audio.pause();
+        success();
+    },
+    getCurrentPositionAudio: function (success, error, args) {
+        error = error || noop;
+        success = success || noop;
+        if (!args.length) {
+            error("Media Object id was not sent in arguments");
+            return;
+        }
+
+        var id = args[0],
+            audio = audioObjects[id];
+
+        if (!audio) {
+            error("Audio Object has not been initialized");
+            return;
+        }
+
+        success(audio.currentTime);
+    },
+    getDuration: function (success, error, args) {
+        error = error || noop;
+        success = success || noop;
+        if (!args.length) {
+            error("Media Object id was not sent in arguments");
+            return;
+        }
+
+        var id = args[0],
+            audio = audioObjects[id];
+
+        if (!audio) {
+            error("Audio Object has not been initialized");
+            return;
+        }
+
+        success(audio.duration);
+    },
+    startRecordingAudio: function (success, error, args) {
+        error = error || noop;
+        error("Not supported");
+    },
+    stopRecordingAudio: function (success, error, args) {
+        error = error || noop;
+        error("Not supported");
+    },
+    release: function (success, error, args) {
+        error = error || noop;
+        success = success || noop;
+        if (!args.length) {
+            error("Media Object id was not sent in arguments");
+            return;
+        }
+
+        var id = args[0],
+            audio = audioObjects[id];
+
+        if (audio) {
+            audioObjects[id] = undefined;
+            audio.src = undefined;
+            //delete audio;
+        }
+
+        success();
+    }
+};
diff --git a/lib/ripple/platform/cordova/1.6/bridge/network.js b/lib/ripple/platform/cordova/2.0.0/bridge/network.js
similarity index 100%
rename from lib/ripple/platform/cordova/1.6/bridge/network.js
rename to lib/ripple/platform/cordova/2.0.0/bridge/network.js
diff --git a/lib/ripple/platform/cordova/1.6/bridge/notification.js b/lib/ripple/platform/cordova/2.0.0/bridge/notification.js
similarity index 100%
rename from lib/ripple/platform/cordova/1.6/bridge/notification.js
rename to lib/ripple/platform/cordova/2.0.0/bridge/notification.js
diff --git a/lib/ripple/platform/cordova/1.6/logger.js b/lib/ripple/platform/cordova/2.0.0/logger.js
similarity index 100%
rename from lib/ripple/platform/cordova/1.6/logger.js
rename to lib/ripple/platform/cordova/2.0.0/logger.js
diff --git a/lib/ripple/platform/cordova/2.0.0/spec.js b/lib/ripple/platform/cordova/2.0.0/spec.js
new file mode 100644
index 0000000..ce92f99
--- /dev/null
+++ b/lib/ripple/platform/cordova/2.0.0/spec.js
@@ -0,0 +1,107 @@
+function loadWebworks(win, device) {
+    var builder = require('ripple/platform/builder'),
+        platform = device.id === "Playbook" || device.id === "Colt" ? "tablet" : "handset",
+        webworks = require('ripple/platform/webworks.' + platform + '/2.0.0/spec');
+
+    builder.build(webworks.objects).into(win);
+    builder.build(webworks.objects).into(window);
+}
+
+module.exports = {
+    id: "cordova",
+    version: "2.0.0",
+    name: "Apache Cordova",
+    type: "platform",
+    nativeMethods: {},
+
+    config: require('ripple/platform/cordova/2.0.0/spec/config'),
+    device: require('ripple/platform/cordova/2.0.0/spec/device'),
+    ui: require('ripple/platform/cordova/2.0.0/spec/ui'),
+    events: require('ripple/platform/cordova/2.0.0/spec/events'),
+
+    initialize: function (win) {
+        var honeypot = require('ripple/honeypot'),
+            devices = require('ripple/devices'),
+            device = devices.getCurrentDevice(),
+            bridge = require('ripple/platform/cordova/2.0.0/bridge'),
+            cordova,
+            topCordova = window.top.require('ripple/platform/cordova/2.0.0/spec'),
+            get = function () {
+                return cordova;
+            },
+            set = function (orig) {
+                if (cordova) {
+                    return;
+                }
+
+                cordova = orig;
+
+                cordova.define.remove("cordova/exec");
+                cordova.define("cordova/exec", function (require, exports, module) {
+                    module.exports = bridge.exec;
+                });
+
+                cordova.UsePolling = true;
+
+                //do nothing here as we will just call the callbacks ourselves
+                cordova.define.remove("cordova/plugin/android/polling");
+                cordova.define("cordova/plugin/android/polling", function (require, exports, module) {
+                    module.exports = function () {};
+                });
+
+                var builder = cordova.require('cordova/builder'),
+                    allTheThings = window,
+                    base = cordova.require('cordova/common'),
+                    iosPlugin;
+
+                //HACK: Overwrite all the things, handles when cordova.js executes before we start booting
+                builder.build(base.objects).intoAndClobber(allTheThings);
+                cordova.require('cordova/channel').onNativeReady.fire();
+                //  DIRTY HACK: once cordova is cleaned up, we do not
+                //  need this.
+                // reference issue: https://issues.apache.org/jira/browse/CB-1013
+                try {
+                    iosPlugin = cordova.require('cordova/plugin/ios/device');
+                    bridge.exec(function (info) {
+                        iosPlugin.setInfo(info);
+                    }, null, 'Device', 'getDeviceInfo', []);
+                } catch (e) {
+                    cordova.require('cordova/channel').onCordovaInfoReady.fire();
+                }
+            };
+
+        if (window.FileReader && !topCordova.nativeMethods.FileReader) {
+            topCordova.nativeMethods.FileReader = window.FileReader;
+        }
+
+        if (device.manufacturer === "Research In Motion") {
+            loadWebworks(win, device);
+        }
+
+        honeypot.monitor(win, "cordova").andRun(get, set);
+    },
+
+    objects: {
+        MediaError: {
+            path: "cordova/2.0.0/MediaError"
+        },
+        org: {
+            children: {
+                apache: {
+                    children: {
+                        cordova: {
+                            children: {
+                                Logger: {
+                                    path: "cordova/2.0.0/logger"
+                                },
+                                JavaPluginManager: {
+                                    path: "cordova/2.0.0/JavaPluginManager"
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+};
diff --git a/lib/ripple/platform/phonegap/1.0/spec/config.js b/lib/ripple/platform/cordova/2.0.0/spec/config.js
similarity index 100%
copy from lib/ripple/platform/phonegap/1.0/spec/config.js
copy to lib/ripple/platform/cordova/2.0.0/spec/config.js
diff --git a/lib/ripple/platform/phonegap/1.0/spec/device.js b/lib/ripple/platform/cordova/2.0.0/spec/device.js
similarity index 100%
copy from lib/ripple/platform/phonegap/1.0/spec/device.js
copy to lib/ripple/platform/cordova/2.0.0/spec/device.js
diff --git a/lib/ripple/platform/cordova/2.0.0/spec/events.js b/lib/ripple/platform/cordova/2.0.0/spec/events.js
new file mode 100644
index 0000000..bca8b56
--- /dev/null
+++ b/lib/ripple/platform/cordova/2.0.0/spec/events.js
@@ -0,0 +1,47 @@
+/*
+ *  Copyright 2011 Research In Motion Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+function _fires(name, data) {
+    return function () {
+        cordova.fireDocumentEvent(name, data);
+    };
+}
+
+module.exports = {
+    "deviceready": {
+        callback: _fires("deviceready")
+    },
+    "backbutton": {
+        callback: _fires("backbutton")
+    },
+    "menubutton": {
+        callback: _fires("menubutton")
+    },
+    "pause": {
+        callback: _fires("pause")
+    },
+    "resume": {
+        callback: _fires("resume")
+    },
+    "searchbutton": {
+        callback: _fires("searchbutton")
+    },
+    "online": {
+        callback: _fires("online")
+    },
+    "offline": {
+        callback: _fires("offline")
+    }
+};
diff --git a/lib/ripple/platform/phonegap/1.0/spec/ui.js b/lib/ripple/platform/cordova/2.0.0/spec/ui.js
similarity index 100%
copy from lib/ripple/platform/phonegap/1.0/spec/ui.js
copy to lib/ripple/platform/cordova/2.0.0/spec/ui.js
diff --git a/lib/ripple/platform/phonegap/1.0/AVCodecsAttributes.js b/lib/ripple/platform/phonegap/1.0.0/AVCodecsAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/AVCodecsAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/AVCodecsAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/Acceleration.js b/lib/ripple/platform/phonegap/1.0.0/Acceleration.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/Acceleration.js
rename to lib/ripple/platform/phonegap/1.0.0/Acceleration.js
diff --git a/lib/ripple/platform/phonegap/1.0/AudioCodecAttributes.js b/lib/ripple/platform/phonegap/1.0.0/AudioCodecAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/AudioCodecAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/AudioCodecAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/AudioDeviceAttributes.js b/lib/ripple/platform/phonegap/1.0.0/AudioDeviceAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/AudioDeviceAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/AudioDeviceAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/BrailleDeviceAttributes.js b/lib/ripple/platform/phonegap/1.0.0/BrailleDeviceAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/BrailleDeviceAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/BrailleDeviceAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/CPUAttributes.js b/lib/ripple/platform/phonegap/1.0.0/CPUAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/CPUAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/CPUAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/CameraAttributes.js b/lib/ripple/platform/phonegap/1.0.0/CameraAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/CameraAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/CameraAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/Connection.js b/lib/ripple/platform/phonegap/1.0.0/Connection.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/Connection.js
rename to lib/ripple/platform/phonegap/1.0.0/Connection.js
diff --git a/lib/ripple/platform/phonegap/1.0/ConnectionAttributes.js b/lib/ripple/platform/phonegap/1.0.0/ConnectionAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/ConnectionAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/ConnectionAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/Contact.js b/lib/ripple/platform/phonegap/1.0.0/Contact.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/Contact.js
rename to lib/ripple/platform/phonegap/1.0.0/Contact.js
diff --git a/lib/ripple/platform/phonegap/1.0/ContactAccount.js b/lib/ripple/platform/phonegap/1.0.0/ContactAccount.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/ContactAccount.js
rename to lib/ripple/platform/phonegap/1.0.0/ContactAccount.js
diff --git a/lib/ripple/platform/phonegap/1.0/ContactAddress.js b/lib/ripple/platform/phonegap/1.0.0/ContactAddress.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/ContactAddress.js
rename to lib/ripple/platform/phonegap/1.0.0/ContactAddress.js
diff --git a/lib/ripple/platform/phonegap/1.0/ContactError.js b/lib/ripple/platform/phonegap/1.0.0/ContactError.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/ContactError.js
rename to lib/ripple/platform/phonegap/1.0.0/ContactError.js
diff --git a/lib/ripple/platform/phonegap/1.0/ContactField.js b/lib/ripple/platform/phonegap/1.0.0/ContactField.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/ContactField.js
rename to lib/ripple/platform/phonegap/1.0.0/ContactField.js
diff --git a/lib/ripple/platform/phonegap/1.0/ContactFindOptions.js b/lib/ripple/platform/phonegap/1.0.0/ContactFindOptions.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/ContactFindOptions.js
rename to lib/ripple/platform/phonegap/1.0.0/ContactFindOptions.js
diff --git a/lib/ripple/platform/phonegap/1.0/ContactName.js b/lib/ripple/platform/phonegap/1.0.0/ContactName.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/ContactName.js
rename to lib/ripple/platform/phonegap/1.0.0/ContactName.js
diff --git a/lib/ripple/platform/phonegap/1.0/ContactOrganization.js b/lib/ripple/platform/phonegap/1.0.0/ContactOrganization.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/ContactOrganization.js
rename to lib/ripple/platform/phonegap/1.0.0/ContactOrganization.js
diff --git a/lib/ripple/platform/phonegap/1.0/DisplayDeviceAttributes.js b/lib/ripple/platform/phonegap/1.0.0/DisplayDeviceAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/DisplayDeviceAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/DisplayDeviceAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/InputDevicesAttributes.js b/lib/ripple/platform/phonegap/1.0.0/InputDevicesAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/InputDevicesAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/InputDevicesAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/KeyboardAttributes.js b/lib/ripple/platform/phonegap/1.0.0/KeyboardAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/KeyboardAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/KeyboardAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/MicrophoneAttributes.js b/lib/ripple/platform/phonegap/1.0.0/MicrophoneAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/MicrophoneAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/MicrophoneAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/NetworkAttributes.js b/lib/ripple/platform/phonegap/1.0.0/NetworkAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/NetworkAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/NetworkAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/NetworkStatus.js b/lib/ripple/platform/phonegap/1.0.0/NetworkStatus.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/NetworkStatus.js
rename to lib/ripple/platform/phonegap/1.0.0/NetworkStatus.js
diff --git a/lib/ripple/platform/phonegap/1.0/OutputDevicesAttributes.js b/lib/ripple/platform/phonegap/1.0.0/OutputDevicesAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/OutputDevicesAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/OutputDevicesAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/PhoneGap.js b/lib/ripple/platform/phonegap/1.0.0/PhoneGap.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/PhoneGap.js
rename to lib/ripple/platform/phonegap/1.0.0/PhoneGap.js
diff --git a/lib/ripple/platform/phonegap/1.0/PointerAttributes.js b/lib/ripple/platform/phonegap/1.0.0/PointerAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/PointerAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/PointerAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/PowerAttributes.js b/lib/ripple/platform/phonegap/1.0.0/PowerAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/PowerAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/PowerAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/PrintingDeviceAttributes.js b/lib/ripple/platform/phonegap/1.0.0/PrintingDeviceAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/PrintingDeviceAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/PrintingDeviceAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/SensorAttributes.js b/lib/ripple/platform/phonegap/1.0.0/SensorAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/SensorAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/SensorAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/StorageUnitAttributes.js b/lib/ripple/platform/phonegap/1.0.0/StorageUnitAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/StorageUnitAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/StorageUnitAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/SystemInfoOptions.js b/lib/ripple/platform/phonegap/1.0.0/SystemInfoOptions.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/SystemInfoOptions.js
rename to lib/ripple/platform/phonegap/1.0.0/SystemInfoOptions.js
diff --git a/lib/ripple/platform/phonegap/1.0/ThermalAttributes.js b/lib/ripple/platform/phonegap/1.0.0/ThermalAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/ThermalAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/ThermalAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/VideoCodecAttributes.js b/lib/ripple/platform/phonegap/1.0.0/VideoCodecAttributes.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/VideoCodecAttributes.js
rename to lib/ripple/platform/phonegap/1.0.0/VideoCodecAttributes.js
diff --git a/lib/ripple/platform/phonegap/1.0/accelerometer.js b/lib/ripple/platform/phonegap/1.0.0/accelerometer.js
similarity index 97%
rename from lib/ripple/platform/phonegap/1.0/accelerometer.js
rename to lib/ripple/platform/phonegap/1.0.0/accelerometer.js
index f5f005c..9a433bc 100644
--- a/lib/ripple/platform/phonegap/1.0/accelerometer.js
+++ b/lib/ripple/platform/phonegap/1.0.0/accelerometer.js
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-var Acceleration = require('ripple/platform/phonegap/1.0/Acceleration'),
+var Acceleration = require('ripple/platform/phonegap/1.0.0/Acceleration'),
     utils = require('ripple/utils'),
     event = require('ripple/event'),
     _accelerometerInfo = new Acceleration(),
diff --git a/lib/ripple/platform/phonegap/1.0/camera.js b/lib/ripple/platform/phonegap/1.0.0/camera.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/camera.js
rename to lib/ripple/platform/phonegap/1.0.0/camera.js
diff --git a/lib/ripple/platform/phonegap/1.0/compass.js b/lib/ripple/platform/phonegap/1.0.0/compass.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/compass.js
rename to lib/ripple/platform/phonegap/1.0.0/compass.js
diff --git a/lib/ripple/platform/phonegap/1.0/contacts.js b/lib/ripple/platform/phonegap/1.0.0/contacts.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/contacts.js
rename to lib/ripple/platform/phonegap/1.0.0/contacts.js
diff --git a/lib/ripple/platform/phonegap/1.0/device.js b/lib/ripple/platform/phonegap/1.0.0/device.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/device.js
rename to lib/ripple/platform/phonegap/1.0.0/device.js
diff --git a/lib/ripple/platform/phonegap/1.0/map.js b/lib/ripple/platform/phonegap/1.0.0/map.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/map.js
rename to lib/ripple/platform/phonegap/1.0.0/map.js
diff --git a/lib/ripple/platform/phonegap/1.0/navigator.js b/lib/ripple/platform/phonegap/1.0.0/navigator.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/navigator.js
rename to lib/ripple/platform/phonegap/1.0.0/navigator.js
diff --git a/lib/ripple/platform/phonegap/1.0/network.js b/lib/ripple/platform/phonegap/1.0.0/network.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/network.js
rename to lib/ripple/platform/phonegap/1.0.0/network.js
diff --git a/lib/ripple/platform/phonegap/1.0/notification.js b/lib/ripple/platform/phonegap/1.0.0/notification.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/notification.js
rename to lib/ripple/platform/phonegap/1.0.0/notification.js
diff --git a/lib/ripple/platform/phonegap/1.0/orientation.js b/lib/ripple/platform/phonegap/1.0.0/orientation.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/orientation.js
rename to lib/ripple/platform/phonegap/1.0.0/orientation.js
diff --git a/lib/ripple/platform/phonegap/1.0/service.js b/lib/ripple/platform/phonegap/1.0.0/service.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/service.js
rename to lib/ripple/platform/phonegap/1.0.0/service.js
diff --git a/lib/ripple/platform/phonegap/1.0/sms.js b/lib/ripple/platform/phonegap/1.0.0/sms.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/sms.js
rename to lib/ripple/platform/phonegap/1.0.0/sms.js
diff --git a/lib/ripple/platform/phonegap/1.0.0/spec.js b/lib/ripple/platform/phonegap/1.0.0/spec.js
new file mode 100644
index 0000000..37b768c
--- /dev/null
+++ b/lib/ripple/platform/phonegap/1.0.0/spec.js
@@ -0,0 +1,171 @@
+module.exports = {
+    id: "phonegap",
+    version: "1.0.0",
+    name: "PhoneGap",
+    type: "platform",
+
+    persistencePrefix: "phonegap-",
+
+    config: require('ripple/platform/phonegap/1.0.0/spec/config'),
+    device: require('ripple/platform/phonegap/1.0.0/spec/device'),
+    ui: require('ripple/platform/phonegap/1.0.0/spec/ui'),
+    events: require('ripple/platform/phonegap/1.0.0/spec/events'),
+
+    initialize: function () { },
+
+    objects: {
+        PhoneGap: {
+            path: "phonegap/1.0.0/PhoneGap"
+        },
+        Coordinates: {
+            path: "w3c/1.0/Coordinates"
+        },
+        Position: {
+            path: "w3c/1.0/Position"
+        },
+        PositionError: {
+            path: "w3c/1.0/PositionError"
+        },
+        Acceleration: {
+            path: "phonegap/1.0.0/Acceleration"
+        },
+        navigator: {
+            path: "phonegap/1.0.0/navigator",
+            children: {
+                accelerometer: {
+                    path: "phonegap/1.0.0/accelerometer"
+                },
+                geolocation: {
+                    path: "w3c/1.0/geolocation"
+                },
+                notification: {
+                    path: "phonegap/1.0.0/notification"
+                },
+                contacts: {
+                    path: "phonegap/1.0.0/contacts"
+                },
+                network: {
+                    path: "phonegap/1.0.0/network"
+                },
+                camera: {
+                    path: "phonegap/1.0.0/camera"
+                },
+                sms: {
+                    path: "phonegap/1.0.0/sms"
+                },
+                telephony: {
+                    path: "phonegap/1.0.0/telephony"
+                },
+                map: {
+                    path: "phonegap/1.0.0/map"
+                },
+                orientation: {
+                    path: "phonegap/1.0.0/orientation"
+                },
+                system: {
+                    path: "phonegap/1.0.0/system"
+                },
+                compass: {
+                    path: "phonegap/1.0.0/compass"
+                }
+            }
+        },
+        ContactError: {
+            path: "phonegap/1.0.0/ContactError"
+        },
+        Contact: {
+            path: "phonegap/1.0.0/Contact"
+        },
+        ContactName: {
+            path: "phonegap/1.0.0/ContactName"
+        },
+        ContactAccount: {
+            path: "phonegap/1.0.0/ContactAccount"
+        },
+        ContactAddress: {
+            path: "phonegap/1.0.0/ContactAddress"
+        },
+        ContactOrganization: {
+            path: "phonegap/1.0.0/ContactOrganization"
+        },
+        ContactFindOptions: {
+            path: "phonegap/1.0.0/ContactFindOptions"
+        },
+        ContactField: {
+            path: "phonegap/1.0.0/ContactField"
+        },
+        NetworkStatus: {
+            path: "phonegap/1.0.0/NetworkStatus"
+        },
+        device: {
+            path: "phonegap/1.0.0/device"
+        },
+        SystemInfoOptions: {
+            path: "phonegap/1.0.0/SystemInfoOptions"
+        },
+        PowerAttributes: {
+            path: "phonegap/1.0.0/PowerAttributes"
+        },
+        CPUAttributes: {
+            path: "phonegap/1.0.0/CPUAttributes"
+        },
+        ThermalAttributes: {
+            path: "phonegap/1.0.0/ThermalAttributes"
+        },
+        NetworkAttributes: {
+            path: "phonegap/1.0.0/NetworkAttributes"
+        },
+        Connection: {
+            path: "phonegap/1.0.0/Connection"
+        },
+        ConnectionAttributes: {
+            path: "phonegap/1.0.0/ConnectionAttributes"
+        },
+        SensorAttributes: {
+            path: "phonegap/1.0.0/SensorAttributes"
+        },
+        AVCodecsAttributes: {
+            path: "phonegap/1.0.0/AVCodecsAttributes"
+        },
+        AudioCodecAttributes: {
+            path: "phonegap/1.0.0/AudioCodecAttributes"
+        },
+        VideoCodecAttributes: {
+            path: "phonegap/1.0.0/VideoCodecAttributes"
+        },
+        StorageUnitAttributes: {
+            path: "phonegap/1.0.0/StorageUnitAttributes"
+        },
+        InputDevicesAttributes: {
+            path: "phonegap/1.0.0/InputDevicesAttributes"
+        },
+        OutputDevicesAttributes: {
+            path: "phonegap/1.0.0/OutputDevicesAttributes"
+        },
+        DisplayDeviceAttributes: {
+            path: "phonegap/1.0.0/DisplayDeviceAttributes"
+        },
+        AudioDeviceAttributes: {
+            path: "phonegap/1.0.0/AudioDeviceAttributes"
+        },
+        PrintingDeviceAttributes: {
+            path: "phonegap/1.0.0/PrintingDeviceAttributes"
+        },
+        BrailleDeviceAttributes: {
+            path: "phonegap/1.0.0/BrailleDeviceAttributes"
+        },
+        PointerAttributes: {
+            path: "phonegap/1.0.0/PointerAttributes"
+        },
+        KeyboardAttributes: {
+            path: "phonegap/1.0.0/KeyboardAttributes"
+        },
+        CameraAttributes: {
+            path: "phonegap/1.0.0/CameraAttributes"
+        },
+        MicrophoneAttributes: {
+            path: "phonegap/1.0.0/MicrophoneAttributes"
+        }
+    }
+
+};
diff --git a/lib/ripple/platform/phonegap/1.0/spec/config.js b/lib/ripple/platform/phonegap/1.0.0/spec/config.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/spec/config.js
rename to lib/ripple/platform/phonegap/1.0.0/spec/config.js
diff --git a/lib/ripple/platform/phonegap/1.0/spec/device.js b/lib/ripple/platform/phonegap/1.0.0/spec/device.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/spec/device.js
rename to lib/ripple/platform/phonegap/1.0.0/spec/device.js
diff --git a/lib/ripple/platform/phonegap/1.0/spec/events.js b/lib/ripple/platform/phonegap/1.0.0/spec/events.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/spec/events.js
rename to lib/ripple/platform/phonegap/1.0.0/spec/events.js
diff --git a/lib/ripple/platform/phonegap/1.0/spec/ui.js b/lib/ripple/platform/phonegap/1.0.0/spec/ui.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/spec/ui.js
rename to lib/ripple/platform/phonegap/1.0.0/spec/ui.js
diff --git a/lib/ripple/platform/phonegap/1.0/system.js b/lib/ripple/platform/phonegap/1.0.0/system.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/system.js
rename to lib/ripple/platform/phonegap/1.0.0/system.js
diff --git a/lib/ripple/platform/phonegap/1.0/telephony.js b/lib/ripple/platform/phonegap/1.0.0/telephony.js
similarity index 100%
rename from lib/ripple/platform/phonegap/1.0/telephony.js
rename to lib/ripple/platform/phonegap/1.0.0/telephony.js
diff --git a/lib/ripple/platform/phonegap/1.0/spec.js b/lib/ripple/platform/phonegap/1.0/spec.js
deleted file mode 100644
index 6c0abf1..0000000
--- a/lib/ripple/platform/phonegap/1.0/spec.js
+++ /dev/null
@@ -1,171 +0,0 @@
-module.exports = {
-    id: "phonegap",
-    version: "1.0",
-    name: "PhoneGap",
-    type: "platform",
-
-    persistencePrefix: "phonegap-",
-
-    config: require('ripple/platform/phonegap/1.0/spec/config'),
-    device: require('ripple/platform/phonegap/1.0/spec/device'),
-    ui: require('ripple/platform/phonegap/1.0/spec/ui'),
-    events: require('ripple/platform/phonegap/1.0/spec/events'),
-
-    initialize: function () { },
-
-    objects: {
-        PhoneGap: {
-            path: "phonegap/1.0/PhoneGap"
-        },
-        Coordinates: {
-            path: "w3c/1.0/Coordinates"
-        },
-        Position: {
-            path: "w3c/1.0/Position"
-        },
-        PositionError: {
-            path: "w3c/1.0/PositionError"
-        },
-        Acceleration: {
-            path: "phonegap/1.0/Acceleration"
-        },
-        navigator: {
-            path: "phonegap/1.0/navigator",
-            children: {
-                accelerometer: {
-                    path: "phonegap/1.0/accelerometer"
-                },
-                geolocation: {
-                    path: "w3c/1.0/geolocation"
-                },
-                notification: {
-                    path: "phonegap/1.0/notification"
-                },
-                contacts: {
-                    path: "phonegap/1.0/contacts"
-                },
-                network: {
-                    path: "phonegap/1.0/network"
-                },
-                camera: {
-                    path: "phonegap/1.0/camera"
-                },
-                sms: {
-                    path: "phonegap/1.0/sms"
-                },
-                telephony: {
-                    path: "phonegap/1.0/telephony"
-                },
-                map: {
-                    path: "phonegap/1.0/map"
-                },
-                orientation: {
-                    path: "phonegap/1.0/orientation"
-                },
-                system: {
-                    path: "phonegap/1.0/system"
-                },
-                compass: {
-                    path: "phonegap/1.0/compass"
-                }
-            }
-        },
-        ContactError: {
-            path: "phonegap/1.0/ContactError"
-        },
-        Contact: {
-            path: "phonegap/1.0/Contact"
-        },
-        ContactName: {
-            path: "phonegap/1.0/ContactName"
-        },
-        ContactAccount: {
-            path: "phonegap/1.0/ContactAccount"
-        },
-        ContactAddress: {
-            path: "phonegap/1.0/ContactAddress"
-        },
-        ContactOrganization: {
-            path: "phonegap/1.0/ContactOrganization"
-        },
-        ContactFindOptions: {
-            path: "phonegap/1.0/ContactFindOptions"
-        },
-        ContactField: {
-            path: "phonegap/1.0/ContactField"
-        },
-        NetworkStatus: {
-            path: "phonegap/1.0/NetworkStatus"
-        },
-        device: {
-            path: "phonegap/1.0/device"
-        },
-        SystemInfoOptions: {
-            path: "phonegap/1.0/SystemInfoOptions"
-        },
-        PowerAttributes: {
-            path: "phonegap/1.0/PowerAttributes"
-        },
-        CPUAttributes: {
-            path: "phonegap/1.0/CPUAttributes"
-        },
-        ThermalAttributes: {
-            path: "phonegap/1.0/ThermalAttributes"
-        },
-        NetworkAttributes: {
-            path: "phonegap/1.0/NetworkAttributes"
-        },
-        Connection: {
-            path: "phonegap/1.0/Connection"
-        },
-        ConnectionAttributes: {
-            path: "phonegap/1.0/ConnectionAttributes"
-        },
-        SensorAttributes: {
-            path: "phonegap/1.0/SensorAttributes"
-        },
-        AVCodecsAttributes: {
-            path: "phonegap/1.0/AVCodecsAttributes"
-        },
-        AudioCodecAttributes: {
-            path: "phonegap/1.0/AudioCodecAttributes"
-        },
-        VideoCodecAttributes: {
-            path: "phonegap/1.0/VideoCodecAttributes"
-        },
-        StorageUnitAttributes: {
-            path: "phonegap/1.0/StorageUnitAttributes"
-        },
-        InputDevicesAttributes: {
-            path: "phonegap/1.0/InputDevicesAttributes"
-        },
-        OutputDevicesAttributes: {
-            path: "phonegap/1.0/OutputDevicesAttributes"
-        },
-        DisplayDeviceAttributes: {
-            path: "phonegap/1.0/DisplayDeviceAttributes"
-        },
-        AudioDeviceAttributes: {
-            path: "phonegap/1.0/AudioDeviceAttributes"
-        },
-        PrintingDeviceAttributes: {
-            path: "phonegap/1.0/PrintingDeviceAttributes"
-        },
-        BrailleDeviceAttributes: {
-            path: "phonegap/1.0/BrailleDeviceAttributes"
-        },
-        PointerAttributes: {
-            path: "phonegap/1.0/PointerAttributes"
-        },
-        KeyboardAttributes: {
-            path: "phonegap/1.0/KeyboardAttributes"
-        },
-        CameraAttributes: {
-            path: "phonegap/1.0/CameraAttributes"
-        },
-        MicrophoneAttributes: {
-            path: "phonegap/1.0/MicrophoneAttributes"
-        }
-    }
-
-};
diff --git a/lib/ripple/platform/spec.js b/lib/ripple/platform/spec.js
index 7b54e64..beac662 100644
--- a/lib/ripple/platform/spec.js
+++ b/lib/ripple/platform/spec.js
@@ -14,10 +14,14 @@
  * limitations under the License.
  */
 module.exports = {
-    "phonegap": {"1.0": require('ripple/platform/phonegap/1.0/spec')},
-    "cordova": {"1.6": require('ripple/platform/cordova/1.6/spec')},
+    "phonegap": {"1.0.0": require('ripple/platform/phonegap/1.0.0/spec')},
+    "cordova": {"2.0.0": require('ripple/platform/cordova/2.0.0/spec')},
     "webworks.bb10": {"1.0.0": require('ripple/platform/webworks.bb10/1.0.0/spec')},
     "webworks.handset": {"2.0.0": require('ripple/platform/webworks.handset/2.0.0/spec')},
     "webworks.tablet": {"2.0.0": require('ripple/platform/webworks.tablet/2.0.0/spec')},
-    "web": {"default": require('ripple/platform/web/default/spec')}
+    "web": {"default": require('ripple/platform/web/default/spec')},
+    "get": function (name, version) {
+        var platform = module.exports[name] || {};
+        return (platform[version] || platform[Object.keys(platform)[0]]);
+    }
 };
diff --git a/lib/ripple/platform/webworks.bb10/1.0.0/app.js b/lib/ripple/platform/webworks.bb10/1.0.0/app.js
index 9ec68e4..56dd91b 100644
--- a/lib/ripple/platform/webworks.bb10/1.0.0/app.js
+++ b/lib/ripple/platform/webworks.bb10/1.0.0/app.js
@@ -15,8 +15,15 @@
  */
 var transport = require('ripple/platform/webworks.core/2.0.0/client/transport'),
     app = require('ripple/app'),
+    notifications = require('ripple/notifications'),
     _uri = "blackberry/app/",
-    _self = {};
+    _self;
+
+_self = {
+    exit: function () {
+        notifications.openNotification("normal", "blackberry.app.exit() was called, in the real world your app will exit, here... you get this notification");
+    }
+};
 
 _self.__defineGetter__("author", function () {
     return app.getInfo().author;
diff --git a/lib/ripple/platform/webworks.bb10/1.0.0/device.js b/lib/ripple/platform/webworks.bb10/1.0.0/device.js
index 04feb38..0d78821 100644
--- a/lib/ripple/platform/webworks.bb10/1.0.0/device.js
+++ b/lib/ripple/platform/webworks.bb10/1.0.0/device.js
@@ -16,7 +16,11 @@
 var devices = require('ripple/devices'),
     _self = {};
 
-_self.__defineGetter__("version", function () {
+_self.__defineGetter__("softwareVersion", function () {
     return devices.getCurrentDevice().osVersion;
 });
+
+_self.__defineGetter__("hardwareId", function () {
+    return devices.getCurrentDevice().hardwareId;
+});
 module.exports = _self;
diff --git a/lib/ripple/platform/webworks.bb10/1.0.0/event.js b/lib/ripple/platform/webworks.bb10/1.0.0/event.js
index 92f7a7f..9893885 100644
--- a/lib/ripple/platform/webworks.bb10/1.0.0/event.js
+++ b/lib/ripple/platform/webworks.bb10/1.0.0/event.js
@@ -17,6 +17,8 @@
 var event = require('ripple/event'),
     settings = require('ripple/deviceSettings'),
     app = require('ripple/app'),
+    cons = require('ripple/console'),
+    utils = require('ripple/utils'),
     events = {
         batterystatus: {
             callbacks: [],
@@ -34,6 +36,14 @@
             callbacks: [],
             feature: 'blackberry.connection'
         },
+        invoked: {
+            callbacks: [],
+            feature: 'blackberry.invoked'
+        },
+        swipedown: {
+            callbacks: [],
+            feature: 'blackberry.app'
+        },
         resume: {
             callbacks: [],
             feature: 'blackberry.app'
@@ -80,11 +90,49 @@
     _apply('connectionchange', [info]);
 });
 
-event.on('appResume', function () {
+event.on("AppInvoke", function (invokeInfo) {
+    var invokeTargets = app.getInfo().invokeTargets;
+
+    if (!invokeTargets) {
+        cons.log("The application cannot be invoked, please add a rim:invoke-target node in config.xml");
+        return;
+    }
+
+    if (invokeTargets.some(function (target) {
+        return target.filter.some(function (filter) {
+            return (
+                (!filter.property ||
+                (filter.property && filter.property[0]["@attributes"].var === "exts" && filter.property[0]["@attributes"].value.split(",").some(function (value) {
+                    return invokeInfo.extension.match(value);
+                })) ||
+                (filter.property && filter.property[0]["@attributes"].var === "uris" && filter.property[0]["@attributes"].value.split(",").some(function (value) {
+                    return invokeInfo.source.match(value);
+                }))) &&
+                filter.action.some(function (action) {
+                    return invokeInfo.action.match(action["#text"][0].replace("*", ""));
+                }) &&
+                filter["mime-type"].some(function (type) {
+                    return invokeInfo.mimeType.match(type["#text"][0].replace("*", ""));
+                })
+            );
+        });
+    })) {
+        _apply('invoked', [invokeInfo]);
+    }
+    else {
+        cons.log("Cannot invoke application, values enter to not match values in rim:invoke-target in config.xml");
+    }
+});
+
+event.on('AppSwipeDown', function () {
+    _apply('swipedown');
+});
+
+event.on('AppResume', function () {
     _apply('resume');
 });
 
-event.on('appPause', function () {
+event.on('AppPause', function () {
     _apply('pause');
 });
 
diff --git a/lib/ripple/platform/webworks.bb10/1.0.0/invoke.js b/lib/ripple/platform/webworks.bb10/1.0.0/invoke.js
index 860a12b..f0e9a6e 100644
--- a/lib/ripple/platform/webworks.bb10/1.0.0/invoke.js
+++ b/lib/ripple/platform/webworks.bb10/1.0.0/invoke.js
@@ -15,60 +15,45 @@
  */

 

 var notifications = require('ripple/notifications'),

-    _self;

+    utils = require('ripple/utils'),

+    _self = {};

 

-_self = {

-    invoke: function (appType, args) {

-        var get = {};

+function _fail(onError) {

+    if (onError && typeof onError === "function") {

+        onError("invalid invocation request");

+    }

+}

 

-        if (appType === 11) {

-            if (!args) {

-                get.appType = "http://";

-            }

-            else {

-                get.appType = args.url.split("://");

+_self.invoke = function (request, onSuccess, onError) {

+    var argsString = "";

 

-                if (get.appType.length === 1) {

-                    get.appType = "http://" + get.appType[0];

-                }

-                else if (get.appType.length === 2) {

-                    if (get.appType[0].indexOf("http") !== 0) {

-                        throw "Protocol specified in the url is not supported.";

-                    }

-                    else {

-                        get.appType = args.url;

-                    }

-                }

-            }

+    if (!request) { // is this check even needed?

+        _fail(onError);

+        return;

+    } else {

+        if (request.data) {

+            utils.forEach(request, function (arg, key) {

+                argsString += key + " = " + arg + "</br>";

+            });

 

-            notifications.openNotification("normal", "Requested to launch: Browser."); 

+            notifications.openNotification("normal", "Requested to invoke external application with the following arguments:</br> " +

+                                           argsString + "</br>");

         }

         else {

-            throw "appType not supported";

+            _fail(onError);

+            return;

         }

     }

 };

 

-_self.__defineGetter__("APP_CAMERA", function () {

-    return 4;

+_self.__defineGetter__("ACTION_OPEN", function () {

+    return "bb.action.OPEN";

 });

-_self.__defineGetter__("APP_MAPS", function () {

-    return 5;

+_self.__defineGetter__("ACTION_VIEW", function () { 

+    return "bb.action.VIEW"; 

 });

-_self.__defineGetter__("APP_BROWSER", function () {

-    return 11;

-});

-_self.__defineGetter__("APP_MUSIC", function () {

-    return 13;

-});

-_self.__defineGetter__("APP_PHOTOS", function () {

-    return 14;

-});

-_self.__defineGetter__("APP_VIDEOS", function () {

-    return 15;

-});

-_self.__defineGetter__("APP_APPWORLD", function () {

-    return 16;

+_self.__defineGetter__("ACTION_SHARE", function () {

+    return "bb.action.SHARE";

 });

 

 module.exports = _self;

diff --git a/lib/ripple/platform/webworks.bb10/1.0.0/spec.js b/lib/ripple/platform/webworks.bb10/1.0.0/spec.js
index a6ddce8..36872cd 100644
--- a/lib/ripple/platform/webworks.bb10/1.0.0/spec.js
+++ b/lib/ripple/platform/webworks.bb10/1.0.0/spec.js
@@ -23,7 +23,7 @@
 
     ui: require('ripple/platform/webworks.bb10/1.0.0/spec/ui'),
     device: require('ripple/platform/webworks.bb10/1.0.0/spec/device'),
-    config: require('ripple/platform/webworks.core/2.0.0/spec/config'),
+    config: require('ripple/platform/webworks.bb10/1.0.0/spec/config'),
     events: require('ripple/platform/webworks.bb10/1.0.0/spec/events'),
 
     initialize: function () {
@@ -69,13 +69,7 @@
                 },
                 invoke: {
                     path: "webworks.bb10/1.0.0/invoke",
-                    feature: "blackberry.invoke",
-                    children: {
-                        BrowserArguments: {
-                            path: "webworks.bb10/1.0.0/BrowserArguments",
-                            feature: "blackberry.invoke.BrowserArguments"
-                        }
-                    }
+                    feature: "blackberry.invoke"
                 },
                 identity: {
                     path: "webworks.bb10/1.0.0/identity",
@@ -84,9 +78,6 @@
                 system: {
                     path: "webworks.bb10/1.0.0/system"
                 },
-                device: {
-                    path: "webworks.bb10/1.0.0/device"
-                },
                 connection: {
                     path: "webworks.bb10/1.0.0/connection"
                 },
diff --git a/lib/ripple/platform/webworks.bb10/1.0.0/spec/config.js b/lib/ripple/platform/webworks.bb10/1.0.0/spec/config.js
new file mode 100644
index 0000000..4492357
--- /dev/null
+++ b/lib/ripple/platform/webworks.bb10/1.0.0/spec/config.js
@@ -0,0 +1,516 @@
+/*
+ *  Copyright 2011 Research In Motion Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var utils = require('ripple/utils');
+
+module.exports = {
+    fileName: "config.xml",
+    validateVersion: function (config) {
+        return true;
+    },
+    extractInfo: function (config) {
+        if (!config) {
+            return null;
+        }
+
+        var widgetInfo = {},
+            widgetFeatures = config.widget.children.feature.validationResult,
+            accessUrls = config.widget.children.access.validationResult,
+            invokeTargets = config.rawJSON.widget[0]["rim:invoke-target"],
+            accessFeatures = config.widget.children.access.children.feature.validationResult,
+            toFeature = function (validationResult) {
+                return {
+                    id: validationResult.attributes.id.value,
+                    required: !validationResult.attributes.required || validationResult.attributes.required.value,
+                    URIs: []
+                };
+            };
+
+        widgetInfo.id = config.widget.validationResult[0].attributes.id.value;
+        widgetInfo.name = config.widget.children.name.validationResult[0].value;
+        widgetInfo.icon = config.widget.children.icon.validationResult[0].attributes.src.value;
+        widgetInfo.version = config.widget.validationResult[0].attributes.version.value;
+        widgetInfo.author = config.widget.children.author.validationResult[0].value;
+        widgetInfo.authorEmail = config.widget.children.author.validationResult[0].attributes.email.value;
+        widgetInfo.authorURL = config.widget.children.author.validationResult[0].attributes.href.value;
+        widgetInfo.copyright = config.widget.children.author.validationResult[0].attributes["rim:copyright"].value;
+        widgetInfo.description = config.widget.children.description.validationResult[0].value;
+        widgetInfo.invokeTargets = invokeTargets;
+        if (config.widget.children.license.validationResult[0]) {
+            widgetInfo.license = config.widget.children.license.validationResult[0].value;
+            widgetInfo.licenseURL = config.widget.children.license.validationResult[0].attributes.href.value;
+        }
+
+        widgetInfo.features = widgetFeatures.reduce(function (features, validationResult) {
+            if (validationResult.valid) {
+                var feature = toFeature(validationResult);
+                feature.URIs.push({
+                    value: utils.location().href,
+                    subdomains: true
+                });
+                features = features || {};
+                features[feature.id] = feature;
+            }
+            return features;
+        }, {});
+
+        widgetInfo.features = accessUrls.map(function (access) {
+            return {
+                uri: access.attributes.uri.value,
+                subdomains: access.attributes.subdomains.value,
+                features: accessFeatures ? accessFeatures.filter(function (f) {
+                    return f.node && f.node.parentNode && f.node.parentNode.attributes.uri.value === access.attributes.uri.value;
+                }) : null
+            };
+        }).reduce(function (result, access) {
+            return access.features ? access.features.reduce(function (features, validationResult) {
+                var feature = features[validationResult.attributes.id.value] || toFeature(validationResult);
+                feature.URIs.push({
+                    value: access.uri,
+                    subdomains: access.subdomains
+                });
+                features[feature.id] = feature;
+                return features;
+            }, result) : result;
+        }, widgetInfo.features);
+
+        return widgetInfo;
+    },
+    schema: {
+        rootElement: "widget",
+        widget: {
+            nodeName: "widget",
+            required: true,
+            occurrence: 1,
+            attributes: {
+                xmlns: {
+                    attributeName: "xmlns",
+                    required: true,
+                    type: "list",
+                    listValues: ["http://www.w3.org/ns/widgets"]
+                },
+                "xmlns:rim": {
+                    attributeName: "xmlns:rim",
+                    required: true,
+                    type: "list",
+                    listValues: ["http://www.blackberry.com/ns/widgets"]
+                },
+                "xml:lang": {
+                    attributeName: "xml:lang",
+                    required: false,
+                    type: "iso-language"
+                },
+                id: {
+                    attributeName: "id",
+                    required: false,
+                    type: "string"
+                },
+                version: {
+                    attributeName: "version",
+                    required: false,
+                    type: "string"
+                },
+                "rim:header": {
+                    attributeName: "rim:header",
+                    required: false,
+                    type: "string"
+                },
+                "rim:backButton": {
+                    attributeName: "rim:backButton",
+                    required: false,
+                    type: "string"
+                }
+            },
+            children: {
+                name: {
+                    nodeName: "name",
+                    required: true,
+                    occurrence: 1,
+                    attributes: {
+                        "xml:lang": {
+                            attributeName: "xml:lang",
+                            required: false,
+                            type: "iso-language"
+                        },
+                        "its:dir": {
+                            attributeName: "its:dir",
+                            required: false,
+                            type: "list",
+                            listValues: ["rtl", "ltr", "lro", "rlo"]
+                        }
+                    }
+                },
+                description: {
+                    nodeName: "description",
+                    required: false,
+                    occurrence: 1,
+                    attributes: {
+                        "xml:lang": {
+                            attributeName: "xml:lang",
+                            required: false,
+                            type: "iso-language"
+                        },
+                        "its:dir": {
+                            attributeName: "its:dir",
+                            required: false,
+                            type: "list",
+                            listValues: ["rtl", "ltr", "lro", "rlo"]
+                        }
+                    }
+                },
+                icon: {
+                    nodeName: "icon",
+                    required: false,
+                    occurrence: 0,
+                    attributes: {
+                        src: {
+                            attributeName: "src",
+                            type: "string",
+                            required: true
+                        },
+                        "rim:hover": {
+                            attributeName: "rim:hover",
+                            type: "boolean",
+                            required: false
+                        }
+                    }
+                },
+                author: {
+                    nodeName: "author",
+                    required: false,
+                    occurrence: 1,
+                    attributes: {
+                        href: {
+                            attributeName: "href",
+                            type: "string",
+                            required: false
+                        },
+                        "rim:copyright": {
+                            attributeName: "rim:copyright",
+                            type: "string",
+                            required: false
+                        },
+                        email: {
+                            attributeName: "email",
+                            type: "string",
+                            required: false
+                        },
+                        "xml:lang": {
+                            attributeName: "xml:lang",
+                            required: false,
+                            type: "iso-language"
+                        },
+                        "its:dir": {
+                            attributeName: "its:dir",
+                            required: false,
+                            type: "list",
+                            listValues: ["rtl", "ltr", "lro", "rlo"]
+                        }
+                    }
+                },
+                license: {
+                    nodeName: "license",
+                    required: false,
+                    occurrence: 1,
+                    attributes : {
+                        href: {
+                            attributeName: "href",
+                            type: "string",
+                            required: false
+                        },
+                        "xml:lang": {
+                            attributeName: "xml:lang",
+                            required: false,
+                            type: "iso-language"
+                        },
+                        "its:dir": {
+                            attributeName: "its:dir",
+                            required: false,
+                            type: "list",
+                            listValues: ["rtl", "ltr", "lro", "rlo"]
+                        }
+                    }
+                },
+                "rim:cache": {
+                    nodeName: "rim:cache",
+                    required: false,
+                    occurrence: 1,
+                    attributes: {
+                        disableAllCache: {
+                            attributeName: "disableAllCache",
+                            required: false,
+                            type: "boolean"
+                        },
+                        aggressiveCacheAge: {
+                            attributeName: "aggressiveCacheAge",
+                            required: false,
+                            type: "number"
+                        },
+                        maxCacheSizeTotal: {
+                            attributeName: "maxCacheSizeTotal",
+                            required: false,
+                            type: "number"
+                        },
+                        maxCacheSizeItem: {
+                            attributeName: "maxCacheSizeItem",
+                            required: false,
+                            type: "number"
+                        }
+                    }
+                },
+                access: {
+                    nodeName: "access",
+                    required: false,
+                    occurrence: 0,
+                    attributes: {
+                        uri: {
+                            attributeName: "uri",
+                            required: true,
+                            type: "string"
+                        },
+                        subdomains: {
+                            attributeName: "subdomains",
+                            required: false,
+                            type: "boolean"
+                        }
+                    },
+                    children: {
+                        feature: {
+                            nodeName: "feature",
+                            required: false,
+                            occurrence: 0,
+                            attributes: {
+                                id: {
+                                    attributeName: "id",
+                                    required: true,
+                                    //TODO: this should be a list
+                                    type: "string"
+                                },
+                                required: {
+                                    attributeName: "required",
+                                    required: false,
+                                    type: "boolean"
+                                },
+                                version: {
+                                    attributeName: "version",
+                                    required: false,
+                                    type: "string"
+                                }
+                            }
+                        }
+                    }
+                },
+                feature: {
+                    nodeName: "feature",
+                    required: false,
+                    occurrence: 0,
+                    attributes: {
+                        id: {
+                            attributeName: "id",
+                            required: true,
+                            //TODO: this should be a list
+                            type: "string"
+                        },
+                        required: {
+                            attributeName: "required",
+                            required: false,
+                            type: "boolean"
+                        },
+                        version: {
+                            attributeName: "version",
+                            required: false,
+                            type: "string"
+                        }
+                    }
+                },
+                "rim:loadingScreen": {
+                    nodeName: "rim:loadingScreen",
+                    required: false,
+                    occurrence: 1,
+                    attributes: {
+                        backgroundColor: {
+                            attributeName: "backgroundColor",
+                            required: false,
+                            type: "string"
+                        },
+                        backgroundImage: {
+                            attributeName: "backgroundImage",
+                            required: false,
+                            type: "string"
+                        },
+                        foregroundImage: {
+                            attributeName: "foregroundImage",
+                            required: false,
+                            type: "string"
+                        },
+                        onRemotePageLoad: {
+                            attributeName: "onRemotePageLoad",
+                            required: false,
+                            type: "boolean"
+                        },
+                        onLocalPageLoad: {
+                            attributeName: "onLocalPageLoad",
+                            required : false,
+                            type: "boolean"
+                        },
+                        onFirstLaunch: {
+                            attributeName: "onFirstLaunch",
+                            required: false,
+                            type: "boolean"
+                        }
+                    },
+                    children: {
+                        "rim:transitionEffect": {
+                            nodeName: "rim:transitionEffect",
+                            required: false,
+                            occurrence: 1,
+                            attributes: {
+                                "type": {
+                                    attributeName: "type",
+                                    required: true,
+                                    type: "list",
+                                    listValues: ["slidePush", "slideOver", "fadeIn", "fadeOut", "wipeIn", "wipeOut", "zoomIn", "zoomOut"]
+                                },
+                                duration: {
+                                    attributeName: "duration",
+                                    required: false,
+                                    type: "number"
+                                },
+                                direction: {
+                                    attributeName: "direction",
+                                    required: false,
+                                    type: "list",
+                                    listValues: ["left", "right", "up", "down"]
+                                }
+                            }
+                        }
+                    }
+                },
+                "rim:invoke-target": {
+                    nodeName: "rim:invoke-target",
+                    required: false,
+                    occurrence: 0,
+                    attributes: {
+                        id: {
+                            attributeName: "id",
+                            required: true,
+                            type: "string"
+                        }
+                    },
+                    children: {
+                        "type": {
+                            nodeName: "type",
+                            required: true,
+                            occurrence: 1
+                        },
+                        "require-source-permissions": {
+                            nodeName: "require-source-permissions",
+                            required: false,
+                            occurence: 0
+                        },
+                        "filter": {
+                            nodeName: "filter",
+                            required: false,
+                            occurence: 0,
+                            children: {
+                                "action": {
+                                    nodeName: "action",
+                                    required: true,
+                                    occurence: 0
+                                },
+                                "mime-type": {
+                                    nodeName: "mime-type",
+                                    required: true,
+                                    occurence: 0
+                                },
+                                "property": {
+                                    nodeName: "property",
+                                    required: false,
+                                    occurenc: 0,
+                                    attributes: {
+                                        "var": {
+                                            attributeName: "var",
+                                            required: false,
+                                            type: "string"
+                                        },
+                                        "value": {
+                                            attributeName: "value",
+                                            required: false,
+                                            type: "string"
+                                        }
+                                    }
+                                }
+                            },
+                        }
+                    }
+                },
+                "rim:connection": {
+                    nodeName: "rim:connection",
+                    required: false,
+                    occurrence: 1,
+                    attributes: {
+                        timeout: {
+                            attributeName: "timeout",
+                            required: false,
+                            type: "number"
+                        }
+                    },
+                    children: {
+                        id: {
+                            nodeName: "id",
+                            required: false,
+                            occurrence: 0
+                        }
+                    }
+                },
+                "rim:navigation": {
+                    nodeName: "rim:navigation",
+                    required: false,
+                    occurrence: 1,
+                    attributes: {
+                        mode: {
+                            attributeName: "mode",
+                            required: false,
+                            type: "list",
+                            listValues: ["focus"]
+                        }
+                    }
+                },
+                "content": {
+                    nodeName: "content",
+                    required: true,
+                    occurrence: 1,
+                    attributes: {
+                        src: {
+                            attributeName: "src",
+                            required: true,
+                            type: "string"
+                        },
+                        type: {
+                            attributeName: "type",
+                            required: false,
+                            type: "string"
+                        },
+                        charset: {
+                            attributeName: "charset",
+                            required: false,
+                            type: "string"
+                        }
+                    }
+                }
+            }
+        }
+    }
+};
diff --git a/lib/ripple/platform/webworks.bb10/1.0.0/spec/device.js b/lib/ripple/platform/webworks.bb10/1.0.0/spec/device.js
index d1f54d5..8d32856 100644
--- a/lib/ripple/platform/webworks.bb10/1.0.0/spec/device.js
+++ b/lib/ripple/platform/webworks.bb10/1.0.0/spec/device.js
@@ -81,5 +81,18 @@
                 event.trigger("DeviceBatteryLevelChanged", [setting]);
             }
         }
+    },
+    "Perimiters": {
+        "perimiter": {
+            "name": "Perimiter",
+            "control": {
+                "type": "select",
+                "value": "Consumer"
+            },
+            "options": {
+                "Enterprise": "Enterprise",
+                "Consumer": "Consumer"
+            }
+        }
     }
 };
diff --git a/lib/ripple/platform/webworks.bb10/1.0.0/spec/events.js b/lib/ripple/platform/webworks.bb10/1.0.0/spec/events.js
index 80d5b09..2190b67 100644
--- a/lib/ripple/platform/webworks.bb10/1.0.0/spec/events.js
+++ b/lib/ripple/platform/webworks.bb10/1.0.0/spec/events.js
@@ -17,14 +17,19 @@
     event = require('ripple/event');
 
 module.exports = {
+    "blackberry.event.swipedown": {
+        callback: function () {
+            event.trigger("AppSwipeDown");
+        }
+    },
     "blackberry.event.resume": {
         callback: function () {
-            event.trigger("appResume");
+            event.trigger("AppResume");
         }
     },
     "blackberry.event.pause": {
         callback: function () {
-            event.trigger("appPause");
+            event.trigger("AppPause");
         }
     }
 };
diff --git a/lib/ripple/platform/webworks.bb10/1.0.0/spec/ui.js b/lib/ripple/platform/webworks.bb10/1.0.0/spec/ui.js
index d7f2840..c19db43 100644
--- a/lib/ripple/platform/webworks.bb10/1.0.0/spec/ui.js
+++ b/lib/ripple/platform/webworks.bb10/1.0.0/spec/ui.js
@@ -20,6 +20,7 @@
         "geoView",
         "platformEvents",
         "widgetConfig",
-        "build"
+        "build",
+        "invoke"
     ]
 };
diff --git a/lib/ripple/platform/webworks.bb10/1.0.0/system.js b/lib/ripple/platform/webworks.bb10/1.0.0/system.js
index 9ade778..e103ed0 100644
--- a/lib/ripple/platform/webworks.bb10/1.0.0/system.js
+++ b/lib/ripple/platform/webworks.bb10/1.0.0/system.js
@@ -53,4 +53,12 @@
     return 1;

 });

 

+_self.__defineGetter__("softwareVersion", function () {

+    return devices.getCurrentDevice().osVersion;

+});

+

+_self.__defineGetter__("hardwareId", function () {

+    return devices.getCurrentDevice().hardwareId;

+});

+

 module.exports = _self;

diff --git a/lib/ripple/platform/webworks.core/2.0.0/select.js b/lib/ripple/platform/webworks.core/2.0.0/select.js
index b6a8096..504dd5f 100644
--- a/lib/ripple/platform/webworks.core/2.0.0/select.js
+++ b/lib/ripple/platform/webworks.core/2.0.0/select.js
@@ -35,7 +35,7 @@
             return left >= right;
         },
         "REGEX": function (left, right) {
-            return left.match(new RegExp(right));
+            return left && left.match(new RegExp(right));
         },
         "CONTAINS": function (left, right) {
             return left.indexOf(right) >= 0;
diff --git a/lib/ripple/platform/webworks.handset/2.0.0/client/Contact.js b/lib/ripple/platform/webworks.handset/2.0.0/client/Contact.js
index 075e9e6..ed9d207 100644
--- a/lib/ripple/platform/webworks.handset/2.0.0/client/Contact.js
+++ b/lib/ripple/platform/webworks.handset/2.0.0/client/Contact.js
@@ -22,9 +22,9 @@
         birthday: null,
         categories: [],
         company: null,
-        email1: null,
-        email2: null,
-        email3: null,
+        email1: "",
+        email2: "",
+        email3: "",
         faxPhone: null,
         firstName: null,
         homeAddress: null,
diff --git a/lib/ripple/platform/webworks.handset/2.0.0/server/appEvent.js b/lib/ripple/platform/webworks.handset/2.0.0/server/appEvent.js
index 685dad9..612bb6f 100644
--- a/lib/ripple/platform/webworks.handset/2.0.0/server/appEvent.js
+++ b/lib/ripple/platform/webworks.handset/2.0.0/server/appEvent.js
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 var event = require('ripple/event'),
+    notifications = require('ripple/notifications'),
     _bg,
     _fg,
     _exit;
@@ -33,6 +34,7 @@
 event.on("AppExit", function () {
     var baton = _exit;
     _exit = null;
+    notifications.openNotification("normal", "blackberry.app.exit() as called, in the real world your app will exit, here... you get this notification");
     return baton && baton.pass({code: 1});
 });
 
diff --git a/lib/ripple/platform/webworks.tablet/2.0.0/server/appEvent.js b/lib/ripple/platform/webworks.tablet/2.0.0/server/appEvent.js
index 90b35c1..3a959b8 100644
--- a/lib/ripple/platform/webworks.tablet/2.0.0/server/appEvent.js
+++ b/lib/ripple/platform/webworks.tablet/2.0.0/server/appEvent.js
@@ -14,12 +14,20 @@
  * limitations under the License.
  */
 var event = require('ripple/event'),
+    notifications = require('ripple/notifications'),
     _bg,
     _fg,
     _swipeDown,
     _swipeStart,
     _exit;
 
+event.on("AppExit", function () {
+    var baton = _exit;
+    _exit = null;
+    notifications.openNotification("normal", "blackberry.app.exit() as called, in the real world your app will exit, here... you get this notification");
+    return baton && baton.pass({code: 1});
+});
+
 event.on("AppRequestBackground", function () {
     var baton = _bg;
     _bg = null;
diff --git a/lib/ripple/ui/plugins/build.js b/lib/ripple/ui/plugins/build.js
index 4f9cb71..9d2b679 100644
--- a/lib/ripple/ui/plugins/build.js
+++ b/lib/ripple/ui/plugins/build.js
@@ -14,11 +14,32 @@
  * limitations under the License.
  */
 var tooltip = require('ripple/ui/plugins/tooltip'),
-    settings = require('ripple/ui/plugins/settings-dialog');
+    settings = require('ripple/ui/plugins/settings-dialog'),
+    bus = require('ripple/bus');
 
 function handleBuild() {
-    var node = $(this),
-        action = node.attr('id').split("-")[2];
+    var node = $(this);
+
+    bus.ajax(
+        "GET",
+        "http://127.0.0.1:9910/ripple/about",
+        null,
+        function () {
+            doBuild(node);
+        },
+        function (error) {
+            if (error.code === 0 || error.code === 404) {
+                startServices(function () {
+                    doBuild(node);
+                });
+            }
+        }
+    );
+
+}
+
+function doBuild(node) {
+    var action = node.attr('id').split("-")[2];
 
     if (node.hasClass("not-ready")) {
         return;
@@ -32,6 +53,16 @@
     }
 }
 
+function startServices(callBack) {
+    var action = "start";
+    bus.send("services", action, function () {
+        if (typeof callBack === "function") {
+            callBack();
+        }
+        $("#options-menu-services").show();
+    });
+}
+
 module.exports = {
     panel: {
         domId: "build-container",
@@ -52,5 +83,7 @@
             $("#options-menu-build-warning").show();
             tooltip.create("#options-menu-build-warning", "Remote Web Inspector should be disabled when packaging for App World release");
         }
+
+        startServices();
     }
 };
diff --git a/lib/ripple/ui/plugins/build/panel.html b/lib/ripple/ui/plugins/build/panel.html
index 3be2ec6..d02d266 100644
--- a/lib/ripple/ui/plugins/build/panel.html
+++ b/lib/ripple/ui/plugins/build/panel.html
@@ -21,6 +21,9 @@
         </section>
     </section>
     <section class="info ui-widget-content ui-corner-all" style="display: none;">
+        <section id="options-menu-services" class="ui-text-fail irrelevant">
+            The Build and Deploy services are currently running at: http://127.0.0.1:9910
+        </section>
         <button id="options-menu-build" class="not-ready ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only">
             <span class="ui-button-text">Package</span>
         </button>
diff --git a/lib/ripple/ui/plugins/camera.js b/lib/ripple/ui/plugins/camera.js
index e7217e0..e3a0c6d 100644
--- a/lib/ripple/ui/plugins/camera.js
+++ b/lib/ripple/ui/plugins/camera.js
@@ -14,19 +14,41 @@
  * limitations under the License.
  */
 
-var ui = require('ripple/ui');
+var ui = require('ripple/ui'),
+    event = require('ripple/event'),
+    video = document.getElementById('camera-video'),
+    upload = document.getElementById('picture-upload'),
+    select = document.getElementById('select-file'),
+    take = document.getElementById('take-file'),
+    pic = document.getElementById("camera-image");
+
+
+select.addEventListener('click', function () {
+    upload.click();
+});
+
+upload.addEventListener('change', function () {
+    pic.src = window.webkitURL.createObjectURL(upload.files[0]);
+    take.style.display = "inline";
+});
+
+take.addEventListener('click', function () {
+    console.log("captured-image: " + pic.src);
+    event.trigger('captured-image', [pic.src, upload.files[0]]);
+    module.exports.hide();
+});
 
 module.exports = {
     show: function () {
-        var video = document.getElementById('camera-video');
-
-        if (navigator.webkitGetUserMedia) {
-            navigator.webkitGetUserMedia("video", function (stream) {
-                video.src = window.webkitURL.createObjectURL(stream);
-            },
-            function (err) {
-                console.log(err);
-            });
+        ui.showOverlay("camera-window");
+        if (pic.src) {
+            take.style.display = "inline";
         }
+        else {
+            take.style.display = "none";
+        }
+    },
+    hide: function () {
+        ui.hideOverlay("camera-window");
     }
 };
diff --git a/lib/ripple/ui/plugins/camera/overlay.html b/lib/ripple/ui/plugins/camera/overlay.html
index 7a6899e..eb2aed7 100644
--- a/lib/ripple/ui/plugins/camera/overlay.html
+++ b/lib/ripple/ui/plugins/camera/overlay.html
@@ -14,8 +14,18 @@
  * limitations under the License.
 -->
 <section id="camera-window" class="overlay">
-    <video id="camera-video" autoplay></video>
-    <button id="take-picture" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only">
-        <span class="ui-button-text">Take a Picture</span>
-    </button>
+    <section>
+        <img id="camera-image"></img>
+    </section>
+    <section>
+        <input id="picture-upload" type="file" accept="image/*" class="ui-button" style="display:none"></input>
+        <input id="video-upload" type="file" accept="video/*" class="ui-button" style="display:none"></input>
+        <input id="audio-upload" type="file" accept="audio/*" class="ui-button" style="display:none"></input>
+        <button id="select-file" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only">
+            <span class="ui-button-text">Select a Picture</span>
+        </button>
+        <button id="take-file" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only">
+            <span class="ui-button-text">Use Selected Picture</span>
+        </button>
+    </section>
 </section>
diff --git a/lib/ripple/ui/plugins/geoView.js b/lib/ripple/ui/plugins/geoView.js
index 39ed890..495293e 100644
--- a/lib/ripple/ui/plugins/geoView.js
+++ b/lib/ripple/ui/plugins/geoView.js
@@ -210,7 +210,9 @@
         }
 
         // HACK: see techdebt http://www.pivotaltracker.com/story/show/5478847 (double HACK!!!)
-        if (platform.current().id === 'phonegap' || platform.current().id === 'webworks') {
+        if (platform.current().id === 'phonegap' || 
+            platform.current().id === 'webworks' ||
+            platform.current().id === 'cordova') {
             // make the fields visible
             jQuery("#geo-cellid-container").hide();
             jQuery("#geo-heading-container").show();
@@ -247,7 +249,7 @@
         });
 
         // HACK: see techdebt http://www.pivotaltracker.com/story/show/5478847 (double HACK!!!)
-        if (platform.current().id === 'phonegap' || platform.current().id === 'webworks') {
+        if (platform.current().id === 'phonegap' || platform.current().id === 'webworks' || platform.current().id === 'cordova') {
             jQuery("#" + GEO_OPTIONS.HEADING).bind("change", function () {
                 updateGeo();
                 updateHeadingValues();
diff --git a/lib/ripple/ui/plugins/invoke.js b/lib/ripple/ui/plugins/invoke.js
new file mode 100644
index 0000000..8e8c361
--- /dev/null
+++ b/lib/ripple/ui/plugins/invoke.js
@@ -0,0 +1,43 @@
+/*
+ *  Copyright 2011 Research In Motion Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var constants = require('ripple/constants'),
+    event = require('ripple/event');
+
+module.exports = {
+    panel: {
+        domId: "invoke-container",
+        collapsed: true,
+        pane: "left"
+    },
+    initialize: function () {
+        document.getElementById("invoke-send")
+            .addEventListener("click", function () {
+                var invokeInfo = {};
+
+                invokeInfo.source = document.getElementById("invoke-source-text").value;
+                invokeInfo.target = document.getElementById("invoke-target-text").value;
+                invokeInfo.action = document.getElementById("invoke-action-text").value;
+                invokeInfo.mimeType = document.getElementById("invoke-mime-type-text").value;
+                invokeInfo.extension = document.getElementById("invoke-extension-text").value;
+                invokeInfo.data = document.getElementById("invoke-data-text").value;
+                if (invokeInfo.data) {
+                    invokeInfo.data = window.btoa(invokeInfo.data);
+                }
+
+                event.trigger("AppInvoke", [invokeInfo]);
+            }, false);
+    }
+};
diff --git a/lib/ripple/ui/plugins/invoke/panel.html b/lib/ripple/ui/plugins/invoke/panel.html
new file mode 100644
index 0000000..e9045b5
--- /dev/null
+++ b/lib/ripple/ui/plugins/invoke/panel.html
@@ -0,0 +1,71 @@
+<!--
+ *  Copyright 2011 Research In Motion Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+<section id="invoke-container" class="ui-box ui-state-default ui-corner-all">
+    <section class="h2 info-header">
+        <section class="collapse-handle">Invoke</section>
+        <section class="drag-handle ui-state-default ui-corner-all ui-state-hover">
+            <span class="ui-icon ui-icon-arrow-4"></span>
+        </section>
+    </section>
+
+    <section id="invoke" class="info ui-widget-content ui-corner-all" style="display: none;">
+        <section class="invoke-info">
+            <table id="invoke-fields" class="panel-table">
+                <tr>
+                    <td><label class="ui-text-label">Source</label></td>
+                    <td><input class="ui-state-default ui-corner-all" id="invoke-source-text" type="text"/></td>
+                </tr>
+                <tr>
+                    <td><label class="ui-text-label">Target</label></td>
+                    <td><input class="ui-state-default ui-corner-all" id="invoke-target-text" type="text"/></td>
+                </tr>
+                <tr>
+                    <td><label class="ui-text-label">Action</label></td>
+                    <td><input class="ui-state-default ui-corner-all" id="invoke-action-text" type="text"/></td>
+                </tr>
+                <tr>
+                    <td><label class="ui-text-label">mime-type</label></td>
+                    <td><input class="ui-state-default ui-corner-all" id="invoke-mime-type-text" type="text"/></td>
+                </tr>
+                <tr>
+                    <td><label class="ui-text-label">Extension (if Data block represents a file)</label></td>
+                    <td><input class="ui-state-default ui-corner-all" id="invoke-extension-text" type="text"/></td>
+                </tr>
+            </table>
+
+            <table class="panel-table">
+                <tr>
+                    <td colspan="2">
+                        <label class="ui-text-label">Data (will be base64 encoded)</label>
+                    </td>
+                </tr>
+                <tr>
+                    <td colspan="2">
+                        <textarea class="ui-state-default ui-corner-all" id="invoke-data-text" rows="4" style="width: 90%;"></textarea>
+                    </td>
+                </tr>
+
+                <tr>
+                    <td colspan="2">
+                        <button id="invoke-send" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only">
+                            <span class="ui-button-text">Send Invoke Request</span>
+                        </button>
+                    </td>
+                </tr>
+            </table>
+        </section>
+    </section>
+</section>
diff --git a/lib/ripple/utils.js b/lib/ripple/utils.js
index 258fbc0..48d3fe1 100644
--- a/lib/ripple/utils.js
+++ b/lib/ripple/utils.js
@@ -145,6 +145,19 @@
         return window.location;
     },
 
+    queryString: function () {
+        // trim the leading ? and split each name=value
+        var args = this.location().search.replace(/^\?/, '').split('&');
+
+        return args.reduce(function (obj, value) {
+            if (value) {
+                value = value.toLowerCase().split("=");
+                obj[value[0]] = value[1];
+            }
+            return obj;
+        }, {});
+    },
+
     extensionUrl: function () {
         return document.getElementById("extension-url").innerText;
     },
diff --git a/lib/ripple/widgetConfig.js b/lib/ripple/widgetConfig.js
index 0d58995..2d62e5c 100644
--- a/lib/ripple/widgetConfig.js
+++ b/lib/ripple/widgetConfig.js
@@ -381,6 +381,36 @@
     }
 }
 
+function _xmlToJson(xml) {
+    var obj = {};
+
+    if (xml.nodeType === 1) { // element
+        if (xml.attributes.length > 0) {
+            obj["@attributes"] = {};
+            utils.forEach(xml.attributes, function (attribute) {
+                obj["@attributes"][attribute.nodeName] = attribute.nodeValue;
+            });
+        }
+    } 
+    else if (xml.nodeType === 3) { // text node
+        obj = xml.nodeValue;
+    }
+
+    if (xml.hasChildNodes && xml.hasChildNodes()) {
+        utils.forEach(xml.childNodes, function (child) {
+            if (!child.nodeName) {
+                return;
+            }
+            if (!obj[child.nodeName]) {
+                obj[child.nodeName] = [];
+            }
+            obj[child.nodeName].push(_xmlToJson(child));
+        });
+    }
+
+    return obj;
+}
+
 module.exports = {
 
     validate: function (configXML) {
@@ -403,6 +433,7 @@
             xmlHttp.send();
             if (xmlHttp.responseXML) {
                 results = _validate(xmlHttp.responseXML);
+                results.rawJSON = _xmlToJson(xmlHttp.responseXML);
                 _process(results);
                 _configValidationResults = results;
             }
diff --git a/package.json b/package.json
index 4554ca0..967adbf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "ripple",
-  "version": "0.9.6.1",
+  "version": "0.9.7",
   "description": "A browser based html5 mobile application development and testing tool",
   "homepage": "http://github.com/blackberry/Rippe-UI",
   "author": {
@@ -14,9 +14,9 @@
   "bin": { "ripple": "./bin/ripple" },
   "main": "lib/index",
   "dependencies": {
-    "connect": "1.7.x",
+    "connect": "2.3.6",
     "argsparser": "0.0.x",
-    "jsdom": "0.2.4",
+    "jsdom": "0.2.14",
     "jWorkflow": "*"
   },
   "bundledDependencies": [
diff --git a/test/integration/webworks.tablet/systemEvent.js b/test/integration/webworks.tablet/systemEvent.js
index a86fd2b..d3df7e2 100644
--- a/test/integration/webworks.tablet/systemEvent.js
+++ b/test/integration/webworks.tablet/systemEvent.js
@@ -50,20 +50,6 @@
     });
 
     describe("deviceBatteryStateChange", function () {
-        it("registers and invokes onBatteryStateChange", function () {
-            var listener = jasmine.createSpy();
-            systemEvent.deviceBatteryStateChange(listener);
-
-            _run(20, [
-                function () {
-                    event.trigger("DeviceBatteryStateChanged", [false]);
-                },
-                function () {
-                    expect(listener).toHaveBeenCalledWith(3); // UNPLUGGED
-                }
-            ]);
-        });
-
         it("de-registers onBatterStateChange", function () {
             var listener = jasmine.createSpy();
             systemEvent.deviceBatteryStateChange(listener);
@@ -80,6 +66,21 @@
                 }
             ]);
         });
+
+        it("registers and invokes onBatteryStateChange", function () {
+            var listener = jasmine.createSpy();
+            systemEvent.deviceBatteryStateChange(listener);
+
+            _run(20, [
+                function () {
+                    event.trigger("DeviceBatteryStateChanged", [false]);
+                },
+                function () {
+                    expect(listener).toHaveBeenCalledWith(3); // UNPLUGGED
+                }
+            ]);
+        });
+
     });
 
     describe("deviceBatteryLevelChange", function () {
diff --git a/test/unit/accelerometer.js b/test/unit/accelerometer.js
index f00fe01..47eddac 100644
--- a/test/unit/accelerometer.js
+++ b/test/unit/accelerometer.js
@@ -148,5 +148,4 @@
         accelValues = accelerometer.getInfo(true);
         expect(initialX).not.toBe(accelValues.x);
     });
-
 });
diff --git a/test/unit/cordova/accelerometer.js b/test/unit/cordova/accelerometer.js
new file mode 100644
index 0000000..6e79059
--- /dev/null
+++ b/test/unit/cordova/accelerometer.js
@@ -0,0 +1,76 @@
+/*
+ *  Copyright 2011 Research In Motion Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+describe("cordova accelerometer bridge", function () {
+    var accel = require('ripple/platform/cordova/2.0.0/bridge/accelerometer'),
+        event = require('ripple/event');
+
+    beforeEach(function () {
+        spyOn(window, "setInterval").andReturn(1);
+        spyOn(window, "clearInterval");
+    });
+
+    afterEach(function () {
+        accel.stop();
+    });
+
+    describe("when starting", function () {
+        it("starts an interval", function () {
+            var s = jasmine.createSpy("success"),
+                f = jasmine.createSpy("fail");
+
+            accel.start(s, f);
+            expect(window.setInterval).toHaveBeenCalledWith(jasmine.any(Function), 50);
+        });
+
+        it("the interval function calls the success callback with the AccelerometerInfoChangedEvent", function () {
+            var s = jasmine.createSpy("success"),
+                f = jasmine.createSpy("fail");
+
+            accel.start(s, f);
+
+            event.trigger("AccelerometerInfoChangedEvent", [{
+                accelerationIncludingGravity: {
+                    x: 9.8,
+                    y: 9.8,
+                    z: 9.8
+                }
+            }], true);
+
+            window.setInterval.mostRecentCall.args[0]();
+
+            expect(s).toHaveBeenCalledWith({
+                x: 9.8,
+                y: 9.8,
+                z: 9.8,
+                timestamp: jasmine.any(Number)
+            });
+
+            expect(f).not.toHaveBeenCalled();
+        });
+    });
+
+    describe("when stopping", function () {
+        it("it clears the interval", function () {
+            var s = jasmine.createSpy("success"),
+                f = jasmine.createSpy("fail");
+
+            accel.start(s, f);
+            accel.stop();
+
+            expect(window.clearInterval).toHaveBeenCalledWith(1);
+        });
+    });
+});
diff --git a/test/unit/cordova/compass.js b/test/unit/cordova/compass.js
new file mode 100644
index 0000000..e5e5c3e
--- /dev/null
+++ b/test/unit/cordova/compass.js
@@ -0,0 +1,42 @@
+/*
+ *  Copyright 2011 Research In Motion Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+describe("cordova compass bridge", function () {
+    var geo = require('ripple/geo'),
+        target = require('ripple/platform/cordova/2.0.0/bridge/compass'),
+        heading = {direction: "southish"};
+
+    beforeEach(function () {
+        spyOn(geo, "getPositionInfo").andReturn({
+            heading: heading
+        });
+    });
+
+    describe("when calling getHeading", function () {
+        it("it returns the heading from geo", function () {
+            var success = jasmine.createSpy("success");
+
+            target.getHeading(success);
+            
+            expect(geo.getPositionInfo).toHaveBeenCalled();
+            expect(success).toHaveBeenCalledWith({
+                magneticHeading: heading,
+                trueHeading: heading,
+                headingAccuracy: 100,
+                timestamp: jasmine.any(Number)
+            });
+        });
+    });
+});
diff --git a/test/unit/cordova/media.js b/test/unit/cordova/media.js
new file mode 100644
index 0000000..2d3fc9c
--- /dev/null
+++ b/test/unit/cordova/media.js
@@ -0,0 +1,426 @@
+/*
+ *  Copyright 2011 Research In Motion Limited.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+describe("cordova media bridge object", function () {
+    var media = require('ripple/platform/cordova/2.0.0/bridge/media'),
+        audio = {
+            play: jasmine.createSpy("audio.play"),
+            pause: jasmine.createSpy("audio.pause"),
+            addEventListener: jasmine.createSpy("audio.addEventListener")
+        };
+
+    beforeEach(function () {
+        audio.play.reset();
+        global.Audio = window.Audio = jasmine.createSpy().andReturn(audio);
+    });
+
+    describe("when creating", function () {
+        it("creates an audio object for the src", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.create(success, error, ["id", "foo.mp3"]);
+
+            expect(window.Audio).toHaveBeenCalled();
+            expect(audio.src).toBe("foo.mp3");
+            expect(success).toHaveBeenCalled();
+            expect(error).not.toHaveBeenCalled();
+        });
+
+        it("calls the error callback when args is empty", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.create(success, error, []);
+
+            expect(success).not.toHaveBeenCalled();
+            expect(error).toHaveBeenCalled();
+        });
+
+        it("can be called without a success callback", function () {
+            expect(function () {
+                media.create(null, null, ["1"]);
+            }).not.toThrow();
+        });
+
+        it("can be called without an error callback", function () {
+            expect(function () {
+                media.create(jasmine.createSpy(), null, ["1"]);
+            }).not.toThrow();
+        });
+
+        it("adds and event listener for error", function () {
+            media.create(null, null, ["1"]);
+            expect(audio.addEventListener).toHaveBeenCalledWith("error", jasmine.any(Function));
+        });
+
+        it("adds and event listener for durationchange", function () {
+            media.create(null, null, ["1"]);
+            expect(audio.addEventListener).toHaveBeenCalledWith("durationchange", jasmine.any(Function));
+        });
+    });
+
+    describe("when starting audio", function () {
+        it("creates an audio object", function () {
+            media.startPlayingAudio(null, null, ["a", "fred.mp3"]);
+            expect(window.Audio).toHaveBeenCalledWith();
+            expect(audio.src).toBe("fred.mp3");
+        });
+
+        it("calls play on the audio object", function () {
+            media.startPlayingAudio(null, null, ["b", "bar.mp3"]);
+            expect(audio.play).toHaveBeenCalled();
+            expect(audio.play.callCount).toBe(1);
+        });
+
+        it("calls the error callback when no args", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.startPlayingAudio(success, error, []);
+
+            expect(error).toHaveBeenCalled();
+            expect(success).not.toHaveBeenCalled();
+        });
+
+        it("calls the error callback when just an id arg", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.startPlayingAudio(success, error, ["larry"]);
+
+            expect(error).toHaveBeenCalled();
+            expect(success).not.toHaveBeenCalled();
+        });
+
+        it("calls the pause method when id exists (as well as the play method)", function () {
+            media.startPlayingAudio(null, null, ["c", "crownRoyal.mp3"]);
+            media.startPlayingAudio(null, null, ["c", "wisers.mp3"]);
+            expect(audio.pause).toHaveBeenCalled();
+            expect(audio.play).toHaveBeenCalled();
+            expect(audio.pause.callCount).toBe(1);
+            expect(audio.play.callCount).toBe(2);
+        });
+
+        it("calls the success callback when things are all rainbows and ponies", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.startPlayingAudio(success, error, ["chimay", "trios pistolues"]);
+
+            expect(success).toHaveBeenCalled();
+            expect(error).not.toHaveBeenCalled();
+        });
+    });
+
+    describe("when stopping audio", function () {
+        it("calls the error callback when no args", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.stopPlayingAudio(success, error, []);
+
+            expect(error).toHaveBeenCalled();
+            expect(success).not.toHaveBeenCalled();
+        });
+
+        it("calls the error callback when it can't find the audio obj", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.stopPlayingAudio(success, error, ['lamb_burger']);
+
+            expect(error).toHaveBeenCalled();
+            expect(success).not.toHaveBeenCalled();
+        });
+
+        it("calls pause on the audio object when things are 20% cooler", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.startPlayingAudio(success, error, ['milk_duds', 'yum.mp3']);
+            audio.pause.reset();
+            media.stopPlayingAudio(success, error, ['milk_duds']);
+
+            expect(audio.pause).toHaveBeenCalled();
+        });
+
+        it("calls pause on the success callback when things are 20% cooler", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.startPlayingAudio(success, error, ['nomnomnom', 'yum.mp3']);
+            audio.pause.reset();
+            media.stopPlayingAudio(success, error, ['nomnomnom']);
+
+            expect(success).toHaveBeenCalled();
+        });
+    });
+
+    describe("when seeking the audio", function () {
+        beforeEach(function () {
+            media.create(null, null, ['seek', 'until_it_sleeps.mp3']);
+        });
+
+        afterEach(function () {
+            media.stopPlayingAudio(null, null, ['seek']);
+        });
+
+        it("calls the error callback when no args", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.seekToAudio(success, error, []);
+
+            expect(error).toHaveBeenCalled();
+            expect(success).not.toHaveBeenCalled();
+        });
+
+        it("calls the error callback when it can't find the id", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.seekToAudio(success, error, ['hey_you_guys']);
+
+            expect(error).toHaveBeenCalled();
+            expect(success).not.toHaveBeenCalled();
+        });
+
+        it("calls the error callback when the seek time isn't provided", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.seekToAudio(success, error, ['seek']);
+
+            expect(error).toHaveBeenCalled();
+            expect(success).not.toHaveBeenCalled();
+        });
+
+        it("sets the currentTime on the audio object", function () {
+            media.seekToAudio(null, null, ['seek', 12345]);
+            expect(audio.currentTime).toBe(12345);
+        });
+
+        it("calls the success callback", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.seekToAudio(success, error, ['seek', 35]);
+
+            expect(success).toHaveBeenCalled();
+            expect(error).not.toHaveBeenCalled();
+        });
+    });
+
+    describe("when pausing audio", function () {
+        beforeEach(function () {
+            media.create(null, null, ['pause', 'hey_jude.mp3']);
+        });
+
+        afterEach(function () {
+            media.stopPlayingAudio(null, null, ['pause']);
+        });
+
+        it("calls the error callback when no args", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.pausePlayingAudio(success, error, []);
+
+            expect(error).toHaveBeenCalled();
+            expect(success).not.toHaveBeenCalled();
+        });
+
+        it("calls the error callback when it can't find the id", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.pausePlayingAudio(success, error, ['all along the watchtower']);
+
+            expect(error).toHaveBeenCalled();
+            expect(success).not.toHaveBeenCalled();
+        });
+
+        it("calls the pause method on the audio object", function () {
+            media.pausePlayingAudio(null, null, ['pause']);
+            expect(audio.pause).toHaveBeenCalled();
+        });
+
+        it("calls the success callback", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.pausePlayingAudio(success, error, ['pause']);
+
+            expect(success).toHaveBeenCalled();
+            expect(error).not.toHaveBeenCalled();
+        });
+    });
+
+    describe("when getting the current position", function () {
+        beforeEach(function () {
+            media.create(null, null, ['position', 'space_hog.mp3']);
+        });
+
+        afterEach(function () {
+            media.stopPlayingAudio(null, null, ['position']);
+        });
+
+        it("calls the error callback when no args", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.getCurrentPositionAudio(success, error, []);
+
+            expect(error).toHaveBeenCalled();
+            expect(success).not.toHaveBeenCalled();
+        });
+
+        it("calls the error callback when it can't find the id", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.getCurrentPositionAudio(success, error, ['hey you guys']);
+
+            expect(error).toHaveBeenCalled();
+            expect(success).not.toHaveBeenCalled();
+        });
+
+        it("calls the success callback with the currentTime", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            audio.currentTime = 12;
+            media.getCurrentPositionAudio(success, error, ['position']);
+
+            expect(success).toHaveBeenCalledWith(12);
+            expect(error).not.toHaveBeenCalled();
+        });
+    });
+
+    describe("when getting the duration", function () {
+        beforeEach(function () {
+            media.create(null, null, ['duration', 'cum_on_feel_the_noise.mp3']);
+        });
+
+        afterEach(function () {
+            media.stopPlayingAudio(null, null, ['duration']);
+        });
+
+        it("calls the error callback when no args", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.getDuration(success, error, []);
+
+            expect(error).toHaveBeenCalled();
+            expect(success).not.toHaveBeenCalled();
+        });
+
+        it("calls the error callback when it can't find the id", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.getDuration(success, error, ['peanuts']);
+
+            expect(error).toHaveBeenCalled();
+            expect(success).not.toHaveBeenCalled();
+        });
+
+        it("calls the success callback with the currentTime", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            audio.duration = 80000;
+            media.getDuration(success, error, ['duration']);
+
+            expect(success).toHaveBeenCalledWith(80000);
+            expect(error).not.toHaveBeenCalled();
+        });
+    });
+
+    describe("when starting to record audio", function () {
+        it("can be called with no callbacks", function () {
+            expect(function () {
+                media.startRecordingAudio();
+            }).not.toThrow();
+        });
+
+        it("calls the error callback", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.startRecordingAudio(success, error, []);
+
+            expect(success).not.toHaveBeenCalled();
+            expect(error).toHaveBeenCalled();
+        });
+    });
+
+    describe("when stopping recording audio", function () {
+        it("can be called with no callbacks", function () {
+            expect(function () {
+                media.stopRecordingAudio();
+            }).not.toThrow();
+        });
+
+        it("calls the error callback", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.stopRecordingAudio(success, error, []);
+
+            expect(success).not.toHaveBeenCalled();
+            expect(error).toHaveBeenCalled();
+        });
+    });
+
+    describe("when releasing", function () {
+        beforeEach(function () {
+            media.create(null, null, ['release', 'just_beat_it.mp3']);
+        });
+
+        it("calls the error callback when no args", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.release(success, error, []);
+
+            expect(error).toHaveBeenCalled();
+            expect(success).not.toHaveBeenCalled();
+        });
+
+        it("calls the success callback when it can't find the id", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            media.release(success, error, ['rainbow dash']);
+
+            expect(success).toHaveBeenCalled();
+            expect(error).not.toHaveBeenCalled();
+        });
+
+        it("calls the success callback", function () {
+            var success = jasmine.createSpy("success"),
+                error = jasmine.createSpy("error");
+
+            audio.duration = 80000;
+            media.release(success, error, ['release']);
+
+            expect(success).toHaveBeenCalled();
+            expect(error).not.toHaveBeenCalled();
+        });
+    });
+});
diff --git a/test/unit/emulatorBridge.js b/test/unit/emulatorBridge.js
index b5857a1..0b96f64 100644
--- a/test/unit/emulatorBridge.js
+++ b/test/unit/emulatorBridge.js
@@ -18,6 +18,7 @@
     var emulatorBridge = require('ripple/emulatorBridge'),
         platform = require('ripple/platform'),
         old_gElById,
+        _currentPlatformInit,
         _emulatedBody,
         _emulatedHtml,
         _emulatedDocument,
@@ -42,6 +43,7 @@
 
         window.tinyHippos = {};
 
+        _currentPlatformInit = jasmine.createSpy('platform.current().initialize');
         _emulatedViewport = document.createElement("section");
         _emulatedDocument = document.createElement("section");
         _emulatedHtml = document.createElement("section");
@@ -54,13 +56,16 @@
         _emulatedDocument.appendChild(_emulatedHtml);
         _emulatedViewport.appendChild(_emulatedDocument);
 
-        spyOn(platform, "current").andReturn({objects: {
-            foo: {a: 1},
-            bar: {b: 1},
-            woot: [1, 2, 3, 4, 5]
-        }});
+        spyOn(platform, "current").andReturn({
+            initialize: _currentPlatformInit,
+            objects: {
+                foo: {a: 1},
+                bar: {b: 1},
+                woot: [1, 2, 3, 4, 5]
+            }
+        });
 
-        emulatorBridge.link(_emulatedFrame);
+        emulatorBridge.link(_emulatedFrame.contentWindow);
     });
 
     afterEach(function () {
@@ -114,6 +119,10 @@
         expect(window.XMLHttpRequest).toBe(_emulatedFrame.contentWindow.XMLHttpRequest);
     });
 
+    it("initializes the current platform (if method exists)", function () {
+        expect(_currentPlatformInit).toHaveBeenCalledWith(_emulatedFrame.contentWindow);
+    });
+
     it("it marshals over everything in the sandbox", function () {
         expect(window.foo).toBeDefined();
         expect(window.bar).toBeDefined();
diff --git a/test/unit/fs.js b/test/unit/fs.js
index 0918cc6..d26b14c 100644
--- a/test/unit/fs.js
+++ b/test/unit/fs.js
@@ -13,12 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-var fs = require('ripple/fs'),
-    event = require('ripple/event'),
-    utils = require('ripple/utils');
-
-xdescribe("fs", function () {
-    var _resultEntries,
+describe("fs", function () {
+    var fs = require('ripple/fs'),
+        event = require('ripple/event'),
+        utils = require('ripple/utils'),
+        _resultEntries,
         _dirEntry,
         _baton,
         _fs,
diff --git a/test/unit/phonegap/accelerometer.js b/test/unit/phonegap/accelerometer.js
index 938003a..c600458 100644
--- a/test/unit/phonegap/accelerometer.js
+++ b/test/unit/phonegap/accelerometer.js
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 describe("phonegap_accelerometer", function () {
-    var accel = require('ripple/platform/phonegap/1.0/accelerometer'),
+    var accel = require('ripple/platform/phonegap/1.0.0/accelerometer'),
         event = require('ripple/event'),
         platform = require('ripple/platform');
 
diff --git a/test/unit/phonegap/camera.js b/test/unit/phonegap/camera.js
index ca484ac..ca6a23f 100644
--- a/test/unit/phonegap/camera.js
+++ b/test/unit/phonegap/camera.js
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 describe("phonegap_camera", function () {
-    var camera = require('ripple/platform/phonegap/1.0/camera');
+    var camera = require('ripple/platform/phonegap/1.0.0/camera');
 
     it("it calls the error callback ALL the time", function () {
         var success = jasmine.createSpy(),
diff --git a/test/unit/phonegap/compass.js b/test/unit/phonegap/compass.js
index 784673e..a71d3e4 100644
--- a/test/unit/phonegap/compass.js
+++ b/test/unit/phonegap/compass.js
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 describe("phonegap_compass", function () {
-    var compass = require('ripple/platform/phonegap/1.0/compass'),
+    var compass = require('ripple/platform/phonegap/1.0.0/compass'),
         geo = require('ripple/geo');
 
     it("clearWatch clears interval", function () {
diff --git a/test/unit/phonegap/contacts.js b/test/unit/phonegap/contacts.js
index 43dce76..bde3435 100644
--- a/test/unit/phonegap/contacts.js
+++ b/test/unit/phonegap/contacts.js
@@ -17,11 +17,11 @@
     var db = require('ripple/db'),
         event = require('ripple/event'),
         utils = require('ripple/utils'),
-        Contact = require('ripple/platform/phonegap/1.0/Contact'),
-        ContactError = require('ripple/platform/phonegap/1.0/ContactError'),
-        ContactField = require('ripple/platform/phonegap/1.0/ContactField'),
-        ContactFindOptions = require('ripple/platform/phonegap/1.0/ContactFindOptions'),
-        contacts = require('ripple/platform/phonegap/1.0/contacts');
+        Contact = require('ripple/platform/phonegap/1.0.0/Contact'),
+        ContactError = require('ripple/platform/phonegap/1.0.0/ContactError'),
+        ContactField = require('ripple/platform/phonegap/1.0.0/ContactField'),
+        ContactFindOptions = require('ripple/platform/phonegap/1.0.0/ContactFindOptions'),
+        contacts = require('ripple/platform/phonegap/1.0.0/contacts');
 
     beforeEach(function () {
         spyOn(window, "setTimeout").andCallFake(function (func) {
@@ -30,51 +30,51 @@
     });
 
     describe("spec", function () {
-        var spec = require('ripple/platform/phonegap/1.0/spec');
+        var spec = require('ripple/platform/phonegap/1.0.0/spec');
 
         it("includes contacts module according to proper object structure", function () {
             expect(spec.objects.navigator.children.contacts.path)
-                .toEqual("phonegap/1.0/contacts");
+                .toEqual("phonegap/1.0.0/contacts");
         });
 
         it("includes ContactError module according to proper object structure", function () {
             expect(spec.objects.ContactError.path)
-                .toEqual("phonegap/1.0/ContactError");
+                .toEqual("phonegap/1.0.0/ContactError");
         });
 
         it("includes Contact module according to proper object structure", function () {
             expect(spec.objects.Contact.path)
-                .toEqual("phonegap/1.0/Contact");
+                .toEqual("phonegap/1.0.0/Contact");
         });
 
         it("includes ContactName module according to proper object structure", function () {
             expect(spec.objects.ContactName.path)
-                .toEqual("phonegap/1.0/ContactName");
+                .toEqual("phonegap/1.0.0/ContactName");
         });
 
         it("includes ContactAccount module according to proper object structure", function () {
             expect(spec.objects.ContactAccount.path)
-                .toEqual("phonegap/1.0/ContactAccount");
+                .toEqual("phonegap/1.0.0/ContactAccount");
         });
 
         it("includes ContactAddress module according to proper object structure", function () {
             expect(spec.objects.ContactAddress.path)
-                .toEqual("phonegap/1.0/ContactAddress");
+                .toEqual("phonegap/1.0.0/ContactAddress");
         });
 
         it("includes ContactOrganization module according to proper object structure", function () {
             expect(spec.objects.ContactOrganization.path)
-                .toEqual("phonegap/1.0/ContactOrganization");
+                .toEqual("phonegap/1.0.0/ContactOrganization");
         });
 
         it("includes ContactFindOptions module according to proper object structure", function () {
             expect(spec.objects.ContactFindOptions.path)
-                .toEqual("phonegap/1.0/ContactFindOptions");
+                .toEqual("phonegap/1.0.0/ContactFindOptions");
         });
 
         it("includes ContactField module according to proper object structure", function () {
             expect(spec.objects.ContactField.path)
-                .toEqual("phonegap/1.0/ContactField");
+                .toEqual("phonegap/1.0.0/ContactField");
         });
     });
 
diff --git a/test/unit/phonegap/device.js b/test/unit/phonegap/device.js
index b2393b3..cdd9282 100644
--- a/test/unit/phonegap/device.js
+++ b/test/unit/phonegap/device.js
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 describe("phonegap_device", function () {
-    var device = require('ripple/platform/phonegap/1.0/device'),
+    var device = require('ripple/platform/phonegap/1.0.0/device'),
         devices = require('ripple/devices');
 
     it("asks the device for the name", function () {
diff --git a/test/unit/phonegap/events.js b/test/unit/phonegap/events.js
index 047a425..77f7b98 100644
--- a/test/unit/phonegap/events.js
+++ b/test/unit/phonegap/events.js
@@ -1,5 +1,5 @@
 describe("phonegap events", function () {
-    var spec = require('ripple/platform/phonegap/1.0/spec'),
+    var spec = require('ripple/platform/phonegap/1.0.0/spec'),
         emulatorBridge = require('ripple/emulatorBridge'),
         events = spec.events;
 
diff --git a/test/unit/phonegap/navigator.js b/test/unit/phonegap/navigator.js
index 9e565e0..a2f047b 100644
--- a/test/unit/phonegap/navigator.js
+++ b/test/unit/phonegap/navigator.js
@@ -22,7 +22,7 @@
 
     beforeEach(function () {
         spyOn(devices, "getCurrentDevice").andReturn("WTF");
-        navigator = require('ripple/platform/phonegap/1.0/navigator');
+        navigator = require('ripple/platform/phonegap/1.0.0/navigator');
     });
 
     it("it fires device ready and logs when tinyHippos Loaded event is raised", function () {
diff --git a/test/unit/phonegap/network.js b/test/unit/phonegap/network.js
index 8be892b..98409b6 100644
--- a/test/unit/phonegap/network.js
+++ b/test/unit/phonegap/network.js
@@ -15,7 +15,7 @@
  */
 describe("phonegap_network", function () {
     var deviceSettings = require('ripple/deviceSettings'),
-        network = require('ripple/platform/phonegap/1.0/network');
+        network = require('ripple/platform/phonegap/1.0.0/network');
 
     it("returns the value from deviceSettings", function () {
         spyOn(deviceSettings, "retrieve").andReturn("tin_cans");
diff --git a/test/unit/phonegap/notification.js b/test/unit/phonegap/notification.js
index 51791cc..3ba7dba 100644
--- a/test/unit/phonegap/notification.js
+++ b/test/unit/phonegap/notification.js
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 describe("phonegap notifications", function () {
-    var notification = require('ripple/platform/phonegap/1.0/notification'),
+    var notification = require('ripple/platform/phonegap/1.0.0/notification'),
         goodVibrations = require('ripple/ui/plugins/goodVibrations'),
         notifications = require('ripple/notifications');
 
diff --git a/test/unit/platform.js b/test/unit/platform.js
index 382f392..7b433ed 100644
--- a/test/unit/platform.js
+++ b/test/unit/platform.js
@@ -18,12 +18,14 @@
         db = require('ripple/db'),
         app = require('ripple/app'),
         builder = require('ripple/platform/builder'),
+        utils = require('ripple/utils'),
         event = require('ripple/event'),
         _console = require('ripple/console');
 
     beforeEach(function () {
         spyOn(db, "retrieveObject");
         spyOn(_console, "log");
+        spyOn(utils, "queryString").andReturn({});
         spyOn(builder, "build").andReturn({
             into: function () {}
         });
@@ -33,9 +35,9 @@
     it("getList should return correct value", function () {
         var returnedPlatforms = platform.getList();
 
-        expect(typeof returnedPlatforms["phonegap"]["1.0"].id).toEqual("string");
-        expect(typeof returnedPlatforms["phonegap"]["1.0"].name).toEqual("string");
-        expect(typeof returnedPlatforms["phonegap"]["1.0"].type).toEqual("string");
+        expect(typeof returnedPlatforms["phonegap"]["1.0.0"].id).toEqual("string");
+        expect(typeof returnedPlatforms["phonegap"]["1.0.0"].name).toEqual("string");
+        expect(typeof returnedPlatforms["phonegap"]["1.0.0"].type).toEqual("string");
     });
 
     describe("when changing the environment", function () {
diff --git a/test/unit/resizer.js b/test/unit/resizer.js
index 23b1922..08c8b38 100644
--- a/test/unit/resizer.js
+++ b/test/unit/resizer.js
@@ -25,7 +25,6 @@
         devices = require('ripple/devices'),
         event = require('ripple/event'),
         db = require('ripple/db'),
-        emulatorBridge = require('ripple/emulatorBridge'),
         platform = require('ripple/platform'),
         _console = require('ripple/console'),
         resizer = require('ripple/resizer');
diff --git a/test/unit/utils.js b/test/unit/utils.js
index 7e005d5..35e3938 100644
--- a/test/unit/utils.js
+++ b/test/unit/utils.js
@@ -550,6 +550,42 @@
         });
     });
 
+    describe("queryString", function () {
+
+        it("can handle a location with no params", function () {
+            spyOn(utils, "location").andReturn({
+                search: ""
+            });
+
+            expect(utils.queryString()).toEqual({});
+        });
+
+        it("can handle a location with a single qs", function () {
+            spyOn(utils, "location").andReturn({
+                search: "?foo=bar"
+            });
+
+            expect(utils.queryString()).toEqual({foo: "bar"});
+        });
+
+        it("can handle a location with a couple of qs", function () {
+            spyOn(utils, "location").andReturn({
+                search: "?foo=bar&baz=fred"
+            });
+
+            expect(utils.queryString()).toEqual({foo: "bar", baz: "fred"});
+        });
+
+        it("lowercases the values", function () {
+            spyOn(utils, "location").andReturn({
+                search: "?YO=Momma&Is=soFat"
+            });
+
+            expect(utils.queryString()).toEqual({yo: "momma", is: "sofat"});
+
+        });
+    });
+
     describe("rippleLocation", function () {
         describe("properly returns the base path for ripple-ui", function () {
             it("returns the base path when index.html is used", function () {
diff --git a/test/unit/webworks.bb10/event.js b/test/unit/webworks.bb10/event.js
index 805c527..966a406 100644
--- a/test/unit/webworks.bb10/event.js
+++ b/test/unit/webworks.bb10/event.js
@@ -23,7 +23,7 @@
                 var cb = jasmine.createSpy();
 
                 target.addEventListener("pause", cb);
-                event.trigger("appPause", null, true);
+                event.trigger("AppPause", null, true);
                 expect(cb).toHaveBeenCalled();
             });
 
@@ -33,7 +33,7 @@
 
                 target.addEventListener("pause", cb);
                 target.addEventListener("pause", cb2);
-                event.trigger("appPause", null, true);
+                event.trigger("AppPause", null, true);
                 expect(cb).toHaveBeenCalled();
                 expect(cb2).toHaveBeenCalled();
             });
@@ -43,7 +43,7 @@
 
                 target.addEventListener("pause", cb);
                 target.addEventListener("pause", cb);
-                event.trigger("appPause", null, true);
+                event.trigger("AppPause", null, true);
                 expect(cb.callCount).toBe(1);
             });
 
@@ -65,7 +65,7 @@
                 });
 
                 target.addEventListener("resume", cb);
-                event.trigger("appResume", null, true);
+                event.trigger("AppResume", null, true);
                 expect(cb).toHaveBeenCalled();
             });
 
@@ -91,7 +91,7 @@
 
                 target.addEventListener("pause", cb);
                 target.removeEventListener("pause", cb);
-                event.trigger("appPause", null, true);
+                event.trigger("AppPause", null, true);
                 expect(cb).not.toHaveBeenCalled();
             });
         });
@@ -103,19 +103,19 @@
                 spyOn(settings, "retrieve").andReturn("22");
             });
 
-            it("triggers the pause event on appPause", function () {
+            it("triggers the pause event on AppPause", function () {
                 var cb = jasmine.createSpy();
 
                 target.addEventListener("pause", cb);
-                event.trigger("appPause", null, true);
+                event.trigger("AppPause", null, true);
                 expect(cb).toHaveBeenCalled();
             });
 
-            it("triggers the resume event on appResume", function () {
+            it("triggers the resume event on AppResume", function () {
                 var cb = jasmine.createSpy();
 
                 target.addEventListener("resume", cb);
-                event.trigger("appResume", null, true);
+                event.trigger("AppResume", null, true);
                 expect(cb).toHaveBeenCalled();
             });
 
diff --git a/test/unit/webworks/fsCache.js b/test/unit/webworks/fsCache.js
index 82fb7b4..e68ab96 100644
--- a/test/unit/webworks/fsCache.js
+++ b/test/unit/webworks/fsCache.js
@@ -13,14 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-var fs = require('ripple/dbfs'),
-    event = require('ripple/event'),
-    FileProperties = require('ripple/platform/webworks.core/2.0.0/client/FileProperties'),
-    bbUtils = require('ripple/platform/webworks.core/2.0.0/client/utils'),
-    cache = require('ripple/platform/webworks.core/2.0.0/fsCache');
-
 describe("fsCache", function () {
-    var _root = [{
+    var fs = require('ripple/dbfs'),
+        event = require('ripple/event'),
+        FileProperties = require('ripple/platform/webworks.core/2.0.0/client/FileProperties'),
+        bbUtils = require('ripple/platform/webworks.core/2.0.0/client/utils'),
+        cache = require('ripple/platform/webworks.core/2.0.0/fsCache'),
+        _root = [{
         fullPath: "/dude",
         name: "dude",
         isDirectory: false,
diff --git a/test/unit/webworks/select.js b/test/unit/webworks/select.js
index 37a351b..6ca3190 100644
--- a/test/unit/webworks/select.js
+++ b/test/unit/webworks/select.js
@@ -264,6 +264,13 @@
 
             expect(result.length).toBe(1);
         });
+
+        it("can handle a search where the field doesn't exist", function () {
+            var fe = new FilterExpression("bloodType", "REGEX", "^B*"),
+                result = select.from(orders).where(fe);
+
+            expect(result.length).toBe(0);
+        });
     });
 
     it("returns an empty array if left is a filter expression but right isn't", function () {
diff --git a/test/unit/widgetConfig.js b/test/unit/widgetConfig.js
index 1aee889..d01dbf7 100644
--- a/test/unit/widgetConfig.js
+++ b/test/unit/widgetConfig.js
@@ -28,7 +28,7 @@
 
     describe("phonegap config", function () {
         beforeEach(function () {
-            spyOn(platform, "current").andReturn(require('ripple/platform/phonegap/1.0/spec'));
+            spyOn(platform, "current").andReturn(require('ripple/platform/phonegap/1.0.0/spec'));
         });
 
         it("validateNumberOfArguments_Throws_Exception_If_No_Arguments", function () {