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