// 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; using MediaRoutes = std::vector; using MediaRouteIds = std::vector; 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()); } 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); }