blob: db84ed37587b533c7ad173037e3e47de13f863e0 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/ash/cast_config_delegate_media_router.h"
#include "base/macros.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/media/router/media_router.h"
#include "chrome/browser/media/router/media_router_factory.h"
#include "chrome/browser/media/router/media_router_feature.h"
#include "chrome/browser/media/router/media_routes_observer.h"
#include "chrome/browser/media/router/media_sinks_observer.h"
#include "chrome/browser/media/router/media_source_helper.h"
#include "chrome/browser/profiles/profile_manager.h"
namespace {
media_router::MediaRouter* media_router_for_test_ = nullptr;
media_router::MediaRouter* GetMediaRouter() {
if (media_router_for_test_)
return media_router_for_test_;
auto* router = media_router::MediaRouterFactory::GetApiForBrowserContext(
ProfileManager::GetPrimaryUserProfile());
DCHECK(router);
return router;
}
// The media router will sometimes append " (Tab)" to the tab title. This
// function will remove that data from the inout param |string|.
std::string StripEndingTab(const std::string& str) {
static const char ending[] = " (Tab)";
if (base::EndsWith(str, ending, base::CompareCase::SENSITIVE))
return str.substr(0, str.size() - strlen(ending));
return str;
}
} // namespace
// This class caches the values that the observers give us so we can query them
// at any point in time. It also emits a device refresh event when new data is
// available.
class CastDeviceCache : public media_router::MediaRoutesObserver,
public media_router::MediaSinksObserver {
public:
using MediaSinks = std::vector<media_router::MediaSink>;
using MediaRoutes = std::vector<media_router::MediaRoute>;
using MediaRouteIds = std::vector<media_router::MediaRoute::Id>;
explicit CastDeviceCache(ash::CastConfigDelegate* cast_config_delegate);
~CastDeviceCache() override;
// This may call cast_config_delegate->RequestDeviceRefresh() before
// returning.
void Init();
const MediaSinks& sinks() const { return sinks_; }
const MediaRoutes& routes() const { return routes_; }
private:
// media_router::MediaSinksObserver:
void OnSinksReceived(const MediaSinks& sinks) override;
// media_router::MediaRoutesObserver:
void OnRoutesUpdated(const MediaRoutes& routes,
const MediaRouteIds& unused_joinable_route_ids) override;
MediaSinks sinks_;
MediaRoutes routes_;
// Not owned.
ash::CastConfigDelegate* cast_config_delegate_;
DISALLOW_COPY_AND_ASSIGN(CastDeviceCache);
};
CastDeviceCache::CastDeviceCache(ash::CastConfigDelegate* cast_config_delegate)
: MediaRoutesObserver(GetMediaRouter()),
MediaSinksObserver(GetMediaRouter(),
media_router::MediaSourceForDesktop()),
cast_config_delegate_(cast_config_delegate) {
}
CastDeviceCache::~CastDeviceCache() {}
void CastDeviceCache::Init() {
CHECK(MediaSinksObserver::Init());
}
void CastDeviceCache::OnSinksReceived(const MediaSinks& sinks) {
sinks_ = sinks;
cast_config_delegate_->RequestDeviceRefresh();
}
void CastDeviceCache::OnRoutesUpdated(
const MediaRoutes& routes,
const MediaRouteIds& unused_joinable_route_ids) {
routes_ = routes;
cast_config_delegate_->RequestDeviceRefresh();
}
////////////////////////////////////////////////////////////////////////////////
// CastConfigDelegateMediaRouter:
// static
bool CastConfigDelegateMediaRouter::IsEnabled() {
return media_router::MediaRouterEnabled(
ProfileManager::GetPrimaryUserProfile()) ||
media_router_for_test_;
}
void CastConfigDelegateMediaRouter::SetMediaRouterForTest(
media_router::MediaRouter* media_router) {
media_router_for_test_ = media_router;
}
CastConfigDelegateMediaRouter::CastConfigDelegateMediaRouter() {}
CastConfigDelegateMediaRouter::~CastConfigDelegateMediaRouter() {}
CastDeviceCache* CastConfigDelegateMediaRouter::devices() {
// The CastDeviceCache instance is lazily allocated because the MediaRouter
// component is not ready when the constructor is invoked.
if (!devices_ && GetMediaRouter() != nullptr) {
devices_.reset(new CastDeviceCache(this));
devices_->Init();
}
return devices_.get();
}
bool CastConfigDelegateMediaRouter::HasCastExtension() const {
return true;
}
void CastConfigDelegateMediaRouter::RequestDeviceRefresh() {
// The media router component isn't ready yet.
if (!devices())
return;
// Build the old-style ReceiverAndActivity set out of the MediaRouter
// source/sink/route setup. We first map the existing sinks, and then we
// update those sinks with activity information.
ReceiversAndActivities items;
for (const media_router::MediaSink& sink : devices()->sinks()) {
ReceiverAndActivity ra;
ra.receiver.id = sink.id();
ra.receiver.name = base::UTF8ToUTF16(sink.name());
items.push_back(ra);
}
for (const media_router::MediaRoute& route : devices()->routes()) {
if (!route.for_display())
continue;
for (ReceiverAndActivity& item : items) {
if (item.receiver.id == route.media_sink_id()) {
item.activity.id = route.media_route_id();
item.activity.title =
base::UTF8ToUTF16(StripEndingTab(route.description()));
item.activity.is_local_source = route.is_local();
if (route.is_local()) {
// TODO(jdufault): Once the extension backend is removed, we can
// remove tab_id and specify the Desktop/Tab capture directly.
// crbug.com/551132.
// TODO(jdufault): We currently don't actually display DIAL casts to
// the user even though we have all the information necessary. We'll
// do this once the extension backend is gone because supporting both
// introduces extra complexity. crbug.com/551132.
// Default to a tab/app capture. This will display the media router
// description. This means we will properly support DIAL casts.
item.activity.tab_id = 0;
if (media_router::IsDesktopMirroringMediaSource(route.media_source()))
item.activity.tab_id = Activity::TabId::DESKTOP;
}
break;
}
}
}
FOR_EACH_OBSERVER(ash::CastConfigDelegate::Observer, observer_list_,
OnDevicesUpdated(items));
}
void CastConfigDelegateMediaRouter::CastToReceiver(
const std::string& receiver_id) {
GetMediaRouter()->CreateRoute(
media_router::MediaSourceForDesktop().id(), receiver_id,
GURL("http://cros-cast-origin/"), nullptr,
std::vector<media_router::MediaRouteResponseCallback>());
}
void CastConfigDelegateMediaRouter::StopCasting(const std::string& route_id) {
GetMediaRouter()->TerminateRoute(route_id);
}
bool CastConfigDelegateMediaRouter::HasOptions() const {
// There are no plans to have an options page for the MediaRouter.
return false;
}
void CastConfigDelegateMediaRouter::LaunchCastOptions() {}
void CastConfigDelegateMediaRouter::AddObserver(
ash::CastConfigDelegate::Observer* observer) {
observer_list_.AddObserver(observer);
}
void CastConfigDelegateMediaRouter::RemoveObserver(
ash::CastConfigDelegate::Observer* observer) {
observer_list_.RemoveObserver(observer);
}