From dd437009e88954fd0fe9dd95b903dbd1ea52e901 Mon Sep 17 00:00:00 2001
From: Monroe Williams <monroe@lindenlab.com>
Date: Fri, 27 Feb 2009 21:01:19 +0000
Subject: [PATCH] svn merge -r 113014:113017
 svn+ssh://svn.lindenlab.com/svn/linden/branches/merge-QAR-1323

Merging in QAR-1323.
---
 indra/newview/app_settings/settings.xml       |   30 +-
 .../installers/darwin/dmg-cleanup.applescript |   28 +
 indra/newview/llfloaterfriends.cpp            |   40 +-
 indra/newview/llimpanel.cpp                   |   88 +-
 indra/newview/llimpanel.h                     |    4 +-
 indra/newview/llimview.cpp                    |   12 +-
 indra/newview/llimview.h                      |    6 +-
 indra/newview/llviewercontrol.cpp             |    1 -
 indra/newview/llvoiceclient.cpp               | 5588 ++++++++++++-----
 indra/newview/llvoiceclient.h                 |  439 +-
 .../default/textures/slim_icon_16_viewer.tga  |  Bin 0 -> 1032 bytes
 indra/newview/viewer_manifest.py              |    6 -
 install.xml                                   |   12 +-
 13 files changed, 4613 insertions(+), 1641 deletions(-)
 create mode 100644 indra/newview/installers/darwin/dmg-cleanup.applescript
 create mode 100644 indra/newview/skins/default/textures/slim_icon_16_viewer.tga

diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 523032bf673..e964799e4ce 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -10198,6 +10198,17 @@
       <key>Value</key>
       <integer>0</integer>
     </map>
+    <key>VivoxAutoPostCrashDumps</key>
+    <map>
+      <key>Comment</key>
+      <string>If true, SLVoice will automatically send crash dumps directly to Vivox.</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>Boolean</string>
+      <key>Value</key>
+      <integer>0</integer>
+    </map>
     <key>VivoxDebugLevel</key>
     <map>
       <key>Comment</key>
@@ -10209,16 +10220,27 @@
       <key>Value</key>
       <string>-1</string>
     </map>
-    <key>VivoxDebugServerName</key>
+    <key>VivoxDebugSIPURIHostName</key>
+    <map>
+      <key>Comment</key>
+      <string>Hostname portion of vivox SIP URIs (empty string for the default).</string>
+      <key>Persist</key>
+      <integer>1</integer>
+      <key>Type</key>
+      <string>String</string>
+      <key>Value</key>
+      <string></string>
+    </map>
+    <key>VivoxDebugVoiceAccountServerURI</key>
     <map>
       <key>Comment</key>
-      <string>Hostname of the vivox account server to use for voice when not connected to Agni.</string>
+      <string>URI to the vivox account management server (empty string for the default).</string>
       <key>Persist</key>
       <integer>1</integer>
       <key>Type</key>
       <string>String</string>
       <key>Value</key>
-      <string>bhd.vivox.com</string>
+      <string></string>
     </map>
     <key>VoiceCallsFriendsOnly</key>
     <map>
@@ -10361,7 +10383,7 @@
       <key>Type</key>
       <string>U32</string>
       <key>Value</key>
-      <integer>44124</integer>
+      <integer>44125</integer>
     </map>
     <key>WLSkyDetail</key>
     <map>
diff --git a/indra/newview/installers/darwin/dmg-cleanup.applescript b/indra/newview/installers/darwin/dmg-cleanup.applescript
new file mode 100644
index 00000000000..f3d39aec218
--- /dev/null
+++ b/indra/newview/installers/darwin/dmg-cleanup.applescript
@@ -0,0 +1,28 @@
+-- First, convert the disk image to "read-write" format with Disk Utility or hdiutil
+-- Mount the image, open the disk image window in the Finder and make it frontmost, then run this script from inside Script Editor
+-- After running the script, unmount the disk image, re-mount it, and copy the .DS_Store file off from the command line.
+
+tell application "Finder"
+	
+	set foo to every item in front window
+	repeat with i in foo
+		if the name of i is "Applications" then
+			set the position of i to {391, 165}
+		else if the name of i ends with ".app" then
+			set the position of i to {121, 166}
+		end if
+	end repeat
+	
+	-- There doesn't seem to be a way to set the background picture with applescript, but all the saved .DS_Store files should already have that set correctly.
+	
+	set foo to front window
+	set current view of foo to icon view
+	set toolbar visible of foo to false
+	set statusbar visible of foo to false
+	set the bounds of foo to {100, 100, 600, 399}
+	
+	-- set the position of front window to {100, 100}
+	-- get {name, position} of every item of front window
+	
+	get properties of front window
+end tell
diff --git a/indra/newview/llfloaterfriends.cpp b/indra/newview/llfloaterfriends.cpp
index f5133584b52..014a631a53e 100644
--- a/indra/newview/llfloaterfriends.cpp
+++ b/indra/newview/llfloaterfriends.cpp
@@ -58,6 +58,7 @@
 #include "llviewermessage.h"
 #include "lltimer.h"
 #include "lltextbox.h"
+#include "llvoiceclient.h"
 
 //Maximum number of people you can select to do an operation on at once.
 #define MAX_FRIEND_SELECT 20
@@ -65,6 +66,8 @@
 #define RIGHTS_CHANGE_TIMEOUT 5.0
 #define OBSERVER_TIMEOUT 0.5
 
+#define ONLINE_SIP_ICON_NAME "slim_icon_16_viewer.tga"
+
 // simple class to observe the calling cards.
 class LLLocalFriendsObserver : public LLFriendObserver, public LLEventTimer
 {
@@ -112,10 +115,14 @@ LLPanelFriends::LLPanelFriends() :
 	mEventTimer.stop();
 	mObserver = new LLLocalFriendsObserver(this);
 	LLAvatarTracker::instance().addObserver(mObserver);
+	// For notification when SIP online status changes.
+	LLVoiceClient::getInstance()->addObserver(mObserver);
 }
 
 LLPanelFriends::~LLPanelFriends()
 {
+	// For notification when SIP online status changes.
+	LLVoiceClient::getInstance()->removeObserver(mObserver);
 	LLAvatarTracker::instance().removeObserver(mObserver);
 	delete mObserver;
 }
@@ -213,7 +220,9 @@ BOOL LLPanelFriends::addFriend(const LLUUID& agent_id)
 	LLAvatarTracker& at = LLAvatarTracker::instance();
 	const LLRelationship* relationInfo = at.getBuddyInfo(agent_id);
 	if(!relationInfo) return FALSE;
-	BOOL online = relationInfo->isOnline();
+
+	bool isOnlineSIP = LLVoiceClient::getInstance()->isOnlineSIP(agent_id);
+	bool isOnline = relationInfo->isOnline();
 
 	std::string fullname;
 	BOOL have_name = gCacheName->getFullName(agent_id, fullname);
@@ -229,12 +238,17 @@ BOOL LLPanelFriends::addFriend(const LLUUID& agent_id)
 	LLSD& online_status_column = element["columns"][LIST_ONLINE_STATUS];
 	online_status_column["column"] = "icon_online_status";
 	online_status_column["type"] = "icon";
-
-	if (online)
+	
+	if (isOnline)
 	{
 		friend_column["font-style"] = "BOLD";	
 		online_status_column["value"] = "icon_avatar_online.tga";
 	}
+	else if(isOnlineSIP)
+	{
+		friend_column["font-style"] = "BOLD";	
+		online_status_column["value"] = ONLINE_SIP_ICON_NAME;
+	}
 
 	LLSD& online_column = element["columns"][LIST_VISIBLE_ONLINE];
 	online_column["column"] = "icon_visible_online";
@@ -272,14 +286,30 @@ BOOL LLPanelFriends::updateFriendItem(const LLUUID& agent_id, const LLRelationsh
 	if (!info) return FALSE;
 	LLScrollListItem* itemp = mFriendsList->getItem(agent_id);
 	if (!itemp) return FALSE;
+	
+	bool isOnlineSIP = LLVoiceClient::getInstance()->isOnlineSIP(itemp->getUUID());
+	bool isOnline = info->isOnline();
 
 	std::string fullname;
 	BOOL have_name = gCacheName->getFullName(agent_id, fullname);
+	
+	// Name of the status icon to use
+	std::string statusIcon;
+	
+	if(isOnline)
+	{
+		statusIcon = "icon_avatar_online.tga";
+	}
+	else if(isOnlineSIP)
+	{
+		statusIcon = ONLINE_SIP_ICON_NAME;
+	}
 
-	itemp->getColumn(LIST_ONLINE_STATUS)->setValue(info->isOnline() ? std::string("icon_avatar_online.tga") : LLStringUtil::null);
+	itemp->getColumn(LIST_ONLINE_STATUS)->setValue(statusIcon);
+	
 	itemp->getColumn(LIST_FRIEND_NAME)->setValue(fullname);
 	// render name of online friends in bold text
-	((LLScrollListText*)itemp->getColumn(LIST_FRIEND_NAME))->setFontStyle(info->isOnline() ? LLFontGL::BOLD : LLFontGL::NORMAL);	
+	((LLScrollListText*)itemp->getColumn(LIST_FRIEND_NAME))->setFontStyle((isOnline || isOnlineSIP) ? LLFontGL::BOLD : LLFontGL::NORMAL);	
 	itemp->getColumn(LIST_VISIBLE_ONLINE)->setValue(info->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS));
 	itemp->getColumn(LIST_VISIBLE_MAP)->setValue(info->isRightGrantedTo(LLRelationship::GRANT_MAP_LOCATION));
 	itemp->getColumn(LIST_EDIT_MINE)->setValue(info->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS));
diff --git a/indra/newview/llimpanel.cpp b/indra/newview/llimpanel.cpp
index bded46b23b1..de9e92fcdd1 100644
--- a/indra/newview/llimpanel.cpp
+++ b/indra/newview/llimpanel.cpp
@@ -358,7 +358,7 @@ LLVoiceChannel::LLVoiceChannel(const LLUUID& session_id, const std::string& sess
 		llwarns << "Duplicate voice channels registered for session_id " << session_id << llendl;
 	}
 
-	LLVoiceClient::getInstance()->addStatusObserver(this);
+	LLVoiceClient::getInstance()->addObserver(this);
 }
 
 LLVoiceChannel::~LLVoiceChannel()
@@ -366,7 +366,7 @@ LLVoiceChannel::~LLVoiceChannel()
 	// Don't use LLVoiceClient::getInstance() here -- this can get called during atexit() time and that singleton MAY have already been destroyed.
 	if(gVoiceClient)
 	{
-		gVoiceClient->removeStatusObserver(this);
+		gVoiceClient->removeObserver(this);
 	}
 	
 	sVoiceChannelMap.erase(mSessionID);
@@ -985,7 +985,8 @@ void LLVoiceChannelP2P::activate()
 		// otherwise answering the call
 		else
 		{
-			LLVoiceClient::getInstance()->answerInvite(mSessionHandle, mOtherUserID);
+			LLVoiceClient::getInstance()->answerInvite(mSessionHandle);
+			
 			// using the session handle invalidates it.  Clear it out here so we can't reuse it by accident.
 			mSessionHandle.clear();
 		}
@@ -1002,7 +1003,7 @@ void LLVoiceChannelP2P::getChannelInfo()
 }
 
 // receiving session from other user who initiated call
-void LLVoiceChannelP2P::setSessionHandle(const std::string& handle)
+void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::string &inURI)
 { 
 	BOOL needs_activate = FALSE;
 	if (callStarted())
@@ -1025,8 +1026,17 @@ void LLVoiceChannelP2P::setSessionHandle(const std::string& handle)
 	}
 
 	mSessionHandle = handle;
+
 	// The URI of a p2p session should always be the other end's SIP URI.
-	setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID));
+	if(!inURI.empty())
+	{
+		setURI(inURI);
+	}
+	else
+	{
+		setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID));
+	}
+	
 	mReceivedCall = TRUE;
 
 	if (needs_activate)
@@ -1209,7 +1219,23 @@ LLFloaterIMPanel::~LLFloaterIMPanel()
 {
 	delete mSpeakers;
 	mSpeakers = NULL;
-
+	
+	// End the text IM session if necessary
+	if(gVoiceClient && mOtherParticipantUUID.notNull())
+	{
+		switch(mDialog)
+		{
+			case IM_NOTHING_SPECIAL:
+			case IM_SESSION_P2P_INVITE:
+				gVoiceClient->endUserIMSession(mOtherParticipantUUID);
+			break;
+			
+			default:
+				// Appease the compiler
+			break;
+		}
+	}
+	
 	//kicks you out of the voice channel if it is currently active
 
 	// HAVE to do this here -- if it happens in the LLVoiceChannel destructor it will call the wrong version (since the object's partially deconstructed at that point).
@@ -1872,33 +1898,45 @@ void deliver_message(const std::string& utf8_text,
 					 EInstantMessage dialog)
 {
 	std::string name;
+	bool sent = false;
 	gAgent.buildFullname(name);
 
 	const LLRelationship* info = NULL;
 	info = LLAvatarTracker::instance().getBuddyInfo(other_participant_id);
+	
 	U8 offline = (!info || info->isOnline()) ? IM_ONLINE : IM_OFFLINE;
-
-	// default to IM_SESSION_SEND unless it's nothing special - in
-	// which case it's probably an IM to everyone.
-	U8 new_dialog = dialog;
-
-	if ( dialog != IM_NOTHING_SPECIAL )
+	
+	if((offline == IM_OFFLINE) && (LLVoiceClient::getInstance()->isOnlineSIP(other_participant_id)))
 	{
-		new_dialog = IM_SESSION_SEND;
+		// User is online through the OOW connector, but not with a regular viewer.  Try to send the message via SLVoice.
+		sent = gVoiceClient->sendTextMessage(other_participant_id, utf8_text);
 	}
+	
+	if(!sent)
+	{
+		// Send message normally.
 
-	pack_instant_message(
-		gMessageSystem,
-		gAgent.getID(),
-		FALSE,
-		gAgent.getSessionID(),
-		other_participant_id,
-		name,
-		utf8_text,
-		offline,
-		(EInstantMessage)new_dialog,
-		im_session_id);
-	gAgent.sendReliableMessage();
+		// default to IM_SESSION_SEND unless it's nothing special - in
+		// which case it's probably an IM to everyone.
+		U8 new_dialog = dialog;
+
+		if ( dialog != IM_NOTHING_SPECIAL )
+		{
+			new_dialog = IM_SESSION_SEND;
+		}
+		pack_instant_message(
+			gMessageSystem,
+			gAgent.getID(),
+			FALSE,
+			gAgent.getSessionID(),
+			other_participant_id,
+			name.c_str(),
+			utf8_text.c_str(),
+			offline,
+			(EInstantMessage)new_dialog,
+			im_session_id);
+		gAgent.sendReliableMessage();
+	}
 
 	// If there is a mute list and this is not a group chat...
 	if ( LLMuteList::getInstance() )
diff --git a/indra/newview/llimpanel.h b/indra/newview/llimpanel.h
index 176d11c8f9c..e54cec56c7f 100644
--- a/indra/newview/llimpanel.h
+++ b/indra/newview/llimpanel.h
@@ -162,7 +162,7 @@ class LLVoiceChannelP2P : public LLVoiceChannelGroup
     /*virtual*/ void activate();
 	/*virtual*/ void getChannelInfo();
 
-	void setSessionHandle(const std::string& handle);
+	void setSessionHandle(const std::string& handle, const std::string &inURI);
 
 protected:
 	virtual void setState(EState state);
@@ -295,8 +295,6 @@ class LLFloaterIMPanel : public LLFloater
 
 	void sendTypingState(BOOL typing);
 	
-	static LLFloaterIMPanel* sInstance;
-
 private:
 	LLLineEditor* mInputEditor;
 	LLViewerTextEditor* mHistoryEditor;
diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp
index 2b9863819ae..a90ea392658 100644
--- a/indra/newview/llimview.cpp
+++ b/indra/newview/llimview.cpp
@@ -364,7 +364,8 @@ bool inviteUserResponse(const LLSD& notification, const LLSD& response)
 				session_id = gIMMgr->addP2PSession(
 					payload["session_name"].asString(),
 					payload["caller_id"].asUUID(),
-					payload["session_handle"].asString());
+					payload["session_handle"].asString(),
+					payload["session_uri"].asString());
 
 				LLFloaterIMPanel* im_floater =
 					gIMMgr->findFloaterBySession(
@@ -725,7 +726,8 @@ BOOL LLIMMgr::isIMSessionOpen(const LLUUID& uuid)
 
 LLUUID LLIMMgr::addP2PSession(const std::string& name,
 							const LLUUID& other_participant_id,
-							const std::string& voice_session_handle)
+							const std::string& voice_session_handle,
+							const std::string& caller_uri)
 {
 	LLUUID session_id = addSession(name, IM_NOTHING_SPECIAL, other_participant_id);
 
@@ -733,7 +735,7 @@ LLUUID LLIMMgr::addP2PSession(const std::string& name,
 	if(floater)
 	{
 		LLVoiceChannelP2P* voice_channelp = (LLVoiceChannelP2P*)floater->getVoiceChannel();
-		voice_channelp->setSessionHandle(voice_session_handle);
+		voice_channelp->setSessionHandle(voice_session_handle, caller_uri);		
 	}
 
 	return session_id;
@@ -856,7 +858,8 @@ void LLIMMgr::inviteToSession(
 	const std::string& caller_name,
 	EInstantMessage type,
 	EInvitationType inv_type,
-	const std::string& session_handle)
+	const std::string& session_handle,
+	const std::string& session_uri)
 {
 	//ignore invites from muted residents
 	if (LLMuteList::getInstance()->isMuted(caller_id))
@@ -898,6 +901,7 @@ void LLIMMgr::inviteToSession(
 	payload["type"] = type;
 	payload["inv_type"] = inv_type;
 	payload["session_handle"] = session_handle;
+	payload["session_uri"] = session_uri;
 	payload["notify_box_type"] = notify_box_type;
 	
 	LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(session_id);
diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h
index 88109a91546..a4e419694df 100644
--- a/indra/newview/llimview.h
+++ b/indra/newview/llimview.h
@@ -98,7 +98,8 @@ class LLIMMgr : public LLSingleton<LLIMMgr>
 	// Creates a P2P session with the requisite handle for responding to voice calls
 	LLUUID addP2PSession(const std::string& name,
 					  const LLUUID& other_participant_id,
-					  const std::string& voice_session_handle);
+					  const std::string& voice_session_handle,
+					  const std::string& caller_uri = LLStringUtil::null);
 
 	// This removes the panel referenced by the uuid, and then
 	// restores internal consistency. The internal pointer is not
@@ -112,7 +113,8 @@ class LLIMMgr : public LLSingleton<LLIMMgr>
 		const std::string& caller_name,
 		EInstantMessage type,
 		EInvitationType inv_type, 
-		const std::string& session_handle = LLStringUtil::null);
+		const std::string& session_handle = LLStringUtil::null,
+		const std::string& session_uri = LLStringUtil::null);
 
 	//Updates a given session's session IDs.  Does not open,
 	//create or do anything new.  If the old session doesn't
diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp
index 1a40da9c1fe..6b99cfbeaf7 100644
--- a/indra/newview/llviewercontrol.cpp
+++ b/indra/newview/llviewercontrol.cpp
@@ -560,7 +560,6 @@ void settings_setup_listeners()
 	gSavedSettings.getControl("PushToTalkButton")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));
 	gSavedSettings.getControl("PushToTalkToggle")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));
 	gSavedSettings.getControl("VoiceEarLocation")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));
-	gSavedSettings.getControl("VivoxDebugServerName")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));
 	gSavedSettings.getControl("VoiceInputAudioDevice")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));
 	gSavedSettings.getControl("VoiceOutputAudioDevice")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));
 	gSavedSettings.getControl("AudioLevelMic")->getSignal()->connect(boost::bind(&handleVoiceClientPrefsChanged, _1));
diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp
index 1d62ec4b9aa..3bcc0af7d5f 100644
--- a/indra/newview/llvoiceclient.cpp
+++ b/indra/newview/llvoiceclient.cpp
@@ -63,15 +63,20 @@
 #include "llviewerwindow.h"
 #include "llviewercamera.h"
 
+#include "llfloaterfriends.h"  //VIVOX, inorder to refresh communicate panel
+#include "llfloaterchat.h"		// for LLFloaterChat::addChat()
+
 // 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"
+// for MD5 hash
+#include "llmd5.h"
+
+#define USE_SESSION_GROUPS 0
+
 static bool sConnectingToAgni = false;
 F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f;
 
@@ -91,6 +96,44 @@ const F32 UPDATE_THROTTLE_SECONDS = 0.1f;
 const F32 LOGIN_RETRY_SECONDS = 10.0f;
 const int MAX_LOGIN_RETRIES = 12;
 
+static void setUUIDFromStringHash(LLUUID &uuid, const std::string &str)
+{
+	LLMD5 md5_uuid;
+	md5_uuid.update((const unsigned char*)str.data(), str.size());
+	md5_uuid.finalize();
+	md5_uuid.raw_digest(uuid.mData);
+}
+
+static int scale_mic_volume(float volume)
+{
+	// incoming volume has the range [0.0 ... 2.0], with 1.0 as the default.
+	// Map it as follows: 0.0 -> 40, 1.0 -> 44, 2.0 -> 75
+
+	volume -= 1.0f;		// offset volume to the range [-1.0 ... 1.0], with 0 at the default.
+	int scaled_volume = 44;	// offset scaled_volume by its default level
+	if(volume < 0.0f)
+		scaled_volume += ((int)(volume * 4.0f));	// (44 - 40)
+	else
+		scaled_volume += ((int)(volume * 31.0f));	// (75 - 44)
+	
+	return scaled_volume;
+}
+
+static int scale_speaker_volume(float volume)
+{
+	// incoming volume has the range [0.0 ... 1.0], with 0.5 as the default.
+	// Map it as follows: 0.0 -> 0, 0.5 -> 62, 1.0 -> 75
+	
+	volume -= 0.5f;		// offset volume to the range [-0.5 ... 0.5], with 0 at the default.
+	int scaled_volume = 62;	// offset scaled_volume by its default level
+	if(volume < 0.0f)
+		scaled_volume += ((int)(volume * 124.0f));	// (62 - 0) * 2
+	else
+		scaled_volume += ((int)(volume * 26.0f));	// (75 - 62) * 2
+	
+	return scaled_volume;
+}
+
 class LLViewerVoiceAccountProvisionResponder :
 	public LLHTTPClient::Responder
 {
@@ -104,12 +147,13 @@ class LLViewerVoiceAccountProvisionResponder :
 	{
 		if ( mRetries > 0 )
 		{
+			LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, retrying.  status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL;
 			if ( gVoiceClient ) gVoiceClient->requestVoiceAccountProvision(
 				mRetries - 1);
 		}
 		else
 		{
-			//TODO: throw an error message?
+			LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, too many retries (giving up).  status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL;
 			if ( gVoiceClient ) gVoiceClient->giveUp();
 		}
 	}
@@ -118,9 +162,23 @@ class LLViewerVoiceAccountProvisionResponder :
 	{
 		if ( gVoiceClient )
 		{
+			std::string voice_sip_uri_hostname;
+			std::string voice_account_server_uri;
+			
+			LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response:" << ll_pretty_print_sd(content) << LL_ENDL;
+			
+			if(content.has("voice_sip_uri_hostname"))
+				voice_sip_uri_hostname = content["voice_sip_uri_hostname"].asString();
+			
+			// this key is actually misnamed -- it will be an entire URI, not just a hostname.
+			if(content.has("voice_account_server_name"))
+				voice_account_server_uri = content["voice_account_server_name"].asString();
+			
 			gVoiceClient->login(
 				content["username"].asString(),
-				content["password"].asString());
+				content["password"].asString(),
+				voice_sip_uri_hostname,
+				voice_account_server_uri);
 		}
 	}
 
@@ -167,21 +225,27 @@ class LLVivoxProtocolParser : public LLIOPipe
 	int				ignoreDepth;
 
 	// Members for processing responses. The values are transient and only valid within a call to processResponse().
+	bool			squelchDebugOutput;
 	int				returnCode;
 	int				statusCode;
 	std::string		statusString;
-	std::string		uuidString;
+	std::string		requestId;
 	std::string		actionString;
 	std::string		connectorHandle;
+	std::string		versionID;
 	std::string		accountHandle;
 	std::string		sessionHandle;
-	std::string		eventSessionHandle;
+	std::string		sessionGroupHandle;
+	std::string		alias;
+	std::string		applicationString;
 
 	// 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;
+	bool			incoming;
+	bool			enabled;
 	std::string		nameString;
 	std::string		audioMediaString;
 	std::string		displayNameString;
@@ -191,6 +255,21 @@ class LLVivoxProtocolParser : public LLIOPipe
 	bool			isSpeaking;
 	int				volume;
 	F32				energy;
+	std::string		messageHeader;
+	std::string		messageBody;
+	std::string		notificationType;
+	bool			hasText;
+	bool			hasAudio;
+	bool			hasVideo;
+	bool			terminated;
+	std::string		blockMask;
+	std::string		presenceOnly;
+	std::string		autoAcceptMask;
+	std::string		autoAddAsBuddy;
+	int				numberOfAliases;
+	std::string		subscriptionHandle;
+	std::string		subscriptionType;
+		
 
 	// Members for processing text between tags
 	std::string		textBuffer;
@@ -223,8 +302,6 @@ void LLVivoxProtocolParser::reset()
 	responseDepth = 0;
 	ignoringTags = false;
 	accumulateText = false;
-	textBuffer.clear();
-
 	energy = 0.f;
 	ignoreDepth = 0;
 	isChannel = false;
@@ -233,10 +310,15 @@ void LLVivoxProtocolParser::reset()
 	isModeratorMuted = false;
 	isSpeaking = false;
 	participantType = 0;
-	returnCode = 0;
+	squelchDebugOutput = false;
+	returnCode = -1;
 	state = 0;
 	statusCode = 0;
 	volume = 0;
+	textBuffer.clear();
+	alias.clear();
+	numberOfAliases = 0;
+	applicationString.clear();
 }
 
 //virtual 
@@ -263,33 +345,11 @@ LLIOPipe::EStatus LLVivoxProtocolParser::process_impl(
 		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();
@@ -300,13 +360,19 @@ LLIOPipe::EStatus LLVivoxProtocolParser::process_impl(
 		XML_SetUserData(parser, this);	
 		XML_Parse(parser, mInput.data() + start, delim - start, false);
 		
+		// If this message isn't set to be squelched, output the raw XML received.
+		if(!squelchDebugOutput)
+		{
+			LL_DEBUGS("Voice") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL;
+		}
+		
 		start = delim + 3;
 	}
 	
 	if(start != 0)
 		mInput = mInput.substr(start);
 
-	LL_DEBUGS("Voice") << "at end, mInput is: " << mInput << LL_ENDL;
+	LL_DEBUGS("VivoxProtocolParser") << "at end, mInput is: " << mInput << LL_ENDL;
 	
 	if(!gVoiceClient->mConnected)
 	{
@@ -361,9 +427,9 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr)
 	
 	if (responseDepth == 0)
 	{	
-		isEvent = strcmp("Event", tag) == 0;
+		isEvent = !stricmp("Event", tag);
 		
-		if (strcmp("Response", tag) == 0 || isEvent)
+		if (!stricmp("Response", tag) || isEvent)
 		{
 			// Grab the attributes
 			while (*attr)
@@ -371,49 +437,62 @@ void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr)
 				const char	*key = *attr++;
 				const char	*value = *attr++;
 				
-				if (strcmp("requestId", key) == 0)
+				if (!stricmp("requestId", key))
 				{
-					uuidString = value;
+					requestId = value;
 				}
-				else if (strcmp("action", key) == 0)
+				else if (!stricmp("action", key))
 				{
 					actionString = value;
 				}
-				else if (strcmp("type", key) == 0)
+				else if (!stricmp("type", key))
 				{
 					eventTypeString = value;
 				}
 			}
 		}
-		LL_DEBUGS("Voice") << tag << " (" << responseDepth << ")"  << LL_ENDL;
+		LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")"  << LL_ENDL;
 	}
 	else
 	{
 		if (ignoringTags)
 		{
-			LL_DEBUGS("Voice") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
+			LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
 		}
 		else
 		{
-			LL_DEBUGS("Voice") << tag << " (" << responseDepth << ")"  << LL_ENDL;
+			LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")"  << LL_ENDL;
 	
 			// Ignore the InputXml stuff so we don't get confused
-			if (strcmp("InputXml", tag) == 0)
+			if (!stricmp("InputXml", tag))
 			{
 				ignoringTags = true;
 				ignoreDepth = responseDepth;
 				accumulateText = false;
 
-				LL_DEBUGS("Voice") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL;
+				LL_DEBUGS("VivoxProtocolParser") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL;
 			}
-			else if (strcmp("CaptureDevices", tag) == 0)
+			else if (!stricmp("CaptureDevices", tag))
 			{
 				gVoiceClient->clearCaptureDevices();
 			}
-			else if (strcmp("RenderDevices", tag) == 0)
+			else if (!stricmp("RenderDevices", tag))
 			{
 				gVoiceClient->clearRenderDevices();
 			}
+			else if (!stricmp("Buddies", tag))
+			{
+				gVoiceClient->deleteAllBuddies();
+			}
+			else if (!stricmp("BlockRules", tag))
+			{
+				gVoiceClient->deleteAllBlockRules();
+			}
+			else if (!stricmp("AutoAcceptRules", tag))
+			{
+				gVoiceClient->deleteAllAutoAcceptRules();
+			}
+			
 		}
 	}
 	responseDepth++;
@@ -432,90 +511,138 @@ void LLVivoxProtocolParser::EndTag(const char *tag)
 	{
 		if (ignoreDepth == responseDepth)
 		{
-			LL_DEBUGS("Voice") << "end of ignore" << LL_ENDL;
+			LL_DEBUGS("VivoxProtocolParser") << "end of ignore" << LL_ENDL;
 			ignoringTags = false;
 		}
 		else
 		{
-			LL_DEBUGS("Voice") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
+			LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
 		}
 	}
 	
 	if (!ignoringTags)
 	{
-		LL_DEBUGS("Voice") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
+		LL_DEBUGS("VivoxProtocolParser") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL;
 
 		// Closing a tag. Finalize the text we've accumulated and reset
-		if (strcmp("ReturnCode", tag) == 0)
+		if (!stricmp("ReturnCode", tag))
 			returnCode = strtol(string.c_str(), NULL, 10);
-		else if (strcmp("StatusCode", tag) == 0)
+		else if (!stricmp("SessionHandle", tag))
+			sessionHandle = string;
+		else if (!stricmp("SessionGroupHandle", tag))
+			sessionGroupHandle = string;
+		else if (!stricmp("StatusCode", tag))
 			statusCode = strtol(string.c_str(), NULL, 10);
-		else if (strcmp("ConnectorHandle", tag) == 0)
+		else if (!stricmp("StatusString", tag))
+			statusString = string;
+		else if (!stricmp("ParticipantURI", tag))
+			uriString = string;
+		else if (!stricmp("Volume", tag))
+			volume = strtol(string.c_str(), NULL, 10);
+		else if (!stricmp("Energy", tag))
+			energy = (F32)strtod(string.c_str(), NULL);
+		else if (!stricmp("IsModeratorMuted", tag))
+			isModeratorMuted = !stricmp(string.c_str(), "true");
+		else if (!stricmp("IsSpeaking", tag))
+			isSpeaking = !stricmp(string.c_str(), "true");
+		else if (!stricmp("Alias", tag))
+			alias = string;
+		else if (!stricmp("NumberOfAliases", tag))
+			numberOfAliases = strtol(string.c_str(), NULL, 10);
+		else if (!stricmp("Application", tag))
+			applicationString = string;
+		else if (!stricmp("ConnectorHandle", tag))
 			connectorHandle = string;
-		else if (strcmp("AccountHandle", tag) == 0)
+		else if (!stricmp("VersionID", tag))
+			versionID = string;
+		else if (!stricmp("AccountHandle", tag))
 			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)
+		else if (!stricmp("State", tag))
 			state = strtol(string.c_str(), NULL, 10);
-		else if (strcmp("URI", tag) == 0)
+		else if (!stricmp("URI", tag))
 			uriString = string;
-		else if (strcmp("IsChannel", tag) == 0)
-			isChannel = string == "true" ? true : false;
-		else if (strcmp("Name", tag) == 0)
+		else if (!stricmp("IsChannel", tag))
+			isChannel = !stricmp(string.c_str(), "true");
+		else if (!stricmp("Incoming", tag))
+			incoming = !stricmp(string.c_str(), "true");
+		else if (!stricmp("Enabled", tag))
+			enabled = !stricmp(string.c_str(), "true");
+		else if (!stricmp("Name", tag))
 			nameString = string;
-		else if (strcmp("AudioMedia", tag) == 0)
+		else if (!stricmp("AudioMedia", tag))
 			audioMediaString = string;
-		else if (strcmp("ChannelName", tag) == 0)
+		else if (!stricmp("ChannelName", tag))
 			nameString = string;
-		else if (strcmp("ParticipantURI", tag) == 0)
-			uriString = string;
-		else if (strcmp("DisplayName", tag) == 0)
+		else if (!stricmp("DisplayName", tag))
 			displayNameString = string;
-		else if (strcmp("AccountName", tag) == 0)
+		else if (!stricmp("AccountName", tag))
 			nameString = string;
-		else if (strcmp("ParticipantTyppe", tag) == 0)
+		else if (!stricmp("ParticipantType", tag))
 			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)
+		else if (!stricmp("IsLocallyMuted", tag))
+			isLocallyMuted = !stricmp(string.c_str(), "true");
+		else if (!stricmp("MicEnergy", tag))
 			energy = (F32)strtod(string.c_str(), NULL);
-		else if (strcmp("ChannelName", tag) == 0)
+		else if (!stricmp("ChannelName", tag))
 			nameString = string;
-		else if (strcmp("ChannelURI", tag) == 0)
+		else if (!stricmp("ChannelURI", tag))
 			uriString = string;
-		else if (strcmp("ChannelListResult", tag) == 0)
-		{
-			gVoiceClient->addChannelMapEntry(nameString, uriString);
-		}
-		else if (strcmp("Device", tag) == 0)
+		else if (!stricmp("BuddyURI", tag))
+			uriString = string;
+		else if (!stricmp("Presence", tag))
+			statusString = string;
+		else if (!stricmp("Device", tag))
 		{
 			// This closing tag shouldn't clear the accumulated text.
 			clearbuffer = false;
 		}
-		else if (strcmp("CaptureDevice", tag) == 0)
+		else if (!stricmp("CaptureDevice", tag))
 		{
 			gVoiceClient->addCaptureDevice(textBuffer);
 		}
-		else if (strcmp("RenderDevice", tag) == 0)
+		else if (!stricmp("RenderDevice", tag))
 		{
 			gVoiceClient->addRenderDevice(textBuffer);
 		}
+		else if (!stricmp("Buddy", tag))
+		{
+			gVoiceClient->processBuddyListEntry(uriString, displayNameString);
+		}
+		else if (!stricmp("BlockRule", tag))
+		{
+			gVoiceClient->addBlockRule(blockMask, presenceOnly);
+		}
+		else if (!stricmp("BlockMask", tag))
+			blockMask = string;
+		else if (!stricmp("PresenceOnly", tag))
+			presenceOnly = string;
+		else if (!stricmp("AutoAcceptRule", tag))
+		{
+			gVoiceClient->addAutoAcceptRule(autoAcceptMask, autoAddAsBuddy);
+		}
+		else if (!stricmp("AutoAcceptMask", tag))
+			autoAcceptMask = string;
+		else if (!stricmp("AutoAddAsBuddy", tag))
+			autoAddAsBuddy = string;
+		else if (!stricmp("MessageHeader", tag))
+			messageHeader = string;
+		else if (!stricmp("MessageBody", tag))
+			messageBody = string;
+		else if (!stricmp("NotificationType", tag))
+			notificationType = string;
+		else if (!stricmp("HasText", tag))
+			hasText = !stricmp(string.c_str(), "true");
+		else if (!stricmp("HasAudio", tag))
+			hasAudio = !stricmp(string.c_str(), "true");
+		else if (!stricmp("HasVideo", tag))
+			hasVideo = !stricmp(string.c_str(), "true");
+		else if (!stricmp("Terminated", tag))
+			terminated = !stricmp(string.c_str(), "true");
+		else if (!stricmp("SubscriptionHandle", tag))
+			subscriptionHandle = string;
+		else if (!stricmp("SubscriptionType", tag))
+			subscriptionType = string;
+		
 
 		if(clearbuffer)
 		{
@@ -550,144 +677,296 @@ void LLVivoxProtocolParser::CharData(const char *buffer, int length)
 
 void LLVivoxProtocolParser::processResponse(std::string tag)
 {
-	LL_DEBUGS("Voice") << tag << LL_ENDL;
+	LL_DEBUGS("VivoxProtocolParser") << tag << LL_ENDL;
 
+	// SLIM SDK: the SDK now returns a statusCode of "200" (OK) for success.  This is a change vs. previous SDKs.
+	// According to Mike S., "The actual API convention is that responses with return codes of 0 are successful, regardless of the status code returned",
+	// so I believe this will give correct behavior.
+	
+	if(returnCode == 0)
+		statusCode = 0;
+		
 	if (isEvent)
 	{
-		if (eventTypeString == "LoginStateChangeEvent")
+		const char *eventTypeCstr = eventTypeString.c_str();
+		if (!stricmp(eventTypeCstr, "AccountLoginStateChangeEvent"))
 		{
-			gVoiceClient->loginStateChangeEvent(accountHandle, statusCode, statusString, state);
+			gVoiceClient->accountLoginStateChangeEvent(accountHandle, statusCode, statusString, state);
 		}
-		else if (eventTypeString == "SessionNewEvent")
+		else if (!stricmp(eventTypeCstr, "SessionAddedEvent"))
 		{
-			gVoiceClient->sessionNewEvent(accountHandle, eventSessionHandle, state, nameString, uriString);
+			/*
+			<Event type="SessionAddedEvent">
+				<SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
+				<SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle>
+				<Uri>sip:confctl-1408789@bhr.vivox.com</Uri>
+				<IsChannel>true</IsChannel>
+				<Incoming>false</Incoming>
+				<ChannelName />
+			</Event>
+			*/
+			gVoiceClient->sessionAddedEvent(uriString, alias, sessionHandle, sessionGroupHandle, isChannel, incoming, nameString, applicationString);
 		}
-		else if (eventTypeString == "SessionStateChangeEvent")
+		else if (!stricmp(eventTypeCstr, "SessionRemovedEvent"))
 		{
-			gVoiceClient->sessionStateChangeEvent(uriString, statusCode, statusString, eventSessionHandle, state, isChannel, nameString);
+			gVoiceClient->sessionRemovedEvent(sessionHandle, sessionGroupHandle);
 		}
-		else if (eventTypeString == "ParticipantStateChangeEvent")
+		else if (!stricmp(eventTypeCstr, "SessionGroupAddedEvent"))
 		{
-			gVoiceClient->participantStateChangeEvent(uriString, statusCode, statusString, state,  nameString, displayNameString, participantType);
-			
+			gVoiceClient->sessionGroupAddedEvent(sessionGroupHandle);
+		}
+		else if (!stricmp(eventTypeCstr, "MediaStreamUpdatedEvent"))
+		{
+			/*
+			<Event type="MediaStreamUpdatedEvent">
+				<SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
+				<SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle>
+				<StatusCode>200</StatusCode>
+				<StatusString>OK</StatusString>
+				<State>2</State>
+				<Incoming>false</Incoming>
+			</Event>
+			*/
+			gVoiceClient->mediaStreamUpdatedEvent(sessionHandle, sessionGroupHandle, statusCode, statusString, state, incoming);
+		}		
+		else if (!stricmp(eventTypeCstr, "TextStreamUpdatedEvent"))
+		{
+			/*
+			<Event type="TextStreamUpdatedEvent">
+				<SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg1</SessionGroupHandle>
+				<SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==1</SessionHandle>
+				<Enabled>true</Enabled>
+				<State>1</State>
+				<Incoming>true</Incoming>
+			</Event>
+			*/
+			gVoiceClient->textStreamUpdatedEvent(sessionHandle, sessionGroupHandle, enabled, state, incoming);
+		}
+		else if (!stricmp(eventTypeCstr, "ParticipantAddedEvent"))
+		{
+			/* 
+			<Event type="ParticipantAddedEvent">
+				<SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4</SessionGroupHandle>
+				<SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==4</SessionHandle>
+				<ParticipantUri>sip:xI5auBZ60SJWIk606-1JGRQ==@bhr.vivox.com</ParticipantUri>
+				<AccountName>xI5auBZ60SJWIk606-1JGRQ==</AccountName>
+				<DisplayName />
+				<ParticipantType>0</ParticipantType>
+			</Event>
+			*/
+			gVoiceClient->participantAddedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString, displayNameString, participantType);
+		}
+		else if (!stricmp(eventTypeCstr, "ParticipantRemovedEvent"))
+		{
+			/*
+			<Event type="ParticipantRemovedEvent">
+				<SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4</SessionGroupHandle>
+				<SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==4</SessionHandle>
+				<ParticipantUri>sip:xtx7YNV-3SGiG7rA1fo5Ndw==@bhr.vivox.com</ParticipantUri>
+				<AccountName>xtx7YNV-3SGiG7rA1fo5Ndw==</AccountName>
+			</Event>
+			*/
+			gVoiceClient->participantRemovedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString);
 		}
-		else if (eventTypeString == "ParticipantPropertiesEvent")
+		else if (!stricmp(eventTypeCstr, "ParticipantUpdatedEvent"))
 		{
-			gVoiceClient->participantPropertiesEvent(uriString, statusCode, statusString, isLocallyMuted, isModeratorMuted, isSpeaking, volume, energy);
+			/*
+			<Event type="ParticipantUpdatedEvent">
+				<SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
+				<SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle>
+				<ParticipantUri>sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com</ParticipantUri>
+				<IsModeratorMuted>false</IsModeratorMuted>
+				<IsSpeaking>true</IsSpeaking>
+				<Volume>44</Volume>
+				<Energy>0.0879437</Energy>
+			</Event>
+			*/
+			
+			// These happen so often that logging them is pretty useless.
+			squelchDebugOutput = true;
+			
+			gVoiceClient->participantUpdatedEvent(sessionHandle, sessionGroupHandle, uriString, alias, isModeratorMuted, isSpeaking, volume, energy);
 		}
-		else if (eventTypeString == "AuxAudioPropertiesEvent")
+		else if (!stricmp(eventTypeCstr, "AuxAudioPropertiesEvent"))
 		{
 			gVoiceClient->auxAudioPropertiesEvent(energy);
 		}
+		else if (!stricmp(eventTypeCstr, "BuddyPresenceEvent"))
+		{
+			gVoiceClient->buddyPresenceEvent(uriString, alias, statusString, applicationString);
+		}
+		else if (!stricmp(eventTypeCstr, "BuddyAndGroupListChangedEvent"))
+		{
+			// The buddy list was updated during parsing.
+			// Need to recheck against the friends list.
+			gVoiceClient->buddyListChanged();
+		}
+		else if (!stricmp(eventTypeCstr, "BuddyChangedEvent"))
+		{
+			/*
+			<Event type="BuddyChangedEvent">
+				<AccountHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==</AccountHandle>
+				<BuddyURI>sip:x9fFHFZjOTN6OESF1DUPrZQ==@bhr.vivox.com</BuddyURI>
+				<DisplayName>Monroe Tester</DisplayName>
+				<BuddyData />
+				<GroupID>0</GroupID>
+				<ChangeType>Set</ChangeType>
+			</Event>
+			*/		
+			// TODO: Question: Do we need to process this at all?
+		}
+		else if (!stricmp(eventTypeCstr, "MessageEvent"))  
+		{
+			gVoiceClient->messageEvent(sessionHandle, uriString, alias, messageHeader, messageBody, applicationString);
+		}
+		else if (!stricmp(eventTypeCstr, "SessionNotificationEvent"))  
+		{
+			gVoiceClient->sessionNotificationEvent(sessionHandle, uriString, notificationType);
+		}
+		else if (!stricmp(eventTypeCstr, "SubscriptionEvent"))  
+		{
+			gVoiceClient->subscriptionEvent(uriString, subscriptionHandle, alias, displayNameString, applicationString, subscriptionType);
+		}
+		else if (!stricmp(eventTypeCstr, "SessionUpdatedEvent"))  
+		{
+			/*
+			<Event type="SessionUpdatedEvent">
+				<SessionGroupHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0</SessionGroupHandle>
+				<SessionHandle>c1_m1000xFnPP04IpREWNkuw1cOXlhw==0</SessionHandle>
+				<Uri>sip:confctl-9@bhd.vivox.com</Uri>
+				<IsMuted>0</IsMuted>
+				<Volume>50</Volume>
+				<TransmitEnabled>1</TransmitEnabled>
+				<IsFocused>0</IsFocused>
+				<SpeakerPosition><Position><X>0</X><Y>0</Y><Z>0</Z></Position></SpeakerPosition>
+				<SessionFontID>0</SessionFontID>
+			</Event>
+			*/
+			// We don't need to process this, but we also shouldn't warn on it, since that confuses people.
+		}
+		else
+		{
+			LL_WARNS("VivoxProtocolParser") << "Unknown event type " << eventTypeString << LL_ENDL;
+		}
 	}
 	else
 	{
-		if (actionString == "Connector.Create.1")
+		const char *actionCstr = actionString.c_str();
+		if (!stricmp(actionCstr, "Connector.Create.1"))
 		{
-			gVoiceClient->connectorCreateResponse(statusCode, statusString, connectorHandle);
+			gVoiceClient->connectorCreateResponse(statusCode, statusString, connectorHandle, versionID);
 		}
-		else if (actionString == "Account.Login.1")
+		else if (!stricmp(actionCstr, "Account.Login.1"))
 		{
-			gVoiceClient->loginResponse(statusCode, statusString, accountHandle);
+			gVoiceClient->loginResponse(statusCode, statusString, accountHandle, numberOfAliases);
 		}
-		else if (actionString == "Session.Create.1")
+		else if (!stricmp(actionCstr, "Session.Create.1"))
 		{
-			gVoiceClient->sessionCreateResponse(statusCode, statusString, sessionHandle);			
+			gVoiceClient->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle);			
 		}
-		else if (actionString == "Session.Connect.1")
+		else if (!stricmp(actionCstr, "SessionGroup.AddSession.1"))
 		{
-			gVoiceClient->sessionConnectResponse(statusCode, statusString);			
+			gVoiceClient->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle);			
 		}
-		else if (actionString == "Session.Terminate.1")
+		else if (!stricmp(actionCstr, "Session.Connect.1"))
 		{
-			gVoiceClient->sessionTerminateResponse(statusCode, statusString);			
+			gVoiceClient->sessionConnectResponse(requestId, statusCode, statusString);			
 		}
-		else if (actionString == "Account.Logout.1")
+		else if (!stricmp(actionCstr, "Account.Logout.1"))
 		{
 			gVoiceClient->logoutResponse(statusCode, statusString);			
 		}
-		else if (actionString == "Connector.InitiateShutdown.1")
+		else if (!stricmp(actionCstr, "Connector.InitiateShutdown.1"))
 		{
 			gVoiceClient->connectorShutdownResponse(statusCode, statusString);			
 		}
-		else if (actionString == "Account.ChannelGetList.1")
+		else if (!stricmp(actionCstr, "Account.ListBlockRules.1"))
 		{
-			gVoiceClient->channelGetListResponse(statusCode, statusString);
+			gVoiceClient->accountListBlockRulesResponse(statusCode, statusString);						
+		}
+		else if (!stricmp(actionCstr, "Account.ListAutoAcceptRules.1"))
+		{
+			gVoiceClient->accountListAutoAcceptRulesResponse(statusCode, statusString);						
+		}
+		else if (!stricmp(actionCstr, "Session.Set3DPosition.1"))
+		{
+			// We don't need to process these, but they're so spammy we don't want to log them.
+			squelchDebugOutput = true;
 		}
 /*
-		else if (actionString == "Connector.AccountCreate.1")
+		else if (!stricmp(actionCstr, "Account.ChannelGetList.1"))
 		{
-			
+			gVoiceClient->channelGetListResponse(statusCode, statusString);
 		}
-		else if (actionString == "Connector.MuteLocalMic.1")
+		else if (!stricmp(actionCstr, "Connector.AccountCreate.1"))
 		{
 			
 		}
-		else if (actionString == "Connector.MuteLocalSpeaker.1")
+		else if (!stricmp(actionCstr, "Connector.MuteLocalMic.1"))
 		{
 			
 		}
-		else if (actionString == "Connector.SetLocalMicVolume.1")
+		else if (!stricmp(actionCstr, "Connector.MuteLocalSpeaker.1"))
 		{
 			
 		}
-		else if (actionString == "Connector.SetLocalSpeakerVolume.1")
+		else if (!stricmp(actionCstr, "Connector.SetLocalMicVolume.1"))
 		{
 			
 		}
-		else if (actionString == "Session.ListenerSetPosition.1")
+		else if (!stricmp(actionCstr, "Connector.SetLocalSpeakerVolume.1"))
 		{
 			
 		}
-		else if (actionString == "Session.SpeakerSetPosition.1")
+		else if (!stricmp(actionCstr, "Session.ListenerSetPosition.1"))
 		{
 			
 		}
-		else if (actionString == "Session.Set3DPosition.1")
+		else if (!stricmp(actionCstr, "Session.SpeakerSetPosition.1"))
 		{
 			
 		}
-		else if (actionString == "Session.AudioSourceSetPosition.1")
+		else if (!stricmp(actionCstr, "Session.AudioSourceSetPosition.1"))
 		{
 			
 		}
-		else if (actionString == "Session.GetChannelParticipants.1")
+		else if (!stricmp(actionCstr, "Session.GetChannelParticipants.1"))
 		{
 			
 		}
-		else if (actionString == "Account.ChannelCreate.1")
+		else if (!stricmp(actionCstr, "Account.ChannelCreate.1"))
 		{
 			
 		}
-		else if (actionString == "Account.ChannelUpdate.1")
+		else if (!stricmp(actionCstr, "Account.ChannelUpdate.1"))
 		{
 			
 		}
-		else if (actionString == "Account.ChannelDelete.1")
+		else if (!stricmp(actionCstr, "Account.ChannelDelete.1"))
 		{
 			
 		}
-		else if (actionString == "Account.ChannelCreateAndInvite.1")
+		else if (!stricmp(actionCstr, "Account.ChannelCreateAndInvite.1"))
 		{
 			
 		}
-		else if (actionString == "Account.ChannelFolderCreate.1")
+		else if (!stricmp(actionCstr, "Account.ChannelFolderCreate.1"))
 		{
 			
 		}
-		else if (actionString == "Account.ChannelFolderUpdate.1")
+		else if (!stricmp(actionCstr, "Account.ChannelFolderUpdate.1"))
 		{
 			
 		}
-		else if (actionString == "Account.ChannelFolderDelete.1")
+		else if (!stricmp(actionCstr, "Account.ChannelFolderDelete.1"))
 		{
 			
 		}
-		else if (actionString == "Account.ChannelAddModerator.1")
+		else if (!stricmp(actionCstr, "Account.ChannelAddModerator.1"))
 		{
 			
 		}
-		else if (actionString == "Account.ChannelDeleteModerator.1")
+		else if (!stricmp(actionCstr, "Account.ChannelDeleteModerator.1"))
 		{
 			
 		}
@@ -701,9 +980,18 @@ class LLVoiceClientMuteListObserver : public LLMuteListObserver
 {
 	/* virtual */ void onChange()  { gVoiceClient->muteListChanged();}
 };
+
+class LLVoiceClientFriendsObserver : public LLFriendObserver
+{
+public:
+	/* virtual */ void changed(U32 mask) { gVoiceClient->updateFriends(mask);}
+};
+
 static LLVoiceClientMuteListObserver mutelist_listener;
 static bool sMuteListListener_listening = false;
 
+static LLVoiceClientFriendsObserver *friendslist_listener = NULL;
+
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
 class LLVoiceClientCapResponder : public LLHTTPClient::Responder
@@ -727,11 +1015,8 @@ void LLVoiceClientCapResponder::error(U32 status, const std::string& reason)
 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;
-	}
+	
+	LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest response:" << ll_pretty_print_sd(content) << LL_ENDL;
 
 	if ( content.has("voice_credentials") )
 	{
@@ -817,26 +1102,26 @@ LLVoiceClient::LLVoiceClient()
 	mUserPTTState = false;
 	mMuteMic = false;
 	mSessionTerminateRequested = false;
+	mRelogRequested = 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;
 
+	mAudioSession = NULL;
+	mAudioSessionChanged = false;
+
 	// Initial dirty state
 	mSpatialCoordsDirty = false;
 	mPTTDirty = true;
-	mVolumeDirty = true;
+	mFriendsListDirty = true;
 	mSpeakerVolumeDirty = true;
 	mMicVolumeDirty = true;
+	mBuddyListMapPopulated = false;
+	mBlockRulesListReceived = false;
+	mAutoAcceptRulesListReceived = false;
 	mCaptureDeviceDirty = false;
 	mRenderDeviceDirty = false;
 
@@ -857,14 +1142,12 @@ LLVoiceClient::LLVoiceClient()
 	//  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 || LL_SOLARIS
-		// MBW -- XXX -- THIS DOES NOT BELONG HERE
+		// HACK: 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?
@@ -900,8 +1183,10 @@ void LLVoiceClient::terminate()
 {
 	if(gVoiceClient)
 	{
-		gVoiceClient->sessionTerminateSendMessage();
+//		gVoiceClient->leaveAudioSession();
 		gVoiceClient->logout();
+		// As of SDK version 4885, this should no longer be necessary.  It will linger after the socket close if it needs to.
+		// ms_sleep(2000);
 		gVoiceClient->connectorShutdown();
 		gVoiceClient->closeSocket();		// Need to do this now -- bad things happen if the destructor does it later.
 		
@@ -926,8 +1211,6 @@ void LLVoiceClient::updateSettings()
 	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);
@@ -950,9 +1233,10 @@ bool LLVoiceClient::writeString(const std::string &str)
 		apr_size_t size = (apr_size_t)str.size();
 		apr_size_t written = size;
 	
-		LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL;
+		//MARK: Turn this on to log outgoing XML
+//		LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL;
 
-		// MBW -- XXX -- check return code - sockets will fail (broken, etc.)
+		// check return code - sockets will fail (broken, etc.)
 		err = apr_socket_send(
 				mSocket->getSocket(),
 				(const char*)str.data(),
@@ -963,7 +1247,7 @@ bool LLVoiceClient::writeString(const std::string &str)
 			// Success.
 			result = true;
 		}
-		// MBW -- XXX -- handle partial writes (written is number of bytes written)
+		// TODO: 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))
 //		{
@@ -987,32 +1271,32 @@ bool LLVoiceClient::writeString(const std::string &str)
 void LLVoiceClient::connectorCreate()
 {
 	std::ostringstream stream;
-	std::string logpath;
+	std::string logpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "");
 	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>"
+		<< "<AccountManagementServer>" << mVoiceAccountServerURI << "</AccountManagementServer>"
+		<< "<Mode>Normal</Mode>"
 		<< "<Logging>"
-			<< "<Enabled>false</Enabled>"
 			<< "<Folder>" << logpath << "</Folder>"
 			<< "<FileNamePrefix>Connector</FileNamePrefix>"
 			<< "<FileNameSuffix>.log</FileNameSuffix>"
 			<< "<LogLevel>" << loglevel << "</LogLevel>"
 		<< "</Logging>"
+		<< "<Application>SecondLifeViewer.1</Application>"
 	<< "</Request>\n\n\n";
 	
 	writeString(stream.str());
@@ -1050,20 +1334,6 @@ void LLVoiceClient::userAuthorized(const std::string& firstName, const std::stri
 
 	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);
 }
 
@@ -1085,24 +1355,69 @@ void LLVoiceClient::requestVoiceAccountProvision(S32 retries)
 }
 
 void LLVoiceClient::login(
-	const std::string& accountName,
-	const std::string &password)
+	const std::string& account_name,
+	const std::string& password,
+	const std::string& voice_sip_uri_hostname,
+	const std::string& voice_account_server_uri)
 {
-	if((getState() >= stateLoggingIn) && (getState() < stateLoggedOut))
+	mVoiceSIPURIHostName = voice_sip_uri_hostname;
+	mVoiceAccountServerURI = voice_account_server_uri;
+
+	if(!mAccountHandle.empty())
 	{
-		// Already logged in.  This is an internal error.
-		LL_ERRS("Voice") << "Can't login again. Called from wrong state." << LL_ENDL;
+		// Already logged in.
+		LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL;
+		
+		// Don't process another login.
+		return;
 	}
-	else if ( accountName != mAccountName )
+	else if ( account_name != mAccountName )
 	{
 		//TODO: error?
-		LL_WARNS("Voice") << "Wrong account name! " << accountName
+		LL_WARNS("Voice") << "Wrong account name! " << account_name
 				<< " instead of " << mAccountName << LL_ENDL;
 	}
 	else
 	{
 		mAccountPassword = password;
 	}
+
+	std::string debugSIPURIHostName = gSavedSettings.getString("VivoxDebugSIPURIHostName");
+	
+	if( !debugSIPURIHostName.empty() )
+	{
+		mVoiceSIPURIHostName = debugSIPURIHostName;
+	}
+	
+	if( mVoiceSIPURIHostName.empty() )
+	{
+		// we have an empty account server name
+		// so we fall back to hardcoded defaults
+
+		if(sConnectingToAgni)
+		{
+			// Use the release account server
+			mVoiceSIPURIHostName = "bhr.vivox.com";
+		}
+		else
+		{
+			// Use the development account server
+			mVoiceSIPURIHostName = "bhd.vivox.com";
+		}
+	}
+	
+	std::string debugAccountServerURI = gSavedSettings.getString("VivoxDebugVoiceAccountServerURI");
+
+	if( !debugAccountServerURI.empty() )
+	{
+		mVoiceAccountServerURI = debugAccountServerURI;
+	}
+	
+	if( mVoiceAccountServerURI.empty() )
+	{
+		// If the account server URI isn't specified, construct it from the SIP URI hostname
+		mVoiceAccountServerURI = "https://www." + mVoiceSIPURIHostName + "/api2/";		
+	}
 }
 
 void LLVoiceClient::idle(void* user_data)
@@ -1120,11 +1435,16 @@ std::string LLVoiceClient::state2string(LLVoiceClient::state inState)
 
 	switch(inState)
 	{
+		CASE(stateDisableCleanup);
 		CASE(stateDisabled);
 		CASE(stateStart);
 		CASE(stateDaemonLaunched);
 		CASE(stateConnecting);
+		CASE(stateConnected);
 		CASE(stateIdle);
+		CASE(stateMicTuningStart);
+		CASE(stateMicTuningRunning);
+		CASE(stateMicTuningStop);
 		CASE(stateConnectorStart);
 		CASE(stateConnectorStarting);
 		CASE(stateConnectorStarted);
@@ -1133,12 +1453,8 @@ std::string LLVoiceClient::state2string(LLVoiceClient::state inState)
 		CASE(stateNeedsLogin);
 		CASE(stateLoggingIn);
 		CASE(stateLoggedIn);
+		CASE(stateCreatingSessionGroup);
 		CASE(stateNoChannel);
-		CASE(stateMicTuningStart);
-		CASE(stateMicTuningRunning);
-		CASE(stateMicTuningStop);
-		CASE(stateSessionCreate);
-		CASE(stateSessionConnect);
 		CASE(stateJoiningSession);
 		CASE(stateSessionJoined);
 		CASE(stateRunning);
@@ -1155,7 +1471,6 @@ std::string LLVoiceClient::state2string(LLVoiceClient::state inState)
 		CASE(stateJoinSessionFailed);
 		CASE(stateJoinSessionFailedWaiting);
 		CASE(stateJail);
-		CASE(stateMicTuningNoLogin);
 	}
 
 #undef CASE
@@ -1177,6 +1492,7 @@ std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserv
 		CASE(STATUS_JOINING);
 		CASE(STATUS_JOINED);
 		CASE(STATUS_LEFT_CHANNEL);
+		CASE(STATUS_VOICE_DISABLED);
 		CASE(BEGIN_ERROR_STATUS);
 		CASE(ERROR_CHANNEL_FULL);
 		CASE(ERROR_CHANNEL_LOCKED);
@@ -1210,9 +1526,13 @@ void LLVoiceClient::stateMachine()
 	{
 		updatePosition();
 	}
+	else if(mTuningMode)
+	{
+		// Tuning mode is special -- it needs to launch SLVoice even if voice is disabled.
+	}
 	else
 	{
-		if(getState() != stateDisabled)
+		if((getState() != stateDisabled) && (getState() != stateDisableCleanup))
 		{
 			// User turned off voice support.  Send the cleanup messages, close the socket, and reset.
 			if(!mConnected)
@@ -1222,13 +1542,10 @@ void LLVoiceClient::stateMachine()
 				killGateway();
 			}
 			
-			sessionTerminateSendMessage();
 			logout();
 			connectorShutdown();
-			closeSocket();
-			removeAllParticipants();
-
-			setState(stateDisabled);
+			
+			setState(stateDisableCleanup);
 		}
 	}
 	
@@ -1243,7 +1560,7 @@ void LLVoiceClient::stateMachine()
 			std::string regionName = region->getName();
 			std::string capURI = region->getCapability("ParcelVoiceInfoRequest");
 		
-			LL_DEBUGS("Voice") << "Region name = \"" << regionName <<"\", " << "parcel local ID = " << parcelLocalID << LL_ENDL;
+//			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.
@@ -1264,13 +1581,30 @@ void LLVoiceClient::stateMachine()
 
 	switch(getState())
 	{
+		//MARK: stateDisableCleanup
+		case stateDisableCleanup:
+			// Clean up and reset everything. 
+			closeSocket();
+			deleteAllSessions();
+			deleteAllBuddies();		
+			
+			mConnectorHandle.clear();
+			mAccountHandle.clear();
+			mAccountPassword.clear();
+			mVoiceAccountServerURI.clear();
+			
+			setState(stateDisabled);	
+		break;
+		
+		//MARK: stateDisabled
 		case stateDisabled:
-			if(mVoiceEnabled && (!mAccountName.empty() || mTuningMode))
+			if(mTuningMode || (mVoiceEnabled && !mAccountName.empty()))
 			{
 				setState(stateStart);
 			}
 		break;
 		
+		//MARK: stateStart
 		case stateStart:
 			if(gSavedSettings.getBOOL("CmdLineDisableVoice"))
 			{
@@ -1301,7 +1635,9 @@ void LLVoiceClient::stateMachine()
 					if(!LLFile::stat(exe_path, &s))
 					{
 						// vivox executable exists.  Build the command line and launch the daemon.
-						std::string args = " -p tcp -h -c";
+						// SLIM SDK: these arguments are no longer necessary.
+//						std::string args = " -p tcp -h -c";
+						std::string args;
 						std::string cmd;
 						std::string loglevel = gSavedSettings.getString("VivoxDebugLevel");
 						
@@ -1386,14 +1722,15 @@ void LLVoiceClient::stateMachine()
 					}	
 					else
 					{
-						LL_INFOS("Voice") << exe_path << "not found." << LL_ENDL;
+						LL_INFOS("Voice") << exe_path << " not found." << LL_ENDL;
 					}	
 				}
 				else
 				{		
+					// SLIM SDK: port changed from 44124 to 44125.
 					// 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
+					//  vivox-gw.exe -p tcp -i 0.0.0.0:44125
 					// and put that host's IP address here.
 					mDaemonHost = LLHost(gSavedSettings.getString("VoiceHost"), gSavedSettings.getU32("VoicePort"));
 				}
@@ -1405,17 +1742,23 @@ void LLVoiceClient::stateMachine()
 				
 				// Dirty the states we'll need to sync with the daemon when it comes up.
 				mPTTDirty = true;
+				mMicVolumeDirty = true;
 				mSpeakerVolumeDirty = true;
+				mSpeakerMuteDirty = true;
 				// These only need to be set if they're not default (i.e. empty string).
 				mCaptureDeviceDirty = !mCaptureDevice.empty();
 				mRenderDeviceDirty = !mRenderDevice.empty();
+				
+				mMainSessionGroupHandle.clear();
 			}
 		break;
-		
+
+		//MARK: stateDaemonLaunched
 		case stateDaemonLaunched:
-			LL_DEBUGS("Voice") << "Connecting to vivox daemon" << LL_ENDL;
 			if(mUpdateTimer.hasExpired())
 			{
+				LL_DEBUGS("Voice") << "Connecting to vivox daemon" << LL_ENDL;
+
 				mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS);
 
 				if(!mSocket)
@@ -1436,6 +1779,7 @@ void LLVoiceClient::stateMachine()
 			}
 		break;
 
+		//MARK: stateConnecting
 		case stateConnecting:
 		// Can't do this until we have the pump available.
 		if(mPump)
@@ -1453,48 +1797,34 @@ void LLVoiceClient::stateMachine()
 
 			mPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS);
 
-			setState(stateIdle);
+			setState(stateConnected);
 		}
 
 		break;
 		
-		case stateIdle:
+		//MARK: stateConnected
+		case stateConnected:
 			// Initial devices query
 			getCaptureDevicesSendMessage();
 			getRenderDevicesSendMessage();
 
 			mLoginRetryCount = 0;
-			
-			setState(stateConnectorStart);
-				
+
+			setState(stateIdle);
 		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)
+
+		//MARK: stateIdle
+		case stateIdle:
+			// This is the idle state where we're connected to the daemon but haven't set up a connector yet.
+			if(mTuningMode)
 			{
-				mTuningExitState = stateConnectorStart;
+				mTuningExitState = stateIdle;
 				setState(stateMicTuningStart);
 			}
-		break;
-		
-		case stateConnectorStarting:	// waiting for connector handle
-			// connectorCreateResponse() will transition from here to stateConnectorStarted.
-		break;
-		
-		case stateConnectorStarted:		// connector handle received
-			if(!mVoiceEnabled)
+			else if(!mVoiceEnabled)
 			{
-				// We were never logged in.  This will shut down the connector.
-				setState(stateLoggedOut);
+				// We never started up the connector.  This will shut down the daemon.
+				setState(stateConnectorStopped);
 			}
 			else if(!mAccountName.empty())
 			{
@@ -1508,12 +1838,13 @@ void LLVoiceClient::stateMachine()
 						{
 							requestVoiceAccountProvision();
 						}
-						setState(stateNeedsLogin);
+						setState(stateConnectorStart);
 					}
 				}
 			}
 		break;
-				
+
+		//MARK: stateMicTuningStart
 		case stateMicTuningStart:
 			if(mUpdateTimer.hasExpired())
 			{
@@ -1521,19 +1852,9 @@ void LLVoiceClient::stateMachine()
 				{
 					// 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;
+					
+					buildSetCaptureDevice(stream);
+					buildSetRenderDevice(stream);
 
 					if(!stream.str().empty())
 					{
@@ -1555,8 +1876,9 @@ void LLVoiceClient::stateMachine()
 			
 		break;
 		
+		//MARK: stateMicTuningRunning
 		case stateMicTuningRunning:
-			if(!mTuningMode || !mVoiceEnabled || mSessionTerminateRequested || mCaptureDeviceDirty || mRenderDeviceDirty)
+			if(!mTuningMode || mCaptureDeviceDirty || mRenderDeviceDirty)
 			{
 				// All of these conditions make us leave tuning mode.
 				setState(stateMicTuningStop);
@@ -1596,6 +1918,7 @@ void LLVoiceClient::stateMachine()
 			}
 		break;
 		
+		//MARK: stateMicTuningStop
 		case stateMicTuningStop:
 		{
 			// transition out of mic tuning
@@ -1609,7 +1932,40 @@ void LLVoiceClient::stateMachine()
 			
 		}
 		break;
-								
+												
+		//MARK: stateConnectorStart
+		case stateConnectorStart:
+			if(!mVoiceEnabled)
+			{
+				// We were never logged in.  This will shut down the connector.
+				setState(stateLoggedOut);
+			}
+			else if(!mVoiceAccountServerURI.empty())
+			{
+				connectorCreate();
+			}
+		break;
+		
+		//MARK: stateConnectorStarting
+		case stateConnectorStarting:	// waiting for connector handle
+			// connectorCreateResponse() will transition from here to stateConnectorStarted.
+		break;
+		
+		//MARK: stateConnectorStarted
+		case stateConnectorStarted:		// connector handle received
+			if(!mVoiceEnabled)
+			{
+				// We were never logged in.  This will shut down the connector.
+				setState(stateLoggedOut);
+			}
+			else
+			{
+				// The connector is started.  Send a login message.
+				setState(stateNeedsLogin);
+			}
+		break;
+				
+		//MARK: stateLoginRetry
 		case stateLoginRetry:
 			if(mLoginRetryCount == 0)
 			{
@@ -1633,6 +1989,7 @@ void LLVoiceClient::stateMachine()
 			}
 		break;
 		
+		//MARK: stateLoginRetryWait
 		case stateLoginRetryWait:
 			if(mUpdateTimer.hasExpired())
 			{
@@ -1640,6 +1997,7 @@ void LLVoiceClient::stateMachine()
 			}
 		break;
 		
+		//MARK: stateNeedsLogin
 		case stateNeedsLogin:
 			if(!mAccountPassword.empty())
 			{
@@ -1648,16 +2006,22 @@ void LLVoiceClient::stateMachine()
 			}		
 		break;
 		
+		//MARK: stateLoggingIn
 		case stateLoggingIn:			// waiting for account handle
 			// loginResponse() will transition from here to stateLoggedIn.
 		break;
 		
+		//MARK: stateLoggedIn
 		case stateLoggedIn:				// account handle received
-			// Initial kick-off of channel lookup logic
-			parcelChanged();
 
 			notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN);
 
+			// request the current set of block rules (we'll need them when updating the friends list)
+			accountListBlockRulesSendMessage();
+			
+			// request the current set of auto-accept rules
+			accountListAutoAcceptRulesSendMessage();
+			
 			// Set up the mute list observer if it hasn't been set up already.
 			if((!sMuteListListener_listening))
 			{
@@ -1665,44 +2029,117 @@ void LLVoiceClient::stateMachine()
 				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)
+			// Set up the friends list observer if it hasn't been set up already.
+			if(friendslist_listener == NULL)
 			{
-				mTuningExitState = stateNoChannel;
-				setState(stateMicTuningStart);
+				friendslist_listener = new LLVoiceClientFriendsObserver;
+				LLAvatarTracker::instance().addObserver(friendslist_listener);
 			}
-			else if(!mNextSessionHandle.empty())
+			
+			// Set the initial state of mic mute, local speaker volume, etc.
 			{
-				setState(stateSessionConnect);
+				std::ostringstream stream;
+				
+				buildLocalAudioUpdates(stream);
+				
+				if(!stream.str().empty())
+				{
+					writeString(stream.str());
+				}
 			}
-			else if(!mNextSessionURI.empty())
+			
+#if USE_SESSION_GROUPS			
+			// create the main session group
+			sessionGroupCreateSendMessage();
+			
+			setState(stateCreatingSessionGroup);
+#else
+			// Not using session groups -- skip the stateCreatingSessionGroup state.
+			setState(stateNoChannel);
+
+			// Initial kick-off of channel lookup logic
+			parcelChanged();		
+#endif
+		break;
+		
+		//MARK: stateCreatingSessionGroup
+		case stateCreatingSessionGroup:
+			if(mSessionTerminateRequested || !mVoiceEnabled)
 			{
-				setState(stateSessionCreate);
+				// TODO: Question: is this the right way out of this state
+				setState(stateSessionTerminated);
 			}
-		break;
+			else if(!mMainSessionGroupHandle.empty())
+			{
+				setState(stateNoChannel);
+				
+				// Start looped recording (needed for "panic button" anti-griefing tool)
+				recordingLoopStart();
 
-		case stateSessionCreate:
-			sessionCreateSendMessage();
-			notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING);
-			setState(stateJoiningSession);
+				// Initial kick-off of channel lookup logic
+				parcelChanged();		
+			}
 		break;
-		
-		case stateSessionConnect:
-			sessionConnectSendMessage();
-			notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING);
-			setState(stateJoiningSession);
+					
+		//MARK: stateNoChannel
+		case stateNoChannel:
+			// Do this here as well as inside sendPositionalUpdate().  
+			// Otherwise, if you log in but don't join a proximal channel (such as when your login location has voice disabled), your friends list won't sync.
+			sendFriendsListUpdates();
+			
+			if(mSessionTerminateRequested || !mVoiceEnabled)
+			{
+				// TODO: Question: Is this the right way out of this state?
+				setState(stateSessionTerminated);
+			}
+			else if(mTuningMode)
+			{
+				mTuningExitState = stateNoChannel;
+				setState(stateMicTuningStart);
+			}
+			else if(sessionNeedsRelog(mNextAudioSession))
+			{
+				requestRelog();
+				setState(stateSessionTerminated);
+			}
+			else if(mNextAudioSession)
+			{				
+				sessionState *oldSession = mAudioSession;
+
+				mAudioSession = mNextAudioSession;
+				if(!mAudioSession->mReconnect)	
+				{
+					mNextAudioSession = NULL;
+				}
+				
+				// The old session may now need to be deleted.
+				reapSession(oldSession);
+				
+				if(!mAudioSession->mHandle.empty())
+				{
+					// Connect to a session by session handle
+
+					sessionMediaConnectSendMessage(mAudioSession);
+				}
+				else
+				{
+					// Connect to a session by URI
+					sessionCreateSendMessage(mAudioSession, true, false);
+				}
+
+				notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING);
+				setState(stateJoiningSession);
+			}
+			else if(!mSpatialSessionURI.empty())
+			{
+				// If we're not headed elsewhere and have a spatial URI, return to spatial.
+				switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials);
+			}
 		break;
-		
+
+		//MARK: stateJoiningSession
 		case stateJoiningSession:		// waiting for session handle
-			// sessionCreateResponse() will transition from here to stateSessionJoined.
+			// joinedAudioSession() will transition from here to stateSessionJoined.
 			if(!mVoiceEnabled)
 			{
 				// User bailed out during connect -- jump straight to teardown.
@@ -1710,30 +2147,27 @@ void LLVoiceClient::stateMachine()
 			}
 			else if(mSessionTerminateRequested)
 			{
-				if(!mSessionHandle.empty())
+				if(mAudioSession && !mAudioSession->mHandle.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)
+					if(mAudioSession->mIsP2P)
 					{
-						sessionTerminateSendMessage();
+						sessionMediaDisconnectSendMessage(mAudioSession);
 						setState(stateSessionTerminated);
 					}
 				}
 			}
 		break;
 		
+		//MARK: stateSessionJoined
 		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.
+			// It appears that I need to wait for BOTH the SessionGroup.AddSession 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 SessionGroup.AddSession 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())
+			if(mAudioSession && mAudioSession->mVoiceEnabled)
 			{
-				// 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;
@@ -1744,6 +2178,11 @@ void LLVoiceClient::stateMachine()
 				// Start the throttle timer
 				mUpdateTimer.start();
 				mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
+
+				// Events that need to happen when a session is joined could go here.
+				// Maybe send initial spatial data?
+				notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED);
+
 			}
 			else if(!mVoiceEnabled)
 			{
@@ -1754,21 +2193,20 @@ void LLVoiceClient::stateMachine()
 			{
 				// 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)
+				if(mAudioSession && mAudioSession->mIsP2P)
 				{
-					sessionTerminateSendMessage();
+					sessionMediaDisconnectSendMessage(mAudioSession);
 					setState(stateSessionTerminated);
 				}
 			}
 		break;
 		
+		//MARK: stateRunning
 		case stateRunning:				// steady state
-			// sessionTerminateSendMessage() will transition from here to stateLeavingSession
-			
 			// Disabling voice or disconnect requested.
 			if(!mVoiceEnabled || mSessionTerminateRequested)
 			{
-				sessionTerminateSendMessage();
+				leaveAudioSession();
 			}
 			else
 			{
@@ -1801,7 +2239,7 @@ void LLVoiceClient::stateMachine()
 					}
 				}
 				
-				if(mNonSpatialChannel)
+				if(!inSpatialChannel())
 				{
 					// When in a non-spatial channel, never send positional updates.
 					mSpatialCoordsDirty = false;
@@ -1814,7 +2252,7 @@ void LLVoiceClient::stateMachine()
 				
 				// 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())
+				if((mAudioSession && mAudioSession->mVolumeDirty) || mPTTDirty || mSpeakerVolumeDirty || mUpdateTimer.hasExpired())
 				{
 					mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS);
 					sendPositionalUpdate();
@@ -1822,25 +2260,38 @@ void LLVoiceClient::stateMachine()
 			}
 		break;
 		
+		//MARK: stateLeavingSession
 		case stateLeavingSession:		// waiting for terminate session response
 			// The handler for the Session.Terminate response will transition from here to stateSessionTerminated.
 		break;
 
+		//MARK: stateSessionTerminated
 		case stateSessionTerminated:
-			// Always reset the terminate request flag when we get here.
-			mSessionTerminateRequested = false;
 			
+			// Must do this first, since it uses mAudioSession.
 			notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL);
+			
+			if(mAudioSession)
+			{
+				sessionState *oldSession = mAudioSession;
+
+				mAudioSession = NULL;
+				// We just notified status observers about this change.  Don't do it again.
+				mAudioSessionChanged = false;
 
-			if(mVoiceEnabled)
+				// The old session may now need to be deleted.
+				reapSession(oldSession);
+			}
+			else
 			{
-				// 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;
-				}
-				
+				LL_WARNS("Voice") << "stateSessionTerminated with NULL mAudioSession" << LL_ENDL;
+			}
+	
+			// Always reset the terminate request flag when we get here.
+			mSessionTerminateRequested = false;
+
+			if(mVoiceEnabled && !mRelogRequested)
+			{				
 				// Just leaving a channel, go back to stateNoChannel (the "logged in but have no channel" state).
 				setState(stateNoChannel);
 			}
@@ -1848,49 +2299,67 @@ void LLVoiceClient::stateMachine()
 			{
 				// Shutting down voice, continue with disconnecting.
 				logout();
+				
+				// The state machine will take it from here
+				mRelogRequested = false;
 			}
 			
 		break;
 		
+		//MARK: stateLoggingOut
 		case stateLoggingOut:			// waiting for logout response
 			// The handler for the Account.Logout response will transition from here to stateLoggedOut.
 		break;
+		//MARK: stateLoggedOut
 		case stateLoggedOut:			// logout response received
 			// shut down the connector
 			connectorShutdown();
 		break;
 		
+		//MARK: stateConnectorStopping
 		case stateConnectorStopping:	// waiting for connector stop
 			// The handler for the Connector.InitiateShutdown response will transition from here to stateConnectorStopped.
 		break;
 
+		//MARK: stateConnectorStopped
 		case stateConnectorStopped:		// connector stop received
-			// Clean up and reset everything. 
-			closeSocket();
-			removeAllParticipants();
-			setState(stateDisabled);
+			setState(stateDisableCleanup);
 		break;
 
+		//MARK: stateConnectorFailed
 		case stateConnectorFailed:
 			setState(stateConnectorFailedWaiting);
 		break;
+		//MARK: stateConnectorFailedWaiting
 		case stateConnectorFailedWaiting:
 		break;
 
+		//MARK: stateLoginFailed
 		case stateLoginFailed:
 			setState(stateLoginFailedWaiting);
 		break;
+		//MARK: stateLoginFailedWaiting
 		case stateLoginFailedWaiting:
 			// No way to recover from these.  Yet.
 		break;
 
+		//MARK: stateJoinSessionFailed
 		case stateJoinSessionFailed:
 			// Transition to error state.  Send out any notifications here.
-			LL_WARNS("Voice") << "stateJoinSessionFailed: (" << mVivoxErrorStatusCode << "): " << mVivoxErrorStatusString << LL_ENDL;
+			if(mAudioSession)
+			{
+				LL_WARNS("Voice") << "stateJoinSessionFailed: (" << mAudioSession->mErrorStatusCode << "): " << mAudioSession->mErrorStatusString << LL_ENDL;
+			}
+			else
+			{
+				LL_WARNS("Voice") << "stateJoinSessionFailed with no current session" << LL_ENDL;
+			}
+			
 			notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN);
 			setState(stateJoinSessionFailedWaiting);
 		break;
 		
+		//MARK: stateJoinSessionFailedWaiting
 		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.
@@ -1900,22 +2369,24 @@ void LLVoiceClient::stateMachine()
 			}
 		break;
 		
+		//MARK: stateJail
 		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)
+	
+	if(mAudioSession && mAudioSession->mParticipantsChanged)
 	{
-		mParticipantMapChanged = false;
-		notifyObservers();
+		mAudioSession->mParticipantsChanged = false;
+		mAudioSessionChanged = true;
+	}
+	
+	if(mAudioSessionChanged)
+	{
+		mAudioSessionChanged = false;
+		notifyParticipantObservers();
 	}
-
 }
 
 void LLVoiceClient::closeSocket(void)
@@ -1927,12 +2398,19 @@ void LLVoiceClient::closeSocket(void)
 void LLVoiceClient::loginSendMessage()
 {
 	std::ostringstream stream;
+
+	bool autoPostCrashDumps = gSavedSettings.getBOOL("VivoxAutoPostCrashDumps");
+
 	stream
 	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.Login.1\">"
 		<< "<ConnectorHandle>" << mConnectorHandle << "</ConnectorHandle>"
 		<< "<AccountName>" << mAccountName << "</AccountName>"
 		<< "<AccountPassword>" << mAccountPassword << "</AccountPassword>"
 		<< "<AudioSessionAnswerMode>VerifyAnswer</AudioSessionAnswerMode>"
+		<< "<EnableBuddiesAndPresence>true</EnableBuddiesAndPresence>"
+		<< "<BuddyManagementMode>Application</BuddyManagementMode>"
+		<< "<ParticipantPropertyFrequency>5</ParticipantPropertyFrequency>"
+		<< (autoPostCrashDumps?"<AutopostCrashDumps>true</AutopostCrashDumps>":"")
 	<< "</Request>\n\n\n";
 	
 	writeString(stream.str());
@@ -1940,7 +2418,10 @@ void LLVoiceClient::loginSendMessage()
 
 void LLVoiceClient::logout()
 {
-	mAccountPassword = "";
+	// Ensure that we'll re-request provisioning before logging in again
+	mAccountPassword.clear();
+	mVoiceAccountServerURI.clear();
+	
 	setState(stateLoggingOut);
 	logoutSendMessage();
 }
@@ -1962,78 +2443,164 @@ void LLVoiceClient::logoutSendMessage()
 	}
 }
 
-void LLVoiceClient::channelGetListSendMessage()
+void LLVoiceClient::accountListBlockRulesSendMessage()
 {
-	std::ostringstream stream;
-	stream
-	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.ChannelGetList.1\">"
-		<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
-	<< "</Request>\n\n\n";
+	if(!mAccountHandle.empty())
+	{		
+		std::ostringstream stream;
 
-	writeString(stream.str());
+		LL_DEBUGS("Voice") << "requesting block rules" << LL_ENDL;
+
+		stream
+		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.ListBlockRules.1\">"
+			<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+		<< "</Request>"
+		<< "\n\n\n";
+
+		writeString(stream.str());
+	}
 }
 
-void LLVoiceClient::sessionCreateSendMessage()
+void LLVoiceClient::accountListAutoAcceptRulesSendMessage()
 {
-	LL_DEBUGS("Voice") << "requesting join: " << mNextSessionURI << LL_ENDL;
+	if(!mAccountHandle.empty())
+	{		
+		std::ostringstream stream;
+
+		LL_DEBUGS("Voice") << "requesting auto-accept rules" << LL_ENDL;
+
+		stream
+		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.ListAutoAcceptRules.1\">"
+			<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+		<< "</Request>"
+		<< "\n\n\n";
+
+		writeString(stream.str());
+	}
+}
+
+void LLVoiceClient::sessionGroupCreateSendMessage()
+{
+	if(!mAccountHandle.empty())
+	{		
+		std::ostringstream stream;
 
-	mSessionURI = mNextSessionURI;
-	mNonSpatialChannel = !mNextSessionSpatial;
-	mSessionResetOnClose = mNextSessionResetOnClose;
-	mNextSessionResetOnClose = false;
-	if(mNextSessionNoReconnect)
+		LL_DEBUGS("Voice") << "creating session group" << LL_ENDL;
+
+		stream
+		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.Create.1\">"
+			<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+			<< "<Type>Normal</Type>"
+		<< "</Request>"
+		<< "\n\n\n";
+
+		writeString(stream.str());
+	}
+}
+
+void LLVoiceClient::sessionCreateSendMessage(sessionState *session, bool startAudio, bool startText)
+{
+	LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL;
+	
+	session->mCreateInProgress = true;
+	if(startAudio)
 	{
-		// Clear the stashed URI so it can't reconnect
-		mNextSessionURI.clear();
+		session->mMediaConnectInProgress = true;
 	}
-	// Only p2p sessions are created with "no reconnect".
-	mSessionP2P = mNextSessionNoReconnect;
 
 	std::ostringstream stream;
 	stream
-	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Create.1\">"
+	<< "<Request requestId=\"" << session->mSIPURI << "\" action=\"Session.Create.1\">"
 		<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
-		<< "<URI>" << mSessionURI << "</URI>";
+		<< "<URI>" << session->mSIPURI << "</URI>";
 
 	static const std::string allowed_chars =
 				"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 				"0123456789"
 				"-._~";
 
-	if(!mNextSessionHash.empty())
+	if(!session->mHash.empty())
 	{
 		stream
-			<< "<Password>" << LLURI::escape(mNextSessionHash, allowed_chars) << "</Password>"
+			<< "<Password>" << LLURI::escape(session->mHash, allowed_chars) << "</Password>"
 			<< "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>";
 	}
 	
 	stream
+		<< "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>"
+		<< "<ConnectText>" << (startText?"true":"false") << "</ConnectText>"
 		<< "<Name>" << mChannelName << "</Name>"
 	<< "</Request>\n\n\n";
 	writeString(stream.str());
 }
 
-void LLVoiceClient::sessionConnectSendMessage()
+void LLVoiceClient::sessionGroupAddSessionSendMessage(sessionState *session, bool startAudio, bool startText)
 {
-	LL_DEBUGS("Voice") << "connecting to session handle: " << mNextSessionHandle << LL_ENDL;
+	LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL;
+	
+	session->mCreateInProgress = true;
+	if(startAudio)
+	{
+		session->mMediaConnectInProgress = true;
+	}
+	
+	std::string password;
+	if(!session->mHash.empty())
+	{
+		static const std::string allowed_chars =
+					"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+					"0123456789"
+					"-._~"
+					;
+		password = LLURI::escape(session->mHash, allowed_chars);
+	}
+
+	std::ostringstream stream;
+	stream
+	<< "<Request requestId=\"" << session->mSIPURI << "\" action=\"SessionGroup.AddSession.1\">"
+		<< "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
+		<< "<URI>" << session->mSIPURI << "</URI>"
+		<< "<Name>" << mChannelName << "</Name>"
+		<< "<ConnectAudio>" << (startAudio?"true":"false") << "</ConnectAudio>"
+		<< "<ConnectText>" << (startText?"true":"false") << "</ConnectText>"
+		<< "<Password>" << password << "</Password>"
+		<< "<PasswordHashAlgorithm>SHA1UserName</PasswordHashAlgorithm>"
+	<< "</Request>\n\n\n"
+	;
 	
-	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;
+	writeString(stream.str());
+}
+
+void LLVoiceClient::sessionMediaConnectSendMessage(sessionState *session)
+{
+	LL_DEBUGS("Voice") << "connecting audio to session handle: " << session->mHandle << LL_ENDL;
+
+	session->mMediaConnectInProgress = true;
 	
 	std::ostringstream stream;
+
+	stream
+	<< "<Request requestId=\"" << session->mHandle << "\" action=\"Session.MediaConnect.1\">"
+		<< "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
+		<< "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+		<< "<Media>Audio</Media>"
+	<< "</Request>\n\n\n";
+
+	writeString(stream.str());
+}
+
+void LLVoiceClient::sessionTextConnectSendMessage(sessionState *session)
+{
+	LL_DEBUGS("Voice") << "connecting text to session handle: " << session->mHandle << LL_ENDL;
 	
+	std::ostringstream stream;
+
 	stream
-	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Connect.1\">"
-		<< "<SessionHandle>" << mSessionHandle << "</SessionHandle>"
-		<< "<AudioMedia>default</AudioMedia>"
+	<< "<Request requestId=\"" << session->mHandle << "\" action=\"Session.TextConnect.1\">"
+		<< "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
+		<< "<SessionHandle>" << session->mHandle << "</SessionHandle>"
 	<< "</Request>\n\n\n";
+
 	writeString(stream.str());
 }
 
@@ -2042,52 +2609,112 @@ void LLVoiceClient::sessionTerminate()
 	mSessionTerminateRequested = true;
 }
 
-void LLVoiceClient::sessionTerminateSendMessage()
+void LLVoiceClient::requestRelog()
 {
-	LL_DEBUGS("Voice") << "leaving session: " << mSessionURI << LL_ENDL;
+	mSessionTerminateRequested = true;
+	mRelogRequested = true;
+}
 
-	switch(getState())
+
+void LLVoiceClient::leaveAudioSession()
+{
+	if(mAudioSession)
 	{
-		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;	
+		LL_DEBUGS("Voice") << "leaving session: " << mAudioSession->mSIPURI << 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(!mAudioSession->mHandle.empty())
+				{
+
+#if RECORD_EVERYTHING
+					// HACK: for testing only
+					// Save looped recording
+					std::string savepath("/tmp/vivoxrecording");
+					{
+						time_t now = time(NULL);
+						const size_t BUF_SIZE = 64;
+						char time_str[BUF_SIZE];	/* Flawfinder: ignore */
+						
+						strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now));
+						savepath += time_str;
+					}
+					recordingLoopSave(savepath);
+#endif
+
+					sessionMediaDisconnectSendMessage(mAudioSession);
+					setState(stateLeavingSession);
+				}
+				else
+				{
+					LL_WARNS("Voice") << "called with no session handle" << LL_ENDL;	
+					setState(stateSessionTerminated);
+				}
+			break;
+			case stateJoinSessionFailed:
+			case stateJoinSessionFailedWaiting:
 				setState(stateSessionTerminated);
-			}
-		break;
-		case stateJoinSessionFailed:
-		case stateJoinSessionFailedWaiting:
-			setState(stateSessionTerminated);
-		break;
-		
-		default:
-			LL_WARNS("Voice") << "called from unknown state" << LL_ENDL;
-		break;
+			break;
+			
+			default:
+				LL_WARNS("Voice") << "called from unknown state" << LL_ENDL;
+			break;
+		}
+	}
+	else
+	{
+		LL_WARNS("Voice") << "called with no active session" << LL_ENDL;
+		setState(stateSessionTerminated);
 	}
 }
 
-void LLVoiceClient::sessionTerminateByHandle(std::string &sessionHandle)
+void LLVoiceClient::sessionTerminateSendMessage(sessionState *session)
 {
-	LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << sessionHandle << LL_ENDL;	
-
 	std::ostringstream stream;
+	
+	LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL;	
 	stream
 	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Terminate.1\">"
-		<< "<SessionHandle>" << sessionHandle << "</SessionHandle>"
-	<< "</Request>"
-	<< "\n\n\n";
+		<< "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+	<< "</Request>\n\n\n";
+	
+	writeString(stream.str());
+}
+
+void LLVoiceClient::sessionMediaDisconnectSendMessage(sessionState *session)
+{
+	std::ostringstream stream;
+	
+	LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL;	
+	stream
+	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.MediaDisconnect.1\">"
+		<< "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
+		<< "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+		<< "<Media>Audio</Media>"
+	<< "</Request>\n\n\n";
+	
+	writeString(stream.str());
+	
+}
+
+void LLVoiceClient::sessionTextDisconnectSendMessage(sessionState *session)
+{
+	std::ostringstream stream;
+	
+	LL_DEBUGS("Voice") << "Sending Session.TextDisconnect with handle " << session->mHandle << LL_ENDL;	
+	stream
+	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.TextDisconnect.1\">"
+		<< "<SessionGroupHandle>" << session->mGroupHandle << "</SessionGroupHandle>"
+		<< "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+	<< "</Request>\n\n\n";
 	
 	writeString(stream.str());
 }
@@ -2114,14 +2741,12 @@ void LLVoiceClient::getRenderDevicesSendMessage()
 
 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);
@@ -2153,15 +2778,13 @@ void LLVoiceClient::setCaptureDevice(const std::string& name)
 }
 
 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);
 }
@@ -2273,29 +2896,22 @@ void LLVoiceClient::tuningCaptureStopSendMessage()
 
 void LLVoiceClient::tuningSetMicVolume(float volume)
 {
-	int scaledVolume = ((int)(volume * 100.0f)) - 100;
-	if(scaledVolume != mTuningMicVolume)
+	int scaled_volume = scale_mic_volume(volume);
+
+	if(scaled_volume != mTuningMicVolume)
 	{
-		mTuningMicVolume = scaledVolume;
+		mTuningMicVolume = scaled_volume;
 		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
+	int scaled_volume = scale_speaker_volume(volume);	
 
-	if(scaledVolume != mTuningSpeakerVolume)
+	if(scaled_volume != mTuningSpeakerVolume)
 	{
-		mTuningSpeakerVolume = scaledVolume;
+		mTuningSpeakerVolume = scaled_volume;
 		mTuningSpeakerVolumeDirty = true;
 	}
 }
@@ -2334,47 +2950,31 @@ 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);
+	setState(stateDisableCleanup);
 }
 
 void LLVoiceClient::giveUp()
 {
 	// All has failed.  Clean up and stop trying.
 	closeSocket();
-	removeAllParticipants();
+	deleteAllSessions();
+	deleteAllBuddies();
 	
 	setState(stateJail);
 }
 
-void LLVoiceClient::sendPositionalUpdate(void)
-{	
-	std::ostringstream stream;
+static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVector3d &pos, LLVector3 &vel)
+{
+	F32 nat[3], nup[3], nl[3], nvel[3]; // the new at, up, left vectors and the  new position and velocity
+	F64 npos[3];
 	
-	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 
+	// The original XML command was sent like this:
+	/*
 			<< "<Position>"
-				<< "<X>" << mAvatarPosition[VX] << "</X>"
-				<< "<Y>" << mAvatarPosition[VZ] << "</Y>"
-				<< "<Z>" << mAvatarPosition[VY] << "</Z>"
+				<< "<X>" << pos[VX] << "</X>"
+				<< "<Y>" << pos[VZ] << "</Y>"
+				<< "<Z>" << pos[VY] << "</Z>"
 			<< "</Position>"
 			<< "<Velocity>"
 				<< "<X>" << mAvatarVelocity[VX] << "</X>"
@@ -2396,6 +2996,148 @@ void LLVoiceClient::sendPositionalUpdate(void)
 				<< "<Y>" << u.mV [VZ] << "</Y>"
 				<< "<Z>" << a.mV [VY] << "</Z>"
 			<< "</LeftOrientation>";
+	*/
+
+#if 1
+	// This was the original transform done when building the XML command
+	nat[0] = left.mV[VX];
+	nat[1] = up.mV[VX];
+	nat[2] = at.mV[VX];
+
+	nup[0] = left.mV[VZ];
+	nup[1] = up.mV[VY];
+	nup[2] = at.mV[VZ];
+
+	nl[0] = left.mV[VY];
+	nl[1] = up.mV[VZ];
+	nl[2] = at.mV[VY];
+
+	npos[0] = pos.mdV[VX];
+	npos[1] = pos.mdV[VZ];
+	npos[2] = pos.mdV[VY];
+
+	nvel[0] = vel.mV[VX];
+	nvel[1] = vel.mV[VZ];
+	nvel[2] = vel.mV[VY];
+
+	for(int i=0;i<3;++i) {
+		at.mV[i] = nat[i];
+		up.mV[i] = nup[i];
+		left.mV[i] = nl[i];
+		pos.mdV[i] = npos[i];
+	}
+	
+	// This was the original transform done in the SDK
+	nat[0] = at.mV[2];
+	nat[1] = 0; // y component of at vector is always 0, this was up[2]
+	nat[2] = -1 * left.mV[2];
+
+	// We override whatever the application gives us
+	nup[0] = 0; // x component of up vector is always 0
+	nup[1] = 1; // y component of up vector is always 1
+	nup[2] = 0; // z component of up vector is always 0
+
+	nl[0] = at.mV[0];
+	nl[1] = 0;  // y component of left vector is always zero, this was up[0]
+	nl[2] = -1 * left.mV[0];
+
+	npos[2] = pos.mdV[2] * -1.0;
+	npos[1] = pos.mdV[1];
+	npos[0] = pos.mdV[0];
+
+	for(int i=0;i<3;++i) {
+		at.mV[i] = nat[i];
+		up.mV[i] = nup[i];
+		left.mV[i] = nl[i];
+		pos.mdV[i] = npos[i];
+	}
+#else
+	// This is the compose of the two transforms (at least, that's what I'm trying for)
+	nat[0] = at.mV[VX];
+	nat[1] = 0; // y component of at vector is always 0, this was up[2]
+	nat[2] = -1 * up.mV[VZ];
+
+	// We override whatever the application gives us
+	nup[0] = 0; // x component of up vector is always 0
+	nup[1] = 1; // y component of up vector is always 1
+	nup[2] = 0; // z component of up vector is always 0
+
+	nl[0] = left.mV[VX];
+	nl[1] = 0;  // y component of left vector is always zero, this was up[0]
+	nl[2] = -1 * left.mV[VY];
+
+	npos[0] = pos.mdV[VX];
+	npos[1] = pos.mdV[VZ];
+	npos[2] = pos.mdV[VY] * -1.0;
+
+	nvel[0] = vel.mV[VX];
+	nvel[1] = vel.mV[VZ];
+	nvel[2] = vel.mV[VY];
+
+	for(int i=0;i<3;++i) {
+		at.mV[i] = nat[i];
+		up.mV[i] = nup[i];
+		left.mV[i] = nl[i];
+		pos.mdV[i] = npos[i];
+	}
+	
+#endif
+}
+
+void LLVoiceClient::sendPositionalUpdate(void)
+{	
+	std::ostringstream stream;
+	
+	if(mSpatialCoordsDirty)
+	{
+		LLVector3 l, u, a, vel;
+		LLVector3d pos;
+
+		mSpatialCoordsDirty = false;
+		
+		// Always send both speaker and listener positions together.
+		stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.Set3DPosition.1\">"		
+			<< "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>";
+		
+		stream << "<SpeakerPosition>";
+
+//		LL_DEBUGS("Voice") << "Sending speaker position " << mAvatarPosition << LL_ENDL;
+		l = mAvatarRot.getLeftRow();
+		u = mAvatarRot.getUpRow();
+		a = mAvatarRot.getFwdRow();
+		pos = mAvatarPosition;
+		vel = mAvatarVelocity;
+
+		// SLIM SDK: the old SDK was doing a transform on the passed coordinates that the new one doesn't do anymore.
+		// The old transform is replicated by this function.
+		oldSDKTransform(l, u, a, pos, vel);
+		
+		stream 
+			<< "<Position>"
+				<< "<X>" << pos.mdV[VX] << "</X>"
+				<< "<Y>" << pos.mdV[VY] << "</Y>"
+				<< "<Z>" << pos.mdV[VZ] << "</Z>"
+			<< "</Position>"
+			<< "<Velocity>"
+				<< "<X>" << vel.mV[VX] << "</X>"
+				<< "<Y>" << vel.mV[VY] << "</Y>"
+				<< "<Z>" << vel.mV[VZ] << "</Z>"
+			<< "</Velocity>"
+			<< "<AtOrientation>"
+				<< "<X>" << a.mV[VX] << "</X>"
+				<< "<Y>" << a.mV[VY] << "</Y>"
+				<< "<Z>" << a.mV[VZ] << "</Z>"
+			<< "</AtOrientation>"
+			<< "<UpOrientation>"
+				<< "<X>" << u.mV[VX] << "</X>"
+				<< "<Y>" << u.mV[VY] << "</Y>"
+				<< "<Z>" << u.mV[VZ] << "</Z>"
+			<< "</UpOrientation>"
+			<< "<LeftOrientation>"
+				<< "<X>" << l.mV [VX] << "</X>"
+				<< "<Y>" << l.mV [VY] << "</Y>"
+				<< "<Z>" << l.mV [VZ] << "</Z>"
+			<< "</LeftOrientation>";
 
 		stream << "</SpeakerPosition>";
 
@@ -2430,43 +3172,158 @@ void LLVoiceClient::sendPositionalUpdate(void)
 		l = earRot.getLeftRow();
 		u = earRot.getUpRow();
 		a = earRot.getFwdRow();
+		pos = earPosition;
+		vel = earVelocity;
 
-		LL_DEBUGS("Voice") << "Sending listener position " << earPosition << LL_ENDL;
-
+//		LL_DEBUGS("Voice") << "Sending listener position " << earPosition << LL_ENDL;
+		
+		oldSDKTransform(l, u, a, pos, vel);
+		
 		stream 
 			<< "<Position>"
-				<< "<X>" << earPosition[VX] << "</X>"
-				<< "<Y>" << earPosition[VZ] << "</Y>"
-				<< "<Z>" << earPosition[VY] << "</Z>"
+				<< "<X>" << pos.mdV[VX] << "</X>"
+				<< "<Y>" << pos.mdV[VY] << "</Y>"
+				<< "<Z>" << pos.mdV[VZ] << "</Z>"
 			<< "</Position>"
 			<< "<Velocity>"
-				<< "<X>" << earVelocity[VX] << "</X>"
-				<< "<Y>" << earVelocity[VZ] << "</Y>"
-				<< "<Z>" << earVelocity[VY] << "</Z>"
+				<< "<X>" << vel.mV[VX] << "</X>"
+				<< "<Y>" << vel.mV[VY] << "</Y>"
+				<< "<Z>" << vel.mV[VZ] << "</Z>"
 			<< "</Velocity>"
 			<< "<AtOrientation>"
-				<< "<X>" << l.mV[VX] << "</X>"
-				<< "<Y>" << u.mV[VX] << "</Y>"
-				<< "<Z>" << a.mV[VX] << "</Z>"
+				<< "<X>" << a.mV[VX] << "</X>"
+				<< "<Y>" << a.mV[VY] << "</Y>"
+				<< "<Z>" << a.mV[VZ] << "</Z>"
 			<< "</AtOrientation>"
 			<< "<UpOrientation>"
-				<< "<X>" << l.mV[VZ] << "</X>"
+				<< "<X>" << u.mV[VX] << "</X>"
 				<< "<Y>" << u.mV[VY] << "</Y>"
-				<< "<Z>" << a.mV[VZ] << "</Z>"
+				<< "<Z>" << u.mV[VZ] << "</Z>"
 			<< "</UpOrientation>"
 			<< "<LeftOrientation>"
-				<< "<X>" << l.mV [VY] << "</X>"
-				<< "<Y>" << u.mV [VZ] << "</Y>"
-				<< "<Z>" << a.mV [VY] << "</Z>"
+				<< "<X>" << l.mV [VX] << "</X>"
+				<< "<Y>" << l.mV [VY] << "</Y>"
+				<< "<Z>" << l.mV [VZ] << "</Z>"
 			<< "</LeftOrientation>";
 
+
 		stream << "</ListenerPosition>";
 
 		stream << "</Request>\n\n\n";
 	}	
+	
+	if(mAudioSession && mAudioSession->mVolumeDirty)
+	{
+		participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin();
+
+		mAudioSession->mVolumeDirty = false;
+		
+		for(; iter != mAudioSession->mParticipantsByURI.end(); iter++)
+		{
+			participantState *p = iter->second;
+			
+			if(p->mVolumeDirty)
+			{
+				// Can't set volume/mute for yourself
+				if(!p->mIsSelf)
+				{
+					int volume = p->mUserVolume;
+					bool mute = p->mOnMuteList;
+										
+					// SLIM SDK: scale volume from 0-400 (with 100 as "normal") to 0-100 (with 56 as "normal")
+					if(volume < 100)
+						volume = (volume * 56) / 100;
+					else
+						volume = (((volume - 100) * (100 - 56)) / 300) + 56;
+
+					if(mute)
+					{
+						// SetParticipantMuteForMe doesn't work in p2p sessions.
+						// If we want the user to be muted, set their volume to 0 as well.
+						// This isn't perfect, but it will at least reduce their volume to a minimum.
+						volume = 0;
+					}
+					
+					if(volume == 0)
+						mute = true;
+
+					LL_DEBUGS("Voice") << "Setting volume/mute for avatar " << p->mAvatarID << " to " << volume << (mute?"/true":"/false") << LL_ENDL;
+					
+					// SLIM SDK: Send both volume and mute commands.
+					
+					// Send a "volume for me" command for the user.
+					stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantVolumeForMe.1\">"
+						<< "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>"
+						<< "<ParticipantURI>" << p->mURI << "</ParticipantURI>"
+						<< "<Volume>" << volume << "</Volume>"
+						<< "</Request>\n\n\n";
+
+					// Send a "mute for me" command for the user
+					stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SetParticipantMuteForMe.1\">"
+						<< "<SessionHandle>" << getAudioSessionHandle() << "</SessionHandle>"
+						<< "<ParticipantURI>" << p->mURI << "</ParticipantURI>"
+						<< "<Mute>" << (mute?"1":"0") << "</Mute>"
+						<< "</Request>\n\n\n";
+				}
+				
+				p->mVolumeDirty = false;
+			}
+		}
+	}
+			
+	buildLocalAudioUpdates(stream);
+	
+	if(!stream.str().empty())
+	{
+		writeString(stream.str());
+	}
+	
+	// Friends list updates can be huge, especially on the first voice login of an account with lots of friends.
+	// Batching them all together can choke SLVoice, so send them in separate writes.
+	sendFriendsListUpdates();
+}
+
+void LLVoiceClient::buildSetCaptureDevice(std::ostringstream &stream)
+{
+	if(mCaptureDeviceDirty)
+	{
+		LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL;
+	
+		stream 
+		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetCaptureDevice.1\">"
+			<< "<CaptureDeviceSpecifier>" << mCaptureDevice << "</CaptureDeviceSpecifier>"
+		<< "</Request>"
+		<< "\n\n\n";
+		
+		mCaptureDeviceDirty = false;
+	}
+}
+
+void LLVoiceClient::buildSetRenderDevice(std::ostringstream &stream)
+{
+	if(mRenderDeviceDirty)
+	{
+		LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL;
+
+		stream
+		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetRenderDevice.1\">"
+			<< "<RenderDeviceSpecifier>" << mRenderDevice << "</RenderDeviceSpecifier>"
+		<< "</Request>"
+		<< "\n\n\n";
+		mRenderDeviceDirty = false;
+	}
+}
+
+void LLVoiceClient::buildLocalAudioUpdates(std::ostringstream &stream)
+{
+	buildSetCaptureDevice(stream);
+
+	buildSetRenderDevice(stream);
 
 	if(mPTTDirty)
 	{
+		mPTTDirty = false;
+
 		// 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)
@@ -2477,286 +3334,476 @@ void LLVoiceClient::sendPositionalUpdate(void)
 			<< "<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");
+		const char *muteval = ((mSpeakerVolume == 0)?"true":"false");
+
+		mSpeakerMuteDirty = 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";		
+			<< "</Request>\n\n\n";	
+		
 	}
 	
 	if(mSpeakerVolumeDirty)
 	{
+		mSpeakerVolumeDirty = false;
+
 		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";		
+			<< "</Request>\n\n\n";
+			
 	}
 	
 	if(mMicVolumeDirty)
 	{
+		mMicVolumeDirty = false;
+
 		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());
+			<< "</Request>\n\n\n";				
 	}
-}
 
-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)
+void LLVoiceClient::checkFriend(const LLUUID& id)
 {
-	LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL;
+	std::string name;
+	buddyListEntry *buddy = findBuddy(id);
 
-	stream
-	<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Aux.SetRenderDevice.1\">"
-		<< "<RenderDeviceSpecifier>" << mRenderDevice << "</RenderDeviceSpecifier>"
-	<< "</Request>"
-	<< "\n\n\n";
-}
+	// Make sure we don't add a name before it's been looked up.
+	if(gCacheName->getFullName(id, name))
+	{
 
-/////////////////////////////
-// Response/Event handlers
+		const LLRelationship* relationInfo = LLAvatarTracker::instance().getBuddyInfo(id);
+		bool canSeeMeOnline = false;
+		if(relationInfo && relationInfo->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS))
+			canSeeMeOnline = true;
+		
+		// When we get here, mNeedsSend is true and mInSLFriends is false.  Change them as necessary.
+		
+		if(buddy)
+		{
+			// This buddy is already in both lists.
 
-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);
+			if(name != buddy->mDisplayName)
+			{
+				// The buddy is in the list with the wrong name.  Update it with the correct name.
+				LL_WARNS("Voice") << "Buddy " << id << " has wrong name (\"" << buddy->mDisplayName << "\" should be \"" << name << "\"), updating."<< LL_ENDL;
+				buddy->mDisplayName = name;
+				buddy->mNeedsNameUpdate = true;		// This will cause the buddy to be resent.
+			}
+		}
+		else
+		{
+			// This buddy was not in the vivox list, needs to be added.
+			buddy = addBuddy(sipURIFromID(id), name);
+			buddy->mUUID = id;
+		}
+		
+		// In all the above cases, the buddy is in the SL friends list (which is how we got here).
+		buddy->mInSLFriends = true;
+		buddy->mCanSeeMeOnline = canSeeMeOnline;
+		buddy->mNameResolved = true;
+		
 	}
 	else
 	{
-		// Connector created, move forward.
-		mConnectorHandle = connectorHandle;
-		if(getState() == stateConnectorStarting)
+		// This name hasn't been looked up yet.  Don't do anything with this buddy list entry until it has.
+		if(buddy)
 		{
-			setState(stateConnectorStarted);
+			buddy->mNameResolved = false;
 		}
+		
+		// Initiate a lookup.
+		// The "lookup completed" callback will ensure that the friends list is rechecked after it completes.
+		lookupName(id);
 	}
 }
 
-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.
+void LLVoiceClient::clearAllLists()
+{
+	// FOR TESTING ONLY
 	
-	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
+	// This will send the necessary commands to delete ALL buddies, autoaccept rules, and block rules SLVoice tells us about.
+	buddyListMap::iterator buddy_it;
+	for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end();)
 	{
-		// Login succeeded, move forward.
-		mAccountHandle = accountHandle;
-		// MBW -- XXX -- This needs to wait until the LoginStateChangeEvent is received.
-//		if(getState() == stateLoggingIn)
-//		{
-//			setState(stateLoggedIn);
-//		}
-	}
-}
+		buddyListEntry *buddy = buddy_it->second;
+		buddy_it++;
+		
+		std::ostringstream stream;
 
-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;
+		if(buddy->mInVivoxBuddies)
+		{
+			// delete this entry from the vivox buddy list
+			buddy->mInVivoxBuddies = false;
+			LL_DEBUGS("Voice") << "delete " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL;
+			stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.BuddyDelete.1\">"
+				<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+				<< "<BuddyURI>" << buddy->mURI << "</BuddyURI>"
+				<< "</Request>\n\n\n";		
 		}
-		else
+
+		if(buddy->mHasBlockListEntry)
 		{
-			// We have a sip URL for this area.
-			LL_INFOS("Voice") << "mapped channel " << mChannelName << " to URI "<< uri << LL_ENDL;
+			// Delete the associated block list entry (so the block list doesn't fill up with junk)
+			buddy->mHasBlockListEntry = false;
+			stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteBlockRule.1\">"
+				<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+				<< "<BlockMask>" << buddy->mURI << "</BlockMask>"
+				<< "</Request>\n\n\n";								
 		}
-		
-		// switchChannel with an empty uri string will do the right thing (leave channel and not rejoin)
-		switchChannel(uri);
+		if(buddy->mHasAutoAcceptListEntry)
+		{
+			// Delete the associated auto-accept list entry (so the auto-accept list doesn't fill up with junk)
+			buddy->mHasAutoAcceptListEntry = false;
+			stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteAutoAcceptRule.1\">"
+				<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+				<< "<AutoAcceptMask>" << buddy->mURI << "</AutoAcceptMask>"
+				<< "</Request>\n\n\n";
+		}
+
+		writeString(stream.str());
+
 	}
 }
 
-void LLVoiceClient::sessionCreateResponse(int statusCode, std::string &statusString, std::string &sessionHandle)
-{	
-	if(statusCode != 0)
+void LLVoiceClient::sendFriendsListUpdates()
+{
+	if(mBuddyListMapPopulated && mBlockRulesListReceived && mAutoAcceptRulesListReceived && mFriendsListDirty)
 	{
-		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
+		mFriendsListDirty = false;
+		
+		if(0)
 		{
-			mVivoxErrorStatusCode = statusCode;		
-			mVivoxErrorStatusString = statusString;
-			setState(stateJoinSessionFailed);
+			// FOR TESTING ONLY -- clear all buddy list, block list, and auto-accept list entries.
+			clearAllLists();
+			return;
 		}
-	}
-	else
-	{
-		LL_DEBUGS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL;
-		if(getState() == stateJoiningSession)
+		
+		LL_INFOS("Voice") << "Checking vivox buddy list against friends list..." << LL_ENDL;
+		
+		buddyListMap::iterator buddy_it;
+		for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++)
 		{
-			// This is also grabbed in the SessionStateChangeEvent handler, but it might be useful to have it early...
-			mSessionHandle = sessionHandle;
+			// reset the temp flags in the local buddy list
+			buddy_it->second->mInSLFriends = false;
 		}
-		else
+		
+		// correlate with the friends list
+		{
+			LLCollectAllBuddies collect;
+			LLAvatarTracker::instance().applyFunctor(collect);
+			LLCollectAllBuddies::buddy_map_t::const_iterator it = collect.mOnline.begin();
+			LLCollectAllBuddies::buddy_map_t::const_iterator end = collect.mOnline.end();
+			
+			for ( ; it != end; ++it)
+			{
+				checkFriend(it->second);
+			}
+			it = collect.mOffline.begin();
+			end = collect.mOffline.end();
+			for ( ; it != end; ++it)
+			{
+				checkFriend(it->second);
+			}
+		}
+				
+		LL_INFOS("Voice") << "Sending friend list updates..." << LL_ENDL;
+
+		for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end();)
 		{
-			// We should never get a session.create response in any state except stateJoiningSession.  Things are out of sync.  Kill this session.
-			sessionTerminateByHandle(sessionHandle);
+			buddyListEntry *buddy = buddy_it->second;
+			buddy_it++;
+			
+			// Ignore entries that aren't resolved yet.
+			if(buddy->mNameResolved)
+			{
+				std::ostringstream stream;
+
+				if(buddy->mInSLFriends && (!buddy->mInVivoxBuddies || buddy->mNeedsNameUpdate))
+				{					
+					if(mNumberOfAliases > 0)
+					{
+						// Add (or update) this entry in the vivox buddy list
+						buddy->mInVivoxBuddies = true;
+						buddy->mNeedsNameUpdate = false;
+						LL_DEBUGS("Voice") << "add/update " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL;
+						stream 
+							<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.BuddySet.1\">"
+								<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+								<< "<BuddyURI>" << buddy->mURI << "</BuddyURI>"
+								<< "<DisplayName>" << buddy->mDisplayName << "</DisplayName>"
+								<< "<BuddyData></BuddyData>"	// Without this, SLVoice doesn't seem to parse the command.
+								<< "<GroupID>0</GroupID>"
+							<< "</Request>\n\n\n";	
+					}
+				}
+				else if(!buddy->mInSLFriends)
+				{
+					// This entry no longer exists in your SL friends list.  Remove all traces of it from the Vivox buddy list.
+ 					if(buddy->mInVivoxBuddies)
+					{
+						// delete this entry from the vivox buddy list
+						buddy->mInVivoxBuddies = false;
+						LL_DEBUGS("Voice") << "delete " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL;
+						stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.BuddyDelete.1\">"
+							<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+							<< "<BuddyURI>" << buddy->mURI << "</BuddyURI>"
+							<< "</Request>\n\n\n";		
+					}
+
+					if(buddy->mHasBlockListEntry)
+					{
+						// Delete the associated block list entry, if any
+						buddy->mHasBlockListEntry = false;
+						stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteBlockRule.1\">"
+							<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+							<< "<BlockMask>" << buddy->mURI << "</BlockMask>"
+							<< "</Request>\n\n\n";								
+					}
+					if(buddy->mHasAutoAcceptListEntry)
+					{
+						// Delete the associated auto-accept list entry, if any
+						buddy->mHasAutoAcceptListEntry = false;
+						stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteAutoAcceptRule.1\">"
+							<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+							<< "<AutoAcceptMask>" << buddy->mURI << "</AutoAcceptMask>"
+							<< "</Request>\n\n\n";
+					}
+				}
+				
+				if(buddy->mInSLFriends)
+				{
+
+					if(buddy->mCanSeeMeOnline)
+					{
+						// Buddy should not be blocked.
+
+						// If this buddy doesn't already have either a block or autoaccept list entry, we'll update their status when we receive a SubscriptionEvent.
+						
+						// If the buddy has a block list entry, delete it.
+						if(buddy->mHasBlockListEntry)
+						{
+							buddy->mHasBlockListEntry = false;
+							stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteBlockRule.1\">"
+								<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+								<< "<BlockMask>" << buddy->mURI << "</BlockMask>"
+								<< "</Request>\n\n\n";		
+							
+							
+							// If we just deleted a block list entry, add an auto-accept entry.
+							if(!buddy->mHasAutoAcceptListEntry)
+							{
+								buddy->mHasAutoAcceptListEntry = true;								
+								stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.CreateAutoAcceptRule.1\">"
+									<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+									<< "<AutoAcceptMask>" << buddy->mURI << "</AutoAcceptMask>"
+									<< "<AutoAddAsBuddy>0</AutoAddAsBuddy>"
+									<< "</Request>\n\n\n";
+							}
+						}
+					}
+					else
+					{
+						// Buddy should be blocked.
+						
+						// If this buddy doesn't already have either a block or autoaccept list entry, we'll update their status when we receive a SubscriptionEvent.
+
+						// If this buddy has an autoaccept entry, delete it
+						if(buddy->mHasAutoAcceptListEntry)
+						{
+							buddy->mHasAutoAcceptListEntry = false;
+							stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.DeleteAutoAcceptRule.1\">"
+								<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+								<< "<AutoAcceptMask>" << buddy->mURI << "</AutoAcceptMask>"
+								<< "</Request>\n\n\n";
+						
+							// If we just deleted an auto-accept entry, add a block list entry.
+							if(!buddy->mHasBlockListEntry)
+							{
+								buddy->mHasBlockListEntry = true;
+								stream << "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.CreateBlockRule.1\">"
+									<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+									<< "<BlockMask>" << buddy->mURI << "</BlockMask>"
+									<< "<PresenceOnly>1</PresenceOnly>"
+									<< "</Request>\n\n\n";								
+							}
+						}
+					}
+
+					if(!buddy->mInSLFriends && !buddy->mInVivoxBuddies)
+					{
+						// Delete this entry from the local buddy list.  This should NOT invalidate the iterator,
+						// since it has already been incremented to the next entry.
+						deleteBuddy(buddy->mURI);
+					}
+
+				}
+				writeString(stream.str());
+			}
 		}
 	}
 }
 
-void LLVoiceClient::sessionConnectResponse(int statusCode, std::string &statusString)
-{
+/////////////////////////////
+// Response/Event handlers
+
+void LLVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID)
+{	
 	if(statusCode != 0)
 	{
-		LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL;
-//		if(statusCode == 1015)
+		LL_WARNS("Voice") << "Connector.Create response failure: " << statusString << LL_ENDL;
+		setState(stateConnectorFailed);
+	}
+	else
+	{
+		// Connector created, move forward.
+		LL_INFOS("Voice") << "Connector.Create succeeded, Vivox SDK version is " << versionID << LL_ENDL;
+		mConnectorHandle = connectorHandle;
+		if(getState() == stateConnectorStarting)
+		{
+			setState(stateConnectorStarted);
+		}
+	}
+}
+
+void LLVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases)
+{ 
+	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;
+		mNumberOfAliases = numberOfAliases;
+		// This needs to wait until the AccountLoginStateChangeEvent is received.
+//		if(getState() == stateLoggingIn)
 //		{
-//			LL_WARNS("Voice") << "terminating existing session" << LL_ENDL;
-//			sessionTerminate();
+//			setState(stateLoggedIn);
 //		}
-//		else
+	}
+}
+
+void LLVoiceClient::sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle)
+{	
+	sessionState *session = findSessionBeingCreatedByURI(requestId);
+	
+	if(session)
+	{
+		session->mCreateInProgress = false;
+	}
+	
+	if(statusCode != 0)
+	{
+		LL_WARNS("Voice") << "Session.Create response failure (" << statusCode << "): " << statusString << LL_ENDL;
+		if(session)
 		{
-			mVivoxErrorStatusCode = statusCode;		
-			mVivoxErrorStatusString = statusString;
-			setState(stateJoinSessionFailed);
+			session->mErrorStatusCode = statusCode;		
+			session->mErrorStatusString = statusString;
+			if(session == mAudioSession)
+			{
+				setState(stateJoinSessionFailed);
+			}
+			else
+			{
+				reapSession(session);
+			}
 		}
 	}
 	else
 	{
-		LL_DEBUGS("Voice") << "Session.Connect response received (success)" << LL_ENDL;
+		LL_INFOS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL;
+		if(session)
+		{
+			setSessionHandle(session, sessionHandle);
+		}
 	}
 }
 
-void LLVoiceClient::sessionTerminateResponse(int statusCode, std::string &statusString)
+void LLVoiceClient::sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle)
 {	
+	sessionState *session = findSessionBeingCreatedByURI(requestId);
+	
+	if(session)
+	{
+		session->mCreateInProgress = false;
+	}
+	
+	if(statusCode != 0)
+	{
+		LL_WARNS("Voice") << "SessionGroup.AddSession response failure (" << statusCode << "): " << statusString << LL_ENDL;
+		if(session)
+		{
+			session->mErrorStatusCode = statusCode;		
+			session->mErrorStatusString = statusString;
+			if(session == mAudioSession)
+			{
+				setState(stateJoinSessionFailed);
+			}
+			else
+			{
+				reapSession(session);
+			}
+		}
+	}
+	else
+	{
+		LL_DEBUGS("Voice") << "SessionGroup.AddSession response received (success), session handle is " << sessionHandle << LL_ENDL;
+		if(session)
+		{
+			setSessionHandle(session, sessionHandle);
+		}
+	}
+}
+
+void LLVoiceClient::sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString)
+{
+	sessionState *session = findSession(requestId);
 	if(statusCode != 0)
 	{
-		LL_WARNS("Voice") << "Session.Terminate response failure: (" << statusCode << "): " << statusString << LL_ENDL;
-		if(getState() == stateLeavingSession)
+		LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL;
+		if(session)
 		{
-			// This is probably "(404): Server reporting Failure. Not a member of this conference."
-			// Do this so we don't get stuck.
-			setState(stateSessionTerminated);
+			session->mMediaConnectInProgress = false;
+			session->mErrorStatusCode = statusCode;		
+			session->mErrorStatusString = statusString;
+			if(session == mAudioSession)
+				setState(stateJoinSessionFailed);
 		}
 	}
-	
+	else
+	{
+		LL_DEBUGS("Voice") << "Session.Connect response received (success)" << LL_ENDL;
+	}
 }
 
 void LLVoiceClient::logoutResponse(int statusCode, std::string &statusString)
@@ -2764,7 +3811,7 @@ 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?
+		// Should this ever fail?  do we care if it does?
 	}
 	
 	if(getState() == stateLoggingOut)
@@ -2778,7 +3825,7 @@ void LLVoiceClient::connectorShutdownResponse(int statusCode, std::string &statu
 	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?
+		// Should this ever fail?  do we care if it does?
 	}
 	
 	mConnected = false;
@@ -2789,448 +3836,1124 @@ void LLVoiceClient::connectorShutdownResponse(int statusCode, std::string &statu
 	}
 }
 
-void LLVoiceClient::sessionStateChangeEvent(
+void LLVoiceClient::sessionAddedEvent(
 		std::string &uriString, 
-		int statusCode, 
-		std::string &statusString, 
-		std::string &sessionHandle,
-		int state,  
+		std::string &alias, 
+		std::string &sessionHandle, 
+		std::string &sessionGroupHandle, 
 		bool isChannel, 
-		std::string &nameString)
+		bool incoming,
+		std::string &nameString,
+		std::string &applicationString)
 {
-	switch(state)
-	{
-		case 4:	// I see this when joining the session
-			LL_INFOS("Voice") << "joined session " << uriString << ", name " << nameString << " handle " << mNextSessionHandle << LL_ENDL;
+	sessionState *session = NULL;
 
-			// Session create succeeded, move forward.
-			mSessionStateEventHandle = sessionHandle;
-			mSessionStateEventURI = uriString;
-			if(sessionHandle == mSessionHandle)
+	LL_INFOS("Voice") << "session " << uriString << ", alias " << alias << ", name " << nameString << " handle " << sessionHandle << LL_ENDL;
+	
+	session = addSession(uriString, sessionHandle);
+	if(session)
+	{
+		session->mGroupHandle = sessionGroupHandle;
+		session->mIsChannel = isChannel;
+		session->mIncoming = incoming;
+		session->mAlias = alias;
+			
+		// Generate a caller UUID -- don't need to do this for channels
+		if(!session->mIsChannel)
+		{
+			if(IDFromName(session->mSIPURI, session->mCallerID))
 			{
-				// 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;
-				}
+				// Normal URI(base64-encoded UUID) 
 			}
-			else if(sessionHandle == mNextSessionHandle)
+			else if(!session->mAlias.empty() && IDFromName(session->mAlias, session->mCallerID))
 			{
-//				LL_DEBUGS("Voice") << "received URI " << uriString << ", name " << nameString << " for next session (handle " << mNextSessionHandle << ")" << LL_ENDL;
+				// Wrong URI, but an alias is available.  Stash the incoming URI as an alternate
+				session->mAlternateSIPURI = session->mSIPURI;
+				
+				// and generate a proper URI from the ID.
+				setSessionURI(session, sipURIFromID(session->mCallerID));
 			}
 			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();
+				LL_INFOS("Voice") << "Could not generate caller id from uri, using hash of uri " << session->mSIPURI << LL_ENDL;
+				setUUIDFromStringHash(session->mCallerID, session->mSIPURI);
+				session->mSynthesizedCallerID = true;
 				
-				switch(getState())
+				// Can't look up the name in this case -- we have to extract it from the URI.
+				std::string namePortion = nameFromsipURI(session->mSIPURI);
+				if(namePortion.empty())
 				{
-					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;
+					// Didn't seem to be a SIP URI, just use the whole provided name.
+					namePortion = nameString;
 				}
-
-				// store status values for later notification of observers
-				mVivoxErrorStatusCode = statusCode;		
-				mVivoxErrorStatusString = statusString;
+				
+				// Some incoming names may be separated with an underscore instead of a space.  Fix this.
+				LLStringUtil::replaceChar(namePortion, '_', ' ');
+				
+				// Act like we just finished resolving the name (this stores it in all the right places)
+				avatarNameResolved(session->mCallerID, namePortion);
 			}
-			else
+		
+			LL_INFOS("Voice") << "caller ID: " << session->mCallerID << LL_ENDL;
+
+			if(!session->mSynthesizedCallerID)
 			{
-				LL_INFOS("Voice") << "leaving unknown session handle " << sessionHandle << ", URI " << uriString << ", name " << nameString << LL_ENDL;
+				// If we got here, we don't have a proper name.  Initiate a lookup.
+				lookupName(session->mCallerID);
 			}
-
-			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)
+void LLVoiceClient::sessionGroupAddedEvent(std::string &sessionGroupHandle)
 {
-	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	
-	*/
+	LL_DEBUGS("Voice") << "handle " << sessionGroupHandle << LL_ENDL;
 	
-	switch(state)
+#if USE_SESSION_GROUPS
+	if(mMainSessionGroupHandle.empty())
 	{
-		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;
+		// This is the first (i.e. "main") session group.  Save its handle.
+		mMainSessionGroupHandle = sessionGroupHandle;
+	}
+	else
+	{
+		LL_DEBUGS("Voice") << "Already had a session group handle " << mMainSessionGroupHandle << LL_ENDL;
 	}
+#endif
 }
 
-void LLVoiceClient::sessionNewEvent(
-		std::string &accountHandle, 
-		std::string &eventSessionHandle, 
-		int state, 
-		std::string &nameString, 
-		std::string &uriString)
+void LLVoiceClient::joinedAudioSession(sessionState *session)
 {
-	LL_DEBUGS("Voice") << "state is " << state << LL_ENDL;
+	if(mAudioSession != session)
+	{
+		sessionState *oldSession = mAudioSession;
+
+		mAudioSession = session;
+		mAudioSessionChanged = true;
+
+		// The old session may now need to be deleted.
+		reapSession(oldSession);
+	}
 	
-	switch(state)
+	// This is the session we're joining.
+	if(getState() == stateJoiningSession)
 	{
-		case 0:
-			{
-				LL_DEBUGS("Voice") << "session handle = " << eventSessionHandle << ", name = " << nameString << ", uri = " << uriString << LL_ENDL;
+		setState(stateSessionJoined);
+		
+		// SLIM SDK: we don't always receive a participant state change for ourselves when joining a channel now.
+		// Add the current user as a participant here.
+		participantState *participant = session->addParticipant(sipURIFromName(mAccountName));
+		if(participant)
+		{
+			participant->mIsSelf = true;
+			lookupName(participant->mAvatarID);
 
-				LLUUID caller_id;
-				if(IDFromName(nameString, caller_id))
+			LL_INFOS("Voice") << "added self as participant \"" << participant->mAccountName 
+					<< "\" (" << participant->mAvatarID << ")"<< LL_ENDL;
+		}
+		
+		if(!session->mIsChannel)
+		{
+			// this is a p2p session.  Make sure the other end is added as a participant.
+			participantState *participant = session->addParticipant(session->mSIPURI);
+			if(participant)
+			{
+				if(participant->mAvatarIDValid)
 				{
-					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);
+					lookupName(participant->mAvatarID);
 				}
-				else
+				else if(!session->mName.empty())
 				{
-					LL_WARNS("Voice") << "Could not generate caller id from uri " << uriString << LL_ENDL;
+					participant->mDisplayName = session->mName;
+					avatarNameResolved(participant->mAvatarID, session->mName);
 				}
+				
+				// TODO: Question: Do we need to set up mAvatarID/mAvatarIDValid here?
+				LL_INFOS("Voice") << "added caller as participant \"" << participant->mAccountName 
+						<< "\" (" << participant->mAvatarID << ")"<< 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)
+void LLVoiceClient::sessionRemovedEvent(
+	std::string &sessionHandle, 
+	std::string &sessionGroupHandle)
 {
-	participantState *participant = NULL;
-	LL_DEBUGS("Voice") << "state is " << state << LL_ENDL;
+	LL_INFOS("Voice") << "handle " << sessionHandle << LL_ENDL;
+	
+	sessionState *session = findSession(sessionHandle);
+	if(session)
+	{
+		leftAudioSession(session);
 
-	switch(state)
+		// This message invalidates the session's handle.  Set it to empty.
+		setSessionHandle(session);
+		
+		// Reset the media state (we now have no info)
+		session->mMediaStreamState = streamStateUnknown;
+		session->mTextStreamState = streamStateUnknown;
+		
+		// Conditionally delete the session
+		reapSession(session);
+	}
+	else
 	{
-		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;
+		LL_WARNS("Voice") << "unknown session " << sessionHandle << " removed" << LL_ENDL;
 	}
 }
 
-void LLVoiceClient::participantPropertiesEvent(
-		std::string &uriString, 
-		int statusCode, 
-		std::string &statusString, 
-		bool isLocallyMuted, 
-		bool isModeratorMuted, 
-		bool isSpeaking, 
-		int volume, 
-		F32 energy)
+void LLVoiceClient::reapSession(sessionState *session)
 {
-	participantState *participant = findParticipant(uriString);
-	if(participant)
+	if(session)
 	{
-		participant->mPTT = !isLocallyMuted;
-		participant->mIsSpeaking = isSpeaking;
-		participant->mIsModeratorMuted = isModeratorMuted;
-		if (isSpeaking)
+		if(!session->mHandle.empty())
+		{
+			LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (non-null session handle)" << LL_ENDL;
+		}
+		else if(session->mCreateInProgress)
+		{
+			LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (create in progress)" << LL_ENDL;
+		}
+		else if(session->mMediaConnectInProgress)
+		{
+			LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (connect in progress)" << LL_ENDL;
+		}
+		else if(session == mAudioSession)
+		{
+			LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the current session)" << LL_ENDL;
+		}
+		else if(session == mNextAudioSession)
 		{
-			participant->mSpeakingTimeout.reset();
+			LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the next session)" << LL_ENDL;
 		}
-		participant->mPower = energy;
-		participant->mVolume = volume;
+		else
+		{
+			// TODO: Question: Should we check for queued text messages here?
+			// We don't have a reason to keep tracking this session, so just delete it.
+			LL_DEBUGS("Voice") << "deleting session " << session->mSIPURI << LL_ENDL;
+			deleteSession(session);
+			session = NULL;
+		}	
 	}
 	else
 	{
-		LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL;
+//		LL_DEBUGS("Voice") << "session is NULL" << LL_ENDL;
 	}
 }
 
-void LLVoiceClient::auxAudioPropertiesEvent(F32 energy)
-{
-	LL_DEBUGS("Voice") << "got energy " << energy << LL_ENDL;
-	mTuningEnergy = energy;
-}
-
-void LLVoiceClient::muteListChanged()
+// Returns true if the session seems to indicate we've moved to a region on a different voice server
+bool LLVoiceClient::sessionNeedsRelog(sessionState *session)
 {
-	// 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();
+	bool result = false;
 	
-	for(; iter != mParticipantMap.end(); iter++)
+	if(session != NULL)
 	{
-		participantState *p = iter->second;
-		
-		// Check to see if this participant is on the mute list already
-		updateMuteState(p);
+		// Only make this check for spatial channels (so it won't happen for group or p2p calls)
+		if(session->mIsSpatial)
+		{
+			std::string::size_type atsign;
+			
+			atsign = session->mSIPURI.find("@");
+			
+			if(atsign != std::string::npos)
+			{
+				std::string urihost = session->mSIPURI.substr(atsign + 1);
+				if(stricmp(urihost.c_str(), mVoiceSIPURIHostName.c_str()))
+				{
+					// The hostname in this URI is different from what we expect.  This probably means we need to relog.
+					
+					// We could make a ProvisionVoiceAccountRequest and compare the result with the current values of
+					// mVoiceSIPURIHostName and mVoiceAccountServerURI to be really sure, but this is a pretty good indicator.
+					
+					result = true;
+				}
+			}
+		}
 	}
+	
+	return result;
 }
 
-/////////////////////////////
-// 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)
+void LLVoiceClient::leftAudioSession(
+	sessionState *session)
 {
+	if(mAudioSession == session)
+	{
+		switch(getState())
+		{
+			case stateJoiningSession:
+			case stateSessionJoined:
+			case stateRunning:
+			case stateLeavingSession:
+			case stateJoinSessionFailed:
+			case stateJoinSessionFailedWaiting:
+				// normal transition
+				LL_DEBUGS("Voice") << "left session " << session->mHandle << " 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 " << session->mHandle << " in state " << state2string(getState()) << LL_ENDL;
+			break;
+			
+			default:
+				LL_WARNS("Voice") << "unexpected SessionStateChangeEvent (left session) in state " << state2string(getState()) << LL_ENDL;
+				setState(stateSessionTerminated);
+			break;
+		}
+	}
 }
 
-LLVoiceClient::participantState *LLVoiceClient::addParticipant(const std::string &uri)
+void LLVoiceClient::accountLoginStateChangeEvent(
+		std::string &accountHandle, 
+		int statusCode, 
+		std::string &statusString, 
+		int state)
 {
-	participantState *result = NULL;
-
-	participantMap::iterator iter = mParticipantMap.find(uri);
+	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	
+	*/
 	
-	if(iter != mParticipantMap.end())
+	switch(state)
 	{
-		// Found a matching participant already in the map.
-		result = iter->second;
+		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;
 	}
+}
 
-	if(!result)
+void LLVoiceClient::mediaStreamUpdatedEvent(
+	std::string &sessionHandle, 
+	std::string &sessionGroupHandle, 
+	int statusCode, 
+	std::string &statusString, 
+	int state, 
+	bool incoming)
+{
+	sessionState *session = findSession(sessionHandle);
+	
+	LL_DEBUGS("Voice") << "session " << sessionHandle << ", status code " << statusCode << ", string \"" << statusString << "\"" << LL_ENDL;
+	
+	if(session)
 	{
-		// participant isn't already in one list or the other.
-		result = new participantState(uri);
-		mParticipantMap.insert(participantMap::value_type(uri, result));
-		mParticipantMapChanged = true;
+		// We know about this session
 		
-		// Try to do a reverse transform on the URI to get the GUID back.
+		// Save the state for later use
+		session->mMediaStreamState = state;
+		
+		switch(statusCode)
 		{
-			LLUUID id;
-			if(IDFromName(uri, id))
-			{
-				result->mAvatarIDValid = true;
-				result->mAvatarID = id;
+			case 0:
+			case 200:
+				// generic success
+				// Don't change the saved error code (it may have been set elsewhere)
+			break;
+			default:
+				// save the status code for later
+				session->mErrorStatusCode = statusCode;
+			break;
+		}
+		
+		switch(state)
+		{
+			case streamStateIdle:
+				// Standard "left audio session"
+				session->mVoiceEnabled = false;
+				session->mMediaConnectInProgress = false;
+				leftAudioSession(session);
+			break;
 
-				updateMuteState(result);
-			}
+			case streamStateConnected:
+				session->mVoiceEnabled = true;
+				session->mMediaConnectInProgress = false;
+				joinedAudioSession(session);
+			break;
+			
+			case streamStateRinging:
+				if(incoming)
+				{
+					// Send the voice chat invite to the GUI layer
+					// TODO: Question: Should we correlate with the mute list here?
+					session->mIMSessionID = LLIMMgr::computeSessionID(IM_SESSION_P2P_INVITE, session->mCallerID);
+					session->mVoiceInvitePending = true;
+					if(session->mName.empty())
+					{
+						lookupName(session->mCallerID);
+					}
+					else
+					{
+						// Act like we just finished resolving the name
+						avatarNameResolved(session->mCallerID, session->mName);
+					}
+				}
+			break;
+			
+			default:
+				LL_WARNS("Voice") << "unknown state " << state << LL_ENDL;
+			break;
+			
 		}
 		
-		LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL;
 	}
-	
-	return result;
+	else
+	{
+		LL_WARNS("Voice") << "session " << sessionHandle << "not found"<< LL_ENDL;
+	}
 }
 
-void LLVoiceClient::updateMuteState(participantState *p)
+void LLVoiceClient::textStreamUpdatedEvent(
+	std::string &sessionHandle, 
+	std::string &sessionGroupHandle, 
+	bool enabled,
+	int state, 
+	bool incoming)
 {
-	if(p->mAvatarIDValid)
+	sessionState *session = findSession(sessionHandle);
+	
+	if(session)
 	{
-		bool isMuted = LLMuteList::getInstance()->isMuted(p->mAvatarID, LLMute::flagVoiceChat);
-		if(p->mOnMuteList != isMuted)
+		// Save the state for later use
+		session->mTextStreamState = state;
+		
+		// We know about this session
+		switch(state)
 		{
-			p->mOnMuteList = isMuted;
-			p->mVolumeDirty = true;
-			mVolumeDirty = true;
+			case 0:	// We see this when the text stream closes
+				LL_DEBUGS("Voice") << "stream closed" << LL_ENDL;
+			break;
+			
+			case 1:	// We see this on an incoming call from the Connector
+				// Try to send any text messages queued for this session.
+				sendQueuedTextMessages(session);
+
+				// Send the text chat invite to the GUI layer
+				// TODO: Question: Should we correlate with the mute list here?
+				session->mTextInvitePending = true;
+				if(session->mName.empty())
+				{
+					lookupName(session->mCallerID);
+				}
+				else
+				{
+					// Act like we just finished resolving the name
+					avatarNameResolved(session->mCallerID, session->mName);
+				}
+			break;
+
+			default:
+				LL_WARNS("Voice") << "unknown state " << state << LL_ENDL;
+			break;
+			
 		}
 	}
 }
 
-void LLVoiceClient::removeParticipant(LLVoiceClient::participantState *participant)
+void LLVoiceClient::participantAddedEvent(
+		std::string &sessionHandle, 
+		std::string &sessionGroupHandle, 
+		std::string &uriString, 
+		std::string &alias, 
+		std::string &nameString, 
+		std::string &displayNameString, 
+		int participantType)
 {
-	if(participant)
+	sessionState *session = findSession(sessionHandle);
+	if(session)
 	{
-		participantMap::iterator iter = mParticipantMap.find(participant->mURI);
-				
-		LL_DEBUGS("Voice") << "participant \"" << participant->mURI <<  "\" (" << participant->mAvatarID << ") removed." << LL_ENDL;
+		participantState *participant = session->addParticipant(uriString);
+		if(participant)
+		{
+			participant->mAccountName = nameString;
+
+			LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName 
+					<< "\" (" << participant->mAvatarID << ")"<< LL_ENDL;
 
-		mParticipantMap.erase(iter);
-		delete participant;
-		mParticipantMapChanged = true;
+			if(participant->mAvatarIDValid)
+			{
+				// Initiate a lookup
+				lookupName(participant->mAvatarID);
+			}
+			else
+			{
+				// If we don't have a valid avatar UUID, we need to fill in the display name to make the active speakers floater work.
+				std::string namePortion = nameFromsipURI(uriString);
+				if(namePortion.empty())
+				{
+					// Problem with the SIP URI, fall back to the display name
+					namePortion = displayNameString;
+				}
+				if(namePortion.empty())
+				{
+					// Problems with both of the above, fall back to the account name
+					namePortion = nameString;
+				}
+				
+				// Set the display name (which is a hint to the active speakers window not to do its own lookup)
+				participant->mDisplayName = namePortion;
+				avatarNameResolved(participant->mAvatarID, namePortion);
+			}
+		}
 	}
 }
 
-void LLVoiceClient::removeAllParticipants()
+void LLVoiceClient::participantRemovedEvent(
+		std::string &sessionHandle, 
+		std::string &sessionGroupHandle, 
+		std::string &uriString, 
+		std::string &alias, 
+		std::string &nameString)
 {
-	LL_DEBUGS("Voice") << "called" << LL_ENDL;
-
-	while(!mParticipantMap.empty())
+	sessionState *session = findSession(sessionHandle);
+	if(session)
+	{
+		participantState *participant = session->findParticipant(uriString);
+		if(participant)
+		{
+			session->removeParticipant(participant);
+		}
+		else
+		{
+			LL_DEBUGS("Voice") << "unknown participant " << uriString << LL_ENDL;
+		}
+	}
+	else
 	{
-		removeParticipant(mParticipantMap.begin()->second);
+		LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL;
 	}
 }
 
-LLVoiceClient::participantMap *LLVoiceClient::getParticipantList(void)
-{
-	return &mParticipantMap;
-}
-
 
-LLVoiceClient::participantState *LLVoiceClient::findParticipant(const std::string &uri)
+void LLVoiceClient::participantUpdatedEvent(
+		std::string &sessionHandle, 
+		std::string &sessionGroupHandle, 
+		std::string &uriString, 
+		std::string &alias, 
+		bool isModeratorMuted, 
+		bool isSpeaking, 
+		int volume, 
+		F32 energy)
 {
-	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)
+	sessionState *session = findSession(sessionHandle);
+	if(session)
 	{
-		participantMap::iterator iter = mParticipantMap.find(uri);
-	
-		if(iter != mParticipantMap.end())
+		participantState *participant = session->findParticipant(uriString);
+		
+		if(participant)
 		{
-			result = iter->second;
+			participant->mIsSpeaking = isSpeaking;
+			participant->mIsModeratorMuted = isModeratorMuted;
+
+			// SLIM SDK: convert range: ensure that energy is set to zero if is_speaking is false
+			if (isSpeaking)
+			{
+				participant->mSpeakingTimeout.reset();
+				participant->mPower = energy;
+			}
+			else
+			{
+				participant->mPower = 0.0f;
+			}
+			participant->mVolume = volume;
+		}
+		else
+		{
+			LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL;
 		}
 	}
-			
-	return result;
+	else
+	{
+		LL_INFOS("Voice") << "unknown session " << sessionHandle << LL_ENDL;
+	}
 }
 
-
-LLVoiceClient::participantState *LLVoiceClient::findParticipantByAvatar(LLVOAvatar *avatar)
+void LLVoiceClient::buddyPresenceEvent(
+		std::string &uriString, 
+		std::string &alias, 
+		std::string &statusString,
+		std::string &applicationString)
 {
-	participantState * result = NULL;
-
-	// You'd think this would work, but it doesn't...
-//	std::string uri = sipURIFromAvatar(avatar);
+	buddyListEntry *buddy = findBuddy(uriString);
 	
-	// Currently, the URI is just the account name.
-	std::string loginName = nameFromAvatar(avatar);
-	result = findParticipant(loginName);
-	
-	if(result != NULL)
+	if(buddy)
 	{
-		if(!result->mAvatarIDValid)
+		LL_DEBUGS("Voice") << "Presence event for " << buddy->mDisplayName << " status \"" << statusString << "\", application \"" << applicationString << "\""<< LL_ENDL;
+		LL_DEBUGS("Voice") << "before: mOnlineSL = " << (buddy->mOnlineSL?"true":"false") << ", mOnlineSLim = " << (buddy->mOnlineSLim?"true":"false") << LL_ENDL;
+
+		if(applicationString.empty())
 		{
-			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);
+			// This presence event is from a client that doesn't set up the Application string.  Do things the old-skool way.
+			// NOTE: this will be needed to support people who aren't on the 3010-class SDK yet.
+
+			if ( stricmp("Unknown", statusString.c_str())== 0) 
+			{
+				// User went offline with a non-SLim-enabled viewer.
+				buddy->mOnlineSL = false;
+			}
+			else if ( stricmp("Online", statusString.c_str())== 0) 
+			{
+				// User came online with a non-SLim-enabled viewer.
+				buddy->mOnlineSL = true;
+			}
+			else
+			{
+				// If the user is online through SLim, their status will be "Online-slc", "Away", or something else.
+				// NOTE: we should never see this unless someone is running an OLD version of SLim -- the versions that should be in use now all set the application string.
+				buddy->mOnlineSLim = true;
+			} 
+		}
+		else if(applicationString.find("SecondLifeViewer") != std::string::npos)
+		{
+			// This presence event is from a viewer that sets the application string
+			if ( stricmp("Unknown", statusString.c_str())== 0) 
+			{
+				// Viewer says they're offline
+				buddy->mOnlineSL = false;
+			}
+			else
+			{
+				// Viewer says they're online
+				buddy->mOnlineSL = true;
+			}
 		}
+		else
+		{
+			// This presence event is from something which is NOT the SL viewer (assume it's SLim).
+			if ( stricmp("Unknown", statusString.c_str())== 0) 
+			{
+				// SLim says they're offline
+				buddy->mOnlineSLim = false;
+			}
+			else
+			{
+				// SLim says they're online
+				buddy->mOnlineSLim = true;
+			}
+		} 
+
+		LL_DEBUGS("Voice") << "after: mOnlineSL = " << (buddy->mOnlineSL?"true":"false") << ", mOnlineSLim = " << (buddy->mOnlineSLim?"true":"false") << LL_ENDL;
 		
+		// HACK -- increment the internal change serial number in the LLRelationship (without changing the actual status), so the UI notices the change.
+		LLAvatarTracker::instance().setBuddyOnline(buddy->mUUID,LLAvatarTracker::instance().isBuddyOnline(buddy->mUUID));
 
+		notifyFriendObservers();
 	}
-
-	return result;
+	else
+	{
+		LL_DEBUGS("Voice") << "Presence for unknown buddy " << uriString << LL_ENDL;
+	}	
 }
 
-LLVoiceClient::participantState* LLVoiceClient::findParticipantByID(const LLUUID& id)
+void LLVoiceClient::messageEvent(
+		std::string &sessionHandle, 
+		std::string &uriString, 
+		std::string &alias, 
+		std::string &messageHeader, 
+		std::string &messageBody,
+		std::string &applicationString)
 {
-	participantState * result = NULL;
+	LL_DEBUGS("Voice") << "Message event, session " << sessionHandle << " from " << uriString << LL_ENDL;
+//	LL_DEBUGS("Voice") << "    header " << messageHeader << ", body: \n" << messageBody << LL_ENDL;
+	
+	if(messageHeader.find("text/html") != std::string::npos)
+	{
+		std::string rawMessage;
 
-	// Currently, the URI is just the account name.
-	std::string loginName = nameFromID(id);
-	result = findParticipant(loginName);
+		{
+			const std::string startMarker = "<body";
+			const std::string startMarker2 = ">";
+			const std::string endMarker = "</body>";
+			const std::string startSpan = "<span";
+			const std::string endSpan = "</span>";
+			std::string::size_type start;
+			std::string::size_type end;
+			
+			// Default to displaying the raw string, so the message gets through.
+			rawMessage = messageBody;
 
-	return result;
-}
+			// Find the actual message text within the XML fragment
+			start = messageBody.find(startMarker);
+			start = messageBody.find(startMarker2, start);
+			end = messageBody.find(endMarker);
 
+			if(start != std::string::npos)
+			{
+				start += startMarker2.size();
+				
+				if(end != std::string::npos)
+					end -= start;
+					
+				rawMessage.assign(messageBody, start, end);
+			}
+			else 
+			{
+				// Didn't find a <body>, try looking for a <span> instead.
+				start = messageBody.find(startSpan);
+				start = messageBody.find(startMarker2, start);
+				end = messageBody.find(endSpan);
+				
+				if(start != std::string::npos)
+				{
+					start += startMarker2.size();
+					
+					if(end != std::string::npos)
+						end -= start;
+					
+					rawMessage.assign(messageBody, start, end);
+				}			
+			}
+		}	
+		
+//		LL_DEBUGS("Voice") << "    raw message = \n" << rawMessage << LL_ENDL;
+
+		// strip formatting tags
+		{
+			std::string::size_type start;
+			std::string::size_type end;
+			
+			while((start = rawMessage.find('<')) != std::string::npos)
+			{
+				if((end = rawMessage.find('>', start + 1)) != std::string::npos)
+				{
+					// Strip out the tag
+					rawMessage.erase(start, (end + 1) - start);
+				}
+				else
+				{
+					// Avoid an infinite loop
+					break;
+				}
+			}
+		}
+		
+		// Decode ampersand-escaped chars
+		{
+			std::string::size_type mark = 0;
+
+			// The text may contain text encoded with &lt;, &gt;, and &amp;
+			mark = 0;
+			while((mark = rawMessage.find("&lt;", mark)) != std::string::npos)
+			{
+				rawMessage.replace(mark, 4, "<");
+				mark += 1;
+			}
+			
+			mark = 0;
+			while((mark = rawMessage.find("&gt;", mark)) != std::string::npos)
+			{
+				rawMessage.replace(mark, 4, ">");
+				mark += 1;
+			}
+			
+			mark = 0;
+			while((mark = rawMessage.find("&amp;", mark)) != std::string::npos)
+			{
+				rawMessage.replace(mark, 5, "&");
+				mark += 1;
+			}
+		}
+		
+		// strip leading/trailing whitespace (since we always seem to get a couple newlines)
+		LLStringUtil::trim(rawMessage);
+		
+//		LL_DEBUGS("Voice") << "    stripped message = \n" << rawMessage << LL_ENDL;
+		
+		sessionState *session = findSession(sessionHandle);
+		if(session)
+		{
+			bool is_busy = gAgent.getBusy();
+			bool is_muted = LLMuteList::getInstance()->isMuted(session->mCallerID, session->mName, LLMute::flagTextChat);
+			bool is_linden = LLMuteList::getInstance()->isLinden(session->mName);
+			bool quiet_chat = false;
+			LLChat chat;
+
+			chat.mMuted = is_muted && !is_linden;
+			
+			if(!chat.mMuted)
+			{
+				chat.mFromID = session->mCallerID;
+				chat.mFromName = session->mName;
+				chat.mSourceType = CHAT_SOURCE_AGENT;
+
+				if(is_busy && !is_linden)
+				{
+					quiet_chat = true;
+					// TODO: Question: Return busy mode response here?  Or maybe when session is started instead?
+				}
+				
+				std::string fullMessage = std::string(": ") + rawMessage;
+				
+				LL_DEBUGS("Voice") << "adding message, name " << session->mName << " session " << session->mIMSessionID << ", target " << session->mCallerID << LL_ENDL;
+				gIMMgr->addMessage(session->mIMSessionID,
+						session->mCallerID,
+						session->mName.c_str(),
+						fullMessage.c_str(),
+						LLStringUtil::null,		// default arg
+						IM_NOTHING_SPECIAL,		// default arg
+						0,						// default arg
+						LLUUID::null,			// default arg
+						LLVector3::zero,		// default arg
+						true);					// prepend name and make it a link to the user's profile
+
+				chat.mText = std::string("IM: ") + session->mName + std::string(": ") + rawMessage;
+				// If the chat should come in quietly (i.e. we're in busy mode), pretend it's from a local agent.
+				LLFloaterChat::addChat( chat, TRUE, quiet_chat );
+			}
+		}		
+	}
+}
+
+void LLVoiceClient::sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string &notificationType)
+{
+	sessionState *session = findSession(sessionHandle);
+	
+	if(session)
+	{
+		participantState *participant = session->findParticipant(uriString);
+		if(participant)
+		{
+			if (!stricmp(notificationType.c_str(), "Typing"))
+			{
+				// Other end started typing
+				// TODO: The proper way to add a typing notification seems to be LLIMMgr::processIMTypingStart().
+				// It requires an LLIMInfo for the message, which we don't have here.
+			}
+			else if (!stricmp(notificationType.c_str(), "NotTyping"))
+			{
+				// Other end stopped typing
+				// TODO: The proper way to remove a typing notification seems to be LLIMMgr::processIMTypingStop().
+				// It requires an LLIMInfo for the message, which we don't have here.
+			}
+			else
+			{
+				LL_DEBUGS("Voice") << "Unknown notification type " << notificationType << "for participant " << uriString << " in session " << session->mSIPURI << LL_ENDL;
+			}
+		}
+		else
+		{
+			LL_DEBUGS("Voice") << "Unknown participant " << uriString << " in session " << session->mSIPURI << LL_ENDL;
+		}
+	}
+	else
+	{
+		LL_DEBUGS("Voice") << "Unknown session handle " << sessionHandle << LL_ENDL;
+	}
+}
+
+void LLVoiceClient::subscriptionEvent(std::string &buddyURI, std::string &subscriptionHandle, std::string &alias, std::string &displayName, std::string &applicationString, std::string &subscriptionType)
+{
+	buddyListEntry *buddy = findBuddy(buddyURI);
+	
+	if(!buddy)
+	{
+		// Couldn't find buddy by URI, try converting the alias...
+		if(!alias.empty())
+		{
+			LLUUID id;
+			if(IDFromName(alias, id))
+			{
+				buddy = findBuddy(id);
+			}
+		}
+	}
+	
+	if(buddy)
+	{
+		std::ostringstream stream;
+		
+		if(buddy->mCanSeeMeOnline)
+		{
+			// Sending the response will create an auto-accept rule
+			buddy->mHasAutoAcceptListEntry = true;
+		}
+		else
+		{
+			// Sending the response will create a block rule
+			buddy->mHasBlockListEntry = true;
+		}
+		
+		if(buddy->mInSLFriends)
+		{
+			buddy->mInVivoxBuddies = true;
+		}
+		
+		stream
+			<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Account.SendSubscriptionReply.1\">"
+				<< "<AccountHandle>" << mAccountHandle << "</AccountHandle>"
+				<< "<BuddyURI>" << buddy->mURI << "</BuddyURI>"
+				<< "<RuleType>" << (buddy->mCanSeeMeOnline?"Allow":"Hide") << "</RuleType>"
+				<< "<AutoAccept>"<< (buddy->mInSLFriends?"1":"0")<< "</AutoAccept>"
+				<< "<SubscriptionHandle>" << subscriptionHandle << "</SubscriptionHandle>"
+			<< "</Request>"
+			<< "\n\n\n";
+			
+		writeString(stream.str());
+	}
+}
 
-void LLVoiceClient::clearChannelMap(void)
+void LLVoiceClient::auxAudioPropertiesEvent(F32 energy)
 {
-	mChannelMap.clear();
+	LL_DEBUGS("Voice") << "got energy " << energy << LL_ENDL;
+	mTuningEnergy = energy;
 }
 
-void LLVoiceClient::addChannelMapEntry(std::string &name, std::string &uri)
+void LLVoiceClient::buddyListChanged()
 {
-	LL_DEBUGS("Voice") << "Adding channel name mapping: " << name << " -> " << uri << LL_ENDL;
-	mChannelMap.insert(channelMap::value_type(name, uri));
+	// This is called after we receive a BuddyAndGroupListChangedEvent.
+	mBuddyListMapPopulated = true;
+	mFriendsListDirty = true;
 }
 
-std::string LLVoiceClient::findChannelURI(std::string &name)
+void LLVoiceClient::muteListChanged()
 {
-	std::string result;
+	// The user's mute list has been updated.  Go through the current participant list and sync it with the mute list.
+	if(mAudioSession)
+	{
+		participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin();
+		
+		for(; iter != mAudioSession->mParticipantsByURI.end(); iter++)
+		{
+			participantState *p = iter->second;
+			
+			// Check to see if this participant is on the mute list already
+			if(p->updateMuteState())
+				mAudioSession->mVolumeDirty = true;
+		}
+	}
+}
+
+void LLVoiceClient::updateFriends(U32 mask)
+{
+	if(mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::POWERS))
+	{
+		// Just resend the whole friend list to the daemon
+		mFriendsListDirty = true;
+	}
+}
+
+/////////////////////////////
+// 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), 
+	 mOnMuteList(false), 
+	 mUserVolume(100), 
+	 mVolumeDirty(false), 
+	 mAvatarIDValid(false),
+	 mIsSelf(false)
+{
+}
+
+LLVoiceClient::participantState *LLVoiceClient::sessionState::addParticipant(const std::string &uri)
+{
+	participantState *result = NULL;
+	bool useAlternateURI = false;
+	
+	// Note: this is mostly the body of LLVoiceClient::sessionState::findParticipant(), but since we need to know if it
+	// matched the alternate SIP URI (so we can add it properly), we need to reproduce it here.
+	{
+		participantMap::iterator iter = mParticipantsByURI.find(&uri);
+
+		if(iter == mParticipantsByURI.end())
+		{
+			if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI))
+			{
+				// This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant.
+				// Use mSIPURI instead, since it will be properly encoded.
+				iter = mParticipantsByURI.find(&(mSIPURI));
+				useAlternateURI = true;
+			}
+		}
+
+		if(iter != mParticipantsByURI.end())
+		{
+			result = iter->second;
+		}
+	}
+		
+	if(!result)
+	{
+		// participant isn't already in one list or the other.
+		result = new participantState(useAlternateURI?mSIPURI:uri);
+		mParticipantsByURI.insert(participantMap::value_type(&(result->mURI), result));
+		mParticipantsChanged = true;
+		
+		// Try to do a reverse transform on the URI to get the GUID back.
+		{
+			LLUUID id;
+			if(IDFromName(result->mURI, id))
+			{
+				result->mAvatarIDValid = true;
+				result->mAvatarID = id;
+
+				if(result->updateMuteState())
+					mVolumeDirty = true;
+			}
+			else
+			{
+				// Create a UUID by hashing the URI, but do NOT set mAvatarIDValid.
+				// This tells both code in LLVoiceClient and code in llfloateractivespeakers.cpp that the ID will not be in the name cache.
+				setUUIDFromStringHash(result->mAvatarID, uri);
+			}
+		}
+		
+		mParticipantsByUUID.insert(participantUUIDMap::value_type(&(result->mAvatarID), result));
+		
+		LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL;
+	}
+	
+	return result;
+}
+
+bool LLVoiceClient::participantState::updateMuteState()
+{
+	bool result = false;
+	
+	if(mAvatarIDValid)
+	{
+		bool isMuted = LLMuteList::getInstance()->isMuted(mAvatarID, LLMute::flagVoiceChat);
+		if(mOnMuteList != isMuted)
+		{
+			mOnMuteList = isMuted;
+			mVolumeDirty = true;
+			result = true;
+		}
+	}
+	return result;
+}
+
+void LLVoiceClient::sessionState::removeParticipant(LLVoiceClient::participantState *participant)
+{
+	if(participant)
+	{
+		participantMap::iterator iter = mParticipantsByURI.find(&(participant->mURI));
+		participantUUIDMap::iterator iter2 = mParticipantsByUUID.find(&(participant->mAvatarID));
+		
+		LL_DEBUGS("Voice") << "participant \"" << participant->mURI <<  "\" (" << participant->mAvatarID << ") removed." << LL_ENDL;
+		
+		if(iter == mParticipantsByURI.end())
+		{
+			LL_ERRS("Voice") << "Internal error: participant " << participant->mURI << " not in URI map" << LL_ENDL;
+		}
+		else if(iter2 == mParticipantsByUUID.end())
+		{
+			LL_ERRS("Voice") << "Internal error: participant ID " << participant->mAvatarID << " not in UUID map" << LL_ENDL;
+		}
+		else if(iter->second != iter2->second)
+		{
+			LL_ERRS("Voice") << "Internal error: participant mismatch!" << LL_ENDL;
+		}
+		else
+		{
+			mParticipantsByURI.erase(iter);
+			mParticipantsByUUID.erase(iter2);
+			
+			delete participant;
+			mParticipantsChanged = true;
+		}
+	}
+}
+
+void LLVoiceClient::sessionState::removeAllParticipants()
+{
+	LL_DEBUGS("Voice") << "called" << LL_ENDL;
+
+	while(!mParticipantsByURI.empty())
+	{
+		removeParticipant(mParticipantsByURI.begin()->second);
+	}
+	
+	if(!mParticipantsByUUID.empty())
+	{
+		LL_ERRS("Voice") << "Internal error: empty URI map, non-empty UUID map" << LL_ENDL
+	}
+}
+
+LLVoiceClient::participantMap *LLVoiceClient::getParticipantList(void)
+{
+	participantMap *result = NULL;
+	if(mAudioSession)
+	{
+		result = &(mAudioSession->mParticipantsByURI);
+	}
+	return result;
+}
+
+
+LLVoiceClient::participantState *LLVoiceClient::sessionState::findParticipant(const std::string &uri)
+{
+	participantState *result = NULL;
 	
-	channelMap::iterator iter = mChannelMap.find(name);
+	participantMap::iterator iter = mParticipantsByURI.find(&uri);
+
+	if(iter == mParticipantsByURI.end())
+	{
+		if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI))
+		{
+			// This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant.
+			// Look up the other URI
+			iter = mParticipantsByURI.find(&(mSIPURI));
+		}
+	}
+
+	if(iter != mParticipantsByURI.end())
+	{
+		result = iter->second;
+	}
+		
+	return result;
+}
+
+LLVoiceClient::participantState* LLVoiceClient::sessionState::findParticipantByID(const LLUUID& id)
+{
+	participantState * result = NULL;
+	participantUUIDMap::iterator iter = mParticipantsByUUID.find(&id);
 
-	if(iter != mChannelMap.end())
+	if(iter != mParticipantsByUUID.end())
 	{
 		result = iter->second;
 	}
+
+	return result;
+}
+
+LLVoiceClient::participantState* LLVoiceClient::findParticipantByID(const LLUUID& id)
+{
+	participantState * result = NULL;
+	
+	if(mAudioSession)
+	{
+		result = mAudioSession->findParticipantByID(id);
+	}
 	
 	return result;
 }
 
+
 void LLVoiceClient::parcelChanged()
 {
-	if(getState() >= stateLoggedIn)
+	if(getState() >= stateNoChannel)
 	{
 		// If the user is logged in, start a channel lookup.
 		LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL;
@@ -3244,7 +4967,7 @@ void LLVoiceClient::parcelChanged()
 	}
 	else
 	{
-		// The transition to stateLoggedIn needs to kick this off again.
+		// The transition to stateNoChannel needs to kick this off again.
 		LL_INFOS("Voice") << "not logged in yet, deferring" << LL_ENDL;
 	}
 }
@@ -3252,12 +4975,17 @@ void LLVoiceClient::parcelChanged()
 void LLVoiceClient::switchChannel(
 	std::string uri,
 	bool spatial,
-	bool noReconnect,
+	bool no_reconnect,
+	bool is_p2p,
 	std::string hash)
 {
 	bool needsSwitch = false;
 	
-	LL_DEBUGS("Voice") << "called in state " << state2string(getState()) << " with uri \"" << uri << "\"" << LL_ENDL;
+	LL_DEBUGS("Voice") 
+		<< "called in state " << state2string(getState()) 
+		<< " with uri \"" << uri << "\"" 
+		<< (spatial?", spatial is true":", spatial is false")
+		<< LL_ENDL;
 	
 	switch(getState())
 	{
@@ -3267,41 +4995,73 @@ void LLVoiceClient::switchChannel(
 			// 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;
+				if(mNextAudioSession)
+				{
+					if(mNextAudioSession->mSIPURI != uri)
+						needsSwitch = true;
+				}
+				else
+				{
+					// mNextAudioSession is null -- this probably means we're on our way back to spatial.
+					if(!uri.empty())
+					{
+						// We do want to process a switch in this case.
+						needsSwitch = true;
+					}
+				}
 			}
 			else
 			{
 				// Otherwise, compare against the URI we're in now.
-				if(mSessionURI != uri)
-					needsSwitch = true;
-			}
-		break;
-	}
-	
+				if(mAudioSession)
+				{
+					if(mAudioSession->mSIPURI != uri)
+					{
+						needsSwitch = true;
+					}
+				}
+				else
+				{
+					if(!uri.empty())
+					{
+						// mAudioSession is null -- it's not clear what case would cause this.
+						// For now, log it as a warning and see if it ever crops up.
+						LL_WARNS("Voice") << "No current audio session." << LL_ENDL;
+					}
+				}
+			}
+		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;
+
+			sessionState *oldSession = mNextAudioSession;
+			mNextAudioSession = NULL;
+
+			// The old session may now need to be deleted.
+			reapSession(oldSession);
+
 			notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED);
 		}
 		else
 		{
 			LL_DEBUGS("Voice") << "switching to channel " << uri << LL_ENDL;
+
+			mNextAudioSession = addSession(uri);
+			mNextAudioSession->mHash = hash;
+			mNextAudioSession->mIsSpatial = spatial;
+			mNextAudioSession->mReconnect = !no_reconnect;
+			mNextAudioSession->mIsP2P = is_p2p;
 		}
 		
 		if(getState() <= stateNoChannel)
@@ -3316,808 +5076,1710 @@ void LLVoiceClient::switchChannel(
 	}
 }
 
-void LLVoiceClient::joinSession(std::string handle, std::string uri)
+void LLVoiceClient::joinSession(sessionState *session)
+{
+	mNextAudioSession = session;
+	
+	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, false, credentials);
+}
+
+void LLVoiceClient::setSpatialChannel(
+	const std::string &uri,
+	const std::string &credentials)
+{
+	mSpatialSessionURI = uri;
+	mSpatialSessionCredentials = credentials;
+	mAreaVoiceDisabled = mSpatialSessionURI.empty();
+
+	LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL;
+	
+	if((mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial)))
+	{
+		// 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, false, mSpatialSessionCredentials);
+	}
+}
+
+void LLVoiceClient::callUser(const LLUUID &uuid)
+{
+	std::string userURI = sipURIFromID(uuid);
+
+	switchChannel(userURI, false, true, true);
+}
+
+LLVoiceClient::sessionState* LLVoiceClient::startUserIMSession(const LLUUID &uuid)
+{
+	// Figure out if a session with the user already exists
+	sessionState *session = findSession(uuid);
+	if(!session)
+	{
+		// No session with user, need to start one.
+		std::string uri = sipURIFromID(uuid);
+		session = addSession(uri);
+		session->mIsSpatial = false;
+		session->mReconnect = false;	
+		session->mIsP2P = true;
+		session->mCallerID = uuid;
+	}
+	
+	if(session)
+	{
+		if(session->mHandle.empty())
+		{
+			// Session isn't active -- start it up.
+			sessionCreateSendMessage(session, false, true);
+		}
+		else
+		{	
+			// Session is already active -- start up text.
+			sessionTextConnectSendMessage(session);
+		}
+	}
+	
+	return session;
+}
+
+bool LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message)
+{
+	bool result = false;
+
+	// Attempt to locate the indicated session
+	sessionState *session = startUserIMSession(participant_id);
+	if(session)
+	{
+		// found the session, attempt to send the message
+		session->mTextMsgQueue.push(message);
+		
+		// Try to send queued messages (will do nothing if the session is not open yet)
+		sendQueuedTextMessages(session);
+
+		// The message is queued, so we succeed.
+		result = true;
+	}	
+	else
+	{
+		LL_DEBUGS("Voice") << "Session not found for participant ID " << participant_id << LL_ENDL;
+	}
+	
+	return result;
+}
+
+void LLVoiceClient::sendQueuedTextMessages(sessionState *session)
+{
+	if(session->mTextStreamState == 1)
+	{
+		if(!session->mTextMsgQueue.empty())
+		{
+			std::ostringstream stream;
+			
+			while(!session->mTextMsgQueue.empty())
+			{
+				std::string message = session->mTextMsgQueue.front();
+				session->mTextMsgQueue.pop();
+				stream
+				<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"Session.SendMessage.1\">"
+					<< "<SessionHandle>" << session->mHandle << "</SessionHandle>"
+					<< "<MessageHeader>text/HTML</MessageHeader>"
+					<< "<MessageBody>" << message << "</MessageBody>"
+				<< "</Request>"
+				<< "\n\n\n";
+			}		
+			writeString(stream.str());
+		}
+	}
+	else
+	{
+		// Session isn't connected yet, defer until later.
+	}
+}
+
+void LLVoiceClient::endUserIMSession(const LLUUID &uuid)
+{
+	// Figure out if a session with the user exists
+	sessionState *session = findSession(uuid);
+	if(session)
+	{
+		// found the session
+		if(!session->mHandle.empty())
+		{
+			sessionTextDisconnectSendMessage(session);
+		}
+	}	
+	else
+	{
+		LL_DEBUGS("Voice") << "Session not found for participant ID " << uuid << LL_ENDL;
+	}
+}
+
+bool LLVoiceClient::answerInvite(std::string &sessionHandle)
+{
+	// this is only ever used to answer incoming p2p call invites.
+	
+	sessionState *session = findSession(sessionHandle);
+	if(session)
+	{
+		session->mIsSpatial = false;
+		session->mReconnect = false;	
+		session->mIsP2P = true;
+
+		joinSession(session);
+		return true;
+	}
+	
+	return false;
+}
+
+bool LLVoiceClient::isOnlineSIP(const LLUUID &id)
+{
+	bool result = false;
+	buddyListEntry *buddy = findBuddy(id);
+	if(buddy)
+	{
+		result = buddy->mOnlineSLim;
+		LL_DEBUGS("Voice") << "Buddy " << buddy->mDisplayName << " is SIP " << (result?"online":"offline") << LL_ENDL;
+	}
+
+	if(!result)
+	{
+		// This user isn't on the buddy list or doesn't show online status through the buddy list, but could be a participant in an existing session if they initiated a text IM.
+		sessionState *session = findSession(id);
+		if(session && !session->mHandle.empty())
+		{
+			if((session->mTextStreamState != streamStateUnknown) || (session->mMediaStreamState > streamStateIdle))
+			{
+				LL_DEBUGS("Voice") << "Open session with " << id << " found, returning SIP online state" << LL_ENDL;
+				// we have a p2p text session open with this user, so by definition they're online.
+				result = true;
+			}
+		}
+	}
+	
+	return result;
+}
+
+void LLVoiceClient::declineInvite(std::string &sessionHandle)
+{
+	sessionState *session = findSession(sessionHandle);
+	if(session)
+	{
+		sessionMediaDisconnectSendMessage(session);
+	}
+}
+
+void LLVoiceClient::leaveNonSpatialChannel()
+{
+	LL_DEBUGS("Voice") 
+		<< "called in state " << state2string(getState()) 
+		<< LL_ENDL;
+	
+	// Make sure we don't rejoin the current session.	
+	sessionState *oldNextSession = mNextAudioSession;
+	mNextAudioSession = NULL;
+	
+	// Most likely this will still be the current session at this point, but check it anyway.
+	reapSession(oldNextSession);
+	
+	verifySessionState();
+	
+	sessionTerminate();
+}
+
+std::string LLVoiceClient::getCurrentChannel()
+{
+	std::string result;
+	
+	if((getState() == stateRunning) && !mSessionTerminateRequested)
+	{
+		result = getAudioSessionURI();
+	}
+	
+	return result;
+}
+
+bool LLVoiceClient::inProximalChannel()
+{
+	bool result = false;
+	
+	if((getState() == stateRunning) && !mSessionTerminateRequested)
+	{
+		result = inSpatialChannel();
+	}
+	
+	return result;
+}
+
+std::string LLVoiceClient::sipURIFromID(const LLUUID &id)
+{
+	std::string result;
+	result = "sip:";
+	result += nameFromID(id);
+	result += "@";
+	result += mVoiceSIPURIHostName;
+	
+	return result;
+}
+
+std::string LLVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar)
+{
+	std::string result;
+	if(avatar)
+	{
+		result = "sip:";
+		result += nameFromID(avatar->getID());
+		result += "@";
+		result += mVoiceSIPURIHostName;
+	}
+	
+	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;
+	
+	if (uuid.isNull()) {
+		//VIVOX, the uuid emtpy look for the mURIString and return that instead.
+		//result.assign(uuid.mURIStringName);
+		LLStringUtil::replaceChar(result, '_', ' ');
+		return 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 '/+' '_-')
+	
+	// The reverse transform can be done with:
+	// echo 'x5mkTKmxDTuGnjWyC__WfMg==' |cut -b 2- -|tr '_-' '/+' |openssl base64 -d|xxd -p
+	
+	return result;
+}
+
+bool LLVoiceClient::IDFromName(const std::string inName, LLUUID &uuid)
+{
+	bool result = false;
+	
+	// SLIM SDK: The "name" may actually be a SIP URI such as: "sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com"
+	// If it is, convert to a bare name before doing the transform.
+	std::string name = nameFromsipURI(inName);
+	
+	// Doesn't look like a SIP URI, assume it's an actual name.
+	if(name.empty())
+		name = inName;
+
+	// 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;
+		}
+	} 
+	
+	if(!result)
+	{
+		// VIVOX:  not a standard account name, just copy the URI name mURIString field
+		// and hope for the best.  bpj
+		uuid.setNull();  // VIVOX, set the uuid field to nulls
+	}
+	
+	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 += mVoiceSIPURIHostName;
+
+//	LLStringUtil::toLower(result);
+
+	return result;
+}
+
+std::string LLVoiceClient::nameFromsipURI(const std::string &uri)
+{
+	std::string result;
+
+	std::string::size_type sipOffset, atOffset;
+	sipOffset = uri.find("sip:");
+	atOffset = uri.find("@");
+	if((sipOffset != std::string::npos) && (atOffset != std::string::npos))
+	{
+		result = uri.substr(sipOffset + 4, atOffset - (sipOffset + 4));
+	}
+	
+	return result;
+}
+
+bool LLVoiceClient::inSpatialChannel(void)
+{
+	bool result = false;
+	
+	if(mAudioSession)
+		result = mAudioSession->mIsSpatial;
+		
+	return result;
+}
+
+std::string LLVoiceClient::getAudioSessionURI()
+{
+	std::string result;
+	
+	if(mAudioSession)
+		result = mAudioSession->mSIPURI;
+		
+	return result;
+}
+
+std::string LLVoiceClient::getAudioSessionHandle()
+{
+	std::string result;
+	
+	if(mAudioSession)
+		result = mAudioSession->mHandle;
+		
+	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;
+
+			// TODO: If camera and avatar velocity are actually used by the voice system, we could compute them here...
+			// They're currently always set to zero.
+
+			// 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();
+			// TODO: 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
+		{
+			// Turning voice off looses your current channel -- this makes sure the UI isn't out of sync when you re-enable it.
+			LLVoiceChannel::getCurrentVoiceChannel()->deactivate();
+		}
+	}
+}
+
+bool LLVoiceClient::voiceEnabled()
+{
+	return gSavedSettings.getBOOL("EnableVoiceChat") && !gSavedSettings.getBOOL("CmdLineDisableVoice");
+}
+
+void LLVoiceClient::setLipSyncEnabled(BOOL enabled)
+{
+	mLipSyncEnabled = enabled;
+}
+
+BOOL LLVoiceClient::lipSyncEnabled()
 {
-	mNextSessionURI.clear();
-	mNextSessionHash.clear();
-	mNextP2PSessionURI = uri;
-	mNextSessionHandle = handle;
-	mNextSessionSpatial = false;
-	mNextSessionNoReconnect = false;
-
-	if(getState() <= stateNoChannel)
+	   
+	if ( mVoiceEnabled && stateDisabled != getState() )
 	{
-		// We're already set up to join a channel, just needed to fill in the session handle
+		return mLipSyncEnabled;
 	}
 	else
 	{
-		// State machine will come around and rejoin if uri/handle is not empty.
-		sessionTerminate();
+		return FALSE;
 	}
 }
 
-void LLVoiceClient::setNonSpatialChannel(
-	const std::string &uri,
-	const std::string &credentials)
+void LLVoiceClient::setUsePTT(bool usePTT)
 {
-	switchChannel(uri, false, false, credentials);
+	if(usePTT && !mUsePTT)
+	{
+		// When the user turns on PTT, reset the current state.
+		mUserPTTState = false;
+	}
+	mUsePTT = usePTT;
 }
 
-void LLVoiceClient::setSpatialChannel(
-	const std::string &uri,
-	const std::string &credentials)
+void LLVoiceClient::setPTTIsToggle(bool PTTIsToggle)
 {
-	mSpatialSessionURI = uri;
-	mAreaVoiceDisabled = mSpatialSessionURI.empty();
-
-	LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL;
+	if(!PTTIsToggle && mPTTIsToggle)
+	{
+		// When the user turns off toggle, reset the current state.
+		mUserPTTState = false;
+	}
 	
-	if(mNonSpatialChannel || !mNextSessionSpatial)
+	mPTTIsToggle = PTTIsToggle;
+}
+
+
+void LLVoiceClient::setPTTKey(std::string &key)
+{
+	if(key == "MiddleMouse")
 	{
-		// 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;
+		mPTTIsMiddleMouse = true;
 	}
 	else
 	{
-		switchChannel(mSpatialSessionURI, true, false, credentials);
+		mPTTIsMiddleMouse = false;
+		if(!LLKeyboard::keyFromString(key, &mPTTKey))
+		{
+			// If the call failed, don't match any key.
+			key = KEY_NONE;
+		}
 	}
 }
 
-void LLVoiceClient::callUser(LLUUID &uuid)
+void LLVoiceClient::setEarLocation(S32 loc)
 {
-	std::string userURI = sipURIFromID(uuid);
-
-	switchChannel(userURI, false, true);
+	if(mEarLocation != loc)
+	{
+		LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL;
+		
+		mEarLocation = loc;
+		mSpatialCoordsDirty = true;
+	}
 }
 
-void LLVoiceClient::answerInvite(std::string &sessionHandle, LLUUID& other_user_id)
+void LLVoiceClient::setVoiceVolume(F32 volume)
 {
-	joinSession(sessionHandle, sipURIFromID(other_user_id));
+	int scaled_volume = scale_speaker_volume(volume);	
+
+	if(scaled_volume != mSpeakerVolume)
+	{
+		if((scaled_volume == 0) || (mSpeakerVolume == 0))
+		{
+			mSpeakerMuteDirty = true;
+		}
+
+		mSpeakerVolume = scaled_volume;
+		mSpeakerVolumeDirty = true;
+	}
 }
 
-void LLVoiceClient::declineInvite(std::string &sessionHandle)
+void LLVoiceClient::setMicGain(F32 volume)
 {
-	sessionTerminateByHandle(sessionHandle);
+	int scaled_volume = scale_mic_volume(volume);
+	
+	if(scaled_volume != mMicVolume)
+	{
+		mMicVolume = scaled_volume;
+		mMicVolumeDirty = true;
+	}
 }
 
-void LLVoiceClient::leaveNonSpatialChannel()
+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)
 {
-	switchChannel(mSpatialSessionURI);
+	if(!mPTTIsMiddleMouse)
+	{
+		if(!mPTTIsToggle && (mPTTKey != KEY_NONE))
+		{
+			setUserPTTState(gKeyboard->getKeyDown(mPTTKey));
+		}
+	}
 }
-
-std::string LLVoiceClient::getCurrentChannel()
+void LLVoiceClient::middleMouseState(bool down)
 {
-	if((getState() == stateRunning) && !mSessionTerminateRequested)
+	if(mPTTIsMiddleMouse)
 	{
-		return mSessionURI;
+		if(mPTTIsToggle)
+		{
+			if(down)
+			{
+				toggleUserPTTState();
+			}
+		}
+		else
+		{
+			setUserPTTState(down);
+		}
 	}
-	
-	return "";
 }
 
-bool LLVoiceClient::inProximalChannel()
+/////////////////////////////
+// Accessors for data related to nearby speakers
+BOOL LLVoiceClient::getVoiceEnabled(const LLUUID& id)
 {
-	bool result = false;
-	
-	if((getState() == stateRunning) && !mSessionTerminateRequested)
+	BOOL result = FALSE;
+	participantState *participant = findParticipantByID(id);
+	if(participant)
 	{
-		result = !mNonSpatialChannel;
+		// 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;
 }
 
-std::string LLVoiceClient::sipURIFromID(const LLUUID &id)
+BOOL LLVoiceClient::getIsSpeaking(const LLUUID& id)
 {
-	std::string result;
-	result = "sip:";
-	result += nameFromID(id);
-	result += "@";
-	result += mAccountServerName;
+	BOOL result = FALSE;
+
+	participantState *participant = findParticipantByID(id);
+	if(participant)
+	{
+		if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT)
+		{
+			participant->mIsSpeaking = FALSE;
+		}
+		result = participant->mIsSpeaking;
+	}
 	
 	return result;
 }
 
-std::string LLVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar)
+BOOL LLVoiceClient::getIsModeratorMuted(const LLUUID& id)
 {
-	std::string result;
-	if(avatar)
+	BOOL result = FALSE;
+
+	participantState *participant = findParticipantByID(id);
+	if(participant)
 	{
-		result = "sip:";
-		result += nameFromID(avatar->getID());
-		result += "@";
-		result += mAccountServerName;
+		result = participant->mIsModeratorMuted;
 	}
 	
 	return result;
 }
 
-std::string LLVoiceClient::nameFromAvatar(LLVOAvatar *avatar)
-{
-	std::string result;
-	if(avatar)
+F32 LLVoiceClient::getCurrentPower(const LLUUID& id)
+{		
+	F32 result = 0;
+	participantState *participant = findParticipantByID(id);
+	if(participant)
 	{
-		result = nameFromID(avatar->getID());
-	}	
+		result = participant->mPower;
+	}
+	
 	return result;
 }
 
-std::string LLVoiceClient::nameFromID(const LLUUID &uuid)
+
+std::string LLVoiceClient::getDisplayName(const LLUUID& id)
 {
 	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 '/+' '_-')
+	participantState *participant = findParticipantByID(id);
+	if(participant)
+	{
+		result = participant->mDisplayName;
+	}
 	
 	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, '_', '/');
+BOOL LLVoiceClient::getUsingPTT(const LLUUID& id)
+{
+	BOOL result = FALSE;
 
-		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;
-		}
+	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;
 }
 
-std::string LLVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar)
+BOOL LLVoiceClient::getOnMuteList(const LLUUID& id)
 {
-	return avatar->getFullname();
+	BOOL result = FALSE;
+	
+	participantState *participant = findParticipantByID(id);
+	if(participant)
+	{
+		result = participant->mOnMuteList;
+	}
+
+	return result;
 }
 
-std::string LLVoiceClient::sipURIFromName(std::string &name)
+// 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)
 {
-	std::string result;
-	result = "sip:";
-	result += name;
-	result += "@";
-	result += mAccountServerName;
-
-//	LLStringUtil::toLower(result);
+	F32 result = 0.0f;
+	
+	participantState *participant = findParticipantByID(id);
+	if(participant)
+	{
+		S32 ires = participant->mUserVolume; // 0-400
+		result = sqrtf(((F32)ires) / 400.f);
+	}
 
 	return result;
 }
 
-/////////////////////////////
-// Sending updates of current state
-
-void LLVoiceClient::enforceTether(void)
+void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume)
 {
-	LLVector3d tethered	= mCameraRequestedPosition;
-
-	// constrain 'tethered' to within 50m of mAvatarPosition.
+	if(mAudioSession)
 	{
-		F32 max_dist = 50.0f;
-		LLVector3d camera_offset = mCameraRequestedPosition - mAvatarPosition;
-		F32 camera_distance = (F32)camera_offset.magVec();
-		if(camera_distance > max_dist)
+		participantState *participant = findParticipantByID(id);
+		if (participant)
 		{
-			tethered = mAvatarPosition + 
-				(max_dist / camera_distance) * camera_offset;
+			// volume can amplify by as much as 4x!
+			S32 ivol = (S32)(400.f * volume * volume);
+			participant->mUserVolume = llclamp(ivol, 0, 400);
+			participant->mVolumeDirty = TRUE;
+			mAudioSession->mVolumeDirty = TRUE;
 		}
 	}
-	
-	if(dist_vec(mCameraPosition, tethered) > 0.1)
+}
+
+std::string LLVoiceClient::getGroupID(const LLUUID& id)
+{
+	std::string result;
+
+	participantState *participant = findParticipantByID(id);
+	if(participant)
 	{
-		mCameraPosition = tethered;
-		mSpatialCoordsDirty = true;
+		result = participant->mGroupID;
 	}
+	
+	return result;
 }
 
-void LLVoiceClient::updatePosition(void)
+BOOL LLVoiceClient::getAreaVoiceDisabled()
+{
+	return mAreaVoiceDisabled;
+}
+
+void LLVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame)
 {
+//	LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL;
 	
-	if(gVoiceClient)
+	if(!mMainSessionGroupHandle.empty())
 	{
-		LLVOAvatar *agent = gAgent.getAvatarObject();
-		LLViewerRegion *region = gAgent.getRegion();
-		if(region && agent)
-		{
-			LLMatrix3 rot;
-			LLVector3d pos;
+		std::ostringstream stream;
+		stream
+		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">"
+		<< "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
+		<< "<RecordingControlType>Start</RecordingControlType>" 
+		<< "<DeltaFramesPerControlFrame>" << deltaFramesPerControlFrame << "</DeltaFramesPerControlFrame>"
+		<< "<Filename>" << "" << "</Filename>"
+		<< "<EnableAudioRecordingEvents>false</EnableAudioRecordingEvents>"
+		<< "<LoopModeDurationSeconds>" << seconds << "</LoopModeDurationSeconds>"
+		<< "</Request>\n\n\n";
 
-			// 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
-		}
+		writeString(stream.str());
 	}
 }
 
-void LLVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot)
+void LLVoiceClient::recordingLoopSave(const std::string& filename)
 {
-	mCameraRequestedPosition = position;
-	
-	if(mCameraVelocity != velocity)
-	{
-		mCameraVelocity = velocity;
-		mSpatialCoordsDirty = true;
-	}
-	
-	if(mCameraRot != rot)
+//	LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Flush)" << LL_ENDL;
+
+	if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
 	{
-		mCameraRot = rot;
-		mSpatialCoordsDirty = true;
+		std::ostringstream stream;
+		stream
+		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">"
+		<< "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
+		<< "<RecordingControlType>Flush</RecordingControlType>" 
+		<< "<Filename>" << filename << "</Filename>"
+		<< "</Request>\n\n\n";
+
+		writeString(stream.str());
 	}
 }
 
-void LLVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot)
+void LLVoiceClient::recordingStop()
 {
-	if(dist_vec(mAvatarPosition, position) > 0.1)
-	{
-		mAvatarPosition = position;
-		mSpatialCoordsDirty = true;
-	}
-	
-	if(mAvatarVelocity != velocity)
-	{
-		mAvatarVelocity = velocity;
-		mSpatialCoordsDirty = true;
-	}
-	
-	if(mAvatarRot != rot)
+//	LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Stop)" << LL_ENDL;
+
+	if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
 	{
-		mAvatarRot = rot;
-		mSpatialCoordsDirty = true;
+		std::ostringstream stream;
+		stream
+		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlRecording.1\">"
+		<< "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
+		<< "<RecordingControlType>Stop</RecordingControlType>" 
+		<< "</Request>\n\n\n";
+
+		writeString(stream.str());
 	}
 }
 
-bool LLVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name)
+void LLVoiceClient::filePlaybackStart(const std::string& filename)
 {
-	bool result = false;
-	
-	if(region)
+//	LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Start)" << LL_ENDL;
+
+	if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
 	{
-		name = region->getName();
+		std::ostringstream stream;
+		stream
+		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">"
+		<< "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
+		<< "<RecordingControlType>Start</RecordingControlType>" 
+		<< "<Filename>" << filename << "</Filename>"
+		<< "</Request>\n\n\n";
+
+		writeString(stream.str());
 	}
-	
-	if(!name.empty())
-		result = true;
-	
-	return result;
 }
 
-void LLVoiceClient::leaveChannel(void)
+void LLVoiceClient::filePlaybackStop()
 {
-	if(getState() == stateRunning)
+//	LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Stop)" << LL_ENDL;
+
+	if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty())
 	{
-		LL_DEBUGS("Voice") << "leaving channel for teleport/logout" << LL_ENDL;
-		mChannelName.clear();
-		sessionTerminate();
+		std::ostringstream stream;
+		stream
+		<< "<Request requestId=\"" << mCommandCookie++ << "\" action=\"SessionGroup.ControlPlayback.1\">"
+		<< "<SessionGroupHandle>" << mMainSessionGroupHandle << "</SessionGroupHandle>"
+		<< "<RecordingControlType>Stop</RecordingControlType>" 
+		<< "</Request>\n\n\n";
+
+		writeString(stream.str());
 	}
 }
 
-void LLVoiceClient::setMuteMic(bool muted)
+void LLVoiceClient::filePlaybackSetPaused(bool paused)
 {
-	mMuteMic = muted;
+	// TODO: Implement once Vivox gives me a sample
 }
 
-void LLVoiceClient::setUserPTTState(bool ptt)
+void LLVoiceClient::filePlaybackSetMode(bool vox, float speed)
 {
-	mUserPTTState = ptt;
+	// TODO: Implement once Vivox gives me a sample
 }
 
-bool LLVoiceClient::getUserPTTState()
+LLVoiceClient::sessionState::sessionState() :
+	mMediaStreamState(streamStateUnknown),
+	mTextStreamState(streamStateUnknown),
+	mCreateInProgress(false),
+	mMediaConnectInProgress(false),
+	mVoiceInvitePending(false),
+	mTextInvitePending(false),
+	mSynthesizedCallerID(false),
+	mIsChannel(false),
+	mIsSpatial(false),
+	mIsP2P(false),
+	mIncoming(false),
+	mVoiceEnabled(false),
+	mReconnect(false),
+	mVolumeDirty(false),
+	mParticipantsChanged(false)
 {
-	return mUserPTTState;
 }
 
-void LLVoiceClient::toggleUserPTTState(void)
+LLVoiceClient::sessionState::~sessionState()
 {
-	mUserPTTState = !mUserPTTState;
+	removeAllParticipants();
 }
 
-void LLVoiceClient::setVoiceEnabled(bool enabled)
+LLVoiceClient::sessionIterator LLVoiceClient::sessionsBegin(void)
 {
-	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();
-		}
-	}
+	return mSessions.begin();
 }
 
-bool LLVoiceClient::voiceEnabled()
+LLVoiceClient::sessionIterator LLVoiceClient::sessionsEnd(void)
 {
-	return gSavedSettings.getBOOL("EnableVoiceChat") && !gSavedSettings.getBOOL("CmdLineDisableVoice");
+	return mSessions.end();
 }
 
-void LLVoiceClient::setLipSyncEnabled(BOOL enabled)
-{
-	mLipSyncEnabled = enabled;
-}
 
-BOOL LLVoiceClient::lipSyncEnabled()
+LLVoiceClient::sessionState *LLVoiceClient::findSession(const std::string &handle)
 {
-	   
-	if ( mVoiceEnabled && stateDisabled != getState() )
-	{
-		return mLipSyncEnabled;
-	}
-	else
+	sessionState *result = NULL;
+	sessionMap::iterator iter = mSessionsByHandle.find(&handle);
+	if(iter != mSessionsByHandle.end())
 	{
-		return FALSE;
+		result = iter->second;
 	}
+	
+	return result;
 }
 
-void LLVoiceClient::setUsePTT(bool usePTT)
-{
-	if(usePTT && !mUsePTT)
+LLVoiceClient::sessionState *LLVoiceClient::findSessionBeingCreatedByURI(const std::string &uri)
+{	
+	sessionState *result = NULL;
+	for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
 	{
-		// When the user turns on PTT, reset the current state.
-		mUserPTTState = false;
+		sessionState *session = *iter;
+		if(session->mCreateInProgress && (session->mSIPURI == uri))
+		{
+			result = session;
+			break;
+		}
 	}
-	mUsePTT = usePTT;
+	
+	return result;
 }
 
-void LLVoiceClient::setPTTIsToggle(bool PTTIsToggle)
+LLVoiceClient::sessionState *LLVoiceClient::findSession(const LLUUID &participant_id)
 {
-	if(!PTTIsToggle && mPTTIsToggle)
+	sessionState *result = NULL;
+	
+	for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
 	{
-		// When the user turns off toggle, reset the current state.
-		mUserPTTState = false;
+		sessionState *session = *iter;
+		if(session->mCallerID == participant_id)
+		{
+			result = session;
+			break;
+		}
 	}
 	
-	mPTTIsToggle = PTTIsToggle;
+	return result;
 }
 
-
-void LLVoiceClient::setPTTKey(std::string &key)
+LLVoiceClient::sessionState *LLVoiceClient::addSession(const std::string &uri, const std::string &handle)
 {
-	if(key == "MiddleMouse")
+	sessionState *result = NULL;
+	
+	if(handle.empty())
 	{
-		mPTTIsMiddleMouse = true;
+		// No handle supplied.
+		// Check whether there's already a session with this URI
+		for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
+		{
+			sessionState *s = *iter;
+			if((s->mSIPURI == uri) || (s->mAlternateSIPURI == uri))
+			{
+				// TODO: I need to think about this logic... it's possible that this case should raise an internal error.
+				result = s;
+				break;
+			}
+		}
 	}
-	else
+	else // (!handle.empty())
 	{
-		mPTTIsMiddleMouse = false;
-		if(!LLKeyboard::keyFromString(key, &mPTTKey))
+		// Check for an existing session with this handle
+		sessionMap::iterator iter = mSessionsByHandle.find(&handle);
+		
+		if(iter != mSessionsByHandle.end())
 		{
-			// If the call failed, don't match any key.
-			key = KEY_NONE;
+			result = iter->second;
 		}
 	}
-}
 
-void LLVoiceClient::setEarLocation(S32 loc)
-{
-	if(mEarLocation != loc)
+	if(!result)
 	{
-		LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL;
+		// No existing session found.
 		
-		mEarLocation = loc;
-		mSpatialCoordsDirty = true;
+		LL_DEBUGS("Voice") << "adding new session: handle " << handle << " URI " << uri << LL_ENDL;
+		result = new sessionState();
+		result->mSIPURI = uri;
+		result->mHandle = handle;
+		
+		mSessions.insert(result);
+
+		if(!result->mHandle.empty())
+		{
+			mSessionsByHandle.insert(sessionMap::value_type(&(result->mHandle), result));
+		}
 	}
+	else
+	{
+		// Found an existing session
+		
+		if(uri != result->mSIPURI)
+		{
+			// TODO: Should this be an internal error?
+			LL_DEBUGS("Voice") << "changing uri from " << result->mSIPURI << " to " << uri << LL_ENDL;
+			setSessionURI(result, uri);
+		}
+
+		if(handle != result->mHandle)
+		{
+			if(handle.empty())
+			{
+				// There's at least one race condition where where addSession was clearing an existing session handle, which caused things to break.
+				LL_DEBUGS("Voice") << "NOT clearing handle " << result->mHandle << LL_ENDL;
+			}
+			else
+			{
+				// TODO: Should this be an internal error?
+				LL_DEBUGS("Voice") << "changing handle from " << result->mHandle << " to " << handle << LL_ENDL;
+				setSessionHandle(result, handle);
+			}
+		}
+		
+		LL_DEBUGS("Voice") << "returning existing session: handle " << handle << " URI " << uri << LL_ENDL;
+	}
+
+	verifySessionState();
+		
+	return result;
 }
 
-void LLVoiceClient::setVoiceVolume(F32 volume)
+void LLVoiceClient::setSessionHandle(sessionState *session, const std::string &handle)
 {
-	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
+	// Have to remove the session from the handle-indexed map before changing the handle, or things will break badly.
 	
-	if(scaledVolume != mSpeakerVolume)
+	if(!session->mHandle.empty())
 	{
-		if((scaledVolume == -100) || (mSpeakerVolume == -100))
+		// Remove session from the map if it should have been there.
+		sessionMap::iterator iter = mSessionsByHandle.find(&(session->mHandle));
+		if(iter != mSessionsByHandle.end())
 		{
-			mSpeakerMuteDirty = true;
+			if(iter->second != session)
+			{
+				LL_ERRS("Voice") << "Internal error: session mismatch!" << LL_ENDL;
+			}
+
+			mSessionsByHandle.erase(iter);
+		}
+		else
+		{
+			LL_ERRS("Voice") << "Internal error: session handle not found in map!" << LL_ENDL;
 		}
+	}
+			
+	session->mHandle = handle;
 
-		mSpeakerVolume = scaledVolume;
-		mSpeakerVolumeDirty = true;
+	if(!handle.empty())
+	{
+		mSessionsByHandle.insert(sessionMap::value_type(&(session->mHandle), session));
 	}
+
+	verifySessionState();
 }
 
-void LLVoiceClient::setMicGain(F32 volume)
+void LLVoiceClient::setSessionURI(sessionState *session, const std::string &uri)
 {
-	int scaledVolume = ((int)(volume * 100.0f)) - 100;
-	if(scaledVolume != mMicVolume)
-	{
-		mMicVolume = scaledVolume;
-		mMicVolumeDirty = true;
-	}
+	// There used to be a map of session URIs to sessions, which made this complex....
+	session->mSIPURI = uri;
+
+	verifySessionState();
 }
 
-void LLVoiceClient::setVivoxDebugServerName(std::string &serverName)
+void LLVoiceClient::deleteSession(sessionState *session)
 {
-	if(!mAccountServerName.empty())
+	// Remove the session from the handle map
+	if(!session->mHandle.empty())
 	{
-		// The name has been filled in already, which means we know whether we're connecting to agni or not.
-		if(!sConnectingToAgni)
+		sessionMap::iterator iter = mSessionsByHandle.find(&(session->mHandle));
+		if(iter != mSessionsByHandle.end())
 		{
-			// Only use the setting if we're connecting to a development grid -- always use bhr when on agni.
-			mAccountServerName = serverName;
+			if(iter->second != session)
+			{
+				LL_ERRS("Voice") << "Internal error: session mismatch" << LL_ENDL
+			}
+			mSessionsByHandle.erase(iter);
 		}
 	}
-}
 
-void LLVoiceClient::keyDown(KEY key, MASK mask)
-{	
-	LL_DEBUGS("Voice") << "key is " << LLKeyboard::stringFromKey(key) << LL_ENDL;
+	// Remove the session from the URI map
+	mSessions.erase(session);
+	
+	// At this point, the session should be unhooked from all lists and all state should be consistent.
+	verifySessionState();
 
-	if (gKeyboard->getKeyRepeated(key))
+	// If this is the current audio session, clean up the pointer which will soon be dangling.
+	if(mAudioSession == session)
 	{
-		// ignore auto-repeat keys
-		return;
+		mAudioSession = NULL;
+		mAudioSessionChanged = true;
 	}
 
-	if(!mPTTIsMiddleMouse)
+	// ditto for the next audio session
+	if(mNextAudioSession == session)
 	{
-		if(mPTTIsToggle)
-		{
-			if(key == mPTTKey)
-			{
-				toggleUserPTTState();
-			}
-		}
-		else if(mPTTKey != KEY_NONE)
-		{
-			setUserPTTState(gKeyboard->getKeyDown(mPTTKey));
-		}
+		mNextAudioSession = NULL;
 	}
+
+	// delete the session
+	delete session;
 }
-void LLVoiceClient::keyUp(KEY key, MASK mask)
+
+void LLVoiceClient::deleteAllSessions()
 {
-	if(!mPTTIsMiddleMouse)
+	LL_DEBUGS("Voice") << "called" << LL_ENDL;
+
+	while(!mSessions.empty())
 	{
-		if(!mPTTIsToggle && (mPTTKey != KEY_NONE))
-		{
-			setUserPTTState(gKeyboard->getKeyDown(mPTTKey));
-		}
+		deleteSession(*(sessionsBegin()));
+	}
+	
+	if(!mSessionsByHandle.empty())
+	{
+		LL_ERRS("Voice") << "Internal error: empty session map, non-empty handle map" << LL_ENDL
 	}
 }
-void LLVoiceClient::middleMouseState(bool down)
+
+void LLVoiceClient::verifySessionState(void)
 {
-	if(mPTTIsMiddleMouse)
+	// This is mostly intended for debugging problems with session state management.
+	LL_DEBUGS("Voice") << "Total session count: " << mSessions.size() << " , session handle map size: " << mSessionsByHandle.size() << LL_ENDL;
+
+	for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
 	{
-		if(mPTTIsToggle)
+		sessionState *session = *iter;
+
+		LL_DEBUGS("Voice") << "session " << session << ": handle " << session->mHandle << ", URI " << session->mSIPURI << LL_ENDL;
+		
+		if(!session->mHandle.empty())
 		{
-			if(down)
+			// every session with a non-empty handle needs to be in the handle map
+			sessionMap::iterator i2 = mSessionsByHandle.find(&(session->mHandle));
+			if(i2 == mSessionsByHandle.end())
 			{
-				toggleUserPTTState();
+				LL_ERRS("Voice") << "internal error (handle " << session->mHandle << " not found in session map)" << LL_ENDL;
+			}
+			else
+			{
+				if(i2->second != session)
+				{
+					LL_ERRS("Voice") << "internal error (handle " << session->mHandle << " in session map points to another session)" << LL_ENDL;
+				}
 			}
 		}
+	}
+		
+	// check that every entry in the handle map points to a valid session in the session set
+	for(sessionMap::iterator iter = mSessionsByHandle.begin(); iter != mSessionsByHandle.end(); iter++)
+	{
+		sessionState *session = iter->second;
+		sessionIterator i2 = mSessions.find(session);
+		if(i2 == mSessions.end())
+		{
+			LL_ERRS("Voice") << "internal error (session for handle " << session->mHandle << " not found in session map)" << LL_ENDL;
+		}
 		else
 		{
-			setUserPTTState(down);
+			if(session->mHandle != (*i2)->mHandle)
+			{
+				LL_ERRS("Voice") << "internal error (session for handle " << session->mHandle << " points to session with different handle " << (*i2)->mHandle << ")" << LL_ENDL;
+			}
 		}
 	}
 }
 
-/////////////////////////////
-// Accessors for data related to nearby speakers
-BOOL LLVoiceClient::getVoiceEnabled(const LLUUID& id)
+LLVoiceClient::buddyListEntry::buddyListEntry(const std::string &uri) :
+	mURI(uri)
 {
-	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;
+	mOnlineSL = false;
+	mOnlineSLim = false;
+	mCanSeeMeOnline = true;
+	mHasBlockListEntry = false;
+	mHasAutoAcceptListEntry = false;
+	mNameResolved = false;
+	mInVivoxBuddies = false;
+	mInSLFriends = false;
+	mNeedsNameUpdate = false;
 }
 
-BOOL LLVoiceClient::getIsSpeaking(const LLUUID& id)
+void LLVoiceClient::processBuddyListEntry(const std::string &uri, const std::string &displayName)
 {
-	BOOL result = FALSE;
+	buddyListEntry *buddy = addBuddy(uri, displayName);
+	buddy->mInVivoxBuddies = true;	
+}
 
-	participantState *participant = findParticipantByID(id);
-	if(participant)
+LLVoiceClient::buddyListEntry *LLVoiceClient::addBuddy(const std::string &uri)
+{
+	std::string empty;
+	buddyListEntry *buddy = addBuddy(uri, empty);
+	if(buddy->mDisplayName.empty())
 	{
-		if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT)
-		{
-			participant->mIsSpeaking = FALSE;
-		}
-		result = participant->mIsSpeaking;
+		buddy->mNameResolved = false;
 	}
-	
-	return result;
+	return buddy;
 }
 
-BOOL LLVoiceClient::getIsModeratorMuted(const LLUUID& id)
+LLVoiceClient::buddyListEntry *LLVoiceClient::addBuddy(const std::string &uri, const std::string &displayName)
 {
-	BOOL result = FALSE;
+	buddyListEntry *result = NULL;
+	buddyListMap::iterator iter = mBuddyListMap.find(&uri);
+	
+	if(iter != mBuddyListMap.end())
+	{
+		// Found a matching buddy already in the map.
+		LL_DEBUGS("Voice") << "adding existing buddy " << uri << LL_ENDL;
+		result = iter->second;
+	}
 
-	participantState *participant = findParticipantByID(id);
-	if(participant)
+	if(!result)
 	{
-		result = participant->mIsModeratorMuted;
+		// participant isn't already in one list or the other.
+		LL_DEBUGS("Voice") << "adding new buddy " << uri << LL_ENDL;
+		result = new buddyListEntry(uri);
+		result->mDisplayName = displayName;
+
+		if(IDFromName(uri, result->mUUID)) 
+		{
+			// Extracted UUID from name successfully.
+		}
+		else
+		{
+			LL_DEBUGS("Voice") << "Couldn't find ID for buddy " << uri << " (\"" << displayName << "\")" << LL_ENDL;
+		}
+
+		mBuddyListMap.insert(buddyListMap::value_type(&(result->mURI), result));
 	}
 	
 	return result;
 }
 
-F32 LLVoiceClient::getCurrentPower(const LLUUID& id)
-{		
-	F32 result = 0;
-	participantState *participant = findParticipantByID(id);
-	if(participant)
+LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddy(const std::string &uri)
+{
+	buddyListEntry *result = NULL;
+	buddyListMap::iterator iter = mBuddyListMap.find(&uri);
+	if(iter != mBuddyListMap.end())
 	{
-		result = participant->mPower;
+		result = iter->second;
 	}
 	
 	return result;
 }
 
-
-std::string LLVoiceClient::getDisplayName(const LLUUID& id)
+LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddy(const LLUUID &id)
 {
-	std::string result;
-	participantState *participant = findParticipantByID(id);
-	if(participant)
+	buddyListEntry *result = NULL;
+	buddyListMap::iterator iter;
+
+	for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++)
 	{
-		result = participant->mDisplayName;
+		if(iter->second->mUUID == id)
+		{
+			result = iter->second;
+			break;
+		}
 	}
 	
 	return result;
 }
 
-
-BOOL LLVoiceClient::getUsingPTT(const LLUUID& id)
+LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddyByDisplayName(const std::string &name)
 {
-	BOOL result = FALSE;
+	buddyListEntry *result = NULL;
+	buddyListMap::iterator iter;
 
-	participantState *participant = findParticipantByID(id);
-	if(participant)
+	for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++)
 	{
-		// 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.
+		if(iter->second->mDisplayName == name)
+		{
+			result = iter->second;
+			break;
+		}
 	}
 	
 	return result;
 }
 
-BOOL LLVoiceClient::getPTTPressed(const LLUUID& id)
+void LLVoiceClient::deleteBuddy(const std::string &uri)
 {
-	BOOL result = FALSE;
-	
-	participantState *participant = findParticipantByID(id);
-	if(participant)
+	buddyListMap::iterator iter = mBuddyListMap.find(&uri);
+	if(iter != mBuddyListMap.end())
+	{
+		LL_DEBUGS("Voice") << "deleting buddy " << uri << LL_ENDL;
+		buddyListEntry *buddy = iter->second;
+		mBuddyListMap.erase(iter);
+		delete buddy;
+	}
+	else
 	{
-		result = participant->mPTT;
+		LL_DEBUGS("Voice") << "attempt to delete nonexistent buddy " << uri << LL_ENDL;
 	}
 	
-	return result;
 }
 
-BOOL LLVoiceClient::getOnMuteList(const LLUUID& id)
+void LLVoiceClient::deleteAllBuddies(void)
 {
-	BOOL result = FALSE;
-	
-	participantState *participant = findParticipantByID(id);
-	if(participant)
+	while(!mBuddyListMap.empty())
 	{
-		result = participant->mOnMuteList;
+		deleteBuddy(*(mBuddyListMap.begin()->first));
 	}
-
-	return result;
+	
+	// Don't want to correlate with friends list when we've emptied the buddy list.
+	mBuddyListMapPopulated = false;
+	
+	// Don't want to correlate with friends list when we've reset the block rules.
+	mBlockRulesListReceived = false;
+	mAutoAcceptRulesListReceived = false;
 }
 
-// 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)
+void LLVoiceClient::deleteAllBlockRules(void)
 {
-	F32 result = 0.0f;
-	
-	participantState *participant = findParticipantByID(id);
-	if(participant)
+	// Clear the block list entry flags from all local buddy list entries
+	buddyListMap::iterator buddy_it;
+	for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++)
 	{
-		S32 ires = participant->mUserVolume; // 0-400
-		result = sqrtf(((F32)ires) / 400.f);
+		buddy_it->second->mHasBlockListEntry = false;
 	}
-
-	return result;
 }
 
-void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume)
+void LLVoiceClient::deleteAllAutoAcceptRules(void)
 {
-	participantState *participant = findParticipantByID(id);
-	if (participant)
+	// Clear the auto-accept list entry flags from all local buddy list entries
+	buddyListMap::iterator buddy_it;
+	for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++)
 	{
-		// 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;
+		buddy_it->second->mHasAutoAcceptListEntry = false;
 	}
 }
 
-
-
-LLVoiceClient::serviceType LLVoiceClient::getServiceType(const LLUUID& id)
+void LLVoiceClient::addBlockRule(const std::string &blockMask, const std::string &presenceOnly)
 {
-	serviceType result = serviceTypeUnknown;
+	buddyListEntry *buddy = NULL;
 
-	participantState *participant = findParticipantByID(id);
-	if(participant)
+	// blockMask is the SIP URI of a friends list entry
+	buddyListMap::iterator iter = mBuddyListMap.find(&blockMask);
+	if(iter != mBuddyListMap.end())
 	{
-		result = participant->mServiceType;
+		LL_DEBUGS("Voice") << "block list entry for " << blockMask << LL_ENDL;
+		buddy = iter->second;
+	}
+
+	if(buddy == NULL)
+	{
+		LL_DEBUGS("Voice") << "block list entry for unknown buddy " << blockMask << LL_ENDL;
+		buddy = addBuddy(blockMask);
 	}
 	
-	return result;
+	if(buddy != NULL)
+	{
+		buddy->mHasBlockListEntry = true;
+	}
 }
 
-std::string LLVoiceClient::getGroupID(const LLUUID& id)
+void LLVoiceClient::addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy)
 {
-	std::string result;
+	buddyListEntry *buddy = NULL;
 
-	participantState *participant = findParticipantByID(id);
-	if(participant)
+	// blockMask is the SIP URI of a friends list entry
+	buddyListMap::iterator iter = mBuddyListMap.find(&autoAcceptMask);
+	if(iter != mBuddyListMap.end())
 	{
-		result = participant->mGroupID;
+		LL_DEBUGS("Voice") << "auto-accept list entry for " << autoAcceptMask << LL_ENDL;
+		buddy = iter->second;
+	}
+
+	if(buddy == NULL)
+	{
+		LL_DEBUGS("Voice") << "auto-accept list entry for unknown buddy " << autoAcceptMask << LL_ENDL;
+		buddy = addBuddy(autoAcceptMask);
+	}
+
+	if(buddy != NULL)
+	{
+		buddy->mHasAutoAcceptListEntry = true;
 	}
-	
-	return result;
 }
 
-BOOL LLVoiceClient::getAreaVoiceDisabled()
+void LLVoiceClient::accountListBlockRulesResponse(int statusCode, const std::string &statusString)
 {
-	return mAreaVoiceDisabled;
+	// Block list entries were updated via addBlockRule() during parsing.  Just flag that we're done.
+	mBlockRulesListReceived = true;
+}
+
+void LLVoiceClient::accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString)
+{
+	// Block list entries were updated via addBlockRule() during parsing.  Just flag that we're done.
+	mAutoAcceptRulesListReceived = true;
 }
 
 void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer)
 {
-	mObservers.insert(observer);
+	mParticipantObservers.insert(observer);
 }
 
 void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer)
 {
-	mObservers.erase(observer);
+	mParticipantObservers.erase(observer);
 }
 
-void LLVoiceClient::notifyObservers()
+void LLVoiceClient::notifyParticipantObservers()
 {
-	for (observer_set_t::iterator it = mObservers.begin();
-		it != mObservers.end();
+	for (observer_set_t::iterator it = mParticipantObservers.begin();
+		it != mParticipantObservers.end();
 		)
 	{
 		LLVoiceClientParticipantObserver* observer = *it;
 		observer->onChange();
 		// In case onChange() deleted an entry.
-		it = mObservers.upper_bound(observer);
+		it = mParticipantObservers.upper_bound(observer);
 	}
 }
 
-void LLVoiceClient::addStatusObserver(LLVoiceClientStatusObserver* observer)
+void LLVoiceClient::addObserver(LLVoiceClientStatusObserver* observer)
 {
 	mStatusObservers.insert(observer);
 }
 
-void LLVoiceClient::removeStatusObserver(LLVoiceClientStatusObserver* observer)
+void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer)
 {
 	mStatusObservers.erase(observer);
 }
 
 void LLVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status)
 {
-	if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN)
+	if(mAudioSession)
 	{
-		switch(mVivoxErrorStatusCode)
+		if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN)
 		{
-		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;
+			switch(mAudioSession->mErrorStatusCode)
+			{
+				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;
+			// Reset the error code to make sure it won't be reused later by accident.
+			mAudioSession->mErrorStatusCode = 0;
+		}
+		else if(status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL)
+		{
+			switch(mAudioSession->mErrorStatusCode)
+			{
+				case 404:	// NOT_FOUND
+				case 480:	// TEMPORARILY_UNAVAILABLE
+				case 408:	// REQUEST_TIMEOUT
+					// 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.
+					mAudioSession->mErrorStatusCode = 0;
+				break;
+			}
+		}
 	}
-	
-	LL_DEBUGS("Voice") << " " << LLVoiceClientStatusObserver::status2string(status)  << ", session URI " << mSessionURI << LL_ENDL;
+		
+	LL_DEBUGS("Voice") 
+		<< " " << LLVoiceClientStatusObserver::status2string(status)  
+		<< ", session URI " << getAudioSessionURI() 
+		<< (inSpatialChannel()?", proximal is true":", proximal is false")
+	<< LL_ENDL;
 
 	for (status_observer_set_t::iterator it = mStatusObservers.begin();
 		it != mStatusObservers.end();
 		)
 	{
 		LLVoiceClientStatusObserver* observer = *it;
-		observer->onChange(status, mSessionURI, !mNonSpatialChannel);
+		observer->onChange(status, getAudioSessionURI(), inSpatialChannel());
 		// In case onError() deleted an entry.
 		it = mStatusObservers.upper_bound(observer);
 	}
 
 }
 
+void LLVoiceClient::addObserver(LLFriendObserver* observer)
+{
+	mFriendObservers.insert(observer);
+}
+
+void LLVoiceClient::removeObserver(LLFriendObserver* observer)
+{
+	mFriendObservers.erase(observer);
+}
+
+void LLVoiceClient::notifyFriendObservers()
+{
+	for (friend_observer_set_t::iterator it = mFriendObservers.begin();
+		it != mFriendObservers.end();
+		)
+	{
+		LLFriendObserver* observer = *it;
+		it++;
+		// The only friend-related thing we notify on is online/offline transitions.
+		observer->changed(LLFriendObserver::ONLINE);
+	}
+}
+
+void LLVoiceClient::lookupName(const LLUUID &id)
+{
+	gCacheName->getName(id, onAvatarNameLookup);
+}
+
 //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);
+void LLVoiceClient::onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data)
+{
+	if(gVoiceClient)
+	{
+		std::string name = llformat("%s %s", first.c_str(), last.c_str());
+		gVoiceClient->avatarNameResolved(id, name);
+	}
+}
 
-// 	if (statep)
-// 	{
-// 		statep->mDisplayName = first + " " + last;
-// 	}
+void LLVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name)
+{
+	// If the avatar whose name just resolved is on our friends list, resync the friends list.
+	if(LLAvatarTracker::instance().getBuddyInfo(id) != NULL)
+	{
+		mFriendsListDirty = true;
+	}
 	
-// 	gVoiceClient->notifyObservers();
-// }
+	// Iterate over all sessions.
+	for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++)
+	{
+		sessionState *session = *iter;
+
+		// Check for this user as a participant in this session
+		participantState *participant = session->findParticipantByID(id);
+		if(participant)
+		{
+			// Found -- fill in the name
+			participant->mAccountName = name;
+			// and post a "participants updated" message to listeners later.
+			session->mParticipantsChanged = true;
+		}
+		
+		// Check whether this is a p2p session whose caller name just resolved
+		if(session->mCallerID == id)
+		{
+			// this session's "caller ID" just resolved.  Fill in the name.
+			session->mName = name;
+			if(session->mTextInvitePending)
+			{
+				session->mTextInvitePending = false;
+
+				// We don't need to call gIMMgr->addP2PSession() here.  The first incoming message will create the panel.				
+			}
+			if(session->mVoiceInvitePending)
+			{
+				session->mVoiceInvitePending = false;
+
+				gIMMgr->inviteToSession(
+					session->mIMSessionID,
+					session->mName,
+					session->mCallerID, 
+					session->mName, 
+					IM_SESSION_P2P_INVITE, 
+					LLIMMgr::INVITATION_TYPE_VOICE,
+					session->mHandle,
+					session->mSIPURI);
+			}
+			
+		}
+	}
+}
 
 class LLViewerParcelVoiceInfo : public LLHTTPNode
 {
diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h
index 4ffe5edf717..43bbc8e29c1 100644
--- a/indra/newview/llvoiceclient.h
+++ b/indra/newview/llvoiceclient.h
@@ -42,6 +42,7 @@ class LLVivoxProtocolParser;
 #include "v3math.h"
 #include "llframetimer.h"
 #include "llviewerregion.h"
+#include "llcallingcard.h"   // for LLFriendObserver
 
 class LLVoiceClientParticipantObserver
 {
@@ -92,41 +93,10 @@ class LLVoiceClient: public LLSingleton<LLVoiceClient>
 
 	public:
 		
-		enum serviceType
-		{
-			serviceTypeUnknown,	// Unknown, returned if no data on the avatar is available
-			serviceTypeA,		// spatialized local chat
-			serviceTypeB,		// remote multi-party chat
-			serviceTypeC		// one-to-one and small group chat
-		};
 		static F32 OVERDRIVEN_POWER_LEVEL;
 
 		void updateSettings(); // call after loading settings and whenever they change
 	
-		/////////////////////////////
-		// session control messages
-		void connect();
-
-		void connectorCreate();
-		void connectorShutdown();
-
-		void requestVoiceAccountProvision(S32 retries = 3);
-		void userAuthorized(
-			const std::string& firstName,
-			const std::string& lastName,
-			const LLUUID &agentID);
-		void login(const std::string& accountName, const std::string &password);
-		void loginSendMessage();
-		void logout();
-		void logoutSendMessage();
-		
-		void channelGetListSendMessage();
-		void sessionCreateSendMessage();
-		void sessionConnectSendMessage();
-		void sessionTerminate();
-		void sessionTerminateSendMessage();
-		void sessionTerminateByHandle(std::string &sessionHandle);
-		
 		void getCaptureDevicesSendMessage();
 		void getRenderDevicesSendMessage();
 		
@@ -171,23 +141,32 @@ class LLVoiceClient: public LLSingleton<LLVoiceClient>
 		
 		/////////////////////////////
 		// Response/Event handlers
-		void connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle);
-		void loginResponse(int statusCode, std::string &statusString, std::string &accountHandle);
-		void channelGetListResponse(int statusCode, std::string &statusString);
-		void sessionCreateResponse(int statusCode, std::string &statusString, std::string &sessionHandle);
-		void sessionConnectResponse(int statusCode, std::string &statusString);
-		void sessionTerminateResponse(int statusCode, std::string &statusString);
+		void connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID);
+		void loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases);
+		void sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle);
+		void sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle);
+		void sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString);
 		void logoutResponse(int statusCode, std::string &statusString);
 		void connectorShutdownResponse(int statusCode, std::string &statusString);
 
-		void loginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state);
-		void sessionNewEvent(std::string &accountHandle, std::string &eventSessionHandle, int state, std::string &nameString, std::string &uriString);
-		void sessionStateChangeEvent(std::string &uriString, int statusCode, std::string &statusString, std::string &sessionHandle, int state,  bool isChannel, std::string &nameString);
-		void participantStateChangeEvent(std::string &uriString, int statusCode, std::string &statusString, int state,  std::string &nameString, std::string &displayNameString, int participantType);
-		void participantPropertiesEvent(std::string &uriString, int statusCode, std::string &statusString, bool isLocallyMuted, bool isModeratorMuted, bool isSpeaking, int volume, F32 energy);
+		void accountLoginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state);
+		void mediaStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, int statusCode, std::string &statusString, int state, bool incoming);
+		void textStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, bool enabled, int state, bool incoming);
+		void sessionAddedEvent(std::string &uriString, std::string &alias, std::string &sessionHandle, std::string &sessionGroupHandle, bool isChannel, bool incoming, std::string &nameString, std::string &applicationString);
+		void sessionGroupAddedEvent(std::string &sessionGroupHandle);
+		void sessionRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle);
+		void participantAddedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString, std::string &displayNameString, int participantType);
+		void participantRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString);
+		void participantUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, bool isModeratorMuted, bool isSpeaking, int volume, F32 energy);
 		void auxAudioPropertiesEvent(F32 energy);
-	
+		void buddyPresenceEvent(std::string &uriString, std::string &alias, std::string &statusString, std::string &applicationString);
+		void messageEvent(std::string &sessionHandle, std::string &uriString, std::string &alias, std::string &messageHeader, std::string &messageBody, std::string &applicationString);
+		void sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string &notificationType);
+		void subscriptionEvent(std::string &buddyURI, std::string &subscriptionHandle, std::string &alias, std::string &displayName, std::string &applicationString, std::string &subscriptionType);
+		
+		void buddyListChanged();
 		void muteListChanged();
+		void updateFriends(U32 mask);
 		
 		/////////////////////////////
 		// Sending updates of current state
@@ -211,7 +190,6 @@ static	void updatePosition(void);
 		void setVoiceVolume(F32 volume);
 		void setMicGain(F32 volume);
 		void setUserVolume(const LLUUID& id, F32 volume); // set's volume for specified agent, from 0-1 (where .5 is nominal)
-		void setVivoxDebugServerName(std::string &serverName);
 		void setLipSyncEnabled(BOOL enabled);
 		BOOL lipSyncEnabled();
 
@@ -226,57 +204,261 @@ static	void updatePosition(void);
 		BOOL getIsSpeaking(const LLUUID& id);
 		BOOL getIsModeratorMuted(const LLUUID& id);
 		F32 getCurrentPower(const LLUUID& id);		// "power" is related to "amplitude" in a defined way.  I'm just not sure what the formula is...
-		BOOL getPTTPressed(const LLUUID& id);			// This is the inverse of the "locally muted" property.
 		BOOL getOnMuteList(const LLUUID& id);
 		F32 getUserVolume(const LLUUID& id);
 		std::string getDisplayName(const LLUUID& id);
 		
 		// MBW -- XXX -- Not sure how to get this data out of the TVC
 		BOOL getUsingPTT(const LLUUID& id);
-		serviceType getServiceType(const LLUUID& id);	// type of chat the user is involved in (see bHear scope doc for definitions of A/B/C)
 		std::string getGroupID(const LLUUID& id);		// group ID if the user is in group chat (empty string if not applicable)
 
 		/////////////////////////////
 		BOOL getAreaVoiceDisabled();		// returns true if the area the avatar is in is speech-disabled.
 											// Use this to determine whether to show a "no speech" icon in the menu bar.
+		
+		/////////////////////////////
+		// Recording controls
+		void recordingLoopStart(int seconds = 3600, int deltaFramesPerControlFrame = 200);
+		void recordingLoopSave(const std::string& filename);
+		void recordingStop();
+		
+		// Playback controls
+		void filePlaybackStart(const std::string& filename);
+		void filePlaybackStop();
+		void filePlaybackSetPaused(bool paused);
+		void filePlaybackSetMode(bool vox = false, float speed = 1.0f);
+		
+		
+		// This is used by the string-keyed maps below, to avoid storing the string twice.
+		// The 'const std::string *' in the key points to a string actually stored in the object referenced by the map.
+		// The add and delete operations for each map allocate and delete in the right order to avoid dangling references.
+		// The default compare operation would just compare pointers, which is incorrect, so they must use this comparitor instead.
+		struct stringMapComparitor
+		{
+			bool operator()(const std::string* a, const std::string * b) const
+			{
+				return a->compare(*b) < 0;
+			}
+		};
 
+		struct uuidMapComparitor
+		{
+			bool operator()(const LLUUID* a, const LLUUID * b) const
+			{
+				return *a < *b;
+			}
+		};
+		
 		struct participantState
 		{
 		public:
 			participantState(const std::string &uri);
+
+			bool updateMuteState();
+
 			std::string mURI;
-			std::string mName;
+			LLUUID mAvatarID;
+			std::string mAccountName;
 			std::string mDisplayName;
-			bool mPTT;
-			bool mIsSpeaking;
-			bool mIsModeratorMuted;
 			LLFrameTimer mSpeakingTimeout;
 			F32	mLastSpokeTimestamp;
 			F32 mPower;
 			int mVolume;
-			serviceType mServiceType;
 			std::string mGroupID;
-			bool mOnMuteList;		// true if this avatar is on the user's mute list (and should be muted)
 			int mUserVolume;
+			bool mPTT;
+			bool mIsSpeaking;
+			bool mIsModeratorMuted;
+			bool mOnMuteList;		// true if this avatar is on the user's mute list (and should be muted)
 			bool mVolumeDirty;		// true if this participant needs a volume command sent (either mOnMuteList or mUserVolume has changed)
 			bool mAvatarIDValid;
-			LLUUID mAvatarID;
+			bool mIsSelf;
+		};
+		typedef std::map<const std::string *, participantState*, stringMapComparitor> participantMap;
+
+		typedef std::map<const LLUUID *, participantState*, uuidMapComparitor> participantUUIDMap;
+	
+		enum streamState
+		{
+			streamStateUnknown = 0,
+			streamStateIdle = 1,
+			streamStateConnected = 2,
+			streamStateRinging = 3,
 		};
-		typedef std::map<std::string, participantState*> participantMap;
 		
-		participantState *findParticipant(const std::string &uri);
-		participantState *findParticipantByAvatar(LLVOAvatar *avatar);
+		struct sessionState
+		{
+		public:
+			sessionState();
+			~sessionState();
+
+			participantState *addParticipant(const std::string &uri);
+			// Note: after removeParticipant returns, the participant* that was passed to it will have been deleted.
+			// Take care not to use the pointer again after that.
+			void removeParticipant(participantState *participant);
+			void removeAllParticipants();
+
+			participantState *findParticipant(const std::string &uri);
+			participantState *findParticipantByID(const LLUUID& id);
+
+			std::string mHandle;
+			std::string mGroupHandle;
+			std::string mSIPURI;
+			std::string mAlias;
+			std::string mName;
+			std::string mAlternateSIPURI;
+			std::string mHash;			// Channel password
+			std::string mErrorStatusString;
+			std::queue<std::string> mTextMsgQueue;
+			
+			LLUUID		mIMSessionID;
+			LLUUID		mCallerID;
+			int			mErrorStatusCode;
+			int			mMediaStreamState;
+			int			mTextStreamState;
+			bool		mCreateInProgress;	// True if a Session.Create has been sent for this session and no response has been received yet.
+			bool		mMediaConnectInProgress;	// True if a Session.MediaConnect has been sent for this session and no response has been received yet.
+			bool		mVoiceInvitePending;	// True if a voice invite is pending for this session (usually waiting on a name lookup)
+			bool		mTextInvitePending;		// True if a text invite is pending for this session (usually waiting on a name lookup)
+			bool		mSynthesizedCallerID;	// True if the caller ID is a hash of the SIP URI -- this means we shouldn't do a name lookup.
+			bool		mIsChannel;	// True for both group and spatial channels (false for p2p, PSTN)
+			bool		mIsSpatial;	// True for spatial channels
+			bool		mIsP2P;
+			bool		mIncoming;
+			bool		mVoiceEnabled;
+			bool		mReconnect;	// Whether we should try to reconnect to this session if it's dropped
+			// Set to true when the mute state of someone in the participant list changes.
+			// The code will have to walk the list to find the changed participant(s).
+			bool		mVolumeDirty;
+
+			bool		mParticipantsChanged;
+			participantMap mParticipantsByURI;
+			participantUUIDMap mParticipantsByUUID;
+		};
+
 		participantState *findParticipantByID(const LLUUID& id);
-		
 		participantMap *getParticipantList(void);
+		
+		typedef std::map<const std::string*, sessionState*, stringMapComparitor> sessionMap;
+		typedef std::set<sessionState*> sessionSet;
+				
+		typedef sessionSet::iterator sessionIterator;
+		sessionIterator sessionsBegin(void);
+		sessionIterator sessionsEnd(void);
+
+		sessionState *findSession(const std::string &handle);
+		sessionState *findSessionBeingCreatedByURI(const std::string &uri);
+		sessionState *findSession(const LLUUID &participant_id);
+		sessionState *findSessionByCreateID(const std::string &create_id);
+		
+		sessionState *addSession(const std::string &uri, const std::string &handle = LLStringUtil::null);
+		void setSessionHandle(sessionState *session, const std::string &handle = LLStringUtil::null);
+		void setSessionURI(sessionState *session, const std::string &uri);
+		void deleteSession(sessionState *session);
+		void deleteAllSessions(void);
+
+		void verifySessionState(void);
+
+		void joinedAudioSession(sessionState *session);
+		void leftAudioSession(sessionState *session);
+
+		// This is called in several places where the session _may_ need to be deleted.
+		// It contains logic for whether to delete the session or keep it around.
+		void reapSession(sessionState *session);
+		
+		// Returns true if the session seems to indicate we've moved to a region on a different voice server
+		bool sessionNeedsRelog(sessionState *session);
+		
+		struct buddyListEntry
+		{
+			buddyListEntry(const std::string &uri);
+			std::string mURI;
+			std::string mDisplayName;
+			LLUUID	mUUID;
+			bool mOnlineSL;
+			bool mOnlineSLim;
+			bool mCanSeeMeOnline;
+			bool mHasBlockListEntry;
+			bool mHasAutoAcceptListEntry;
+			bool mNameResolved;
+			bool mInSLFriends;
+			bool mInVivoxBuddies;
+			bool mNeedsNameUpdate;
+		};
+
+		typedef std::map<const std::string*, buddyListEntry*, stringMapComparitor> buddyListMap;
+		
+		// This should be called when parsing a buddy list entry sent by SLVoice.		
+		void processBuddyListEntry(const std::string &uri, const std::string &displayName);
+
+		buddyListEntry *addBuddy(const std::string &uri);
+		buddyListEntry *addBuddy(const std::string &uri, const std::string &displayName);
+		buddyListEntry *findBuddy(const std::string &uri);
+		buddyListEntry *findBuddy(const LLUUID &id);
+		buddyListEntry *findBuddyByDisplayName(const std::string &name);
+		void deleteBuddy(const std::string &uri);
+		void deleteAllBuddies(void);
+
+		void deleteAllBlockRules(void);
+		void addBlockRule(const std::string &blockMask, const std::string &presenceOnly);
+		void deleteAllAutoAcceptRules(void);
+		void addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy);
+		void accountListBlockRulesResponse(int statusCode, const std::string &statusString);						
+		void accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString);						
+		
+		/////////////////////////////
+		// session control messages
+		void connectorCreate();
+		void connectorShutdown();
 
+		void requestVoiceAccountProvision(S32 retries = 3);
+		void userAuthorized(
+			const std::string& firstName,
+			const std::string& lastName,
+			const LLUUID &agentID);
+		void login(
+			const std::string& account_name,
+			const std::string& password,
+			const std::string& voice_sip_uri_hostname,
+			const std::string& voice_account_server_uri);
+		void loginSendMessage();
+		void logout();
+		void logoutSendMessage();
+
+		void accountListBlockRulesSendMessage();
+		void accountListAutoAcceptRulesSendMessage();
+		
+		void sessionGroupCreateSendMessage();
+		void sessionCreateSendMessage(sessionState *session, bool startAudio = true, bool startText = false);
+		void sessionGroupAddSessionSendMessage(sessionState *session, bool startAudio = true, bool startText = false);
+		void sessionMediaConnectSendMessage(sessionState *session);		// just joins the audio session
+		void sessionTextConnectSendMessage(sessionState *session);		// just joins the text session
+		void sessionTerminateSendMessage(sessionState *session);
+		void sessionMediaDisconnectSendMessage(sessionState *session);
+		void sessionTextDisconnectSendMessage(sessionState *session);
+
+		// Pokes the state machine to leave the audio session next time around.
+		void sessionTerminate();	
+		
+		// Pokes the state machine to shut down the connector and restart it.
+		void requestRelog();
+		
+		// Does the actual work to get out of the audio session
+		void leaveAudioSession();
+		
 		void addObserver(LLVoiceClientParticipantObserver* observer);
 		void removeObserver(LLVoiceClientParticipantObserver* observer);
 
-		void addStatusObserver(LLVoiceClientStatusObserver* observer);
-		void removeStatusObserver(LLVoiceClientStatusObserver* observer);
+		void addObserver(LLVoiceClientStatusObserver* observer);
+		void removeObserver(LLVoiceClientStatusObserver* observer);
+
+		void addObserver(LLFriendObserver* observer);
+		void removeObserver(LLFriendObserver* observer);
+		
+		void lookupName(const LLUUID &id);
+		static void onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data);
+		void avatarNameResolved(const LLUUID &id, const std::string &name);
 		
-// 		static void onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* user_data);
 		typedef std::vector<std::string> deviceList;
 
 		deviceList *getCaptureDevices();
@@ -288,8 +470,16 @@ static	void updatePosition(void);
 		void setSpatialChannel(
 			const std::string &uri,
 			const std::string &credentials);
-		void callUser(LLUUID &uuid);
-		void answerInvite(std::string &sessionHandle, LLUUID& other_user_id);
+		// start a voice session with the specified user
+		void callUser(const LLUUID &uuid);
+		
+		// Send a text message to the specified user, initiating the session if necessary.
+		bool sendTextMessage(const LLUUID& participant_id, const std::string& message);
+		
+		// close any existing text IM session with the specified user
+		void endUserIMSession(const LLUUID &uuid);
+		
+		bool answerInvite(std::string &sessionHandle);
 		void declineInvite(std::string &sessionHandle);
 		void leaveNonSpatialChannel();
 
@@ -302,33 +492,37 @@ static	void updatePosition(void);
 		bool inProximalChannel();
 
 		std::string sipURIFromID(const LLUUID &id);
-
+				
+		// Returns true if the indicated user is online via SIP presence according to SLVoice.
+		// Note that we only get SIP presence data for other users that are in our vivox buddy list.
+		bool isOnlineSIP(const LLUUID &id);
+		
 	private:
 
 		// internal state for a simple state machine.  This is used to deal with the asynchronous nature of some of the messages.
 		// Note: if you change this list, please make corresponding changes to LLVoiceClient::state2string().
 		enum state
 		{
+			stateDisableCleanup,
 			stateDisabled,				// Voice is turned off.
 			stateStart,					// Class is initialized, socket is created
 			stateDaemonLaunched,		// Daemon has been launched
 			stateConnecting,			// connect() call has been issued
+			stateConnected,				// connection to the daemon has been made, send some initial setup commands.
 			stateIdle,					// socket is connected, ready for messaging
+			stateMicTuningStart,
+			stateMicTuningRunning,		
+			stateMicTuningStop,
 			stateConnectorStart,		// connector needs to be started
 			stateConnectorStarting,		// waiting for connector handle
 			stateConnectorStarted,		// connector handle received
-			stateMicTuningNoLogin,		// mic tuning before login
 			stateLoginRetry,			// need to retry login (failed due to changing password)
 			stateLoginRetryWait,		// waiting for retry timer
 			stateNeedsLogin,			// send login request
 			stateLoggingIn,				// waiting for account handle
 			stateLoggedIn,				// account handle received
+			stateCreatingSessionGroup,	// Creating the main session group
 			stateNoChannel,				// 
-			stateMicTuningStart,
-			stateMicTuningRunning,		
-			stateMicTuningStop,
-			stateSessionCreate,			// need to send Session.Create command
-			stateSessionConnect,		// need to send Session.Connect command
 			stateJoiningSession,		// waiting for session handle
 			stateSessionJoined,			// session handle received
 			stateRunning,				// in session, steady state
@@ -355,7 +549,7 @@ static	void updatePosition(void);
 		
 		state mState;
 		bool mSessionTerminateRequested;
-		bool mNonSpatialChannel;
+		bool mRelogRequested;
 		
 		void setState(state inState);
 		state getState(void)  { return mState; };
@@ -378,18 +572,7 @@ static	void updatePosition(void);
 		std::string mAccountDisplayName;
 		std::string mAccountFirstName;
 		std::string mAccountLastName;
-		
-		std::string mNextP2PSessionURI;		// URI of the P2P session to join next
-		std::string mNextSessionURI;		// URI of the session to join next
-		std::string mNextSessionHandle;		// Session handle of the session to join next
-		std::string mNextSessionHash;		// Password hash for the session to join next
-		bool mNextSessionSpatial;			// Will next session be a spatial chat?
-		bool mNextSessionNoReconnect;		// Next session should not auto-reconnect (i.e. user -> user chat)
-		bool mNextSessionResetOnClose;		// If this is true, go back to spatial chat when the next session terminates.
-		
-		std::string mSessionStateEventHandle;	// session handle received in SessionStateChangeEvents
-		std::string mSessionStateEventURI;		// session URI received in SessionStateChangeEvents
-		
+				
 		bool mTuningMode;
 		float mTuningEnergy;
 		std::string mTuningAudioFile;
@@ -400,32 +583,40 @@ static	void updatePosition(void);
 		state mTuningExitState;					// state to return to when we leave tuning mode.
 		
 		std::string mSpatialSessionURI;
-		
-		bool mSessionResetOnClose;
-		
-		int mVivoxErrorStatusCode;		
-		std::string mVivoxErrorStatusString;
+		std::string mSpatialSessionCredentials;
+
+		std::string mMainSessionGroupHandle; // handle of the "main" session group.
 		
 		std::string mChannelName;			// Name of the channel to be looked up 
 		bool mAreaVoiceDisabled;
-		std::string mSessionURI;			// URI of the session we're in.
-		bool mSessionP2P;					// true if this session is a p2p call
+		sessionState *mAudioSession;		// Session state for the current audio session
+		bool mAudioSessionChanged;			// set to true when the above pointer gets changed, so observers can be notified.
+
+		sessionState *mNextAudioSession;	// Session state for the audio session we're trying to join
+
+//		std::string mSessionURI;			// URI of the session we're in.
+//		std::string mSessionHandle;		// returned by ?
 		
 		S32 mCurrentParcelLocalID;			// Used to detect parcel boundary crossings
 		std::string mCurrentRegionName;		// Used to detect parcel boundary crossings
 		
 		std::string mConnectorHandle;	// returned by "Create Connector" message
 		std::string mAccountHandle;		// returned by login message		
-		std::string mSessionHandle;		// returned by ?
+		int 		mNumberOfAliases;
 		U32 mCommandCookie;
 	
-		std::string mAccountServerName;
-		std::string mAccountServerURI;
+		std::string mVoiceAccountServerURI;
+		std::string mVoiceSIPURIHostName;
 		
 		int mLoginRetryCount;
 		
-		participantMap mParticipantMap;
-		bool mParticipantMapChanged;
+		sessionMap mSessionsByHandle;				// Active sessions, indexed by session handle.  Sessions which are being initiated may not be in this map.
+		sessionSet mSessions;						// All sessions, not indexed.  This is the canonical session list.
+		
+		bool mBuddyListMapPopulated;
+		bool mBlockRulesListReceived;
+		bool mAutoAcceptRulesListReceived;
+		buddyListMap mBuddyListMap;
 		
 		deviceList mCaptureDevices;
 		deviceList mRenderDevices;
@@ -435,40 +626,41 @@ static	void updatePosition(void);
 		bool mCaptureDeviceDirty;
 		bool mRenderDeviceDirty;
 		
-		participantState *addParticipant(const std::string &uri);
-		// Note: after removeParticipant returns, the participant* that was passed to it will have been deleted.
-		// Take care not to use the pointer again after that.
-		void removeParticipant(participantState *participant);
-		void removeAllParticipants();
-
-		void updateMuteState(participantState *participant);
-
-		typedef std::map<std::string, std::string> channelMap;
-		channelMap mChannelMap;
-		
-		// These are used by the parser when processing a channel list response.
-		void clearChannelMap(void);
-		void addChannelMapEntry(std::string &name, std::string &uri);
-		std::string findChannelURI(std::string &name);
-			
 		// This should be called when the code detects we have changed parcels.
 		// It initiates the call to the server that gets the parcel channel.
 		void parcelChanged();
 		
-	void switchChannel(std::string uri = std::string(), bool spatial = true, bool noReconnect = false, std::string hash = "");
-		void joinSession(std::string handle, std::string uri);
+	void switchChannel(std::string uri = std::string(), bool spatial = true, bool no_reconnect = false, bool is_p2p = false, std::string hash = "");
+		void joinSession(sessionState *session);
 		
-		std::string nameFromAvatar(LLVOAvatar *avatar);
-		std::string nameFromID(const LLUUID &id);
-		bool IDFromName(const std::string name, LLUUID &uuid);
-		std::string displayNameFromAvatar(LLVOAvatar *avatar);
+static 	std::string nameFromAvatar(LLVOAvatar *avatar);
+static	std::string nameFromID(const LLUUID &id);
+static	bool IDFromName(const std::string name, LLUUID &uuid);
+static	std::string displayNameFromAvatar(LLVOAvatar *avatar);
 		std::string sipURIFromAvatar(LLVOAvatar *avatar);
 		std::string sipURIFromName(std::string &name);
+		
+		// Returns the name portion of the SIP URI if the string looks vaguely like a SIP URI, or an empty string if not.
+static	std::string nameFromsipURI(const std::string &uri);		
+
+		bool inSpatialChannel(void);
+		std::string getAudioSessionURI();
+		std::string getAudioSessionHandle();
 				
 		void sendPositionalUpdate(void);
 		
 		void buildSetCaptureDevice(std::ostringstream &stream);
 		void buildSetRenderDevice(std::ostringstream &stream);
+		void buildLocalAudioUpdates(std::ostringstream &stream);
+		
+		void clearAllLists();
+		void checkFriend(const LLUUID& id);
+		void sendFriendsListUpdates();
+
+		// start a text IM session with the specified user
+		// This will be asynchronous, the session may be established at a future time.
+		sessionState* startUserIMSession(const LLUUID& uuid);
+		void sendQueuedTextMessages(sessionState *session);
 		
 		void enforceTether(void);
 		
@@ -492,10 +684,9 @@ static	void updatePosition(void);
 		bool		mPTTIsToggle;
 		bool		mUserPTTState;
 		bool		mMuteMic;
-		
-		// Set to true when the mute state of someone in the participant list changes.
-		// The code will have to walk the list to find the changed participant(s).
-		bool		mVolumeDirty;
+				
+		// Set to true when the friends list is known to have changed.
+		bool		mFriendsListDirty;
 		
 		enum
 		{
@@ -523,14 +714,18 @@ static	void updatePosition(void);
 		BOOL		mLipSyncEnabled;
 
 		typedef std::set<LLVoiceClientParticipantObserver*> observer_set_t;
-		observer_set_t mObservers;
+		observer_set_t mParticipantObservers;
 
-		void notifyObservers();
+		void notifyParticipantObservers();
 
 		typedef std::set<LLVoiceClientStatusObserver*> status_observer_set_t;
 		status_observer_set_t mStatusObservers;
 		
 		void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status);
+
+		typedef std::set<LLFriendObserver*> friend_observer_set_t;
+		friend_observer_set_t mFriendObservers;
+		void notifyFriendObservers();
 };
 
 extern LLVoiceClient *gVoiceClient;
diff --git a/indra/newview/skins/default/textures/slim_icon_16_viewer.tga b/indra/newview/skins/default/textures/slim_icon_16_viewer.tga
new file mode 100644
index 0000000000000000000000000000000000000000..552181d36a1f420061816998b6caf5f3f7d12fbc
GIT binary patch
literal 1032
zcmZuwSxggA6fMTYEm84LjfTJn(P&KE5~2j-8q*(*RrZ3GB}R?AtkPm>sn`~zWorc@
zQ4|VAqDYMkf}#~tMMVXP8(M)kGq0VQPN5LM`)csZNlxa?eeaxm&wX=6qWPkkXR&Cp
zC@5&r^z^i-z5hCi1_0&)mWGFiN8E31sc{Ys4r4ds9k07OWir`8*joW08Uchu;1__v
z9KbTMSiI+XXS>bCA@BW;8$NZ(Q6WnoFIZYjMtLti;ds+!Pf1OVgtJ75BnwzX79k-a
zYq}lwHddf~cOF%aX6fU&3|$n?DvYJF3gf6ueKeDHC6cw+N(BziyzY5rKOos30o?gS
zVClQwH!p0@YJKO_5;pxx6n*h(Je6B=7%9t+P?t&%(*PtdPC%LZ80y01L-=mnbz$J^
zhd$u0AOfK-juO%}(Y&VO7;UOoFhw<IX|*YtQdK0;dF3+yT3daBu53`^s)l@5t|7_I
z3dk2661<)WtafvhH$xlE<{FM5Q@tGDd2Heybk=$-4~*Q+dphoky}{GgW%XEWMz^l&
z6qQzRh(WGF->%&|BK!iRY2cO3DV8G0NNFll>Bfhp9LqD4Q&W@Ey#w~KlG-#36@j%x
zl&+1$Blx>-=dSJB{tB?jn_U#oDGUjS9LhJR(3)$hbcdr^K-f89xOpCv*o;7iHtPQZ
zau6UDsLjcAzB!qxZPBqWds@9}QwpOpB{5`AVN4kLYvISg3PM?)@@DE|xr<k&l(I}l
zsmhZ`<Cy9umAm!X4ITvi&4inwvWwz|&#3k@H0vDS8onirSXkcox%XX5d(CL3E(Vhf
zaDDzt%ImUmz0JTvW~4ACBJy8c&>Y0o4I17#@{I;=FcFxyanpu~FW)~p3(O~ZxNAh3
z2?QEC&Q#RNU0{s{-!5(CY5wV}rm<mq5bFvHjstfg5g;?Ly!o!RvH5Y8a5`@vKAV4l
zgK9kT;zT~XNXnPp$`QKXKOF!0`|FUU@pf5YVBlKd5bmrQfnZ0Ey~kXaH-20$9;<6H
zPI$cRIP_)=b0d=jUp@^~n93DESOr)Dm_0L03*cj}Qk@gad8x@KcIy<vzoz>8``${W
z(iqrV7Zw(_8Qwtvl5Dn;j4zqT1>KI9m!Efb+_E-VizO1tUWlzEalkA9@fC0^M=Z)C
Fj6aBh9Torp

literal 0
HcmV?d00001

diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index 249787ad8dc..c79c2aedf1d 100755
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -237,11 +237,6 @@ def construct(self):
         # Vivox runtimes
         if self.prefix(src="vivox-runtime/i686-win32", dst=""):
             self.path("SLVoice.exe")
-            self.path("SLVoiceAgent.exe")
-            self.path("libeay32.dll")
-            self.path("srtp.dll")
-            self.path("ssleay32.dll")
-            self.path("tntk.dll")
             self.path("alut.dll")
             self.path("vivoxsdk.dll")
             self.path("ortp.dll")
@@ -451,7 +446,6 @@ def construct(self):
                 self.path("vivox-runtime/universal-darwin/libortp.dylib", "libortp.dylib")
                 self.path("vivox-runtime/universal-darwin/libvivoxsdk.dylib", "libvivoxsdk.dylib")
                 self.path("vivox-runtime/universal-darwin/SLVoice", "SLVoice")
-                self.path("vivox-runtime/universal-darwin/SLVoiceAgent.app", "SLVoiceAgent.app")
 
                 # llkdu dynamic library
                 self.path("../../libraries/universal-darwin/lib_release/libllkdu.dylib", "libllkdu.dylib")
diff --git a/install.xml b/install.xml
index c83ecc06e89..b60fc7494b7 100644
--- a/install.xml
+++ b/install.xml
@@ -1193,23 +1193,23 @@ anguage Infrstructure (CLI) international standard</string>
           <key>darwin</key>
           <map>
             <key>md5sum</key>
-            <string>4d786ce5732b70cf230f0eebcec9571c</string>
+            <string>352eae0cd76bb561da7b4183fc5a2857</string>
             <key>url</key>
-            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/vivox-darwin-20080613.tar.bz2</uri>
+            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/vivox-2.1.3010.6151-darwin-20090218.tar.bz2</uri>
           </map>
           <key>linux</key>
           <map>
             <key>md5sum</key>
-            <string>3b7dc33c106e1c4c10fc515249ac7bda</string>
+            <string>2279e1637568a837d9a4971cd27ed5f7</string>
             <key>url</key>
-            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/vivox-linux-20080613.tar.bz2</uri>
+            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/vivox-2.1.3010.6151-linux-20090218.tar.bz2</uri>
           </map>
           <key>windows</key>
           <map>
             <key>md5sum</key>
-            <string>ef09fcc8dea5d5fc911667f03c8b8a99</string>
+            <string>311bedab0abbd3a0ddf07216711af0ea</string>
             <key>url</key>
-            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/vivox-windows-20080613.tar.bz2</uri>
+            <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/vivox-2.1.3010.6151-windows-20090218.tar.bz2</uri>
           </map>
         </map>
       </map>
-- 
GitLab