You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by an...@apache.org on 2012/08/24 23:57:39 UTC

[42/50] [abbrv] moved webworks platforms into air and java folders

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/air/plugin/air/FileEntry.js
----------------------------------------------------------------------
diff --git a/lib/webworks/air/plugin/air/FileEntry.js b/lib/webworks/air/plugin/air/FileEntry.js
new file mode 100644
index 0000000..e017ec3
--- /dev/null
+++ b/lib/webworks/air/plugin/air/FileEntry.js
@@ -0,0 +1,59 @@
+var FileEntry = require('cordova/plugin/FileEntry'),
+    Entry = require('cordova/plugin/air/Entry'),
+    FileWriter = require('cordova/plugin/air/FileWriter'),
+    File = require('cordova/plugin/air/File'),
+    FileError = require('cordova/plugin/FileError');
+
+module.exports = {
+    /**
+     * Creates a new FileWriter associated with the file that this FileEntry represents.
+     *
+     * @param {Function} successCallback is called with the new FileWriter
+     * @param {Function} errorCallback is called with a FileError
+     */
+    createWriter : function(successCallback, errorCallback) {
+        this.file(function(filePointer) {
+            var writer = new FileWriter(filePointer);
+
+            if (writer.fileName === null || writer.fileName === "") {
+                if (typeof errorCallback === "function") {
+                    errorCallback(new FileError(FileError.INVALID_STATE_ERR));
+                }
+            } else {
+                if (typeof successCallback === "function") {
+                    successCallback(writer);
+                }
+            }
+        }, errorCallback);
+    },
+
+    /**
+     * Returns a File that represents the current state of the file that this FileEntry represents.
+     *
+     * @param {Function} successCallback is called with the new File object
+     * @param {Function} errorCallback is called with a FileError
+     */
+    file : function(successCallback, errorCallback) {
+        var win = typeof successCallback !== 'function' ? null : function(f) {
+            var file = new File(f.name, f.fullPath, f.type, f.lastModifiedDate, f.size);
+            successCallback(file);
+        };
+        var fail = typeof errorCallback !== 'function' ? null : function(code) {
+            errorCallback(new FileError(code));
+        };
+
+        if(blackberry.io.file.exists(this.fullPath)){
+            var theFileProperties = blackberry.io.file.getFileProperties(this.fullPath);
+            var theFile = {};
+
+            theFile.fullPath = this.fullPath;
+            theFile.type = theFileProperties.fileExtension;
+            theFile.lastModifiedDate = theFileProperties.dateModified;
+            theFile.size = theFileProperties.size;
+            win(theFile);
+        }else{
+            fail(FileError.NOT_FOUND_ERR);
+        }
+    }
+};
+

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/air/plugin/air/FileReader.js
----------------------------------------------------------------------
diff --git a/lib/webworks/air/plugin/air/FileReader.js b/lib/webworks/air/plugin/air/FileReader.js
new file mode 100644
index 0000000..4414a18
--- /dev/null
+++ b/lib/webworks/air/plugin/air/FileReader.js
@@ -0,0 +1,241 @@
+var FileError = require('cordova/plugin/FileError'),
+    ProgressEvent = require('cordova/plugin/ProgressEvent');
+
+/**
+ * This class reads the mobile device file system.
+ *
+ * For Android:
+ *      The root directory is the root of the file system.
+ *      To read from the SD card, the file name is "sdcard/my_file.txt"
+ * @constructor
+ */
+var FileReader = function() {
+    this.fileName = "";
+
+    this.readyState = 0; // FileReader.EMPTY
+
+    // File data
+    this.result = null;
+
+    // Error
+    this.error = null;
+
+    // Event handlers
+    this.onloadstart = null;    // When the read starts.
+    this.onprogress = null;     // While reading (and decoding) file or fileBlob data, and reporting partial file data (progess.loaded/progress.total)
+    this.onload = null;         // When the read has successfully completed.
+    this.onerror = null;        // When the read has failed (see errors).
+    this.onloadend = null;      // When the request has completed (either in success or failure).
+    this.onabort = null;        // When the read has been aborted. For instance, by invoking the abort() method.
+};
+
+// States
+FileReader.EMPTY = 0;
+FileReader.LOADING = 1;
+FileReader.DONE = 2;
+
+/**
+ * Abort reading file.
+ */
+FileReader.prototype.abort = function() {
+    this.result = null;
+
+    if (this.readyState == FileReader.DONE || this.readyState == FileReader.EMPTY) {
+      return;
+    }
+
+    this.readyState = FileReader.DONE;
+
+    // If abort callback
+    if (typeof this.onabort === 'function') {
+        this.onabort(new ProgressEvent('abort', {target:this}));
+    }
+    // If load end callback
+    if (typeof this.onloadend === 'function') {
+        this.onloadend(new ProgressEvent('loadend', {target:this}));
+    }
+};
+
+/**
+ * Read text file.
+ *
+ * @param file          {File} File object containing file properties
+ * @param encoding      [Optional] (see http://www.iana.org/assignments/character-sets)
+ */
+FileReader.prototype.readAsText = function(file, encoding) {
+    // Figure out pathing
+    this.fileName = '';
+    if (typeof file.fullPath === 'undefined') {
+        this.fileName = file;
+    } else {
+        this.fileName = file.fullPath;
+    }
+
+    // Already loading something
+    if (this.readyState == FileReader.LOADING) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    // LOADING state
+    this.readyState = FileReader.LOADING;
+
+    // If loadstart callback
+    if (typeof this.onloadstart === "function") {
+        this.onloadstart(new ProgressEvent("loadstart", {target:this}));
+    }
+
+    // Default encoding is UTF-8
+    var enc = encoding ? encoding : "UTF-8";
+
+    var me = this;
+    // Read file
+    if(blackberry.io.file.exists(this.fileName)){
+        var theText = '';
+        var getFileContents = function(path,blob){
+            if(blob){
+
+                theText = blackberry.utils.blobToString(blob, enc);
+                me.result = theText;
+
+                if (typeof me.onload === "function") {
+                    me.onload(new ProgressEvent("load", {target:me}));
+                }
+
+                me.readyState = FileReader.DONE;
+
+                if (typeof me.onloadend === "function") {
+                    me.onloadend(new ProgressEvent("loadend", {target:me}));
+                }
+            }
+        };
+        // setting asynch to off
+        blackberry.io.file.readFile(this.fileName, getFileContents, false);
+
+    }else{
+        // If DONE (cancelled), then don't do anything
+        if (me.readyState === FileReader.DONE) {
+            return;
+        }
+
+        // DONE state
+        me.readyState = FileReader.DONE;
+
+        me.result = null;
+
+        // Save error
+        me.error = new FileError(FileError.NOT_FOUND_ERR);
+
+        // If onerror callback
+        if (typeof me.onerror === "function") {
+            me.onerror(new ProgressEvent("error", {target:me}));
+        }
+
+        // If onloadend callback
+        if (typeof me.onloadend === "function") {
+            me.onloadend(new ProgressEvent("loadend", {target:me}));
+        }
+    }
+};
+
+
+/**
+ * Read file and return data as a base64 encoded data url.
+ * A data url is of the form:
+ *      data:[<mediatype>][;base64],<data>
+ *
+ * @param file          {File} File object containing file properties
+ */
+FileReader.prototype.readAsDataURL = function(file) {
+    this.fileName = "";
+    if (typeof file.fullPath === "undefined") {
+        this.fileName = file;
+    } else {
+        this.fileName = file.fullPath;
+    }
+
+    // Already loading something
+    if (this.readyState == FileReader.LOADING) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    // LOADING state
+    this.readyState = FileReader.LOADING;
+
+    // If loadstart callback
+    if (typeof this.onloadstart === "function") {
+        this.onloadstart(new ProgressEvent("loadstart", {target:this}));
+    }
+
+    var enc = "BASE64";
+
+    var me = this;
+
+    // Read file
+    if(blackberry.io.file.exists(this.fileName)){
+        var theText = '';
+        var getFileContents = function(path,blob){
+            if(blob){
+                theText = blackberry.utils.blobToString(blob, enc);
+                me.result = "data:text/plain;base64," +theText;
+
+                if (typeof me.onload === "function") {
+                    me.onload(new ProgressEvent("load", {target:me}));
+                }
+
+                me.readyState = FileReader.DONE;
+
+                if (typeof me.onloadend === "function") {
+                    me.onloadend(new ProgressEvent("loadend", {target:me}));
+                }
+            }
+        };
+        // setting asynch to off
+        blackberry.io.file.readFile(this.fileName, getFileContents, false);
+
+    }else{
+        // If DONE (cancelled), then don't do anything
+        if (me.readyState === FileReader.DONE) {
+            return;
+        }
+
+        // DONE state
+        me.readyState = FileReader.DONE;
+
+        me.result = null;
+
+        // Save error
+        me.error = new FileError(FileError.NOT_FOUND_ERR);
+
+        // If onerror callback
+        if (typeof me.onerror === "function") {
+            me.onerror(new ProgressEvent("error", {target:me}));
+        }
+
+        // If onloadend callback
+        if (typeof me.onloadend === "function") {
+            me.onloadend(new ProgressEvent("loadend", {target:me}));
+        }
+    }
+};
+
+/**
+ * Read file and return data as a binary data.
+ *
+ * @param file          {File} File object containing file properties
+ */
+FileReader.prototype.readAsBinaryString = function(file) {
+    // TODO - Can't return binary data to browser.
+    console.log('method "readAsBinaryString" is not supported at this time.');
+};
+
+/**
+ * Read file and return data as a binary data.
+ *
+ * @param file          {File} File object containing file properties
+ */
+FileReader.prototype.readAsArrayBuffer = function(file) {
+    // TODO - Can't return binary data to browser.
+    console.log('This method is not supported at this time.');
+};
+
+module.exports = FileReader;

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/air/plugin/air/FileWriter.js
----------------------------------------------------------------------
diff --git a/lib/webworks/air/plugin/air/FileWriter.js b/lib/webworks/air/plugin/air/FileWriter.js
new file mode 100644
index 0000000..3f13d24
--- /dev/null
+++ b/lib/webworks/air/plugin/air/FileWriter.js
@@ -0,0 +1,248 @@
+var FileError = require('cordova/plugin/FileError'),
+    ProgressEvent = require('cordova/plugin/ProgressEvent');
+
+/**
+ * @constructor
+ * @param file {File} File object containing file properties
+ * @param append if true write to the end of the file, otherwise overwrite the file
+ */
+var FileWriter = function(file) {
+    this.fileName = "";
+    this.length = 0;
+    if (file) {
+        this.fileName = file.fullPath || file;
+        this.length = file.size || 0;
+    }
+    // default is to write at the beginning of the file
+    this.position = 0;
+
+    this.readyState = 0; // EMPTY
+
+    this.result = null;
+
+    // Error
+    this.error = null;
+
+    // Event handlers
+    this.onwritestart = null;   // When writing starts
+    this.onprogress = null;     // While writing the file, and reporting partial file data
+    this.onwrite = null;        // When the write has successfully completed.
+    this.onwriteend = null;     // When the request has completed (either in success or failure).
+    this.onabort = null;        // When the write has been aborted. For instance, by invoking the abort() method.
+    this.onerror = null;        // When the write has failed (see errors).
+};
+
+// States
+FileWriter.INIT = 0;
+FileWriter.WRITING = 1;
+FileWriter.DONE = 2;
+
+/**
+ * Abort writing file.
+ */
+FileWriter.prototype.abort = function() {
+    // check for invalid state
+    if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    // set error
+    this.error = new FileError(FileError.ABORT_ERR);
+
+    this.readyState = FileWriter.DONE;
+
+    // If abort callback
+    if (typeof this.onabort === "function") {
+        this.onabort(new ProgressEvent("abort", {"target":this}));
+    }
+
+    // If write end callback
+    if (typeof this.onwriteend === "function") {
+        this.onwriteend(new ProgressEvent("writeend", {"target":this}));
+    }
+};
+
+/**
+ * Writes data to the file
+ *
+ * @param text to be written
+ */
+FileWriter.prototype.write = function(text) {
+    // Throw an exception if we are already writing a file
+    if (this.readyState === FileWriter.WRITING) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    // WRITING state
+    this.readyState = FileWriter.WRITING;
+
+    var me = this;
+
+    // If onwritestart callback
+    if (typeof me.onwritestart === "function") {
+        me.onwritestart(new ProgressEvent("writestart", {"target":me}));
+    }
+
+    var textBlob = blackberry.utils.stringToBlob(text);
+
+    if(blackberry.io.file.exists(this.fileName)){
+
+        var oldText = '';
+        var newText = text;
+
+        var getFileContents = function(path,blob){
+
+            if(blob){
+                oldText = blackberry.utils.blobToString(blob);
+                if(oldText.length>0){
+                    newText = oldText.substr(0,me.position) + text;
+                }
+            }
+
+            var tempFile = me.fileName+'temp';
+            if(blackberry.io.file.exists(tempFile)){
+                blackberry.io.file.deleteFile(tempFile);
+            }
+
+            var newTextBlob = blackberry.utils.stringToBlob(newText);
+
+            // crete a temp file, delete file we are 'overwriting', then rename temp file
+            blackberry.io.file.saveFile(tempFile, newTextBlob);
+            blackberry.io.file.deleteFile(me.fileName);
+            blackberry.io.file.rename(tempFile, me.fileName.split('/').pop());
+
+            me.position = newText.length;
+            me.length = me.position;
+
+            if (typeof me.onwrite === "function") {
+                me.onwrite(new ProgressEvent("write", {"target":me}));
+            }
+        };
+
+        // setting asynch to off
+        blackberry.io.file.readFile(this.fileName, getFileContents, false);
+
+    }else{
+
+        // file is new so just save it
+        blackberry.io.file.saveFile(this.fileName, textBlob);
+        me.position = text.length;
+        me.length = me.position;
+    }
+
+    me.readyState = FileWriter.DONE;
+
+    if (typeof me.onwriteend === "function") {
+                me.onwriteend(new ProgressEvent("writeend", {"target":me}));
+    }
+};
+
+/**
+ * Moves the file pointer to the location specified.
+ *
+ * If the offset is a negative number the position of the file
+ * pointer is rewound.  If the offset is greater than the file
+ * size the position is set to the end of the file.
+ *
+ * @param offset is the location to move the file pointer to.
+ */
+FileWriter.prototype.seek = function(offset) {
+    // Throw an exception if we are already writing a file
+    if (this.readyState === FileWriter.WRITING) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    if (!offset && offset !== 0) {
+        return;
+    }
+
+    // See back from end of file.
+    if (offset < 0) {
+        this.position = Math.max(offset + this.length, 0);
+    }
+    // Offset is bigger then file size so set position
+    // to the end of the file.
+    else if (offset > this.length) {
+        this.position = this.length;
+    }
+    // Offset is between 0 and file size so set the position
+    // to start writing.
+    else {
+        this.position = offset;
+    }
+};
+
+/**
+ * Truncates the file to the size specified.
+ *
+ * @param size to chop the file at.
+ */
+FileWriter.prototype.truncate = function(size) {
+    // Throw an exception if we are already writing a file
+    if (this.readyState === FileWriter.WRITING) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    // WRITING state
+    this.readyState = FileWriter.WRITING;
+
+    var me = this;
+
+    // If onwritestart callback
+    if (typeof me.onwritestart === "function") {
+        me.onwritestart(new ProgressEvent("writestart", {"target":this}));
+    }
+
+    if(blackberry.io.file.exists(this.fileName)){
+
+        var oldText = '';
+        var newText = '';
+
+        var getFileContents = function(path,blob){
+
+            if(blob){
+                oldText = blackberry.utils.blobToString(blob);
+                if(oldText.length>0){
+                    newText = oldText.slice(0,size);
+                }else{
+                    // TODO: throw error
+                }
+            }
+
+            var tempFile = me.fileName+'temp';
+            if(blackberry.io.file.exists(tempFile)){
+                blackberry.io.file.deleteFile(tempFile);
+            }
+
+            var newTextBlob = blackberry.utils.stringToBlob(newText);
+
+            // crete a temp file, delete file we are 'overwriting', then rename temp file
+            blackberry.io.file.saveFile(tempFile, newTextBlob);
+            blackberry.io.file.deleteFile(me.fileName);
+            blackberry.io.file.rename(tempFile, me.fileName.split('/').pop());
+
+            me.position = newText.length;
+            me.length = me.position;
+
+            if (typeof me.onwrite === "function") {
+                 me.onwrite(new ProgressEvent("write", {"target":me}));
+            }
+        };
+
+        // setting asynch to off - worry about making this all callbacks later
+        blackberry.io.file.readFile(this.fileName, getFileContents, false);
+
+    }else{
+
+        // TODO: file doesn't exist - throw error
+
+    }
+
+    me.readyState = FileWriter.DONE;
+
+    if (typeof me.onwriteend === "function") {
+                me.onwriteend(new ProgressEvent("writeend", {"target":me}));
+    }
+};
+
+module.exports = FileWriter;

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/air/plugin/air/accelerometer.js
----------------------------------------------------------------------
diff --git a/lib/webworks/air/plugin/air/accelerometer.js b/lib/webworks/air/plugin/air/accelerometer.js
new file mode 100644
index 0000000..68f1183
--- /dev/null
+++ b/lib/webworks/air/plugin/air/accelerometer.js
@@ -0,0 +1,22 @@
+var cordova = require('cordova'),
+    callback;
+
+module.exports = {
+    start: function (args, win, fail) {
+        window.removeEventListener("devicemotion", callback);
+        callback = function (motion) {
+            win({
+                x: motion.accelerationIncludingGravity.x,
+                y: motion.accelerationIncludingGravity.y,
+                z: motion.accelerationIncludingGravity.z,
+                timestamp: motion.timestamp
+            });
+        };
+        window.addEventListener("devicemotion", callback);
+        return { "status" : cordova.callbackStatus.NO_RESULT, "message" : "WebWorks Is On It" };
+    },
+    stop: function (args, win, fail) {
+        window.removeEventListener("devicemotion", callback);
+        return { "status" : cordova.callbackStatus.NO_RESULT, "message" : "WebWorks Is On It" };
+    }
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/air/plugin/air/battery.js
----------------------------------------------------------------------
diff --git a/lib/webworks/air/plugin/air/battery.js b/lib/webworks/air/plugin/air/battery.js
new file mode 100644
index 0000000..14eb7ce
--- /dev/null
+++ b/lib/webworks/air/plugin/air/battery.js
@@ -0,0 +1,36 @@
+var cordova = require('cordova');
+
+module.exports = {
+    start: function (args, win, fail) {
+        // Register one listener to each of level and state change
+        // events using WebWorks API.
+        blackberry.system.event.deviceBatteryStateChange(function(state) {
+            var me = navigator.battery;
+            // state is either CHARGING or UNPLUGGED
+            if (state === 2 || state === 3) {
+                var info = {
+                    "level" : me._level,
+                    "isPlugged" : state === 2
+                };
+
+                if (me._isPlugged !== info.isPlugged && typeof win === 'function') {
+                    win(info);
+                }
+            }
+        });
+        blackberry.system.event.deviceBatteryLevelChange(function(level) {
+            var me = navigator.battery;
+            if (level != me._level && typeof win === 'function') {
+                win({'level' : level, 'isPlugged' : me._isPlugged});
+            }
+        });
+
+        return { "status" : cordova.callbackStatus.NO_RESULT, "message" : "WebWorks Is On It" };
+    },
+    stop: function (args, win, fail) {
+        // Unregister battery listeners.
+        blackberry.system.event.deviceBatteryStateChange(null);
+        blackberry.system.event.deviceBatteryLevelChange(null);
+        return { "status" : cordova.callbackStatus.NO_RESULT, "message" : "WebWorks Is On It" };
+    }
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/air/plugin/air/camera.js
----------------------------------------------------------------------
diff --git a/lib/webworks/air/plugin/air/camera.js b/lib/webworks/air/plugin/air/camera.js
new file mode 100644
index 0000000..b8f33b9
--- /dev/null
+++ b/lib/webworks/air/plugin/air/camera.js
@@ -0,0 +1,19 @@
+var cordova = require('cordova');
+
+module.exports = {
+    takePicture: function (args, win, fail) {
+        var onCaptured = blackberry.events.registerEventHandler("onCaptured", win),
+            onCameraClosed = blackberry.events.registerEventHandler("onCameraClosed", function () {}),
+            onError = blackberry.events.registerEventHandler("onError", fail),
+            request = new blackberry.transport.RemoteFunctionCall('blackberry/media/camera/takePicture');
+
+        request.addParam("onCaptured", onCaptured);
+        request.addParam("onCameraClosed", onCameraClosed);
+        request.addParam("onError", onError);
+
+        //HACK: this is a sync call due to:
+        //https://github.com/blackberry/WebWorks-TabletOS/issues/51
+        request.makeSyncCall();
+        return { "status" : cordova.callbackStatus.NO_RESULT, "message" : "WebWorks Is On It" };
+    }
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/air/plugin/air/capture.js
----------------------------------------------------------------------
diff --git a/lib/webworks/air/plugin/air/capture.js b/lib/webworks/air/plugin/air/capture.js
new file mode 100644
index 0000000..3f6185e
--- /dev/null
+++ b/lib/webworks/air/plugin/air/capture.js
@@ -0,0 +1,93 @@
+var cordova = require('cordova');
+
+function capture(action, win, fail) {
+    var onCaptured = blackberry.events.registerEventHandler("onCaptured", function (path) {
+            var file = blackberry.io.file.getFileProperties(path);
+            win([{
+                fullPath: path,
+                lastModifiedDate: file.dateModified,
+                name: path.replace(file.directory + "/", ""),
+                size: file.size,
+                type: file.fileExtension
+            }]);
+        }),
+        onCameraClosed = blackberry.events.registerEventHandler("onCameraClosed", function () {}),
+        onError = blackberry.events.registerEventHandler("onError", fail),
+        request = new blackberry.transport.RemoteFunctionCall('blackberry/media/camera/' + action);
+
+    request.addParam("onCaptured", onCaptured);
+    request.addParam("onCameraClosed", onCameraClosed);
+    request.addParam("onError", onError);
+
+    //HACK: this is a sync call due to:
+    //https://github.com/blackberry/WebWorks-TabletOS/issues/51
+    request.makeSyncCall();
+}
+
+module.exports = {
+    getSupportedAudioModes: function (args, win, fail) {
+        return {"status": cordova.callbackStatus.OK, "message": []};
+    },
+    getSupportedImageModes: function (args, win, fail) {
+        return {"status": cordova.callbackStatus.OK, "message": []};
+    },
+    getSupportedVideoModes: function (args, win, fail) {
+        return {"status": cordova.callbackStatus.OK, "message": []};
+    },
+    captureImage: function (args, win, fail) {
+        if (args[0].limit > 0) {
+            capture("takePicture", win, fail);
+        }
+        else {
+            win([]);
+        }
+
+        return { "status" : cordova.callbackStatus.NO_RESULT, "message" : "WebWorks Is On It" };
+    },
+    captureVideo: function (args, win, fail) {
+        if (args[0].limit > 0) {
+            capture("takeVideo", win, fail);
+        }
+        else {
+            win([]);
+        }
+
+        return { "status" : cordova.callbackStatus.NO_RESULT, "message" : "WebWorks Is On It" };
+    },
+    captureAudio: function (args, win, fail) {
+        var onCaptureAudioWin = function(filePath){
+        // for some reason the filePath is coming back as a string between two double quotes
+        filePath = filePath.slice(1, filePath.length-1);
+            var file = blackberry.io.file.getFileProperties(filePath);
+
+            win([{
+                fullPath: filePath,
+                lastModifiedDate: file.dateModified,
+                name: filePath.replace(file.directory + "/", ""),
+                size: file.size,
+                type: file.fileExtension
+            }]);
+        };
+
+        var onCaptureAudioFail = function(){
+            fail([]);
+        };
+
+        if (args[0].limit > 0 && args[0].duration){
+            // a sloppy way of creating a uuid since there's no built in date function to get milliseconds since epoch
+            // might be better to instead check files within directory and then figure out the next file name shoud be
+            // ie, img000 -> img001 though that would take awhile and would add a whole bunch of checks
+            var id = new Date();
+            id = (id.getDay()).toString() + (id.getHours()).toString() + (id.getSeconds()).toString() + (id.getMilliseconds()).toString() + (id.getYear()).toString();
+
+            var fileName = blackberry.io.dir.appDirs.shared.music.path+'/audio'+id+'.wav';
+            blackberry.media.microphone.record(fileName, onCaptureAudioWin, onCaptureAudioFail);
+            // multiple duration by a 1000 since it comes in as seconds
+            setTimeout(blackberry.media.microphone.stop,args[0].duration*1000);
+        }
+        else {
+            win([]);
+        }
+        return {"status": cordova.callbackStatus.NO_RESULT, "message": "WebWorks Is On It"};
+    }
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/air/plugin/air/device.js
----------------------------------------------------------------------
diff --git a/lib/webworks/air/plugin/air/device.js b/lib/webworks/air/plugin/air/device.js
new file mode 100644
index 0000000..a42191e
--- /dev/null
+++ b/lib/webworks/air/plugin/air/device.js
@@ -0,0 +1,20 @@
+var channel = require('cordova/channel'),
+    cordova = require('cordova');
+
+// Tell cordova channel to wait on the CordovaInfoReady event
+channel.waitForInitialization('onCordovaInfoReady');
+
+module.exports = {
+    getDeviceInfo : function(args, win, fail){
+        win({
+            platform: "PlayBook",
+            version: blackberry.system.softwareVersion,
+            name: blackberry.system.model,
+            uuid: blackberry.identity.PIN,
+            cordova: "2.0.0"
+        });
+
+        return { "status" : cordova.callbackStatus.NO_RESULT, "message" : "Device info returned" };
+    }
+
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/air/plugin/air/logger.js
----------------------------------------------------------------------
diff --git a/lib/webworks/air/plugin/air/logger.js b/lib/webworks/air/plugin/air/logger.js
new file mode 100644
index 0000000..8b8310b
--- /dev/null
+++ b/lib/webworks/air/plugin/air/logger.js
@@ -0,0 +1,9 @@
+var cordova = require('cordova');
+
+module.exports = {
+    log: function (args, win, fail) {
+        console.log(args);
+        return {"status" : cordova.callbackStatus.OK,
+                "message" : 'Message logged to console: ' + args};
+    }
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/air/plugin/air/media.js
----------------------------------------------------------------------
diff --git a/lib/webworks/air/plugin/air/media.js b/lib/webworks/air/plugin/air/media.js
new file mode 100644
index 0000000..1609238
--- /dev/null
+++ b/lib/webworks/air/plugin/air/media.js
@@ -0,0 +1,167 @@
+var cordova = require('cordova'),
+    audioObjects = {};
+
+module.exports = {
+    create: function (args, win, fail) {
+        if (!args.length) {
+            return {"status" : 9, "message" : "Media Object id was not sent in arguments"};
+        }
+
+        var id = args[0],
+            src = args[1];
+
+        audioObjects[id] = new Audio(src);
+        return {"status" : 1, "message" : "Audio object created" };
+    },
+    startPlayingAudio: function (args, win, fail) {
+        if (!args.length) {
+            return {"status" : 9, "message" : "Media Object id was not sent in arguments"};
+        }
+
+        var id = args[0],
+            audio = audioObjects[id],
+            result;
+
+        if (args.length === 1) {
+            return {"status" : 9, "message" : "Media source argument not found"};
+        }
+
+        if (audio) {
+            audio.pause();
+            audioObjects[id] = undefined;
+        }
+
+        audio = audioObjects[id] = new Audio(args[1]);
+        audio.play();
+
+        return {"status" : 1, "message" : "Audio play started" };
+    },
+    stopPlayingAudio: function (args, win, fail) {
+        if (!args.length) {
+            return {"status" : 9, "message" : "Media Object id was not sent in arguments"};
+        }
+
+        var id = args[0],
+            audio = audioObjects[id],
+            result;
+
+        if (!audio) {
+            return {"status" : 2, "message" : "Audio Object has not been initialized"};
+        }
+
+        audio.pause();
+        audioObjects[id] = undefined;
+
+        return {"status" : 1, "message" : "Audio play stopped" };
+    },
+    seekToAudio: function (args, win, fail) {
+        if (!args.length) {
+            return {"status" : 9, "message" : "Media Object id was not sent in arguments"};
+        }
+
+        var id = args[0],
+            audio = audioObjects[id],
+            result;
+
+        if (!audio) {
+            result = {"status" : 2, "message" : "Audio Object has not been initialized"};
+        } else if (args.length === 1) {
+            result = {"status" : 9, "message" : "Media seek time argument not found"};
+        } else {
+            try {
+                audio.currentTime = args[1];
+            } catch (e) {
+                console.log('Error seeking audio: ' + e);
+                return {"status" : 3, "message" : "Error seeking audio: " + e};
+            }
+
+            result = {"status" : 1, "message" : "Seek to audio succeeded" };
+        }
+
+        return result;
+    },
+    pausePlayingAudio: function (args, win, fail) {
+        if (!args.length) {
+            return {"status" : 9, "message" : "Media Object id was not sent in arguments"};
+        }
+
+        var id = args[0],
+            audio = audioObjects[id],
+            result;
+
+        if (!audio) {
+            return {"status" : 2, "message" : "Audio Object has not been initialized"};
+        }
+
+        audio.pause();
+
+        return {"status" : 1, "message" : "Audio paused" };
+    },
+    getCurrentPositionAudio: function (args, win, fail) {
+        if (!args.length) {
+            return {"status" : 9, "message" : "Media Object id was not sent in arguments"};
+        }
+
+        var id = args[0],
+            audio = audioObjects[id],
+            result;
+
+        if (!audio) {
+            return {"status" : 2, "message" : "Audio Object has not been initialized"};
+        }
+
+        return {"status" : 1, "message" : audio.currentTime };
+    },
+    getDuration: function (args, win, fail) {
+        if (!args.length) {
+            return {"status" : 9, "message" : "Media Object id was not sent in arguments"};
+        }
+
+        var id = args[0],
+            audio = audioObjects[id],
+            result;
+
+        if (!audio) {
+            return {"status" : 2, "message" : "Audio Object has not been initialized"};
+        }
+
+        return {"status" : 1, "message" : audio.duration };
+    },
+    startRecordingAudio: function (args, win, fail) {
+        if (!args.length) {
+            return {"status" : 9, "message" : "Media Object id was not sent in arguments"};
+        }
+
+        var id = args[0],
+            audio = audioObjects[id],
+            result;
+
+        if (args.length <= 1) {
+            result = {"status" : 9, "message" : "Media start recording, insufficient arguments"};
+        }
+
+        blackberry.media.microphone.record(args[1], win, fail);
+        return { "status" : cordova.callbackStatus.NO_RESULT, "message" : "WebWorks Is On It" };
+    },
+    stopRecordingAudio: function (args, win, fail) {
+    },
+    release: function (args, win, fail) {
+        if (!args.length) {
+            return {"status" : 9, "message" : "Media Object id was not sent in arguments"};
+        }
+
+        var id = args[0],
+            audio = audioObjects[id],
+            result;
+
+        if (audio) {
+            audioObjects[id] = undefined;
+            audio.src = undefined;
+            //delete audio;
+        }
+
+        result = {"status" : 1, "message" : "Media resources released"};
+
+        return result;
+    }
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/air/plugin/air/network.js
----------------------------------------------------------------------
diff --git a/lib/webworks/air/plugin/air/network.js b/lib/webworks/air/plugin/air/network.js
new file mode 100644
index 0000000..d8e0e91
--- /dev/null
+++ b/lib/webworks/air/plugin/air/network.js
@@ -0,0 +1,33 @@
+var cordova = require('cordova'),
+    connection = require('cordova/plugin/Connection');
+
+module.exports = {
+    getConnectionInfo: function (args, win, fail) {
+        var connectionType = connection.NONE,
+            eventType = "offline",
+            callbackID,
+            request;
+
+        /**
+         * For PlayBooks, we currently only have WiFi connections, so
+         * return WiFi if there is any access at all.
+         * TODO: update if/when PlayBook gets other connection types...
+         */
+        if (blackberry.system.hasDataCoverage()) {
+            connectionType = connection.WIFI;
+            eventType = "online";
+        }
+
+        //Register an event handler for the networkChange event
+        callbackID = blackberry.events.registerEventHandler("networkChange", function (status) {
+            win(status.type);
+        });
+
+        //pass our callback id down to our network extension
+        request = new blackberry.transport.RemoteFunctionCall("org/apache/cordova/getConnectionInfo");
+        request.addParam("networkStatusChangedID", callbackID);
+        request.makeSyncCall();
+
+        return { "status": cordova.callbackStatus.OK, "message": connectionType};
+    }
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/air/plugin/air/notification.js
----------------------------------------------------------------------
diff --git a/lib/webworks/air/plugin/air/notification.js b/lib/webworks/air/plugin/air/notification.js
new file mode 100644
index 0000000..fb95527
--- /dev/null
+++ b/lib/webworks/air/plugin/air/notification.js
@@ -0,0 +1,31 @@
+var cordova = require('cordova');
+
+module.exports = {
+    alert: function (args, win, fail) {
+        if (args.length !== 3) {
+            return {"status" : 9, "message" : "Notification action - alert arguments not found"};
+        }
+
+        //Unpack and map the args
+        var msg = args[0],
+            title = args[1],
+            btnLabel = args[2];
+
+        blackberry.ui.dialog.customAskAsync.apply(this, [ msg, [ btnLabel ], win, { "title" : title } ]);
+        return { "status" : cordova.callbackStatus.NO_RESULT, "message" : "WebWorks Is On It" };
+    },
+    confirm: function (args, win, fail) {
+        if (args.length !== 3) {
+            return {"status" : 9, "message" : "Notification action - confirm arguments not found"};
+        }
+
+        //Unpack and map the args
+        var msg = args[0],
+            title = args[1],
+            btnLabel = args[2],
+            btnLabels = btnLabel.split(",");
+
+        blackberry.ui.dialog.customAskAsync.apply(this, [msg, btnLabels, win, {"title" : title} ]);
+        return { "status" : cordova.callbackStatus.NO_RESULT, "message" : "WebWorks Is On It" };
+    }
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/air/plugin/air/requestFileSystem.js
----------------------------------------------------------------------
diff --git a/lib/webworks/air/plugin/air/requestFileSystem.js b/lib/webworks/air/plugin/air/requestFileSystem.js
new file mode 100644
index 0000000..1b4bf95
--- /dev/null
+++ b/lib/webworks/air/plugin/air/requestFileSystem.js
@@ -0,0 +1,59 @@
+var DirectoryEntry = require('cordova/plugin/DirectoryEntry'),
+FileError = require('cordova/plugin/FileError'),
+FileSystem = require('cordova/plugin/FileSystem'),
+LocalFileSystem = require('cordova/plugin/LocalFileSystem');
+
+/**
+ * Request a file system in which to store application data.
+ * @param type  local file system type
+ * @param size  indicates how much storage space, in bytes, the application expects to need
+ * @param successCallback  invoked with a FileSystem object
+ * @param errorCallback  invoked if error occurs retrieving file system
+ */
+var requestFileSystem = function(type, size, successCallback, errorCallback) {
+    var fail = function(code) {
+        if (typeof errorCallback === 'function') {
+            errorCallback(new FileError(code));
+        }
+    };
+
+    if (type < 0 || type > 3) {
+        fail(FileError.SYNTAX_ERR);
+    } else {
+        // if successful, return a FileSystem object
+        var success = function(file_system) {
+            if (file_system) {
+                if (typeof successCallback === 'function') {
+                    successCallback(file_system);
+                }
+            }
+            else {
+                // no FileSystem object returned
+                fail(FileError.NOT_FOUND_ERR);
+            }
+        };
+
+        // guessing the max file size is 2GB - 1 bytes?
+        // https://bdsc.webapps.blackberry.com/native/documentation/com.qnx.doc.neutrino.user_guide/topic/limits_filesystems.html
+
+        if(size>=2147483648){
+            fail(FileError.QUOTA_EXCEEDED_ERR);
+            return;
+        }
+
+
+        var theFileSystem;
+        try{
+            // is there a way to get space for the app that doesn't point to the appDirs folder?
+            if(type==LocalFileSystem.TEMPORARY){
+                theFileSystem = new FileSystem('temporary', new DirectoryEntry('root', blackberry.io.dir.appDirs.app.storage.path));
+            }else if(type==LocalFileSystem.PERSISTENT){
+                theFileSystem = new FileSystem('persistent', new DirectoryEntry('root', blackberry.io.dir.appDirs.app.storage.path));
+            }
+            success(theFileSystem);
+        }catch(e){
+            fail(FileError.SYNTAX_ERR);
+        }
+    }
+};
+module.exports = requestFileSystem;

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/air/plugin/air/resolveLocalFileSystemURI.js
----------------------------------------------------------------------
diff --git a/lib/webworks/air/plugin/air/resolveLocalFileSystemURI.js b/lib/webworks/air/plugin/air/resolveLocalFileSystemURI.js
new file mode 100644
index 0000000..cbca1ac
--- /dev/null
+++ b/lib/webworks/air/plugin/air/resolveLocalFileSystemURI.js
@@ -0,0 +1,81 @@
+var DirectoryEntry = require('cordova/plugin/DirectoryEntry'),
+    FileEntry = require('cordova/plugin/FileEntry'),
+    FileError = require('cordova/plugin/FileError');
+
+/**
+ * Look up file system Entry referred to by local URI.
+ * @param {DOMString} uri  URI referring to a local file or directory
+ * @param successCallback  invoked with Entry object corresponding to URI
+ * @param errorCallback    invoked if error occurs retrieving file system entry
+ */
+module.exports = function(uri, successCallback, errorCallback) {
+    // error callback
+    var fail = function(error) {
+        if (typeof errorCallback === 'function') {
+            errorCallback(new FileError(error));
+        }
+    };
+    // if successful, return either a file or directory entry
+    var success = function(entry) {
+        var result;
+
+        if (entry) {
+            if (typeof successCallback === 'function') {
+                // create appropriate Entry object
+                result = (entry.isDirectory) ? new DirectoryEntry(entry.name, entry.fullPath) : new FileEntry(entry.name, entry.fullPath);
+                try {
+                    successCallback(result);
+                }
+                catch (e) {
+                    console.log('Error invoking callback: ' + e);
+                }
+            }
+        }
+        else {
+            // no Entry object returned
+            fail(FileError.NOT_FOUND_ERR);
+            return;
+        }
+    };
+
+    if(!uri || uri === ""){
+        fail(FileError.NOT_FOUND_ERR);
+        return;
+    }
+
+    // decode uri if % char found
+    if(uri.indexOf('%')>=0){
+        uri = decodeURI(uri);
+    }
+
+    // pop the parameters if any
+    if(uri.indexOf('?')>=0){
+        uri = uri.split('?')[0];
+    }
+
+    // check for leading /
+    if(uri.indexOf('/')===0){
+        fail(FileError.ENCODING_ERR);
+        return;
+    }
+
+    // Entry object is borked - unable to instantiate a new Entry object so just create one
+    var theEntry = {};
+    if(blackberry.io.dir.exists(uri)){
+        theEntry.isDirectory = true;
+        theEntry.name = uri.split('/').pop();
+        theEntry.fullPath = uri;
+
+        success(theEntry);
+    }else if(blackberry.io.file.exists(uri)){
+        theEntry.isDirectory = false;
+        theEntry.name = uri.split('/').pop();
+        theEntry.fullPath = uri;
+        success(theEntry);
+        return;
+    }else{
+        fail(FileError.NOT_FOUND_ERR);
+        return;
+    }
+
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/air/plugin/manager.js
----------------------------------------------------------------------
diff --git a/lib/webworks/air/plugin/manager.js b/lib/webworks/air/plugin/manager.js
new file mode 100644
index 0000000..e2873a8
--- /dev/null
+++ b/lib/webworks/air/plugin/manager.js
@@ -0,0 +1,32 @@
+var cordova = require('cordova'),
+    plugins = {
+        'Device' : require('cordova/plugin/air/device'),
+        'Battery' : require('cordova/plugin/air/battery'),
+        'Camera' : require('cordova/plugin/air/camera'),
+        'Logger' : require('cordova/plugin/air/logger'),
+        'Media' : require('cordova/plugin/air/media'),
+        'Capture' : require('cordova/plugin/air/capture'),
+        'Accelerometer' : require('cordova/plugin/air/accelerometer'),
+        'NetworkStatus' : require('cordova/plugin/air/network'),
+        'Notification' : require('cordova/plugin/air/notification')
+    };
+
+module.exports = {
+    exec: function (win, fail, clazz, action, args) {
+        var result = {"status" : cordova.callbackStatus.CLASS_NOT_FOUND_EXCEPTION, "message" : "Class " + clazz + " cannot be found"};
+
+        if (plugins[clazz]) {
+            if (plugins[clazz][action]) {
+                result = plugins[clazz][action](args, win, fail);
+            }
+            else {
+                result = { "status" : cordova.callbackStatus.INVALID_ACTION, "message" : "Action not found: " + action };
+            }
+        }
+
+        return result;
+    },
+    resume: function () {},
+    pause: function () {},
+    destroy: function () {}
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/java/platform.js
----------------------------------------------------------------------
diff --git a/lib/webworks/java/platform.js b/lib/webworks/java/platform.js
new file mode 100644
index 0000000..ebe7fcd
--- /dev/null
+++ b/lib/webworks/java/platform.js
@@ -0,0 +1,176 @@
+module.exports = {
+    id: "blackberry",
+    initialize:function() {
+        var cordova = require('cordova'),
+            exec = require('cordova/exec'),
+            channel = require('cordova/channel'),
+            manager = require('cordova/plugin/manager'),
+            app = require('cordova/plugin/java/app');
+
+        // BB OS 5 does not define window.console.
+        if (typeof window.console === 'undefined') {
+            window.console = {};
+        }
+
+        // Override console.log with native logging ability.
+        // BB OS 7 devices define console.log for use with web inspector
+        // debugging. If console.log is already defined, invoke it in addition
+        // to native logging.
+        var origLog = window.console.log;
+        window.console.log = function(msg) {
+            if (typeof origLog === 'function') {
+                origLog.call(window.console, msg);
+            }
+            org.apache.cordova.Logger.log(''+msg);
+        };
+
+        // Mapping of button events to BlackBerry key identifier.
+        var buttonMapping = {
+            'backbutton'         : blackberry.system.event.KEY_BACK,
+            'conveniencebutton1' : blackberry.system.event.KEY_CONVENIENCE_1,
+            'conveniencebutton2' : blackberry.system.event.KEY_CONVENIENCE_2,
+            'endcallbutton'      : blackberry.system.event.KEY_ENDCALL,
+            'menubutton'         : blackberry.system.event.KEY_MENU,
+            'startcallbutton'    : blackberry.system.event.KEY_STARTCALL,
+            'volumedownbutton'   : blackberry.system.event.KEY_VOLUMEDOWN,
+            'volumeupbutton'     : blackberry.system.event.KEY_VOLUMEUP
+        };
+
+        // Generates a function which fires the specified event.
+        var fireEvent = function(event) {
+            return function() {
+                cordova.fireDocumentEvent(event, null);
+            };
+        };
+
+        var eventHandler = function(event) {
+            return { onSubscribe : function() {
+                // If we just attached the first handler, let native know we
+                // need to override the back button.
+                if (this.numHandlers === 1) {
+                    blackberry.system.event.onHardwareKey(
+                            buttonMapping[event], fireEvent(event));
+                }
+            },
+            onUnsubscribe : function() {
+                // If we just detached the last handler, let native know we
+                // no longer override the back button.
+                if (this.numHandlers === 0) {
+                    blackberry.system.event.onHardwareKey(
+                            buttonMapping[event], null);
+                }
+            }};
+        };
+
+        // Inject listeners for buttons on the document.
+        for (var button in buttonMapping) {
+            if (buttonMapping.hasOwnProperty(button)) {
+                cordova.addDocumentEventHandler(button, eventHandler(button));
+            }
+        }
+
+        // Fires off necessary code to pause/resume app
+        var resume = function() {
+            cordova.fireDocumentEvent('resume');
+            manager.resume();
+        };
+        var pause = function() {
+            cordova.fireDocumentEvent('pause');
+            manager.pause();
+        };
+
+        /************************************************
+         * Patch up the generic pause/resume listeners. *
+         ************************************************/
+
+        // Unsubscribe handler - turns off native backlight change
+        // listener
+        var onUnsubscribe = function() {
+            if (channel.onResume.numHandlers === 0 && channel.onPause.numHandlers === 0) {
+                exec(null, null, 'App', 'ignoreBacklight', []);
+            }
+        };
+
+        // Native backlight detection win/fail callbacks
+        var backlightWin = function(isOn) {
+            if (isOn === true) {
+                resume();
+            } else {
+                pause();
+            }
+        };
+        var backlightFail = function(e) {
+            console.log("Error detecting backlight on/off.");
+        };
+
+        // Override stock resume and pause listeners so we can trigger
+        // some native methods during attach/remove
+        channel.onResume = cordova.addDocumentEventHandler('resume', {
+            onSubscribe:function() {
+                // If we just attached the first handler and there are
+                // no pause handlers, start the backlight system
+                // listener on the native side.
+                if (channel.onResume.numHandlers === 1 && channel.onPause.numHandlers === 0) {
+                    exec(backlightWin, backlightFail, "App", "detectBacklight", []);
+                }
+            },
+            onUnsubscribe:onUnsubscribe
+        });
+        channel.onPause = cordova.addDocumentEventHandler('pause', {
+            onSubscribe:function() {
+                // If we just attached the first handler and there are
+                // no resume handlers, start the backlight system
+                // listener on the native side.
+                if (channel.onResume.numHandlers === 0 && channel.onPause.numHandlers === 1) {
+                    exec(backlightWin, backlightFail, "App", "detectBacklight", []);
+                }
+            },
+            onUnsubscribe:onUnsubscribe
+        });
+
+        // Fire resume event when application brought to foreground.
+        blackberry.app.event.onForeground(resume);
+
+        // Fire pause event when application sent to background.
+        blackberry.app.event.onBackground(pause);
+
+        // Trap BlackBerry WebWorks exit. Allow plugins to clean up before exiting.
+        blackberry.app.event.onExit(app.exitApp);
+    },
+    objects: {
+        navigator: {
+            children: {
+                app: {
+                    path: "cordova/plugin/java/app"
+                }
+            }
+        },
+        File: { // exists natively on BlackBerry OS 7, override
+            path: "cordova/plugin/File"
+        }
+    },
+    merges: {
+        navigator: {
+            children: {
+                contacts: {
+                    path: 'cordova/plugin/java/contacts'
+                },
+                notification: {
+                    path: 'cordova/plugin/java/notification'
+                }
+            }
+        },
+        Contact: {
+            path: 'cordova/plugin/java/Contact'
+        },
+        DirectoryEntry: {
+            path: 'cordova/plugin/java/DirectoryEntry'
+        },
+        Entry: {
+            path: 'cordova/plugin/java/Entry'
+        },
+        MediaError: { // Exists natively on BB OS 6+, merge in Cordova specifics
+            path: 'cordova/plugin/java/MediaError'
+        }
+    }
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/java/plugin/java/Contact.js
----------------------------------------------------------------------
diff --git a/lib/webworks/java/plugin/java/Contact.js b/lib/webworks/java/plugin/java/Contact.js
new file mode 100644
index 0000000..a11df6f
--- /dev/null
+++ b/lib/webworks/java/plugin/java/Contact.js
@@ -0,0 +1,385 @@
+var ContactError = require('cordova/plugin/ContactError'),
+    ContactUtils = require('cordova/plugin/java/ContactUtils'),
+    utils = require('cordova/utils'),
+    ContactAddress = require('cordova/plugin/ContactAddress'),
+    exec = require('cordova/exec');
+
+// ------------------
+// Utility functions
+// ------------------
+
+/**
+ * Retrieves a BlackBerry contact from the device by unique id.
+ *
+ * @param uid
+ *            Unique id of the contact on the device
+ * @return {blackberry.pim.Contact} BlackBerry contact or null if contact with
+ *         specified id is not found
+ */
+var findByUniqueId = function(uid) {
+    if (!uid) {
+        return null;
+    }
+    var bbContacts = blackberry.pim.Contact.find(new blackberry.find.FilterExpression("uid", "==", uid));
+    return bbContacts[0] || null;
+};
+
+/**
+ * Creates a BlackBerry contact object from the W3C Contact object and persists
+ * it to device storage.
+ *
+ * @param {Contact}
+ *            contact The contact to save
+ * @return a new contact object with all properties set
+ */
+var saveToDevice = function(contact) {
+
+    if (!contact) {
+        return;
+    }
+
+    var bbContact = null;
+    var update = false;
+
+    // if the underlying BlackBerry contact already exists, retrieve it for
+    // update
+    if (contact.id) {
+        // we must attempt to retrieve the BlackBerry contact from the device
+        // because this may be an update operation
+        bbContact = findByUniqueId(contact.id);
+    }
+
+    // contact not found on device, create a new one
+    if (!bbContact) {
+        bbContact = new blackberry.pim.Contact();
+    }
+    // update the existing contact
+    else {
+        update = true;
+    }
+
+    // NOTE: The user may be working with a partial Contact object, because only
+    // user-specified Contact fields are returned from a find operation (blame
+    // the W3C spec). If this is an update to an existing Contact, we don't
+    // want to clear an attribute from the contact database simply because the
+    // Contact object that the user passed in contains a null value for that
+    // attribute. So we only copy the non-null Contact attributes to the
+    // BlackBerry contact object before saving.
+    //
+    // This means that a user must explicitly set a Contact attribute to a
+    // non-null value in order to update it in the contact database.
+    //
+    // name
+    if (contact.name !== null) {
+        if (contact.name.givenName) {
+            bbContact.firstName = contact.name.givenName;
+        }
+        if (contact.name.familyName) {
+            bbContact.lastName = contact.name.familyName;
+        }
+        if (contact.name.honorificPrefix) {
+            bbContact.title = contact.name.honorificPrefix;
+        }
+    }
+
+    // display name
+    if (contact.displayName !== null) {
+        bbContact.user1 = contact.displayName;
+    }
+
+    // note
+    if (contact.note !== null) {
+        bbContact.note = contact.note;
+    }
+
+    // birthday
+    //
+    // user may pass in Date object or a string representation of a date
+    // if it is a string, we don't know the date format, so try to create a
+    // new Date with what we're given
+    //
+    // NOTE: BlackBerry's Date.parse() does not work well, so use new Date()
+    //
+    if (contact.birthday !== null) {
+        if (utils.isDate(contact.birthday)) {
+            bbContact.birthday = contact.birthday;
+        } else {
+            var bday = contact.birthday.toString();
+            bbContact.birthday = (bday.length > 0) ? new Date(bday) : "";
+        }
+    }
+
+    // BlackBerry supports three email addresses
+    if (contact.emails && utils.isArray(contact.emails)) {
+
+        // if this is an update, re-initialize email addresses
+        if (update) {
+            bbContact.email1 = "";
+            bbContact.email2 = "";
+            bbContact.email3 = "";
+        }
+
+        // copy the first three email addresses found
+        var email = null;
+        for ( var i = 0; i < contact.emails.length; i += 1) {
+            email = contact.emails[i];
+            if (!email || !email.value) {
+                continue;
+            }
+            if (bbContact.email1 === "") {
+                bbContact.email1 = email.value;
+            } else if (bbContact.email2 === "") {
+                bbContact.email2 = email.value;
+            } else if (bbContact.email3 === "") {
+                bbContact.email3 = email.value;
+            }
+        }
+    }
+
+    // BlackBerry supports a finite number of phone numbers
+    // copy into appropriate fields based on type
+    if (contact.phoneNumbers && utils.isArray(contact.phoneNumbers)) {
+
+        // if this is an update, re-initialize phone numbers
+        if (update) {
+            bbContact.homePhone = "";
+            bbContact.homePhone2 = "";
+            bbContact.workPhone = "";
+            bbContact.workPhone2 = "";
+            bbContact.mobilePhone = "";
+            bbContact.faxPhone = "";
+            bbContact.pagerPhone = "";
+            bbContact.otherPhone = "";
+        }
+
+        var type = null;
+        var number = null;
+        for ( var j = 0; j < contact.phoneNumbers.length; j += 1) {
+            if (!contact.phoneNumbers[j] || !contact.phoneNumbers[j].value) {
+                continue;
+            }
+            type = contact.phoneNumbers[j].type;
+            number = contact.phoneNumbers[j].value;
+            if (type === 'home') {
+                if (bbContact.homePhone === "") {
+                    bbContact.homePhone = number;
+                } else if (bbContact.homePhone2 === "") {
+                    bbContact.homePhone2 = number;
+                }
+            } else if (type === 'work') {
+                if (bbContact.workPhone === "") {
+                    bbContact.workPhone = number;
+                } else if (bbContact.workPhone2 === "") {
+                    bbContact.workPhone2 = number;
+                }
+            } else if (type === 'mobile' && bbContact.mobilePhone === "") {
+                bbContact.mobilePhone = number;
+            } else if (type === 'fax' && bbContact.faxPhone === "") {
+                bbContact.faxPhone = number;
+            } else if (type === 'pager' && bbContact.pagerPhone === "") {
+                bbContact.pagerPhone = number;
+            } else if (bbContact.otherPhone === "") {
+                bbContact.otherPhone = number;
+            }
+        }
+    }
+
+    // BlackBerry supports two addresses: home and work
+    // copy the first two addresses found from Contact
+    if (contact.addresses && utils.isArray(contact.addresses)) {
+
+        // if this is an update, re-initialize addresses
+        if (update) {
+            bbContact.homeAddress = null;
+            bbContact.workAddress = null;
+        }
+
+        var address = null;
+        var bbHomeAddress = null;
+        var bbWorkAddress = null;
+        for ( var k = 0; k < contact.addresses.length; k += 1) {
+            address = contact.addresses[k];
+            if (!address || address.id === undefined || address.pref === undefined || address.type === undefined || address.formatted === undefined) {
+                continue;
+            }
+
+            if (bbHomeAddress === null && (!address.type || address.type === "home")) {
+                bbHomeAddress = createBlackBerryAddress(address);
+                bbContact.homeAddress = bbHomeAddress;
+            } else if (bbWorkAddress === null && (!address.type || address.type === "work")) {
+                bbWorkAddress = createBlackBerryAddress(address);
+                bbContact.workAddress = bbWorkAddress;
+            }
+        }
+    }
+
+    // copy first url found to BlackBerry 'webpage' field
+    if (contact.urls && utils.isArray(contact.urls)) {
+
+        // if this is an update, re-initialize web page
+        if (update) {
+            bbContact.webpage = "";
+        }
+
+        var url = null;
+        for ( var m = 0; m < contact.urls.length; m += 1) {
+            url = contact.urls[m];
+            if (!url || !url.value) {
+                continue;
+            }
+            if (bbContact.webpage === "") {
+                bbContact.webpage = url.value;
+                break;
+            }
+        }
+    }
+
+    // copy fields from first organization to the
+    // BlackBerry 'company' and 'jobTitle' fields
+    if (contact.organizations && utils.isArray(contact.organizations)) {
+
+        // if this is an update, re-initialize org attributes
+        if (update) {
+            bbContact.company = "";
+        }
+
+        var org = null;
+        for ( var n = 0; n < contact.organizations.length; n += 1) {
+            org = contact.organizations[n];
+            if (!org) {
+                continue;
+            }
+            if (bbContact.company === "") {
+                bbContact.company = org.name || "";
+                bbContact.jobTitle = org.title || "";
+                break;
+            }
+        }
+    }
+
+    // categories
+    if (contact.categories && utils.isArray(contact.categories)) {
+        bbContact.categories = [];
+        var category = null;
+        for ( var o = 0; o < contact.categories.length; o += 1) {
+            category = contact.categories[o];
+            if (typeof category == "string") {
+                bbContact.categories.push(category);
+            }
+        }
+    }
+
+    // save to device
+    bbContact.save();
+
+    // invoke native side to save photo
+    // fail gracefully if photo URL is no good, but log the error
+    if (contact.photos && utils.isArray(contact.photos)) {
+        var photo = null;
+        for ( var p = 0; p < contact.photos.length; p += 1) {
+            photo = contact.photos[p];
+            if (!photo || !photo.value) {
+                continue;
+            }
+            exec(
+            // success
+            function() {
+            },
+            // fail
+            function(e) {
+                console.log('Contact.setPicture failed:' + e);
+            }, "Contacts", "setPicture", [ bbContact.uid, photo.type,
+                    photo.value ]);
+            break;
+        }
+    }
+
+    // Use the fully populated BlackBerry contact object to create a
+    // corresponding W3C contact object.
+    return ContactUtils.createContact(bbContact, [ "*" ]);
+};
+
+/**
+ * Creates a BlackBerry Address object from a W3C ContactAddress.
+ *
+ * @return {blackberry.pim.Address} a BlackBerry address object
+ */
+var createBlackBerryAddress = function(address) {
+    var bbAddress = new blackberry.pim.Address();
+
+    if (!address) {
+        return bbAddress;
+    }
+
+    bbAddress.address1 = address.streetAddress || "";
+    bbAddress.city = address.locality || "";
+    bbAddress.stateProvince = address.region || "";
+    bbAddress.zipPostal = address.postalCode || "";
+    bbAddress.country = address.country || "";
+
+    return bbAddress;
+};
+
+module.exports = {
+    /**
+     * Persists contact to device storage.
+     */
+    save : function(success, fail) {
+        try {
+            // save the contact and store it's unique id
+            var fullContact = saveToDevice(this);
+            this.id = fullContact.id;
+
+            // This contact object may only have a subset of properties
+            // if the save was an update of an existing contact. This is
+            // because the existing contact was likely retrieved using a
+            // subset of properties, so only those properties were set in the
+            // object. For this reason, invoke success with the contact object
+            // returned by saveToDevice since it is fully populated.
+            if (typeof success === 'function') {
+                success(fullContact);
+            }
+        } catch (e) {
+            console.log('Error saving contact: ' + e);
+            if (typeof fail === 'function') {
+                fail(new ContactError(ContactError.UNKNOWN_ERROR));
+            }
+        }
+    },
+
+    /**
+     * Removes contact from device storage.
+     *
+     * @param success
+     *            success callback
+     * @param fail
+     *            error callback
+     */
+    remove : function(success, fail) {
+        try {
+            // retrieve contact from device by id
+            var bbContact = null;
+            if (this.id) {
+                bbContact = findByUniqueId(this.id);
+            }
+
+            // if contact was found, remove it
+            if (bbContact) {
+                console.log('removing contact: ' + bbContact.uid);
+                bbContact.remove();
+                if (typeof success === 'function') {
+                    success(this);
+                }
+            }
+            // attempting to remove a contact that hasn't been saved
+            else if (typeof fail === 'function') {
+                fail(new ContactError(ContactError.UNKNOWN_ERROR));
+            }
+        } catch (e) {
+            console.log('Error removing contact ' + this.id + ": " + e);
+            if (typeof fail === 'function') {
+                fail(new ContactError(ContactError.UNKNOWN_ERROR));
+            }
+        }
+    }
+};

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/366572ae/lib/webworks/java/plugin/java/ContactUtils.js
----------------------------------------------------------------------
diff --git a/lib/webworks/java/plugin/java/ContactUtils.js b/lib/webworks/java/plugin/java/ContactUtils.js
new file mode 100644
index 0000000..fe216c4
--- /dev/null
+++ b/lib/webworks/java/plugin/java/ContactUtils.js
@@ -0,0 +1,361 @@
+var ContactAddress = require('cordova/plugin/ContactAddress'),
+    ContactName = require('cordova/plugin/ContactName'),
+    ContactField = require('cordova/plugin/ContactField'),
+    ContactOrganization = require('cordova/plugin/ContactOrganization'),
+    utils = require('cordova/utils'),
+    Contact = require('cordova/plugin/Contact');
+
+/**
+ * Mappings for each Contact field that may be used in a find operation. Maps
+ * W3C Contact fields to one or more fields in a BlackBerry contact object.
+ *
+ * Example: user searches with a filter on the Contact 'name' field:
+ *
+ * <code>Contacts.find(['name'], onSuccess, onFail, {filter:'Bob'});</code>
+ *
+ * The 'name' field does not exist in a BlackBerry contact. Instead, a filter
+ * expression will be built to search the BlackBerry contacts using the
+ * BlackBerry 'title', 'firstName' and 'lastName' fields.
+ */
+var fieldMappings = {
+    "id" : "uid",
+    "displayName" : "user1",
+    "name" : [ "title", "firstName", "lastName" ],
+    "name.formatted" : [ "title", "firstName", "lastName" ],
+    "name.givenName" : "firstName",
+    "name.familyName" : "lastName",
+    "name.honorificPrefix" : "title",
+    "phoneNumbers" : [ "faxPhone", "homePhone", "homePhone2", "mobilePhone",
+            "pagerPhone", "otherPhone", "workPhone", "workPhone2" ],
+    "phoneNumbers.value" : [ "faxPhone", "homePhone", "homePhone2",
+            "mobilePhone", "pagerPhone", "otherPhone", "workPhone",
+            "workPhone2" ],
+    "emails" : [ "email1", "email2", "email3" ],
+    "addresses" : [ "homeAddress.address1", "homeAddress.address2",
+            "homeAddress.city", "homeAddress.stateProvince",
+            "homeAddress.zipPostal", "homeAddress.country",
+            "workAddress.address1", "workAddress.address2", "workAddress.city",
+            "workAddress.stateProvince", "workAddress.zipPostal",
+            "workAddress.country" ],
+    "addresses.formatted" : [ "homeAddress.address1", "homeAddress.address2",
+            "homeAddress.city", "homeAddress.stateProvince",
+            "homeAddress.zipPostal", "homeAddress.country",
+            "workAddress.address1", "workAddress.address2", "workAddress.city",
+            "workAddress.stateProvince", "workAddress.zipPostal",
+            "workAddress.country" ],
+    "addresses.streetAddress" : [ "homeAddress.address1",
+            "homeAddress.address2", "workAddress.address1",
+            "workAddress.address2" ],
+    "addresses.locality" : [ "homeAddress.city", "workAddress.city" ],
+    "addresses.region" : [ "homeAddress.stateProvince",
+            "workAddress.stateProvince" ],
+    "addresses.country" : [ "homeAddress.country", "workAddress.country" ],
+    "organizations" : [ "company", "jobTitle" ],
+    "organizations.name" : "company",
+    "organizations.title" : "jobTitle",
+    "birthday" : "birthday",
+    "note" : "note",
+    "categories" : "categories",
+    "urls" : "webpage",
+    "urls.value" : "webpage"
+};
+
+/*
+ * Build an array of all of the valid W3C Contact fields. This is used to
+ * substitute all the fields when ["*"] is specified.
+ */
+var allFields = [];
+for ( var key in fieldMappings) {
+    if (fieldMappings.hasOwnProperty(key)) {
+        allFields.push(key);
+    }
+}
+
+/**
+ * Create a W3C ContactAddress object from a BlackBerry Address object.
+ *
+ * @param {String}
+ *            type the type of address (e.g. work, home)
+ * @param {blackberry.pim.Address}
+ *            bbAddress a BlakcBerry Address object
+ * @return {ContactAddress} a contact address object or null if the specified
+ *         address is null
+ */
+var createContactAddress = function(type, bbAddress) {
+
+    if (!bbAddress) {
+        return null;
+    }
+
+    var address1 = bbAddress.address1 || "";
+    var address2 = bbAddress.address2 || "";
+    var streetAddress = address1 + ", " + address2;
+    var locality = bbAddress.city || "";
+    var region = bbAddress.stateProvince || "";
+    var postalCode = bbAddress.zipPostal || "";
+    var country = bbAddress.country || "";
+    var formatted = streetAddress + ", " + locality + ", " + region + ", " + postalCode + ", " + country;
+
+    return new ContactAddress(null, type, formatted, streetAddress, locality,
+            region, postalCode, country);
+};
+
+module.exports = {
+    /**
+     * Builds a BlackBerry filter expression for contact search using the
+     * contact fields and search filter provided.
+     *
+     * @param {String[]}
+     *            fields Array of Contact fields to search
+     * @param {String}
+     *            filter Filter, or search string
+     * @return filter expression or null if fields is empty or filter is null or
+     *         empty
+     */
+    buildFilterExpression : function(fields, filter) {
+
+        // ensure filter exists
+        if (!filter || filter === "") {
+            return null;
+        }
+
+        if (fields.length == 1 && fields[0] === "*") {
+            // Cordova enhancement to allow fields value of ["*"] to indicate
+            // all supported fields.
+            fields = allFields;
+        }
+
+        // BlackBerry API uses specific operators to build filter expressions
+        // for
+        // querying Contact lists. The operators are
+        // ["!=","==","<",">","<=",">="].
+        // Use of regex is also an option, and the only one we can use to
+        // simulate
+        // an SQL '%LIKE%' clause.
+        //
+        // Note: The BlackBerry regex implementation doesn't seem to support
+        // conventional regex switches that would enable a case insensitive
+        // search.
+        // It does not honor the (?i) switch (which causes Contact.find() to
+        // fail).
+        // We need case INsensitivity to match the W3C Contacts API spec.
+        // So the guys at RIM proposed this method:
+        //
+        // original filter = "norm"
+        // case insensitive filter = "[nN][oO][rR][mM]"
+        //
+        var ciFilter = "";
+        for ( var i = 0; i < filter.length; i++) {
+            ciFilter = ciFilter + "[" + filter[i].toLowerCase() + filter[i].toUpperCase() + "]";
+        }
+
+        // match anything that contains our filter string
+        filter = ".*" + ciFilter + ".*";
+
+        // build a filter expression using all Contact fields provided
+        var filterExpression = null;
+        if (fields && utils.isArray(fields)) {
+            var fe = null;
+            for (var f = 0; f < fields.length; f++) {
+                if (!fields[f]) {
+                    continue;
+                }
+
+                // retrieve the BlackBerry contact fields that map to the one
+                // specified
+                var bbFields = fieldMappings[fields[f]];
+
+                // BlackBerry doesn't support the field specified
+                if (!bbFields) {
+                    continue;
+                }
+
+                if (!utils.isArray(bbFields)) {
+                    bbFields = [bbFields];
+                }
+
+                // construct the filter expression using the BlackBerry fields
+                for (var j = 0; j < bbFields.length; j++) {
+                    fe = new blackberry.find.FilterExpression(bbFields[j],
+                            "REGEX", filter);
+                    if (filterExpression === null) {
+                        filterExpression = fe;
+                    } else {
+                        // combine the filters
+                        filterExpression = new blackberry.find.FilterExpression(
+                                filterExpression, "OR", fe);
+                    }
+                }
+            }
+        }
+
+        return filterExpression;
+    },
+
+    /**
+     * Creates a Contact object from a BlackBerry Contact object, copying only
+     * the fields specified.
+     *
+     * This is intended as a privately used function but it is made globally
+     * available so that a Contact.save can convert a BlackBerry contact object
+     * into its W3C equivalent.
+     *
+     * @param {blackberry.pim.Contact}
+     *            bbContact BlackBerry Contact object
+     * @param {String[]}
+     *            fields array of contact fields that should be copied
+     * @return {Contact} a contact object containing the specified fields or
+     *         null if the specified contact is null
+     */
+    createContact : function(bbContact, fields) {
+
+        if (!bbContact) {
+            return null;
+        }
+
+        // construct a new contact object
+        // always copy the contact id and displayName fields
+        var contact = new Contact(bbContact.uid, bbContact.user1);
+
+        // nothing to do
+        if (!fields || !(utils.isArray(fields)) || fields.length === 0) {
+            return contact;
+        } else if (fields.length == 1 && fields[0] === "*") {
+            // Cordova enhancement to allow fields value of ["*"] to indicate
+            // all supported fields.
+            fields = allFields;
+        }
+
+        // add the fields specified
+        for (var i = 0; i < fields.length; i++) {
+            var field = fields[i];
+
+            if (!field) {
+                continue;
+            }
+
+            // name
+            if (field.indexOf('name') === 0) {
+                var formattedName = bbContact.title + ' ' + bbContact.firstName + ' ' + bbContact.lastName;
+                contact.name = new ContactName(formattedName,
+                        bbContact.lastName, bbContact.firstName, null,
+                        bbContact.title, null);
+            }
+            // phone numbers
+            else if (field.indexOf('phoneNumbers') === 0) {
+                var phoneNumbers = [];
+                if (bbContact.homePhone) {
+                    phoneNumbers.push(new ContactField('home',
+                            bbContact.homePhone));
+                }
+                if (bbContact.homePhone2) {
+                    phoneNumbers.push(new ContactField('home',
+                            bbContact.homePhone2));
+                }
+                if (bbContact.workPhone) {
+                    phoneNumbers.push(new ContactField('work',
+                            bbContact.workPhone));
+                }
+                if (bbContact.workPhone2) {
+                    phoneNumbers.push(new ContactField('work',
+                            bbContact.workPhone2));
+                }
+                if (bbContact.mobilePhone) {
+                    phoneNumbers.push(new ContactField('mobile',
+                            bbContact.mobilePhone));
+                }
+                if (bbContact.faxPhone) {
+                    phoneNumbers.push(new ContactField('fax',
+                            bbContact.faxPhone));
+                }
+                if (bbContact.pagerPhone) {
+                    phoneNumbers.push(new ContactField('pager',
+                            bbContact.pagerPhone));
+                }
+                if (bbContact.otherPhone) {
+                    phoneNumbers.push(new ContactField('other',
+                            bbContact.otherPhone));
+                }
+                contact.phoneNumbers = phoneNumbers.length > 0 ? phoneNumbers
+                        : null;
+            }
+            // emails
+            else if (field.indexOf('emails') === 0) {
+                var emails = [];
+                if (bbContact.email1) {
+                    emails.push(new ContactField(null, bbContact.email1, null));
+                }
+                if (bbContact.email2) {
+                    emails.push(new ContactField(null, bbContact.email2, null));
+                }
+                if (bbContact.email3) {
+                    emails.push(new ContactField(null, bbContact.email3, null));
+                }
+                contact.emails = emails.length > 0 ? emails : null;
+            }
+            // addresses
+            else if (field.indexOf('addresses') === 0) {
+                var addresses = [];
+                if (bbContact.homeAddress) {
+                    addresses.push(createContactAddress("home",
+                            bbContact.homeAddress));
+                }
+                if (bbContact.workAddress) {
+                    addresses.push(createContactAddress("work",
+                            bbContact.workAddress));
+                }
+                contact.addresses = addresses.length > 0 ? addresses : null;
+            }
+            // birthday
+            else if (field.indexOf('birthday') === 0) {
+                if (bbContact.birthday) {
+                    contact.birthday = bbContact.birthday;
+                }
+            }
+            // note
+            else if (field.indexOf('note') === 0) {
+                if (bbContact.note) {
+                    contact.note = bbContact.note;
+                }
+            }
+            // organizations
+            else if (field.indexOf('organizations') === 0) {
+                var organizations = [];
+                if (bbContact.company || bbContact.jobTitle) {
+                    organizations.push(new ContactOrganization(null, null,
+                            bbContact.company, null, bbContact.jobTitle));
+                }
+                contact.organizations = organizations.length > 0 ? organizations
+                        : null;
+            }
+            // categories
+            else if (field.indexOf('categories') === 0) {
+                if (bbContact.categories && bbContact.categories.length > 0) {
+                    contact.categories = bbContact.categories;
+                } else {
+                    contact.categories = null;
+                }
+            }
+            // urls
+            else if (field.indexOf('urls') === 0) {
+                var urls = [];
+                if (bbContact.webpage) {
+                    urls.push(new ContactField(null, bbContact.webpage));
+                }
+                contact.urls = urls.length > 0 ? urls : null;
+            }
+            // photos
+            else if (field.indexOf('photos') === 0) {
+                var photos = [];
+                // The BlackBerry Contact object will have a picture attribute
+                // with Base64 encoded image
+                if (bbContact.picture) {
+                    photos.push(new ContactField('base64', bbContact.picture));
+                }
+                contact.photos = photos.length > 0 ? photos : null;
+            }
+        }
+
+        return contact;
+    }
+};