diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 6ec6f5489cf530ad5e71668eacbf8a5ca4637791..18314904a7e478ca2c05ba4f927912bf93c3a040 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -121,6 +121,7 @@
 #include <boost/bind.hpp>
 #include <boost/foreach.hpp>
 #include <boost/algorithm/string.hpp>
+#include <boost/regex.hpp>
 
 
 #if LL_WINDOWS
@@ -2831,25 +2832,46 @@ namespace {
 		std::string notification_name;
 		void (*apply_callback)(LLSD const &, LLSD const &) = NULL;
 
+		/* Build up the notification name...
+		 * it can be any of these, which are included here for the sake of grep:
+		 *   RequiredUpdateDownloadedDialog
+		 *   RequiredUpdateDownloadedVerboseDialog
+		 *   OtherChannelRequiredUpdateDownloadedDialog
+		 *   OtherChannelRequiredUpdateDownloadedVerbose
+		 *   DownloadBackgroundTip
+		 *   DownloadBackgroundDialog
+		 *   OtherChannelDownloadBackgroundTip
+		 *   OtherChannelDownloadBackgroundDialog
+		 */
+		{
+			LL_DEBUGS("UpdaterService") << "data = ";
+			std::ostringstream data_dump;
+			LLSDSerialize::toNotation(data, data_dump);
+			LL_CONT << data_dump.str() << LL_ENDL;
+		}
+		if(data["channel"].asString() != LLVersionInfo::getChannel())
+		{
+			notification_name.append("OtherChannel");
+		}
 		if(data["required"].asBoolean())
 		{
 			if(LLStartUp::getStartupState() <= STATE_LOGIN_WAIT)
 			{
 				// The user never saw the progress bar.
 				apply_callback = &apply_update_ok_callback;
-				notification_name = "RequiredUpdateDownloadedVerboseDialog";
+				notification_name += "RequiredUpdateDownloadedVerboseDialog";
 			}
 			else if(LLStartUp::getStartupState() < STATE_WORLD_INIT)
 			{
 				// The user is logging in but blocked.
 				apply_callback = &apply_update_ok_callback;
-				notification_name = "RequiredUpdateDownloadedDialog";
+				notification_name += "RequiredUpdateDownloadedDialog";
 			}
 			else
 			{
 				// The user is already logged in; treat like an optional update.
 				apply_callback = &apply_update_callback;
-				notification_name = "DownloadBackgroundTip";
+				notification_name += "DownloadBackgroundTip";
 			}
 		}
 		else
@@ -2859,36 +2881,47 @@ namespace {
 			{
 				// CHOP-262 we need to use a different notification
 				// method prior to login.
-				notification_name = "DownloadBackgroundDialog";
+				notification_name += "DownloadBackgroundDialog";
 			}
 			else
 			{
-				notification_name = "DownloadBackgroundTip";
+				notification_name += "DownloadBackgroundTip";
 			}
 		}
 
 		LLSD substitutions;
 		substitutions["VERSION"] = data["version"];
-
-		// truncate version at the rightmost '.' 
-		std::string version_short(data["version"]);
-		size_t short_length = version_short.rfind('.');
-		if (short_length != std::string::npos)
+		std::string new_channel = data["channel"].asString();
+		substitutions["NEW_CHANNEL"] = new_channel;
+		std::string info_url    = data["info_url"].asString();
+		if ( !info_url.empty() )
 		{
-			version_short.resize(short_length);
+			substitutions["INFO_URL"] = info_url;
 		}
+		else
+		{
+			LL_WARNS("UpdaterService") << "no info url supplied - defaulting to hard coded release notes pattern" << LL_ENDL;
 
-		LLUIString relnotes_url("[RELEASE_NOTES_BASE_URL][CHANNEL_URL]/[VERSION_SHORT]");
-		relnotes_url.setArg("[VERSION_SHORT]", version_short);
+			// truncate version at the rightmost '.' 
+			std::string version_short(data["version"]);
+			size_t short_length = version_short.rfind('.');
+			if (short_length != std::string::npos)
+			{
+				version_short.resize(short_length);
+			}
 
-		// *TODO thread the update service's response through to this point
-		std::string const & channel = LLVersionInfo::getChannel();
-		boost::shared_ptr<char> channel_escaped(curl_escape(channel.c_str(), channel.size()), &curl_free);
+			LLUIString relnotes_url("[RELEASE_NOTES_BASE_URL][CHANNEL_URL]/[VERSION_SHORT]");
+			relnotes_url.setArg("[VERSION_SHORT]", version_short);
 
-		relnotes_url.setArg("[CHANNEL_URL]", channel_escaped.get());
-		relnotes_url.setArg("[RELEASE_NOTES_BASE_URL]", LLTrans::getString("RELEASE_NOTES_BASE_URL"));
-		substitutions["RELEASE_NOTES_FULL_URL"] = relnotes_url.getString();
+			// *TODO thread the update service's response through to this point
+			std::string const & channel = LLVersionInfo::getChannel();
+			boost::shared_ptr<char> channel_escaped(curl_escape(channel.c_str(), channel.size()), &curl_free);
 
+			relnotes_url.setArg("[CHANNEL_URL]", channel_escaped.get());
+			relnotes_url.setArg("[RELEASE_NOTES_BASE_URL]", LLTrans::getString("RELEASE_NOTES_BASE_URL"));
+			substitutions["INFO_URL"] = relnotes_url.getString();
+		}
+		
 		LLNotificationsUtil::add(notification_name, substitutions, LLSD(), apply_callback);
 	}
 
@@ -2940,7 +2973,8 @@ void LLAppViewer::initUpdater()
 	U32 check_period = gSavedSettings.getU32("UpdaterServiceCheckPeriod");
 	bool willing_to_test;
 	LL_DEBUGS("UpdaterService") << "channel " << channel << LL_ENDL;
-	if (channel.find("Test") != std::string::npos) // TBD - should be a regex
+	static const boost::regex is_test_channel("\\bTest$");
+	if (boost::regex_search(channel, is_test_channel)) 
 	{
 		LL_INFOS("UpdaterService") << "Test build: overriding willing_to_test by sending testno" << LL_ENDL;
 		willing_to_test = false;
diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp
index 12796ca26250c16b2e071d9232a1a6e80aa521d8..b27a566c2375624bf4348d71f5843bfdeee9debd 100644
--- a/indra/newview/lllogininstance.cpp
+++ b/indra/newview/lllogininstance.cpp
@@ -777,20 +777,20 @@ void LLLoginInstance::updateApp(bool mandatory, const std::string& auth_msg)
 	LLSD payload;
 	payload["mandatory"] = mandatory;
 
-/*
- We're constructing one of the following 9 strings here:
-	 "DownloadWindowsMandatory"
-	 "DownloadWindowsReleaseForDownload"
-	 "DownloadWindows"
-	 "DownloadMacMandatory"
-	 "DownloadMacReleaseForDownload"
-	 "DownloadMac"
-	 "DownloadLinuxMandatory"
-	 "DownloadLinuxReleaseForDownload"
-	 "DownloadLinux"
- 
- I've called them out explicitly in this comment so that they can be grepped for.
- */
+	/*
+	 * We're constructing one of the following 9 strings here:
+	 *   "DownloadWindowsMandatory"
+	 *	 "DownloadWindowsReleaseForDownload"
+	 *	 "DownloadWindows"
+	 *	 "DownloadMacMandatory"
+	 *	 "DownloadMacReleaseForDownload"
+	 *	 "DownloadMac"
+	 *	 "DownloadLinuxMandatory"
+	 *	 "DownloadLinuxReleaseForDownload"
+	 *	 "DownloadLinux"
+ 	 *
+	 * I've called them out explicitly in this comment so that they can be grepped for.
+	 */
 	std::string notification_name = "Download";
 	
 #if LL_WINDOWS
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index 8c7242188872c881f3a825c1616eab7b7829aad7..2d9c127b8743c55630162abcf443d4d082301f4b 100755
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -34,6 +34,7 @@
 #include <fstream>
 #include <algorithm>
 #include <boost/lambda/core.hpp>
+#include <boost/regex.hpp>
 
 #include "llagent.h"
 #include "llagentcamera.h"
@@ -2235,9 +2236,9 @@ void LLViewerWindow::setMenuBackgroundColor(bool god_mode, bool dev_grid)
 
 	// no l10n problem because channel is always an english string
 	std::string channel = LLVersionInfo::getChannel();
-	bool isProject = (channel.find("Project") != std::string::npos); // TBD - should be a regex
-	bool isBeta = (channel.find("Beta") != std::string::npos); // TBD - should be a regex
-	bool isTest = (channel.find("Test") != std::string::npos); // TBD - should be a regex
+	static const boost::regex is_beta_channel("\\bBeta\\b");
+	static const boost::regex is_project_channel("\\bProject\\b");
+	static const boost::regex is_test_channel("\\bTest$");
 	
 	// god more important than project, proj more important than grid
     if ( god_mode ) 
@@ -2251,15 +2252,15 @@ void LLViewerWindow::setMenuBackgroundColor(bool god_mode, bool dev_grid)
 			new_bg_color = LLUIColorTable::instance().getColor( "MenuNonProductionGodBgColor" );
 		}
     }
-	else if (isBeta)
+	else if (boost::regex_search(channel, is_beta_channel))
 	{
 		new_bg_color = LLUIColorTable::instance().getColor( "MenuBarBetaBgColor" );
 	}
-	else if (isProject)
+	else if (boost::regex_search(channel, is_project_channel))
 	{
 		new_bg_color = LLUIColorTable::instance().getColor( "MenuBarProjectBgColor" );
 	}
-	else if (isTest)
+	else if (boost::regex_search(channel, is_test_channel))
 	{
 		new_bg_color = LLUIColorTable::instance().getColor( "MenuBarTestBgColor" );
 	}
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index c8f5cbb2b015209d797cfd0c4e0a88495f95285d..23197293396157f586eff73ca263ab327a81c760 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -3452,7 +3452,7 @@ or you can install it now.
    name="DownloadBackgroundTip"
    type="notify">
 We have downloaded an update to your [APP_NAME] installation.
-Version [VERSION] [[RELEASE_NOTES_FULL_URL] Information about this update]
+Version [VERSION] [[INFO_URL] Information about this update]
     <tag>confirm</tag>
     <usetemplate
      name="okcancelbuttons"
@@ -3465,7 +3465,7 @@ Version [VERSION] [[RELEASE_NOTES_FULL_URL] Information about this update]
  name="DownloadBackgroundDialog"
  type="alertmodal">
 We have downloaded an update to your [APP_NAME] installation.
-Version [VERSION] [[RELEASE_NOTES_FULL_URL] Information about this update]
+Version [VERSION] [[INFO_URL] Information about this update]
     <tag>confirm</tag>
     <usetemplate
      name="okcancelbuttons"
@@ -3478,7 +3478,7 @@ Version [VERSION] [[RELEASE_NOTES_FULL_URL] Information about this update]
  name="RequiredUpdateDownloadedVerboseDialog"
  type="alertmodal">
 We have downloaded a required software update.
-Version [VERSION]
+Version [VERSION] [[INFO_URL] Information about this update]
 
 We must restart [APP_NAME] to install the update.
     <tag>confirm</tag>
@@ -3492,6 +3492,66 @@ We must restart [APP_NAME] to install the update.
  name="RequiredUpdateDownloadedDialog"
  type="alertmodal">
 We must restart [APP_NAME] to install the update.
+[[INFO_URL] Information about this update]
+    <tag>confirm</tag>
+    <usetemplate
+     name="okbutton"
+     yestext="OK"/>
+  </notification>
+
+  <notification
+   icon="notify.tga"
+   name="OtherChannelDownloadBackgroundTip"
+   type="notify">
+We have downloaded an update to your [APP_NAME] installation.
+Version [VERSION] 
+This experimental viewer has been replaced by a [NEW_CHANNEL] viewer;
+see [[INFO_URL] for details about this update]
+    <tag>confirm</tag>
+    <usetemplate
+     name="okcancelbuttons"
+     notext="Later..."
+     yestext="Install now and restart [APP_NAME]"/>
+  </notification>
+
+  <notification
+ icon="alertmodal.tga"
+ name="OtherChannelDownloadBackgroundDialog"
+ type="alertmodal">
+We have downloaded an update to your [APP_NAME] installation.
+Version [VERSION]
+This experimental viewer has been replaced by a [NEW_CHANNEL] viewer;
+see [[INFO_URL] Information about this update]
+    <tag>confirm</tag>
+    <usetemplate
+     name="okcancelbuttons"
+     notext="Later..."
+     yestext="Install now and restart [APP_NAME]"/>
+  </notification>
+  
+  <notification
+ icon="alertmodal.tga"
+ name="OtherChannelRequiredUpdateDownloadedVerboseDialog"
+ type="alertmodal">
+We have downloaded a required software update.
+Version [VERSION]
+This experimental viewer has been replaced by a [NEW_CHANNEL] viewer;
+see [[INFO_URL] Information about this update]
+
+We must restart [APP_NAME] to install the update.
+    <tag>confirm</tag>
+    <usetemplate
+     name="okbutton"
+     yestext="OK"/>
+  </notification>
+  
+  <notification
+ icon="alertmodal.tga"
+ name="OtherChannelRequiredUpdateDownloadedDialog"
+ type="alertmodal">
+We must restart [APP_NAME] to install the update.
+This experimental viewer has been replaced by a [NEW_CHANNEL] viewer;
+see [[INFO_URL] Information about this update]
     <tag>confirm</tag>
     <usetemplate
      name="okbutton"
diff --git a/indra/viewer_components/updater/llupdatechecker.cpp b/indra/viewer_components/updater/llupdatechecker.cpp
index 6d0758b226149c0e7823e6705aa6a527b7a7a9fe..734747c8115e194fb4a0af944dd0ae54249dafcc 100644
--- a/indra/viewer_components/updater/llupdatechecker.cpp
+++ b/indra/viewer_components/updater/llupdatechecker.cpp
@@ -143,8 +143,8 @@ void LLUpdateChecker::Implementation::completed(U32 status,
 
 				LL_WARNS("UpdaterService")
 					<< "update response using " << sProtocolVersion
-					<< " was 404... retry at " << retryUrl
-					<< " with legacy protocol"
+					<< " was 404... retry with legacy protocol" << mProtocol
+					<< "\n at " << retryUrl
 					<< LL_ENDL;
 	
 				mHttpClient.get(retryUrl, this);
@@ -164,22 +164,9 @@ void LLUpdateChecker::Implementation::completed(U32 status,
 			mClient.error(reason);
 		}
 	}
-	else if(!content.asBoolean())
-	{
-		LL_INFOS("UpdaterService") << "up to date" << LL_ENDL;
-		mClient.upToDate();
-	}
-	else if(content["required"].asBoolean())
-	{
-		LL_INFOS("UpdaterService") << "version invalid" << LL_ENDL;
-		LLURI uri(content["url"].asString());
-		mClient.requiredUpdate(content["version"].asString(), uri, content["hash"].asString());
-	}
 	else
 	{
-		LL_INFOS("UpdaterService") << "newer version " << content["version"].asString() << " available" << LL_ENDL;
-		LLURI uri(content["url"].asString());
-		mClient.optionalUpdate(content["version"].asString(), uri, content["hash"].asString());
+		mClient.response(content);
 	}
 }
 
@@ -215,8 +202,10 @@ std::string LLUpdateChecker::Implementation::buildUrl(std::string const & hostUr
     {
         platform = "mac";
     }
-#else
+#elif LL_LINUX
 	static const char * platform = "lnx";
+#else
+#   error "unsupported platform"
 #endif
 	
 	LLSD path;
diff --git a/indra/viewer_components/updater/llupdatechecker.h b/indra/viewer_components/updater/llupdatechecker.h
index b60f21549e2b6d6e157a9dc24df5ade439ad8c9d..55806137d748ff836900bff73d76aab0e749ee5b 100644
--- a/indra/viewer_components/updater/llupdatechecker.h
+++ b/indra/viewer_components/updater/llupdatechecker.h
@@ -117,18 +117,8 @@ class LLUpdateChecker::Client
 	// An error occurred while checking for an update.
 	virtual void error(std::string const & message) = 0;
 	
-	// A newer version is available, but the current version may still be used.
-	virtual void optionalUpdate(std::string const & newVersion,
-								LLURI const & uri,
-								std::string const & hash) = 0;
-	
-	// A newer version is available, and the current version is no longer valid. 
-	virtual void requiredUpdate(std::string const & newVersion,
-								LLURI const & uri,
-								std::string const & hash) = 0;
-	
-	// The checked version is up to date; no newer version exists.
-	virtual void upToDate(void) = 0;
+	// A successful response was received from the viewer version manager
+	virtual void response(LLSD const & content) = 0;
 };
 
 
diff --git a/indra/viewer_components/updater/llupdatedownloader.cpp b/indra/viewer_components/updater/llupdatedownloader.cpp
index 001dd5ed163412d3eadeabd2428df43500261345..c28ad76c77f81d18fed2c26ef18d0822e0bbf9b2 100644
--- a/indra/viewer_components/updater/llupdatedownloader.cpp
+++ b/indra/viewer_components/updater/llupdatedownloader.cpp
@@ -50,7 +50,9 @@ class LLUpdateDownloader::Implementation:
 	void cancel(void);
 	void download(LLURI const & uri,
 				  std::string const & hash,
+				  std::string const & updateChannel,
 				  std::string const & updateVersion,
+				  std::string const & info_url,
 				  bool required);
 	bool isDownloading(void);
 	size_t onHeader(void * header, size_t size);
@@ -125,10 +127,12 @@ void LLUpdateDownloader::cancel(void)
 
 void LLUpdateDownloader::download(LLURI const & uri,
 								  std::string const & hash,
+								  std::string const & updateChannel,
 								  std::string const & updateVersion,
+								  std::string const & info_url,
 								  bool required)
 {
-	mImplementation->download(uri, hash, updateVersion, required);
+	mImplementation->download(uri, hash, updateChannel, updateVersion, info_url, required);
 }
 
 
@@ -222,18 +226,28 @@ void LLUpdateDownloader::Implementation::cancel(void)
 
 void LLUpdateDownloader::Implementation::download(LLURI const & uri,
 												  std::string const & hash,
+												  std::string const & updateChannel,
 												  std::string const & updateVersion,
+												  std::string const & info_url,
 												  bool required)
-{
+{ 
 	if(isDownloading()) mClient.downloadError("download in progress");
 
 	mDownloadRecordPath = downloadMarkerPath();
 	mDownloadData = LLSD();
 	mDownloadData["required"] = required;
+	mDownloadData["update_channel"] = updateChannel;
 	mDownloadData["update_version"] = updateVersion;
-	try {
+	if (!info_url.empty())
+	{
+		mDownloadData["info_url"] = info_url;
+	}
+	try
+	{
 		startDownloading(uri, hash);
-	} catch(DownloadError const & e) {
+	}
+	catch(DownloadError const & e)
+	{
 		mClient.downloadError(e.what());
 	}
 }
@@ -249,47 +263,65 @@ void LLUpdateDownloader::Implementation::resume(void)
 {
 	mCancelled = false;
 
-	if(isDownloading()) {
+	if(isDownloading())
+	{
 		mClient.downloadError("download in progress");
 	}
 
 	mDownloadRecordPath = downloadMarkerPath();
 	llifstream dataStream(mDownloadRecordPath);
-	if(!dataStream) {
+	if(!dataStream)
+	{
 		mClient.downloadError("no download marker");
 		return;
 	}
 
 	LLSDSerialize::fromXMLDocument(mDownloadData, dataStream);
 
-	if(!mDownloadData.asBoolean()) {
+	if(!mDownloadData.asBoolean())
+	{
 		mClient.downloadError("no download information in marker");
 		return;
 	}
 
 	std::string filePath = mDownloadData["path"].asString();
-	try {
-		if(LLFile::isfile(filePath)) {
+	try
+	{
+		if(LLFile::isfile(filePath))
+		{
 			llstat fileStatus;
 			LLFile::stat(filePath, &fileStatus);
-			if(fileStatus.st_size != mDownloadData["size"].asInteger()) {
+			if(fileStatus.st_size != mDownloadData["size"].asInteger())
+			{
 				resumeDownloading(fileStatus.st_size);
-			} else if(!validateDownload()) {
+			}
+			else if(!validateDownload())
+			{
 				LLFile::remove(filePath);
 				download(LLURI(mDownloadData["url"].asString()),
 						 mDownloadData["hash"].asString(),
+						 mDownloadData["update_channel"].asString(),
 						 mDownloadData["update_version"].asString(),
+						 mDownloadData["info_url"].asString(),
 						 mDownloadData["required"].asBoolean());
-			} else {
+			}
+			else
+			{
 				mClient.downloadComplete(mDownloadData);
 			}
-		} else {
+		}
+		else
+		{
 			download(LLURI(mDownloadData["url"].asString()),
 					 mDownloadData["hash"].asString(),
+					 mDownloadData["update_channel"].asString(),
 					 mDownloadData["update_version"].asString(),
+					 mDownloadData["info_url"].asString(),
 					 mDownloadData["required"].asBoolean());
 		}
-	} catch(DownloadError & e) {
+	}
+	catch(DownloadError & e)
+	{
 		mClient.downloadError(e.what());
 	}
 }
@@ -297,13 +329,18 @@ void LLUpdateDownloader::Implementation::resume(void)
 
 void LLUpdateDownloader::Implementation::setBandwidthLimit(U64 bytesPerSecond)
 {
-	if((mBandwidthLimit != bytesPerSecond) && isDownloading() && !mDownloadData["required"].asBoolean()) {
+	if((mBandwidthLimit != bytesPerSecond) && isDownloading() && !mDownloadData["required"].asBoolean())
+	{
 		llassert(mCurl != 0);
 		mBandwidthLimit = bytesPerSecond;
 		CURLcode code = curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, &mBandwidthLimit);
-		if(code != CURLE_OK) LL_WARNS("UpdaterService") <<
-			"unable to change dowload bandwidth" << LL_ENDL;
-	} else {
+		if(code != CURLE_OK)
+		{
+			LL_WARNS("UpdaterService") << "unable to change dowload bandwidth" << LL_ENDL;
+		}
+	}
+	else
+	{
 		mBandwidthLimit = bytesPerSecond;
 	}
 }
@@ -381,29 +418,44 @@ void LLUpdateDownloader::Implementation::run(void)
 {
 	CURLcode code = curl_easy_perform(mCurl);
 	mDownloadStream.close();
-	if(code == CURLE_OK) {
+	if(code == CURLE_OK)
+	{
 		LLFile::remove(mDownloadRecordPath);
-		if(validateDownload()) {
+		if(validateDownload())
+		{
 			LL_INFOS("UpdaterService") << "download successful" << LL_ENDL;
 			mClient.downloadComplete(mDownloadData);
-		} else {
+		}
+		else
+		{
 			LL_INFOS("UpdaterService") << "download failed hash check" << LL_ENDL;
 			std::string filePath = mDownloadData["path"].asString();
-			if(filePath.size() != 0) LLFile::remove(filePath);
+			if(filePath.size() != 0)
+			{
+				LLFile::remove(filePath);
+			}
 			mClient.downloadError("failed hash check");
 		}
-	} else if(mCancelled && (code == CURLE_WRITE_ERROR)) {
+	}
+	else if(mCancelled && (code == CURLE_WRITE_ERROR))
+	{
 		LL_INFOS("UpdaterService") << "download canceled by user" << LL_ENDL;
 		// Do not call back client.
-	} else {
+	}
+	else
+	{
 		LL_WARNS("UpdaterService") << "download failed with error '" <<
 			curl_easy_strerror(code) << "'" << LL_ENDL;
 		LLFile::remove(mDownloadRecordPath);
-		if(mDownloadData.has("path")) LLFile::remove(mDownloadData["path"].asString());
+		if(mDownloadData.has("path"))
+		{
+			LLFile::remove(mDownloadData["path"].asString());
+		}
 		mClient.downloadError("curl error");
 	}
 
-	if(mHeaderList) {
+	if(mHeaderList)
+	{
 		curl_slist_free_all(mHeaderList);
 		mHeaderList = 0;
 	}
@@ -421,13 +473,16 @@ void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & u
 		curl_easy_reset(mCurl);
 	}
 
-	if(mCurl == 0) throw DownloadError("failed to initialize curl");
-
+	if(mCurl == 0)
+	{
+		throw DownloadError("failed to initialize curl");
+	}
 	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_NOSIGNAL, true));
 	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_FOLLOWLOCATION, true));
 	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, &write_function));
 	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, this));
-	if(processHeader) {
+	if(processHeader)
+	{
 	   throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERFUNCTION, &header_function));
 	   throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERDATA, this));
 	}
@@ -456,7 +511,10 @@ void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte)
 	boost::format rangeHeaderFormat("Range: bytes=%u-");
 	rangeHeaderFormat % startByte;
 	mHeaderList = curl_slist_append(mHeaderList, rangeHeaderFormat.str().c_str());
-	if(mHeaderList == 0) throw DownloadError("cannot add Range header");
+	if(mHeaderList == 0)
+	{
+		throw DownloadError("cannot add Range header");
+	}
 	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPHEADER, mHeaderList));
 
 	mDownloadStream.open(mDownloadData["path"].asString(),
@@ -508,19 +566,26 @@ bool LLUpdateDownloader::Implementation::validateDownload(void)
 {
 	std::string filePath = mDownloadData["path"].asString();
 	llifstream fileStream(filePath, std::ios_base::in | std::ios_base::binary);
-	if(!fileStream) return false;
+	if(!fileStream)
+	{
+		return false;
+	}
 
 	std::string hash = mDownloadData["hash"].asString();
-	if(hash.size() != 0) {
+	if(hash.size() != 0)
+	{
 		LL_INFOS("UpdaterService") << "checking hash..." << LL_ENDL;
 		char digest[33];
 		LLMD5(fileStream).hex_digest(digest);
-		if(hash != digest) {
-			LL_WARNS("UpdaterService") << "download hash mismatch; expeted " << hash <<
+		if(hash != digest)
+		{
+			LL_WARNS("UpdaterService") << "download hash mismatch; expected " << hash <<
 				" but download is " << digest << LL_ENDL;
 		}
 		return hash == digest;
-	} else {
+	}
+	else
+	{
 		return true; // No hash check provided.
 	}
 }
diff --git a/indra/viewer_components/updater/llupdatedownloader.h b/indra/viewer_components/updater/llupdatedownloader.h
index 0d635640cf2e1d6734f6745dbe51e5b132c94309..f759988f120245b2c2f5bd0fcd1d9685027532d2 100644
--- a/indra/viewer_components/updater/llupdatedownloader.h
+++ b/indra/viewer_components/updater/llupdatedownloader.h
@@ -54,7 +54,9 @@ class LLUpdateDownloader
 	// Start a new download.
 	void download(LLURI const & uri,
 				  std::string const & hash, 
+				  std::string const & updateChannel,
 				  std::string const & updateVersion,
+				  std::string const & info_url,
 				  bool required=false);
 	
 	// Returns true if a download is in progress.
diff --git a/indra/viewer_components/updater/llupdaterservice.cpp b/indra/viewer_components/updater/llupdaterservice.cpp
index c6c89655d31139888a13130b39fb2c34dffafdef..324b051b217d4ad6542fdbae7708b7eb862bcf01 100644
--- a/indra/viewer_components/updater/llupdaterservice.cpp
+++ b/indra/viewer_components/updater/llupdaterservice.cpp
@@ -140,13 +140,9 @@ class LLUpdaterServiceImpl :
 
 	// LLUpdateChecker::Client:
 	virtual void error(std::string const & message);
-	virtual void optionalUpdate(std::string const & newVersion,
-								LLURI const & uri,
-								std::string const & hash);
-	virtual void requiredUpdate(std::string const & newVersion,
-								LLURI const & uri,
-								std::string const & hash);
-	virtual void upToDate(void);
+	
+	// A successful response was received from the viewer version manager
+	virtual void response(LLSD const & content);
 	
 	// LLUpdateDownloader::Client
 	void downloadComplete(LLSD const & data);
@@ -155,6 +151,7 @@ class LLUpdaterServiceImpl :
 	bool onMainLoop(LLSD const & event);
 
 private:
+	std::string mNewChannel;
 	std::string mNewVersion;
 	
 	void restartTimer(unsigned int seconds);
@@ -334,9 +331,13 @@ bool LLUpdaterServiceImpl::checkForInstall(bool launchInstaller)
 				if((result == 0) && mAppExitCallback)
 				{
 					mAppExitCallback();
-				} else if(result != 0) {
+				}
+				else if(result != 0)
+				{
 					LL_WARNS("UpdaterService") << "failed to run update install script" << LL_ENDL;
-				} else {
+				}
+				else
+				{
 					; // No op.
 				}
 			}
@@ -364,6 +365,7 @@ bool LLUpdaterServiceImpl::checkForResume()
 			{
 				mIsDownloading = true;
 				mNewVersion = download_info["update_version"].asString();
+				mNewChannel = download_info["update_channel"].asString();
 				mUpdateDownloader.resume();
 				result = true;
 			}
@@ -372,7 +374,10 @@ bool LLUpdaterServiceImpl::checkForResume()
 				// The viewer that started this download is not the same as this viewer; ignore.
 				LL_INFOS("UpdaterService") << "ignoring partial download from different viewer version" << LL_ENDL;;
 				std::string path = download_info["path"].asString();
-				if(!path.empty()) LLFile::remove(path);
+				if(!path.empty())
+				{
+					LLFile::remove(path);
+				}
 				LLFile::remove(download_marker_path);
 			}
 		} 
@@ -389,36 +394,43 @@ void LLUpdaterServiceImpl::error(std::string const & message)
 	}
 }
 
-void LLUpdaterServiceImpl::optionalUpdate(std::string const & newVersion,
-										  LLURI const & uri,
-										  std::string const & hash)
-{
-	stopTimer();
-	mNewVersion = newVersion;
-	mIsDownloading = true;
-	setState(LLUpdaterService::DOWNLOADING);
-	mUpdateDownloader.download(uri, hash, newVersion, false);
-}
-
-void LLUpdaterServiceImpl::requiredUpdate(std::string const & newVersion,
-										  LLURI const & uri,
-										  std::string const & hash)
-{
-	stopTimer();
-	mNewVersion = newVersion;
-	mIsDownloading = true;
-	setState(LLUpdaterService::DOWNLOADING);
-	mUpdateDownloader.download(uri, hash, newVersion, true);
-}
-
-void LLUpdaterServiceImpl::upToDate(void)
+// A successful response was received from the viewer version manager
+void LLUpdaterServiceImpl::response(LLSD const & content)
 {
-	if(mIsChecking)
+	if(!content.asBoolean()) // an empty response means "no update"
 	{
-		restartTimer(mCheckPeriod);
-	}
+		LL_INFOS("UpdaterService") << "up to date" << LL_ENDL;
+		if(mIsChecking)
+		{
+			restartTimer(mCheckPeriod);
+		}
 	
-	setState(LLUpdaterService::UP_TO_DATE);
+		setState(LLUpdaterService::UP_TO_DATE);
+	}
+	else
+	{
+		// there is an update available...
+		stopTimer();
+		mNewChannel = content["channel"].asString();
+		if (mNewChannel.empty())
+		{
+			LL_INFOS("UpdaterService") << "no channel supplied, assuming current channel" << LL_ENDL;
+			mNewChannel = mChannel;
+		}
+		mNewVersion = content["version"].asString();
+		mIsDownloading = true;
+		setState(LLUpdaterService::DOWNLOADING);
+		BOOL required = content["required"].asBoolean();
+		LLURI url(content["url"].asString());
+		std::string more_info = content["more_info"].asString();
+		LL_DEBUGS("UpdaterService")
+			<< "Starting download of "
+			<< ( required ? "required" : "optional" ) << " update "
+			<< "to channel '" << mNewChannel << "' version " << mNewVersion
+			<< "more info '" << more_info << "'"
+			<< LL_ENDL;
+		mUpdateDownloader.download(url, content["hash"].asString(), mNewChannel, mNewVersion, more_info, required);
+	}
 }
 
 void LLUpdaterServiceImpl::downloadComplete(LLSD const & data) 
@@ -436,9 +448,19 @@ void LLUpdaterServiceImpl::downloadComplete(LLSD const & data)
 	payload["type"] = LLSD(LLUpdaterService::DOWNLOAD_COMPLETE);
 	payload["required"] = data["required"];
 	payload["version"] = mNewVersion;
+	payload["channel"] = mNewChannel;
+	payload["info_url"] = data["info_url"];
 	event["payload"] = payload;
+	LL_DEBUGS("UpdaterService")
+		<< "Download complete "
+		<< ( data["required"].asBoolean() ? "required" : "optional" )
+		<< "channel " << mNewChannel
+		<< "version " << mNewVersion
+		<< "info " << data["info_url"].asString()
+		<< LL_ENDL;
+
 	LLEventPumps::instance().obtain("mainlooprepeater").post(event);
-	
+
 	setState(LLUpdaterService::TERMINAL);
 }
 
@@ -512,15 +534,18 @@ bool LLUpdaterServiceImpl::onMainLoop(LLSD const & event)
 		// Check for failed install.
 		if(LLFile::isfile(ll_install_failed_marker_path()))
 		{
+			LL_DEBUGS("UpdaterService") << "found marker " << ll_install_failed_marker_path() << LL_ENDL;;
 			int requiredValue = 0; 
 			{
 				llifstream stream(ll_install_failed_marker_path());
 				stream >> requiredValue;
-				if(stream.fail()) requiredValue = 0;
+				if(stream.fail())
+				{
+					requiredValue = 0;
+				}
 			}
 			// TODO: notify the user.
-			LL_INFOS("UpdaterService") << "found marker " << ll_install_failed_marker_path() << LL_ENDL;;
-			LL_INFOS("UpdaterService") << "last install attempt failed" << LL_ENDL;;
+			LL_WARNS("UpdaterService") << "last install attempt failed" << LL_ENDL;;
 			LLFile::remove(ll_install_failed_marker_path());
 			
 			LLSD event;
diff --git a/indra/viewer_components/updater/tests/llupdaterservice_test.cpp b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp
index ddaaccc0515592fd21ea4df26db0cc5431ccad61..51b63dcb7beaf6c50c328da84bab685bca20daac 100644
--- a/indra/viewer_components/updater/tests/llupdaterservice_test.cpp
+++ b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp
@@ -53,7 +53,7 @@ void LLUpdateChecker::checkVersion(std::string const & hostUrl,
 								   bool                willing_to_test)
 {}
 LLUpdateDownloader::LLUpdateDownloader(Client & ) {}
-void LLUpdateDownloader::download(LLURI const & , std::string const &, std::string const &, bool){}
+void LLUpdateDownloader::download(LLURI const & , std::string const &, std::string const &, std::string const &, std::string const &, bool){}
 
 class LLDir_Mock : public LLDir
 {