/* Copyright 2009 Palm, Inc.  All rights reserved. */

var Bluetooth = Class.create({
	
	initialize: function() {
		Mojo.Log.info("====== Bluetooth instantiated ======");
		// Initialize variables available to assistants
		this.serviceUp = false;
		this.isRadioOn = false;
		this.isRadioTurningOn = false;
		this.trustedDevices = [];
		this.deviceCoD = {};
		this.deviceName = {};
		this.deviceAwaitingConnection = null;
		this.isFullScreen = false;
		this.btProfileStatus = {},
		this.btProfiles = ['hfg', 'a2dp','pan'],
		this.uiDebugMode = 0;		// 1 = prepoplulate lists and allow BT to work without being connected to an actual device
		
		// Initialize internal variables
		this.initDelay = 0;
		this.assistants = [];
		this.serviceCalls = [];
		this.gapNotificationServiceCall = null;
		this.profNotificationServiceCall = null;
		this.oppNotificationServiceCall = null;
		this.pairingServiceCall = null;
		this.pairingDirection = null;
		this.deviceAwaitingConnection = null;
		this.newlyPairedDevice = null;
		this.activeScene = null;
		this.passkeyDialog = null;
		this.allowInboundDialog = null;
		this.PIN = {};

		this.monitorNotificationHandler = this.handleMonitorNotifications.bind(this);
		this.profGapNotificationHandler = this.handleNotifications.bind(this);
		this.pairingNotificationHandler = this.handlePairingNotification.bind(this);
		this.oppNotificationHandler = this.handleOppNotification.bind(this);

		this.PinAttributes = {
			requiresEnterKey: true,
			focus: true,
			modifierState: Mojo.Widget.numLock,
			maxLength: 16,
			hintText: $L("Passkey"),
			changeOnKeyPress: true,
		};
	},
	
	registerAssistant: function(assistant) {
		// Register the assistant for notification callbacks
		this.assistants.push(assistant);
		Mojo.Log.info("====== registerAssistant: " + this.assistants.length);
	},
	
	unregisterAssistant: function(assistant) {
		// Unregister the assistant for notification callbacks
		var count = 0;
		for (var i = 0; i < this.assistants.length; i++) {
			if (this.assistants[i] == assistant) 
				this.assistants[i] = null;
			else {
				if (this.assistants[i] != null)
					count++;
			}
		}
		// Are all the assistants unregistered?  If so, unsubscribe too because this app assistant is about to disappear
		Mojo.Log.info("====== unregisterAssistant: " + count);
		if (count == 0) {
			this.subscribeNotifications("unregister");
			this.subscribeMonitorNotifications("unregister");
			this.allowPairing(false);
		}
	},
	
	notifyAssistants: function(event, data) {
		// Sent the event to the assistant for it to handle
		Mojo.Log.info("====== sending event: " + event);
		this.assistants.each(function(cb) {if (cb) cb(event, data)});
	},
	
	// Set the active scene so that passkey dialogs pop-up on the correct scene
	setActiveScene: function(scene) {
		this.activeScene = scene;
	},
	
	// Set whether the app is full screen or not
	setFullScreen: function(fullScreen) {
		this.isFullScreen = fullScreen;
	},

	startBT: function() {
		if (!this.BtStarted && !this.uiDebugMode) {
			this.BtStarted = true;
			Mojo.Log.info("====== startBT ======");

			// Connect to the service, register for notifications and get current status
			this.btServiceStart();
			return false;
		}
		else {
			// The service has started.  Let the scene know
			return true;
		}
	},
	
	btServiceStart: function() {
		// Register to be notified when the BT Service goes up and down
		this.btMonitorServiceSession = this.btMojoService("palm://com.palm.bus/signal/registerServerStatus", 
			{serviceName: 'com.palm.btmonitor', subscribe:true}, function(payload) {
			// Is the service running?
			if (payload.connected == true) {
				Mojo.Log.error("btServiceStart: BT Monitor service is now up (not an error)");
				this.monitorServiceUp = true;
				
				// Default to the case that happens if BT service is up: the radio must be on and we should get the state
				this.getRadioState = true;
				this.isRadioOn = true;
				
				// Subscribe for on/off notifications
				this.subscribeMonitorNotifications("register");

				// Get the radio state (on/off or turning on/off)
				this.btMojoService("palm://com.palm.btmonitor/monitor/getradiostate", null, 
					function(payload) {
						switch (payload.radio) {
							case 'turningon':
								this.isRadioTurningOn = true;
								this.isRadioOn = false;
								this.getRadioState = false;
								break;
								
							case 'turningoff':
							case 'off':
								// The radio is not "on", so wait until 'radioon' notification is received before initializing the state
								this.isRadioOn = false;
								this.getRadioState = false;
								this.notifyAssistants(Bluetooth.serviceDown);
								break;
						}
						this.radioStateObtained = true;
					}.bind(this));
			}
			else {
				Mojo.Log.error("btServiceStart: BT Monitor service has gone down");
				this.isRadioOn = false;
				this.isRadioTurningOn = false;
				this.serviceUp = false;
				this.monitorServiceUp = false;
				this.notifyAssistants(Bluetooth.serviceDown);

				// Unsubscribe for on/off notifications
				this.subscribeMonitorNotifications("unregister");
			} 
		}.bind(this));
		
		// Register to be notified when the BT Engine goes up and down
		this.btServiceSession = this.btMojoService("palm://com.palm.bus/signal/registerServerStatus", 
			{serviceName: 'com.palm.bluetooth', subscribe:true}, function(payload) {
			// Is the service running?
			if (payload.connected == true) {
				Mojo.Log.error("btServiceStart: BT service is now up (not an error)");
				this.serviceUp = true;
				this.isRadioTurningOn = false;

				// Get the radio status now if the radio was already on, otherwise get it once 'radioon' is broadcast
				if (this.getRadioState) {
					this.getRadioState = false;
					Mojo.Log.info("====== Radio was on, so getting state now");
					// Wait a bit before initializing 
					setTimeout(this.init.bind(this), this.initDelay);
				}
			
				// Register to handle pairing and general notifications					
				this.subscribeNotifications("register");
			}
			else {
				Mojo.Log.error("btServiceStart: BT service has gone down");
				this.serviceUp = false;
				if (this.radioStateObtained && !this.isRadioTurningOn) {
					this.notifyAssistants(Bluetooth.serviceDown);
					
					// Close the pairing dialog box if it is open
					this.closePairingDialogBox();
					
					// Simulate the radio turning off
					this.handleMonitorNotifications({notification: 'notifnradiooff'});
					
					// Unregister for notifications
					this.subscribeNotifications("unregister");
					this.allowPairing(false);
					
					// Clear all profile status so they will be obtained once the BT Engine comes up
					this.btProfiles.each(function(profile) {
						if (this.btProfileStatus[profile]) 
							delete this.btProfileStatus[profile];
					}.bind(this));
					
					// Delay initialization to prevent race with status bar
					this.initDelay = 500;
				}
				else {
					// The radio is in the process of turning on
					this.notifyAssistants(Bluetooth.radioTurningOn);
				}
			} 
		}.bind(this));

		// Register to be notified when the power daemon goes up and down
		this.btMojoService("palm://com.palm.bus/signal/registerServerStatus", 
			{serviceName: 'com.palm.power', subscribe:true}, function(payload) {
			// Is the service running?
			if (payload.connected == true) {
				this.btMojoService("palm://com.palm.power/com/palm/power/identify", {"subscribe":true,"clientName":"mojo-app-bluetooth"}, 
					function(payload) {
						powerClientId = payload.clientId;
						this.btMojoService("palm://com.palm.power/com/palm/power/prepareSuspendRegister", {"register":true,"clientId":payload.clientId, subscribe:true}, null, true);
						this.btMojoService("palm://com.palm.bus/signal/addmatch", {"category":"/com/palm/power","method":"prepareSuspend", subscribe:true}, this.handleSuspendNotification.bind(this), true);
						this.btMojoService("palm://com.palm.bus/signal/addmatch", {"category":"/com/palm/power","method":"resume", subscribe:true}, this.handleResumeNotification.bind(this), true);
					}.bind(this));
			}
		}.bind(this));
	},
	
	init: function() {
		Mojo.Log.info("====== init ======");
		
		// Get the state of all profiles which, in turn, gets the trusted devices
		this.getConnectedStateOfAllProfiles();
	},

	// Get the connected state of all supported profiles, one after the other
	getConnectedStateOfAllProfiles: function() {
		Mojo.Log.info("====== getConnectedStateOfAllProfiles ======");
		// Iterate through all the profiles, and get the status of those that aren't known
		for (var i=0; i< this.btProfiles.length; i++) {
			var profile = this.btProfiles[i];
			if (!this.btProfileStatus[profile]) {
				this.btMojoService("palm://com.palm.bluetooth/prof/profgetstate",
						{"profile":profile}, function(payload){
							if (payload.status)
								this.btProfileStatus[profile] = payload;
	
							// Get the next profile now
							if (!payload.serviceName)
								this.getConnectedStateOfAllProfiles();
						 }.bind(this));
				// Wait for the callback before sending the next request
				return;
			}
		}
		Mojo.Log.info("getConnectedStateOfAllProfiles: "+ Object.toJSON(this.btProfileStatus));
	
		// Get a list of all trusted devices if done getting all the profiles
		this.getTrustedDevices();			
	},

	// Get the list of trusted devices
	getTrustedDevices: function() {
		Mojo.Log.info("====== getTrustedDevices ======");
	 	this.btMojoService("palm://com.palm.bluetooth/gap/gettrusteddevices", null,
					function(payload) {
						Mojo.Log.info("====== Trusted Devices: " + Object.toJSON(payload));
						
						if (!payload.trusteddevices)
							return;
						
						// Save the list of trusted devices
						this.trustedDevices = payload.trusteddevices;

						// Loop through the list of trusted devices						
						for (var i = 0; i < this.trustedDevices.length; i++) {
							// Save the COD so that it can be used later
							if (this.trustedDevices[i].cod) 
								this.deviceCoD[this.trustedDevices[i].address] = this.trustedDevices[i].cod;
							else {
								this.btMojoService("palm://com.palm.bluetooth/prof/profdisassociate", {error: 'Why are you sending us a CoD of zero???'}, null);
								Mojo.Log.error("CoD is zero for " + this.trustedDevices[i].address);
							}
							
							// Save the name so that it can be used later, if necessary
							this.deviceName[this.trustedDevices[i].address] = this.trustedDevices[i].name;
						}
		
						// If there is a newly paired device (but no timer to connect to it) then connect now
						Mojo.Log.info("====== getTrustedDevices: newlyPairedDevice="+this.newlyPairedDevice + " connectToNewlyPairedDeviceTimer="+this.connectToNewlyPairedDeviceTimer);
						if (this.newlyPairedDevice && !this.connectToNewlyPairedDeviceTimer)
							this.connectToNewlyPairedDevice();
						
						// Let the UI know that trusted devices have been obtained
						this.notifyAssistants(Bluetooth.trustedDevices);
						
						// Let the UI know that the radio is on and all state has been obtained
						this.notifyAssistants(Bluetooth.radioOnAndReady);
						
						// The UI can be drawn now
						this.notifyAssistants(Bluetooth.allowActivation);
					}.bind(this));
	},
	
	// Set the visibility and connectability
	setVisibilityAndConnectability: function(visible, connectable) {
		Mojo.Log.info("====== setVisibilityAndConnectability: connectable="+ connectable + " visible=" + visible);
		this.btMojoService("palm://com.palm.bluetooth/gap/setscanstate", {visible: visible, connectable: connectable}, null);
	},
	
	getDeviceFromTrustedList: function(addr) {
		if (!addr)
			return undefined;
		for (var i = 0; i< this.trustedDevices.length; i++) {
			if (this.trustedDevices[i].address == addr) {
				return this.trustedDevices[i];
			}									
		}
		return undefined;	
	},

	// Turn the BT radio off or on
	toggleRadioState: function() {
		Mojo.Log.info("====== toggleRadioState: radio was on = " + this.isRadioOn);
		if (this.isRadioOn) {
			this.btMojoService("palm://com.palm.btmonitor/monitor/radiooff", null, null);
			
			// Pairing isn't happening anymore
			this.pairingDirection = null;
			
			// All profiles are disconnected
			this.btProfiles.each(function(profile){
				this.btProfileStatus[profile].status = 'disconnected';
			}.bind(this));
			
			// Let all the scenes know that the radio is turning off
			this.notifyAssistants(Bluetooth.radioTurningOff);
		}
		else
		{
			// Turn the radio on
			this.btMojoService("palm://com.palm.btmonitor/monitor/radioon", {visible: true, connectable: true}, null);		

			// Let all the scenes know that the radio is turning on
			this.notifyAssistants(Bluetooth.radioTurningOn);
		}
	},
	
	// Cancel pairing if it is inbound
	cancelInboundPairing: function() {
		Mojo.Log.info("====== cancelInboundPairing: direction = " + this.pairingDirection);
		if (this.pairingDirection == "inbound") {
			this.sendPairCancel(this.pairDevAddress);
			this.pairingDirection = null;
		}
	},

	// Cancel pairing if it is outbound
	cancelOutboundPairing: function() {
		Mojo.Log.info("====== cancelOutboundPairing: direction = " + this.pairingDirection);
		if (this.pairingDirection == "outbound") {
			this.sendPairCancel(this.pairDevAddress);
			this.pairingDirection = null;
		}
	},

	// Start inquiry
	findDevices: function() {
		Mojo.Log.info("====== findDevices ======");
		// Don't start inquiry if there is a pairing attempt in progress
		if (this.pairingDirection)
			return;
		return this.btMojoService("palm://com.palm.bluetooth/gap/finddevices",
				{cod:1024, seconds:10, subscribe:true},
				function(payload) {
					if (payload.founddevice == "device") {
						// Not interested in devices that aren't audio devices
						if (!isAudioDevice(payload.cod))
							return;
							
						// Save the CoD so that it can be used later, if necessary
						if (payload.cod)
							this.deviceCoD[payload.address] = payload.cod;

						// Was a name returned by the inquiry?  If so, it should be updated
						if (payload.name) {
							payload.NAMESTATE = "UpdateName";
							// Save the name so that it can be used later, if necessary
							this.deviceName[payload.address] = payload.name;
						}
						else {
							payload.name = "";
							payload.NAMESTATE = "GetName";
						}

						// See if the device is trusted
						var trusted = this.getDeviceFromTrustedList(payload.address);

						if (trusted) {
							// Skip if connected
							if (trusted.CONNECTSTATE == "connected")
								return;
								
							// Get the name from the trusted list
							payload.name = trusted.name;
							// Update the name if it hasn't been renamed
							if (trusted.renamed)
								payload.NAMESTATE = "NameOkay";
							else
								payload.NAMESTATE = "UpdateName";	
							payload.TRUSTED = 'trusted';	
						}

						// Let the UI know that a device has been discovered
						this.notifyAssistants(Bluetooth.inqDeviceFound, payload);
					}
					else if (payload.founddevice == "done") {
						// Let the UI know that inquiry has completed
						this.notifyAssistants(Bluetooth.inqComplete, payload);
					}
				}.bind(this));
	},

	handleMonitorNotifications: function(payload) {
		if (!payload || !payload.notification)
			return;
		Mojo.Log.info("====== handleMonitorNotifications: notification = " + payload.notification);
		switch (payload.notification) {
			case 'notifnradioturningon':
				// Something else (probably Status bar) has turned the radio on
				this.notifyAssistants(Bluetooth.radioTurningOn);
				break;
			case 'notifnradioon':
				// The Bluetooth radio is on
				this.isRadioOn = true;
				// The service has just come up so initialize it.  Wait a bit to avoid conflict with status bar
				setTimeout(this.init.bind(this), 500);
				break;
			case 'notifnradiooff':			
				// The Bluetooth radio is off
				this.isRadioOn = false;
				this.notifyAssistants(Bluetooth.radioOff);
				// Allow scene activation since the radio is off
				this.notifyAssistants(Bluetooth.allowActivation);
				break;
		}
	},

	// Handle general notifications from the BT Engine
	handleNotifications: function(payload) {
		if (!payload || !payload.notification)
			return;
		Mojo.Log.info("====== handleNotifications: notification = " + payload.notification);
	
		// Find the device
		var device = this.getDeviceFromTrustedList(payload.address);
		
		switch (payload.notification) {
			case 'notifnconnecting':
				// Update the profile connected status
				payload.status = 'connecting';
				this.btProfileStatus[payload.profile] = payload;
					
				// Show the spinner if there isn't a pending connection
				if (device && !this.deviceAwaitingConnection) {
					this.updateDeviceConnectedState(payload.address);
					this.notifyAssistants(Bluetooth.updateTrustedStatus, payload.address);
					// If the device fails to connect then show an error message
					device.showErrorIfConnectFails = true;
				}
				break;
			case 'notifndisconnecting':
				payload.status = 'disconnecting';
				this.btProfileStatus[payload.profile] = payload;
				// Remove the spinner or connected icon from the screen, in anticipation of the disconnect notification
				if (device) {
					this.updateDeviceConnectedState(payload.address);
					this.notifyAssistants(Bluetooth.updateTrustedStatus, payload.address);
				}
				break;
			case 'notifnconnected':
				// Update the profile connected status
				if (payload.error == 0) {
					payload.status = 'connected';
					this.btProfileStatus[payload.profile] = payload;
					
					// If this is the device we are waiting on to make a connection to then clear the flag
					if (payload.address == this.newlyPairedDevice)
						this.newlyPairedDevice = null;
					
					// The device is connected, but if there is a pending connection to a
					// different device then disconnect this connection
					if (this.deviceAwaitingConnection && this.deviceAwaitingConnection != payload.address) {
						if (payload.profile == 'hfg' || payload.profile == 'a2dp') {
							this.disconnectProfile(payload.profile);
							break;
						}
					}
					
					// Show the connected status on the UI			
					this.updateDeviceConnectedState(payload.address);
					this.notifyAssistants(Bluetooth.updateTrustedStatus, payload.address);
					
					// Don't show a connect error message for this device (if another profile fails)
					if (device && device.showErrorIfConnectFails)
						delete device.showErrorIfConnectFails;
					break;
				}
				
				// There was a connect error
				// Is a device already connected on the profile?
				if (payload.alreadyconnectedaddr) {
					Mojo.Log.info("====== Profile " + payload.profile + " is already connected to " + payload.alreadyconnectedaddr);
					// If the connected device is not the same the the device a connection was
					// requested for, then display a "Unable to connect" message
					if (payload.address != payload.alreadyconnectedaddr)
						this.notifyAssistants(Bluetooth.cannotConnect);
					// Mark the profile as connected to the given address
					this.btProfileStatus[payload.profile].status = 'connected';
					this.btProfileStatus[payload.profile].address = payload.alreadyconnectedaddr;

					// Show the connected status on the UI			
					this.updateDeviceConnectedState(payload.address);
					this.notifyAssistants(Bluetooth.updateTrustedStatus, payload.address);
					break;
				}
				
				// Should "Connect error" be displayed?
				if (!this.deviceAwaitingConnection && this.isRadioOn && device && device.showErrorIfConnectFails) {
					// Update this profile to 'disconnected'
					this.btProfileStatus[payload.profile].status = 'disconnected';
					// Update the connected state of this device
					this.updateDeviceConnectedState(payload.address);
					// If the device is disconnected then display the connect error message
					switch (payload.profile) {
						case 'hfg':
						case 'a2dp':
							this.notifyAssistants(Bluetooth.cannotConnect);
							break;
						case 'opp' :
							// If the error code is 6, then either OPP is not supported or car kit is not in receive mode.
							if (payload.error == 6) 
								this.notifyAssistants(Bluetooth.oppNotSupported);
							else  
								this.notifyAssistants(Bluetooth.cannotConnectOPP);
							break;
					}
					delete device.showErrorIfConnectFails;
				}
				
				// Intentional fall-through for connect error
				
			case 'notifndisconnected':
				// Update the profile connected status
				payload.status = 'disconnected';
				this.btProfileStatus[payload.profile] = payload;
				
				Mojo.Log.info("====== notifndisconnected: deviceAwaitingConnection=" + this.deviceAwaitingConnection + " btProfileStatus="+Object.toJSON(this.btProfileStatus));
				// If there is a pending audio connection and this device is no longer
				// connected on a audio profile then connect to the pending device
				if (this.deviceAwaitingConnection && this.btProfileStatus['hfg'].status == "disconnected" && this.btProfileStatus['a2dp'].status == "disconnected") {
					this.connectAudio(this.deviceAwaitingConnection);
					this.deviceAwaitingConnection = null;			
				}
				
				// Update the UI now (if the device exists)
				if (device) {
					this.updateDeviceConnectedState(payload.address);
					this.notifyAssistants(Bluetooth.updateTrustedStatus, payload.address);
				}
				break;
				
			case 'notifndevremoved':
				// Remove the device from the trusted list
				var i;
				for (i = 0; i< this.trustedDevices.length; i++) {
					if (this.trustedDevices[i].address == payload.address)
						break;
				}
			
				if (i < this.trustedDevices.length) {
					// Remove the device from the list
					this.trustedDevices.splice(i, 1);
					// Let all the scenes know that a device was removed
					this.notifyAssistants(Bluetooth.deviceDeleted, payload.address);
				}
				
				break;
				
			case 'notifndevrenamed':
				// Let all the scenes know that a device was renamed
				this.notifyAssistants(Bluetooth.deviceRenamed, payload.address);
				break;
		}
	},

	// Handle pairing notifications from BT Engine
	handlePairingNotification: function(payload) {
		// Ignore subscribe notifications
		if (payload.returnValue)
			return;
		Mojo.Log.info("====== handlePairingNotification: this.pairingDirection = " + this.pairingDirection);
		Mojo.Log.info("====== handlePairingNotification: " + Object.toJSON(payload));
		
		// Ignore anything that isn't a notification or doesn't have an address
		if (!payload.notification || !payload.address)
			return;
			
		// If a CoD wasn't supplied then get it from the saved values, otherwise save the value
		if (!payload.cod) {
			payload.cod = this.deviceCoD[payload.address];
			if (!payload.cod)
				Mojo.Log.error("handlePairingNotification: No COD!, but pairing anyway");
		}
		else 
			this.deviceCoD[payload.address] = payload.cod;
			
		// If a name hasn't been supplied then hopefully we've seen the name somewhere else before (inquiry, trusted)
		if (!payload.name) {
			payload.name = this.deviceName[payload.address];
			
			// If the name is still unknown then retrieve it from the device
			if (!payload.name) {
				this.getPairingDeviceName(payload);
				// Handle the notification once the name has been retrieved
				return;
			}
		}	
				
		switch (payload.notification) {
			case 'notifnpincoderequest':
				// Make a note that this is "regular pairing" (not Secure Simple Pairing)
				this.pairingUsesSSP = false;
				
				// If currently pairing and another pair request appears then reject it
				if (this.pairingDirection && this.pairDevAddress && payload.address != this.pairDevAddress) {
					this.sendPairCancel(payload.address);
					break;
				}
				
				// Is this an inbound pairing attempt?
				if (!this.pairingDirection) {				
					this.pairingDirection = "inbound";
					this.pairDevAddress = payload.address;
					// Auto-pairing should not be attempted
			        this.attemptAutoPair = false;
	 				this.promptForPairingConfirmation(payload);
					break;				
				}
				
				// Attempt to auto-pair if an attempt hasn't already been made
				if (this.attemptAutoPair) {
					// Send a pass key of "0000"
					this.sendPasskey("0000");
					this.autoPairAttempted = true;	
					this.attemptAutoPair = false;
					break;				
				}
	
				// Prompt the user for the passkey
				this.promptForPasskey(payload);					
				break;
				
			case 'notifnssppincoderequest':
				// Make a note that this is Secure Simple Pairing
				this.pairingUsesSSP = true;
	
				// If attempting outbound pairing and another pair request appears then reject it
				if (this.pairingDirection && this.pairDevAddress && payload.address != this.pairDevAddress) {
					this.sendPairCancel(payload.address);
					break;
				}			
				
				// Is this an inbound pairing attempt?
				if (!this.pairingDirection) {
					this.pairingDirection = "inbound";
					this.pairDevAddress = payload.address;
					// Auto-pairing should not be attempted
			        this.attemptAutoPair = false;
	 				this.promptForPairingConfirmation(payload);
					break;				
				}
	
				// Attempt to auto-pair if an attempt hasn't already been made
				if (this.attemptAutoPair) {
					// Send a pass key of "0000"
					this.sendPasskey("0000");
					this.autoPairAttempted = true;	
					this.attemptAutoPair = false;
					break;						
				}
				
				// Prompt the user for the passkey
				this.promptForPasskey(payload);					
				break;
				
			case 'notifnssppincodecheckrequest':
				// Make a note that this is Secure Simple Pairing
				this.pairingUsesSSP = true;
	
				// If attempting outbound pairing and another pair request appears then reject it
				if (this.pairingDirection && this.pairDevAddress && payload.address != this.pairDevAddress) {
					this.sendPairCancel(payload.address);
					break;
				}			
	
				// Is this an inbound pairing attempt?
				if (!this.pairingDirection) {
					this.pairingDirection = "inbound";
					this.pairDevAddress = payload.address;
	 				this.promptForPairingConfirmation(payload);
					break;				
				}
				
				// Display the dialog to confirm or enter the passkey
				if (payload.onlycompare)
					this.promptToConfirmSSPPasskey(payload);				
				else
					this.promptToConfirmSSPRemotePasskey(payload);
				break;
			
			case 'notifnsspjustworks':
				// Make a note that this is Secure Simple Pairing
				this.pairingUsesSSP = true;
	
				// If attempting outbound pairing and another pair request appears then reject it
				if (this.pairingDirection && this.pairDevAddress && payload.address != this.pairDevAddress) {
					this.sendPairCancel(payload.address);
					break;
				}			
	
				// Is this an inbound pairing attempt?
				if (!this.pairingDirection) {
					this.pairingDirection = "inbound";
					this.pairDevAddress = payload.address;
	 				this.promptForPairingConfirmation(payload);
					break;				
				}
				
				// Accept the "just works" pairing
				this.sendPasskey(null);
				break;
				
			case 'notifnpaired':
				Mojo.Log.info("notifnpaired error: "+payload.error + " payload.address=" + payload.address+ " pairDevAddress=" + this.pairDevAddress);
				// Refresh the list of trusted devices (one may have been added, or removed on error)
				this.getTrustedDevices();

				if (payload.address == this.pairDevAddress) {
					if (payload.error == 0) {
						// Pairing was successful
						Mojo.Log.info("======  Successfully paired: addr="+payload.address+" cod=" + payload.cod + " name="+ payload.name);
						
						// Play around with associations if the newly paired device supports HFG or A2DP
						var isHFG = isHFGSupported(payload.cod);
						var isA2DP = isA2DPSupported(payload.cod);
						if (isHFG || isA2DP) {
							// Disconnect any devices that may be connected on HFG or A2DP
							if (this.btProfileStatus['hfg'].status != 'disconnected')
								this.disconnectProfile('hfg');
							if (this.btProfileStatus['a2dp'].status != 'disconnected')
								this.disconnectProfile('a2dp');
							
							// Associate this device on the profiles it supports
							var associateHFG = isHFG? "associate": "disassociate";
							var associateA2DP = isA2DP? "associate": "disassociate";
								
							this.btMojoService("palm://com.palm.bluetooth/prof/prof" + associateHFG, {profile: 'hfg', address: payload.address}, null);
							this.btMojoService("palm://com.palm.bluetooth/prof/prof" + associateA2DP, {profile: 'a2dp', address: payload.address}, null);
						}
						
						// If still on the inquiry screen then return to the main screen after a short delay
						// Prevent user from tapping on anything on the Inquiry screen
						this.notifyAssistants(Bluetooth.disablePairing);
						// Return to the main screen in a short while
						this.popInquiryScene({delay: 500});
						// Don't draw the main scene until the list of trusted devices is returned
						this.notifyAssistants(Bluetooth.delayActivation);
						// Let everyone know about the newly paired device
						this.notifyAssistants(Bluetooth.pairedDevice, payload.address);
							
						if (this.pairingDirection == "outbound") {
							// Connect to the newly paired device
							// If the newly added device is a car kit then delay connecting to it
							if (isCarkit(this.deviceCoD[payload.address]) && postPairConnectDelay(payload.name)) {
								if (this.connectToNewlyPairedDeviceTimer)
									clearTimeout(this.connectToNewlyPairedDeviceTimer);
								this.connectToNewlyPairedDeviceTimer = setTimeout(this.connectToNewlyPairedDevice.bind(this), postPairConnectDelay(payload.name));
							}
							else {
								// Connection will happen once the list of trusted devices is received
							}
							this.newlyPairedDevice = payload.address;
						}
						
						// Ready to pair to the next device
						this.pairingDirection = null; 
						this.autoPairAttempted = false;
					}
					else {
						// If auto-pair was attempted then wait and try to pair again
						if (this.autoPairAttempted) {
							this.autoPairAttempted = false;
							// Attempt to pair again in a few seconds
							setTimeout(this.sendPairRequest.bind(this), 4000);
						}
						else {
							// Ready to pair to the next device
							this.pairingDirection = null;
							// Pairing failed so restart inquiry (if the inquiry scene is running).  "true" means pairing failed
							this.notifyAssistants(Bluetooth.restartInquiry, true);
						}
					}
					// Hide the dialog box if it is still showing
					this.closePairingDialogBox();
				}
				else {
					// This paired notification is for a device we don't care about.
					//  Pair with the one we do care about
					if (this.pairingDirection == "outbound")
						this.sendPairRequest();
					
					// Restart inquiry if not pairing
					if (!this.pairingDirection)
						this.notifyAssistants(Bluetooth.restartInquiry, false);
				}
				break;
		}	
	},
	
	// Handle OPP notifications
	handleOppNotification: function(payload) {
		// Ignore subscribe notifications
		if (payload.returnValue)
			return;
		Mojo.Log.info("====== handleOppNotification: " + Object.toJSON(payload));
		switch (payload.notification) {
			case 'notifnpushprogress':
				this.notifyAssistants(Bluetooth.oppProgressPercent, payload.progress);
				break;
			case 'notifnpushcomplete':
				// Was there an error?
				if (payload.error)
					this.notifyAssistants(Bluetooth.oppError, payload.error);
				else
					this.notifyAssistants(Bluetooth.oppProgressPercent, payload.progress);
				break;
		}
	},
	
	updateDeviceConnectedState: function(addr) {
		Mojo.Log.info("====== updateDeviceConnectedState: " + addr);
		if (!addr)
			return;
			
		// Find the device
		var device = this.getDeviceFromTrustedList(addr);
		if (!device)
			return;
			
		// Make sure the status of all profiles is known
		if (!this.btProfileStatus['hfg'] || !this.btProfileStatus['a2dp'] || !this.btProfileStatus['pan'])
			return;
			
		// Assume device is disconnected
		device.CONNECTSTATE = 'disconnected';
		device.CONNECTSTATE_HFG = 'disconnected';
		device.CONNECTSTATE_A2DP = 'disconnected';
		device.CONNECTSTATE_OPP = 'disconnected';
		device.SHOW_PAN_ICON = '';
		device.spinning = false;
		device.oppSpinning = false;
	
		// Is the device connected on PAN?
		if (this.btProfileStatus['pan'].status == "connected" && this.btProfileStatus['pan'].address == addr) {
			device.CONNECTSTATE = 'connected';
			device.SHOW_PAN_ICON = 'enabled';
		}
			
		// If the device is connected on either audio profile then show that
		if (this.btProfileStatus['hfg'].status == "connected" && this.btProfileStatus['hfg'].address == addr ||
			this.btProfileStatus['a2dp'].status == "connected" && this.btProfileStatus['a2dp'].address == addr) {
			device.CONNECTSTATE = 'connected';
			
			// Show the icon corresponding to the profile that is connected
			if (this.btProfileStatus['hfg'].status == "connected" && this.btProfileStatus['hfg'].address == addr)
				device.CONNECTSTATE_HFG = 'connected';
			if (this.btProfileStatus['a2dp'].status == "connected" && this.btProfileStatus['a2dp'].address == addr)
				device.CONNECTSTATE_A2DP = 'connected';
		}
		else {
			// If there is a pending connection to this device, or it is currently connecting then show the spinner
			if (this.deviceAwaitingConnection == addr || this.newlyPairedDevice == addr ||
				this.btProfileStatus['hfg'].status == "connecting" && this.btProfileStatus['hfg'].address == addr ||
				this.btProfileStatus['a2dp'].status == "connecting" && this.btProfileStatus['a2dp'].address == addr) {
				device.CONNECTSTATE = 'connecting';
				device.spinning = true;
			}
		}
		// Is the device connecting on OPP?
		if (this.btProfileStatus['opp'] && this.btProfileStatus['opp'].address == addr) {
			device.CONNECTSTATE_OPP = this.btProfileStatus['opp'].status;
			if (device.CONNECTSTATE_OPP === 'connecting')
				device.oppSpinning = true;
		}
		// Show connecting on OPP if there is a newly paired device
		if (this.newlyPairedDevice && this.newlyPairedDevice === addr)
			device.oppSpinning = true;
			
		Mojo.Log.info("====== updateDeviceConnectedState: " + addr + " CONNECTSTATE="+device.CONNECTSTATE + " oppSpinning=" + device.oppSpinning);
	},

	// Connect or disconnect the device that the user tapped on (in the "Audio" group)
	connectOrDisconnectDevice: function(addr) {
		Mojo.Log.info("====== connectOrDisconnectDevice: " + addr + " newly=" + this.newlyPairedDevice);
		// If we're waiting to connect to a newly paired device, then don't do anything
		if (this.newlyPairedDevice != null)
			return;
			
		// Find the device that was tapped on
		var device = this.getDeviceFromTrustedList(addr);
		if (!device)
			return;
			
		Mojo.Log.info("====== connectOrDisconnectDevice: state=" + device.CONNECTSTATE);
		// There is a different action based on the current connect status of the device
		switch (device.CONNECTSTATE) {
			case 'connected':
				// Device is connected so disconnect all profiles it is connected on
				this.disconnectAllProfiles(addr);
				// Update the UI to reflect that it is no longer connected
				this.updateDeviceConnectedState(addr);
				this.notifyAssistants(Bluetooth.updateTrustedStatus, addr);
				break;
				
			case 'connecting':
				// Is this device waiting to connect?
				if (addr == this.deviceAwaitingConnection) {
					// There is no longer a pending connection attempt to this device
					this.deviceAwaitingConnection = null;
				}
				else {
					// Abort the connection attempt on the supported profiles
					if (isHFGSupported(device.cod) && this.btProfileStatus['hfg'].status != 'disconnected') 
						this.disconnectProfile('hfg');
					if (isA2DPSupported(device.cod) && this.btProfileStatus['a2dp'].status != 'disconnected') 
						this.disconnectProfile('a2dp');
					// Don't show the connect failure message
					delete device.showErrorIfConnectFails;
				}
	
				// Update the UI to reflect that it is no longer connecting
				this.updateDeviceConnectedState(addr);
				this.notifyAssistants(Bluetooth.updateTrustedStatus, addr);
				break;
				
			case 'disconnected':
				// Do nothing if the device is not a audio device.
				if (device.DEVICETYPE == 'Other')
					return;
				
				// Is there a pending connection attempt to another device?
				if (this.deviceAwaitingConnection && this.deviceAwaitingConnection != addr) {
					var addr2 = this.deviceAwaitingConnection;
					Mojo.Log.info("====== connectOrDisconnectDevice: Connect no longer required to " + addr2);
					// A connection to the previously pending device is no longer desired
					this.deviceAwaitingConnection = null;
					// Update the UI to reflect that there is no longer a pending connection attempt to the old device
					// Iterate through the list array to find out the index
					this.updateDeviceConnectedState(addr2);
					this.notifyAssistants(Bluetooth.updateTrustedStatus, addr2);
				}
				
				// Iterate through the list and disconnect the audio profiles
				['hfg', 'a2dp'].each(function(profile){
					if (this.btProfileStatus[profile].status != 'disconnected') {
						Mojo.Log.info("====== connectOrDisconnectDevice: Making " + addr + " pending while disconnecting " + profile);
						this.disconnectProfile(profile);
						// Make the connection attempt to this device pending.  The connection will
						// happen once the disconnect notification is received
						this.deviceAwaitingConnection = addr;
						// Update the status of the device being disconnected
						this.updateDeviceConnectedState(this.btProfileStatus[profile].address);
						this.notifyAssistants(Bluetooth.updateTrustedStatus, this.btProfileStatus[profile].address);
					}
				}.bind(this));
				
				// If we don't have to wait for the disconnect then connect now
				Mojo.Log.info("====== connectOrDisconnectDevice: waiting= " + this.deviceAwaitingConnection);
				if (!this.deviceAwaitingConnection) {
					// Connect to the device now
					this.connectAudio(addr);
				}				
	
				// The device the user tapped on must show "connecting"
				this.updateDeviceConnectedState(addr);
				this.notifyAssistants(Bluetooth.updateTrustedStatus, addr);
		}
	},

	// Register for general notifications
	subscribeMonitorNotifications: function(register) {
		if (register == "register") {
			// Subscribe for "turning on" notification
			if (!this.monitorServiceCall)
				this.monitorServiceCall = this.btMojoService("palm://com.palm.btmonitor/monitor/subscribenotifications", {subscribe: true}, this.monitorNotificationHandler, true);
		}
		else {
			// Unsubscribe for "turning on" notification
			if (this.monitorServiceCall) {
				this.monitorServiceCall.cancel();
				this.monitorServiceCall = null;
			}
		}
	},

	// Register for general notifications
	subscribeNotifications: function(register) {
		if (register == "register") {
			// Subscribe for GAP notifications
			if (!this.gapNotificationServiceCall)
				this.gapNotificationServiceCall = this.btMojoService("palm://com.palm.bluetooth/gap/subscribenotifications", {subscribe: true}, this.profGapNotificationHandler, true);
			// Subscribe for PROF notifications
			if (!this.profNotificationServiceCall)
				this.profNotificationServiceCall = this.btMojoService("palm://com.palm.bluetooth/prof/subscribenotifications", {subscribe: true}, this.profGapNotificationHandler, true);
			// Subscribe for OPP notifications
			if (!this.oppNotificationServiceCall)
				this.oppNotificationServiceCall = this.btMojoService("palm://com.palm.bluetooth/opp/subscribenotifications", {subscribe: true}, this.oppNotificationHandler, true);
		}
		else {
			// Unsubscribe for GAP notifications
			if (this.gapNotificationServiceCall) {
				this.gapNotificationServiceCall.cancel();
				this.gapNotificationServiceCall = null;
			}
			// Unsubscribe for PROF notifications
			if (this.profNotificationServiceCall) {
				this.profNotificationServiceCall.cancel();
				this.profNotificationServiceCall = null;
			}
			// Unsubscribe for OPP notifications
			if (this.oppNotificationServiceCall) {
				this.oppNotificationServiceCall.cancel();
				this.oppNotificationServiceCall = null;
			}
		}
	},

	// Handle notification that device is going to suspend (deep sleep)
	handleSuspendNotification: function(payload) {
		Mojo.Log.info("handleSuspendNotification " + powerClientId + ":" + Object.toJSON(payload));
		if (payload.returnValue)
			return;
	
		// Don't do anything if the radio is off
		if (!this.isRadioOn) {
			this.btMojoService("palm://com.palm.power/com/palm/power/prepareSuspendAck", {"ack":true,"clientId":powerClientId}, null);
			return;
		}
	
		// Phone should not be visible so allow inbound pairing cannot happen
		this.btMojoService("palm://com.palm.bluetooth/gap/setscanstate", {visible: false, connectable: true}, 
			function() {
				this.btMojoService("palm://com.palm.power/com/palm/power/prepareSuspendAck", {ack:true, clientId:powerClientId}, null);
			}.bind(this));
	},

	// Handle notification that device is waking from deep sleep
	handleResumeNotification: function(payload) {
		Mojo.Log.info("handleResumeNotification " + powerClientId + ":" + Object.toJSON(payload));
		if (payload.returnValue)
			return;
	
		// Set visibility if the radio is on.  Phone should be visible if full screen
		if (this.isRadioOn)
			this.btMojoService("palm://com.palm.bluetooth/gap/setscanstate", {visible: this.isFullScreen, connectable: true}, null);
	},


	// Start oubound pairing
	startOutboundPairing: function(addr) {
		Mojo.Log.info("====== startOutboundPairing:direction="+ this.pairingDirection+" pairdevAddr:"+this.pairDevAddress + " to:"+addr + "name=" + this.deviceName[addr]);
		
		// Prevent outbound pairing if there is an inbound pairing attempt in progress
		if (this.pairingDirection == "inbound")
			return;

		// Auto-pairing should be attempted for headsets only
		if (this.deviceCoD[addr] && shouldAutoPairBeAttempted(this.deviceCoD[addr], this.deviceName[addr])) {
			this.attemptAutoPair = true;
			this.autoPairAttempted = false;
		}
 	    
		// Is there an outbound pair attempt in progress already?
        if (this.pairingDirection == "outbound") {
			Mojo.Log.info("====== Cancelling pairing with " + this.pairDevAddress + " and starting pairing with " + addr);
            // The pair attempt in progress must be canceled
            this.sendPairCancel(this.pairDevAddress);

	        // Is the pairing attempt to the same device? If so, this just cancels pairing
            if (this.pairDevAddress == addr) {
				this.pairDevAddress = null;
                return;
			}
					
			// Make a note of the device to pair with
			this.pairDevAddress = addr;
			
			// The pair request will be made once the pair failure notification is received
			return;
        }
		// The pairing is outbound
		this.pairingDirection = "outbound";
		
        // Save Pairing Device Address
        this.pairDevAddress = addr;

        // Send the pair request to Linux
		this.sendPairRequest();      
		
	},

	// Start the pairing process
	sendPairRequest: function() {
		// Make sure the address is valid
		if (!this.pairDevAddress) {
			this.pairingDirection = null;
			return;
		}

		// Let the UI know that pairing to this device has been started
		this.notifyAssistants(Bluetooth.pairingStarted, this.pairDevAddress);
		
		this.btMojoService("palm://com.palm.bluetooth/gap/pair", {address:this.pairDevAddress, cod:this.deviceCoD[this.pairDevAddress]}, null);
	},

	// Cancel pairing to the specified device	
	sendPairCancel: function(addr) {
		// Make sure the address is valid
		if (!addr)
			return;

		// Let the UI know that pairing to this device has been cancelled
		this.notifyAssistants(Bluetooth.pairingComplete, addr);
	
		if (this.pairingUsesSSP)
			this.btMojoService("palm://com.palm.bluetooth/gap/ssppairaccept", {accept:false, address: addr, passkey:0}, null);
		else
			this.btMojoService("palm://com.palm.bluetooth/gap/paircancel", {address: addr}, null);
	},
	
	allowPairing: function(allow) {
		// BT app is full screen so allow pairing
		if (allow && !this.pairingServiceCall)
	        this.pairingServiceCall = this.btMojoService("palm://com.palm.bluetooth/gap/subscribepair", {subscribe:true}, this.pairingNotificationHandler, true); 
		if (!allow && this.pairingServiceCall) {
	        this.btMojoService("palm://com.palm.bluetooth/gap/unsubscribepair", null, null); 
	        this.pairingServiceCall = null; 
		} 
	},
	
	// Get the name of the device being paired with, then continue handling the pairing notification
	getPairingDeviceName: function (payload) {
		this.btMojoService("palm://com.palm.bluetooth/gap/findremotename",
			 {address:payload.address},	
					function(name) {
						if (name.returnValue) {
							payload.name = name.name;
							// If there really isn't a name, set it to anything so pairing can proceed
							if (!payload.name) 
								payload.name = $L("Bluetooth device");
							else {
								// Save the name so that it can be used later, if necessary
								this.deviceName[payload.address] = name.name;
							}
								
							this.handlePairingNotification(payload);
						}
						else {
							this.sendPairCancel(payload.address)
						}
					}.bind(this));		
	},

	// Get the remote device name during an inquiry
	getInquiryDeviceName: function(addr, device) {
		this.btMojoService("palm://com.palm.bluetooth/gap/findremotename",
				 {address:addr},	
					function(payload){
						if (payload && payload.returnValue) {
							Mojo.Log.info("====== getInquiryDeviceName: name of " + addr + " is " + payload.name);
							device.name = payload.name;

							// Save the name so that it can be used later, if necessary
							this.deviceName[addr] = payload.name;

							// The name was found
							device.NAMESTATE = "NameOkay";							
						}
						else {
							// If updating the name failed just use the old name
							if (device.NAMESTATE == "UpdateName") 
								device.NAMESTATE = "NameOkay";
							else
								device.NAMESTATE = "NameFailed";
						}
						// Let the UI know that the device name has been retrieved
						this.notifyAssistants(Bluetooth.inqDeviceName, device);
					}.bind(this));
	},
	
	// Send the passkey to the BT Engine.  Expect the bt.notifnpaired notification next
	sendPasskey: function(passkey) {
		if (this.pairingUsesSSP) {
			if (passkey)
				this.btMojoService("palm://com.palm.bluetooth/gap/ssppairaccept", {accept:true, address:this.pairDevAddress, passkey:passkey}, null);
			else
				this.btMojoService("palm://com.palm.bluetooth/gap/ssppairaccept", {accept:true, address:this.pairDevAddress}, null);
		} else {
			this.btMojoService("palm://com.palm.bluetooth/gap/sendpasskeyresp", {address:this.pairDevAddress, passkey:passkey}, null);
		}
	},
	
	popInquiryScene: function(data) {
		// Tell the Inquiry scene to abort and pop itself
		this.notifyAssistants(Bluetooth.popInquiryScene, data);
	},
	
	popInquirySceneIfPairing: function() {
		if (this.pairingDirection)
			this.popInquiryScene();
	},
	
	// Connect to devices after pairing
	connectToNewlyPairedDevice: function() {
		Mojo.Log.info("====== connectToNewlyPairedDevice ======");
		var addr = this.newlyPairedDevice;
		this.connectToNewlyPairedDeviceTimer = null;
		this.newlyPairedDevice = null;
	
		var device = this.getDeviceFromTrustedList(addr);
		if (!device)
			return;
		Mojo.Log.info("====== connectToNewlyPairedDevice device: " + addr);
			
		// Update the device status now that there is no longer a pending connection to the car kit
		this.updateDeviceConnectedState(addr);
		this.notifyAssistants(Bluetooth.updateTrustedStatus, addr);
		
		// If the device is not connected then connect now
		if (device.CONNECTSTATE == 'disconnected')
			this.connectOrDisconnectDevice(addr);
	},

	// Pop-up the Passkey window to get the passkey from user. 
	promptForPasskey: function(payload) {
		this.showPasskeyDialogBox({prompt:$L("Enter a passkey for “#{name}”").interpolate(payload),
					topButtonText:$L("Next"),
					bottomButtonText:$L("Cancel"),
					showInputField:true,
					topButtonCb: this.sendPasskey.bind(this),
					payload:payload});
	},
	
	// Pop-up the Passkey window so the user can confirm the SSP passkey 
	promptToConfirmSSPPasskey: function(payload) {
		Mojo.Log.info("====== promptToConfirmSSPPasskey ======");
		this.showPasskeyDialogBox({prompt:$L("Does the passkey match with “#{name}”?").interpolate(payload),
					passkey: "<br><div class=\"bt_ssp_passkey\">" + payload.passkey + "</div>",
					topButtonText:$L("Yes, connect"),
					bottomButtonText:$L("No, cancel"),
					topButtonCb: this.sendPasskey.bind(this),
					payload:payload});
	},
	
	// Pop-up the Passkey window so the user is told to enter the passkey on the remote device (probably a BT keyboard) 
	promptToConfirmSSPRemotePasskey: function(payload) {
		Mojo.Log.info("====== promptToConfirmSSPRemotePasskey ======");
		this.showPasskeyDialogBox({prompt:$L("Enter #{passkey} on “#{name}”").interpolate(payload),
					bottomButtonText:$L("Cancel"),
					payload:payload});
	},
	
	// Show the passkey dialog
	showPasskeyDialogBox: function(boxData) {
		Mojo.Log.info("====== showPasskeyDialogBox ======");
		// Make sure there is a scene for the dialog
		if (!this.activeScene)
			return;
		// Hide some fields if not required
		boxData.inputField = boxData.showInputField? "":"display:none";
		boxData.topButtonVisible = boxData.topButtonText? "":"display:none";
	
		boxData.template = 'template/passkey',
		boxData.preventCancel = true;
		boxData.assistant = new passkeyAssistant(this.activeScene, boxData, this);
		
		// Setup the PIN input widget		
		this.activeScene.controller.setupWidget('PINinput', this.PinAttributes, this.PIN);
	
		this.activeScene.controller.showDialog(boxData);
	},

	// Pop-up the confirmation window so the user can confirm the inbound pair notification
	promptForPairingConfirmation: function(payload) {
		var dialog = {template: 'template/allowInbound',
					preventCancel: true};
			
		// Make sure there is a scene for the dialog
		if (!this.activeScene)
			return;
		// If the device is trusted then use the trusted name (or nickname)
		var device = this.getDeviceFromTrustedList(payload.address);
		if (device) {
			payload.name = device.name;
			dialog.prompt = $L("Allow “#{name}” to pair with this device?").interpolate(payload);
			dialog.assistant = new inboundPairingConfirmationAssistant(this.activeScene, payload, this),
			this.activeScene.controller.showDialog(dialog);
		}
		else {
			// Get the name, even if it was provided
			this.btMojoService("palm://com.palm.bluetooth/gap/findremotename", {address: payload.address}, function(name) {
				if (name.returnValue) {
					payload.name = name.name;
					// Save the name so that it can be used later, if necessary
					this.deviceName[payload.address] = name.name;
					dialog.prompt = $L("Allow “#{name}” to pair with this device?").interpolate(payload);
					dialog.assistant = new inboundPairingConfirmationAssistant(this.activeScene, payload, this),
					this.activeScene.controller.showDialog(dialog);
				}
				else {
					this.sendPairCancel(payload.address)
				}
			}.bind(this));
		}	
	},

	// If the pairing dialog box is open then close it
	closePairingDialogBox: function() {
		Mojo.Log.info("====== closePairingDialogBox ======");
		// Close the "Allow Inbound Pairing" dialog
		if (this.allowInboundDialog) {
			this.allowInboundDialog.mojo.close();
			this.allowInboundDialog = null;
		}
		// Close the passkey dialog
		if (this.passkeyDialog) {
			this.passkeyDialog.mojo.close();
			this.passkeyDialog = null;
		}
	},

	// Delete the device from the trusted list 
	deleteTrustedDevice: function(addr) {
		// To match what the engine does, make sure no profiles reference this device
		// Notifications should be sent anyway, but to be sure ...
		this.btProfiles.each(function(profile) {
			if (this.btProfileStatus[profile].address == addr)
				this.btProfileStatus[profile].status = 'disconnected'; 
		}.bind(this));	
	
		this.btMojoService("palm://com.palm.bluetooth/gap/removetrusteddevice", {address:addr}, null);
	},

	// Connect HFG and/or A2DP profile to the specified device
	connectAudio: function(addr) {
		var device = this.getDeviceFromTrustedList(addr);
		if (!device)
			return;
		Mojo.Log.info("====== connectAudio: " + addr);
	
		// Disassociate from profiles not supported by this device
		if (!isHFGSupported(device.cod))
			this.btMojoService("palm://com.palm.bluetooth/prof/profdisassociate", {profile:'hfg'}, null);
		if (!isA2DPSupported(device.cod))
			this.btMojoService("palm://com.palm.bluetooth/prof/profdisassociate", {profile:'a2dp'}, null);
		
		// If HFG is supported then do associate and connect to that profile	
		if (isHFGSupported(device.cod)) {
			this.btMojoService("palm://com.palm.bluetooth/prof/profassociate", {profile:'hfg',address:addr}, null);
			this.btMojoService("palm://com.palm.bluetooth/prof/profconnect", {profile:'hfg'}, null); 
			this.btProfileStatus['hfg'].status = 'connecting';
			this.btProfileStatus['hfg'].address = addr;
		}
		
		// If A2DP is supported then do associate and connect to that profile	
		if (isA2DPSupported(device.cod)) {
			this.btMojoService("palm://com.palm.bluetooth/prof/profassociate", {profile:'a2dp',address:addr}, null);
			this.btMojoService("palm://com.palm.bluetooth/prof/profconnect", {profile:'a2dp'}, null);
			this.btProfileStatus['a2dp'].status = 'connecting';
			this.btProfileStatus['a2dp'].address = addr;
		}
	},

	// Disconnect all profiles the device is connected on
	disconnectAllProfiles: function(addr) {
		// Disconnect all profiles
		this.btProfiles.each(function(profile) {
			if (this.btProfileStatus[profile] && this.btProfileStatus[profile].address == addr && this.btProfileStatus[profile].status != 'disconnected')
				this.disconnectProfile(profile);
		}.bind(this));	 	
	},
	
	// Disconnect a single profile
	disconnectProfile: function(profile) {
		this.btMojoService("palm://com.palm.bluetooth/prof/profdisconnect", {profile:profile}, null);
		if (this.btProfileStatus[profile])
			this.btProfileStatus[profile].status = 'disconnecting';
	},

	// Send a file using OPP
	sendOppData: function(addr, file, type, deleteWhenDone) {
		this.btMojoService("palm://com.palm.bluetooth/prof/profassociate", {profile:'opp',address:addr}, null);
		this.btMojoService("palm://com.palm.bluetooth/prof/profconnect", {profile:'opp', custom:{file:file, type:type, deletefile:deleteWhenDone}}, null);
	},

	// Cancel the OPP transfer	
	cancelOPP: function() {
		if (this.serviceUp)
			this.disconnectProfile('opp');
	},

	// Mojo service wrapper function
	btMojoService: function(url, params, cb)
	{
		this.serviceCalls[url] = new Mojo.Service.Request(url, {
			onSuccess: cb,
			onFailure: cb,
			parameters: params,
	    	});	
		return this.serviceCalls[url];
	},
});


// Events
Bluetooth.serviceDown = 'serviceDown';					// The BT service has gone down
Bluetooth.delayActivation = 'delayActivation';			// Delay activation because not all the data needed to draw is available
Bluetooth.allowActivation = 'allowActivation';			// Allow activation now
Bluetooth.radioOnAndReady = 'radioOnAndReady';			// the Bluetotoh radio is on, all state has been obtained and ready to receive commands
Bluetooth.radioTurningOn = 'radioTurningOn';			// The BT radio is turning on
Bluetooth.radioTurningOff = 'radioTurningOff';			// The BT radio is turning off
Bluetooth.radioOff = 'radioOff';						// The BT radio has turned off
Bluetooth.trustedDevices = 'trustedDevices';			// List of trusted devices has been updated
Bluetooth.popInquiryScene = 'popInquiryScene';			// Inquiry scene is no longer needed
Bluetooth.restartInquiry = 'restartInquiry';			// Restart inquiry after pairing failure
Bluetooth.disablePairing = 'disablePairing';			// Prevent clicks on Inq scene from initiating pairing
Bluetooth.inqDeviceFound = 'inqDeviceFound';			// Device has been found during device inquiry
Bluetooth.inqDeviceName = 'inqDeviceName';				// The name of a device found during inquiry 
Bluetooth.inqComplete = 'inqComplete';					// Inquiry has finished
Bluetooth.pairingStarted = 'pairingStarted';			// Pairing to the specified device has started
Bluetooth.pairingComplete = 'pairingComplete';			// Pairing to the specified device is complete
Bluetooth.pairedDevice = 'pairedDevice';				// Broadcast the newly paired device, in case something needs to happen (like OPP)
Bluetooth.updateTrustedStatus = 'updateTrustedStatus';	// The connection status of a trusted device has changed
Bluetooth.cannotConnect = 'cannotConnect';				// The connect attempt failed (HFG/A2DP)
Bluetooth.cannotConnectOPP = 'cannotConnectOPP';		// The connect attempt failed (OPP)
Bluetooth.deviceDeleted = 'deviceDeleted';				// A trusted device has been deleted
Bluetooth.deviceRenamed = 'deviceRenamed';				// A trusted device has been renamed
Bluetooth.oppProgressPercent = 'oppProgressPercent';	// OPP push progress, as a percentage
Bluetooth.oppError = 'oppError';						// OPP error occured
Bluetooth.oppNotSupported = 'oppNotSupported';			// Connection succeeded, but OPP is not supported



// Assistant for the inbound pairing "allow device to pair?" dialog
var inboundPairingConfirmationAssistant = Class.create({
	initialize: function(sceneAssistant, payload, bt) {
		Mojo.Log.info("====== inboundPairingConfirmationAssistant initialize ======");
		this.bt = bt;
		this.sceneAssistant = sceneAssistant;
		this.payload = payload;
		this.handleAllowPairingH = this.handleAllowPairing.bindAsEventListener(this);
		this.handleCancelPairingH = this.handleCancelPairing.bindAsEventListener(this);
	},
	
	setup : function(widget) {
		Mojo.Log.info("====== inboundPairingConfirmationAssistant setup ======");
		// Save the widget so it can be cancelled programatically
		this.bt.allowInboundDialog = widget;
		this.dialog = widget;
		Mojo.Event.listen(this.sceneAssistant.controller.get('allowPairing'), Mojo.Event.tap, this.handleAllowPairingH, false);
		Mojo.Event.listen(this.sceneAssistant.controller.get('cancelPairing'), Mojo.Event.tap, this.handleCancelPairingH, false);
		
		// Turn the screen on (or restart the auto-dim timer)
		this.bt.btMojoService("palm://com.palm.display/control/setState", {state:"on"}, null);
	},
	
	handleAllowPairing: function() {
		Mojo.Log.info("====== inboundPairingConfirmationAssistant handleAllowPairing ======");
		this.dialog.mojo.close();
		this.bt.handlePairingNotification(this.payload);
	},
	
	handleCancelPairing: function() {
		Mojo.Log.info("====== inboundPairingConfirmationAssistant handleCancelPairing ======");
		this.bt.pairDevAddress = null;
		this.bt.pairingDirection = null;
		this.dialog.mojo.close();
		this.bt.sendPairCancel(this.payload.address);
	},
	
	cleanup: function() {
		Mojo.Log.info("====== inboundPairingConfirmationAssistant cleanup ======");
		this.bt.allowInboundDialog = null;
		Mojo.Event.stopListening(this.sceneAssistant.controller.get('allowPairing'), Mojo.Event.tap, this.handleAllowPairingH, false);
		Mojo.Event.stopListening(this.sceneAssistant.controller.get('cancelPairing'), Mojo.Event.tap, this.handleCancelPairingH, false);
	}
});

// Assistant for the passkey dialog
var passkeyAssistant = Class.create({
	initialize: function(sceneAssistant, boxData, bt) {
		Mojo.Log.info("====== passkeyAssistant initialize ======");
		this.bt = bt;
		this.sceneAssistant = sceneAssistant;
		this.boxData = boxData;
		this.bt.PIN.value = undefined;
		this.handleAllowPairingH = this.handleAllowPairing.bindAsEventListener(this);
		this.handleCancelPairingH = this.handleCancelPairing.bindAsEventListener(this); 
		this.handleFocusLostH = this.handleFocusLost.bindAsEventListener(this);
		this.handleEnterH = this.handleEnter.bindAsEventListener(this);
	},
	
	setup : function(widget) {
		Mojo.Log.info("====== passkeyAssistant setup ======");
		// Save the widget so it can be cancelled programatically
		this.bt.passkeyDialog = widget;
		this.dialog = widget;
		Mojo.Event.listen(this.sceneAssistant.controller.get('allowPasskey'), Mojo.Event.tap, this.handleAllowPairingH, false);
		Mojo.Event.listen(this.sceneAssistant.controller.get('cancelPasskey'), Mojo.Event.tap, this.handleCancelPairingH, false);
    	Mojo.Event.listen(this.sceneAssistant.controller.get('PINinput'), 'blur', this.handleFocusLostH, true);
		
		// Listen for the Enter key
		this.sceneAssistant.controller.listen(this.dialog, Mojo.Event.propertyChange, this.handleEnterH);

		// Turn the screen on (or restart the auto-dim timer)
		new Mojo.Service.Request("palm://com.palm.display/control/setState", {parameters: {state: "on"}});
	},	
	
	handleEnter: function(event) {
		if (event && event.originalEvent && Mojo.Char.isEnterKey(event.originalEvent.keyCode)) {
			this.handleAllowPairingH();
		}
	},
	
	handleAllowPairing: function() {
		Mojo.Log.info("====== passkeyAssistant handleAllowPairing ======");
		// Get the callback depending on which button was pressed
		var cb = this.boxData.topButtonCb;
		var cbData = this.boxData.topButtonCbData;
				
		// If there is callback data then use that, otherwise use the input field on the dialog (if it exists)
		if (!cbData && this.boxData.showInputField) {
			cbData = this.bt.PIN.value;
			// Check for passkey value. Don't accept no value 
			if (!cbData)
				return;
		}		

		// Close the dialog box
		this.dialog.mojo.close();

		// Call the callback now
		if (cb)
			cb(cbData);
	},
	
	handleCancelPairing: function() {
		Mojo.Log.info("====== passkeyAssistant handleCancelPairing ======");
		this.bt.pairDevAddress = null;
		this.bt.pairingDirection = null;
		this.dialog.mojo.close();
		this.bt.sendPairCancel(this.boxData.payload.address);
	},
	
	handleFocusLost: function() {
		// Keep the focus on the PIN Input field
		setTimeout(this.sceneAssistant.controller.get('PINinput').mojo.focus, 0);
	},
	
	cleanup: function() {
		Mojo.Log.info("====== passkeyAssistant cleanup ======");
		this.bt.passkeyDialog = null;
		Mojo.Event.stopListening(this.sceneAssistant.controller.get('allowPasskey'), Mojo.Event.tap, this.handleAllowPairingH, false);
		Mojo.Event.stopListening(this.sceneAssistant.controller.get('cancelPasskey'), Mojo.Event.tap, this.handleCancelPairingH, false);
		Mojo.Event.stopListening(this.sceneAssistant.controller.get('PINinput'), 'blur', this.handleFocusLostH, true);
	}
});
