/** 
 * @file llvoiceclient.cpp
 * @brief Implementation of LLVoiceClient class which is the interface to the voice client process.
 *
 * $LicenseInfo:firstyear=2001&license=viewergpl$
 * 
 * Copyright (c) 2001-2008, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/LicenseInfo$
 */

#include "llviewerprecompiledheaders.h"
#include "llvoiceclient.h"

#include <boost/tokenizer.hpp>

#include "llsdutil.h"

#include "llvoavatar.h"
#include "llbufferstream.h"
#include "llfile.h"
#ifdef LL_STANDALONE
# include "expat.h"
#else
# include "expat/expat.h"
#endif
#include "llcallbacklist.h"
#include "llviewerregion.h"
#include "llviewernetwork.h"		// for gGridChoice
#include "llbase64.h"
#include "llviewercontrol.h"
#include "llkeyboard.h"
#include "llappviewer.h"	// for gDisconnected, gDisableVoice
#include "llmutelist.h"  // to check for muted avatars
#include "llagent.h"
#include "llcachename.h"
#include "llimview.h" // for LLIMMgr
#include "llimpanel.h" // for LLVoiceChannel
#include "llparcel.h"
#include "llviewerparcelmgr.h"
#include "llfirstuse.h"
#include "llviewerwindow.h"
#include "llviewercamera.h"

// for base64 decoding
#include "apr_base64.h"

// for SHA1 hash
#include "apr_sha1.h"

// If we are connecting to agni AND the user's last name is "Linden", join this channel instead of looking up the sim name.
// If we are connecting to agni and the user's last name is NOT "Linden", disable voice.
#define AGNI_LINDENS_ONLY_CHANNEL "SL"
static bool sConnectingToAgni = false;
F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f;

const F32 SPEAKING_TIMEOUT = 1.f;

const int VOICE_MAJOR_VERSION = 1;
const int VOICE_MINOR_VERSION = 0;

LLVoiceClient *gVoiceClient = NULL;

// Don't retry connecting to the daemon more frequently than this:
const F32 CONNECT_THROTTLE_SECONDS = 1.0f;

// Don't send positional updates more frequently than this:
const F32 UPDATE_THROTTLE_SECONDS = 0.1f;

const F32 LOGIN_RETRY_SECONDS = 10.0f;
const int MAX_LOGIN_RETRIES = 12;

class LLViewerVoiceAccountProvisionResponder :
	public LLHTTPClient::Responder
{
public:
	LLViewerVoiceAccountProvisionResponder(int retries)
	{
		mRetries = retries;
	}

	virtual void error(U32 status, const std::string& reason)
	{
		if ( mRetries > 0 )
		{
			if ( gVoiceClient ) gVoiceClient->requestVoiceAccountProvision(
				mRetries - 1);
		}
		else
		{
			//TODO: throw an error message?
			if ( gVoiceClient ) gVoiceClient->giveUp();
		}
	}

	virtual void result(const LLSD& content)
	{
		if ( gVoiceClient )
		{
			gVoiceClient->login(
				content["username"].asString(),
				content["password"].asString());
		}
	}

private:
	int mRetries;
};

/** 
 * @class LLVivoxProtocolParser
 * @brief This class helps construct new LLIOPipe specializations
 * @see LLIOPipe
 *
 * THOROUGH_DESCRIPTION
 */
class LLVivoxProtocolParser : public LLIOPipe
{
	LOG_CLASS(LLVivoxProtocolParser);
public:
	LLVivoxProtocolParser();
	virtual ~LLVivoxProtocolParser();

protected:
	/* @name LLIOPipe virtual implementations
	 */
	//@{
	/** 
	 * @brief Process the data in buffer
	 */
	virtual EStatus process_impl(
		const LLChannelDescriptors& channels,
		buffer_ptr_t& buffer,
		bool& eos,
		LLSD& context,
		LLPumpIO* pump);
	//@}
	
	std::string 	mInput;
	
	// Expat control members
	XML_Parser		parser;
	int				responseDepth;
	bool			ignoringTags;
	bool			isEvent;
	int				ignoreDepth;

	// Members for processing responses. The values are transient and only valid within a call to processResponse().
	int				returnCode;
	int				statusCode;
	std::string		statusString;
	std::string		uuidString;
	std::string		actionString;
	std::string		connectorHandle;
	std::string		accountHandle;
	std::string		sessionHandle;
	std::string		eventSessionHandle;

	// Members for processing events. The values are transient and only valid within a call to processResponse().
	std::string		eventTypeString;
	int				state;
	std::string		uriString;
	bool			isChannel;
	std::string		nameString;
	std::string		audioMediaString;
	std::string		displayNameString;
	int				participantType;
	bool			isLocallyMuted;
	bool			isModeratorMuted;
	bool			isSpeaking;
	int				volume;
	F32				energy;

	// Members for processing text between tags
	std::string		textBuffer;
	bool			accumulateText;
	
	void			reset();

	void			processResponse(std::string tag);

static void XMLCALL ExpatStartTag(void *data, const char *el, const char **attr);
static void XMLCALL ExpatEndTag(void *data, const char *el);
static void XMLCALL ExpatCharHandler(void *data, const XML_Char *s, int len);

	void			StartTag(const char *tag, const char **attr);
	void			EndTag(const char *tag);
	void			CharData(const char *buffer, int length);
	
};

LLVivoxProtocolParser::LLVivoxProtocolParser()
{
	parser = NULL;
	parser = XML_ParserCreate(NULL);
	
	reset();
}

void LLVivoxProtocolParser::reset()
{
	responseDepth = 0;
	ignoringTags = false;
	accumulateText = false;
	textBuffer.clear();

	energy = 0.f;
	ignoreDepth = 0;
	isChannel = false;
	isEvent = false;
	isLocallyMuted = false;
	isModeratorMuted = false;
	isSpeaking = false;
	participantType = 0;
	returnCode = 0;
	state = 0;
	statusCode = 0;
	volume = 0;
}

//virtual 
LLVivoxProtocolParser::~LLVivoxProtocolParser()
{
	if (parser)
		XML_ParserFree(parser);
}

// virtual
LLIOPipe::EStatus LLVivoxProtocolParser::process_impl(
	const LLChannelDescriptors& channels,
	buffer_ptr_t& buffer,
	bool& eos,
	LLSD& context,
	LLPumpIO* pump)
{
	LLBufferStream istr(channels, buffer.get());
	std::ostringstream ostr;
	while (istr.good())
	{
		char buf[1024];
		istr.read(buf, sizeof(buf));
		mInput.append(buf, istr.gcount());
	}
	
	// MBW -- XXX -- This should no longer be necessary.  Or even possible.
	// We've read all the data out of the buffer.  Make sure it doesn't accumulate.
//	buffer->clear();
	
	// Look for input delimiter(s) in the input buffer.  If one is found, send the message to the xml parser.
	int start = 0;
	int delim;
	while((delim = mInput.find("\n\n\n", start)) != std::string::npos)
	{	
		// Turn this on to log incoming XML
		if(0)
		{
			int foo = mInput.find("Set3DPosition", start);
			int bar = mInput.find("ParticipantPropertiesEvent", start);
			if(foo != std::string::npos && (foo < delim))
			{
				// This is a Set3DPosition response.  Don't print it, since these are way too spammy.
			}
			else if(bar != std::string::npos && (bar < delim))
			{
				// This is a ParticipantPropertiesEvent response.  Don't print it, since these are way too spammy.
			}
			else
			{
				LL_INFOS("Voice") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL;
			}
		}
		
		// Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser)
		reset();
		
		XML_ParserReset(parser, NULL);
		XML_SetElementHandler(parser, ExpatStartTag, ExpatEndTag);
		XML_SetCharacterDataHandler(parser, ExpatCharHandler);
		XML_SetUserData(parser, this);	
		XML_Parse(parser, mInput.data() + start, delim - start, false);
		
		start = delim + 3;
	}
	
	if(start != 0)
		mInput = mInput.substr(start);

	LL_DEBUGS("Voice") << "at end, mInput is: " << mInput << LL_ENDL;
	
	if(!gVoiceClient->mConnected)
	{
		// If voice has been disabled, we just want to close the socket.  This does so.
		LL_INFOS("Voice") << "returning STATUS_STOP" << LL_ENDL;
		return STATUS_STOP;
	}
	
	return STATUS_OK;
}

void XMLCALL LLVivoxProtocolParser::ExpatStartTag(void *data, const char *el, const char **attr)
{
	if (data)
	{
		LLVivoxProtocolParser	*object = (LLVivoxProtocolParser*)data;
		object->StartTag(el, attr);
	}
}

// --------------------------------------------------------------------------------

void XMLCALL LLVivoxProtocolParser::ExpatEndTag(void *data, const char *el)
{
	if (data)
	{
		LLVivoxProtocolParser	*object = (LLVivoxProtocolParser*)data;
		object->EndTag(el);
	}
}

// --------------------------------------------------------------------------------

void XMLCALL LLVivoxProtocolParser::ExpatCharHandler(void *data, const XML_Char *s, int len)
{
	if (data)
	{
		LLVivoxProtocolParser	*object = (LLVivoxProtocolParser*)data;
		object->CharData(s, len);
	}
}

// --------------------------------------------------------------------------------


void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr)
{
	// Reset the text accumulator. We shouldn't have strings that are inturrupted by new tags
	textBuffer.clear();
	// only accumulate text if we're not ignoring tags.
	accumulateText = !ignoringTags;
	
	if (responseDepth == 0)
	{	
		isEvent = strcmp("Event", tag) == 0;
		
		if (strcmp("Response", tag) == 0 || isEvent)
		{
			// Grab the attributes
			while (*attr)
			{
				const char	*key = *attr++;
				const char	*value = *attr++;
				
				if (strcmp("requestId", key) == 0)
				{
					uuidString = value;
				}
				else if (strcmp("action", key) == 0)
				{
					actionString = value;
				}
				else if (strcmp("type", key) == 0)
				{
					eventTypeString = value;
				}
			}
		}
		LL_DEBUGS("Voice") << tag << " (" << responseDepth << ")"  << LL_ENDL;
	}
	else
	{
		if (ignoringTags)
		{
			LL_DEBUGS("Voice") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
		}
		else
		{
			LL_DEBUGS("Voice") << tag << " (" << responseDepth << ")"  << LL_ENDL;
	
			// Ignore the InputXml stuff so we don't get confused
			if (strcmp("InputXml", tag) == 0)
			{
				ignoringTags = true;
				ignoreDepth = responseDepth;
				accumulateText = false;

				LL_DEBUGS("Voice") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL;
			}
			else if (strcmp("CaptureDevices", tag) == 0)
			{
				gVoiceClient->clearCaptureDevices();
			}
			else if (strcmp("RenderDevices", tag) == 0)
			{
				gVoiceClient->clearRenderDevices();
			}
		}
	}
	responseDepth++;
}

// --------------------------------------------------------------------------------

void LLVivoxProtocolParser::EndTag(const char *tag)
{
	const std::string& string = textBuffer;
	bool clearbuffer = true;

	responseDepth--;

	if (ignoringTags)
	{
		if (ignoreDepth == responseDepth)
		{
			LL_DEBUGS("Voice") << "end of ignore" << LL_ENDL;
			ignoringTags = false;
		}
		else
		{
			LL_DEBUGS("Voice") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
		}
	}
	
	if (!ignoringTags)
	{
		LL_DEBUGS("Voice") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;

		// Closing a tag. Finalize the text we've accumulated and reset
		if (strcmp("ReturnCode", tag) == 0)
			returnCode = strtol(string.c_str(), NULL, 10);
		else if (strcmp("StatusCode", tag) == 0)
			statusCode = strtol(string.c_str(), NULL, 10);
		else if (strcmp("ConnectorHandle", tag) == 0)
			connectorHandle = string;
		else if (strcmp("AccountHandle", tag) == 0)
			accountHandle = string;
		else if (strcmp("SessionHandle", tag) == 0)
		{
			if (isEvent)
				eventSessionHandle = string;
			else
				sessionHandle = string;
		}
		else if (strcmp("StatusString", tag) == 0)
			statusString = string;
		else if (strcmp("State", tag) == 0)
			state = strtol(string.c_str(), NULL, 10);
		else if (strcmp("URI", tag) == 0)
			uriString = string;
		else if (strcmp("IsChannel", tag) == 0)
			isChannel = string == "true" ? true : false;
		else if (strcmp("Name", tag) == 0)
			nameString = string;
		else if (strcmp("AudioMedia", tag) == 0)
			audioMediaString = string;
		else if (strcmp("ChannelName", tag) == 0)
			nameString = string;
		else if (strcmp("ParticipantURI", tag) == 0)
			uriString = string;
		else if (strcmp("DisplayName", tag) == 0)
			displayNameString = string;
		else if (strcmp("AccountName", tag) == 0)
			nameString = string;
		else if (strcmp("ParticipantTyppe", tag) == 0)
			participantType = strtol(string.c_str(), NULL, 10);
		else if (strcmp("IsLocallyMuted", tag) == 0)
			isLocallyMuted = string == "true" ? true : false;
		else if (strcmp("IsModeratorMuted", tag) == 0)
			isModeratorMuted = string == "true" ? true : false;
		else if (strcmp("IsSpeaking", tag) == 0)
			isSpeaking = string == "true" ? true : false;
		else if (strcmp("Volume", tag) == 0)
			volume = strtol(string.c_str(), NULL, 10);
		else if (strcmp("Energy", tag) == 0)
			energy = (F32)strtod(string.c_str(), NULL);
		else if (strcmp("MicEnergy", tag) == 0)
			energy = (F32)strtod(string.c_str(), NULL);
		else if (strcmp("ChannelName", tag) == 0)
			nameString = string;
		else if (strcmp("ChannelURI", tag) == 0)
			uriString = string;
		else if (strcmp("ChannelListResult", tag) == 0)
		{
			gVoiceClient->addChannelMapEntry(nameString, uriString);
		}
		else if (strcmp("Device", tag) == 0)
		{
			// This closing tag shouldn't clear the accumulated text.
			clearbuffer = false;
		}
		else if (strcmp("CaptureDevice", tag) == 0)
		{
			gVoiceClient->addCaptureDevice(textBuffer);
		}
		else if (strcmp("RenderDevice", tag) == 0)
		{
			gVoiceClient->addRenderDevice(textBuffer);
		}

		if(clearbuffer)
		{
			textBuffer.clear();
			accumulateText= false;
		}
		
		if (responseDepth == 0)
		{
			// We finished all of the XML, process the data
			processResponse(tag);
		}
	}
}

// --------------------------------------------------------------------------------

void LLVivoxProtocolParser::CharData(const char *buffer, int length)
{
	/*
		This method is called for anything that isn't a tag, which can be text you
		want that lies between tags, and a lot of stuff you don't want like file formatting
		(tabs, spaces, CR/LF, etc).
		
		Only copy text if we are in accumulate mode...
	*/
	if (accumulateText)
		textBuffer.append(buffer, length);
}

// --------------------------------------------------------------------------------

void LLVivoxProtocolParser::processResponse(std::string tag)
{
	LL_DEBUGS("Voice") << tag << LL_ENDL;

	if (isEvent)
	{
		if (eventTypeString == "LoginStateChangeEvent")
		{
			gVoiceClient->loginStateChangeEvent(accountHandle, statusCode, statusString, state);
		}
		else if (eventTypeString == "SessionNewEvent")
		{
			gVoiceClient->sessionNewEvent(accountHandle, eventSessionHandle, state, nameString, uriString);
		}
		else if (eventTypeString == "SessionStateChangeEvent")
		{
			gVoiceClient->sessionStateChangeEvent(uriString, statusCode, statusString, eventSessionHandle, state, isChannel, nameString);
		}
		else if (eventTypeString == "ParticipantStateChangeEvent")
		{
			gVoiceClient->participantStateChangeEvent(uriString, statusCode, statusString, state,  nameString, displayNameString, participantType);
			
		}
		else if (eventTypeString == "ParticipantPropertiesEvent")
		{
			gVoiceClient->participantPropertiesEvent(uriString, statusCode, statusString, isLocallyMuted, isModeratorMuted, isSpeaking, volume, energy);
		}
		else if (eventTypeString == "AuxAudioPropertiesEvent")
		{
			gVoiceClient->auxAudioPropertiesEvent(energy);
		}
	}
	else
	{
		if (actionString == "Connector.Create.1")
		{
			gVoiceClient->connectorCreateResponse(statusCode, statusString, connectorHandle);
		}
		else if (actionString == "Account.Login.1")
		{
			gVoiceClient->loginResponse(statusCode, statusString, accountHandle);
		}
		else if (actionString == "Session.Create.1")
		{
			gVoiceClient->sessionCreateResponse(statusCode, statusString, sessionHandle);			
		}
		else if (actionString == "Session.Connect.1")
		{
			gVoiceClient->sessionConnectResponse(statusCode, statusString);			
		}
		else if (actionString == "Session.Terminate.1")
		{
			gVoiceClient->sessionTerminateResponse(statusCode, statusString);			
		}
		else if (actionString == "Account.Logout.1")
		{
			gVoiceClient->logoutResponse(statusCode, statusString);			
		}
		else if (actionString == "Connector.InitiateShutdown.1")
		{
			gVoiceClient->connectorShutdownResponse(statusCode, statusString);			
		}
		else if (actionString == "Account.ChannelGetList.1")
		{
			gVoiceClient->channelGetListResponse(statusCode, statusString);
		}
/*
		else if (actionString == "Connector.AccountCreate.1")
		{
			
		}
		else if (actionString == "Connector.MuteLocalMic.1")
		{
			
		}
		else if (actionString == "Connector.MuteLocalSpeaker.1")
		{
			
		}
		else if (actionString == "Connector.SetLocalMicVolume.1")
		{
			
		}
		else if (actionString == "Connector.SetLocalSpeakerVolume.1")
		{
			
		}
		else if (actionString == "Session.ListenerSetPosition.1")
		{
			
		}
		else if (actionString == "Session.SpeakerSetPosition.1")
		{
			
		}
		else if (actionString == "Session.Set3DPosition.1")
		{
			
		}
		else if (actionString == "Session.AudioSourceSetPosition.1")
		{
			
		}
		else if (actionString == "Session.GetChannelParticipants.1")
		{
			
		}
		else if (actionString == "Account.ChannelCreate.1")
		{
			
		}
		else if (actionString == "Account.ChannelUpdate.1")
		{
			
		}
		else if (actionString == "Account.ChannelDelete.1")
		{
			
		}
		else if (actionString == "Account.ChannelCreateAndInvite.1")
		{
			
		}
		else if (actionString == "Account.ChannelFolderCreate.1")
		{
			
		}
		else if (actionString == "Account.ChannelFolderUpdate.1")
		{
			
		}
		else if (actionString == "Account.ChannelFolderDelete.1")
		{
			
		}
		else if (actionString == "Account.ChannelAddModerator.1")
		{
			
		}
		else if (actionString == "Account.ChannelDeleteModerator.1")
		{
			
		}
*/
	}
}

///////////////////////////////////////////////////////////////////////////////////////////////

class LLVoiceClientMuteListObserver : public LLMuteListObserver
{
	/* virtual */ void onChange()  { gVoiceClient->muteListChanged();}
};
static LLVoiceClientMuteListObserver mutelist_listener;
static bool sMuteListListener_listening = false;

///////////////////////////////////////////////////////////////////////////////////////////////

class LLVoiceClientCapResponder : public LLHTTPClient::Responder
{
public:
	LLVoiceClientCapResponder(void){};

	virtual void error(U32 status, const std::string& reason);	// called with bad status codes
	virtual void result(const LLSD& content);

private:
};

void LLVoiceClientCapResponder::error(U32 status, const std::string& reason)
{
	LL_WARNS("Voice") << "LLVoiceClientCapResponder::error("
		<< status << ": " << reason << ")"
		<< LL_ENDL;
}

void LLVoiceClientCapResponder::result(const LLSD& content)
{
	LLSD::map_const_iterator iter;
	for(iter = content.beginMap(); iter != content.endMap(); ++iter)
	{
		LL_DEBUGS("Voice") << "LLVoiceClientCapResponder::result got " 
			<< iter->first << LL_ENDL;
	}

	if ( content.has("voice_credentials") )
	{
		LLSD voice_credentials = content["voice_credentials"];
		std::string uri;
		std::string credentials;

		if ( voice_credentials.has("channel_uri") )
		{
			uri = voice_credentials["channel_uri"].asString();
		}
		if ( voice_credentials.has("channel_credentials") )
		{
			credentials =
				voice_credentials["channel_credentials"].asString();
		}

		gVoiceClient->setSpatialChannel(uri, credentials);
	}
}



#if LL_WINDOWS
static HANDLE sGatewayHandle = 0;

static bool isGatewayRunning()
{
	bool result = false;
	if(sGatewayHandle != 0)		
	{
		DWORD waitresult = WaitForSingleObject(sGatewayHandle, 0);
		if(waitresult != WAIT_OBJECT_0)
		{
			result = true;
		}			
	}
	return result;
}
static void killGateway()
{
	if(sGatewayHandle != 0)
	{
		TerminateProcess(sGatewayHandle,0);
	}
}

#else // Mac and linux

static pid_t sGatewayPID = 0;
static bool isGatewayRunning()
{
	bool result = false;
	if(sGatewayPID != 0)
	{
		// A kill with signal number 0 has no effect, just does error checking.  It should return an error if the process no longer exists.
		if(kill(sGatewayPID, 0) == 0)
		{
			result = true;
		}
	}
	return result;
}

static void killGateway()
{
	if(sGatewayPID != 0)
	{
		kill(sGatewayPID, SIGTERM);
	}
}

#endif

///////////////////////////////////////////////////////////////////////////////////////////////

LLVoiceClient::LLVoiceClient()
{	
	gVoiceClient = this;
	mWriteInProgress = false;
	mAreaVoiceDisabled = false;
	mPTT = true;
	mUserPTTState = false;
	mMuteMic = false;
	mSessionTerminateRequested = false;
	mCommandCookie = 0;
	mNonSpatialChannel = false;
	mNextSessionSpatial = true;
	mNextSessionNoReconnect = false;
	mSessionP2P = false;
	mCurrentParcelLocalID = 0;
	mLoginRetryCount = 0;
	mVivoxErrorStatusCode = 0;

	mNextSessionResetOnClose = false;
	mSessionResetOnClose = false;
	mSpeakerVolume = 0;
	mMicVolume = 0;

	// Initial dirty state
	mSpatialCoordsDirty = false;
	mPTTDirty = true;
	mVolumeDirty = true;
	mSpeakerVolumeDirty = true;
	mMicVolumeDirty = true;
	mCaptureDeviceDirty = false;
	mRenderDeviceDirty = false;

	// Use default values for everything then call updateSettings() after preferences are loaded
	mVoiceEnabled = false;
	mUsePTT = true;
	mPTTIsToggle = false;
	mEarLocation = 0;
	mLipSyncEnabled = false;
	
	mTuningMode = false;
	mTuningEnergy = 0.0f;
	mTuningMicVolume = 0;
	mTuningMicVolumeDirty = true;
	mTuningSpeakerVolume = 0;
	mTuningSpeakerVolumeDirty = true;
					
	//  gMuteListp isn't set up at this point, so we defer this until later.
//	gMuteListp->addObserver(&mutelist_listener);
	
	mParticipantMapChanged = false;

	// stash the pump for later use
	// This now happens when init() is called instead.
	mPump = NULL;
	
#if LL_DARWIN || LL_LINUX
		// MBW -- XXX -- THIS DOES NOT BELONG HERE
		// When the vivox daemon dies, the next write attempt on our socket generates a SIGPIPE, which kills us.
		// This should cause us to ignore SIGPIPE and handle the error through proper channels.
		// This should really be set up elsewhere.  Where should it go?
		signal(SIGPIPE, SIG_IGN);
		
		// Since we're now launching the gateway with fork/exec instead of system(), we need to deal with zombie processes.
		// Ignoring SIGCHLD should prevent zombies from being created.  Alternately, we could use wait(), but I'd rather not do that.
		signal(SIGCHLD, SIG_IGN);
#endif

	// set up state machine
	setState(stateDisabled);
	
	gIdleCallbacks.addFunction(idle, this);
}

//---------------------------------------------------

LLVoiceClient::~LLVoiceClient()
{
}

//----------------------------------------------

void LLVoiceClient::init(LLPumpIO *pump)
{
	// constructor will set up gVoiceClient
	LLVoiceClient::getInstance()->mPump = pump;
	LLVoiceClient::getInstance()->updateSettings();
}

void LLVoiceClient::terminate()
{
	if(gVoiceClient)
	{
		gVoiceClient->sessionTerminateSendMessage();
		gVoiceClient->logout();
		gVoiceClient->connectorShutdown();
		gVoiceClient->closeSocket();		// Need to do this now -- bad things happen if the destructor does it later.
		
		// This will do unpleasant things on windows.
//		killGateway();
		
		// Don't do this anymore -- LLSingleton will take care of deleting the object.		
//		delete gVoiceClient;
		
		// Hint to other code not to access the voice client anymore.
		gVoiceClient = NULL;
	}
}

//---------------------------------------------------

void LLVoiceClient::updateSettings()
{
	setVoiceEnabled(gSavedSettings.getBOOL("EnableVoiceChat"));
	setUsePTT(gSavedSettings.getBOOL("PTTCurrentlyEnabled"));
	std::string keyString = gSavedSettings.getString("PushToTalkButton");
	setPTTKey(keyString);
	setPTTIsToggle(gSavedSettings.getBOOL("PushToTalkToggle"));
	setEarLocation(gSavedSettings.getS32("VoiceEarLocation"));
	std::string serverName = gSavedSettings.getString("VivoxDebugServerName");
	setVivoxDebugServerName(serverName);

	std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice");
	setCaptureDevice(inputDevice);
	std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice");
	setRenderDevice(outputDevice);
	setLipSyncEnabled(gSavedSettings.getBOOL("LipSyncEnabled"));
}

/////////////////////////////
// utility functions

bool LLVoiceClient::writeString(const std::string &str)
{
	bool result = false;
	if(mConnected)
	{
		apr_status_t err;
		apr_size_t size = (apr_size_t)str.size();
		apr_size_t written = size;
	
		LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL;

		// MBW -- XXX -- check return code - sockets will fail (broken, etc.)
		err = apr_socket_send(
				mSocket->getSocket(),
				(const char*)str.data(),
				&written);
		
		if(err == 0)
		{
			// Success.
			result = true;
		}
		// MBW -- XXX -- handle partial writes (written is number of bytes written)
		// Need to set socket to non-blocking before this will work.
//		else if(APR_STATUS_IS_EAGAIN(err))
//		{
//			// 
//		}
		else
		{
			// Assume any socket error means something bad.  For now, just close the socket.
			char buf[MAX_STRING];
			LL_WARNS("Voice") << "apr error " << err << " ("<< apr_strerror(err, buf, MAX_STRING) << ") sending data to vivox daemon." << LL_ENDL;
			daemonDied();
		}
	}
		
	return result;
}


/////////////////////////////
// session control messages
void LLVoiceClient::connectorCreate()
{
	std::ostringstream stream;
	std::string logpath;
	std::string loglevel = "0";
	
	// Transition to stateConnectorStarted when the connector handle comes back.
	setState(stateConnectorStarting);

	std::string savedLogLevel = gSavedSettings.getString("VivoxDebugLevel");
	
	if(savedLogLevel != "-1")
	{
		LL_DEBUGS("Voice") << "creating connector with logging enabled" << LL_ENDL;
		loglevel = "10";
		logpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "");
	}
	
	stream 
	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.Create.1\">"
		<< "<ClientName>V2 SDK</ClientName>"
		<< "<AccountManagementServer>" << mAccountServerURI << "</AccountManagementServer>"
		<< "<Logging>"
			<< "<Enabled>false</Enabled>"
			<< "<Folder>" << logpath << "</Folder>"
			<< "<FileNamePrefix>Connector</FileNamePrefix>"
			<< "<FileNameSuffix>.log</FileNameSuffix>"
			<< "<LogLevel>" << loglevel << "</LogLevel>"
		<< "</Logging>"
	<< "</Request>\n\n\n";
	
	writeString(stream.str());
}

void LLVoiceClient::connectorShutdown()
{
	setState(stateConnectorStopping);
	
	if(!mConnectorHandle.empty())
	{
		std::ostringstream stream;
		stream
		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.InitiateShutdown.1\">"
			<< "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
		<< "</Request>"
		<< "\n\n\n";
		
		mConnectorHandle.clear();
		
		writeString(stream.str());
	}
}

void LLVoiceClient::userAuthorized(const std::string& firstName, const std::string& lastName, const LLUUID &agentID)
{
	mAccountFirstName = firstName;
	mAccountLastName = lastName;

	mAccountDisplayName = firstName;
	mAccountDisplayName += " ";
	mAccountDisplayName += lastName;

	LL_INFOS("Voice") << "name \"" << mAccountDisplayName << "\" , ID " << agentID << LL_ENDL;

	sConnectingToAgni = LLViewerLogin::getInstance()->isInProductionGrid();

	// MBW -- XXX -- Enable this when the bhd.vivox.com server gets a real ssl cert.	
	if(sConnectingToAgni)
	{
		// Use the release account server
		mAccountServerName = "bhr.vivox.com";
		mAccountServerURI = "https://www." + mAccountServerName + "/api2/";
	}
	else
	{
		// Use the development account server
		mAccountServerName = gSavedSettings.getString("VivoxDebugServerName");
		mAccountServerURI = "https://www." + mAccountServerName + "/api2/";
	}

	mAccountName = nameFromID(agentID);
}

void LLVoiceClient::requestVoiceAccountProvision(S32 retries)
{
	if ( gAgent.getRegion() && mVoiceEnabled )
	{
		std::string url = 
			gAgent.getRegion()->getCapability(
				"ProvisionVoiceAccountRequest");

		if ( url == "" ) return;

		LLHTTPClient::post(
			url,
			LLSD(),
			new LLViewerVoiceAccountProvisionResponder(retries));
	}
}

void LLVoiceClient::login(
	const std::string& accountName,
	const std::string &password)
{
	if((getState() >= stateLoggingIn) && (getState() < stateLoggedOut))
	{
		// Already logged in.  This is an internal error.
		LL_ERRS("Voice") << "Can't login again. Called from wrong state." << LL_ENDL;
	}
	else if ( accountName != mAccountName )
	{
		//TODO: error?
		LL_WARNS("Voice") << "Wrong account name! " << accountName
				<< " instead of " << mAccountName << LL_ENDL;
	}
	else
	{
		mAccountPassword = password;
	}
}

void LLVoiceClient::idle(void* user_data)
{
	LLVoiceClient* self = (LLVoiceClient*)user_data;
	self->stateMachine();
}

std::string LLVoiceClient::state2string(LLVoiceClient::state inState)
{
	std::string result = "UNKNOWN";
	
		// Prevent copy-paste errors when updating this list...
#define CASE(x)  case x:  result = #x;  break

	switch(inState)
	{
		CASE(stateDisabled);
		CASE(stateStart);
		CASE(stateDaemonLaunched);
		CASE(stateConnecting);
		CASE(stateIdle);
		CASE(stateConnectorStart);
		CASE(stateConnectorStarting);
		CASE(stateConnectorStarted);
		CASE(stateLoginRetry);
		CASE(stateLoginRetryWait);
		CASE(stateNeedsLogin);
		CASE(stateLoggingIn);
		CASE(stateLoggedIn);
		CASE(stateNoChannel);
		CASE(stateMicTuningStart);
		CASE(stateMicTuningRunning);
		CASE(stateMicTuningStop);
		CASE(stateSessionCreate);
		CASE(stateSessionConnect);
		CASE(stateJoiningSession);
		CASE(stateSessionJoined);
		CASE(stateRunning);
		CASE(stateLeavingSession);
		CASE(stateSessionTerminated);
		CASE(stateLoggingOut);
		CASE(stateLoggedOut);
		CASE(stateConnectorStopping);
		CASE(stateConnectorStopped);
		CASE(stateConnectorFailed);
		CASE(stateConnectorFailedWaiting);
		CASE(stateLoginFailed);
		CASE(stateLoginFailedWaiting);
		CASE(stateJoinSessionFailed);
		CASE(stateJoinSessionFailedWaiting);
		CASE(stateJail);
		CASE(stateMicTuningNoLogin);
	}

#undef CASE
	
	return result;
}

std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserver::EStatusType inStatus)
{
	std::string result = "UNKNOWN";
	
		// Prevent copy-paste errors when updating this list...
#define CASE(x)  case x:  result = #x;  break

	switch(inStatus)
	{
		CASE(STATUS_LOGIN_RETRY);
		CASE(STATUS_LOGGED_IN);
		CASE(STATUS_JOINING);
		CASE(STATUS_JOINED);
		CASE(STATUS_LEFT_CHANNEL);
		CASE(BEGIN_ERROR_STATUS);
		CASE(ERROR_CHANNEL_FULL);
		CASE(ERROR_CHANNEL_LOCKED);
		CASE(ERROR_NOT_AVAILABLE);
		CASE(ERROR_UNKNOWN);
	default:
		break;
	}

#undef CASE
	
	return result;
}

void LLVoiceClient::setState(state inState)
{
	LL_DEBUGS("Voice") << "entering state " << state2string(inState) << LL_ENDL;
	
	mState = inState;
}

void LLVoiceClient::stateMachine()
{
	if(gDisconnected)
	{
		// The viewer has been disconnected from the sim.  Disable voice.
		setVoiceEnabled(false);
	}
	
	if(mVoiceEnabled)
	{
		updatePosition();
	}
	else
	{
		if(getState() != stateDisabled)
		{
			// User turned off voice support.  Send the cleanup messages, close the socket, and reset.
			if(!mConnected)
			{
				// if voice was turned off after the daemon was launched but before we could connect to it, we may need to issue a kill.
				LL_INFOS("Voice") << "Disabling voice before connection to daemon, terminating." << LL_ENDL;
				killGateway();
			}
			
			sessionTerminateSendMessage();
			logout();
			connectorShutdown();
			closeSocket();
			removeAllParticipants();

			setState(stateDisabled);
		}
	}
	
	// Check for parcel boundary crossing
	{
		LLViewerRegion *region = gAgent.getRegion();
		LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
		
		if(region && parcel)
		{
			S32 parcelLocalID = parcel->getLocalID();
			std::string regionName = region->getName();
			std::string capURI = region->getCapability("ParcelVoiceInfoRequest");
		
			LL_DEBUGS("Voice") << "Region name = \"" << regionName <<"\", " << "parcel local ID = " << parcelLocalID << LL_ENDL;

			// The region name starts out empty and gets filled in later.  
			// Also, the cap gets filled in a short time after the region cross, but a little too late for our purposes.
			// If either is empty, wait for the next time around.
			if(!regionName.empty() && !capURI.empty())
			{
				if((parcelLocalID != mCurrentParcelLocalID) || (regionName != mCurrentRegionName))
				{
					// We have changed parcels.  Initiate a parcel channel lookup.
					mCurrentParcelLocalID = parcelLocalID;
					mCurrentRegionName = regionName;
					
					parcelChanged();
				}
			}
		}
	}

	switch(getState())
	{
		case stateDisabled:
			if(mVoiceEnabled && (!mAccountName.empty() || mTuningMode))
			{
				setState(stateStart);
			}
		break;
		
		case stateStart:
			if(gSavedSettings.getBOOL("CmdLineDisableVoice"))
			{
				// Voice is locked out, we must not launch the vivox daemon.
				setState(stateJail);
			}
			else if(!isGatewayRunning())
			{
				if(true)
				{
					// Launch the voice daemon
					
					// *FIX:Mani - Using the executable dir instead 
					// of mAppRODataDir, the working directory from which the app
					// is launched.
					//std::string exe_path = gDirUtilp->getAppRODataDir();
					std::string exe_path = gDirUtilp->getExecutableDir();
					exe_path += gDirUtilp->getDirDelimiter();
#if LL_WINDOWS
					exe_path += "SLVoice.exe";
#elif LL_DARWIN
					exe_path += "../Resources/SLVoice";
#else
					exe_path += "SLVoice";
#endif
					// See if the vivox executable exists
					llstat s;
					if(!LLFile::stat(exe_path, &s))
					{
						// vivox executable exists.  Build the command line and launch the daemon.
						std::string args = " -p tcp -h -c";
						std::string cmd;
						std::string loglevel = gSavedSettings.getString("VivoxDebugLevel");
						
						if(loglevel.empty())
						{
							loglevel = "-1";	// turn logging off completely
						}
						
						args += " -ll ";
						args += loglevel;
						
						LL_DEBUGS("Voice") << "Args for SLVoice: " << args << LL_ENDL;

#if LL_WINDOWS
						PROCESS_INFORMATION pinfo;
						STARTUPINFOA sinfo;
						memset(&sinfo, 0, sizeof(sinfo));
						std::string exe_dir = gDirUtilp->getAppRODataDir();
						cmd = "SLVoice.exe";
						cmd += args;
						
						// So retarded.  Windows requires that the second parameter to CreateProcessA be a writable (non-const) string...
						char *args2 = new char[args.size() + 1];
						strcpy(args2, args.c_str());

						if(!CreateProcessA(exe_path.c_str(), args2, NULL, NULL, FALSE, 0, NULL, exe_dir.c_str(), &sinfo, &pinfo))
						{
//							DWORD dwErr = GetLastError();
						}
						else
						{
							// foo = pinfo.dwProcessId; // get your pid here if you want to use it later on
							// CloseHandle(pinfo.hProcess); // stops leaks - nothing else
							sGatewayHandle = pinfo.hProcess;
							CloseHandle(pinfo.hThread); // stops leaks - nothing else
						}		
						
						delete[] args2;
#else	// LL_WINDOWS
						// This should be the same for mac and linux
						{
							std::vector<std::string> arglist;
							arglist.push_back(exe_path);
							
							// Split the argument string into separate strings for each argument
							typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
							boost::char_separator<char> sep(" ");
							tokenizer tokens(args, sep);
							tokenizer::iterator token_iter;

							for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter)
							{
								arglist.push_back(*token_iter);
							}
							
							// create an argv vector for the child process
							char **fakeargv = new char*[arglist.size() + 1];
							int i;
							for(i=0; i < arglist.size(); i++)
								fakeargv[i] = const_cast<char*>(arglist[i].c_str());

							fakeargv[i] = NULL;
							
							fflush(NULL); // flush all buffers before the child inherits them
							pid_t id = vfork();
							if(id == 0)
							{
								// child
								execv(exe_path.c_str(), fakeargv);
								
								// If we reach this point, the exec failed.
								// Use _exit() instead of exit() per the vfork man page.
								_exit(0);
							}

							// parent
							delete[] fakeargv;
							sGatewayPID = id;
						}
#endif	// LL_WINDOWS
						mDaemonHost = LLHost(gSavedSettings.getString("VoiceHost").c_str(), gSavedSettings.getU32("VoicePort"));
					}	
					else
					{
						LL_INFOS("Voice") << exe_path << "not found." << LL_ENDL;
					}	
				}
				else
				{		
					// We can connect to a client gateway running on another host.  This is useful for testing.
					// To do this, launch the gateway on a nearby host like this:
					//  vivox-gw.exe -p tcp -i 0.0.0.0:44124
					// and put that host's IP address here.
					mDaemonHost = LLHost(gSavedSettings.getString("VoiceHost"), gSavedSettings.getU32("VoicePort"));
				}

				mUpdateTimer.start();
				mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS);

				setState(stateDaemonLaunched);
				
				// Dirty the states we'll need to sync with the daemon when it comes up.
				mPTTDirty = true;
				mSpeakerVolumeDirty = true;
				// These only need to be set if they're not default (i.e. empty string).
				mCaptureDeviceDirty = !mCaptureDevice.empty();
				mRenderDeviceDirty = !mRenderDevice.empty();
			}
		break;
		
		case stateDaemonLaunched:
			LL_DEBUGS("Voice") << "Connecting to vivox daemon" << LL_ENDL;
			if(mUpdateTimer.hasExpired())
			{
				mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS);

				if(!mSocket)
				{
					mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP);	
				}
				
				mConnected = mSocket->blockingConnect(mDaemonHost);
				if(mConnected)
				{
					setState(stateConnecting);
				}
				else
				{
					// If the connect failed, the socket may have been put into a bad state.  Delete it.
					closeSocket();
				}
			}
		break;

		case stateConnecting:
		// Can't do this until we have the pump available.
		if(mPump)
		{
			// MBW -- Note to self: pumps and pipes examples in
			//  indra/test/io.cpp
			//  indra/test/llpipeutil.{cpp|h}

			// Attach the pumps and pipes
				
			LLPumpIO::chain_t readChain;

			readChain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(mSocket)));
			readChain.push_back(LLIOPipe::ptr_t(new LLVivoxProtocolParser()));

			mPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS);

			setState(stateIdle);
		}

		break;
		
		case stateIdle:
			// Initial devices query
			getCaptureDevicesSendMessage();
			getRenderDevicesSendMessage();

			mLoginRetryCount = 0;
			
			setState(stateConnectorStart);
				
		break;
		
		case stateConnectorStart:
			if(!mVoiceEnabled)
			{
				// We were never logged in.  This will shut down the connector.
				setState(stateLoggedOut);
			}
			else if(!mAccountServerURI.empty())
			{
				connectorCreate();
			}
			else if(mTuningMode)
			{
				mTuningExitState = stateConnectorStart;
				setState(stateMicTuningStart);
			}
		break;
		
		case stateConnectorStarting:	// waiting for connector handle
			// connectorCreateResponse() will transition from here to stateConnectorStarted.
		break;
		
		case stateConnectorStarted:		// connector handle received
			if(!mVoiceEnabled)
			{
				// We were never logged in.  This will shut down the connector.
				setState(stateLoggedOut);
			}
			else if(!mAccountName.empty())
			{
				LLViewerRegion *region = gAgent.getRegion();
				
				if(region)
				{
					if ( region->getCapability("ProvisionVoiceAccountRequest") != "" )
					{
						if ( mAccountPassword.empty() )
						{
							requestVoiceAccountProvision();
						}
						setState(stateNeedsLogin);
					}
				}
			}
		break;
				
		case stateMicTuningStart:
			if(mUpdateTimer.hasExpired())
			{
				if(mCaptureDeviceDirty || mRenderDeviceDirty)
				{
					// These can't be changed while in tuning mode.  Set them before starting.
					std::ostringstream stream;

					if(mCaptureDeviceDirty)
					{
						buildSetCaptureDevice(stream);
					}

					if(mRenderDeviceDirty)
					{
						buildSetRenderDevice(stream);
					}

					mCaptureDeviceDirty = false;
					mRenderDeviceDirty = false;

					if(!stream.str().empty())
					{
						writeString(stream.str());
					}				

					// This will come around again in the same state and start the capture, after the timer expires.
					mUpdateTimer.start();
					mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
				}
				else
				{
					// duration parameter is currently unused, per Mike S.
					tuningCaptureStartSendMessage(10000);

					setState(stateMicTuningRunning);
				}
			}
			
		break;
		
		case stateMicTuningRunning:
			if(!mTuningMode || !mVoiceEnabled || mSessionTerminateRequested || mCaptureDeviceDirty || mRenderDeviceDirty)
			{
				// All of these conditions make us leave tuning mode.
				setState(stateMicTuningStop);
			}
			else
			{
				// process mic/speaker volume changes
				if(mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty)
				{
					std::ostringstream stream;
					
					if(mTuningMicVolumeDirty)
					{
						LL_INFOS("Voice") << "setting tuning mic level to " << mTuningMicVolume << LL_ENDL;
						stream
						<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetMicLevel.1\">"
						<< "<Level>" << mTuningMicVolume << "</Level>"
						<< "</Request>\n\n\n";
					}
					
					if(mTuningSpeakerVolumeDirty)
					{
						stream
						<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetSpeakerLevel.1\">"
						<< "<Level>" << mTuningSpeakerVolume << "</Level>"
						<< "</Request>\n\n\n";
					}
					
					mTuningMicVolumeDirty = false;
					mTuningSpeakerVolumeDirty = false;

					if(!stream.str().empty())
					{
						writeString(stream.str());
					}
				}
			}
		break;
		
		case stateMicTuningStop:
		{
			// transition out of mic tuning
			tuningCaptureStopSendMessage();
			
			setState(mTuningExitState);
			
			// if we exited just to change devices, this will keep us from re-entering too fast.
			mUpdateTimer.start();
			mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
			
		}
		break;
								
		case stateLoginRetry:
			if(mLoginRetryCount == 0)
			{
				// First retry -- display a message to the user
				notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGIN_RETRY);
			}
			
			mLoginRetryCount++;
			
			if(mLoginRetryCount > MAX_LOGIN_RETRIES)
			{
				LL_WARNS("Voice") << "too many login retries, giving up." << LL_ENDL;
				setState(stateLoginFailed);
			}
			else
			{
				LL_INFOS("Voice") << "will retry login in " << LOGIN_RETRY_SECONDS << " seconds." << LL_ENDL;
				mUpdateTimer.start();
				mUpdateTimer.setTimerExpirySec(LOGIN_RETRY_SECONDS);
				setState(stateLoginRetryWait);
			}
		break;
		
		case stateLoginRetryWait:
			if(mUpdateTimer.hasExpired())
			{
				setState(stateNeedsLogin);
			}
		break;
		
		case stateNeedsLogin:
			if(!mAccountPassword.empty())
			{
				setState(stateLoggingIn);
				loginSendMessage();
			}		
		break;
		
		case stateLoggingIn:			// waiting for account handle
			// loginResponse() will transition from here to stateLoggedIn.
		break;
		
		case stateLoggedIn:				// account handle received
			// Initial kick-off of channel lookup logic
			parcelChanged();

			notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN);

			// Set up the mute list observer if it hasn't been set up already.
			if((!sMuteListListener_listening))
			{
				LLMuteList::getInstance()->addObserver(&mutelist_listener);
				sMuteListListener_listening = true;
			}

			setState(stateNoChannel);
		break;
					
		case stateNoChannel:
			if(mSessionTerminateRequested || !mVoiceEnabled)
			{
				// MBW -- XXX -- Is this the right way out of this state?
				setState(stateSessionTerminated);
			}
			else if(mTuningMode)
			{
				mTuningExitState = stateNoChannel;
				setState(stateMicTuningStart);
			}
			else if(!mNextSessionHandle.empty())
			{
				setState(stateSessionConnect);
			}
			else if(!mNextSessionURI.empty())
			{
				setState(stateSessionCreate);
			}
		break;

		case stateSessionCreate:
			sessionCreateSendMessage();
			notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING);
			setState(stateJoiningSession);
		break;
		
		case stateSessionConnect:
			sessionConnectSendMessage();
			notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING);
			setState(stateJoiningSession);
		break;
		
		case stateJoiningSession:		// waiting for session handle
			// sessionCreateResponse() will transition from here to stateSessionJoined.
			if(!mVoiceEnabled)
			{
				// User bailed out during connect -- jump straight to teardown.
				setState(stateSessionTerminated);
			}
			else if(mSessionTerminateRequested)
			{
				if(!mSessionHandle.empty())
				{
					// Only allow direct exits from this state in p2p calls (for cancelling an invite).
					// Terminating a half-connected session on other types of calls seems to break something in the vivox gateway.
					if(mSessionP2P)
					{
						sessionTerminateSendMessage();
						setState(stateSessionTerminated);
					}
				}
			}
		break;
		
		case stateSessionJoined:		// session handle received
			// MBW -- XXX -- It appears that I need to wait for BOTH the Session.Create response and the SessionStateChangeEvent with state 4
			//  before continuing from this state.  They can happen in either order, and if I don't wait for both, things can get stuck.
			// For now, the Session.Create response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined.
			// This is a cheap way to make sure both have happened before proceeding.
			if(!mSessionHandle.empty())
			{
				// Events that need to happen when a session is joined could go here.
				// Maybe send initial spatial data?
				notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED);

				// Dirty state that may need to be sync'ed with the daemon.
				mPTTDirty = true;
				mSpeakerVolumeDirty = true;
				mSpatialCoordsDirty = true;
				
				setState(stateRunning);
				
				// Start the throttle timer
				mUpdateTimer.start();
				mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
			}
			else if(!mVoiceEnabled)
			{
				// User bailed out during connect -- jump straight to teardown.
				setState(stateSessionTerminated);
			}
			else if(mSessionTerminateRequested)
			{
				// Only allow direct exits from this state in p2p calls (for cancelling an invite).
				// Terminating a half-connected session on other types of calls seems to break something in the vivox gateway.
				if(mSessionP2P)
				{
					sessionTerminateSendMessage();
					setState(stateSessionTerminated);
				}
			}
		break;
		
		case stateRunning:				// steady state
			// sessionTerminateSendMessage() will transition from here to stateLeavingSession
			
			// Disabling voice or disconnect requested.
			if(!mVoiceEnabled || mSessionTerminateRequested)
			{
				sessionTerminateSendMessage();
			}
			else
			{
				
				// Figure out whether the PTT state needs to change
				{
					bool newPTT;
					if(mUsePTT)
					{
						// If configured to use PTT, track the user state.
						newPTT = mUserPTTState;
					}
					else
					{
						// If not configured to use PTT, it should always be true (otherwise the user will be unable to speak).
						newPTT = true;
					}
					
					if(mMuteMic)
					{
						// This always overrides any other PTT setting.
						newPTT = false;
					}
					
					// Dirty if state changed.
					if(newPTT != mPTT)
					{
						mPTT = newPTT;
						mPTTDirty = true;
					}
				}
				
				if(mNonSpatialChannel)
				{
					// When in a non-spatial channel, never send positional updates.
					mSpatialCoordsDirty = false;
				}
				else
				{
					// Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position)
					enforceTether();
				}
				
				// Send an update if the ptt state has changed (which shouldn't be able to happen that often -- the user can only click so fast)
				// or every 10hz, whichever is sooner.
				if(mVolumeDirty || mPTTDirty || mSpeakerVolumeDirty || mUpdateTimer.hasExpired())
				{
					mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
					sendPositionalUpdate();
				}
			}
		break;
		
		case stateLeavingSession:		// waiting for terminate session response
			// The handler for the Session.Terminate response will transition from here to stateSessionTerminated.
		break;

		case stateSessionTerminated:
			// Always reset the terminate request flag when we get here.
			mSessionTerminateRequested = false;
			
			notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL);

			if(mVoiceEnabled)
			{
				// SPECIAL CASE: if going back to spatial but in a parcel with an empty URI, transfer the non-spatial flag now.
				// This fixes the case where you come out of a group chat in a parcel with voice disabled, and get stuck unable to rejoin spatial chat thereafter.
				if(mNextSessionSpatial && mNextSessionURI.empty())
				{
					mNonSpatialChannel = !mNextSessionSpatial;
				}
				
				// Just leaving a channel, go back to stateNoChannel (the "logged in but have no channel" state).
				setState(stateNoChannel);
			}
			else
			{
				// Shutting down voice, continue with disconnecting.
				logout();
			}
			
		break;
		
		case stateLoggingOut:			// waiting for logout response
			// The handler for the Account.Logout response will transition from here to stateLoggedOut.
		break;
		case stateLoggedOut:			// logout response received
			// shut down the connector
			connectorShutdown();
		break;
		
		case stateConnectorStopping:	// waiting for connector stop
			// The handler for the Connector.InitiateShutdown response will transition from here to stateConnectorStopped.
		break;

		case stateConnectorStopped:		// connector stop received
			// Clean up and reset everything. 
			closeSocket();
			removeAllParticipants();
			setState(stateDisabled);
		break;

		case stateConnectorFailed:
			setState(stateConnectorFailedWaiting);
		break;
		case stateConnectorFailedWaiting:
		break;

		case stateLoginFailed:
			setState(stateLoginFailedWaiting);
		break;
		case stateLoginFailedWaiting:
			// No way to recover from these.  Yet.
		break;

		case stateJoinSessionFailed:
			// Transition to error state.  Send out any notifications here.
			LL_WARNS("Voice") << "stateJoinSessionFailed: (" << mVivoxErrorStatusCode << "): " << mVivoxErrorStatusString << LL_ENDL;
			notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN);
			setState(stateJoinSessionFailedWaiting);
		break;
		
		case stateJoinSessionFailedWaiting:
			// Joining a channel failed, either due to a failed channel name -> sip url lookup or an error from the join message.
			// Region crossings may leave this state and try the join again.
			if(mSessionTerminateRequested)
			{
				setState(stateSessionTerminated);
			}
		break;
		
		case stateJail:
			// We have given up.  Do nothing.
		break;

	        case stateMicTuningNoLogin:
			// *TODO: Implement me.
			LL_WARNS("Voice") << "stateMicTuningNoLogin not handled" << LL_ENDL;
		break;
	}

	if(mParticipantMapChanged)
	{
		mParticipantMapChanged = false;
		notifyObservers();
	}

}

void LLVoiceClient::closeSocket(void)
{
	mSocket.reset();
	mConnected = false;	
}

void LLVoiceClient::loginSendMessage()
{
	std::ostringstream stream;
	stream
	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Login.1\">"
		<< "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
		<< "<AccountName>" << mAccountName << "</AccountName>"
		<< "<AccountPassword>" << mAccountPassword << "</AccountPassword>"
		<< "<AudioSessionAnswerMode>VerifyAnswer</AudioSessionAnswerMode>"
	<< "</Request>\n\n\n";
	
	writeString(stream.str());
}

void LLVoiceClient::logout()
{
	mAccountPassword = "";
	setState(stateLoggingOut);
	logoutSendMessage();
}

void LLVoiceClient::logoutSendMessage()
{
	if(!mAccountHandle.empty())
	{
		std::ostringstream stream;
		stream
		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Logout.1\">"
			<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
		<< "</Request>"
		<< "\n\n\n";

		mAccountHandle.clear();

		writeString(stream.str());
	}
}

void LLVoiceClient::channelGetListSendMessage()
{
	std::ostringstream stream;
	stream
	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.ChannelGetList.1\">"
		<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
	<< "</Request>\n\n\n";

	writeString(stream.str());
}

void LLVoiceClient::sessionCreateSendMessage()
{
	LL_DEBUGS("Voice") << "requesting join: " << mNextSessionURI << LL_ENDL;

	mSessionURI = mNextSessionURI;
	mNonSpatialChannel = !mNextSessionSpatial;
	mSessionResetOnClose = mNextSessionResetOnClose;
	mNextSessionResetOnClose = false;
	if(mNextSessionNoReconnect)
	{
		// Clear the stashed URI so it can't reconnect
		mNextSessionURI.clear();
	}
	// Only p2p sessions are created with "no reconnect".
	mSessionP2P = mNextSessionNoReconnect;

	std::ostringstream stream;
	stream
	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Create.1\">"
		<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
		<< "<URI>" << mSessionURI << "</URI>";

	static const std::string allowed_chars =
				"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
				"0123456789"
				"-._~";

	if(!mNextSessionHash.empty())
	{
		stream
			<< "<Password>" << LLURI::escape(mNextSessionHash, allowed_chars) << "</Password>"
			<< "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>";
	}
	
	stream
		<< "<Name>" << mChannelName << "</Name>"
	<< "</Request>\n\n\n";
	writeString(stream.str());
}

void LLVoiceClient::sessionConnectSendMessage()
{
	LL_DEBUGS("Voice") << "connecting to session handle: " << mNextSessionHandle << LL_ENDL;
	
	mSessionHandle = mNextSessionHandle;
	mSessionURI = mNextP2PSessionURI;
	mNextSessionHandle.clear();		// never want to re-use these.
	mNextP2PSessionURI.clear();
	mNonSpatialChannel = !mNextSessionSpatial;
	mSessionResetOnClose = mNextSessionResetOnClose;
	mNextSessionResetOnClose = false;
	// Joining by session ID is only used to answer p2p invitations, so we know this is a p2p session.
	mSessionP2P = true;
	
	std::ostringstream stream;
	
	stream
	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Connect.1\">"
		<< "<SessionHandle>" << mSessionHandle << "</SessionHandle>"
		<< "<AudioMedia>default</AudioMedia>"
	<< "</Request>\n\n\n";
	writeString(stream.str());
}

void LLVoiceClient::sessionTerminate()
{
	mSessionTerminateRequested = true;
}

void LLVoiceClient::sessionTerminateSendMessage()
{
	LL_DEBUGS("Voice") << "leaving session: " << mSessionURI << LL_ENDL;

	switch(getState())
	{
		case stateNoChannel:
			// In this case, we want to pretend the join failed so our state machine doesn't get stuck.
			// Skip the join failed transition state so we don't send out error notifications.
			setState(stateJoinSessionFailedWaiting);
		break;
		case stateJoiningSession:
		case stateSessionJoined:
		case stateRunning:
			if(!mSessionHandle.empty())
			{
				sessionTerminateByHandle(mSessionHandle);
				setState(stateLeavingSession);
			}
			else
			{
				LL_WARNS("Voice") << "called with no session handle" << LL_ENDL;	
				setState(stateSessionTerminated);
			}
		break;
		case stateJoinSessionFailed:
		case stateJoinSessionFailedWaiting:
			setState(stateSessionTerminated);
		break;
		
		default:
			LL_WARNS("Voice") << "called from unknown state" << LL_ENDL;
		break;
	}
}

void LLVoiceClient::sessionTerminateByHandle(std::string &sessionHandle)
{
	LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << sessionHandle << LL_ENDL;	

	std::ostringstream stream;
	stream
	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Terminate.1\">"
		<< "<SessionHandle>" << sessionHandle << "</SessionHandle>"
	<< "</Request>"
	<< "\n\n\n";
	
	writeString(stream.str());
}

void LLVoiceClient::getCaptureDevicesSendMessage()
{
	std::ostringstream stream;
	stream
	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetCaptureDevices.1\">"
	<< "</Request>\n\n\n";
	
	writeString(stream.str());
}

void LLVoiceClient::getRenderDevicesSendMessage()
{
	std::ostringstream stream;
	stream
	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.GetRenderDevices.1\">"
	<< "</Request>\n\n\n";
	
	writeString(stream.str());
}

void LLVoiceClient::clearCaptureDevices()
{
	// MBW -- XXX -- do something here
	LL_DEBUGS("Voice") << "called" << LL_ENDL;
	mCaptureDevices.clear();
}

void LLVoiceClient::addCaptureDevice(const std::string& name)
{
	// MBW -- XXX -- do something here
	LL_DEBUGS("Voice") << name << LL_ENDL;

	mCaptureDevices.push_back(name);
}

LLVoiceClient::deviceList *LLVoiceClient::getCaptureDevices()
{
	return &mCaptureDevices;
}

void LLVoiceClient::setCaptureDevice(const std::string& name)
{
	if(name == "Default")
	{
		if(!mCaptureDevice.empty())
		{
			mCaptureDevice.clear();
			mCaptureDeviceDirty = true;	
		}
	}
	else
	{
		if(mCaptureDevice != name)
		{
			mCaptureDevice = name;
			mCaptureDeviceDirty = true;	
		}
	}
}

void LLVoiceClient::clearRenderDevices()
{
	// MBW -- XXX -- do something here
	LL_DEBUGS("Voice") << "called" << LL_ENDL;
	mRenderDevices.clear();
}

void LLVoiceClient::addRenderDevice(const std::string& name)
{
	// MBW -- XXX -- do something here
	LL_DEBUGS("Voice") << name << LL_ENDL;
	mRenderDevices.push_back(name);
}

LLVoiceClient::deviceList *LLVoiceClient::getRenderDevices()
{
	return &mRenderDevices;
}

void LLVoiceClient::setRenderDevice(const std::string& name)
{
	if(name == "Default")
	{
		if(!mRenderDevice.empty())
		{
			mRenderDevice.clear();
			mRenderDeviceDirty = true;	
		}
	}
	else
	{
		if(mRenderDevice != name)
		{
			mRenderDevice = name;
			mRenderDeviceDirty = true;	
		}
	}
	
}

void LLVoiceClient::tuningStart()
{
	mTuningMode = true;
	if(getState() >= stateNoChannel)
	{
		sessionTerminate();
	}
}

void LLVoiceClient::tuningStop()
{
	mTuningMode = false;
}

bool LLVoiceClient::inTuningMode()
{
	bool result = false;
	switch(getState())
	{
	case stateMicTuningRunning:
		result = true;
		break;
	default:
		break;
	}
	return result;
}

void LLVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop)
{		
	mTuningAudioFile = name;
	std::ostringstream stream;
	stream
	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStart.1\">"
    << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>"
    << "<Loop>" << (loop?"1":"0") << "</Loop>"
	<< "</Request>\n\n\n";
	
	writeString(stream.str());
}

void LLVoiceClient::tuningRenderStopSendMessage()
{
	std::ostringstream stream;
	stream
	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.RenderAudioStop.1\">"
    << "<SoundFilePath>" << mTuningAudioFile << "</SoundFilePath>"
	<< "</Request>\n\n\n";
	
	writeString(stream.str());
}

void LLVoiceClient::tuningCaptureStartSendMessage(int duration)
{
	LL_DEBUGS("Voice") << "sending CaptureAudioStart" << LL_ENDL;
	
	std::ostringstream stream;
	stream
	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStart.1\">"
    << "<Duration>" << duration << "</Duration>"
	<< "</Request>\n\n\n";
	
	writeString(stream.str());
}

void LLVoiceClient::tuningCaptureStopSendMessage()
{
	LL_DEBUGS("Voice") << "sending CaptureAudioStop" << LL_ENDL;
	
	std::ostringstream stream;
	stream
	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.CaptureAudioStop.1\">"
	<< "</Request>\n\n\n";
	
	writeString(stream.str());

	mTuningEnergy = 0.0f;
}

void LLVoiceClient::tuningSetMicVolume(float volume)
{
	int scaledVolume = ((int)(volume * 100.0f)) - 100;
	if(scaledVolume != mTuningMicVolume)
	{
		mTuningMicVolume = scaledVolume;
		mTuningMicVolumeDirty = true;
	}
}

void LLVoiceClient::tuningSetSpeakerVolume(float volume)
{
	// incoming volume has the range [0.0 ... 1.0], with 0.5 as the default.
	// Map it as follows: 0.0 -> -100, 0.5 -> 24, 1.0 -> 50
	
	volume -= 0.5f;		// offset volume to the range [-0.5 ... 0.5], with 0 at the default.
	int scaledVolume = 24;	// offset scaledVolume by its default level
	if(volume < 0.0f)
		scaledVolume += ((int)(volume * 248.0f));	// (24 - (-100)) * 2
	else
		scaledVolume += ((int)(volume * 52.0f));	// (50 - 24) * 2

	if(scaledVolume != mTuningSpeakerVolume)
	{
		mTuningSpeakerVolume = scaledVolume;
		mTuningSpeakerVolumeDirty = true;
	}
}
				
float LLVoiceClient::tuningGetEnergy(void)
{
	return mTuningEnergy;
}

bool LLVoiceClient::deviceSettingsAvailable()
{
	bool result = true;
	
	if(!mConnected)
		result = false;
	
	if(mRenderDevices.empty())
		result = false;
	
	return result;
}

void LLVoiceClient::refreshDeviceLists(bool clearCurrentList)
{
	if(clearCurrentList)
	{
		clearCaptureDevices();
		clearRenderDevices();
	}
	getCaptureDevicesSendMessage();
	getRenderDevicesSendMessage();
}

void LLVoiceClient::daemonDied()
{
	// The daemon died, so the connection is gone.  Reset everything and start over.
	LL_WARNS("Voice") << "Connection to vivox daemon lost.  Resetting state."<< LL_ENDL;

	closeSocket();
	removeAllParticipants();
	
	// Try to relaunch the daemon
	setState(stateDisabled);
}

void LLVoiceClient::giveUp()
{
	// All has failed.  Clean up and stop trying.
	closeSocket();
	removeAllParticipants();
	
	setState(stateJail);
}

void LLVoiceClient::sendPositionalUpdate(void)
{	
	std::ostringstream stream;
	
	if(mSpatialCoordsDirty)
	{
		LLVector3 l, u, a;
		
		// Always send both speaker and listener positions together.
		stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Set3DPosition.1\">"		
			<< "<SessionHandle>" << mSessionHandle << "</SessionHandle>";
		
		stream << "<SpeakerPosition>";

		l = mAvatarRot.getLeftRow();
		u = mAvatarRot.getUpRow();
		a = mAvatarRot.getFwdRow();
			
		LL_DEBUGS("Voice") << "Sending speaker position " << mAvatarPosition << LL_ENDL;

		stream 
			<< "<Position>"
				<< "<X>" << mAvatarPosition[VX] << "</X>"
				<< "<Y>" << mAvatarPosition[VZ] << "</Y>"
				<< "<Z>" << mAvatarPosition[VY] << "</Z>"
			<< "</Position>"
			<< "<Velocity>"
				<< "<X>" << mAvatarVelocity[VX] << "</X>"
				<< "<Y>" << mAvatarVelocity[VZ] << "</Y>"
				<< "<Z>" << mAvatarVelocity[VY] << "</Z>"
			<< "</Velocity>"
			<< "<AtOrientation>"
				<< "<X>" << l.mV[VX] << "</X>"
				<< "<Y>" << u.mV[VX] << "</Y>"
				<< "<Z>" << a.mV[VX] << "</Z>"
			<< "</AtOrientation>"
			<< "<UpOrientation>"
				<< "<X>" << l.mV[VZ] << "</X>"
				<< "<Y>" << u.mV[VY] << "</Y>"
				<< "<Z>" << a.mV[VZ] << "</Z>"
			<< "</UpOrientation>"
			<< "<LeftOrientation>"
				<< "<X>" << l.mV [VY] << "</X>"
				<< "<Y>" << u.mV [VZ] << "</Y>"
				<< "<Z>" << a.mV [VY] << "</Z>"
			<< "</LeftOrientation>";

		stream << "</SpeakerPosition>";

		stream << "<ListenerPosition>";

		LLVector3d	earPosition;
		LLVector3	earVelocity;
		LLMatrix3	earRot;
		
		switch(mEarLocation)
		{
			case earLocCamera:
			default:
				earPosition = mCameraPosition;
				earVelocity = mCameraVelocity;
				earRot = mCameraRot;
			break;
			
			case earLocAvatar:
				earPosition = mAvatarPosition;
				earVelocity = mAvatarVelocity;
				earRot = mAvatarRot;
			break;
			
			case earLocMixed:
				earPosition = mAvatarPosition;
				earVelocity = mAvatarVelocity;
				earRot = mCameraRot;
			break;
		}

		l = earRot.getLeftRow();
		u = earRot.getUpRow();
		a = earRot.getFwdRow();

		LL_DEBUGS("Voice") << "Sending listener position " << earPosition << LL_ENDL;

		stream 
			<< "<Position>"
				<< "<X>" << earPosition[VX] << "</X>"
				<< "<Y>" << earPosition[VZ] << "</Y>"
				<< "<Z>" << earPosition[VY] << "</Z>"
			<< "</Position>"
			<< "<Velocity>"
				<< "<X>" << earVelocity[VX] << "</X>"
				<< "<Y>" << earVelocity[VZ] << "</Y>"
				<< "<Z>" << earVelocity[VY] << "</Z>"
			<< "</Velocity>"
			<< "<AtOrientation>"
				<< "<X>" << l.mV[VX] << "</X>"
				<< "<Y>" << u.mV[VX] << "</Y>"
				<< "<Z>" << a.mV[VX] << "</Z>"
			<< "</AtOrientation>"
			<< "<UpOrientation>"
				<< "<X>" << l.mV[VZ] << "</X>"
				<< "<Y>" << u.mV[VY] << "</Y>"
				<< "<Z>" << a.mV[VZ] << "</Z>"
			<< "</UpOrientation>"
			<< "<LeftOrientation>"
				<< "<X>" << l.mV [VY] << "</X>"
				<< "<Y>" << u.mV [VZ] << "</Y>"
				<< "<Z>" << a.mV [VY] << "</Z>"
			<< "</LeftOrientation>";

		stream << "</ListenerPosition>";

		stream << "</Request>\n\n\n";
	}	

	if(mPTTDirty)
	{
		// Send a local mute command.
		// NOTE that the state of "PTT" is the inverse of "local mute".
		//   (i.e. when PTT is true, we send a mute command with "false", and vice versa)
		
		LL_DEBUGS("Voice") << "Sending MuteLocalMic command with parameter " << (mPTT?"false":"true") << LL_ENDL;

		stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalMic.1\">"
			<< "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
			<< "<Value>" << (mPTT?"false":"true") << "</Value>"
			<< "</Request>\n\n\n";

	}
	
	if(mVolumeDirty)
	{
		participantMap::iterator iter = mParticipantMap.begin();
		
		for(; iter != mParticipantMap.end(); iter++)
		{
			participantState *p = iter->second;
			
			if(p->mVolumeDirty)
			{
				int volume = p->mOnMuteList?0:p->mUserVolume;
				
				LL_INFOS("Voice") << "Setting volume for avatar " << p->mAvatarID << " to " << volume << LL_ENDL;
				
				// Send a mute/unumte command for the user (actually "volume for me").
				stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantVolumeForMe.1\">"
					<< "<SessionHandle>" << mSessionHandle << "</SessionHandle>"
					<< "<ParticipantURI>" << p->mURI << "</ParticipantURI>"
					<< "<Volume>" << volume << "</Volume>"
					<< "</Request>\n\n\n";

				p->mVolumeDirty = false;
			}
		}
	}
	
	if(mSpeakerMuteDirty)
	{
		const char *muteval = ((mSpeakerVolume == -100)?"true":"false");
		LL_INFOS("Voice") << "Setting speaker mute to " << muteval  << LL_ENDL;
		
		stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.MuteLocalSpeaker.1\">"
			<< "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
			<< "<Value>" << muteval << "</Value>"
			<< "</Request>\n\n\n";		
	}
	
	if(mSpeakerVolumeDirty)
	{
		LL_INFOS("Voice") << "Setting speaker volume to " << mSpeakerVolume  << LL_ENDL;

		stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalSpeakerVolume.1\">"
			<< "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
			<< "<Value>" << mSpeakerVolume << "</Value>"
			<< "</Request>\n\n\n";		
	}
	
	if(mMicVolumeDirty)
	{
		LL_INFOS("Voice") << "Setting mic volume to " << mMicVolume  << LL_ENDL;

		stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Connector.SetLocalMicVolume.1\">"
			<< "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
			<< "<Value>" << mMicVolume << "</Value>"
			<< "</Request>\n\n\n";		
	}

	
	// MBW -- XXX -- Maybe check to make sure the capture/render devices are in the current list here?
	if(mCaptureDeviceDirty)
	{
		buildSetCaptureDevice(stream);
	}

	if(mRenderDeviceDirty)
	{
		buildSetRenderDevice(stream);
	}
	
	mSpatialCoordsDirty = false;
	mPTTDirty = false;
	mVolumeDirty = false;
	mSpeakerVolumeDirty = false;
	mMicVolumeDirty = false;
	mSpeakerMuteDirty = false;
	mCaptureDeviceDirty = false;
	mRenderDeviceDirty = false;
	
	if(!stream.str().empty())
	{
		writeString(stream.str());
	}
}

void LLVoiceClient::buildSetCaptureDevice(std::ostringstream &stream)
{
	LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL;
	
	stream 
	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetCaptureDevice.1\">"
		<< "<CaptureDeviceSpecifier>" << mCaptureDevice << "</CaptureDeviceSpecifier>"
	<< "</Request>"
	<< "\n\n\n";
}

void LLVoiceClient::buildSetRenderDevice(std::ostringstream &stream)
{
	LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL;

	stream
	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetRenderDevice.1\">"
		<< "<RenderDeviceSpecifier>" << mRenderDevice << "</RenderDeviceSpecifier>"
	<< "</Request>"
	<< "\n\n\n";
}

/////////////////////////////
// Response/Event handlers

void LLVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle)
{	
	if(statusCode != 0)
	{
		LL_WARNS("Voice") << "Connector.Create response failure: " << statusString << LL_ENDL;
		setState(stateConnectorFailed);
	}
	else
	{
		// Connector created, move forward.
		mConnectorHandle = connectorHandle;
		if(getState() == stateConnectorStarting)
		{
			setState(stateConnectorStarted);
		}
	}
}

void LLVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle)
{ 
	LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL;
	
	// Status code of 20200 means "bad password".  We may want to special-case that at some point.
	
	if ( statusCode == 401 )
	{
		// Login failure which is probably caused by the delay after a user's password being updated.
		LL_INFOS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL;
		setState(stateLoginRetry);
	}
	else if(statusCode != 0)
	{
		LL_WARNS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL;
		setState(stateLoginFailed);
	}
	else
	{
		// Login succeeded, move forward.
		mAccountHandle = accountHandle;
		// MBW -- XXX -- This needs to wait until the LoginStateChangeEvent is received.
//		if(getState() == stateLoggingIn)
//		{
//			setState(stateLoggedIn);
//		}
	}
}

void LLVoiceClient::channelGetListResponse(int statusCode, std::string &statusString)
{
	if(statusCode != 0)
	{
		LL_WARNS("Voice") << "Account.ChannelGetList response failure: " << statusString << LL_ENDL;
		switchChannel();
	}
	else
	{
		// Got the channel list, try to do a lookup.
		std::string uri = findChannelURI(mChannelName);
		if(uri.empty())
		{	
			// Lookup failed, can't join a channel for this area.
			LL_INFOS("Voice") << "failed to map channel name: " << mChannelName << LL_ENDL;
		}
		else
		{
			// We have a sip URL for this area.
			LL_INFOS("Voice") << "mapped channel " << mChannelName << " to URI "<< uri << LL_ENDL;
		}
		
		// switchChannel with an empty uri string will do the right thing (leave channel and not rejoin)
		switchChannel(uri);
	}
}

void LLVoiceClient::sessionCreateResponse(int statusCode, std::string &statusString, std::string &sessionHandle)
{	
	if(statusCode != 0)
	{
		LL_WARNS("Voice") << "Session.Create response failure (" << statusCode << "): " << statusString << LL_ENDL;
//		if(statusCode == 1015)
//		{
//			if(getState() == stateJoiningSession)
//			{
//				// this happened during a real join.  Going to sessionTerminated should cause a retry in appropriate cases.
//				LL_WARNS("Voice") << "session handle \"" << sessionHandle << "\", mSessionStateEventHandle \"" << mSessionStateEventHandle << "\""<< LL_ENDL;
//				if(!sessionHandle.empty())
//				{
//					// This session is bad.  Terminate it.
//					mSessionHandle = sessionHandle;
//					sessionTerminateByHandle(sessionHandle);
//					setState(stateLeavingSession);
//				}
//				else if(!mSessionStateEventHandle.empty())
//				{
//					mSessionHandle = mSessionStateEventHandle;
//					sessionTerminateByHandle(mSessionStateEventHandle);
//					setState(stateLeavingSession);
//				}
//				else
//				{
//					setState(stateSessionTerminated);
//				}
//			}
//			else
//			{
//				// We didn't think we were in the middle of a join.  Don't change state.
//				LL_WARNS("Voice") << "Not in stateJoiningSession, ignoring" << LL_ENDL;
//			}
//		}
//		else
		{
			mVivoxErrorStatusCode = statusCode;		
			mVivoxErrorStatusString = statusString;
			setState(stateJoinSessionFailed);
		}
	}
	else
	{
		LL_DEBUGS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL;
		if(getState() == stateJoiningSession)
		{
			// This is also grabbed in the SessionStateChangeEvent handler, but it might be useful to have it early...
			mSessionHandle = sessionHandle;
		}
		else
		{
			// We should never get a session.create response in any state except stateJoiningSession.  Things are out of sync.  Kill this session.
			sessionTerminateByHandle(sessionHandle);
		}
	}
}

void LLVoiceClient::sessionConnectResponse(int statusCode, std::string &statusString)
{
	if(statusCode != 0)
	{
		LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL;
//		if(statusCode == 1015)
//		{
//			LL_WARNS("Voice") << "terminating existing session" << LL_ENDL;
//			sessionTerminate();
//		}
//		else
		{
			mVivoxErrorStatusCode = statusCode;		
			mVivoxErrorStatusString = statusString;
			setState(stateJoinSessionFailed);
		}
	}
	else
	{
		LL_DEBUGS("Voice") << "Session.Connect response received (success)" << LL_ENDL;
	}
}

void LLVoiceClient::sessionTerminateResponse(int statusCode, std::string &statusString)
{	
	if(statusCode != 0)
	{
		LL_WARNS("Voice") << "Session.Terminate response failure: (" << statusCode << "): " << statusString << LL_ENDL;
		if(getState() == stateLeavingSession)
		{
			// This is probably "(404): Server reporting Failure. Not a member of this conference."
			// Do this so we don't get stuck.
			setState(stateSessionTerminated);
		}
	}
	
}

void LLVoiceClient::logoutResponse(int statusCode, std::string &statusString)
{	
	if(statusCode != 0)
	{
		LL_WARNS("Voice") << "Account.Logout response failure: " << statusString << LL_ENDL;
		// MBW -- XXX -- Should this ever fail?  do we care if it does?
	}
	
	if(getState() == stateLoggingOut)
	{
		setState(stateLoggedOut);
	}
}

void LLVoiceClient::connectorShutdownResponse(int statusCode, std::string &statusString)
{
	if(statusCode != 0)
	{
		LL_WARNS("Voice") << "Connector.InitiateShutdown response failure: " << statusString << LL_ENDL;
		// MBW -- XXX -- Should this ever fail?  do we care if it does?
	}
	
	mConnected = false;
	
	if(getState() == stateConnectorStopping)
	{
		setState(stateConnectorStopped);
	}
}

void LLVoiceClient::sessionStateChangeEvent(
		std::string &uriString, 
		int statusCode, 
		std::string &statusString, 
		std::string &sessionHandle,
		int state,  
		bool isChannel, 
		std::string &nameString)
{
	switch(state)
	{
		case 4:	// I see this when joining the session
			LL_INFOS("Voice") << "joined session " << uriString << ", name " << nameString << " handle " << mNextSessionHandle << LL_ENDL;

			// Session create succeeded, move forward.
			mSessionStateEventHandle = sessionHandle;
			mSessionStateEventURI = uriString;
			if(sessionHandle == mSessionHandle)
			{
				// This is the session we're joining.
				if(getState() == stateJoiningSession)
				{
					setState(stateSessionJoined);
					//RN: the uriString being returned by vivox here is actually your account uri, not the channel
					// you are attempting to join, so ignore it
					//LL_DEBUGS("Voice") << "received URI " << uriString << "(previously " << mSessionURI << ")" << LL_ENDL;
					//mSessionURI = uriString;
				}
			}
			else if(sessionHandle == mNextSessionHandle)
			{
//				LL_DEBUGS("Voice") << "received URI " << uriString << ", name " << nameString << " for next session (handle " << mNextSessionHandle << ")" << LL_ENDL;
			}
			else
			{
				LL_WARNS("Voice") << "joining unknown session handle " << sessionHandle << ", URI " << uriString << ", name " << nameString << LL_ENDL;
				// MBW -- XXX -- Should we send a Session.Terminate here?
			}
			
		break;
		case 5:	// I see this when leaving the session
			LL_INFOS("Voice") << "left session " << uriString << ", name " << nameString << " handle " << mNextSessionHandle << LL_ENDL;

			// Set the session handle to the empty string.  If we get back to stateJoiningSession, we'll want to wait for the new session handle.
			if(sessionHandle == mSessionHandle)
			{
				// MBW -- XXX -- I think this is no longer necessary, now that we've got mNextSessionURI/mNextSessionHandle
				// mSessionURI.clear();
				// clear the session handle here just for sanity.
				mSessionHandle.clear();
				if(mSessionResetOnClose)
				{
					mSessionResetOnClose = false;
					mNonSpatialChannel = false;
					mNextSessionSpatial = true;
					parcelChanged();
				}
			
				removeAllParticipants();
				
				switch(getState())
				{
					case stateJoiningSession:
					case stateSessionJoined:
					case stateRunning:
					case stateLeavingSession:
					case stateJoinSessionFailed:
					case stateJoinSessionFailedWaiting:
						// normal transition
						LL_INFOS("Voice") << "left session " << sessionHandle << "in state " << state2string(getState()) << LL_ENDL;
						setState(stateSessionTerminated);
					break;
					
					case stateSessionTerminated:
						// this will happen sometimes -- there are cases where we send the terminate and then go straight to this state.
						LL_WARNS("Voice") << "left session " << sessionHandle << "in state " << state2string(getState()) << LL_ENDL;
					break;
					
					default:
						LL_WARNS("Voice") << "unexpected SessionStateChangeEvent (left session) in state " << state2string(getState()) << LL_ENDL;
						setState(stateSessionTerminated);
					break;
				}

				// store status values for later notification of observers
				mVivoxErrorStatusCode = statusCode;		
				mVivoxErrorStatusString = statusString;
			}
			else
			{
				LL_INFOS("Voice") << "leaving unknown session handle " << sessionHandle << ", URI " << uriString << ", name " << nameString << LL_ENDL;
			}

			mSessionStateEventHandle.clear();
			mSessionStateEventURI.clear();
		break;
		default:
			LL_WARNS("Voice") << "unknown state: " << state << LL_ENDL;
		break;
	}
}

void LLVoiceClient::loginStateChangeEvent(
		std::string &accountHandle, 
		int statusCode, 
		std::string &statusString, 
		int state)
{
	LL_DEBUGS("Voice") << "state is " << state << LL_ENDL;
	/*
		According to Mike S., status codes for this event are:
		login_state_logged_out=0,
        login_state_logged_in = 1,
        login_state_logging_in = 2,
        login_state_logging_out = 3,
        login_state_resetting = 4,
        login_state_error=100	
	*/
	
	switch(state)
	{
		case 1:
		if(getState() == stateLoggingIn)
		{
			setState(stateLoggedIn);
		}
		break;
		
		default:
			//Used to be a commented out warning
			LL_DEBUGS("Voice") << "unknown state: " << state << LL_ENDL;
		break;
	}
}

void LLVoiceClient::sessionNewEvent(
		std::string &accountHandle, 
		std::string &eventSessionHandle, 
		int state, 
		std::string &nameString, 
		std::string &uriString)
{
	LL_DEBUGS("Voice") << "state is " << state << LL_ENDL;
	
	switch(state)
	{
		case 0:
			{
				LL_DEBUGS("Voice") << "session handle = " << eventSessionHandle << ", name = " << nameString << ", uri = " << uriString << LL_ENDL;

				LLUUID caller_id;
				if(IDFromName(nameString, caller_id))
				{
					gIMMgr->inviteToSession(
						LLIMMgr::computeSessionID(
							IM_SESSION_P2P_INVITE,
							caller_id),
						LLStringUtil::null,
						caller_id, 
						LLStringUtil::null, 
						IM_SESSION_P2P_INVITE, 
						LLIMMgr::INVITATION_TYPE_VOICE,
						eventSessionHandle);
				}
				else
				{
					LL_WARNS("Voice") << "Could not generate caller id from uri " << uriString << LL_ENDL;
				}
			}
		break;
		
		default:
			LL_WARNS("Voice") << "unknown state: " << state << LL_ENDL;
		break;
	}
}

void LLVoiceClient::participantStateChangeEvent(
		std::string &uriString, 
		int statusCode, 
		std::string &statusString, 
		int state,  
		std::string &nameString, 
		std::string &displayNameString, 
		int participantType)
{
	participantState *participant = NULL;
	LL_DEBUGS("Voice") << "state is " << state << LL_ENDL;

	switch(state)
	{
		case 7:	// I see this when a participant joins
			participant = addParticipant(uriString);
			if(participant)
			{
				participant->mName = nameString;
				LL_DEBUGS("Voice") << "added participant \"" << participant->mName 
						<< "\" (" << participant->mAvatarID << ")"<< LL_ENDL;
			}
		break;
		case 9:	// I see this when a participant leaves
			participant = findParticipant(uriString);
			if(participant)
			{
				removeParticipant(participant);
			}
		break;
		default:
			LL_DEBUGS("Voice") << "unknown state: " << state << LL_ENDL;
		break;
	}
}

void LLVoiceClient::participantPropertiesEvent(
		std::string &uriString, 
		int statusCode, 
		std::string &statusString, 
		bool isLocallyMuted, 
		bool isModeratorMuted, 
		bool isSpeaking, 
		int volume, 
		F32 energy)
{
	participantState *participant = findParticipant(uriString);
	if(participant)
	{
		participant->mPTT = !isLocallyMuted;
		participant->mIsSpeaking = isSpeaking;
		participant->mIsModeratorMuted = isModeratorMuted;
		if (isSpeaking)
		{
			participant->mSpeakingTimeout.reset();
		}
		participant->mPower = energy;
		participant->mVolume = volume;
	}
	else
	{
		LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL;
	}
}

void LLVoiceClient::auxAudioPropertiesEvent(F32 energy)
{
	LL_DEBUGS("Voice") << "got energy " << energy << LL_ENDL;
	mTuningEnergy = energy;
}

void LLVoiceClient::muteListChanged()
{
	// The user's mute list has been updated.  Go through the current participant list and sync it with the mute list.

	participantMap::iterator iter = mParticipantMap.begin();
	
	for(; iter != mParticipantMap.end(); iter++)
	{
		participantState *p = iter->second;
		
		// Check to see if this participant is on the mute list already
		updateMuteState(p);
	}
}

/////////////////////////////
// Managing list of participants
LLVoiceClient::participantState::participantState(const std::string &uri) : 
	 mURI(uri), mPTT(false), mIsSpeaking(false), mIsModeratorMuted(false), mLastSpokeTimestamp(0.f), mPower(0.f), mVolume(0), mServiceType(serviceTypeUnknown),
	 mOnMuteList(false), mUserVolume(100), mVolumeDirty(false), mAvatarIDValid(false)
{
}

LLVoiceClient::participantState *LLVoiceClient::addParticipant(const std::string &uri)
{
	participantState *result = NULL;

	participantMap::iterator iter = mParticipantMap.find(uri);
	
	if(iter != mParticipantMap.end())
	{
		// Found a matching participant already in the map.
		result = iter->second;
	}

	if(!result)
	{
		// participant isn't already in one list or the other.
		result = new participantState(uri);
		mParticipantMap.insert(participantMap::value_type(uri, result));
		mParticipantMapChanged = true;
		
		// Try to do a reverse transform on the URI to get the GUID back.
		{
			LLUUID id;
			if(IDFromName(uri, id))
			{
				result->mAvatarIDValid = true;
				result->mAvatarID = id;

				updateMuteState(result);
			}
		}
		
		LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL;
	}
	
	return result;
}

void LLVoiceClient::updateMuteState(participantState *p)
{
	if(p->mAvatarIDValid)
	{
		bool isMuted = LLMuteList::getInstance()->isMuted(p->mAvatarID, LLMute::flagVoiceChat);
		if(p->mOnMuteList != isMuted)
		{
			p->mOnMuteList = isMuted;
			p->mVolumeDirty = true;
			mVolumeDirty = true;
		}
	}
}

void LLVoiceClient::removeParticipant(LLVoiceClient::participantState *participant)
{
	if(participant)
	{
		participantMap::iterator iter = mParticipantMap.find(participant->mURI);
				
		LL_DEBUGS("Voice") << "participant \"" << participant->mURI <<  "\" (" << participant->mAvatarID << ") removed." << LL_ENDL;

		mParticipantMap.erase(iter);
		delete participant;
		mParticipantMapChanged = true;
	}
}

void LLVoiceClient::removeAllParticipants()
{
	LL_DEBUGS("Voice") << "called" << LL_ENDL;

	while(!mParticipantMap.empty())
	{
		removeParticipant(mParticipantMap.begin()->second);
	}
}

LLVoiceClient::participantMap *LLVoiceClient::getParticipantList(void)
{
	return &mParticipantMap;
}


LLVoiceClient::participantState *LLVoiceClient::findParticipant(const std::string &uri)
{
	participantState *result = NULL;
	
	// Don't find any participants if we're not connected.  This is so that we don't continue to get stale data
	// after the daemon dies.
	if(mConnected)
	{
		participantMap::iterator iter = mParticipantMap.find(uri);
	
		if(iter != mParticipantMap.end())
		{
			result = iter->second;
		}
	}
			
	return result;
}


LLVoiceClient::participantState *LLVoiceClient::findParticipantByAvatar(LLVOAvatar *avatar)
{
	participantState * result = NULL;

	// You'd think this would work, but it doesn't...
//	std::string uri = sipURIFromAvatar(avatar);
	
	// Currently, the URI is just the account name.
	std::string loginName = nameFromAvatar(avatar);
	result = findParticipant(loginName);
	
	if(result != NULL)
	{
		if(!result->mAvatarIDValid)
		{
			result->mAvatarID = avatar->getID();
			result->mAvatarIDValid = true;
			
			// We just figured out the avatar ID, so the participant list has "changed" from the perspective of anyone who uses that to identify participants.
			mParticipantMapChanged = true;
			
			updateMuteState(result);
		}
		

	}

	return result;
}

LLVoiceClient::participantState* LLVoiceClient::findParticipantByID(const LLUUID& id)
{
	participantState * result = NULL;

	// Currently, the URI is just the account name.
	std::string loginName = nameFromID(id);
	result = findParticipant(loginName);

	return result;
}


void LLVoiceClient::clearChannelMap(void)
{
	mChannelMap.clear();
}

void LLVoiceClient::addChannelMapEntry(std::string &name, std::string &uri)
{
	LL_DEBUGS("Voice") << "Adding channel name mapping: " << name << " -> " << uri << LL_ENDL;
	mChannelMap.insert(channelMap::value_type(name, uri));
}

std::string LLVoiceClient::findChannelURI(std::string &name)
{
	std::string result;
	
	channelMap::iterator iter = mChannelMap.find(name);

	if(iter != mChannelMap.end())
	{
		result = iter->second;
	}
	
	return result;
}

void LLVoiceClient::parcelChanged()
{
	if(getState() >= stateLoggedIn)
	{
		// If the user is logged in, start a channel lookup.
		LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL;

		std::string url = gAgent.getRegion()->getCapability("ParcelVoiceInfoRequest");
		LLSD data;
		LLHTTPClient::post(
			url,
			data,
			new LLVoiceClientCapResponder);
	}
	else
	{
		// The transition to stateLoggedIn needs to kick this off again.
		LL_INFOS("Voice") << "not logged in yet, deferring" << LL_ENDL;
	}
}

void LLVoiceClient::switchChannel(
	std::string uri,
	bool spatial,
	bool noReconnect,
	std::string hash)
{
	bool needsSwitch = false;
	
	LL_DEBUGS("Voice") << "called in state " << state2string(getState()) << " with uri \"" << uri << "\"" << LL_ENDL;
	
	switch(getState())
	{
		case stateJoinSessionFailed:
		case stateJoinSessionFailedWaiting:
		case stateNoChannel:
			// Always switch to the new URI from these states.
			needsSwitch = true;
		break;
		
		default:
			if(mSessionTerminateRequested)
			{
				// If a terminate has been requested, we need to compare against where the URI we're already headed to.
				if(mNextSessionURI != uri)
					needsSwitch = true;
			}
			else
			{
				// Otherwise, compare against the URI we're in now.
				if(mSessionURI != uri)
					needsSwitch = true;
			}
		break;
	}
	
	if(needsSwitch)
	{
		mNextSessionURI = uri;
		mNextSessionHash = hash;
		mNextSessionHandle.clear();
		mNextP2PSessionURI.clear();
		mNextSessionSpatial = spatial;
		mNextSessionNoReconnect = noReconnect;
		
		if(uri.empty())
		{
			// Leave any channel we may be in
			LL_DEBUGS("Voice") << "leaving channel" << LL_ENDL;
			notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED);
		}
		else
		{
			LL_DEBUGS("Voice") << "switching to channel " << uri << LL_ENDL;
		}
		
		if(getState() <= stateNoChannel)
		{
			// We're already set up to join a channel, just needed to fill in the session URI
		}
		else
		{
			// State machine will come around and rejoin if uri/handle is not empty.
			sessionTerminate();
		}
	}
}

void LLVoiceClient::joinSession(std::string handle, std::string uri)
{
	mNextSessionURI.clear();
	mNextSessionHash.clear();
	mNextP2PSessionURI = uri;
	mNextSessionHandle = handle;
	mNextSessionSpatial = false;
	mNextSessionNoReconnect = false;

	if(getState() <= stateNoChannel)
	{
		// We're already set up to join a channel, just needed to fill in the session handle
	}
	else
	{
		// State machine will come around and rejoin if uri/handle is not empty.
		sessionTerminate();
	}
}

void LLVoiceClient::setNonSpatialChannel(
	const std::string &uri,
	const std::string &credentials)
{
	switchChannel(uri, false, false, credentials);
}

void LLVoiceClient::setSpatialChannel(
	const std::string &uri,
	const std::string &credentials)
{
	mSpatialSessionURI = uri;
	mAreaVoiceDisabled = mSpatialSessionURI.empty();

	LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL;
	
	if(mNonSpatialChannel || !mNextSessionSpatial)
	{
		// User is in a non-spatial chat or joining a non-spatial chat.  Don't switch channels.
		LL_INFOS("Voice") << "in non-spatial chat, not switching channels" << LL_ENDL;
	}
	else
	{
		switchChannel(mSpatialSessionURI, true, false, credentials);
	}
}

void LLVoiceClient::callUser(LLUUID &uuid)
{
	std::string userURI = sipURIFromID(uuid);

	switchChannel(userURI, false, true);
}

void LLVoiceClient::answerInvite(std::string &sessionHandle, LLUUID& other_user_id)
{
	joinSession(sessionHandle, sipURIFromID(other_user_id));
}

void LLVoiceClient::declineInvite(std::string &sessionHandle)
{
	sessionTerminateByHandle(sessionHandle);
}

void LLVoiceClient::leaveNonSpatialChannel()
{
	switchChannel(mSpatialSessionURI);
}

std::string LLVoiceClient::getCurrentChannel()
{
	if((getState() == stateRunning) && !mSessionTerminateRequested)
	{
		return mSessionURI;
	}
	
	return "";
}

bool LLVoiceClient::inProximalChannel()
{
	bool result = false;
	
	if((getState() == stateRunning) && !mSessionTerminateRequested)
	{
		result = !mNonSpatialChannel;
	}
	
	return result;
}

std::string LLVoiceClient::sipURIFromID(const LLUUID &id)
{
	std::string result;
	result = "sip:";
	result += nameFromID(id);
	result += "@";
	result += mAccountServerName;
	
	return result;
}

std::string LLVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar)
{
	std::string result;
	if(avatar)
	{
		result = "sip:";
		result += nameFromID(avatar->getID());
		result += "@";
		result += mAccountServerName;
	}
	
	return result;
}

std::string LLVoiceClient::nameFromAvatar(LLVOAvatar *avatar)
{
	std::string result;
	if(avatar)
	{
		result = nameFromID(avatar->getID());
	}	
	return result;
}

std::string LLVoiceClient::nameFromID(const LLUUID &uuid)
{
	std::string result;
	// Prepending this apparently prevents conflicts with reserved names inside the vivox and diamondware code.
	result = "x";
	
	// Base64 encode and replace the pieces of base64 that are less compatible 
	// with e-mail local-parts.
	// See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet"
	result += LLBase64::encode(uuid.mData, UUID_BYTES);
	LLStringUtil::replaceChar(result, '+', '-');
	LLStringUtil::replaceChar(result, '/', '_');
	
	// If you need to transform a GUID to this form on the Mac OS X command line, this will do so:
	// echo -n x && (echo e669132a-6c43-4ee1-a78d-6c82fff59f32 |xxd -r -p |openssl base64|tr '/+' '_-')
	
	return result;
}

bool LLVoiceClient::IDFromName(const std::string name, LLUUID &uuid)
{
	bool result = false;
	
	// This will only work if the name is of the proper form.
	// As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is:
	// "xFnPP04IpREWNkuw1cOXlhw=="
	
	if((name.size() == 25) && (name[0] == 'x') && (name[23] == '=') && (name[24] == '='))
	{
		// The name appears to have the right form.

		// Reverse the transforms done by nameFromID
		std::string temp = name;
		LLStringUtil::replaceChar(temp, '-', '+');
		LLStringUtil::replaceChar(temp, '_', '/');

		U8 rawuuid[UUID_BYTES + 1]; 
		int len = apr_base64_decode_binary(rawuuid, temp.c_str() + 1);
		if(len == UUID_BYTES)
		{
			// The decode succeeded.  Stuff the bits into the result's UUID
			memcpy(uuid.mData, rawuuid, UUID_BYTES);
			result = true;
		}
	}
	
	return result;
}

std::string LLVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar)
{
	return avatar->getFullname();
}

std::string LLVoiceClient::sipURIFromName(std::string &name)
{
	std::string result;
	result = "sip:";
	result += name;
	result += "@";
	result += mAccountServerName;

//	LLStringUtil::toLower(result);

	return result;
}

/////////////////////////////
// Sending updates of current state

void LLVoiceClient::enforceTether(void)
{
	LLVector3d tethered	= mCameraRequestedPosition;

	// constrain 'tethered' to within 50m of mAvatarPosition.
	{
		F32 max_dist = 50.0f;
		LLVector3d camera_offset = mCameraRequestedPosition - mAvatarPosition;
		F32 camera_distance = (F32)camera_offset.magVec();
		if(camera_distance > max_dist)
		{
			tethered = mAvatarPosition + 
				(max_dist / camera_distance) * camera_offset;
		}
	}
	
	if(dist_vec(mCameraPosition, tethered) > 0.1)
	{
		mCameraPosition = tethered;
		mSpatialCoordsDirty = true;
	}
}

void LLVoiceClient::updatePosition(void)
{
	
	if(gVoiceClient)
	{
		LLVOAvatar *agent = gAgent.getAvatarObject();
		LLViewerRegion *region = gAgent.getRegion();
		if(region && agent)
		{
			LLMatrix3 rot;
			LLVector3d pos;

			// MBW -- XXX -- Setting both camera and avatar velocity to 0 for now.  May figure it out later...

			// Send the current camera position to the voice code
			rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (),  LLViewerCamera::getInstance()->getUpAxis());		
			pos = gAgent.getRegion()->getPosGlobalFromRegion(LLViewerCamera::getInstance()->getOrigin());
			
			gVoiceClient->setCameraPosition(
					pos,				// position
					LLVector3::zero, 	// velocity
					rot);				// rotation matrix
					
			// Send the current avatar position to the voice code
			rot = agent->getRootJoint()->getWorldRotation().getMatrix3();
	
			pos = agent->getPositionGlobal();
			// MBW -- XXX -- Can we get the head offset from outside the LLVOAvatar?
//			pos += LLVector3d(mHeadOffset);
			pos += LLVector3d(0.f, 0.f, 1.f);
		
			gVoiceClient->setAvatarPosition(
					pos,				// position
					LLVector3::zero, 	// velocity
					rot);				// rotation matrix
		}
	}
}

void LLVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot)
{
	mCameraRequestedPosition = position;
	
	if(mCameraVelocity != velocity)
	{
		mCameraVelocity = velocity;
		mSpatialCoordsDirty = true;
	}
	
	if(mCameraRot != rot)
	{
		mCameraRot = rot;
		mSpatialCoordsDirty = true;
	}
}

void LLVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot)
{
	if(dist_vec(mAvatarPosition, position) > 0.1)
	{
		mAvatarPosition = position;
		mSpatialCoordsDirty = true;
	}
	
	if(mAvatarVelocity != velocity)
	{
		mAvatarVelocity = velocity;
		mSpatialCoordsDirty = true;
	}
	
	if(mAvatarRot != rot)
	{
		mAvatarRot = rot;
		mSpatialCoordsDirty = true;
	}
}

bool LLVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name)
{
	bool result = false;
	
	if(region)
	{
		name = region->getName();
	}
	
	if(!name.empty())
		result = true;
	
	return result;
}

void LLVoiceClient::leaveChannel(void)
{
	if(getState() == stateRunning)
	{
		LL_DEBUGS("Voice") << "leaving channel for teleport/logout" << LL_ENDL;
		mChannelName.clear();
		sessionTerminate();
	}
}

void LLVoiceClient::setMuteMic(bool muted)
{
	mMuteMic = muted;
}

void LLVoiceClient::setUserPTTState(bool ptt)
{
	mUserPTTState = ptt;
}

bool LLVoiceClient::getUserPTTState()
{
	return mUserPTTState;
}

void LLVoiceClient::toggleUserPTTState(void)
{
	mUserPTTState = !mUserPTTState;
}

void LLVoiceClient::setVoiceEnabled(bool enabled)
{
	if (enabled != mVoiceEnabled)
	{
		mVoiceEnabled = enabled;
		if (enabled)
		{
			LLVoiceChannel::getCurrentVoiceChannel()->activate();
		}
		else
		{
			// for now, leave active channel, to auto join when turning voice back on
			//LLVoiceChannel::getCurrentVoiceChannel->deactivate();
		}
	}
}

bool LLVoiceClient::voiceEnabled()
{
	return gSavedSettings.getBOOL("EnableVoiceChat") && !gSavedSettings.getBOOL("CmdLineDisableVoice");
}

void LLVoiceClient::setLipSyncEnabled(BOOL enabled)
{
	mLipSyncEnabled = enabled;
}

BOOL LLVoiceClient::lipSyncEnabled()
{
	   
	if ( mVoiceEnabled && stateDisabled != getState() )
	{
		return mLipSyncEnabled;
	}
	else
	{
		return FALSE;
	}
}

void LLVoiceClient::setUsePTT(bool usePTT)
{
	if(usePTT && !mUsePTT)
	{
		// When the user turns on PTT, reset the current state.
		mUserPTTState = false;
	}
	mUsePTT = usePTT;
}

void LLVoiceClient::setPTTIsToggle(bool PTTIsToggle)
{
	if(!PTTIsToggle && mPTTIsToggle)
	{
		// When the user turns off toggle, reset the current state.
		mUserPTTState = false;
	}
	
	mPTTIsToggle = PTTIsToggle;
}


void LLVoiceClient::setPTTKey(std::string &key)
{
	if(key == "MiddleMouse")
	{
		mPTTIsMiddleMouse = true;
	}
	else
	{
		mPTTIsMiddleMouse = false;
		if(!LLKeyboard::keyFromString(key, &mPTTKey))
		{
			// If the call failed, don't match any key.
			key = KEY_NONE;
		}
	}
}

void LLVoiceClient::setEarLocation(S32 loc)
{
	if(mEarLocation != loc)
	{
		LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL;
		
		mEarLocation = loc;
		mSpatialCoordsDirty = true;
	}
}

void LLVoiceClient::setVoiceVolume(F32 volume)
{
	LL_DEBUGS("Voice") << "volume is " << volume << LL_ENDL;

	// incoming volume has the range [0.0 ... 1.0], with 0.5 as the default.
	// Map it as follows: 0.0 -> -100, 0.5 -> 24, 1.0 -> 50
	
	volume -= 0.5f;		// offset volume to the range [-0.5 ... 0.5], with 0 at the default.
	int scaledVolume = 24;	// offset scaledVolume by its default level
	if(volume < 0.0f)
		scaledVolume += ((int)(volume * 248.0f));	// (24 - (-100)) * 2
	else
		scaledVolume += ((int)(volume * 52.0f));	// (50 - 24) * 2
	
	if(scaledVolume != mSpeakerVolume)
	{
		if((scaledVolume == -100) || (mSpeakerVolume == -100))
		{
			mSpeakerMuteDirty = true;
		}

		mSpeakerVolume = scaledVolume;
		mSpeakerVolumeDirty = true;
	}
}

void LLVoiceClient::setMicGain(F32 volume)
{
	int scaledVolume = ((int)(volume * 100.0f)) - 100;
	if(scaledVolume != mMicVolume)
	{
		mMicVolume = scaledVolume;
		mMicVolumeDirty = true;
	}
}

void LLVoiceClient::setVivoxDebugServerName(std::string &serverName)
{
	if(!mAccountServerName.empty())
	{
		// The name has been filled in already, which means we know whether we're connecting to agni or not.
		if(!sConnectingToAgni)
		{
			// Only use the setting if we're connecting to a development grid -- always use bhr when on agni.
			mAccountServerName = serverName;
		}
	}
}

void LLVoiceClient::keyDown(KEY key, MASK mask)
{	
	LL_DEBUGS("Voice") << "key is " << LLKeyboard::stringFromKey(key) << LL_ENDL;

	if (gKeyboard->getKeyRepeated(key))
	{
		// ignore auto-repeat keys
		return;
	}

	if(!mPTTIsMiddleMouse)
	{
		if(mPTTIsToggle)
		{
			if(key == mPTTKey)
			{
				toggleUserPTTState();
			}
		}
		else if(mPTTKey != KEY_NONE)
		{
			setUserPTTState(gKeyboard->getKeyDown(mPTTKey));
		}
	}
}
void LLVoiceClient::keyUp(KEY key, MASK mask)
{
	if(!mPTTIsMiddleMouse)
	{
		if(!mPTTIsToggle && (mPTTKey != KEY_NONE))
		{
			setUserPTTState(gKeyboard->getKeyDown(mPTTKey));
		}
	}
}
void LLVoiceClient::middleMouseState(bool down)
{
	if(mPTTIsMiddleMouse)
	{
		if(mPTTIsToggle)
		{
			if(down)
			{
				toggleUserPTTState();
			}
		}
		else
		{
			setUserPTTState(down);
		}
	}
}

/////////////////////////////
// Accessors for data related to nearby speakers
BOOL LLVoiceClient::getVoiceEnabled(const LLUUID& id)
{
	BOOL result = FALSE;
	participantState *participant = findParticipantByID(id);
	if(participant)
	{
		// I'm not sure what the semantics of this should be.
		// For now, if we have any data about the user that came through the chat channel, assume they're voice-enabled.
		result = TRUE;
	}
	
	return result;
}

BOOL LLVoiceClient::getIsSpeaking(const LLUUID& id)
{
	BOOL result = FALSE;

	participantState *participant = findParticipantByID(id);
	if(participant)
	{
		if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT)
		{
			participant->mIsSpeaking = FALSE;
		}
		result = participant->mIsSpeaking;
	}
	
	return result;
}

BOOL LLVoiceClient::getIsModeratorMuted(const LLUUID& id)
{
	BOOL result = FALSE;

	participantState *participant = findParticipantByID(id);
	if(participant)
	{
		result = participant->mIsModeratorMuted;
	}
	
	return result;
}

F32 LLVoiceClient::getCurrentPower(const LLUUID& id)
{		
	F32 result = 0;
	participantState *participant = findParticipantByID(id);
	if(participant)
	{
		result = participant->mPower;
	}
	
	return result;
}


std::string LLVoiceClient::getDisplayName(const LLUUID& id)
{
	std::string result;
	participantState *participant = findParticipantByID(id);
	if(participant)
	{
		result = participant->mDisplayName;
	}
	
	return result;
}


BOOL LLVoiceClient::getUsingPTT(const LLUUID& id)
{
	BOOL result = FALSE;

	participantState *participant = findParticipantByID(id);
	if(participant)
	{
		// I'm not sure what the semantics of this should be.
		// Does "using PTT" mean they're configured with a push-to-talk button?
		// For now, we know there's no PTT mechanism in place, so nobody is using it.
	}
	
	return result;
}

BOOL LLVoiceClient::getPTTPressed(const LLUUID& id)
{
	BOOL result = FALSE;
	
	participantState *participant = findParticipantByID(id);
	if(participant)
	{
		result = participant->mPTT;
	}
	
	return result;
}

BOOL LLVoiceClient::getOnMuteList(const LLUUID& id)
{
	BOOL result = FALSE;
	
	participantState *participant = findParticipantByID(id);
	if(participant)
	{
		result = participant->mOnMuteList;
	}

	return result;
}

// External accessiors. Maps 0.0 to 1.0 to internal values 0-400 with .5 == 100
// internal = 400 * external^2
F32 LLVoiceClient::getUserVolume(const LLUUID& id)
{
	F32 result = 0.0f;
	
	participantState *participant = findParticipantByID(id);
	if(participant)
	{
		S32 ires = participant->mUserVolume; // 0-400
		result = sqrtf(((F32)ires) / 400.f);
	}

	return result;
}

void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume)
{
	participantState *participant = findParticipantByID(id);
	if (participant)
	{
		// volume can amplify by as much as 4x!
		S32 ivol = (S32)(400.f * volume * volume);
		participant->mUserVolume = llclamp(ivol, 0, 400);
		participant->mVolumeDirty = TRUE;
		mVolumeDirty = TRUE;
	}
}



LLVoiceClient::serviceType LLVoiceClient::getServiceType(const LLUUID& id)
{
	serviceType result = serviceTypeUnknown;

	participantState *participant = findParticipantByID(id);
	if(participant)
	{
		result = participant->mServiceType;
	}
	
	return result;
}

std::string LLVoiceClient::getGroupID(const LLUUID& id)
{
	std::string result;

	participantState *participant = findParticipantByID(id);
	if(participant)
	{
		result = participant->mGroupID;
	}
	
	return result;
}

BOOL LLVoiceClient::getAreaVoiceDisabled()
{
	return mAreaVoiceDisabled;
}

void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer)
{
	mObservers.insert(observer);
}

void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer)
{
	mObservers.erase(observer);
}

void LLVoiceClient::notifyObservers()
{
	for (observer_set_t::iterator it = mObservers.begin();
		it != mObservers.end();
		)
	{
		LLVoiceClientParticipantObserver* observer = *it;
		observer->onChange();
		// In case onChange() deleted an entry.
		it = mObservers.upper_bound(observer);
	}
}

void LLVoiceClient::addStatusObserver(LLVoiceClientStatusObserver* observer)
{
	mStatusObservers.insert(observer);
}

void LLVoiceClient::removeStatusObserver(LLVoiceClientStatusObserver* observer)
{
	mStatusObservers.erase(observer);
}

void LLVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status)
{
	if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN)
	{
		switch(mVivoxErrorStatusCode)
		{
		case 20713:		status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; 		break;
		case 20714:		status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; 	break;
		case 20715:
			//invalid channel, we may be using a set of poorly cached
			//info
			status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE;
			break;
		case 1009:
			//invalid username and password
			status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE;
			break;
		}

		// Reset the error code to make sure it won't be reused later by accident.
		mVivoxErrorStatusCode = 0;
	}
	
	if (status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL 
		//NOT_FOUND || TEMPORARILY_UNAVAILABLE || REQUEST_TIMEOUT
		&& (mVivoxErrorStatusCode == 404 || mVivoxErrorStatusCode == 480 || mVivoxErrorStatusCode == 408)) 
	{
		// call failed because other user was not available
		// treat this as an error case
		status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE;

		// Reset the error code to make sure it won't be reused later by accident.
		mVivoxErrorStatusCode = 0;
	}
	
	LL_DEBUGS("Voice") << " " << LLVoiceClientStatusObserver::status2string(status)  << ", session URI " << mSessionURI << LL_ENDL;

	for (status_observer_set_t::iterator it = mStatusObservers.begin();
		it != mStatusObservers.end();
		)
	{
		LLVoiceClientStatusObserver* observer = *it;
		observer->onChange(status, mSessionURI, !mNonSpatialChannel);
		// In case onError() deleted an entry.
		it = mStatusObservers.upper_bound(observer);
	}

}

//static
// void LLVoiceClient::onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data)
// {
// 	participantState* statep = gVoiceClient->findParticipantByID(id);

// 	if (statep)
// 	{
// 		statep->mDisplayName = first + " " + last;
// 	}
	
// 	gVoiceClient->notifyObservers();
// }

class LLViewerParcelVoiceInfo : public LLHTTPNode
{
	virtual void post(
		LLHTTPNode::ResponsePtr response,
		const LLSD& context,
		const LLSD& input) const
	{
		//the parcel you are in has changed something about its
		//voice information

		//this is a misnomer, as it can also be when you are not in
		//a parcel at all.  Should really be something like
		//LLViewerVoiceInfoChanged.....
		if ( input.has("body") )
		{
			LLSD body = input["body"];

			//body has "region_name" (str), "parcel_local_id"(int),
			//"voice_credentials" (map).

			//body["voice_credentials"] has "channel_uri" (str),
			//body["voice_credentials"] has "channel_credentials" (str)

			//if we really wanted to be extra careful,
			//we'd check the supplied
			//local parcel id to make sure it's for the same parcel
			//we believe we're in
			if ( body.has("voice_credentials") )
			{
				LLSD voice_credentials = body["voice_credentials"];
				std::string uri;
				std::string credentials;

				if ( voice_credentials.has("channel_uri") )
				{
					uri = voice_credentials["channel_uri"].asString();
				}
				if ( voice_credentials.has("channel_credentials") )
				{
					credentials =
						voice_credentials["channel_credentials"].asString();
				}

				gVoiceClient->setSpatialChannel(uri, credentials);
			}
		}
	}
};

class LLViewerRequiredVoiceVersion : public LLHTTPNode
{
	static BOOL sAlertedUser;
	virtual void post(
		LLHTTPNode::ResponsePtr response,
		const LLSD& context,
		const LLSD& input) const
	{
		//You received this messsage (most likely on region cross or
		//teleport)
		if ( input.has("body") && input["body"].has("major_version") )
		{
			int major_voice_version =
				input["body"]["major_version"].asInteger();
// 			int minor_voice_version =
// 				input["body"]["minor_version"].asInteger();

			if (gVoiceClient &&
				(major_voice_version > VOICE_MAJOR_VERSION) )
			{
				if (!sAlertedUser)
				{
					//sAlertedUser = TRUE;
					gViewerWindow->alertXml("VoiceVersionMismatch");
					gSavedSettings.setBOOL("EnableVoiceChat", FALSE); // toggles listener
				}
			}
		}
	}
};
BOOL LLViewerRequiredVoiceVersion::sAlertedUser = FALSE;

LLHTTPRegistration<LLViewerParcelVoiceInfo>
    gHTTPRegistrationMessageParcelVoiceInfo(
		"/message/ParcelVoiceInfo");

LLHTTPRegistration<LLViewerRequiredVoiceVersion>
    gHTTPRegistrationMessageRequiredVoiceVersion(
		"/message/RequiredVoiceVersion");