some changes
[wl-mobile.git] / www / js / phonegap-1.1.0.js
1 /*
2  * PhoneGap v1.1.0 is available under *either* the terms of the modified BSD license *or* the
3  * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
4  * 
5  * Copyright (c) 2005-2010, Nitobi Software Inc.
6  * Copyright (c) 2010-2011, IBM Corporation
7  * Copyright (c) 2011, Codevise Solutions Ltd.
8  * Copyright (c) 2011, Proyectos Equis Ka, S.L.
9  * 
10  */
11
12 if (typeof PhoneGap === "undefined") {
13
14 if (typeof(DeviceInfo) !== 'object'){
15     DeviceInfo = {};
16 }
17 /**
18  * This represents the PhoneGap API itself, and provides a global namespace for accessing
19  * information about the state of PhoneGap.
20  * @class
21  */
22 PhoneGap = {
23     // This queue holds the currently executing command and all pending
24     // commands executed with PhoneGap.exec().
25     commandQueue: [],
26     // Indicates if we're currently in the middle of flushing the command
27     // queue on the native side.
28     commandQueueFlushing: false,
29     _constructors: [],
30     documentEventHandler: {},   // Collection of custom document event handlers
31     windowEventHandler: {} 
32 };
33
34 /**
35  * List of resource files loaded by PhoneGap.
36  * This is used to ensure JS and other files are loaded only once.
37  */
38 PhoneGap.resources = {base: true};
39
40 /**
41  * Determine if resource has been loaded by PhoneGap
42  *
43  * @param name
44  * @return
45  */
46 PhoneGap.hasResource = function(name) {
47     return PhoneGap.resources[name];
48 };
49
50 /**
51  * Add a resource to list of loaded resources by PhoneGap
52  *
53  * @param name
54  */
55 PhoneGap.addResource = function(name) {
56     PhoneGap.resources[name] = true;
57 };
58
59 /**
60  * Boolean flag indicating if the PhoneGap API is available and initialized.
61  */ // TODO: Remove this, it is unused here ... -jm
62 PhoneGap.available = DeviceInfo.uuid != undefined;
63
64 /**
65  * Add an initialization function to a queue that ensures it will run and initialize
66  * application constructors only once PhoneGap has been initialized.
67  * @param {Function} func The function callback you want run once PhoneGap is initialized
68  */
69 PhoneGap.addConstructor = function(func) {
70     var state = document.readyState;
71     if ( ( state == 'loaded' || state == 'complete' ) && DeviceInfo.uuid != null )
72     {
73         func();
74     }
75     else
76     {
77         PhoneGap._constructors.push(func);
78     }
79 };
80
81 (function() 
82  {
83     var timer = setInterval(function()
84     {
85                             
86         var state = document.readyState;
87                             
88         if ( ( state == 'loaded' || state == 'complete' ) && DeviceInfo.uuid != null )
89         {
90             clearInterval(timer); // stop looking
91             // run our constructors list
92             while (PhoneGap._constructors.length > 0) 
93             {
94                 var constructor = PhoneGap._constructors.shift();
95                 try 
96                 {
97                     constructor();
98                 } 
99                 catch(e) 
100                 {
101                     if (typeof(console['log']) == 'function')
102                     {
103                         console.log("Failed to run constructor: " + console.processMessage(e));
104                     }
105                     else
106                     {
107                         alert("Failed to run constructor: " + e.message);
108                     }
109                 }
110             }
111             // all constructors run, now fire the deviceready event
112             var e = document.createEvent('Events'); 
113             e.initEvent('deviceready');
114             document.dispatchEvent(e);
115         }
116     }, 1);
117 })();
118
119 // session id for calls
120 PhoneGap.sessionKey = 0;
121
122 // centralized callbacks
123 PhoneGap.callbackId = 0;
124 PhoneGap.callbacks = {};
125 PhoneGap.callbackStatus = {
126     NO_RESULT: 0,
127     OK: 1,
128     CLASS_NOT_FOUND_EXCEPTION: 2,
129     ILLEGAL_ACCESS_EXCEPTION: 3,
130     INSTANTIATION_EXCEPTION: 4,
131     MALFORMED_URL_EXCEPTION: 5,
132     IO_EXCEPTION: 6,
133     INVALID_ACTION: 7,
134     JSON_EXCEPTION: 8,
135     ERROR: 9
136     };
137
138 /**
139  * Creates a gap bridge iframe used to notify the native code about queued
140  * commands.
141  *
142  * @private
143  */
144 PhoneGap.createGapBridge = function() {
145     gapBridge = document.createElement("iframe");
146     gapBridge.setAttribute("style", "display:none;");
147     gapBridge.setAttribute("height","0px");
148     gapBridge.setAttribute("width","0px");
149     gapBridge.setAttribute("frameborder","0");
150     document.documentElement.appendChild(gapBridge);
151     return gapBridge;
152 }
153
154 /** 
155  * Execute a PhoneGap command by queuing it and letting the native side know
156  * there are queued commands. The native side will then request all of the
157  * queued commands and execute them.
158  *
159  * Arguments may be in one of two formats:
160  *
161  * FORMAT ONE (preferable)
162  * The native side will call PhoneGap.callbackSuccess or
163  * PhoneGap.callbackError, depending upon the result of the action.
164  *
165  * @param {Function} success    The success callback
166  * @param {Function} fail       The fail callback
167  * @param {String} service      The name of the service to use
168  * @param {String} action       The name of the action to use
169  * @param {String[]} [args]     Zero or more arguments to pass to the method
170  *      
171  * FORMAT TWO
172  * @param {String} command    Command to be run in PhoneGap, e.g.
173  *                            "ClassName.method"
174  * @param {String[]} [args]   Zero or more arguments to pass to the method
175  *                            object parameters are passed as an array object
176  *                            [object1, object2] each object will be passed as
177  *                            JSON strings 
178  */
179 PhoneGap.exec = function() { 
180     if (!PhoneGap.available) {
181         alert("ERROR: Attempting to call PhoneGap.exec()"
182               +" before 'deviceready'. Ignoring.");
183         return;
184     }
185
186     var successCallback, failCallback, service, action, actionArgs;
187     var callbackId = null;
188     if (typeof arguments[0] !== "string") {
189         // FORMAT ONE
190         successCallback = arguments[0];
191         failCallback = arguments[1];
192         service = arguments[2];
193         action = arguments[3];
194         actionArgs = arguments[4];
195
196         // Since we need to maintain backwards compatibility, we have to pass
197         // an invalid callbackId even if no callback was provided since plugins
198         // will be expecting it. The PhoneGap.exec() implementation allocates
199         // an invalid callbackId and passes it even if no callbacks were given.
200         callbackId = 'INVALID';
201     } else {
202         // FORMAT TWO
203         splitCommand = arguments[0].split(".");
204         action = splitCommand.pop();
205         service = splitCommand.join(".");
206         actionArgs = Array.prototype.splice.call(arguments, 1);
207     }
208     
209     // Start building the command object.
210     var command = {
211         className: service,
212         methodName: action,
213         arguments: []
214     };
215
216     // Register the callbacks and add the callbackId to the positional
217     // arguments if given.
218     if (successCallback || failCallback) {
219         callbackId = service + PhoneGap.callbackId++;
220         PhoneGap.callbacks[callbackId] = 
221             {success:successCallback, fail:failCallback};
222     }
223     if (callbackId != null) {
224         command.arguments.push(callbackId);
225     }
226
227     for (var i = 0; i < actionArgs.length; ++i) {
228         var arg = actionArgs[i];
229         if (arg == undefined || arg == null) {
230             continue;
231         } else if (typeof(arg) == 'object') {
232             command.options = arg;
233         } else {
234             command.arguments.push(arg);
235         }
236     }
237
238     // Stringify and queue the command. We stringify to command now to
239     // effectively clone the command arguments in case they are mutated before
240     // the command is executed.
241     PhoneGap.commandQueue.push(JSON.stringify(command));
242
243     // If the queue length is 1, then that means it was empty before we queued
244     // the given command, so let the native side know that we have some
245     // commands to execute, unless the queue is currently being flushed, in
246     // which case the command will be picked up without notification.
247     if (PhoneGap.commandQueue.length == 1 && !PhoneGap.commandQueueFlushing) {
248         if (!PhoneGap.gapBridge) {
249             PhoneGap.gapBridge = PhoneGap.createGapBridge();
250         }
251
252         PhoneGap.gapBridge.src = "gap://ready";
253     }
254 }
255
256 /**
257  * Called by native code to retrieve all queued commands and clear the queue.
258  */
259 PhoneGap.getAndClearQueuedCommands = function() {
260   json = JSON.stringify(PhoneGap.commandQueue);
261   PhoneGap.commandQueue = [];
262   return json;
263 }
264
265 /**
266  * Called by native code when returning successful result from an action.
267  *
268  * @param callbackId
269  * @param args
270  *        args.status - PhoneGap.callbackStatus
271  *        args.message - return value
272  *        args.keepCallback - 0 to remove callback, 1 to keep callback in PhoneGap.callbacks[]
273  */
274 PhoneGap.callbackSuccess = function(callbackId, args) {
275     if (PhoneGap.callbacks[callbackId]) {
276
277         // If result is to be sent to callback
278         if (args.status == PhoneGap.callbackStatus.OK) {
279             try {
280                 if (PhoneGap.callbacks[callbackId].success) {
281                        PhoneGap.callbacks[callbackId].success(args.message);
282                 }
283             }
284             catch (e) {
285                 console.log("Error in success callback: "+callbackId+" = "+e);
286             }
287         }
288     
289         // Clear callback if not expecting any more results
290         if (!args.keepCallback) {
291             delete PhoneGap.callbacks[callbackId];
292         }
293     }
294 };
295
296 /**
297  * Called by native code when returning error result from an action.
298  *
299  * @param callbackId
300  * @param args
301  */
302 PhoneGap.callbackError = function(callbackId, args) {
303     if (PhoneGap.callbacks[callbackId]) {
304         try {
305             if (PhoneGap.callbacks[callbackId].fail) {
306                 PhoneGap.callbacks[callbackId].fail(args.message);
307             }
308         }
309         catch (e) {
310             console.log("Error in error callback: "+callbackId+" = "+e);
311         }
312         
313         // Clear callback if not expecting any more results
314         if (!args.keepCallback) {
315             delete PhoneGap.callbacks[callbackId];
316         }
317     }
318 };
319
320
321 /**
322  * Does a deep clone of the object.
323  *
324  * @param obj
325  * @return
326  */
327 PhoneGap.clone = function(obj) {
328     if(!obj) { 
329         return obj;
330     }
331
332     if(obj instanceof Array){
333         var retVal = new Array();
334         for(var i = 0; i < obj.length; ++i){
335             retVal.push(PhoneGap.clone(obj[i]));
336         }
337         return retVal;
338     }
339
340     if (obj instanceof Function) {
341         return obj;
342     }
343
344     if(!(obj instanceof Object)){
345         return obj;
346     }
347     
348     if (obj instanceof Date) {
349         return obj;
350     }
351
352     retVal = new Object();
353     for(i in obj){
354         if(!(i in retVal) || retVal[i] != obj[i]) {
355             retVal[i] = PhoneGap.clone(obj[i]);
356         }
357     }
358     return retVal;
359 };
360
361 // Intercept calls to document.addEventListener 
362 PhoneGap.m_document_addEventListener = document.addEventListener;
363
364 // Intercept calls to window.addEventListener
365 PhoneGap.m_window_addEventListener = window.addEventListener;
366
367 /**
368  * Add a custom window event handler.
369  *
370  * @param {String} event            The event name that callback handles
371  * @param {Function} callback       The event handler
372  */
373 PhoneGap.addWindowEventHandler = function(event, callback) {
374     PhoneGap.windowEventHandler[event] = callback;
375 }
376
377 /**
378  * Add a custom document event handler.
379  *
380  * @param {String} event            The event name that callback handles
381  * @param {Function} callback       The event handler
382  */
383 PhoneGap.addDocumentEventHandler = function(event, callback) {
384     PhoneGap.documentEventHandler[event] = callback;
385 }
386
387 /**
388  * Intercept adding document event listeners and handle our own
389  *
390  * @param {Object} evt
391  * @param {Function} handler
392  * @param capture
393  */
394 document.addEventListener = function(evt, handler, capture) {
395     var e = evt.toLowerCase();
396            
397     // If subscribing to an event that is handled by a plugin
398     if (typeof PhoneGap.documentEventHandler[e] !== "undefined") {
399         if (PhoneGap.documentEventHandler[e](e, handler, true)) {
400             return; // Stop default behavior
401         }
402     }
403     
404     PhoneGap.m_document_addEventListener.call(document, evt, handler, capture); 
405 };
406
407 /**
408  * Intercept adding window event listeners and handle our own
409  *
410  * @param {Object} evt
411  * @param {Function} handler
412  * @param capture
413  */
414 window.addEventListener = function(evt, handler, capture) {
415     var e = evt.toLowerCase();
416         
417     // If subscribing to an event that is handled by a plugin
418     if (typeof PhoneGap.windowEventHandler[e] !== "undefined") {
419         if (PhoneGap.windowEventHandler[e](e, handler, true)) {
420             return; // Stop default behavior
421         }
422     }
423         
424     PhoneGap.m_window_addEventListener.call(window, evt, handler, capture);
425 };
426
427 // Intercept calls to document.removeEventListener and watch for events that
428 // are generated by PhoneGap native code
429 PhoneGap.m_document_removeEventListener = document.removeEventListener;
430
431 // Intercept calls to window.removeEventListener
432 PhoneGap.m_window_removeEventListener = window.removeEventListener;
433
434 /**
435  * Intercept removing document event listeners and handle our own
436  *
437  * @param {Object} evt
438  * @param {Function} handler
439  * @param capture
440  */
441 document.removeEventListener = function(evt, handler, capture) {
442     var e = evt.toLowerCase();
443
444     // If unsubcribing from an event that is handled by a plugin
445     if (typeof PhoneGap.documentEventHandler[e] !== "undefined") {
446         if (PhoneGap.documentEventHandler[e](e, handler, false)) {
447             return; // Stop default behavior
448         }
449     }
450
451     PhoneGap.m_document_removeEventListener.call(document, evt, handler, capture);
452 };
453
454 /**
455  * Intercept removing window event listeners and handle our own
456  *
457  * @param {Object} evt
458  * @param {Function} handler
459  * @param capture
460  */
461 window.removeEventListener = function(evt, handler, capture) {
462     var e = evt.toLowerCase();
463
464     // If unsubcribing from an event that is handled by a plugin
465     if (typeof PhoneGap.windowEventHandler[e] !== "undefined") {
466         if (PhoneGap.windowEventHandler[e](e, handler, false)) {
467             return; // Stop default behavior
468         }
469     }
470
471     PhoneGap.m_window_removeEventListener.call(window, evt, handler, capture);
472 };
473
474 /**
475  * Method to fire document event
476  *
477  * @param {String} type             The event type to fire
478  * @param {Object} data             Data to send with event
479  */
480 PhoneGap.fireDocumentEvent = function(type, data) {
481     var e = document.createEvent('Events');
482     e.initEvent(type);
483     if (data) {
484         for (var i in data) {
485             e[i] = data[i];
486         }
487     }
488     document.dispatchEvent(e);
489 };
490
491 /**
492  * Method to fire window event
493  *
494  * @param {String} type             The event type to fire
495  * @param {Object} data             Data to send with event
496  */
497 PhoneGap.fireWindowEvent = function(type, data) {
498     var e = document.createEvent('Events');
499     e.initEvent(type);
500     if (data) {
501         for (var i in data) {
502             e[i] = data[i];
503         }
504     }
505     window.dispatchEvent(e);
506 };
507
508 /**
509  * Method to fire event from native code
510  * Leaving this generic version to handle problems with iOS 3.x. Is currently used by orientation and battery events
511  * Remove when iOS 3.x no longer supported and call fireWindowEvent or fireDocumentEvent directly
512  */
513 PhoneGap.fireEvent = function(type, target, data) {
514     var e = document.createEvent('Events');
515     e.initEvent(type);
516     if (data) {
517         for (var i in data) {
518             e[i] = data[i];
519         }
520     }
521     target = target || document;
522     if (target.dispatchEvent === undefined) { // ie window.dispatchEvent is undefined in iOS 3.x
523         target = document;
524     } 
525
526     target.dispatchEvent(e);
527 };
528 /**
529  * Create a UUID
530  *
531  * @return
532  */
533 PhoneGap.createUUID = function() {
534     return PhoneGap.UUIDcreatePart(4) + '-' +
535         PhoneGap.UUIDcreatePart(2) + '-' +
536         PhoneGap.UUIDcreatePart(2) + '-' +
537         PhoneGap.UUIDcreatePart(2) + '-' +
538         PhoneGap.UUIDcreatePart(6);
539 };
540
541 PhoneGap.UUIDcreatePart = function(length) {
542     var uuidpart = "";
543     for (var i=0; i<length; i++) {
544         var uuidchar = parseInt((Math.random() * 256)).toString(16);
545         if (uuidchar.length == 1) {
546             uuidchar = "0" + uuidchar;
547         }
548         uuidpart += uuidchar;
549     }
550     return uuidpart;
551 };
552 };
553
554
555 if (!PhoneGap.hasResource("debugconsole")) {
556         PhoneGap.addResource("debugconsole");
557         
558 /**
559  * This class provides access to the debugging console.
560  * @constructor
561  */
562 var DebugConsole = function() {
563     this.winConsole = window.console;
564     this.logLevel = DebugConsole.INFO_LEVEL;
565 }
566
567 // from most verbose, to least verbose
568 DebugConsole.ALL_LEVEL    = 1; // same as first level
569 DebugConsole.INFO_LEVEL   = 1;
570 DebugConsole.WARN_LEVEL   = 2;
571 DebugConsole.ERROR_LEVEL  = 4;
572 DebugConsole.NONE_LEVEL   = 8;
573                                                                                                         
574 DebugConsole.prototype.setLevel = function(level) {
575     this.logLevel = level;
576 };
577
578 /**
579  * Utility function for rendering and indenting strings, or serializing
580  * objects to a string capable of being printed to the console.
581  * @param {Object|String} message The string or object to convert to an indented string
582  * @private
583  */
584 DebugConsole.prototype.processMessage = function(message, maxDepth) {
585         if (maxDepth === undefined) maxDepth = 0;
586     if (typeof(message) != 'object') {
587         return (this.isDeprecated ? "WARNING: debug object is deprecated, please use console object \n" + message : message);
588     } else {
589         /**
590          * @function
591          * @ignore
592          */
593         function indent(str) {
594             return str.replace(/^/mg, "    ");
595         }
596         /**
597          * @function
598          * @ignore
599          */
600         function makeStructured(obj, depth) {
601             var str = "";
602             for (var i in obj) {
603                 try {
604                     if (typeof(obj[i]) == 'object' && depth < maxDepth) {
605                         str += i + ":\n" + indent(makeStructured(obj[i])) + "\n";
606                     } else {
607                         str += i + " = " + indent(String(obj[i])).replace(/^    /, "") + "\n";
608                     }
609                 } catch(e) {
610                     str += i + " = EXCEPTION: " + e.message + "\n";
611                 }
612             }
613             return str;
614         }
615         
616         return ("Object:\n" + makeStructured(message, maxDepth));
617     }
618 };
619
620 /**
621  * Print a normal log message to the console
622  * @param {Object|String} message Message or object to print to the console
623  */
624 DebugConsole.prototype.log = function(message, maxDepth) {
625     if (PhoneGap.available && this.logLevel <= DebugConsole.INFO_LEVEL)
626         PhoneGap.exec(null, null, 'com.phonegap.debugconsole', 'log',
627             [ this.processMessage(message, maxDepth), { logLevel: 'INFO' } ]
628         );
629     else
630         this.winConsole.log(message);
631 };
632
633 /**
634  * Print a warning message to the console
635  * @param {Object|String} message Message or object to print to the console
636  */
637 DebugConsole.prototype.warn = function(message, maxDepth) {
638     if (PhoneGap.available && this.logLevel <= DebugConsole.WARN_LEVEL)
639         PhoneGap.exec(null, null, 'com.phonegap.debugconsole', 'log',
640             [ this.processMessage(message, maxDepth), { logLevel: 'WARN' } ]
641         );
642     else
643         this.winConsole.error(message);
644 };
645
646 /**
647  * Print an error message to the console
648  * @param {Object|String} message Message or object to print to the console
649  */
650 DebugConsole.prototype.error = function(message, maxDepth) {
651     if (PhoneGap.available && this.logLevel <= DebugConsole.ERROR_LEVEL)
652                 PhoneGap.exec(null, null, 'com.phonegap.debugconsole', 'log',
653             [ this.processMessage(message, maxDepth), { logLevel: 'ERROR' } ]
654         );
655     else
656         this.winConsole.error(message);
657 };
658
659 PhoneGap.addConstructor(function() {
660     window.console = new DebugConsole();
661 });
662 };
663 if (!PhoneGap.hasResource("position")) {
664         PhoneGap.addResource("position");
665
666 /**
667  * This class contains position information.
668  * @param {Object} lat
669  * @param {Object} lng
670  * @param {Object} acc
671  * @param {Object} alt
672  * @param {Object} altAcc
673  * @param {Object} head
674  * @param {Object} vel
675  * @constructor
676  */
677 Position = function(coords, timestamp) {
678         this.coords = Coordinates.cloneFrom(coords);
679     this.timestamp = timestamp || new Date().getTime();
680 };
681
682 Position.prototype.equals = function(other) {
683     return (this.coords && other && other.coords &&
684             this.coords.latitude == other.coords.latitude &&
685             this.coords.longitude == other.coords.longitude);
686 };
687
688 Position.prototype.clone = function()
689 {
690     return new Position(
691         this.coords? this.coords.clone() : null,
692         this.timestamp? this.timestamp : new Date().getTime()
693     );
694 }
695
696 Coordinates = function(lat, lng, alt, acc, head, vel, altAcc) {
697         /**
698          * The latitude of the position.
699          */
700         this.latitude = lat;
701         /**
702          * The longitude of the position,
703          */
704         this.longitude = lng;
705         /**
706          * The altitude of the position.
707          */
708         this.altitude = alt;
709         /**
710          * The accuracy of the position.
711          */
712         this.accuracy = acc;
713         /**
714          * The direction the device is moving at the position.
715          */
716         this.heading = head;
717         /**
718          * The velocity with which the device is moving at the position.
719          */
720         this.speed = vel;
721         /**
722          * The altitude accuracy of the position.
723          */
724         this.altitudeAccuracy = (altAcc != 'undefined') ? altAcc : null; 
725 };
726
727 Coordinates.prototype.clone = function()
728 {
729     return new Coordinates(
730         this.latitude,
731         this.longitude,
732         this.altitude,
733         this.accuracy,
734         this.heading,
735         this.speed,
736         this.altitudeAccuracy
737     );
738 };
739
740 Coordinates.cloneFrom = function(obj)
741 {
742     return new Coordinates(
743         obj.latitude,
744         obj.longitude,
745         obj.altitude,
746         obj.accuracy,
747         obj.heading,
748         obj.speed,
749         obj.altitudeAccuracy
750     );
751 };
752
753 /**
754  * This class specifies the options for requesting position data.
755  * @constructor
756  */
757 PositionOptions = function(enableHighAccuracy, timeout, maximumAge) {
758         /**
759          * Specifies the desired position accuracy.
760          */
761         this.enableHighAccuracy = enableHighAccuracy || false;
762         /**
763          * The timeout after which if position data cannot be obtained the errorCallback
764          * is called.
765          */
766         this.timeout = timeout || 10000;
767         /**
768      * The age of a cached position whose age is no greater than the specified time 
769      * in milliseconds. 
770      */
771         this.maximumAge = maximumAge || 0;
772         
773         if (this.maximumAge < 0) {
774                 this.maximumAge = 0;
775         }
776 };
777
778 /**
779  * This class contains information about any GPS errors.
780  * @constructor
781  */
782 PositionError = function(code, message) {
783         this.code = code || 0;
784         this.message = message || "";
785 };
786
787 PositionError.UNKNOWN_ERROR = 0;
788 PositionError.PERMISSION_DENIED = 1;
789 PositionError.POSITION_UNAVAILABLE = 2;
790 PositionError.TIMEOUT = 3;
791
792 };if (!PhoneGap.hasResource("acceleration")) {
793         PhoneGap.addResource("acceleration");
794         
795
796 /**
797  * This class contains acceleration information
798  * @constructor
799  * @param {Number} x The force applied by the device in the x-axis.
800  * @param {Number} y The force applied by the device in the y-axis.
801  * @param {Number} z The force applied by the device in the z-axis.
802  */
803 Acceleration = function(x, y, z) {
804         /**
805          * The force applied by the device in the x-axis.
806          */
807         this.x = x;
808         /**
809          * The force applied by the device in the y-axis.
810          */
811         this.y = y;
812         /**
813          * The force applied by the device in the z-axis.
814          */
815         this.z = z;
816         /**
817          * The time that the acceleration was obtained.
818          */
819         this.timestamp = new Date().getTime();
820 }
821
822 /**
823  * This class specifies the options for requesting acceleration data.
824  * @constructor
825  */
826 AccelerationOptions = function() {
827         /**
828          * The timeout after which if acceleration data cannot be obtained the errorCallback
829          * is called.
830          */
831         this.timeout = 10000;
832 }
833 };if (!PhoneGap.hasResource("accelerometer")) {
834         PhoneGap.addResource("accelerometer");
835
836 /**
837  * This class provides access to device accelerometer data.
838  * @constructor
839  */
840 Accelerometer = function() 
841 {
842         /**
843          * The last known acceleration.
844          */
845         this.lastAcceleration = new Acceleration(0,0,0);
846 }
847
848 /**
849  * Asynchronously aquires the current acceleration.
850  * @param {Function} successCallback The function to call when the acceleration
851  * data is available
852  * @param {Function} errorCallback The function to call when there is an error 
853  * getting the acceleration data.
854  * @param {AccelerationOptions} options The options for getting the accelerometer data
855  * such as timeout.
856  */
857 Accelerometer.prototype.getCurrentAcceleration = function(successCallback, errorCallback, options) {
858         // If the acceleration is available then call success
859         // If the acceleration is not available then call error
860         
861         // Created for iPhone, Iphone passes back _accel obj litteral
862         if (typeof successCallback == "function") {
863                 successCallback(this.lastAcceleration);
864         }
865 };
866
867 // private callback called from Obj-C by name
868 Accelerometer.prototype._onAccelUpdate = function(x,y,z)
869 {
870    this.lastAcceleration = new Acceleration(x,y,z);
871 };
872
873 /**
874  * Asynchronously aquires the acceleration repeatedly at a given interval.
875  * @param {Function} successCallback The function to call each time the acceleration
876  * data is available
877  * @param {Function} errorCallback The function to call when there is an error 
878  * getting the acceleration data.
879  * @param {AccelerationOptions} options The options for getting the accelerometer data
880  * such as timeout.
881  */
882
883 Accelerometer.prototype.watchAcceleration = function(successCallback, errorCallback, options) {
884         //this.getCurrentAcceleration(successCallback, errorCallback, options);
885         // TODO: add the interval id to a list so we can clear all watches
886         var frequency = (options != undefined && options.frequency != undefined) ? options.frequency : 10000;
887         var updatedOptions = {
888                 desiredFrequency:frequency 
889         }
890         PhoneGap.exec(null, null, "com.phonegap.accelerometer", "start", [options]);
891
892         return setInterval(function() {
893                 navigator.accelerometer.getCurrentAcceleration(successCallback, errorCallback, options);
894         }, frequency);
895 };
896
897 /**
898  * Clears the specified accelerometer watch.
899  * @param {String} watchId The ID of the watch returned from #watchAcceleration.
900  */
901 Accelerometer.prototype.clearWatch = function(watchId) {
902         PhoneGap.exec(null, null, "com.phonegap.accelerometer", "stop", []);
903         clearInterval(watchId);
904 };
905
906 Accelerometer.install = function()
907 {
908     if (typeof navigator.accelerometer == "undefined") {
909                 navigator.accelerometer = new Accelerometer();
910         }
911 };
912
913 Accelerometer.installDeviceMotionHandler = function()
914 {
915         if (!(window.DeviceMotionEvent == undefined)) {
916                 // supported natively, so we don't have to add support
917                 return;
918         }       
919         
920         var self = this;
921         var devicemotionEvent = 'devicemotion';
922         self.deviceMotionWatchId = null;
923         self.deviceMotionListenerCount = 0;
924         self.deviceMotionLastEventTimestamp = 0;
925         
926         // backup original `window.addEventListener`, `window.removeEventListener`
927     var _addEventListener = window.addEventListener;
928     var _removeEventListener = window.removeEventListener;
929                                                                                                         
930         var windowDispatchAvailable = !(window.dispatchEvent === undefined); // undefined in iOS 3.x
931                                                                                                         
932         var accelWin = function(acceleration) {
933                 var evt = document.createEvent('Events');
934             evt.initEvent(devicemotionEvent);
935         
936                 evt.acceleration = null; // not all devices have gyroscope, don't care for now if we actually have it.
937                 evt.rotationRate = null; // not all devices have gyroscope, don't care for now if we actually have it:
938                 evt.accelerationIncludingGravity = acceleration; // accelerometer, all iOS devices have it
939                 
940                 var currentTime = new Date().getTime();
941                 evt.interval =  (self.deviceMotionLastEventTimestamp == 0) ? 0 : (currentTime - self.deviceMotionLastEventTimestamp);
942                 self.deviceMotionLastEventTimestamp = currentTime;
943                 
944                 if (windowDispatchAvailable) {
945                         window.dispatchEvent(evt);
946                 } else {
947                         document.dispatchEvent(evt);
948                 }
949         };
950         
951         var accelFail = function() {
952                 
953         };
954                                                                                                         
955     // override `window.addEventListener`
956     window.addEventListener = function() {
957         if (arguments[0] === devicemotionEvent) {
958             ++(self.deviceMotionListenerCount);
959                         if (self.deviceMotionListenerCount == 1) { // start
960                                 self.deviceMotionWatchId = navigator.accelerometer.watchAcceleration(accelWin, accelFail, { frequency:500});
961                         }
962                 } 
963                                                                                                         
964                 if (!windowDispatchAvailable) {
965                         return document.addEventListener.apply(this, arguments);
966                 } else {
967                         return _addEventListener.apply(this, arguments);
968                 }
969     };  
970
971     // override `window.removeEventListener'
972     window.removeEventListener = function() {
973         if (arguments[0] === devicemotionEvent) {
974             --(self.deviceMotionListenerCount);
975                         if (self.deviceMotionListenerCount == 0) { // stop
976                                 navigator.accelerometer.clearWatch(self.deviceMotionWatchId);
977                         }
978                 } 
979                 
980                 if (!windowDispatchAvailable) {
981                         return document.removeEventListener.apply(this, arguments);
982                 } else {
983                         return _removeEventListener.apply(this, arguments);
984                 }
985     };  
986 };
987
988
989 PhoneGap.addConstructor(Accelerometer.install);
990 PhoneGap.addConstructor(Accelerometer.installDeviceMotionHandler);
991
992 };/*
993  * PhoneGap is available under *either* the terms of the modified BSD license *or* the
994  * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
995  *
996  * Copyright (c) 2005-2010, Nitobi Software Inc.
997  * Copyright (c) 2010-2011, IBM Corporation
998  */
999
1000 if (!PhoneGap.hasResource("battery")) {
1001 PhoneGap.addResource("battery");
1002
1003 /**
1004  * This class contains information about the current battery status.
1005  * @constructor
1006  */
1007 var Battery = function() {
1008     this._level = null;
1009     this._isPlugged = null;
1010     this._batteryListener = [];
1011     this._lowListener = [];
1012     this._criticalListener = [];
1013 };
1014
1015 /**
1016  * Registers as an event producer for battery events.
1017  * 
1018  * @param {Object} eventType
1019  * @param {Object} handler
1020  * @param {Object} add
1021  */
1022 Battery.prototype.eventHandler = function(eventType, handler, add) {
1023     var me = navigator.battery;
1024     if (add) {
1025         // If there are no current registered event listeners start the battery listener on native side.
1026         if (me._batteryListener.length === 0 && me._lowListener.length === 0 && me._criticalListener.length === 0) {
1027             PhoneGap.exec(me._status, me._error, "com.phonegap.battery", "start", []);
1028         }
1029         
1030         // Register the event listener in the proper array
1031         if (eventType === "batterystatus") {
1032             var pos = me._batteryListener.indexOf(handler);
1033             if (pos === -1) {
1034                 me._batteryListener.push(handler);
1035             }
1036         } else if (eventType === "batterylow") {
1037             var pos = me._lowListener.indexOf(handler);
1038             if (pos === -1) {
1039                 me._lowListener.push(handler);
1040             }
1041         } else if (eventType === "batterycritical") {
1042             var pos = me._criticalListener.indexOf(handler);
1043             if (pos === -1) {
1044                 me._criticalListener.push(handler);
1045             }
1046         }
1047     } else {
1048         // Remove the event listener from the proper array
1049         if (eventType === "batterystatus") {
1050             var pos = me._batteryListener.indexOf(handler);
1051             if (pos > -1) {
1052                 me._batteryListener.splice(pos, 1);        
1053             }
1054         } else if (eventType === "batterylow") {
1055             var pos = me._lowListener.indexOf(handler);
1056             if (pos > -1) {
1057                 me._lowListener.splice(pos, 1);        
1058             }
1059         } else if (eventType === "batterycritical") {
1060             var pos = me._criticalListener.indexOf(handler);
1061             if (pos > -1) {
1062                 me._criticalListener.splice(pos, 1);        
1063             }
1064         }
1065         
1066         // If there are no more registered event listeners stop the battery listener on native side.
1067         if (me._batteryListener.length === 0 && me._lowListener.length === 0 && me._criticalListener.length === 0) {
1068             PhoneGap.exec(null, null, "com.phonegap.battery", "stop", []);
1069         }
1070     }
1071 };
1072
1073 /**
1074  * Callback for battery status
1075  * 
1076  * @param {Object} info                 keys: level, isPlugged
1077  */
1078 Battery.prototype._status = function(info) {
1079         if (info) {
1080                 var me = this;
1081                 if (me._level != info.level || me._isPlugged != info.isPlugged) {
1082                         // Fire batterystatus event
1083                         //PhoneGap.fireWindowEvent("batterystatus", info);
1084                         // use this workaround since iOS 3.x does have window.dispatchEvent
1085                         PhoneGap.fireEvent("batterystatus", window, info);      
1086
1087                         // Fire low battery event
1088                         if (info.level == 20 || info.level == 5) {
1089                                 if (info.level == 20) {
1090                                         //PhoneGap.fireWindowEvent("batterylow", info);
1091                                         // use this workaround since iOS 3.x does not have window.dispatchEvent
1092                                         PhoneGap.fireEvent("batterylow", window, info);
1093                                 }
1094                                 else {
1095                                         //PhoneGap.fireWindowEvent("batterycritical", info);
1096                                         // use this workaround since iOS 3.x does not have window.dispatchEvent
1097                                         PhoneGap.fireEvent("batterycritical", window, info);
1098                                 }
1099                         }
1100                 }
1101                 me._level = info.level;
1102                 me._isPlugged = info.isPlugged; 
1103         }
1104 };
1105
1106 /**
1107  * Error callback for battery start
1108  */
1109 Battery.prototype._error = function(e) {
1110     console.log("Error initializing Battery: " + e);
1111 };
1112
1113 PhoneGap.addConstructor(function() {
1114     if (typeof navigator.battery === "undefined") {
1115         navigator.battery = new Battery();
1116         PhoneGap.addWindowEventHandler("batterystatus", navigator.battery.eventHandler);
1117         PhoneGap.addWindowEventHandler("batterylow", navigator.battery.eventHandler);
1118         PhoneGap.addWindowEventHandler("batterycritical", navigator.battery.eventHandler);
1119     }
1120 });
1121 }if (!PhoneGap.hasResource("camera")) {
1122         PhoneGap.addResource("camera");
1123         
1124
1125 /**
1126  * This class provides access to the device camera.
1127  * @constructor
1128  */
1129 Camera = function() {
1130         
1131 }
1132 /**
1133  *  Available Camera Options
1134  *  {boolean} allowEdit - true to allow editing image, default = false
1135  *      {number} quality 0-100 (low to high) default =  100
1136  *  {Camera.DestinationType} destinationType default = DATA_URL
1137  *      {Camera.PictureSourceType} sourceType default = CAMERA
1138  *      {number} targetWidth - width in pixels to scale image default = 0 (no scaling)
1139  *  {number} targetHeight - height in pixels to scale image default = 0 (no scaling)
1140  *  {Camera.EncodingType} - encodingType default = JPEG
1141  */
1142 /**
1143  * Format of image that is returned from getPicture.
1144  *
1145  * Example: navigator.camera.getPicture(success, fail,
1146  *              { quality: 80,
1147  *                destinationType: Camera.DestinationType.DATA_URL,
1148  *                sourceType: Camera.PictureSourceType.PHOTOLIBRARY})
1149  */
1150 Camera.DestinationType = {
1151     DATA_URL: 0,                // Return base64 encoded string
1152     FILE_URI: 1                 // Return file uri 
1153 };
1154 Camera.prototype.DestinationType = Camera.DestinationType;
1155
1156 /**
1157  * Source to getPicture from.
1158  *
1159  * Example: navigator.camera.getPicture(success, fail,
1160  *              { quality: 80,
1161  *                destinationType: Camera.DestinationType.DATA_URL,
1162  *                sourceType: Camera.PictureSourceType.PHOTOLIBRARY})
1163  */
1164 Camera.PictureSourceType = {
1165     PHOTOLIBRARY : 0,           // Choose image from picture library 
1166     CAMERA : 1,                 // Take picture from camera
1167     SAVEDPHOTOALBUM : 2         // Choose image from picture library 
1168 };
1169 Camera.prototype.PictureSourceType = Camera.PictureSourceType;
1170
1171 /** 
1172  * Encoding of image returned from getPicture. 
1173  * 
1174  * Example: navigator.camera.getPicture(success, fail, 
1175  *              { quality: 80, 
1176  *                destinationType: Camera.DestinationType.DATA_URL, 
1177  *                sourceType: Camera.PictureSourceType.CAMERA, 
1178  *                encodingType: Camera.EncodingType.PNG}) 
1179  */ 
1180 Camera.EncodingType = { 
1181         JPEG: 0,                    // Return JPEG encoded image 
1182         PNG: 1                      // Return PNG encoded image 
1183 };
1184 Camera.prototype.EncodingType = Camera.EncodingType;
1185
1186 /** 
1187  * Type of pictures to select from.  Only applicable when
1188  *      PictureSourceType is PHOTOLIBRARY or SAVEDPHOTOALBUM 
1189  * 
1190  * Example: navigator.camera.getPicture(success, fail, 
1191  *              { quality: 80, 
1192  *                destinationType: Camera.DestinationType.DATA_URL, 
1193  *                sourceType: Camera.PictureSourceType.PHOTOLIBRARY, 
1194  *                mediaType: Camera.MediaType.PICTURE}) 
1195  */ 
1196 Camera.MediaType = { 
1197         PICTURE: 0,             // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType
1198         VIDEO: 1,                // allow selection of video only, ONLY RETURNS URL
1199         ALLMEDIA : 2                    // allow selection from all media types
1200 };
1201 Camera.prototype.MediaType = Camera.MediaType;
1202
1203 /**
1204  * Gets a picture from source defined by "options.sourceType", and returns the
1205  * image as defined by the "options.destinationType" option.
1206
1207  * The defaults are sourceType=CAMERA and destinationType=DATA_URL.
1208  *
1209  * @param {Function} successCallback
1210  * @param {Function} errorCallback
1211  * @param {Object} options
1212  */
1213 Camera.prototype.getPicture = function(successCallback, errorCallback, options) {
1214         // successCallback required
1215         if (typeof successCallback != "function") {
1216         console.log("Camera Error: successCallback is not a function");
1217         return;
1218     }
1219
1220     // errorCallback optional
1221     if (errorCallback && (typeof errorCallback != "function")) {
1222         console.log("Camera Error: errorCallback is not a function");
1223         return;
1224     }
1225         
1226         PhoneGap.exec(successCallback, errorCallback, "com.phonegap.camera","getPicture",[options]);
1227 };
1228
1229
1230
1231 PhoneGap.addConstructor(function() {
1232     if (typeof navigator.camera == "undefined") navigator.camera = new Camera();
1233 });
1234 };
1235
1236 if (!PhoneGap.hasResource("device")) {
1237         PhoneGap.addResource("device");
1238
1239 /**
1240  * this represents the mobile device, and provides properties for inspecting the model, version, UUID of the
1241  * phone, etc.
1242  * @constructor
1243  */
1244 Device = function() 
1245 {
1246     this.platform = null;
1247     this.version  = null;
1248     this.name     = null;
1249     this.phonegap      = null;
1250     this.uuid     = null;
1251     try 
1252         {      
1253                 this.platform = DeviceInfo.platform;
1254                 this.version  = DeviceInfo.version;
1255                 this.name     = DeviceInfo.name;
1256                 this.phonegap = DeviceInfo.gap;
1257                 this.uuid     = DeviceInfo.uuid;
1258
1259     } 
1260         catch(e) 
1261         {
1262         // TODO: 
1263     }
1264         this.available = PhoneGap.available = this.uuid != null;
1265 }
1266
1267 PhoneGap.addConstructor(function() {
1268         if (typeof navigator.device === "undefined") {
1269         navigator.device = window.device = new Device();
1270         }
1271 });
1272 };
1273
1274 if (!PhoneGap.hasResource("capture")) {
1275         PhoneGap.addResource("capture");
1276 /**
1277  * The CaptureError interface encapsulates all errors in the Capture API.
1278  */
1279 function CaptureError() {
1280    this.code = null;
1281 };
1282
1283 // Capture error codes
1284 CaptureError.CAPTURE_INTERNAL_ERR = 0;
1285 CaptureError.CAPTURE_APPLICATION_BUSY = 1;
1286 CaptureError.CAPTURE_INVALID_ARGUMENT = 2;
1287 CaptureError.CAPTURE_NO_MEDIA_FILES = 3;
1288 CaptureError.CAPTURE_NOT_SUPPORTED = 20;
1289
1290 /**
1291  * The Capture interface exposes an interface to the camera and microphone of the hosting device.
1292  */
1293 function Capture() {
1294         this.supportedAudioModes = [];
1295         this.supportedImageModes = [];
1296         this.supportedVideoModes = [];
1297 };
1298
1299 /**
1300  * Launch audio recorder application for recording audio clip(s).
1301  * 
1302  * @param {Function} successCB
1303  * @param {Function} errorCB
1304  * @param {CaptureAudioOptions} options
1305  *
1306  * No audio recorder to launch for iOS - return CAPTURE_NOT_SUPPORTED
1307  */
1308 Capture.prototype.captureAudio = function(successCallback, errorCallback, options) {
1309         /*if (errorCallback && typeof errorCallback === "function") {
1310                 errorCallback({
1311                                 "code": CaptureError.CAPTURE_NOT_SUPPORTED
1312                         });
1313         }*/
1314     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.mediacapture", "captureAudio", [options]);
1315 };
1316
1317 /**
1318  * Launch camera application for taking image(s).
1319  * 
1320  * @param {Function} successCB
1321  * @param {Function} errorCB
1322  * @param {CaptureImageOptions} options
1323  */
1324 Capture.prototype.captureImage = function(successCallback, errorCallback, options) {
1325     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.mediacapture", "captureImage", [options]);
1326 };
1327
1328 /**
1329  * Launch camera application for taking image(s).
1330  * 
1331  * @param {Function} successCB
1332  * @param {Function} errorCB
1333  * @param {CaptureImageOptions} options
1334  */
1335 Capture.prototype._castMediaFile = function(pluginResult) {
1336     var mediaFiles = [];
1337     var i;
1338     for (i=0; i<pluginResult.message.length; i++) {
1339         var mediaFile = new MediaFile();
1340             mediaFile.name = pluginResult.message[i].name;
1341             mediaFile.fullPath = pluginResult.message[i].fullPath;
1342             mediaFile.type = pluginResult.message[i].type;
1343             mediaFile.lastModifiedDate = pluginResult.message[i].lastModifiedDate;
1344             mediaFile.size = pluginResult.message[i].size;
1345         mediaFiles.push(mediaFile);
1346     }
1347     pluginResult.message = mediaFiles;
1348     return pluginResult;
1349 };
1350
1351 /**
1352  * Launch device camera application for recording video(s).
1353  * 
1354  * @param {Function} successCB
1355  * @param {Function} errorCB
1356  * @param {CaptureVideoOptions} options
1357  */
1358 Capture.prototype.captureVideo = function(successCallback, errorCallback, options) {
1359     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.mediacapture", "captureVideo", [options]);
1360 };
1361
1362 /**
1363  * Encapsulates a set of parameters that the capture device supports.
1364  */
1365 function ConfigurationData() {
1366     // The ASCII-encoded string in lower case representing the media type. 
1367     this.type; 
1368     // The height attribute represents height of the image or video in pixels. 
1369     // In the case of a sound clip this attribute has value 0. 
1370     this.height = 0;
1371     // The width attribute represents width of the image or video in pixels. 
1372     // In the case of a sound clip this attribute has value 0
1373     this.width = 0;
1374 };
1375
1376 /**
1377  * Encapsulates all image capture operation configuration options.
1378  */
1379 var CaptureImageOptions = function() {
1380     // Upper limit of images user can take. Value must be equal or greater than 1.
1381     this.limit = 1; 
1382     // The selected image mode. Must match with one of the elements in supportedImageModes array.
1383     this.mode = null; 
1384 };
1385
1386 /**
1387  * Encapsulates all video capture operation configuration options.
1388  */
1389 var CaptureVideoOptions = function() {
1390     // Upper limit of videos user can record. Value must be equal or greater than 1.
1391     this.limit = 1;
1392     // Maximum duration of a single video clip in seconds.
1393     this.duration = 0;
1394     // The selected video mode. Must match with one of the elements in supportedVideoModes array.
1395     this.mode = null;
1396 };
1397
1398 /**
1399  * Encapsulates all audio capture operation configuration options.
1400  */
1401 var CaptureAudioOptions = function() {
1402     // Upper limit of sound clips user can record. Value must be equal or greater than 1.
1403     this.limit = 1;
1404     // Maximum duration of a single sound clip in seconds.
1405     this.duration = 0;
1406     // The selected audio mode. Must match with one of the elements in supportedAudioModes array.
1407     this.mode = null;
1408 };
1409
1410 /**
1411  * Represents a single file.
1412  * 
1413  * name {DOMString} name of the file, without path information
1414  * fullPath {DOMString} the full path of the file, including the name
1415  * type {DOMString} mime type
1416  * lastModifiedDate {Date} last modified date
1417  * size {Number} size of the file in bytes
1418  */
1419 function MediaFile(name, fullPath, type, lastModifiedDate, size) {
1420     this.name = name || null;
1421     this.fullPath = fullPath || null;
1422     this.type = type || null;
1423     this.lastModifiedDate = lastModifiedDate || null;
1424     this.size = size || 0;
1425 }
1426
1427 /**
1428  * Request capture format data for a specific file and type
1429  * 
1430  * @param {Function} successCB
1431  * @param {Function} errorCB
1432  */
1433 MediaFile.prototype.getFormatData = function(successCallback, errorCallback) {
1434         if (typeof this.fullPath === "undefined" || this.fullPath === null) {
1435                 errorCallback({
1436                                 "code": CaptureError.CAPTURE_INVALID_ARGUMENT
1437                         });
1438         } else {
1439         PhoneGap.exec(successCallback, errorCallback, "com.phonegap.mediacapture", "getFormatData", [this.fullPath, this.type]);
1440         }       
1441 };
1442
1443 /**
1444  * MediaFileData encapsulates format information of a media file.
1445  * 
1446  * @param {DOMString} codecs
1447  * @param {long} bitrate
1448  * @param {long} height
1449  * @param {long} width
1450  * @param {float} duration
1451  */
1452 function MediaFileData(codecs, bitrate, height, width, duration) {
1453     this.codecs = codecs || null;
1454     this.bitrate = bitrate || 0;
1455     this.height = height || 0;
1456     this.width = width || 0;
1457     this.duration = duration || 0;
1458 }
1459
1460 PhoneGap.addConstructor(function() {
1461     if (typeof navigator.device === "undefined") {
1462         navigator.device = window.device = new Device();
1463     }
1464     if (typeof navigator.device.capture === "undefined") {
1465         navigator.device.capture = window.device.capture = new Capture();
1466     }
1467 });
1468 };
1469 if (!PhoneGap.hasResource("contact")) {
1470         PhoneGap.addResource("contact");
1471
1472
1473 /**
1474 * Contains information about a single contact.
1475 * @param {DOMString} id unique identifier
1476 * @param {DOMString} displayName
1477 * @param {ContactName} name
1478 * @param {DOMString} nickname
1479 * @param {ContactField[]} phoneNumbers array of phone numbers
1480 * @param {ContactField[]} emails array of email addresses
1481 * @param {ContactAddress[]} addresses array of addresses
1482 * @param {ContactField[]} ims instant messaging user ids
1483 * @param {ContactOrganization[]} organizations
1484 * @param {DOMString} birthday contact's birthday
1485 * @param {DOMString} note user notes about contact
1486 * @param {ContactField[]} photos
1487 * @param {Array.<ContactField>} categories
1488 * @param {ContactField[]} urls contact's web sites
1489 */
1490 var Contact = function(id, displayName, name, nickname, phoneNumbers, emails, addresses,
1491     ims, organizations, birthday, note, photos, categories, urls) {
1492     this.id = id || null;
1493     this.displayName = displayName || null;
1494     this.name = name || null; // ContactName
1495     this.nickname = nickname || null;
1496     this.phoneNumbers = phoneNumbers || null; // ContactField[]
1497     this.emails = emails || null; // ContactField[]
1498     this.addresses = addresses || null; // ContactAddress[]
1499     this.ims = ims || null; // ContactField[]
1500     this.organizations = organizations || null; // ContactOrganization[]
1501     this.birthday = birthday || null; // JS Date
1502     this.note = note || null;
1503     this.photos = photos || null; // ContactField[]
1504     this.categories = categories || null; 
1505     this.urls = urls || null; // ContactField[]
1506 };
1507
1508 /**
1509 * Converts Dates to milliseconds before sending to iOS
1510 */
1511 Contact.prototype.convertDatesOut = function()
1512 {
1513         var dates = new Array("birthday");
1514         for (var i=0; i<dates.length; i++){
1515                 var value = this[dates[i]];
1516                 if (value){
1517                         if (!value instanceof Date){
1518                                 try {
1519                                         value = new Date(value);
1520                                 } catch(exception){
1521                                         value = null;
1522                                 }
1523                         }
1524                         if (value instanceof Date){
1525                                 value = value.valueOf();
1526                         }
1527                         this[dates[i]] = value;
1528                 }
1529         }
1530         
1531 };
1532 /**
1533 * Converts milliseconds to JS Date when returning from iOS
1534 */
1535 Contact.prototype.convertDatesIn = function()
1536 {
1537         var dates = new Array("birthday");
1538         for (var i=0; i<dates.length; i++){
1539                 var value = this[dates[i]];
1540                 if (value){
1541                         try {
1542                                 this[dates[i]] = new Date(parseFloat(value));
1543                         } catch (exception){
1544                                 console.log("exception creating date");
1545                         }
1546                 }
1547         }
1548 };
1549 /**
1550 * Removes contact from device storage.
1551 * @param successCB success callback
1552 * @param errorCB error callback (optional)
1553 */
1554 Contact.prototype.remove = function(successCB, errorCB) {
1555         if (this.id == null) {
1556         var errorObj = new ContactError();
1557         errorObj.code = ContactError.UNKNOWN_ERROR;
1558         errorCB(errorObj);
1559     }
1560     else {
1561         PhoneGap.exec(successCB, errorCB, "com.phonegap.contacts", "remove", [{ "contact": this}]);
1562     }
1563 };
1564 /**
1565 * iOS ONLY
1566 * displays contact via iOS UI
1567 *       NOT part of W3C spec so no official documentation
1568 *
1569 * @param errorCB error callback
1570 * @param options object
1571 *       allowsEditing: boolean AS STRING
1572 *               "true" to allow editing the contact
1573 *               "false" (default) display contact
1574 */
1575 Contact.prototype.display = function(errorCB, options) { 
1576         if (this.id == null) {
1577         if (typeof errorCB == "function") {
1578                 var errorObj = new ContactError();
1579                 errorObj.code = ContactError.UNKNOWN_ERROR;
1580                 errorCB(errorObj);
1581                 }
1582     }
1583     else {
1584         PhoneGap.exec(null, errorCB, "com.phonegap.contacts","displayContact", [this.id, options]);
1585     }
1586 };
1587
1588 /**
1589 * Creates a deep copy of this Contact.
1590 * With the contact ID set to null.
1591 * @return copy of this Contact
1592 */
1593 Contact.prototype.clone = function() {
1594     var clonedContact = PhoneGap.clone(this);
1595     clonedContact.id = null;
1596     // Loop through and clear out any id's in phones, emails, etc.
1597     if (clonedContact.phoneNumbers) {
1598         for (i=0; i<clonedContact.phoneNumbers.length; i++) {
1599                 clonedContact.phoneNumbers[i].id = null;
1600         }
1601     }
1602     if (clonedContact.emails) {
1603         for (i=0; i<clonedContact.emails.length; i++) {
1604                 clonedContact.emails[i].id = null;
1605         }
1606     }
1607     if (clonedContact.addresses) {
1608         for (i=0; i<clonedContact.addresses.length; i++) {
1609                 clonedContact.addresses[i].id = null;
1610         }
1611     }
1612     if (clonedContact.ims) {
1613         for (i=0; i<clonedContact.ims.length; i++) {
1614                 clonedContact.ims[i].id = null;
1615         }
1616     }
1617     if (clonedContact.organizations) {
1618         for (i=0; i<clonedContact.organizations.length; i++) {
1619                 clonedContact.organizations[i].id = null;
1620         }
1621     }
1622     if (clonedContact.photos) {
1623         for (i=0; i<clonedContact.photos.length; i++) {
1624                 clonedContact.photos[i].id = null;
1625         }
1626     }
1627     if (clonedContact.urls) {
1628         for (i=0; i<clonedContact.urls.length; i++) {
1629                 clonedContact.urls[i].id = null;
1630         }
1631     }
1632     return clonedContact;
1633 };
1634
1635 /**
1636 * Persists contact to device storage.
1637 * @param successCB success callback
1638 * @param errorCB error callback - optional
1639 */
1640 Contact.prototype.save = function(successCB, errorCB) {
1641         // don't modify the original contact
1642         var cloned = PhoneGap.clone(this);
1643         cloned.convertDatesOut(); 
1644         PhoneGap.exec(successCB, errorCB, "com.phonegap.contacts","save", [{"contact": cloned}]);
1645 };
1646
1647 /**
1648 * Contact name.
1649 * @param formatted
1650 * @param familyName
1651 * @param givenName
1652 * @param middle
1653 * @param prefix
1654 * @param suffix
1655 */
1656 var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) {
1657     this.formatted = formatted != "undefined" ? formatted : null;
1658     this.familyName = familyName != "undefined" ? familyName : null;
1659     this.givenName = givenName != "undefined" ? givenName : null;
1660     this.middleName = middle != "undefined" ? middle : null;
1661     this.honorificPrefix = prefix != "undefined" ? prefix : null;
1662     this.honorificSuffix = suffix != "undefined" ? suffix : null;
1663 };
1664
1665 /**
1666 * Generic contact field.
1667 * @param type
1668 * @param value
1669 * @param pref
1670 * @param id
1671 */
1672 var ContactField = function(type, value, pref, id) {
1673     this.type = type != "undefined" ? type : null;
1674     this.value = value != "undefined" ? value : null;
1675     this.pref = pref != "undefined" ? pref : null;
1676     this.id = id != "undefined" ? id : null;
1677 };
1678
1679 /**
1680 * Contact address.
1681 * @param pref - boolean is primary / preferred address
1682 * @param type - string - work, home…..
1683 * @param formatted
1684 * @param streetAddress
1685 * @param locality
1686 * @param region
1687 * @param postalCode
1688 * @param country
1689 */
1690 var ContactAddress = function(pref, type, formatted, streetAddress, locality, region, postalCode, country, id) {
1691         this.pref = pref != "undefined" ? pref : null;
1692         this.type = type != "undefined" ? type : null;
1693     this.formatted = formatted != "undefined" ? formatted : null;
1694     this.streetAddress = streetAddress != "undefined" ? streetAddress : null;
1695     this.locality = locality != "undefined" ? locality : null;
1696     this.region = region != "undefined" ? region : null;
1697     this.postalCode = postalCode != "undefined" ? postalCode : null;
1698     this.country = country != "undefined" ? country : null;
1699     this.id = id != "undefined" ? id : null;
1700 };
1701
1702 /**
1703 * Contact organization.
1704 * @param pref - boolean is primary / preferred address
1705 * @param type - string - work, home…..
1706 * @param name
1707 * @param dept
1708 * @param title
1709 */
1710 var ContactOrganization = function(pref, type, name, dept, title) {
1711         this.pref = pref != "undefined" ? pref : null;
1712         this.type = type != "undefined" ? type : null;
1713     this.name = name != "undefined" ? name : null;
1714     this.department = dept != "undefined" ? dept : null;
1715     this.title = title != "undefined" ? title : null;
1716 };
1717
1718 /**
1719 * Contact account.
1720 * @param domain
1721 * @param username
1722 * @param userid
1723 */
1724 /*var ContactAccount = function(domain, username, userid) {
1725     this.domain = domain != "undefined" ? domain : null;
1726     this.username = username != "undefined" ? username : null;
1727     this.userid = userid != "undefined" ? userid : null;
1728 }*/
1729
1730 /**
1731 * Represents a group of Contacts.
1732 */
1733 var Contacts = function() {
1734     this.inProgress = false;
1735     this.records = new Array();
1736 };
1737 /**
1738 * Returns an array of Contacts matching the search criteria.
1739 * @param fields that should be searched
1740 * @param successCB success callback
1741 * @param errorCB error callback (optional)
1742 * @param {ContactFindOptions} options that can be applied to contact searching
1743 * @return array of Contacts matching search criteria
1744 */
1745 Contacts.prototype.find = function(fields, successCB, errorCB, options) {
1746         if (successCB === null) {
1747         throw new TypeError("You must specify a success callback for the find command.");
1748     }
1749     if (fields === null || fields === "undefined" || fields.length === "undefined" || fields.length <= 0) {
1750         if (typeof errorCB === "function") {
1751                         errorCB({"code": ContactError.INVALID_ARGUMENT_ERROR});
1752         }
1753     } else {
1754                 PhoneGap.exec(successCB, errorCB, "com.phonegap.contacts","search", [{"fields":fields, "findOptions":options}]);
1755     }
1756 };
1757 /**
1758 * need to turn the array of JSON strings representing contact objects into actual objects
1759 * @param array of JSON strings with contact data
1760 * @return call results callback with array of Contact objects
1761 *  This function is called from objective C Contacts.search() method.
1762 */
1763 Contacts.prototype._findCallback = function(pluginResult) {
1764         var contacts = new Array();
1765         try {
1766                 for (var i=0; i<pluginResult.message.length; i++) {
1767                         var newContact = navigator.contacts.create(pluginResult.message[i]); 
1768                         newContact.convertDatesIn();
1769                         contacts.push(newContact);
1770                 }
1771                 pluginResult.message = contacts;
1772         } catch(e){
1773                         console.log("Error parsing contacts: " +e);
1774         }
1775         return pluginResult;
1776 }
1777
1778 /**
1779 * need to turn the JSON string representing contact object into actual object
1780 * @param JSON string with contact data
1781 * Call stored results function with  Contact object
1782 *  This function is called from objective C Contacts remove and save methods
1783 */
1784 Contacts.prototype._contactCallback = function(pluginResult)
1785 {
1786         var newContact = null;
1787         if (pluginResult.message){
1788                 try {
1789                         newContact = navigator.contacts.create(pluginResult.message);
1790                         newContact.convertDatesIn();
1791                 } catch(e){
1792                         console.log("Error parsing contact");
1793                 }
1794         }
1795         pluginResult.message = newContact;
1796         return pluginResult;
1797         
1798 };
1799 /** 
1800 * Need to return an error object rather than just a single error code
1801 * @param error code
1802 * Call optional error callback if found.
1803 * Called from objective c find, remove, and save methods on error.
1804 */
1805 Contacts.prototype._errCallback = function(pluginResult)
1806 {
1807         var errorObj = new ContactError();
1808         errorObj.code = pluginResult.message;
1809         pluginResult.message = errorObj;
1810         return pluginResult;
1811 };
1812 // iPhone only api to create a new contact via the GUI
1813 Contacts.prototype.newContactUI = function(successCallback) { 
1814     PhoneGap.exec(successCallback, null, "com.phonegap.contacts","newContact", []);
1815 };
1816 // iPhone only api to select a contact via the GUI
1817 Contacts.prototype.chooseContact = function(successCallback, options) {
1818     PhoneGap.exec(successCallback, null, "com.phonegap.contacts","chooseContact", options);
1819 };
1820
1821
1822 /**
1823 * This function creates a new contact, but it does not persist the contact
1824 * to device storage. To persist the contact to device storage, invoke
1825 * contact.save().
1826 * @param properties an object who's properties will be examined to create a new Contact
1827 * @returns new Contact object
1828 */
1829 Contacts.prototype.create = function(properties) {
1830     var i;
1831     var contact = new Contact();
1832     for (i in properties) {
1833         if (contact[i] !== 'undefined') {
1834             contact[i] = properties[i];
1835         }
1836     }
1837     return contact;
1838 };
1839
1840 /**
1841  * ContactFindOptions.
1842  * @param filter used to match contacts against
1843  * @param multiple boolean used to determine if more than one contact should be returned
1844  */
1845 var ContactFindOptions = function(filter, multiple, updatedSince) {
1846     this.filter = filter || '';
1847     this.multiple = multiple || false;
1848 };
1849
1850 /**
1851  *  ContactError.
1852  *  An error code assigned by an implementation when an error has occurred
1853  */
1854 var ContactError = function() {
1855     this.code=null;
1856 };
1857
1858 /**
1859  * Error codes
1860  */
1861 ContactError.UNKNOWN_ERROR = 0;
1862 ContactError.INVALID_ARGUMENT_ERROR = 1;
1863 ContactError.TIMEOUT_ERROR = 2;
1864 ContactError.PENDING_OPERATION_ERROR = 3;
1865 ContactError.IO_ERROR = 4;
1866 ContactError.NOT_SUPPORTED_ERROR = 5;
1867 ContactError.PERMISSION_DENIED_ERROR = 20;
1868
1869 /**
1870  * Add the contact interface into the browser.
1871  */
1872 PhoneGap.addConstructor(function() { 
1873     if(typeof navigator.contacts == "undefined") {
1874         navigator.contacts = new Contacts();
1875     }
1876 });
1877 };
1878 if (!PhoneGap.hasResource("file")) {
1879         PhoneGap.addResource("file");
1880
1881 /**
1882  * This class provides generic read and write access to the mobile device file system.
1883  * They are not used to read files from a server.
1884  */
1885
1886 /**
1887  * This class provides some useful information about a file.
1888  * This is the fields returned when navigator.fileMgr.getFileProperties() 
1889  * is called.
1890  */
1891 FileProperties = function(filePath) {
1892     this.filePath = filePath;
1893     this.size = 0;
1894     this.lastModifiedDate = null;
1895 }
1896 /**
1897  * Represents a single file.
1898  * 
1899  * name {DOMString} name of the file, without path information
1900  * fullPath {DOMString} the full path of the file, including the name
1901  * type {DOMString} mime type
1902  * lastModifiedDate {Date} last modified date
1903  * size {Number} size of the file in bytes
1904  */
1905 File = function(name, fullPath, type, lastModifiedDate, size) {
1906         this.name = name || null;
1907     this.fullPath = fullPath || null;
1908         this.type = type || null;
1909     this.lastModifiedDate = lastModifiedDate || null;
1910     this.size = size || 0;
1911 }
1912 /**
1913  * Create an event object since we can't set target on DOM event.
1914  *
1915  * @param type
1916  * @param target
1917  *
1918  */
1919 File._createEvent = function(type, target) {
1920     // Can't create event object, since we can't set target (its readonly)
1921     //var evt = document.createEvent('Events');
1922     //evt.initEvent("onload", false, false);
1923     var evt = {"type": type};
1924     evt.target = target;
1925     return evt;
1926 };
1927
1928 FileError = function() {
1929    this.code = null;
1930 }
1931
1932 // File error codes
1933 // Found in DOMException
1934 FileError.NOT_FOUND_ERR = 1;
1935 FileError.SECURITY_ERR = 2;
1936 FileError.ABORT_ERR = 3;
1937
1938 // Added by this specification
1939 FileError.NOT_READABLE_ERR = 4;
1940 FileError.ENCODING_ERR = 5;
1941 FileError.NO_MODIFICATION_ALLOWED_ERR = 6;
1942 FileError.INVALID_STATE_ERR = 7;
1943 FileError.SYNTAX_ERR = 8;
1944 FileError.INVALID_MODIFICATION_ERR = 9;
1945 FileError.QUOTA_EXCEEDED_ERR = 10;
1946 FileError.TYPE_MISMATCH_ERR = 11;
1947 FileError.PATH_EXISTS_ERR = 12;
1948
1949 //-----------------------------------------------------------------------------
1950 // File manager
1951 //-----------------------------------------------------------------------------
1952
1953 FileMgr = function() {
1954 }
1955
1956 FileMgr.prototype.testFileExists = function(fileName, successCallback, errorCallback) {
1957     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "testFileExists", [fileName]);
1958 };
1959
1960 FileMgr.prototype.testDirectoryExists = function(dirName, successCallback, errorCallback) {
1961     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "testDirectoryExists", [dirName]);
1962 };
1963
1964 FileMgr.prototype.getFreeDiskSpace = function(successCallback, errorCallback) {
1965     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "getFreeDiskSpace", []);
1966 };
1967
1968 FileMgr.prototype.write = function(fileName, data, position, successCallback, errorCallback) {
1969     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "write", [fileName, data, position]);
1970 };
1971
1972 FileMgr.prototype.truncate = function(fileName, size, successCallback, errorCallback) {
1973     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "truncateFile", [fileName, size]);
1974 };
1975
1976 FileMgr.prototype.readAsText = function(fileName, encoding, successCallback, errorCallback) {
1977     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "readFile", [fileName, encoding]);
1978 };
1979
1980 FileMgr.prototype.readAsDataURL = function(fileName, successCallback, errorCallback) {
1981         PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "readAsDataURL", [fileName]);
1982 };
1983
1984 PhoneGap.addConstructor(function() {
1985     if (typeof navigator.fileMgr === "undefined") {
1986         navigator.fileMgr = new FileMgr();
1987     }
1988 });
1989
1990
1991 //-----------------------------------------------------------------------------
1992 // File Reader
1993 //-----------------------------------------------------------------------------
1994
1995 /**
1996  * This class reads the mobile device file system.
1997  *
1998  */
1999 FileReader = function() {
2000     this.fileName = "";
2001
2002     this.readyState = 0;
2003
2004     // File data
2005     this.result = null;
2006
2007     // Error
2008     this.error = null;
2009
2010     // Event handlers
2011     this.onloadstart = null;    // When the read starts.
2012     this.onprogress = null;     // While reading (and decoding) file or fileBlob data, and reporting partial file data (progess.loaded/progress.total)
2013     this.onload = null;         // When the read has successfully completed.
2014     this.onerror = null;        // When the read has failed (see errors).
2015     this.onloadend = null;      // When the request has completed (either in success or failure).
2016     this.onabort = null;        // When the read has been aborted. For instance, by invoking the abort() method.
2017 }
2018
2019 // States
2020 FileReader.EMPTY = 0;
2021 FileReader.LOADING = 1;
2022 FileReader.DONE = 2;
2023
2024 /**
2025  * Abort reading file.
2026  */
2027 FileReader.prototype.abort = function() {
2028     var evt;
2029     this.readyState = FileReader.DONE;
2030     this.result = null;
2031
2032     // set error
2033     var error = new FileError();
2034     error.code = error.ABORT_ERR;
2035     this.error = error;
2036    
2037     // If error callback
2038     if (typeof this.onerror === "function") {
2039         evt = File._createEvent("error", this);
2040         this.onerror(evt);
2041     }
2042     // If abort callback
2043     if (typeof this.onabort === "function") {
2044         evt = File._createEvent("abort", this);
2045         this.onabort(evt);
2046     }
2047     // If load end callback
2048     if (typeof this.onloadend === "function") {
2049         evt = File._createEvent("loadend", this);
2050         this.onloadend(evt);
2051     }
2052 };
2053
2054 /**
2055  * Read text file.
2056  *
2057  * @param file          The name of the file
2058  * @param encoding      [Optional] (see http://www.iana.org/assignments/character-sets)
2059  */
2060 FileReader.prototype.readAsText = function(file, encoding) {
2061     this.fileName = "";
2062         if (typeof file.fullPath === "undefined") {
2063                 this.fileName = file;
2064         } else {
2065                 this.fileName = file.fullPath;
2066         }
2067
2068     // LOADING state
2069     this.readyState = FileReader.LOADING;
2070
2071     // If loadstart callback
2072     if (typeof this.onloadstart === "function") {
2073         var evt = File._createEvent("loadstart", this);
2074         this.onloadstart(evt);
2075     }
2076
2077     // Default encoding is UTF-8
2078     var enc = encoding ? encoding : "UTF-8";
2079
2080     var me = this;
2081
2082     // Read file
2083     navigator.fileMgr.readAsText(this.fileName, enc,
2084
2085         // Success callback
2086         function(r) {
2087             var evt;
2088
2089             // If DONE (cancelled), then don't do anything
2090             if (me.readyState === FileReader.DONE) {
2091                 return;
2092             }
2093
2094             // Save result
2095             me.result = decodeURIComponent(r);
2096
2097             // If onload callback
2098             if (typeof me.onload === "function") {
2099                 evt = File._createEvent("load", me);
2100                 me.onload(evt);
2101             }
2102
2103             // DONE state
2104             me.readyState = FileReader.DONE;
2105
2106             // If onloadend callback
2107             if (typeof me.onloadend === "function") {
2108                 evt = File._createEvent("loadend", me);
2109                 me.onloadend(evt);
2110             }
2111         },
2112
2113         // Error callback
2114         function(e) {
2115             var evt;
2116             // If DONE (cancelled), then don't do anything
2117             if (me.readyState === FileReader.DONE) {
2118                 return;
2119             }
2120
2121             // Save error
2122             me.error = e;
2123
2124             // If onerror callback
2125             if (typeof me.onerror === "function") {
2126                 evt = File._createEvent("error", me);
2127                 me.onerror(evt);
2128             }
2129
2130             // DONE state
2131             me.readyState = FileReader.DONE;
2132
2133             // If onloadend callback
2134             if (typeof me.onloadend === "function") {
2135                 evt = File._createEvent("loadend", me);
2136                 me.onloadend(evt);
2137             }
2138         }
2139         );
2140 };
2141
2142
2143 /**
2144  * Read file and return data as a base64 encoded data url.
2145  * A data url is of the form:
2146  *      data:[<mediatype>][;base64],<data>
2147  *
2148  * @param file          {File} File object containing file properties
2149  */
2150 FileReader.prototype.readAsDataURL = function(file) {
2151     this.fileName = "";
2152     
2153     if (typeof file.fullPath === "undefined") {
2154         this.fileName = file;
2155     } else {
2156         this.fileName = file.fullPath;
2157     }
2158
2159     // LOADING state
2160     this.readyState = FileReader.LOADING;
2161
2162     // If loadstart callback
2163     if (typeof this.onloadstart === "function") {
2164         var evt = File._createEvent("loadstart", this);
2165         this.onloadstart(evt);
2166     }
2167
2168     var me = this;
2169
2170     // Read file
2171     navigator.fileMgr.readAsDataURL(this.fileName,
2172
2173         // Success callback
2174         function(r) {
2175             var evt;
2176
2177             // If DONE (cancelled), then don't do anything
2178             if (me.readyState === FileReader.DONE) {
2179                 return;
2180             }
2181
2182             // Save result
2183             me.result = r;
2184
2185             // If onload callback
2186             if (typeof me.onload === "function") {
2187                 evt = File._createEvent("load", me);
2188                 me.onload(evt);
2189             }
2190
2191             // DONE state
2192             me.readyState = FileReader.DONE;
2193
2194             // If onloadend callback
2195             if (typeof me.onloadend === "function") {
2196                 evt = File._createEvent("loadend", me);
2197                 me.onloadend(evt);
2198             }
2199         },
2200
2201         // Error callback
2202         function(e) {
2203             var evt;
2204             // If DONE (cancelled), then don't do anything
2205             if (me.readyState === FileReader.DONE) {
2206                 return;
2207             }
2208
2209             // Save error
2210             me.error = e;
2211
2212             // If onerror callback
2213             if (typeof me.onerror === "function") {
2214                 evt = File._createEvent("error", me);
2215                 me.onerror(evt);
2216             }
2217
2218             // DONE state
2219             me.readyState = FileReader.DONE;
2220
2221             // If onloadend callback
2222             if (typeof me.onloadend === "function") {
2223                 evt = File._createEvent("loadend", me);
2224                 me.onloadend(evt);
2225             }
2226         }
2227         );
2228 };
2229
2230 /**
2231  * Read file and return data as a binary data.
2232  *
2233  * @param file          The name of the file
2234  */
2235 FileReader.prototype.readAsBinaryString = function(file) {
2236     // TODO - Can't return binary data to browser.
2237     this.fileName = file;
2238 };
2239
2240 /**
2241  * Read file and return data as a binary data.
2242  *
2243  * @param file          The name of the file
2244  */
2245 FileReader.prototype.readAsArrayBuffer = function(file) {
2246     // TODO - Can't return binary data to browser.
2247     this.fileName = file;
2248 };
2249
2250 //-----------------------------------------------------------------------------
2251 // File Writer
2252 //-----------------------------------------------------------------------------
2253
2254 /**
2255  * This class writes to the mobile device file system.
2256  *
2257   @param file {File} a File object representing a file on the file system
2258 */
2259 FileWriter = function(file) {
2260     this.fileName = "";
2261     this.length = 0;
2262         if (file) {
2263             this.fileName = file.fullPath || file;
2264             this.length = file.size || 0;
2265         }
2266         
2267         // default is to write at the beginning of the file
2268     this.position = 0;
2269     
2270     this.readyState = 0; // EMPTY
2271
2272     this.result = null;
2273
2274     // Error
2275     this.error = null;
2276
2277     // Event handlers
2278     this.onwritestart = null;   // When writing starts
2279     this.onprogress = null;             // While writing the file, and reporting partial file data
2280     this.onwrite = null;                // When the write has successfully completed.
2281     this.onwriteend = null;             // When the request has completed (either in success or failure).
2282     this.onabort = null;                // When the write has been aborted. For instance, by invoking the abort() method.
2283     this.onerror = null;                // When the write has failed (see errors).
2284 }
2285
2286 // States
2287 FileWriter.INIT = 0;
2288 FileWriter.WRITING = 1;
2289 FileWriter.DONE = 2;
2290
2291 /**
2292  * Abort writing file.
2293  */
2294 FileWriter.prototype.abort = function() {
2295     // check for invalid state
2296         if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) {
2297                 throw FileError.INVALID_STATE_ERR;
2298         } 
2299
2300     // set error
2301     var error = new FileError(), evt;
2302     error.code = error.ABORT_ERR;
2303     this.error = error;
2304     
2305     // If error callback
2306     if (typeof this.onerror === "function") {
2307         evt = File._createEvent("error", this);
2308         this.onerror(evt);
2309     }
2310     // If abort callback
2311     if (typeof this.onabort === "function") {
2312         evt = File._createEvent("abort", this);
2313         this.onabort(evt);
2314     }
2315     
2316     this.readyState = FileWriter.DONE;
2317
2318     // If write end callback
2319     if (typeof this.onwriteend == "function") {
2320         evt = File._createEvent("writeend", this);
2321         this.onwriteend(evt);
2322     }
2323 };
2324
2325 /**
2326  * @Deprecated: use write instead
2327  * 
2328  * @param file to write the data to
2329  * @param text to be written
2330  * @param bAppend if true write to end of file, otherwise overwrite the file
2331  */
2332 FileWriter.prototype.writeAsText = function(file, text, bAppend) {
2333         // Throw an exception if we are already writing a file
2334         if (this.readyState === FileWriter.WRITING) {
2335                 throw FileError.INVALID_STATE_ERR;
2336         }
2337
2338         if (bAppend !== true) {
2339         bAppend = false; // for null values
2340     }
2341
2342     this.fileName = file;
2343
2344     // WRITING state
2345     this.readyState = FileWriter.WRITING;
2346
2347     var me = this;
2348
2349     // If onwritestart callback
2350     if (typeof me.onwritestart === "function") {
2351         var evt = File._createEvent("writestart", me);
2352         me.onwritestart(evt);
2353     }
2354         
2355         
2356     // Write file 
2357         navigator.fileMgr.writeAsText(file, text, bAppend,
2358         // Success callback
2359         function(r) {
2360             var evt;
2361
2362             // If DONE (cancelled), then don't do anything
2363             if (me.readyState === FileWriter.DONE) {
2364                 return;
2365             }
2366
2367             // Save result
2368             me.result = r;
2369
2370             // If onwrite callback
2371             if (typeof me.onwrite === "function") {
2372                 evt = File._createEvent("write", me);
2373                 me.onwrite(evt);
2374             }
2375
2376             // DONE state
2377             me.readyState = FileWriter.DONE;
2378
2379             // If onwriteend callback
2380             if (typeof me.onwriteend === "function") {
2381                 evt = File._createEvent("writeend", me);
2382                 me.onwriteend(evt);
2383             }
2384         },
2385
2386         // Error callback
2387         function(e) {
2388             var evt;
2389
2390             // If DONE (cancelled), then don't do anything
2391             if (me.readyState === FileWriter.DONE) {
2392                 return;
2393             }
2394
2395             // Save error
2396             me.error = e;
2397
2398             // If onerror callback
2399             if (typeof me.onerror === "function") {
2400                 evt = File._createEvent("error", me);
2401                 me.onerror(evt);
2402             }
2403
2404             // DONE state
2405             me.readyState = FileWriter.DONE;
2406
2407             // If onwriteend callback
2408             if (typeof me.onwriteend === "function") {
2409                 evt = File._createEvent("writeend", me);
2410                 me.onwriteend(evt);
2411             }
2412         }
2413     );
2414 };
2415
2416 /**
2417  * Writes data to the file
2418  *  
2419  * @param text to be written
2420  */
2421 FileWriter.prototype.write = function(text) {
2422         // Throw an exception if we are already writing a file
2423         if (this.readyState === FileWriter.WRITING) {
2424                 throw FileError.INVALID_STATE_ERR;
2425         }
2426
2427     // WRITING state
2428     this.readyState = FileWriter.WRITING;
2429
2430     var me = this;
2431
2432     // If onwritestart callback
2433     if (typeof me.onwritestart === "function") {
2434         var evt = File._createEvent("writestart", me);
2435         me.onwritestart(evt);
2436     }
2437
2438     // Write file
2439     navigator.fileMgr.write(this.fileName, text, this.position,
2440
2441         // Success callback
2442         function(r) {
2443             var evt;
2444             // If DONE (cancelled), then don't do anything
2445             if (me.readyState === FileWriter.DONE) {
2446                 return;
2447             }
2448
2449             
2450             // position always increases by bytes written because file would be extended
2451             me.position += r;
2452                         // The length of the file is now where we are done writing.
2453                         me.length = me.position;
2454             
2455             // If onwrite callback
2456             if (typeof me.onwrite === "function") {
2457                 evt = File._createEvent("write", me);
2458                 me.onwrite(evt);
2459             }
2460
2461             // DONE state
2462             me.readyState = FileWriter.DONE;
2463
2464             // If onwriteend callback
2465             if (typeof me.onwriteend === "function") {
2466                 evt = File._createEvent("writeend", me);
2467                 me.onwriteend(evt);
2468             }
2469         },
2470
2471         // Error callback
2472         function(e) {
2473             var evt;
2474
2475             // If DONE (cancelled), then don't do anything
2476             if (me.readyState === FileWriter.DONE) {
2477                 return;
2478             }
2479
2480             // Save error
2481             me.error = e;
2482
2483             // If onerror callback
2484             if (typeof me.onerror === "function") {
2485                 evt = File._createEvent("error", me);
2486                 me.onerror(evt);
2487             }
2488
2489             // DONE state
2490             me.readyState = FileWriter.DONE;
2491
2492             // If onwriteend callback
2493             if (typeof me.onwriteend === "function") {
2494                 evt = File._createEvent("writeend", me);
2495                 me.onwriteend(evt);
2496             }
2497         }
2498         );
2499
2500 };
2501
2502 /** 
2503  * Moves the file pointer to the location specified.
2504  * 
2505  * If the offset is a negative number the position of the file 
2506  * pointer is rewound.  If the offset is greater than the file 
2507  * size the position is set to the end of the file.  
2508  * 
2509  * @param offset is the location to move the file pointer to.
2510  */
2511 FileWriter.prototype.seek = function(offset) {
2512     // Throw an exception if we are already writing a file
2513     if (this.readyState === FileWriter.WRITING) {
2514         throw FileError.INVALID_STATE_ERR;
2515     }
2516
2517     if (!offset) {
2518         return;
2519     }
2520     
2521     // See back from end of file.
2522     if (offset < 0) {
2523                 this.position = Math.max(offset + this.length, 0);
2524         }
2525     // Offset is bigger then file size so set position 
2526     // to the end of the file.
2527         else if (offset > this.length) {
2528                 this.position = this.length;
2529         }
2530     // Offset is between 0 and file size so set the position
2531     // to start writing.
2532         else {
2533                 this.position = offset;
2534         }       
2535 };
2536
2537 /** 
2538  * Truncates the file to the size specified.
2539  * 
2540  * @param size to chop the file at.
2541  */
2542 FileWriter.prototype.truncate = function(size) {
2543         // Throw an exception if we are already writing a file
2544         if (this.readyState === FileWriter.WRITING) {
2545                 throw FileError.INVALID_STATE_ERR;
2546         }
2547         // what if no size specified? 
2548
2549     // WRITING state
2550     this.readyState = FileWriter.WRITING;
2551
2552     var me = this;
2553
2554     // If onwritestart callback
2555     if (typeof me.onwritestart === "function") {
2556         var evt = File._createEvent("writestart", me);
2557         me.onwritestart(evt);
2558     }
2559
2560     // Write file
2561     navigator.fileMgr.truncate(this.fileName, size,
2562
2563         // Success callback
2564         function(r) {
2565             var evt;
2566             // If DONE (cancelled), then don't do anything
2567             if (me.readyState === FileWriter.DONE) {
2568                 return;
2569             }
2570
2571             // Update the length of the file
2572             me.length = r;
2573             me.position = Math.min(me.position, r);
2574
2575             // If onwrite callback
2576             if (typeof me.onwrite === "function") {
2577                 evt = File._createEvent("write", me);
2578                 me.onwrite(evt);
2579             }
2580
2581             // DONE state
2582             me.readyState = FileWriter.DONE;
2583
2584             // If onwriteend callback
2585             if (typeof me.onwriteend === "function") {
2586                 evt = File._createEvent("writeend", me);
2587                 me.onwriteend(evt);
2588             }
2589         },
2590
2591         // Error callback
2592         function(e) {
2593             var evt;
2594             // If DONE (cancelled), then don't do anything
2595             if (me.readyState === FileWriter.DONE) {
2596                 return;
2597             }
2598
2599             // Save error
2600             me.error = e;
2601
2602             // If onerror callback
2603             if (typeof me.onerror === "function") {
2604                 evt = File._createEvent("error", me);
2605                 me.onerror(evt);
2606             }
2607
2608             // DONE state
2609             me.readyState = FileWriter.DONE;
2610
2611             // If onwriteend callback
2612             if (typeof me.onwriteend === "function") {
2613                 evt = File._createEvent("writeend", me);
2614                 me.onwriteend(evt);
2615             }
2616         }
2617     );
2618 };
2619
2620 LocalFileSystem = function() {
2621 };
2622
2623 // File error codes
2624 LocalFileSystem.TEMPORARY = 0;
2625 LocalFileSystem.PERSISTENT = 1;
2626 LocalFileSystem.RESOURCE = 2;
2627 LocalFileSystem.APPLICATION = 3;
2628
2629 /**
2630  * Requests a filesystem in which to store application data.
2631  * 
2632  * @param {int} type of file system being requested
2633  * @param {Function} successCallback is called with the new FileSystem
2634  * @param {Function} errorCallback is called with a FileError
2635  */
2636 LocalFileSystem.prototype.requestFileSystem = function(type, size, successCallback, errorCallback) {
2637         if (type < 0 || type > 3) {
2638                 if (typeof errorCallback == "function") {
2639                         errorCallback({
2640                                 "code": FileError.SYNTAX_ERR
2641                         });
2642                 }
2643         }
2644         else {
2645                 PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "requestFileSystem", [type, size]);
2646         }
2647 };
2648
2649 /**
2650  * 
2651  * @param {DOMString} uri referring to a local file in a filesystem
2652  * @param {Function} successCallback is called with the new entry
2653  * @param {Function} errorCallback is called with a FileError
2654  */
2655 LocalFileSystem.prototype.resolveLocalFileSystemURI = function(uri, successCallback, errorCallback) {
2656     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "resolveLocalFileSystemURI", [uri]);
2657 };
2658
2659 /**
2660 * This function  is required as we need to convert raw 
2661 * JSON objects into concrete File and Directory objects.  
2662
2663 * @param a JSON Objects that need to be converted to DirectoryEntry or FileEntry objects.
2664 * @returns an entry 
2665 */
2666 LocalFileSystem.prototype._castFS = function(pluginResult) {
2667     var entry = null;
2668     entry = new DirectoryEntry();
2669     entry.isDirectory = pluginResult.message.root.isDirectory;
2670     entry.isFile = pluginResult.message.root.isFile;
2671     entry.name = pluginResult.message.root.name;
2672     entry.fullPath = pluginResult.message.root.fullPath;
2673     pluginResult.message.root = entry;
2674     return pluginResult;    
2675 }
2676
2677 LocalFileSystem.prototype._castEntry = function(pluginResult) {
2678     var entry = null;
2679     if (pluginResult.message.isDirectory) {
2680         entry = new DirectoryEntry();
2681     }
2682     else if (pluginResult.message.isFile) {
2683                 entry = new FileEntry();
2684     }
2685     entry.isDirectory = pluginResult.message.isDirectory;
2686     entry.isFile = pluginResult.message.isFile;
2687     entry.name = pluginResult.message.name;
2688     entry.fullPath = pluginResult.message.fullPath;
2689     pluginResult.message = entry;
2690     return pluginResult;    
2691 }
2692
2693 LocalFileSystem.prototype._castEntries = function(pluginResult) {
2694     var entries = pluginResult.message;
2695         var retVal = []; 
2696         for (i=0; i<entries.length; i++) {
2697                 retVal.push(window.localFileSystem._createEntry(entries[i]));
2698         }
2699     pluginResult.message = retVal;
2700     return pluginResult;    
2701 }
2702
2703 LocalFileSystem.prototype._createEntry = function(castMe) {
2704         var entry = null;
2705     if (castMe.isDirectory) {
2706         entry = new DirectoryEntry();
2707     }
2708     else if (castMe.isFile) {
2709         entry = new FileEntry();
2710     }
2711     entry.isDirectory = castMe.isDirectory;
2712     entry.isFile = castMe.isFile;
2713     entry.name = castMe.name;
2714     entry.fullPath = castMe.fullPath;
2715     return entry;    
2716
2717 }
2718
2719 LocalFileSystem.prototype._castDate = function(pluginResult) {
2720         if (pluginResult.message.modificationTime) {
2721                 var metadataObj = new Metadata();
2722                 
2723             metadataObj.modificationTime = new Date(pluginResult.message.modificationTime);
2724             pluginResult.message = metadataObj;
2725         }
2726         else if (pluginResult.message.lastModifiedDate) {
2727                 var file = new File();
2728         file.size = pluginResult.message.size;
2729         file.type = pluginResult.message.type;
2730         file.name = pluginResult.message.name;
2731         file.fullPath = pluginResult.message.fullPath;
2732                 file.lastModifiedDate = new Date(pluginResult.message.lastModifiedDate);
2733             pluginResult.message = file;                
2734         }
2735
2736     return pluginResult;        
2737 }
2738 LocalFileSystem.prototype._castError = function(pluginResult) {
2739         var fileError = new FileError();
2740         fileError.code = pluginResult.message;
2741         pluginResult.message = fileError;
2742         return pluginResult;
2743 }
2744
2745 /**
2746  * Information about the state of the file or directory
2747  * 
2748  * {Date} modificationTime (readonly)
2749  */
2750 Metadata = function() {
2751     this.modificationTime=null;
2752 };
2753
2754 /**
2755  * Supplies arguments to methods that lookup or create files and directories
2756  * 
2757  * @param {boolean} create file or directory if it doesn't exist 
2758  * @param {boolean} exclusive if true the command will fail if the file or directory exists
2759  */
2760 Flags = function(create, exclusive) {
2761     this.create = create || false;
2762     this.exclusive = exclusive || false;
2763 };
2764
2765 /**
2766  * An interface representing a file system
2767  * 
2768  * {DOMString} name the unique name of the file system (readonly)
2769  * {DirectoryEntry} root directory of the file system (readonly)
2770  */
2771 FileSystem = function() {
2772     this.name = null;
2773     this.root = null;
2774 };
2775
2776 /**
2777  * An interface representing a directory on the file system.
2778  * 
2779  * {boolean} isFile always false (readonly)
2780  * {boolean} isDirectory always true (readonly)
2781  * {DOMString} name of the directory, excluding the path leading to it (readonly)
2782  * {DOMString} fullPath the absolute full path to the directory (readonly)
2783  * {FileSystem} filesystem on which the directory resides (readonly)
2784  */
2785 DirectoryEntry = function() {
2786     this.isFile = false;
2787     this.isDirectory = true;
2788     this.name = null;
2789     this.fullPath = null;
2790     this.filesystem = null;
2791 };
2792
2793 /**
2794  * Copies a directory to a new location
2795  * 
2796  * @param {DirectoryEntry} parent the directory to which to copy the entry
2797  * @param {DOMString} newName the new name of the entry, defaults to the current name
2798  * @param {Function} successCallback is called with the new entry
2799  * @param {Function} errorCallback is called with a FileError
2800  */
2801 DirectoryEntry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) {
2802     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "copyTo", [this.fullPath, parent, newName]);
2803 };
2804
2805 /**
2806  * Looks up the metadata of the entry
2807  * 
2808  * @param {Function} successCallback is called with a Metadata object
2809  * @param {Function} errorCallback is called with a FileError
2810  */
2811 DirectoryEntry.prototype.getMetadata = function(successCallback, errorCallback) {
2812     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "getMetadata", [this.fullPath]);
2813 };
2814
2815 /**
2816  * Gets the parent of the entry
2817  * 
2818  * @param {Function} successCallback is called with a parent entry
2819  * @param {Function} errorCallback is called with a FileError
2820  */
2821 DirectoryEntry.prototype.getParent = function(successCallback, errorCallback) {
2822     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "getParent", [this.fullPath]);
2823 };
2824
2825 /**
2826  * Moves a directory to a new location
2827  * 
2828  * @param {DirectoryEntry} parent the directory to which to move the entry
2829  * @param {DOMString} newName the new name of the entry, defaults to the current name
2830  * @param {Function} successCallback is called with the new entry
2831  * @param {Function} errorCallback is called with a FileError
2832  */
2833 DirectoryEntry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) {
2834     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "moveTo", [this.fullPath, parent, newName]);
2835 };
2836
2837 /**
2838  * Removes the entry
2839  * 
2840  * @param {Function} successCallback is called with no parameters
2841  * @param {Function} errorCallback is called with a FileError
2842  */
2843 DirectoryEntry.prototype.remove = function(successCallback, errorCallback) {
2844     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "remove", [this.fullPath]);
2845 };
2846
2847 /**
2848  * Returns a URI that can be used to identify this entry.
2849  * 
2850  * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI.
2851  * @param {Function} successCallback is called with the new entry
2852  * @param {Function} errorCallback is called with a FileError
2853  */
2854 DirectoryEntry.prototype.toURI = function(mimeType, successCallback, errorCallback) {
2855     return "file://localhost" + this.fullPath;
2856     //PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "toURI", [this.fullPath, mimeType]);
2857 };
2858
2859 /**
2860  * Creates a new DirectoryReader to read entries from this directory
2861  */
2862 DirectoryEntry.prototype.createReader = function(successCallback, errorCallback) {
2863     return new DirectoryReader(this.fullPath);
2864 };
2865
2866 /**
2867  * Creates or looks up a directory
2868  * 
2869  * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory
2870  * @param {Flags} options to create or excluively create the directory
2871  * @param {Function} successCallback is called with the new entry
2872  * @param {Function} errorCallback is called with a FileError
2873  */
2874 DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) {
2875     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "getDirectory", [this.fullPath, path, options]);
2876 };
2877
2878 /**
2879  * Creates or looks up a file
2880  * 
2881  * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file
2882  * @param {Flags} options to create or excluively create the file
2883  * @param {Function} successCallback is called with the new entry
2884  * @param {Function} errorCallback is called with a FileError
2885  */
2886 DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) {
2887     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "getFile", [this.fullPath, path, options]);
2888 };
2889
2890 /**
2891  * Deletes a directory and all of it's contents
2892  * 
2893  * @param {Function} successCallback is called with no parameters
2894  * @param {Function} errorCallback is called with a FileError
2895  */
2896 DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) {
2897     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "removeRecursively", [this.fullPath]);
2898 };
2899
2900 /**
2901  * An interface that lists the files and directories in a directory.
2902  */
2903 DirectoryReader = function(fullPath){
2904         this.fullPath = fullPath || null;    
2905 };
2906
2907 /**
2908  * Returns a list of entries from a directory.
2909  * 
2910  * @param {Function} successCallback is called with a list of entries
2911  * @param {Function} errorCallback is called with a FileError
2912  */
2913 DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) {
2914     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "readEntries", [this.fullPath]);
2915 }
2916  
2917 /**
2918  * An interface representing a directory on the file system.
2919  * 
2920  * {boolean} isFile always true (readonly)
2921  * {boolean} isDirectory always false (readonly)
2922  * {DOMString} name of the file, excluding the path leading to it (readonly)
2923  * {DOMString} fullPath the absolute full path to the file (readonly)
2924  * {FileSystem} filesystem on which the directory resides (readonly)
2925  */
2926 FileEntry = function() {
2927     this.isFile = true;
2928     this.isDirectory = false;
2929     this.name = null;
2930     this.fullPath = null;
2931     this.filesystem = null;
2932 };
2933
2934 /**
2935  * Copies a file to a new location
2936  * 
2937  * @param {DirectoryEntry} parent the directory to which to copy the entry
2938  * @param {DOMString} newName the new name of the entry, defaults to the current name
2939  * @param {Function} successCallback is called with the new entry
2940  * @param {Function} errorCallback is called with a FileError
2941  */
2942 FileEntry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) {
2943     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "copyTo", [this.fullPath, parent, newName]);
2944 };
2945
2946 /**
2947  * Looks up the metadata of the entry
2948  * 
2949  * @param {Function} successCallback is called with a Metadata object
2950  * @param {Function} errorCallback is called with a FileError
2951  */
2952 FileEntry.prototype.getMetadata = function(successCallback, errorCallback) {
2953     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "getMetadata", [this.fullPath]);
2954 };
2955
2956 /**
2957  * Gets the parent of the entry
2958  * 
2959  * @param {Function} successCallback is called with a parent entry
2960  * @param {Function} errorCallback is called with a FileError
2961  */
2962 FileEntry.prototype.getParent = function(successCallback, errorCallback) {
2963     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "getParent", [this.fullPath]);
2964 };
2965
2966 /**
2967  * Moves a directory to a new location
2968  * 
2969  * @param {DirectoryEntry} parent the directory to which to move the entry
2970  * @param {DOMString} newName the new name of the entry, defaults to the current name
2971  * @param {Function} successCallback is called with the new entry
2972  * @param {Function} errorCallback is called with a FileError
2973  */
2974 FileEntry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) {
2975     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "moveTo", [this.fullPath, parent, newName]);
2976 };
2977
2978 /**
2979  * Removes the entry
2980  * 
2981  * @param {Function} successCallback is called with no parameters
2982  * @param {Function} errorCallback is called with a FileError
2983  */
2984 FileEntry.prototype.remove = function(successCallback, errorCallback) {
2985     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "remove", [this.fullPath]);
2986 };
2987
2988 /**
2989  * Returns a URI that can be used to identify this entry.
2990  * 
2991  * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI.
2992  * @param {Function} successCallback is called with the new entry
2993  * @param {Function} errorCallback is called with a FileError
2994  */
2995 FileEntry.prototype.toURI = function(mimeType, successCallback, errorCallback) {
2996     return "file://localhost" + this.fullPath;
2997     //PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "toURI", [this.fullPath, mimeType]);
2998 };
2999
3000 /**
3001  * Creates a new FileWriter associated with the file that this FileEntry represents.
3002  * 
3003  * @param {Function} successCallback is called with the new FileWriter
3004  * @param {Function} errorCallback is called with a FileError
3005  */
3006 FileEntry.prototype.createWriter = function(successCallback, errorCallback) {
3007         this.file(function(filePointer) {       
3008                 var writer = new FileWriter(filePointer);
3009                 if (writer.fileName == null || writer.fileName == "") {
3010                         if (typeof errorCallback == "function") {
3011                                 errorCallback({
3012                                         "code": FileError.INVALID_STATE_ERR
3013                                 });
3014                 }
3015                 }
3016                 if (typeof successCallback == "function") {
3017                         successCallback(writer);
3018                 }       
3019         }, errorCallback);
3020 };
3021
3022 /**
3023  * Returns a File that represents the current state of the file that this FileEntry represents.
3024  * 
3025  * @param {Function} successCallback is called with the new File object
3026  * @param {Function} errorCallback is called with a FileError
3027  */
3028 FileEntry.prototype.file = function(successCallback, errorCallback) {
3029     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.file", "getFileMetadata", [this.fullPath]);
3030 };
3031
3032 /**
3033  * Add the FileSystem interface into the browser.
3034  */
3035 PhoneGap.addConstructor(function() {
3036         var pgLocalFileSystem = new LocalFileSystem();
3037         // Needed for cast methods
3038     if(typeof window.localFileSystem == "undefined") window.localFileSystem  = pgLocalFileSystem;
3039     if(typeof window.requestFileSystem == "undefined") window.requestFileSystem  = pgLocalFileSystem.requestFileSystem;
3040     if(typeof window.resolveLocalFileSystemURI == "undefined") window.resolveLocalFileSystemURI = pgLocalFileSystem.resolveLocalFileSystemURI;
3041 });
3042 };
3043
3044
3045
3046
3047 /*
3048  * PhoneGap is available under *either* the terms of the modified BSD license *or* the
3049  * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
3050  *  
3051  * Copyright (c) 2005-2011, Nitobi Software Inc.
3052  * Copyright (c) 2011, Matt Kane
3053  */
3054
3055 if (!PhoneGap.hasResource("filetransfer")) {
3056         PhoneGap.addResource("filetransfer");
3057
3058 /**
3059  * FileTransfer uploads a file to a remote server.
3060  */
3061 FileTransfer = function() {}
3062
3063 /**
3064  * FileUploadResult
3065  */
3066 FileUploadResult = function() {
3067     this.bytesSent = 0;
3068     this.responseCode = null;
3069     this.response = null;
3070 }
3071
3072 /**
3073  * FileTransferError
3074  */
3075 FileTransferError = function(errorCode) {
3076     this.code = errorCode || null;
3077 }
3078
3079 FileTransferError.FILE_NOT_FOUND_ERR = 1;
3080 FileTransferError.INVALID_URL_ERR = 2;
3081 FileTransferError.CONNECTION_ERR = 3;
3082
3083 /**
3084 * Given an absolute file path, uploads a file on the device to a remote server 
3085 * using a multipart HTTP request.
3086 * @param filePath {String}           Full path of the file on the device
3087 * @param server {String}             URL of the server to receive the file
3088 * @param successCallback (Function}  Callback to be invoked when upload has completed
3089 * @param errorCallback {Function}    Callback to be invoked upon error
3090 * @param options {FileUploadOptions} Optional parameters such as file name and mimetype           
3091 */
3092 FileTransfer.prototype.upload = function(filePath, server, successCallback, errorCallback, options) {
3093         if(!options.params) {
3094                 options.params = {};
3095         }
3096         options.filePath = filePath;
3097         options.server = server;
3098         if(!options.fileKey) {
3099                 options.fileKey = 'file';
3100         }
3101         if(!options.fileName) {
3102                 options.fileName = 'image.jpg';
3103         }
3104         if(!options.mimeType) {
3105                 options.mimeType = 'image/jpeg';
3106         }
3107         
3108         // successCallback required
3109         if (typeof successCallback != "function") {
3110         console.log("FileTransfer Error: successCallback is not a function");
3111         return;
3112     }
3113
3114
3115     // errorCallback optional
3116     if (errorCallback && (typeof errorCallback != "function")) {
3117         console.log("FileTransfer Error: errorCallback is not a function");
3118         return;
3119     }
3120         
3121     PhoneGap.exec(successCallback, errorCallback, 'com.phonegap.filetransfer', 'upload', [options]);
3122 };
3123
3124 FileTransfer.prototype._castTransferError = function(pluginResult) {
3125         var fileError = new FileTransferError(pluginResult.message);
3126         //fileError.code = pluginResult.message;
3127         pluginResult.message = fileError;
3128         return pluginResult;
3129 }
3130
3131 FileTransfer.prototype._castUploadResult = function(pluginResult) {
3132         var result = new FileUploadResult();
3133         result.bytesSent = pluginResult.message.bytesSent;
3134         result.responseCode = pluginResult.message.responseCode;
3135         result.response = decodeURIComponent(pluginResult.message.response);
3136         pluginResult.message = result;
3137         return pluginResult;
3138 }
3139
3140 /**
3141  * Options to customize the HTTP request used to upload files.
3142  * @param fileKey {String}   Name of file request parameter.
3143  * @param fileName {String}  Filename to be used by the server. Defaults to image.jpg.
3144  * @param mimeType {String}  Mimetype of the uploaded file. Defaults to image/jpeg.
3145  * @param params {Object}    Object with key: value params to send to the server.
3146  */
3147 FileUploadOptions = function(fileKey, fileName, mimeType, params) {
3148     this.fileKey = fileKey || null;
3149     this.fileName = fileName || null;
3150     this.mimeType = mimeType || null;
3151     this.params = params || null;
3152 }
3153
3154
3155 PhoneGap.addConstructor(function() {
3156     if (typeof navigator.fileTransfer == "undefined") navigator.fileTransfer = new FileTransfer();
3157 });
3158 };
3159 if (!PhoneGap.hasResource("geolocation")) {
3160         PhoneGap.addResource("geolocation");
3161
3162 /**
3163  * This class provides access to device GPS data.
3164  * @constructor
3165  */
3166 Geolocation = function() {
3167     // The last known GPS position.
3168     this.lastPosition = null;
3169     this.listener = null;
3170     this.timeoutTimerId = 0;
3171
3172 };
3173
3174
3175 /**
3176  * Asynchronously aquires the current position.
3177  * @param {Function} successCallback The function to call when the position
3178  * data is available
3179  * @param {Function} errorCallback The function to call when there is an error 
3180  * getting the position data.
3181  * @param {PositionOptions} options The options for getting the position data
3182  * such as timeout.
3183  * PositionOptions.forcePrompt:Bool default false, 
3184  * - tells iPhone to prompt the user to turn on location services.
3185  * - may cause your app to exit while the user is sent to the Settings app
3186  * PositionOptions.distanceFilter:double aka Number
3187  * - used to represent a distance in meters.
3188 PositionOptions
3189 {
3190    desiredAccuracy:Number
3191    - a distance in meters 
3192                 < 10   = best accuracy  ( Default value )
3193                 < 100  = Nearest Ten Meters
3194                 < 1000 = Nearest Hundred Meters
3195                 < 3000 = Accuracy Kilometers
3196                 3000+  = Accuracy 3 Kilometers
3197                 
3198         forcePrompt:Boolean default false ( iPhone Only! )
3199     - tells iPhone to prompt the user to turn on location services.
3200         - may cause your app to exit while the user is sent to the Settings app
3201         
3202         distanceFilter:Number
3203         - The minimum distance (measured in meters) a device must move laterally before an update event is generated.
3204         - measured relative to the previously delivered location
3205         - default value: null ( all movements will be reported )
3206         
3207 }
3208
3209  */
3210  
3211 Geolocation.prototype.getCurrentPosition = function(successCallback, errorCallback, options) 
3212 {
3213     // create an always valid local success callback
3214     var win = successCallback;
3215     if (!win || typeof(win) != 'function')
3216     {
3217         win = function(position) {};
3218     }
3219     
3220     // create an always valid local error callback
3221     var fail = errorCallback;
3222     if (!fail || typeof(fail) != 'function')
3223     {
3224         fail = function(positionError) {};
3225     }   
3226
3227     var self = this;
3228     var totalTime = 0;
3229         var timeoutTimerId;
3230         
3231         // set params to our default values
3232         var params = new PositionOptions();
3233         
3234     if (options) 
3235     {
3236         if (options.maximumAge) 
3237         {
3238             // special case here if we have a cached value that is younger than maximumAge
3239             if(this.lastPosition)
3240             {
3241                 var now = new Date().getTime();
3242                 if((now - this.lastPosition.timestamp) < options.maximumAge)
3243                 {
3244                     win(this.lastPosition); // send cached position immediately 
3245                     return;                 // Note, execution stops here -jm
3246                 }
3247             }
3248             params.maximumAge = options.maximumAge;
3249         }
3250         if (options.enableHighAccuracy) 
3251         {
3252             params.enableHighAccuracy = (options.enableHighAccuracy == true); // make sure it's truthy
3253         }
3254         if (options.timeout) 
3255         {
3256             params.timeout = options.timeout;
3257         }
3258     }
3259     
3260     this.listener = {"success":win,"fail":fail};
3261     this.start(params);
3262         
3263         var onTimeout = function()
3264         {
3265             self.setError(new PositionError(PositionError.TIMEOUT,"Geolocation Error: Timeout."));
3266         };
3267
3268     clearTimeout(this.timeoutTimerId);
3269     this.timeoutTimerId = setTimeout(onTimeout, params.timeout); 
3270 };
3271
3272 /**
3273  * Asynchronously aquires the position repeatedly at a given interval.
3274  * @param {Function} successCallback The function to call each time the position
3275  * data is available
3276  * @param {Function} errorCallback The function to call when there is an error 
3277  * getting the position data.
3278  * @param {PositionOptions} options The options for getting the position data
3279  * such as timeout and the frequency of the watch.
3280  */
3281 Geolocation.prototype.watchPosition = function(successCallback, errorCallback, options) {
3282         // Invoke the appropriate callback with a new Position object every time the implementation 
3283         // determines that the position of the hosting device has changed. 
3284
3285         var self = this; // those == this & that
3286         
3287         var params = new PositionOptions();
3288
3289     if(options)
3290     {
3291         if (options.maximumAge) {
3292             params.maximumAge = options.maximumAge;
3293         }
3294         if (options.enableHighAccuracy) {
3295             params.enableHighAccuracy = options.enableHighAccuracy;
3296         }
3297         if (options.timeout) {
3298             params.timeout = options.timeout;
3299         }
3300     }
3301
3302         var that = this;
3303     var lastPos = that.lastPosition? that.lastPosition.clone() : null;
3304     
3305         var intervalFunction = function() {
3306         
3307                 var filterFun = function(position) {
3308             if (lastPos == null || !position.equals(lastPos)) {
3309                 // only call the success callback when there is a change in position, per W3C
3310                 successCallback(position);
3311             }
3312             
3313             // clone the new position, save it as our last position (internal var)
3314             lastPos = position.clone();
3315         };
3316                 
3317                 that.getCurrentPosition(filterFun, errorCallback, params);
3318         };
3319         
3320     // Retrieve location immediately and schedule next retrieval afterwards
3321         intervalFunction();
3322         
3323         return setInterval(intervalFunction, params.timeout);
3324 };
3325
3326
3327 /**
3328  * Clears the specified position watch.
3329  * @param {String} watchId The ID of the watch returned from #watchPosition.
3330  */
3331 Geolocation.prototype.clearWatch = function(watchId) {
3332         clearInterval(watchId);
3333 };
3334
3335 /**
3336  * Called by the geolocation framework when the current location is found.
3337  * @param {PositionOptions} position The current position.
3338  */
3339 Geolocation.prototype.setLocation = function(position) 
3340 {
3341     var _position = new Position(position.coords, position.timestamp);
3342
3343     if(this.timeoutTimerId)
3344     {
3345         clearTimeout(this.timeoutTimerId);
3346         this.timeoutTimerId = 0;
3347     }
3348     
3349         this.lastError = null;
3350     this.lastPosition = _position;
3351     
3352     if(this.listener && typeof(this.listener.success) == 'function')
3353     {
3354         this.listener.success(_position);
3355     }
3356     
3357     this.listener = null;
3358 };
3359
3360 /**
3361  * Called by the geolocation framework when an error occurs while looking up the current position.
3362  * @param {String} message The text of the error message.
3363  */
3364 Geolocation.prototype.setError = function(error) 
3365 {
3366         var _error = new PositionError(error.code, error.message);
3367         
3368     if(this.timeoutTimerId)
3369     {
3370         clearTimeout(this.timeoutTimerId);
3371         this.timeoutTimerId = 0;
3372     }
3373     
3374     this.lastError = _error;
3375     // call error handlers directly
3376     if(this.listener && typeof(this.listener.fail) == 'function')
3377     {
3378         this.listener.fail(_error);
3379     }
3380     this.listener = null;
3381
3382 };
3383
3384 Geolocation.prototype.start = function(positionOptions) 
3385 {
3386     PhoneGap.exec(null, null, "com.phonegap.geolocation", "startLocation", [positionOptions]);
3387
3388 };
3389
3390 Geolocation.prototype.stop = function() 
3391 {
3392     PhoneGap.exec(null, null, "com.phonegap.geolocation", "stopLocation", []);
3393 };
3394
3395
3396 PhoneGap.addConstructor(function() 
3397 {
3398     if (typeof navigator._geo == "undefined") 
3399     {
3400         // replace origObj's functions ( listed in funkList ) with the same method name on proxyObj
3401         // this is a workaround to prevent UIWebView/MobileSafari default implementation of GeoLocation
3402         // because it includes the full page path as the title of the alert prompt
3403         var __proxyObj = function (origObj,proxyObj,funkList)
3404         {
3405             var replaceFunk = function(org,proxy,fName)
3406             { 
3407                 org[fName] = function()
3408                 { 
3409                    return proxy[fName].apply(proxy,arguments); 
3410                 }; 
3411             };
3412
3413             for(var v in funkList) { replaceFunk(origObj,proxyObj,funkList[v]);}
3414         }
3415         navigator._geo = new Geolocation();
3416         __proxyObj(navigator.geolocation, navigator._geo,
3417                  ["setLocation","getCurrentPosition","watchPosition",
3418                   "clearWatch","setError","start","stop"]);
3419
3420     }
3421
3422 });
3423 };
3424 if (!PhoneGap.hasResource("compass")) {
3425         PhoneGap.addResource("compass");
3426
3427 CompassError = function(){
3428    this.code = null;
3429 };
3430
3431 // Capture error codes
3432 CompassError.COMPASS_INTERNAL_ERR = 0;
3433 CompassError.COMPASS_NOT_SUPPORTED = 20;
3434
3435 CompassHeading = function() {
3436         this.magneticHeading = null;
3437         this.trueHeading = null;
3438         this.headingAccuracy = null;
3439         this.timestamp = null;
3440 }       
3441 /**
3442  * This class provides access to device Compass data.
3443  * @constructor
3444  */
3445 Compass = function() {
3446     /**
3447      * List of compass watch timers
3448      */
3449     this.timers = {};
3450 };
3451
3452 /**
3453  * Asynchronously acquires the current heading.
3454  * @param {Function} successCallback The function to call when the heading
3455  * data is available
3456  * @param {Function} errorCallback The function to call when there is an error 
3457  * getting the heading data.
3458  * @param {PositionOptions} options The options for getting the heading data (not used).
3459  */
3460 Compass.prototype.getCurrentHeading = function(successCallback, errorCallback, options) {
3461         // successCallback required
3462     if (typeof successCallback !== "function") {
3463         console.log("Compass Error: successCallback is not a function");
3464         return;
3465     }
3466
3467     // errorCallback optional
3468     if (errorCallback && (typeof errorCallback !== "function")) {
3469         console.log("Compass Error: errorCallback is not a function");
3470         return;
3471     }
3472
3473     // Get heading
3474     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.geolocation", "getCurrentHeading", []);
3475 };
3476
3477 /**
3478  * Asynchronously acquires the heading repeatedly at a given interval.
3479  * @param {Function} successCallback The function to call each time the heading
3480  * data is available
3481  * @param {Function} errorCallback The function to call when there is an error 
3482  * getting the heading data.
3483  * @param {HeadingOptions} options The options for getting the heading data
3484  * such as timeout and the frequency of the watch.
3485  */
3486 Compass.prototype.watchHeading= function(successCallback, errorCallback, options) 
3487 {
3488         // Default interval (100 msec)
3489     var frequency = (options !== undefined) ? options.frequency : 100;
3490
3491     // successCallback required
3492     if (typeof successCallback !== "function") {
3493         console.log("Compass Error: successCallback is not a function");
3494         return;
3495     }
3496
3497     // errorCallback optional
3498     if (errorCallback && (typeof errorCallback !== "function")) {
3499         console.log("Compass Error: errorCallback is not a function");
3500         return;
3501     }
3502
3503     // Start watch timer to get headings
3504     var id = PhoneGap.createUUID();
3505     navigator.compass.timers[id] = setInterval(
3506         function() {
3507             PhoneGap.exec(successCallback, errorCallback, "com.phonegap.geolocation", "getCurrentHeading", [{repeats: 1}]);
3508         }, frequency);
3509
3510     return id;
3511 };
3512
3513
3514 /**
3515  * Clears the specified heading watch.
3516  * @param {String} watchId The ID of the watch returned from #watchHeading.
3517  */
3518 Compass.prototype.clearWatch = function(id) 
3519 {
3520         // Stop javascript timer & remove from timer list
3521     if (id && navigator.compass.timers[id]) {
3522         clearInterval(navigator.compass.timers[id]);
3523         delete navigator.compass.timers[id];
3524     }
3525     if (navigator.compass.timers.length == 0) {
3526         // stop the 
3527         PhoneGap.exec(null, null, "com.phonegap.geolocation", "stopHeading", []);
3528     }
3529 };
3530
3531 /** iOS only
3532  * Asynchronously fires when the heading changes from the last reading.  The amount of distance 
3533  * required to trigger the event is specified in the filter paramter.
3534  * @param {Function} successCallback The function to call each time the heading
3535  * data is available
3536  * @param {Function} errorCallback The function to call when there is an error 
3537  * getting the heading data.
3538  * @param {HeadingOptions} options The options for getting the heading data
3539  *                      @param {filter} number of degrees change to trigger a callback with heading data (float)
3540  *
3541  * In iOS this function is more efficient than calling watchHeading  with a frequency for updates.
3542  * Only one watchHeadingFilter can be in effect at one time.  If a watchHeadingFilter is in effect, calling
3543  * getCurrentHeading or watchHeading will use the existing filter value for specifying heading change. 
3544   */
3545 Compass.prototype.watchHeadingFilter = function(successCallback, errorCallback, options) 
3546 {
3547  
3548         if (options === undefined || options.filter === undefined) {
3549                 console.log("Compass Error:  options.filter not specified");
3550                 return;
3551         }
3552
3553     // successCallback required
3554     if (typeof successCallback !== "function") {
3555         console.log("Compass Error: successCallback is not a function");
3556         return;
3557     }
3558
3559     // errorCallback optional
3560     if (errorCallback && (typeof errorCallback !== "function")) {
3561         console.log("Compass Error: errorCallback is not a function");
3562         return;
3563     }
3564     PhoneGap.exec(successCallback, errorCallback, "com.phonegap.geolocation", "watchHeadingFilter", [options]);
3565 }
3566 Compass.prototype.clearWatchFilter = function() 
3567 {
3568         PhoneGap.exec(null, null, "com.phonegap.geolocation", "stopHeading", []);
3569 };
3570
3571 PhoneGap.addConstructor(function() 
3572 {
3573     if (typeof navigator.compass == "undefined") 
3574     {
3575         navigator.compass = new Compass();
3576     }
3577 });
3578 };
3579
3580 if (!PhoneGap.hasResource("media")) {
3581         PhoneGap.addResource("media");
3582
3583 /*
3584  * PhoneGap is available under *either* the terms of the modified BSD license *or* the
3585  * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
3586  *
3587  * Copyright (c) 2005-2010, Nitobi Software Inc.
3588  * Copyright (c) 2010,2011 IBM Corporation
3589  */
3590
3591 /**
3592  * List of media objects.
3593  * PRIVATE
3594  */
3595 PhoneGap.mediaObjects = {};
3596
3597 /**
3598  * Object that receives native callbacks.
3599  * PRIVATE
3600  */
3601 PhoneGap.Media = function() {};
3602
3603
3604 /**
3605  * Get the media object.
3606  * PRIVATE
3607  *
3608  * @param id            The media object id (string)
3609  */
3610 PhoneGap.Media.getMediaObject = function(id) {
3611     return PhoneGap.mediaObjects[id];
3612 };
3613
3614 /**
3615  * Audio has status update.
3616  * PRIVATE
3617  *
3618  * @param id            The media object id (string)
3619  * @param msg           The status message (int)
3620  * @param value        The status code (int)
3621  */
3622 PhoneGap.Media.onStatus = function(id, msg, value) {
3623     var media = PhoneGap.mediaObjects[id];
3624
3625     // If state update
3626     if (msg == Media.MEDIA_STATE) {
3627         if (value == Media.MEDIA_STOPPED) {
3628             if (media.successCallback) {
3629                 media.successCallback();
3630             }
3631         }
3632         if (media.statusCallback) {
3633             media.statusCallback(value);
3634         }
3635     }
3636     else if (msg == Media.MEDIA_DURATION) {
3637         media._duration = value;
3638     }
3639     else if (msg == Media.MEDIA_ERROR) {
3640         if (media.errorCallback) {
3641             media.errorCallback(value);
3642         }
3643     }
3644     else if (msg == Media.MEDIA_POSITION) {
3645         media._position = value;
3646     }
3647 };
3648
3649 /**
3650  * This class provides access to the device media, interfaces to both sound and video
3651  *
3652  * @param src                   The file name or url to play
3653  * @param successCallback       The callback to be called when the file is done playing or recording.
3654  *                                  successCallback() - OPTIONAL
3655  * @param errorCallback         The callback to be called if there is an error.
3656  *                                  errorCallback(int errorCode) - OPTIONAL
3657  * @param statusCallback        The callback to be called when media status has changed.
3658  *                                  statusCallback(int statusCode) - OPTIONAL
3659  * @param positionCallback      The callback to be called when media position has changed.
3660  *                                  positionCallback(long position) - OPTIONAL
3661  */
3662 Media = function(src, successCallback, errorCallback, statusCallback, positionCallback) {
3663
3664     // successCallback optional
3665     if (successCallback && (typeof successCallback != "function")) {
3666         console.log("Media Error: successCallback is not a function");
3667         return;
3668     }
3669
3670     // errorCallback optional
3671     if (errorCallback && (typeof errorCallback != "function")) {
3672         console.log("Media Error: errorCallback is not a function");
3673         return;
3674     }
3675
3676     // statusCallback optional
3677     if (statusCallback && (typeof statusCallback != "function")) {
3678         console.log("Media Error: statusCallback is not a function");
3679         return;
3680     }
3681
3682     // positionCallback optional -- NOT SUPPORTED
3683     if (positionCallback && (typeof positionCallback != "function")) {
3684         console.log("Media Error: positionCallback is not a function");
3685         return;
3686     }
3687
3688     this.id = PhoneGap.createUUID();
3689     PhoneGap.mediaObjects[this.id] = this;
3690     this.src = src;
3691     this.successCallback = successCallback;
3692     this.errorCallback = errorCallback;
3693     this.statusCallback = statusCallback;
3694     this.positionCallback = positionCallback;
3695     this._duration = -1;
3696     this._position = -1;
3697 };
3698
3699 // Media messages
3700 Media.MEDIA_STATE = 1;
3701 Media.MEDIA_DURATION = 2;
3702 Media.MEDIA_POSITION = 3;
3703 Media.MEDIA_ERROR = 9;
3704
3705 // Media states
3706 Media.MEDIA_NONE = 0;
3707 Media.MEDIA_STARTING = 1;
3708 Media.MEDIA_RUNNING = 2;
3709 Media.MEDIA_PAUSED = 3;
3710 Media.MEDIA_STOPPED = 4;
3711 Media.MEDIA_MSG = ["None", "Starting", "Running", "Paused", "Stopped"];
3712
3713 // TODO: Will MediaError be used?
3714 /**
3715  * This class contains information about any Media errors.
3716  * @constructor
3717  */
3718
3719 MediaError = function() {
3720         this.code = null,
3721         this.message = "";
3722 }
3723
3724
3725 MediaError.MEDIA_ERR_ABORTED        = 1;
3726 MediaError.MEDIA_ERR_NETWORK        = 2;
3727 MediaError.MEDIA_ERR_DECODE         = 3;
3728 MediaError.MEDIA_ERR_NONE_SUPPORTED = 4;
3729
3730 /**
3731  * Start or resume playing audio file.
3732  */
3733 Media.prototype.play = function(options) {
3734     PhoneGap.exec(null, null, "com.phonegap.media", "play", [this.id, this.src, options]);
3735 };
3736
3737 /**
3738  * Stop playing audio file.
3739  */
3740 Media.prototype.stop = function() {
3741     PhoneGap.exec(null, null, "com.phonegap.media","stop", [this.id, this.src]);
3742 };
3743
3744 /**
3745  * Pause playing audio file.
3746  */
3747 Media.prototype.pause = function() {
3748     PhoneGap.exec(null, null, "com.phonegap.media","pause", [this.id, this.src]);
3749 };
3750
3751 /**
3752  * Seek or jump to a new time in the track..
3753  */
3754 Media.prototype.seekTo = function(milliseconds) {
3755     PhoneGap.exec(null, null, "com.phonegap.media", "seekTo", [this.id, this.src, milliseconds]);
3756 };
3757
3758 /**
3759  * Get duration of an audio file.
3760  * The duration is only set for audio that is playing, paused or stopped.
3761  *
3762  * @return      duration or -1 if not known.
3763  */
3764 Media.prototype.getDuration = function() {
3765     return this._duration;
3766 };
3767
3768 /**
3769  * Get position of audio.
3770  *
3771  * @return
3772  */
3773 Media.prototype.getCurrentPosition = function(successCB, errorCB) {
3774         var errCallback = (errorCB == undefined || errorCB == null) ? null : errorCB;
3775     PhoneGap.exec(successCB, errorCB, "com.phonegap.media", "getCurrentPosition", [this.id, this.src]);
3776 };
3777
3778 // iOS only.  prepare/load the audio in preparation for playing
3779 Media.prototype.prepare = function(successCB, errorCB) {
3780         PhoneGap.exec(successCB, errorCB, "com.phonegap.media", "prepare", [this.id, this.src]);
3781 }
3782
3783 /**
3784  * Start recording audio file.
3785  */
3786 Media.prototype.startRecord = function() {
3787     PhoneGap.exec(null, null, "com.phonegap.media","startAudioRecord", [this.id, this.src]);
3788 };
3789
3790 /**
3791  * Stop recording audio file.
3792  */
3793 Media.prototype.stopRecord = function() {
3794     PhoneGap.exec(null, null, "com.phonegap.media","stopAudioRecord", [this.id, this.src]);
3795 };
3796
3797 /**
3798  * Release the resources.
3799  */
3800 Media.prototype.release = function() {
3801     PhoneGap.exec(null, null, "com.phonegap.media","release", [this.id, this.src]);
3802 };
3803
3804 };
3805 if (!PhoneGap.hasResource("notification")) {
3806         PhoneGap.addResource("notification");
3807
3808 /**
3809  * This class provides access to notifications on the device.
3810  */
3811 Notification = function() {
3812 };
3813
3814 /**
3815  * Open a native alert dialog, with a customizable title and button text.
3816  *
3817  * @param {String} message              Message to print in the body of the alert
3818  * @param {Function} completeCallback   The callback that is called when user clicks on a button.
3819  * @param {String} title                Title of the alert dialog (default: Alert)
3820  * @param {String} buttonLabel          Label of the close button (default: OK)
3821  */
3822 Notification.prototype.alert = function(message, completeCallback, title, buttonLabel) {
3823     var _title = title;
3824     if (title == null || typeof title === 'undefined') {
3825         _title = "Alert";
3826     }
3827     var _buttonLabel = (buttonLabel || "OK");
3828     PhoneGap.exec(completeCallback, null, "com.phonegap.notification", "alert", [message,{ "title": _title, "buttonLabel": _buttonLabel}]);
3829 };
3830
3831 /**
3832  * Open a native confirm dialog, with a customizable title and button text.
3833  * The result that the user selects is returned to the result callback.
3834  *
3835  * @param {String} message              Message to print in the body of the alert
3836  * @param {Function} resultCallback     The callback that is called when user clicks on a button.
3837  * @param {String} title                Title of the alert dialog (default: Confirm)
3838  * @param {String} buttonLabels         Comma separated list of the labels of the buttons (default: 'OK,Cancel')
3839  */
3840 Notification.prototype.confirm = function(message, resultCallback, title, buttonLabels) {
3841     var _title = (title || "Confirm");
3842     var _buttonLabels = (buttonLabels || "OK,Cancel");
3843     this.alert(message, resultCallback, _title, _buttonLabels);
3844 };
3845
3846 /**
3847  * Causes the device to blink a status LED.
3848  * @param {Integer} count The number of blinks.
3849  * @param {String} colour The colour of the light.
3850  */
3851 Notification.prototype.blink = function(count, colour) {
3852 // NOT IMPLEMENTED      
3853 };
3854
3855 Notification.prototype.vibrate = function(mills) {
3856         PhoneGap.exec(null, null, "com.phonegap.notification", "vibrate", []);
3857 };
3858
3859 Notification.prototype.beep = function(count, volume) {
3860         // No Volume yet for the iphone interface
3861         // We can use a canned beep sound and call that
3862         new Media('beep.wav').play();
3863 };
3864
3865 PhoneGap.addConstructor(function() {
3866     if (typeof navigator.notification == "undefined") navigator.notification = new Notification();
3867 });
3868 };
3869 if (!PhoneGap.hasResource("orientation")) {
3870         PhoneGap.addResource("orientation");
3871
3872 /**
3873  * This class provides access to the device orientation.
3874  * @constructor
3875  */
3876 Orientation  = function() {
3877         /**
3878          * The current orientation, or null if the orientation hasn't changed yet.
3879          */
3880         this.currentOrientation = null;
3881 }
3882
3883 /**
3884  * Set the current orientation of the phone.  This is called from the device automatically.
3885  * 
3886  * When the orientation is changed, the DOMEvent \c orientationChanged is dispatched against
3887  * the document element.  The event has the property \c orientation which can be used to retrieve
3888  * the device's current orientation, in addition to the \c Orientation.currentOrientation class property.
3889  *
3890  * @param {Number} orientation The orientation to be set
3891  */
3892 Orientation.prototype.setOrientation = function(orientation) {
3893     Orientation.currentOrientation = orientation;
3894     var e = document.createEvent('Events');
3895     e.initEvent('orientationChanged', 'false', 'false');
3896     e.orientation = orientation;
3897     document.dispatchEvent(e);
3898 };
3899
3900 /**
3901  * Asynchronously aquires the current orientation.
3902  * @param {Function} successCallback The function to call when the orientation
3903  * is known.
3904  * @param {Function} errorCallback The function to call when there is an error 
3905  * getting the orientation.
3906  */
3907 Orientation.prototype.getCurrentOrientation = function(successCallback, errorCallback) {
3908         // If the position is available then call success
3909         // If the position is not available then call error
3910 };
3911
3912 /**
3913  * Asynchronously aquires the orientation repeatedly at a given interval.
3914  * @param {Function} successCallback The function to call each time the orientation
3915  * data is available.
3916  * @param {Function} errorCallback The function to call when there is an error 
3917  * getting the orientation data.
3918  */
3919 Orientation.prototype.watchOrientation = function(successCallback, errorCallback) {
3920         // Invoke the appropriate callback with a new Position object every time the implementation 
3921         // determines that the position of the hosting device has changed. 
3922         this.getCurrentPosition(successCallback, errorCallback);
3923         return setInterval(function() {
3924                 navigator.orientation.getCurrentOrientation(successCallback, errorCallback);
3925         }, 10000);
3926 };
3927
3928 /**
3929  * Clears the specified orientation watch.
3930  * @param {String} watchId The ID of the watch returned from #watchOrientation.
3931  */
3932 Orientation.prototype.clearWatch = function(watchId) {
3933         clearInterval(watchId);
3934 };
3935
3936 Orientation.install = function()
3937 {
3938     if (typeof navigator.orientation == "undefined") { 
3939                 navigator.orientation = new Orientation();
3940         }
3941         
3942         var windowDispatchAvailable = !(window.dispatchEvent === undefined); // undefined in iOS 3.x
3943         if (windowDispatchAvailable) {
3944                 return;
3945         } 
3946         
3947         // the code below is to capture window.add/remove eventListener calls on window
3948         // this is for iOS 3.x where listening on 'orientationchange' events don't work on document/window (don't know why)
3949         // however, window.onorientationchange DOES handle the 'orientationchange' event (sent through document), so...
3950         // then we multiplex the window.onorientationchange event (consequently - people shouldn't overwrite this)
3951         
3952         var self = this;
3953         var orientationchangeEvent = 'orientationchange';
3954         var newOrientationchangeEvent = 'orientationchange_pg';
3955         
3956         // backup original `window.addEventListener`, `window.removeEventListener`
3957     var _addEventListener = window.addEventListener;
3958     var _removeEventListener = window.removeEventListener;
3959
3960         window.onorientationchange = function() {
3961                 PhoneGap.fireEvent(newOrientationchangeEvent, window);
3962         }
3963         
3964     // override `window.addEventListener`
3965     window.addEventListener = function() {
3966         if (arguments[0] === orientationchangeEvent) {
3967                         arguments[0] = newOrientationchangeEvent; 
3968                 } 
3969                                                                                                         
3970                 if (!windowDispatchAvailable) {
3971                         return document.addEventListener.apply(this, arguments);
3972                 } else {
3973                         return _addEventListener.apply(this, arguments);
3974                 }
3975     };  
3976
3977     // override `window.removeEventListener'
3978     window.removeEventListener = function() {
3979         if (arguments[0] === orientationchangeEvent) {
3980                         arguments[0] = newOrientationchangeEvent; 
3981                 } 
3982                 
3983                 if (!windowDispatchAvailable) {
3984                         return document.removeEventListener.apply(this, arguments);
3985                 } else {
3986                         return _removeEventListener.apply(this, arguments);
3987                 }
3988     };  
3989 };
3990
3991 PhoneGap.addConstructor(Orientation.install);
3992
3993 };
3994 if (!PhoneGap.hasResource("sms")) {
3995         PhoneGap.addResource("sms");
3996
3997 /**
3998  * This class provides access to the device SMS functionality.
3999  * @constructor
4000  */
4001 Sms = function() {
4002
4003 }
4004
4005 /**
4006  * Sends an SMS message.
4007  * @param {Integer} number The phone number to send the message to.
4008  * @param {String} message The contents of the SMS message to send.
4009  * @param {Function} successCallback The function to call when the SMS message is sent.
4010  * @param {Function} errorCallback The function to call when there is an error sending the SMS message.
4011  * @param {PositionOptions} options The options for accessing the GPS location such as timeout and accuracy.
4012  */
4013 Sms.prototype.send = function(number, message, successCallback, errorCallback, options) {
4014         // not sure why this is here when it does nothing????
4015 };
4016
4017 PhoneGap.addConstructor(function() {
4018     if (typeof navigator.sms == "undefined") navigator.sms = new Sms();
4019 });
4020 };
4021 if (!PhoneGap.hasResource("telephony")) {
4022         PhoneGap.addResource("telephony");
4023
4024 /**
4025  * This class provides access to the telephony features of the device.
4026  * @constructor
4027  */
4028 Telephony = function() {
4029         
4030 }
4031
4032 /**
4033  * Calls the specifed number.
4034  * @param {Integer} number The number to be called.
4035  */
4036 Telephony.prototype.call = function(number) {
4037         // not sure why this is here when it does nothing????
4038 };
4039
4040 PhoneGap.addConstructor(function() {
4041     if (typeof navigator.telephony == "undefined") navigator.telephony = new Telephony();
4042 });
4043 };if (!PhoneGap.hasResource("network")) {
4044         PhoneGap.addResource("network");
4045
4046 // //////////////////////////////////////////////////////////////////
4047
4048 Connection = function() {
4049         /*
4050          * One of the connection constants below.
4051          */
4052         this.type = Connection.UNKNOWN;
4053
4054         /* initialize from the extended DeviceInfo properties */
4055     try {      
4056                 this.type       = DeviceInfo.connection.type;
4057     } 
4058         catch(e) {
4059     }
4060 };
4061
4062 Connection.UNKNOWN = "unknown"; // Unknown connection type
4063 Connection.ETHERNET = "ethernet";
4064 Connection.WIFI = "wifi";
4065 Connection.CELL_2G = "2g"; // the default for iOS, for any cellular connection
4066 Connection.CELL_3G = "3g";
4067 Connection.CELL_4G = "4g";
4068 Connection.NONE = "none"; // NO connectivity
4069
4070
4071 PhoneGap.addConstructor(function() {
4072     if (typeof navigator.network == "undefined") navigator.network = {};
4073     if (typeof navigator.network.connection == "undefined") navigator.network.connection = new Connection();
4074 });
4075
4076 };if (!PhoneGap.hasResource("splashscreen")) {
4077         PhoneGap.addResource("splashscreen");
4078
4079 /**
4080  * This class provides access to the splashscreen
4081  */
4082 SplashScreen = function() {
4083 };
4084
4085 SplashScreen.prototype.show = function() {
4086     PhoneGap.exec(null, null, "com.phonegap.splashscreen", "show", []);
4087 };
4088
4089 SplashScreen.prototype.hide = function() {
4090     PhoneGap.exec(null, null, "com.phonegap.splashscreen", "hide", []);
4091 };
4092
4093 PhoneGap.addConstructor(function() {
4094     if (typeof navigator.splashscreen == "undefined") navigator.splashscreen = new SplashScreen();
4095 });
4096
4097 };