1 if (Garmin == undefined) var Garmin = {};
  2 /** Copyright © 2007-2010 Garmin Ltd. or its subsidiaries.
  3  *
  4  * Licensed under the Apache License, Version 2.0 (the 'License')
  5  * you may not use this file except in compliance with the License.
  6  * You may obtain a copy of the License at
  7  *
  8  *    http://www.apache.org/licenses/LICENSE-2.0
  9  *
 10  * Unless required by applicable law or agreed to in writing, software
 11  * distributed under the License is distributed on an 'AS IS' BASIS,
 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  * See the License for the specific language governing permissions and
 14  * limitations under the License.
 15  * 
 16  * @fileoverview Garmin.DeviceControl A high-level JavaScript API which supports listener and callback functionality.
 17  * @version 1.9
 18  */
 19 /** A controller object that can retrieve and send data to a Garmin 
 20  * device.<br><br>
 21  * @class
 22  * A controller object that can retrieve and send data to a Garmin 
 23  * device. Provides listener and callback functionality.<br><br>
 24  * 
 25  * The controller must be unlocked before anything can be done with it.  
 26  * Then you'll have to find a device before you can start to read data from
 27  * and write data to the device.<br><br>
 28  * 
 29  * We use the <a href="http://en.wikipedia.org/wiki/Observer_pattern">observer pattern</a> 
 30  * to handle the asynchronous nature of device communication.  You must register
 31  * your class as a listener to this Object and then implement methods that will 
 32  * get called on certain events.<br><br>
 33  * 
 34  * Events:<br><br>
 35  *     onStartFindDevices called when starting to search for devices.
 36  *       the object returned is {controller: this}<br><br>
 37  *
 38  *     onCancelFindDevices is called when the controller is told to cancel finding
 39  *         devices {controller: this}<br><br>
 40  *
 41  *     onFinishFindDevices called when the devices are found.
 42  *       the object returned is {controller: this}<br><br>
 43  *
 44  *     onException is called when an exception occurs in a method
 45  *         object passed back is {msg: exception}<br><br>
 46  *
 47  *	   onInteractionWithNoDevice is called when the device is lazy loaded, but finds no devices,
 48  * 			yet still attempts a read/write action {controller: this}<br><br>
 49  * 
 50  *     onStartReadFromDevice is called when the controller is about to start
 51  *         reading from the device {controller: this}<br><br>
 52  * 
 53  *     onFinishReadFromDevice is called when the controller is done reading 
 54  *         the device.  the read is either a success or failure, which is 
 55  *         communicated via json.  object passed back contains 
 56  *         {success:this.garminPlugin.GpsTransferSucceeded, controller: this} <br><br>
 57  *
 58  *     onWaitingReadFromDevice is called when the controller is waiting for input
 59  *         from the user about the device.  object passed back contains: 
 60  *         {message: this.garminPlugin.MessageBoxXml, controller: this}<br><br>
 61  *
 62  *     onProgressReadFromDevice is called when the controller is still reading information
 63  *         from the device.  in this case the message is a percent complete/ 
 64  *         {progress: this.getDeviceStatus(), controller: this}<br><br>
 65  *
 66  *     onCancelReadFromDevice is called when the controller is told to cancel reading
 67  *         from the device {controller: this}<br><br>
 68  *
 69  *     onFinishWriteToDevice is called when the controller is done writing to 
 70  *         the device.  the write is either a success or failure, which is 
 71  *         communicated via json.  object passed back contains 
 72  *         {success:this.garminPlugin.GpsTransferSucceeded, controller: this}<br><br>
 73  *
 74  *     onWaitingWriteToDevice is called when the controller is waiting for input
 75  *         from the user about the device.  object passed back contains: 
 76  *         {message: this.garminPlugin.MessageBoxXml, controller: this}<br><br>
 77  *
 78  *     onProgressWriteToDevice is called when the controller is still writing information
 79  *         to the device.  in this case the message is a percent complete/ 
 80  *         {progress: this.getDeviceStatus(), controller: this}<br><br>
 81  *
 82  *     onCancelWriteToDevice is called when the controller is told to cancel writing
 83  *         to the device {controller: this}<br><br>
 84  *
 85  * @constructor 
 86  *
 87  * @requires Prototype
 88  * @requires BrowserDetect
 89  * @requires Garmin.DevicePlugin
 90  * @requires Garmin.Broadcaster
 91  * @requires Garmin.XmlConverter
 92  */
 93 Garmin.DeviceControl = function(){}; //just here for jsdoc
 94 Garmin.DeviceControl = Class.create();
 95 Garmin.DeviceControl.prototype = {
 96 
 97 
 98 	/////////////////////// Initialization Code ///////////////////////	
 99 
100     /** Instantiates a Garmin.DeviceControl object, but does not unlock/activate plugin.
101      */
102 	initialize: function() {
103 		
104 		this.pluginUnlocked = false;
105 		this.dirInterval = null;
106 		//keep state when doing multi-type file listings
107 		this.fileListingOptions = null;
108 		this.fileListingIndex = 0;
109 
110 		try {
111 			if (typeof(Garmin.DevicePlugin) == 'undefined') throw '';
112 		} catch(e) {
113 			throw new Error(Garmin.DeviceControl.MESSAGES.deviceControlMissing);
114 		};
115 
116     	// check that the browser is supported
117      	if(!BrowserSupport.isBrowserSupported()) {
118     	    var notSupported = new Error(Garmin.DeviceControl.MESSAGES.browserNotSupported);
119     	    notSupported.name = "BrowserNotSupportedException";
120     	    throw notSupported;
121         }
122 		
123 		// make sure the browser has the plugin installed
124 		if (!PluginDetect.detectGarminCommunicatorPlugin()) {
125      	    var notInstalled = new Error(Garmin.DeviceControl.MESSAGES.pluginNotInstalled);
126     	    notInstalled.name = "PluginNotInstalledException";
127     	    throw notInstalled;			
128 		}
129 				
130 		// grab the plugin object on the page
131 		var pluginElement;
132 		if( window.ActiveXObject ) { // IE
133 			pluginElement = $("GarminActiveXControl");
134 		} else { // FireFox
135 			pluginElement = $("GarminNetscapePlugin");
136 		}
137 		
138 		// make sure the plugin object exists on the page
139 		if (pluginElement == null) {
140 			var error = new Error(Garmin.DeviceControl.MESSAGES.missingPluginTag);
141 			error.name = "HtmlTagNotFoundException";
142 			throw error;			
143 		}
144 		
145 		// instantiate a garmin plugin
146 		this.garminPlugin = new Garmin.DevicePlugin(pluginElement);
147 		 
148 		// validate the garmin plugin
149 		this.validatePlugin();
150 		
151 		// instantiate a broacaster
152 		this._broadcaster = new Garmin.Broadcaster();
153 
154 		this.getDetailedDeviceData = true;
155 		this.devices = new Array();
156 		this.deviceNumber = null;
157 		this.numDevices = 0;
158 
159 		this.gpsData = null;
160 		this.gpsDataType = null; //used by both read and write methods to track data context
161 		this.gpsDataString = "";
162 		this.gpsDataStringCompressed = "";  // Compresed version of gpsDataString.  gzip compressed and base 64 expanded.
163 	},
164 
165 	/** Checks plugin validity: browser support, installation and required version.
166 	 * @private
167      * @throws BrowserNotSupportedException
168      * @throws PluginNotInstalledException
169      * @throws OutOfDatePluginException
170      */
171     validatePlugin: function() {
172 		if (!this.isPluginInstalled()) {
173      	    var notInstalled = new Error(Garmin.DeviceControl.MESSAGES.pluginNotInstalled);
174     	    notInstalled.name = "PluginNotInstalledException";
175     	    throw notInstalled;
176         }
177 		if(this.garminPlugin.isPluginOutOfDate()) {
178     	    var outOfDate = new Error(Garmin.DeviceControl.MESSAGES.outOfDatePlugin1+Garmin.DevicePlugin.REQUIRED_VERSION.toString()+Garmin.DeviceControl.MESSAGES.outOfDatePlugin2+this.getPluginVersionString());
179     	    outOfDate.name = "OutOfDatePluginException";
180     	    outOfDate.version = this.getPluginVersionString();
181     	    throw outOfDate;
182         }
183     },
184     
185     /** Checks plugin for updates.  Throws an exception if the user's plugin version is
186      * older than the one set by the API.
187      * 
188      * Plugin updates are not required so use this function with caution.
189      * @see #setPluginLatestVersion
190      */
191     checkForUpdates: function() {
192     	if(this.garminPlugin.isUpdateAvailable()) {
193     		var notLatest = new Error(Garmin.DeviceControl.MESSAGES.updatePlugin1+Garmin.DevicePlugin.LATEST_VERSION.toString()+Garmin.DeviceControl.MESSAGES.updatePlugin2+this.getPluginVersionString());
194     	    notLatest.name = "UpdatePluginException";
195     	    notLatest.version = this.getPluginVersionString();
196     	    throw notLatest;
197     	}
198     },
199     
200 	/////////////////////// Device Handling Methods ///////////////////////	
201 
202 	/** Finds any connected Garmin Devices.  
203      * When it's done finding the devices, onFinishFindDevices is dispatched <br/>
204      * <br/>
205      * this.numDevices = the number of devices found<br/>
206      * this.deviceNumber is the device that we'll use to communicate with<br/>
207      * <br/>
208      * Use this.getDevices() to get an array of the found devices and 
209      * this.setDeviceNumber({Number}) to change the device. <br/>
210      * <br/>
211      * Minimum Plugin version 2.0.0.4
212      * 
213      * @see #getDevices
214      * @see #setDeviceNumber
215      */	
216 	findDevices: function() {
217 		if (!this.isUnlocked())
218 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
219         this.garminPlugin.startFindDevices();
220 	    this._broadcaster.dispatch("onStartFindDevices", {controller: this});
221         setTimeout(function() { this._finishFindDevices() }.bind(this), 1000);
222 	},
223 
224 	/** Cancels the current find devices interaction. <br/>
225 	 * <br/>
226 	 * Minimum Plugin version 2.0.0.4
227      */	
228 	cancelFindDevices: function() {
229 		this.garminPlugin.cancelFindDevices();
230     	this._broadcaster.dispatch("onCancelFindDevices", {controller: this});
231 	},
232 
233 	/** Loads device data into devices array.
234 	 * 
235 	 * Minimum Plugin version 2.0.0.4
236 	 * 
237 	 * @private
238      */	
239 	_finishFindDevices: function() {
240     	try {
241         	if(this.garminPlugin.finishFindDevices()) {
242                 this.devices = Garmin.PluginUtils.parseDeviceXml(this.garminPlugin, this.getDetailedDeviceData);
243                 this.numDevices = this.devices.length;
244            		this.deviceNumber = 0;
245     	        this._broadcaster.dispatch("onFinishFindDevices", {controller: this});
246         	} else {
247         		setTimeout(function() { this._finishFindDevices() }.bind(this), 500);
248         	}
249 		} catch(e) {
250 		    this._reportException(e);
251         }        
252 	},
253 
254 	/** Sets the deviceNumber variable which determines which connected device to talk to.
255      * @param {Number} deviceNumber The device number
256      */	
257 	setDeviceNumber: function(deviceNumber) {
258 		this.deviceNumber = deviceNumber;
259 	},
260 	
261 	/**
262 	 * Get the device number of the connected device to communicate with (multiple devices may
263 	 * be connected simultaneously, but the plugin only transfers data with one at a time).
264 	 * @return the device number (assigned by the plugin) determining which connected device
265 	 * to talk to.
266 	 */
267 	getDeviceNumber: function() {
268         return this.deviceNumber;
269 	},
270 
271 	/** Get a list of the devices found
272      * @type Array<Garmin.Device>
273      */	
274 	getDevices: function() {
275 		return this.devices;
276 	},
277 	
278 	/** Returns the DeviceXML of the current device, as a string.
279 	 */
280 	getCurrentDeviceXml: function() {
281 		return this.garminPlugin.getDeviceDescriptionXml(this.deviceNumber);		
282 	},
283 	
284 	/** Returns the FIT Directory XML of the current device, as a string.
285 	 * @private
286      * @returns {String}
287 	 */
288 	getCurrentDeviceFitDirectoryXml: function() {
289 		try
290 		{
291 			this.garminPlugin.startReadFitDirectory(this.deviceNumber);
292 			this.waitForReadToFinish();
293 			this.pause(1000);
294 		}
295 		catch(aException)
296 		{}
297 		
298 		var xml = this.garminPlugin.getDirectoryXml();
299 		if(xml == "")
300 		{
301 			//this.garminPlugin.resetDirectoryXml();
302 		}
303 		return xml;		
304 	},
305 	
306 	/** Returns true if FIT health data can be read from the device.
307      * @returns {Boolean}
308 	 */
309 	doesCurrentDeviceSupportHealth: function(){
310     	var supported = false;
311     	var directoryXml = this.getCurrentDeviceFitDirectoryXml();
312 		if(directoryXml != "")
313 		{
314 			var files = Garmin.DirectoryFactory.parseString(directoryXml);
315 			if(Garmin.DirectoryFactory.getHealthDataFiles(files).length > 0)
316 			{
317 				supported = true;
318 			}
319 		}
320 		return supported;
321     },
322 
323 	/*@private*/
324 	pause: function(millis)
325 	{
326 		var date = new Date();
327 		var curDate = null;
328 
329 		do { curDate = new Date(); }
330 		while(curDate-date < millis);
331 	},
332 	
333 	/*@private*/
334 	waitForReadToFinish: function()
335 	{
336 		var complete = false;
337 		
338 		while(complete == false)
339 		{
340 			try
341 			{
342 				var theCompletionState =this.garminPlugin.finishReadFitDirectory();
343 				//alert("thecompletionState = " + theCompletionState);
344 				if( theCompletionState == 3 ) //Finished
345 				{
346 					complete = true;
347 				}
348 				else if( theCompletionState == 2 ) //Message Waiting
349 				{
350 					complete = true;
351 				}
352 				else
353 				{
354 				}
355 			}
356 			catch( aException )
357 			{
358 				complete = true;
359 			}
360 		}
361 	},
362 
363 
364 	/////////////////////// Read Methods ///////////////////////
365 	
366 	/** Generic read method, supporting GPX and TCX Fitness types: Courses, Workouts, User Profiles, Activity Goals, 
367 	 * TCX activity directory, and various directory reads. <br/>
368 	 * <br/>
369 	 * Fitness detail reading (one specific activity) is not supported by this read method, refer to 
370 	 * readDetailFromDevice for that. <br/><br/>
371 	 * As of Communicator v3.0.0.0, if TCX data is requested from a FIT
372      * device, the plugin will attempt a conversion from FIT to TCX.
373      * <strong>Note:</strong> TCX cannot fully represent FIT, therefore this conversion can be lossy.
374      * For guaranteed fidelity when reading FIT files, use getBinaryFile instead.<br/><br/>
375      * <strong>Examples:</strong>
376      *@example
377      * myControl.readDataFromDevice( Garmin.DeviceControl.FILE_TYPES.gpx ); 
378      *
379 	 * @example
380      * var theListOptions = [{dataTypeName: 'UserDataSync',
381      *                         dataTypeID: 'http://www.topografix.com/GPX/1/1',
382      *                         computeMD5: false}];
383 	 * myControl.readDataFromDevice( Garmin.DeviceControl.FILE_TYPES.readableDir,
384 	 *                               theListOptions );
385      *
386 	 * @param {String} fileType The filetype to read from device.  Possible values for fileType are located in Garmin.DeviceControl.FILE_TYPES -- detail types are not supported. <br/>
387      * @param {Object[]} [fileListingOptions] Array of objects that define file listing options. <br/>
388      * <strong>fileListingOptions properties:</strong> <br/>
389      * {String} dataTypeName: Name from GarminDevice.xml <br/>
390      * {String} dataTypeID: Identifier from GarminDevice.xml<br/>
391      * {Boolean} computeMD5: compute MD5 checksum for each listed file<br/>
392      * @see #readDetailFromDevice, #getBinaryFile, Garmin.DeviceControl#FILE_TYPES
393 	 * @throws InvalidTypeException, UnsupportedTransferTypeException
394      */	
395 	readDataFromDevice: function(fileType, fileListingOptions) {
396 		if (!this.isUnlocked())
397 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
398 		if (this.numDevices == 0)
399 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
400 		// Make sure filetype passed in is a valid type
401 		if ( ! this._isAMember(fileType, [Garmin.DeviceControl.FILE_TYPES.gpx,
402 										  Garmin.DeviceControl.FILE_TYPES.gpxDir,
403 		                                  Garmin.DeviceControl.FILE_TYPES.tcx, 
404 		                                  Garmin.DeviceControl.FILE_TYPES.crs, 
405 										  Garmin.DeviceControl.FILE_TYPES.tcxDir, 
406 										  Garmin.DeviceControl.FILE_TYPES.crsDir, 
407 										  Garmin.DeviceControl.FILE_TYPES.wkt, 
408 										  Garmin.DeviceControl.FILE_TYPES.tcxProfile,
409 										  Garmin.DeviceControl.FILE_TYPES.goals,
410 										  Garmin.DeviceControl.FILE_TYPES.fitDir,
411 										  Garmin.DeviceControl.FILE_TYPES.fitHealthData, 
412 										  Garmin.DeviceControl.FILE_TYPES.readableDir
413 										  ])) {
414 			var error = new Error(Garmin.DeviceControl.MESSAGES.invalidFileType + fileType);
415 			error.name = "InvalidTypeException";
416 			throw error;
417 		}
418 		if( fileType == Garmin.DeviceControl.FILE_TYPES.readableDir && 
419 		    fileListingOptions === undefined ) {
420 			var error = new Error("You have specified invalid or conflicting fileListingOptions");
421 			error.name = "InvalidParameterException";
422 			throw error;
423 		}
424 		// Make sure the device supports this type of data transfer for this type
425 		if( !this.checkDeviceReadSupport(fileType) ) {
426 		    var error = new Error(Garmin.DeviceControl.MESSAGES.unsupportedReadDataType + fileType);
427     	    error.name = "UnsupportedDataTypeException";
428 			throw error;
429 		}
430 		this.gpsDataType = fileType;
431 		this.gpsData = null;		
432 		this.gpsDataString = null;
433 		this.idle = false;
434 		this.fileListingOptions = fileListingOptions;
435 		this.fileListingIndex = 0;
436 		try {
437         	this._broadcaster.dispatch("onStartReadFromDevice", {controller: this});
438         	
439         	switch(this.gpsDataType) {        		
440 				case Garmin.DeviceControl.FILE_TYPES.gpxDir:
441         		case Garmin.DeviceControl.FILE_TYPES.gpx:
442         			this.garminPlugin.startReadFromGps( this.deviceNumber );
443         			break;
444         		case Garmin.DeviceControl.FILE_TYPES.tcx:
445         		case Garmin.DeviceControl.FILE_TYPES.crs:
446         		case Garmin.DeviceControl.FILE_TYPES.wkt:
447         		case Garmin.DeviceControl.FILE_TYPES.goals:
448         		case Garmin.DeviceControl.FILE_TYPES.tcxProfile:        		
449         			this.garminPlugin.startReadFitnessData( this.deviceNumber, this.gpsDataType );
450         			break;
451         		case Garmin.DeviceControl.FILE_TYPES.tcxDir:
452         			this.garminPlugin.startReadFitnessDirectory(this.deviceNumber, Garmin.DeviceControl.FILE_TYPES.tcx);
453         			break;
454         		case Garmin.DeviceControl.FILE_TYPES.crsDir:
455         			this.garminPlugin.startReadFitnessDirectory(this.deviceNumber, Garmin.DeviceControl.FILE_TYPES.crs);
456         			break;
457         		case Garmin.DeviceControl.FILE_TYPES.fitDir:
458         		case Garmin.DeviceControl.FILE_TYPES.fitHealthData:
459                     this.garminPlugin.startReadFitDirectory(this.deviceNumber);
460         			break;
461         		case Garmin.DeviceControl.FILE_TYPES.deviceXml:
462         			this.gpsDataString = this.getCurrentDeviceXml();
463         			break;
464         		case Garmin.DeviceControl.FILE_TYPES.readableDir:
465                     this.garminPlugin.startReadableFileListing(this.deviceNumber, 
466                                                                fileListingOptions[this.fileListingIndex].dataTypeName,
467                                                                fileListingOptions[this.fileListingIndex].dataTypeID,
468                                                                fileListingOptions[this.fileListingIndex].computeMD5);
469         			break;
470         	} 
471 		    this._progressRead();
472 		} catch(e) {
473 		    this._reportException(e);
474 		}
475 	},
476 
477 	/** Generic detail read method, which reads a specific fitness activity from the device given an activity ID.  
478 	 * Supported detail types are history activities and course activities.  The resulting data read is available 
479 	 * in gpsData as an XML DOM and gpsDataString as an XML string once the read successfully finishes. 
480 	 * Typically used after calling readDataFromDevice to read a fitness directory.<br/> 
481 	 * <br/>
482 	 * Minimum plugin version 2.2.0.2
483 	 * 
484 	 * @param {String} fileType Filetype to be read from the device.  Supported values are 
485 	 * Garmin.DeviceControl.FILE_TYPES.tcxDetail and Garmin.DeviceControl.FILE_TYPES.crsDetail
486 	 * @param {String} dataId The ID of the data to be read from the device.  The format of these values depends 
487 	 * on the type of data being read (i.e. course data or history data). The CourseName element in the course schema 
488 	 * is used to identify courses, and the Id element is used to identify history activities.
489 	 * @see #readDataFromDevice, #readHistoryDetailFromFitnessDevice, #readCourseDetailFromFitnessDevice
490 	 * @throws InvalidTypeException, UnsupportedTransferTypeException
491 	 */
492 	readDetailFromDevice: function(fileType, dataId) {
493 		if (!this.isUnlocked())
494 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
495 		if (this.numDevices == 0)
496 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
497 		if ( ! this._isAMember(fileType, [Garmin.DeviceControl.FILE_TYPES.tcxDetail, Garmin.DeviceControl.FILE_TYPES.crsDetail])) {
498 			var error = new Error(Garmin.DeviceControl.MESSAGES.invalidFileType + fileType);
499 			error.name = "InvalidTypeException";
500 			throw error;
501 		}
502 		if( !this.checkDeviceReadSupport(fileType) ) {
503 			throw new Error(Garmin.DeviceControl.MESSAGES.unsupportedReadDataType + fileType);
504 		}
505 		
506 		this.gpsDataType = fileType;
507 		this.gpsData = null;		
508 		this.gpsDataString = null;
509 		this.idle = false;
510 		
511 		try {
512         	this._broadcaster.dispatch("onStartReadFromDevice", {controller: this});
513         	
514         	switch(this.gpsDataType) {
515         		case Garmin.DeviceControl.FILE_TYPES.tcxDetail:
516         			this.garminPlugin.startReadFitnessDetail(this.deviceNumber, Garmin.DeviceControl.FILE_TYPES.tcx, dataId);
517         			break;
518         		case Garmin.DeviceControl.FILE_TYPES.crsDetail:
519         			this.garminPlugin.startReadFitnessDetail(this.deviceNumber, Garmin.DeviceControl.FILE_TYPES.crs, dataId);
520         			break;
521         	} 
522 		    this._progressRead();
523 		} catch(e) {
524 		    this._reportException(e);
525 		}
526 	},
527 	
528 	/** Asynchronously reads GPX data from the connected device.  Only handles reading
529      * from the device in this.deviceNumber. <br/>
530      * <br/>
531      * When the data has been gathered, the onFinishedReadFromDevice is fired, and the
532      * data is stored in this.gpsDataString and this.gpsData
533      * 
534      * @see #readDataFromDevice
535      */
536 	readFromDevice: function() {
537 		this.readDataFromDevice(Garmin.DeviceControl.FILE_TYPES.gpx);
538 	},
539 	
540 	/** Asynchronously reads a single fitness history record from the connected device as TCX format.
541 	 * Only handles reading from the device in this.deviceNumber.<br/>
542 	 * <br/>
543      * When the data has been gathered, the onFinishedReadFromDevice is fired, and the
544      * data is stored in this.gpsDataString
545      * 
546      * Minimum plugin version 2.2.0.2
547      * 
548      * @param {String} historyId The ID of the history record on the device.
549      * 
550      * @see #readDetailFromDevice
551      */	
552 	readHistoryDetailFromFitnessDevice: function(historyId) {
553 		this.readDetailFromDevice(Garmin.DeviceControl.FILE_TYPES.tcx, historyId)
554 	},
555 	
556 	/** Asynchronously reads a single fitness course from the connected device as TCX format.
557 	 * Only handles reading from the device in this.deviceNumber. <br/>
558      * <br/>
559      * When the data has been gathered, the onFinishedReadFromDevice is fired, and the
560      * data is stored in this.gpsDataString<br/>
561      * <br/>
562      * Minimum plugin version 2.2.0.2
563      * 
564      * @param {String} courseId The name of the course on the device.
565      * 
566      * @see #readDetailFromDevice
567      */			
568 	readCourseDetailFromFitnessDevice: function(courseId){
569 		this.readDetailFromDevice(Garmin.DeviceControl.FILE_TYPES.crs, courseId)
570 	},
571 	
572 	/** Asynchronously reads entire fitness history data (TCX) from the connected device.  
573 	 * Only handles reading from the device in this.deviceNumber. <br/>
574      * <br/>
575      * When the data has been gathered, the onFinishedReadFromDevice is fired, and the
576      * data is stored in this.gpsDataString<br/>
577      * <br/>
578      * Minimum plugin version 2.1.0.3
579      * 
580      * @see #readDataFromDevice
581      */	
582 	readHistoryFromFitnessDevice: function() {	
583 		this.readDataFromDevice(Garmin.DeviceControl.FILE_TYPES.tcx);
584 	},
585 	
586 	/** Asynchronously reads entire fitness course data (CRS) from the connected device.  
587 	 * Only handles reading from the device in this.deviceNumber<br/>
588      * <br/>
589      * When the data has been gathered, the onFinishedReadFromDevice is fired, and the
590      * data is stored in this.gpsDataString<br/>
591      * <br/>
592      * Minimum plugin version 2.2.0.1
593      * 
594      * @see #readDataFromDevice
595      */	
596 	readCoursesFromFitnessDevice: function() {
597 		this.readDataFromDevice(Garmin.DeviceControl.FILE_TYPES.crs);
598 	},
599 	
600 	/** Asynchronously reads fitness workout data (WKT) from the connected device.  
601 	 * Only handles reading from the device in this.deviceNumber<br/>
602      * <br/>
603      * When the data has been gathered, the onFinishedReadFromDevice is fired, and the
604      * data is stored in this.gpsDataString<br/>
605      * <br/>
606      * Minimum plugin version 2.2.0.1
607      * 
608      * @see #readDataFromDevice
609      */	
610 	readWorkoutsFromFitnessDevice: function() {
611 		this.readDataFromDevice(Garmin.DeviceControl.FILE_TYPES.wkt);
612 	},
613 	
614 	/** Asynchronously reads fitness profile data (TCX) from the connected device.
615 	 * Only handles reading from the device in this.deviceNumber<br/>
616      * <br/>
617      * When the data has been gathered, the onFinishedReadFromDevice is fired, and the
618      * data is stored in this.gpsDataString<br/>
619      * <br/>
620      * Minimum plugin version 2.2.0.1
621      * 
622      * @see #readDataFromDevice
623      */	
624 	readUserProfileFromFitnessDevice: function() {
625 		this.readDataFromDevice(Garmin.DeviceControl.FILE_TYPES.tcxProfile);
626 	},
627 
628 	/** Asynchronously reads fitness goals data (TCX) from the connected device.
629 	 * Only handles reading from the device in this.deviceNumber<br/>
630      * <br/>
631      * When the data has been gathered, the onFinishedReadFromDevice is fired, and the
632      * data is stored in this.gpsDataString<br/>
633      * <br/>
634      * Minimum plugin version 2.2.0.1
635      * 
636      * @see #readDataFromDevice
637      */	
638 	readGoalsFromFitnessDevice: function() {
639 		this.readDataFromDevice(Garmin.DeviceControl.FILE_TYPES.goals);
640 	},
641 	
642 	
643 	
644 	/** Returns the GPS data that was last read as an XML DOM. <br/> 
645 	 * Pre-requisite - Read function was called successfully.  <br/> 
646 	 * <br/>
647 	 * Minimum plugin version 2.1.0.3
648 	 * 
649 	 * @return XML DOM of read GPS data
650 	 * @see #readDataFromDevice
651 	 * @see #readHistoryFromFitnessDevice
652 	 * @see #readHistoryDetailFromFitnessDevice
653 	 * @see #readCourseDetailFromFitnessDevice
654 	 */
655 	getGpsData: function() {
656 		
657 		if (!this.isUnlocked())
658 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
659 		if (this.numDevices == 0)
660 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
661 		if( this.getReadCompletionState != Garmin.DeviceControl.FINISH_STATES.finished ) {
662 			throw new Error(Garmin.DeviceControl.MESSAGES.incompleteRead);
663 		}
664 		
665 		return this.gpsData;
666 	},
667 	
668 	/** Returns the GPS data that was last read as an XML string. <br/>  
669 	 * Pre-requisite - Read function was called successfully. <br/>
670 	 * <br/>
671 	 * Minimum plugin version 2.1.0.3
672 	 * 
673 	 * @return XML string of read GPS data
674 	 * @see #readDataFromDevice
675 	 * @see #readHistoryFromFitnessDevice
676 	 * @see #readHistoryDetailFromFitnessDevice
677 	 * @see #readCourseDetailFromFitnessDevice
678 	 */
679 	getGpsDataString: function() {
680 		if (!this.isUnlocked())
681 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
682 		if (this.numDevices == 0)
683 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
684 		if( this.getReadCompletionState != Garmin.DeviceControl.FINISH_STATES.finished ) {
685 			throw new Error(Garmin.DeviceControl.MESSAGES.incompleteRead);
686 		}
687 		
688 		return this.gpsDataString;
689 	},
690 	
691 	/** Returns the last read fitness data in compressed format.  A fitness read method must be called and the read must
692 	 * finish successfully before this function returns good data. <br/>
693 	 * <br/>
694 	 * Minimum plugin version 2.2.0.2
695 	 * 
696 	 * @return Compressed fitness XML data from the last successful read.  The data is gzp compressed and base64 expanded.
697 	 * @see #readDataFromDevice
698 	 * @see #readHistoryFromFitnessDevice
699 	 * @see #readHistoryDetailFromFitnessDevice
700 	 * @see #readCourseDetailFromFitnessDevice
701 	 */
702 	getCompressedFitnessData: function() {
703 		
704 		if (!this.isUnlocked())
705 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
706 		if (this.numDevices == 0)
707 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
708 		if( this.getReadCompletionState != Garmin.DeviceControl.FINISH_STATES.finished ) {
709 			throw new Error(Garmin.DeviceControl.MESSAGES.incompleteRead);
710 		}
711 
712 		try{
713 			this.garminPlugin.getTcdXmlz();
714 		}
715 		catch( aException ) {
716  			this._reportException( aException );
717 		}
718 	},
719 
720 	/** Returns the completion state of the current read.  This function can be used with
721 	 * GPX and TCX (fitness) reads.
722 	 * 
723 	 * @type Number
724 	 * @return {Number} The completion state of the current read.  The completion state can be one of the following: <br/>
725 	 *  <br/>
726 	 *	0 = idle <br/>
727  	 * 	1 = working <br/>
728  	 * 	2 = waiting <br/>
729  	 * 	3 = finished <br/>
730 	 */	
731 	getReadCompletionState: function() {
732 		switch(this.gpsDataType) {
733 			case Garmin.DeviceControl.FILE_TYPES.gpxDir:
734 			case Garmin.DeviceControl.FILE_TYPES.gpxDetail:
735 			case Garmin.DeviceControl.FILE_TYPES.gpx:
736 				return this.garminPlugin.finishReadFromGps();
737 			case Garmin.DeviceControl.FILE_TYPES.tcx:
738 			case Garmin.DeviceControl.FILE_TYPES.crs:
739 			case Garmin.DeviceControl.FILE_TYPES.wkt:
740 			case Garmin.DeviceControl.FILE_TYPES.tcxProfile:
741 			case Garmin.DeviceControl.FILE_TYPES.goals:
742 				return this.garminPlugin.finishReadFitnessData();
743 			case Garmin.DeviceControl.FILE_TYPES.tcxDir:
744 			case Garmin.DeviceControl.FILE_TYPES.crsDir:
745 				return this.garminPlugin.finishReadFitnessDirectory();
746 			case Garmin.DeviceControl.FILE_TYPES.tcxDetail:
747 			case Garmin.DeviceControl.FILE_TYPES.crsDetail:
748 				return this.garminPlugin.finishReadFitnessDetail();
749 			case Garmin.DeviceControl.FILE_TYPES.fitHealthData:
750 			case Garmin.DeviceControl.FILE_TYPES.fitDir:
751                 return this.garminPlugin.finishReadFitDirectory();
752 			case Garmin.DeviceControl.FILE_TYPES.readableDir:
753                 return this.garminPlugin.finishReadableFileListing();
754 		}
755 	},
756 	
757 	/** Internal read dispatching and polling delay.
758 	 * @private
759      */	
760 	_progressRead: function() {
761 		this._broadcaster.dispatch("onProgressReadFromDevice", {progress: this.getDeviceStatus(), controller: this});
762         setTimeout(function() { this._finishReadFromDevice() }.bind(this), 200); //200		 
763 	},
764 	
765 	/** Internal read state logic. <br/>
766 	 * <br/>
767 	 * Minimum plugin version 2.0.0.4 for GPX and TCX history read.<br/>
768 	 * Minimum plugin version 2.2.0.2 for directory and detail read.<br/>
769 	 * Minimum plugin version 2.2.0.2 for compressed file get.
770 	 * 
771 	 * @private
772      */	
773 	_finishReadFromDevice: function() {
774 		var completionState = this.getReadCompletionState();
775         try {
776         	
777 			if( completionState == Garmin.DeviceControl.FINISH_STATES.finished ) {
778 			    var theSuccess = false;
779 	        	switch( this.gpsDataType ) {
780 					case Garmin.DeviceControl.FILE_TYPES.gpxDir:
781 	        		case Garmin.DeviceControl.FILE_TYPES.gpxDetail:
782 	        		case Garmin.DeviceControl.FILE_TYPES.gpx:
783 	        			theSuccess = this.garminPlugin.gpsTransferSucceeded();
784 	        			if (theSuccess) {
785 		        			this.gpsDataString = this.garminPlugin.getGpsXml();
786 							this.gpsData = Garmin.XmlConverter.toDocument(this.gpsDataString);
787 							this._broadcaster.dispatch("onFinishReadFromDevice", {success: theSuccess, controller: this});	
788 	        			}
789 						break;
790 					case Garmin.DeviceControl.FILE_TYPES.tcx:
791 					case Garmin.DeviceControl.FILE_TYPES.crs:
792 					case Garmin.DeviceControl.FILE_TYPES.tcxDir:
793 					case Garmin.DeviceControl.FILE_TYPES.crsDir:
794 					case Garmin.DeviceControl.FILE_TYPES.tcxDetail:
795 					case Garmin.DeviceControl.FILE_TYPES.crsDetail:
796 					case Garmin.DeviceControl.FILE_TYPES.wkt:
797 					case Garmin.DeviceControl.FILE_TYPES.tcxProfile:
798 					case Garmin.DeviceControl.FILE_TYPES.goals:
799 						theSuccess = this.garminPlugin.fitnessTransferSucceeded();
800 						if (theSuccess) {
801 							this.gpsDataString = this.garminPlugin.getTcdXml();
802 							this.gpsDataStringCompressed = this.garminPlugin.getTcdXmlz();
803 							
804 							this.gpsData = Garmin.XmlConverter.toDocument(this.gpsDataString);
805 							this._broadcaster.dispatch("onFinishReadFromDevice", {success: theSuccess, controller: this});										
806 						}
807 						break;
808 					case Garmin.DeviceControl.FILE_TYPES.fitHealthData:
809 					case Garmin.DeviceControl.FILE_TYPES.fitDir:
810                         theSuccess = this.garminPlugin.fitnessTransferSucceeded();
811 						this.gpsDataString = this.garminPlugin.getDirectoryXml();
812 						this.gpsData = Garmin.XmlConverter.toDocument(this.gpsDataString);
813 						this._broadcaster.dispatch("onFinishReadFromDevice", {success: theSuccess, controller: this});
814                         break;
815 					case Garmin.DeviceControl.FILE_TYPES.readableDir:
816 						this._appendDirXml(this.garminPlugin.getDirectoryXml());
817 						++this.fileListingIndex;
818 						if( this.fileListingIndex < this.fileListingOptions.length) {
819 							//start the next file listing operation
820 							this.garminPlugin.startReadableFileListing(this.deviceNumber, 
821 											   this.fileListingOptions[this.fileListingIndex].dataTypeName,
822 											   this.fileListingOptions[this.fileListingIndex].dataTypeID,
823 											   this.fileListingOptions[this.fileListingIndex].computeMD5);
824 							this._progressRead();
825 							break;
826 						} else {
827 						    //done with file listings
828 						    theSuccess = true;
829 						    this._broadcaster.dispatch("onFinishReadFromDevice", {success: theSuccess, controller: this});
830                             break;
831 						}
832 	        	} //end switch
833 			} else if( completionState == Garmin.DeviceControl.FINISH_STATES.messageWaiting ) {
834 				var msg = this._messageWaiting();
835 				this._broadcaster.dispatch("onWaitingReadFromDevice", {message: msg, controller: this});
836 			} else {
837 	    	    this._progressRead();
838 			}
839 		} catch( aException ) {
840  			this._reportException( aException );
841 		}
842     },
843 
844 	/** User canceled the read. <br/>
845 	 * <br/>
846 	 * Minimum plugin version 2.0.0.4
847      */	
848 	cancelReadFromDevice: function() {
849 		if (this.gpsDataType == Garmin.DeviceControl.FILE_TYPES.gpx) {
850 			this.garminPlugin.cancelReadFromGps();
851 		} else {
852 			this.garminPlugin.cancelReadFitnessData();
853 		}
854     	this._broadcaster.dispatch("onCancelReadFromDevice", {controller: this});
855 	},
856 	
857 	
858 	/** Return the specified file as a UU-Encoded string
859      * <br/>
860      * Minimum version 2.6.3.1
861      * 
862      * If the file is known to be compressed, compressed should be
863      * set to false. Otherwise, set compressed to true to retrieve a
864      * gzipped and uuencoded file.
865      * 
866      * @param {String} relativeFilePath path relative to the Garmin folder on the device
867      */
868      getBinaryFile: function(deviceNumber, relativeFilePath) {
869         if (!this.isUnlocked())
870 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
871 		if(this.numDevices == 0)
872 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
873         // Attempt to detect Fit file
874         if(relativeFilePath.capitalize().endsWith(".fit")) {
875             // Capitalize makes all but first letters lowercase. I can't believe prototype doesn't have a lowercase method. :(
876             this.gpsDataType = Garmin.DeviceControl.FILE_TYPES.fit;
877         } else {
878     		this.gpsDataType = Garmin.DeviceControl.FILE_TYPES.binary;
879         }
880 		var success;
881 		try {
882 		    this.gpsDataString = this.garminPlugin.getBinaryFile(deviceNumber, relativeFilePath, false);
883 		    this.gpsDataStringCompressed = this.garminPlugin.getBinaryFile(deviceNumber, relativeFilePath, true);
884 		    success = true;
885 	    } catch(e) {
886 	        success = false;
887 			this._reportException(e);
888 	    }
889 	    
890 	    this._broadcaster.dispatch("onFinishReadFromDevice", {success: success, controller: this});
891         return this.gpsData;
892      },
893 
894 	/////////////////////// Web Drop Methods (Write) ///////////////////////
895 	
896     /** Writes an address to the currently selected device.
897      * 
898      * @param {String} address The address to be written to the device. This doesn't check validity
899      */	
900 	writeAddressToDevice: function(address) {
901 		if (!this.isUnlocked())
902 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
903 		if (!this.geocoder) {
904 			this.geocoder = new Garmin.Geocode();
905 			this.geocoder.register(this);
906 		}
907 		this.geocoder.findLatLng(address);
908 	},
909 
910 	/** Handles call-back from geocoder and forwards call to onException on registered listeners.
911 	 * @private
912      * @param {Error} json error wrapped in JSON 'msg' object.
913      */
914 	onException: function(json) {
915 		this._reportException(json.msg);
916 	},
917 	
918 	/** Handles call-back from geocoder and forwards call to writeToDevice.
919 	 * Registered listeners will recieve an onFinishedFindLatLon call before writeToDevice is invoked.
920 	 * Listeners can change the 'fileName' if they choose avoiding overwritting old waypoints on
921 	 * some devices.
922 	 * @private
923      * @param {Object} json waypoint, fileName and controller in JSON wrapper.
924      */
925 	onFinishedFindLatLon: function(json) {
926 		json.fileName = "address.gpx";
927 		json.controller = this;
928 		this._broadcaster.dispatch("onFinishedFindLatLon", json);
929    		var factory = new Garmin.GpsDataFactory();
930 		var gpxStr = factory.produceGpxString(null, [json.waypoint]);
931 		this.writeToDevice(gpxStr, json.fileName);
932 	},
933 
934 	/////////////////////// More Write Methods ///////////////////////	
935     /**
936      * Generic write method for GPX and TCX file formats.  For binary write, use {@link #downloadToDevice}.
937      * 
938      * @param {String} dataType - the datatype to write to device.  Possible values are located in {@link #Garmin.DeviceControl.FILE_TYPES}
939      * @param {String} dataString - the datastring to write to device.  Should be in the format of the dataType value.
940      * @param {String} fileName - the filename to write the data to on the device. File extension is not necessary, 
941      * but is suggested for device compatibility.  This parameter is ignored when the dataType value is FitnessActivityGoals (see {@link #writeGoalsToFitnessDevice}).
942      * @see #writeToDevice, #writeFitnessToDevice
943      * @throws InvalidTypeException, UnsupportedTransferTypeException
944      */ 
945     writeDataToDevice: function(dataType, dataString, fileName) {
946         if (!this.isUnlocked()) {
947 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
948         }
949         
950 		if (this.numDevices == 0) {
951 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
952         }
953 
954 		this.gpsDataType = dataType;
955         
956 		if (!this.checkDeviceWriteSupport(this.gpsDataType)) {
957 			throw new Error(Garmin.DeviceControl.MESSAGES.unsupportedWriteDataType + this.gpsDataType);
958 		}
959 		
960 		try {
961         	this._broadcaster.dispatch("onStartWriteToDevice", {controller: this});
962             
963         	switch(this.gpsDataType) {
964             	case Garmin.DeviceControl.FILE_TYPES.gpx:
965         			this.garminPlugin.startWriteToGps(dataString, fileName, this.deviceNumber);
966         			break;
967         		case Garmin.DeviceControl.FILE_TYPES.crs:
968         		case Garmin.DeviceControl.FILE_TYPES.wkt:
969         		case Garmin.DeviceControl.FILE_TYPES.goals:
970         		case Garmin.DeviceControl.FILE_TYPES.tcxProfile:
971         		case Garmin.DeviceControl.FILE_TYPES.nlf:                
972         			this.garminPlugin.startWriteFitnessData(dataString, this.deviceNumber, fileName, this.gpsDataType);
973         			break;
974         		default:
975 					throw new Error(Garmin.DeviceControl.MESSAGES.unsupportedWriteDataType + this.gpsDataType);
976         	}
977 		    this._progressWrite();
978 	    } catch(e) {
979 			this._reportException(e);
980 	   	}
981     },
982     
983     /** Writes the given GPX XML string to the device selected in this.deviceNumber. <br/>
984      * <br/>
985      * Minimum plugin version 2.0.0.4
986      * 
987      * @param gpxString XML to be written to the device. This doesn't check validity.
988      * @param fileName The filename to write data to.  Validity is not checked here.
989      */	
990 	writeToDevice: function(gpxString, fileName) {
991         this.writeDataToDevice(Garmin.DeviceControl.FILE_TYPES.gpx, gpxString, fileName);	    
992 	},
993 
994 	/** DEPRECATED - See {@link #writeCoursesToFitnessDevice}<br/> 
995 	 * <br/>
996 	 * Writes fitness course data (TCX) to the device selected in this.deviceNumber. <br/>
997 	 * <br/>
998 	 * Minimum plugin version 2.2.0.1
999 	 * 
1000      * @param tcxString {String} TCX Course XML string to be written to the device. This doesn't check validity.
1001      * @param fileName {String} filename to write data to on the device.  Validity is not checked here.
1002      */	
1003 	writeFitnessToDevice: function(tcxString, fileName) {
1004 		this.writeDataToDevice(Garmin.DeviceControl.FILE_TYPES.crs, tcxString, fileName);
1005 	},
1006 
1007 	/** Writes fitness course data (TCX) to the device selected in this.deviceNumber. <br/>
1008 	 * <br/>
1009 	 * Minimum plugin version 2.2.0.1
1010 	 * 
1011      * @param tcxString {String} TCX Course XML string to be written to the device. This doesn't check validity.
1012      * @param fileName {String} filename to write data to on the device.  Validity is not checked here.
1013      */	
1014 	writeCoursesToFitnessDevice: function(tcxString, fileName) {
1015 		this.writeDataToDevice(Garmin.DeviceControl.FILE_TYPES.crs, tcxString, fileName);
1016 	},
1017 
1018 	/** Writes fitness goals data (TCX) string to the device selected in this.deviceNumber. All fitness goals
1019 	 * are written to the filename 'ActivityGoals.TCX' in the device's goals directory, in order for the device
1020 	 * to recognize the file.<br/> 
1021 	 * <br/>
1022 	 * Minimum plugin version 2.2.0.1
1023 	 * 
1024      * @param tcxString {String} ActivityGoals TCX string to be written to the device. This doesn't check validity.
1025      */	
1026 	writeGoalsToFitnessDevice: function(tcxString) {
1027 		this.writeDataToDevice(Garmin.DeviceControl.FILE_TYPES.goals, tcxString, '');
1028 	},
1029 	
1030 	/** Writes fitness workouts data (XML) string to the device selected in this.deviceNumber. <br/>
1031 	 * <br/>
1032 	 * Minimum plugin version 2.2.0.1
1033 	 * 
1034      * @param tcxString XML (workouts) string to be written to the device. This doesn't check validity.
1035      * @param fileName String of filename to write it to on the device.  Validity is not checked here.
1036      */	
1037 	writeWorkoutsToFitnessDevice: function(tcxString, fileName) {
1038 		this.writeDataToDevice(Garmin.DeviceControl.FILE_TYPES.wkt, tcxString, fileName);
1039 	},
1040 	
1041 	/** Writes fitness user profile data (TCX) string to the device selected in this.deviceNumber. <br/>
1042 	 * <br/>
1043 	 * Minimum plugin version 2.2.0.1
1044 	 * 
1045      * @param tcxString XML (user profile) string to be written to the device. This doesn't check validity.
1046      * @param fileName String of filename to write it to on the device.  Validity is not checked here.
1047      */	
1048 	writeUserProfileToFitnessDevice: function(tcxString, fileName) {
1049 		this.writeDataToDevice(Garmin.DeviceControl.FILE_TYPES.tcxProfile, tcxString, fileName);
1050 	},
1051 	
1052 	/** Downloads and writes binary data asynchronously to device. <br/>
1053 	 * <br/>
1054 	 * Minimum plugin version 2.0.0.4
1055      *
1056      * @param xmlDownloadDescription {String} XML string containing information about the files to be downloaded onto the device.<br/>
1057 	 * This xml must conform to one of two schemas:<br/>
1058 	 * <a href=http://www.garmin.com/xmlschemas/GarminPluginAPIV1.xsd>GarminPluginAPIV1</a> <br/>
1059 	 * <a href=http://www.garmin.com/xmlschemas/DeviceDownloadV1.xsd>DeviceDownloadV1</a> <strong>Recommended</strong> <br/>
1060      * @see #Garmin.GpiUtil
1061      */	
1062 	downloadToDevice: function(xmlDownloadDescription) {
1063 		if (!this.isUnlocked())
1064 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
1065 		if(this.numDevices == 0)
1066 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
1067 		this.gpsDataType = Garmin.DeviceControl.FILE_TYPES.binary;
1068 		try {
1069 		    this.garminPlugin.startDownloadData(xmlDownloadDescription, this.deviceNumber );
1070 		    this._progressWrite();
1071 	    } catch(e) {
1072 			this._reportException(e);
1073 	    }
1074 	},
1075 	
1076 	/** Internal dispatch and polling delay.
1077 	 * @private
1078      */	
1079 	_progressWrite: function() {
1080 		//console.debug("control._progressWrite gpsDataType="+this.gpsDataType)		
1081     	this._broadcaster.dispatch("onProgressWriteToDevice", {progress: this.getDeviceStatus(), controller: this});
1082         setTimeout(function() { this._finishWriteToDevice() }.bind(this), 200);
1083 	},
1084 	
1085 	/** Internal write lifecycle handling.
1086 	 * @private
1087      */	
1088 	_finishWriteToDevice: function() {
1089         try {
1090 			var completionState;
1091 			var success;
1092 			
1093 			switch( this.gpsDataType ) {
1094 				
1095 				case Garmin.DeviceControl.FILE_TYPES.gpx : 
1096 					completionState = this.garminPlugin.finishWriteToGps();
1097 					success = this.garminPlugin.gpsTransferSucceeded();
1098 					break;
1099 				case Garmin.DeviceControl.FILE_TYPES.crs :
1100 				case Garmin.DeviceControl.FILE_TYPES.goals :
1101 				case Garmin.DeviceControl.FILE_TYPES.wkt :
1102 				case Garmin.DeviceControl.FILE_TYPES.tcxProfile :
1103 				case Garmin.DeviceControl.FILE_TYPES.nlf :
1104 					completionState = this.garminPlugin.finishWriteFitnessData();
1105 					success = this.garminPlugin.fitnessTransferSucceeded();
1106 					break;
1107 				case Garmin.DeviceControl.FILE_TYPES.gpi :
1108 				case Garmin.DeviceControl.FILE_TYPES.fitCourse :
1109 				case Garmin.DeviceControl.FILE_TYPES.fitSettings :
1110 				case Garmin.DeviceControl.FILE_TYPES.fitSport :
1111 				case Garmin.DeviceControl.FILE_TYPES.binary :
1112 					completionState = this.garminPlugin.finishDownloadData();
1113 					success = this.garminPlugin.downloadDataSucceeded();
1114 					break;				
1115 				case Garmin.DeviceControl.FILE_TYPES.firmware :
1116 					completionState = this.garminPlugin.finishUnitSoftwareUpdate();
1117 					success = this.garminPlugin.downloadDataSucceeded();
1118 					break;				
1119 				default:
1120 					throw new Error(Garmin.DeviceControl.MESSAGES.unsupportedWriteDataType + this.gpsDataType);
1121 			}
1122 			
1123 			if( completionState == Garmin.DeviceControl.FINISH_STATES.finished ) {
1124 				this._broadcaster.dispatch("onFinishWriteToDevice", {success: success, controller: this});											
1125 			} else if( completionState == Garmin.DeviceControl.FINISH_STATES.messageWaiting ) {
1126 				var msg = this._messageWaiting();
1127 				this._broadcaster.dispatch("onWaitingWriteToDevice", {message: msg, controller: this});
1128 			} else {
1129 	    	     this._progressWrite();
1130 			}
1131 		} catch( aException ) {
1132  			this._reportException( aException );
1133 		}
1134 	},
1135 
1136 	/** Cancels the current write transfer to the device. <br/>
1137 	 * <br/>
1138 	 * Minimum plugin version 2.0.0.4<br/>
1139      * Minimum plugin version 2.2.0.1 for writes of GPX to SD Card
1140      */	
1141 	cancelWriteToDevice: function() {
1142 		switch( this.gpsDataType) {
1143 			case Garmin.DeviceControl.FILE_TYPES.gpx:
1144 				this.garminPlugin.cancelWriteToGps();
1145 				break;
1146 			case Garmin.DeviceControl.FILE_TYPES.gpi:
1147 			case Garmin.DeviceControl.FILE_TYPES.binary:
1148 				this.garminPlugin.cancelDownloadData();
1149 				break;
1150 			case Garmin.DeviceControl.FILE_TYPES.firmware:
1151 				this.garminPlugin.cancelUnitSoftwareUpdate();
1152 				break;
1153 			case Garmin.DeviceControl.FILE_TYPES.crs:
1154 			case Garmin.DeviceControl.FILE_TYPES.goals:
1155 			case Garmin.DeviceControl.FILE_TYPES.wkt:
1156 			case Garmin.DeviceControl.FILE_TYPES.tcxProfile:
1157 			case Garmin.DeviceControl.FILE_TYPES.nlf:
1158 				this.garminPlugin.cancelWriteFitnessData();
1159 				break;
1160 		}
1161 		this._broadcaster.dispatch("onCancelWriteToDevice", {controller: this});
1162 	},
1163 
1164     /**
1165 	 * Determine the amount of space available on a mass storage mode device (the
1166 	 * currently selected device according to this.deviceNumber). 
1167 	 * <br/> 
1168 	 * Minimum Plugin version 2.5.1
1169 	 * 
1170 	 * @param {String} relativeFilePath - if a file is being replaced, set to relative path on device, otherwise set to empty string.
1171 	 * @return -1 for non-mass storage mode devices.
1172 	 * @see #downloadToDevice 
1173 	 */
1174 	bytesAvailable: function(relativeFilePath) {
1175 	    return this.garminPlugin.bytesAvailable(this.getDeviceNumber(), relativeFilePath);
1176 	},
1177 	
1178 	/** Download and install a list of unit software updates.  Start the asynchronous 
1179      * StartUnitSoftwareUpdate operation.
1180      * 
1181      * Check for completion with the FinishUnitSoftwareUpdate() method.  After
1182      * completion check the DownloadDataSucceeded property to make sure that all of the downloads 
1183      * were successfully placed on the device. 
1184      * 
1185      * See the Schema UnitSoftwareUpdatev3.xsd for the format of the UpdateResponsesXml description
1186      *
1187      * @see Garmin.DeviceControl.cancelWriteToDevice
1188      * @see Garmin.DevicePlugin.downloadDataSucceeded
1189      * @see Garmin.DevicePlugin._finishWriteToDevice
1190      * @version plugin v2.6.2.0
1191      */
1192     downloadFirmwareToDevice: function(updateResponsesXml) {
1193         if (!this.isUnlocked())
1194 			throw new Error(Garmin.DeviceControl.MESSAGES.pluginNotUnlocked);
1195 		if(this.numDevices == 0)
1196 			throw new Error(Garmin.DeviceControl.MESSAGES.noDevicesConnected);
1197 		this.gpsDataType = Garmin.DeviceControl.FILE_TYPES.firmware;
1198 		try {
1199             this.garminPlugin.startUnitSoftwareUpdate(updateResponsesXml, this.deviceNumber);
1200 		    this._progressWrite();
1201 	    } catch(e) {
1202 			this._reportException(e);
1203 	    }
1204     },
1205     
1206 	/////////////////////// Support Methods ///////////////////////	
1207 
1208 
1209 	/** Unlocks the GpsControl object to be used at the given web address. <br/>
1210      * <br/>
1211      * Minimum Plugin version 2.0.0.4
1212      * 
1213      * @param {Array} pathKeyPairsArray baseURL and key pairs.  
1214      * @type Boolean 
1215      * @return True if the plug-in was unlocked successfully
1216      */
1217 	unlock: function(pathKeyPairsArray) {
1218 		this.pluginUnlocked = this.garminPlugin.unlock(pathKeyPairsArray);
1219 		return this.pluginUnlocked;
1220 	},
1221 
1222 	/** Register to be an event listener.  An object that is registered will be dispatched
1223      * a method if they have a function with the same dispatch name.  So if you register a
1224      * listener with an onFinishFindDevices, and the onFinishFindDevices message is called, you'll
1225      * get that message.  See class comments for event types
1226      *
1227      * @param {Object} listener Object that will listen for events coming from this object 
1228      * @see {Garmin.Broadcaster}
1229      */	
1230 	register: function(listener) {
1231         this._broadcaster.register(listener);
1232 	},
1233 
1234 	/** True if plugin has been successfully created and unlocked.
1235 	 * @type Boolean
1236 	 */
1237 	 isUnlocked: function() {
1238 	 	return this.pluginUnlocked;
1239 	 },
1240 	 
1241     /** Responds to a message box on the device.
1242      * 
1243      * Minimum version 2.0.0.4
1244      * 
1245      * @param {Number} response should be an int which corresponds to a button value from this.garminPlugin.MessageBoxXml
1246      */
1247     // TODO: this method only works with writes - should it work with reads?
1248     respondToMessageBox: function(response) {
1249         this.garminPlugin.respondToMessageBox(response ? 1 : 2);
1250         this._progressWrite();
1251     },
1252 
1253 	/** Called when device generates a message.
1254 	 * This occurs when completionState == Garmin.DeviceControl.FINISH_STATES.messageWaiting.
1255 	 * @private
1256      */	
1257 	_messageWaiting: function() {
1258 		var messageDoc = Garmin.XmlConverter.toDocument(this.garminPlugin.getMessageBoxXml());
1259 		var text = messageDoc.getElementsByTagName("Text")[0].childNodes[0].nodeValue;
1260 		
1261 		var message = new Garmin.MessageBox("Question",text);
1262 		
1263 		var buttonNodes = messageDoc.getElementsByTagName("Button");
1264 		for(var i=0; i<buttonNodes.length; i++) {
1265 			var caption = buttonNodes[i].getAttribute("Caption");
1266 			var value = buttonNodes[i].getAttribute("Value");
1267 			message.addButton(caption, value);
1268 		}
1269 		return message;
1270 	},
1271 
1272 	/** Get the status/progress of the current state or transfer
1273      * @type Garmin.TransferProgress
1274      */	
1275 	getDeviceStatus: function() {
1276 		var aProgressXml = this.garminPlugin.getProgressXml();
1277 		var theProgressDoc = Garmin.XmlConverter.toDocument(aProgressXml);
1278 		
1279 		var title = "";
1280 		if(theProgressDoc.getElementsByTagName("Title").length > 0) {
1281 			title = theProgressDoc.getElementsByTagName("Title")[0].childNodes[0].nodeValue;
1282 		}
1283 		
1284 		var progress = new Garmin.TransferProgress(title);
1285 
1286 		var textNodes = theProgressDoc.getElementsByTagName("Text");
1287 		for( var i=0; i < textNodes.length; i++ ) {
1288 			if(textNodes[i].childNodes.length > 0) {
1289 				var text = textNodes[i].childNodes[0].nodeValue;
1290 				if(text != "") progress.addText(text);
1291 			}
1292 		}
1293 		
1294 		var percentageNode = theProgressDoc.getElementsByTagName("ProgressBar")[0];
1295 		if(percentageNode != undefined) {
1296 			if(percentageNode.getAttribute("Type") == "Percentage") {
1297 				progress.setPercentage(percentageNode.getAttribute("Value"));
1298 			} else if (percentageNode.getAttribute("Type") == "Indefinite") {
1299 				progress.setPercentage(100);			
1300 			}
1301 		}
1302 
1303 		return progress;
1304 	},
1305 		
1306 	/**
1307 	 * @private
1308 	 */
1309 	_isAMember: function(element, array) {
1310 		return array.any( function(str){ return str==element; } );
1311 	},
1312 	
1313 	/** Gets the version number for the plugin the user has currently installed.
1314      * @type Array 
1315      * @return An array of the format [versionMajor, versionMinor, buildMajor, buildMinor].
1316      * @see #getPluginVersionString
1317      */	
1318 	getPluginVersion: function() {
1319 		
1320     	return this.garminPlugin.getPluginVersion();
1321 	},
1322 
1323 	/** Gets a string of the version number for the plugin the user has currently installed.
1324      * @type String 
1325      * @return A string of the format "versionMajor.versionMinor.buildMajor.buildMinor", i.e. "2.0.0.4"
1326      * @see #getPluginVersion
1327      */	
1328 	getPluginVersionString: function() {
1329 		return this.garminPlugin.getPluginVersionString();
1330 	},
1331 	
1332 	/** Sets the required version number for the plugin for the application.
1333 	 * @param reqVersionArray {Array} The required version to set to.  In the format [versionMajor, versionMinor, buildMajor, buildMinor]
1334 	 * 			i.e. [2,2,0,1]
1335 	 */
1336 	setPluginRequiredVersion: function(reqVersionArray) {
1337 		if( reqVersionArray != null ) {
1338 			this.garminPlugin.setPluginRequiredVersion(reqVersionArray);
1339 		}
1340 	},
1341 	
1342 	/** Sets the latest plugin version number.  This represents the latest version available for download at Garmin.
1343 	 * We will attempt to keep the default value of this up to date with each API release, but this is not guaranteed,
1344 	 * so set this to be safe or if you don't want to upgrade to the latest API.
1345 	 * 
1346 	 * @param reqVersionArray {Array} The latest version to set to.  In the format [versionMajor, versionMinor, buildMajor, buildMinor]
1347 	 * 			i.e. [2,2,0,1]
1348 	 */
1349 	setPluginLatestVersion: function(reqVersionArray) {
1350 		if( reqVersionArray != null ) {
1351 			this.garminPlugin.setPluginLatestVersion(reqVersionArray);
1352 		}
1353 	},
1354 	
1355 	/** Determines if the plugin is initialized
1356      * @type Boolean
1357      */	
1358 	isPluginInitialized: function() {
1359 		return (this.garminPlugin != null);
1360 	},
1361 
1362 	/** Determines if the plugin is installed on the user's machine
1363      * @type Boolean
1364      */	
1365 	isPluginInstalled: function() {
1366 		return (this.garminPlugin.getVersionXml() != undefined);
1367 	},
1368 
1369 	/** Internal exception handling for asynchronous calls.
1370 	 * @private
1371       */	
1372 	_reportException: function(exception) {
1373 		this._broadcaster.dispatch("onException", {msg: exception, controller: this});
1374 	},
1375 	
1376 	/** Number of devices detected by plugin.
1377 	 * @type Number
1378     */	
1379 	getDevicesCount: function() {
1380 	    return this.numDevices;
1381 	},
1382 	
1383 	/** Checks if the device lists the given datatype as a supported readable type.
1384 	 * Plugin version affects the results of this function.  The latest plugin version is encouraged.
1385 	 * 
1386 	 * Internal file type support (such as directory types) is detected based on base
1387 	 * type. i.e. tcxDir -> tcx, fitDir -> fit
1388 	 */
1389 	checkDeviceReadSupport: function( datatype ) {
1390 		
1391         // Do the plugin version check early for fit directory reading
1392 		if( datatype == Garmin.DeviceControl.FILE_TYPES.fitDir) {
1393 		    if( this.garminPlugin.getSupportsFitDirectoryRead() == false) {
1394     	        // Yeah, breaking the 1 return rule... This is still cleaner than all the other options
1395     	        // and at least eliminates confusion between other types.
1396 		        return false;
1397 		    }
1398         }
1399         
1400 		var isDatatypeSupported = false;
1401 
1402 		// The selected device
1403 		var device = this._getDeviceByNumber(this.deviceNumber);
1404 		var baseDatatype;
1405 
1406 		// Internal types use base type for the support check.
1407 		switch(datatype) {
1408 			case Garmin.DeviceControl.FILE_TYPES.gpxDir:
1409 			case Garmin.DeviceControl.FILE_TYPES.gpxDetail:
1410 			     baseDatatype = Garmin.DeviceControl.FILE_TYPES.gpx;
1411 			     break;			
1412             case Garmin.DeviceControl.FILE_TYPES.tcxDir:
1413             case Garmin.DeviceControl.FILE_TYPES.tcxDetail:
1414     		    baseDatatype = Garmin.DeviceControl.FILE_TYPES.tcx; 
1415     		    break;
1416             case Garmin.DeviceControl.FILE_TYPES.crsDir:
1417             case Garmin.DeviceControl.FILE_TYPES.crsDetail:
1418                 baseDatatype = Garmin.DeviceControl.FILE_TYPES.crs;
1419                 break;
1420             case Garmin.DeviceControl.FILE_TYPES.fitDir:
1421             case Garmin.DeviceControl.FILE_TYPES.fitFile:
1422                 baseDatatype = Garmin.DeviceControl.FILE_TYPES.fit;
1423                 break;
1424             default:     
1425                 baseDatatype = datatype;
1426 		}
1427 		
1428 		// Special Cases:
1429 		// Every device has a device xml and firmware.
1430 		if(baseDatatype == Garmin.DeviceControl.FILE_TYPES.deviceXml || 
1431 		   baseDatatype == Garmin.DeviceControl.FILE_TYPES.firmware ) {
1432 		    isDatatypeSupported = true;
1433 		}else if(baseDatatype == Garmin.DeviceControl.FILE_TYPES.readableDir) {
1434 		  isDatatypeSupported = device.isFileBased();
1435 		}
1436 		else {
1437 			isDatatypeSupported = device.supportDeviceDataTypeRead(baseDatatype);
1438 			
1439 			//Enable TCX compatibility mode for FIT devices if the plugin supports it:
1440 			if(isDatatypeSupported == false &&
1441 			   this.garminPlugin.checkPluginVersionSupport(Array(3,0,0,0))) {
1442                 var theFitType = this.mapTcxToFit(baseDatatype);
1443                 if(theFitType) {
1444                     try {
1445                         console.log("Enabling TCX compatibility mode, fitness data conversion may be lossy");
1446                     }
1447                     catch(e) {
1448                         //do nothing.
1449                     }
1450                     isDatatypeSupported = device.supportDeviceDataTypeRead(theFitType);
1451                 }
1452             }
1453 			
1454 			//Check device support via alternative means for devices which might
1455 			//not correctly indicate supported datatypes in their GarminDevice.xml files
1456 			if(baseDatatype == Garmin.DeviceControl.FILE_TYPES.fitHealthData && isDatatypeSupported == false)
1457 				isDatatypeSupported = this.doesCurrentDeviceSupportHealth();
1458 		}
1459 
1460 		return isDatatypeSupported;
1461 	},
1462 	
1463 	/** Attempts to map a TCX fitness type to the corresponding FIT type.
1464      * @param Garmin.DeviceControl.FILE_TYPES fileType
1465 	 * @returns {Garmin.DeviceControl.FILE_TYPES} fileType, null if no mapping exists.
1466      */
1467     mapTcxToFit: function(aTcxType) {
1468         var theMappedType = null;
1469         switch(aTcxType) {
1470             case Garmin.DeviceControl.FILE_TYPES.tcx:
1471             case Garmin.DeviceControl.FILE_TYPES.tcxDir:
1472                 theMappedType = Garmin.DeviceControl.FILE_TYPES.fitActivity;
1473                 break;
1474             case Garmin.DeviceControl.FILE_TYPES.crs:
1475             case Garmin.DeviceControl.FILE_TYPES.crsDir:
1476                 theMappedType = Garmin.DeviceControl.FILE_TYPES.fitCourse;
1477                 break;
1478             case Garmin.DeviceControl.FILE_TYPES.wkt:
1479                 theMappedType = Garmin.DeviceControl.FILE_TYPES.fitWorkout;
1480                 break;
1481         }
1482         return theMappedType;
1483     },
1484 	
1485 	/** Checks if the device lists the given datatype as a supported writeable type.
1486 	 * Plugin version affects the results of this function.  The latest plugin version is encouraged.
1487 	 * 
1488 	 * Internal file types (such as directory types) are NOT detected as supported write types.
1489 	 */
1490 	checkDeviceWriteSupport: function(datatype) {
1491 	    var isDatatypeSupported = false;
1492 
1493 		// The selected device
1494 		var device = this._getDeviceByNumber(this.deviceNumber);
1495 		
1496 		// Don't include types that aren't in the Device XML
1497 		if ( datatype == Garmin.DeviceControl.FILE_TYPES.binary 
1498 		  || datatype == Garmin.DeviceControl.FILE_TYPES.gpi) {
1499 		    isDatatypeSupported = true;
1500 		} else {
1501             isDatatypeSupported = device.supportDeviceDataTypeWrite(datatype);
1502 		}
1503 		
1504 		return isDatatypeSupported;
1505 	},
1506 	
1507 	/** Retrieve a device from the list of found devices by device number. 
1508 	 * @return Garmin.Device
1509 	 */
1510 	_getDeviceByNumber: function(deviceNum) {
1511 		for( var index = 0; index < this.devices.length; index++) {
1512 			if( this.devices[index].getNumber() == deviceNum){
1513 				return this.devices[index];
1514 			}
1515 		}		
1516 	},
1517 	
1518    /* Internal helper to properly append directory listings
1519     * @param {String} Raw Directory V1 XML
1520 	* @private
1521 	*/
1522 	_appendDirXml: function(aXml)
1523 	{
1524 		if(!this.gpsDataString) {
1525 			this.gpsDataString = aXml;
1526 			this.gpsData = Garmin.XmlConverter.toDocument(this.gpsDataString);
1527 		} else {
1528 			//merge
1529 			this.gpsData = Garmin.DirectoryUtils.merge(this.gpsDataString, aXml);
1530 			this.gpsDataString = Garmin.XmlConverter.toString(this.gpsData);
1531 		}
1532 	},
1533 	
1534 	/** String representation of instance.
1535 	 * @type String
1536      */	
1537 	toString: function() {
1538 	    return "Garmin Javascript GPS Controller managing " + this.numDevices + " device(s)";
1539 	}
1540 };
1541 
1542 /** Dedicated browser support singleton.
1543  */
1544 var BrowserSupport = {
1545     /** Determines if the users browser is currently supported by the plugin
1546      * @type Boolean
1547      */	 
1548 	isBrowserSupported: function() {
1549 		// TODO Move this out to plugin layer? 
1550 		return ( (BrowserDetect.OS == "Windows" && 
1551 					(BrowserDetect.browser == "Firefox" 
1552 					|| BrowserDetect.browser == "Mozilla" 
1553 					|| BrowserDetect.browser == "Explorer"
1554 					|| BrowserDetect.browser == "Safari"))
1555 				|| (BrowserDetect.OS == "Mac" && 
1556 					(BrowserDetect.browser == "Firefox" 
1557 					|| BrowserDetect.browser == "Mozilla"
1558 					|| BrowserDetect.browser == "Safari")) );
1559 	}
1560 };
1561 
1562 /** Constants defining possible errors messages for various errors on the page
1563  */
1564 Garmin.DeviceControl.MESSAGES = {
1565 	deviceControlMissing: "Garmin.DeviceControl depends on the Garmin.DevicePlugin framework.",
1566 	missingPluginTag: "Plug-In HTML tag not found.",
1567 	browserNotSupported: "Your browser is not supported by the Garmin Communicator Plug-In.",
1568 	pluginNotInstalled: "Garmin Communicator Plugin NOT detected.",
1569 	outOfDatePlugin1: "Your version of the Garmin Communicator Plug-In is out of date.<br/>Required: ",
1570 	outOfDatePlugin2: "Current: ",
1571 	updatePlugin1: "Your version of the Garmin Communicator Plug-In is not the latest version. Latest version: ",
1572 	updatePlugin2: ", current: ",
1573 	pluginNotUnlocked: "Garmin Plugin has not been unlocked",
1574 	noDevicesConnected: "No device connected, can't communicate with device.",
1575 	invalidFileType: "Cannot process the device file type: ",
1576 	incompleteRead: "Incomplete read, cannot get compressed format.",
1577 	unsupportedReadDataType: "Your device does not support reading of the type: ",
1578 	unsupportedWriteDataType: "Your device does not support writing of the type: "
1579 };
1580 
1581 /** Constants defining possible states when you poll the finishActions
1582  */
1583 Garmin.DeviceControl.FINISH_STATES = {
1584 	idle: 0,
1585 	working: 1,
1586 	messageWaiting: 2,
1587 	finished: 3	
1588 };
1589 
1590 /** Constants defining possible file types associated with read and write methods.  File types can
1591  * be accessed in a static way, like so:<br/>
1592  * <br/>
1593  * Garmin.DeviceControl.FILE_TYPES.gpx<br/>
1594  * <br/>
1595  * NOTE: 'gpi' is being deprecated--please use 'binary' instead for gpi and other binary data. 
1596  */
1597 Garmin.DeviceControl.FILE_TYPES = {
1598 	gpx:               "GPSData",
1599 	tcx:               "FitnessHistory",
1600 	gpi:               "gpi", //deprecated, use binary instead
1601 	crs:               "FitnessCourses",
1602 	wkt:               "FitnessWorkouts",
1603 	goals:             "FitnessActivityGoals",
1604 	tcxProfile:        "FitnessUserProfile",
1605 	binary:            "BinaryData", // Not in Device XML, so writing this type is "supported" for all devices. For FIT data, use fitFile.
1606 	voices:            "Voices",
1607 	nlf:               "FitnessNewLeaf",
1608 	fit:               "FITBinary",
1609 	fitSettings:       "FIT_TYPE_2",
1610 	fitSport:          "FIT_TYPE_3",
1611 	fitActivity:       "FIT_TYPE_4",
1612 	fitWorkout:        "FIT_TYPE_5",
1613 	fitCourse:         "FIT_TYPE_6",
1614 	fitHealthData:	   "FIT_TYPE_9",
1615 	
1616 	// The following types are internal types used by the API only and cannot be found in the Device XML.
1617 	// NOTE: When adding or removing types to this internal list, modify checkDeviceReadSupport() accordingly.
1618 	readableDir:       "ReadableFilesDirectory",
1619 	tcxDir: 		   "FitnessHistoryDirectory",
1620 	crsDir: 		   "FitnessCoursesDirectory",
1621 	gpxDir: 		   "GPSDataDirectory",
1622 	tcxDetail:         "FitnessHistoryDetail",
1623 	crsDetail: 		   "FitnessCoursesDetail",
1624 	gpxDetail:         "GPSDataDetail",
1625 	deviceXml: 	       "DeviceXml",
1626 	fitDir:     	   "FitDirectory",
1627 	fitFile:    	   "FitFile",
1628 	firmware:          "Firmware"
1629 };
1630 
1631 /** Constants defining the strings used by the Device.xml to indicate 
1632  * transfer direction of each file type
1633  */
1634 Garmin.DeviceControl.TRANSFER_DIRECTIONS = {
1635 	read:              "OutputFromUnit",
1636 	write:             "InputToUnit",
1637 	both:              "InputOutput"
1638 };
1639 
1640 /** Encapsulates the data provided by the device for the current process' progress.
1641  * Use this to relay progress information to the user.
1642  * @class Garmin.TransferProgress
1643  * @constructor 
1644  */
1645 Garmin.TransferProgress = Class.create();
1646 Garmin.TransferProgress.prototype = {
1647 	initialize: function(title) {
1648 		this.title = title;
1649 		this.text = new Array();
1650 		this.percentage = null;
1651 	},
1652 	
1653 	addText: function(textString) {
1654 		this.text.push(textString);
1655 	},
1656 
1657     /** Get all the text entries for the transfer
1658      * @type Array
1659      */	 
1660 	getText: function() {
1661 		return this.text;
1662 	},
1663 
1664     /** Get the title for the transfer
1665      * @type String
1666      */	 
1667 	getTitle: function() {
1668 		return this.title;
1669 	},
1670 	
1671 	setPercentage: function(percentage) {
1672 		this.percentage = percentage;
1673 	},
1674 
1675     /** Get the completed percentage value for the transfer
1676      * @type Number
1677      */
1678 	getPercentage: function() {
1679 		return this.percentage;
1680 	},
1681 
1682     /** String representation of instance.
1683      * @type String
1684      */	 	
1685 	toString: function() {
1686 		var progressString = "";
1687 		if(this.getTitle() != null) {
1688 			progressString += this.getTitle();
1689 		}
1690 		if(this.getPercentage() != null) {
1691 			progressString += ": " + this.getPercentage() + "%";
1692 		}
1693 		return progressString;
1694 	}
1695 };
1696 
1697 
1698 /** Encapsulates the data to display a message box to the user when the plug-in is waiting for feedback
1699  * @class Garmin.MessageBox
1700  * @constructor 
1701  */
1702 Garmin.MessageBox = Class.create();
1703 Garmin.MessageBox.prototype = {
1704 	initialize: function(type, text) {
1705 		this.type = type;
1706 		this.text = text;
1707 		this.buttons = new Array();
1708 	},
1709 
1710     /** Get the type of the message box
1711      * @type String
1712      */	 
1713 	getType: function() {
1714 		return this.type;
1715 	},
1716 
1717     /** Get the text entry for the message box
1718      * @type String
1719      */	 
1720 	getText: function() {
1721 		return this.text;
1722 	},
1723 
1724     /** Get the text entry for the message box
1725      */	 
1726 	addButton: function(caption, value) {
1727 		this.buttons.push({caption: caption, value: value});
1728 	},
1729 
1730     /** Get the buttons for the message box
1731      * @type Array
1732      */	 
1733 	getButtons: function() {
1734 		return this.buttons;
1735 	},
1736 	
1737 	getButtonValue: function(caption) {
1738 		for(var i=0; i< this.buttons.length; i++) {
1739 			if(this.buttons[i].caption == caption) {
1740 				return this.buttons[i].value;
1741 			}
1742 		}
1743 		return null;
1744 	},
1745 
1746     /**
1747 	 * @type String
1748      */	 
1749 	toString: function() {
1750 		return this.getText();
1751 	}
1752 };
1753 
1754 /*
1755  * Dynamic include of required libraries and check for Prototype
1756  * Code taken from scriptaculous (http://script.aculo.us/) - thanks guys!
1757 var GarminDeviceControl = {
1758 	require: function(libraryName) {
1759 		// inserting via DOM fails in Safari 2.0, so brute force approach
1760 		document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
1761 	},
1762 
1763 	load: function() {
1764 		if((typeof Prototype=='undefined') || 
1765 			(typeof Element == 'undefined') || 
1766 			(typeof Element.Methods=='undefined') ||
1767 			parseFloat(Prototype.Version.split(".")[0] + "." +
1768 			Prototype.Version.split(".")[1]) < 1.5) {
1769 			throw("GarminDeviceControl requires the Prototype JavaScript framework >= 1.5.0");
1770 		}
1771 
1772 		$A(document.getElementsByTagName("script"))
1773 		.findAll(
1774 			function(s) {
1775 				return (s.src && s.src.match(/GarminDeviceControl\.js(\?.*)?$/))
1776 			}
1777 		)
1778 		.each(
1779 			function(s) {
1780 				var path = s.src.replace(/GarminDeviceControl\.js(\?.*)?$/,'../../');
1781 				var includes = s.src.match(/\?.*load=([a-z,]*)/);
1782 				var dependencies = 'garmin/device/GarminDevicePlugin' +
1783 									',garmin/device/GarminDevice' +
1784 									',garmin/util/Util-XmlConverter' +
1785 									',garmin/util/Util-Broadcaster' +
1786 									',garmin/util/Util-DateTimeFormat' +
1787 									',garmin/util/Util-BrowserDetect' +
1788 									',garmin/util/Util-PluginDetect' +
1789 									',garmin/device/GarminObjectGenerator';
1790 			    (includes ? includes[1] : dependencies).split(',').each(
1791 					function(include) {
1792 						GarminDeviceControl.require(path+include+'.js') 
1793 					}
1794 				);
1795 			}
1796 		);
1797 	}
1798 }
1799 
1800 GarminDeviceControl.load();
1801  */
1802 
1803