1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.h"
6 
7 #include <algorithm>
8 #include <string>
9 #include <utility>
10 #include <vector>
11 
12 #include "base/bind.h"
13 #include "base/i18n/number_formatting.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/media/router/media_router_feature.h"
16 #include "chrome/browser/media/router/providers/wired_display/wired_display_presentation_receiver_factory.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/grit/generated_resources.h"
19 #include "components/media_router/common/media_source.h"
20 #include "components/media_router/common/route_request_result.h"
21 #include "ui/base/l10n/l10n_util.h"
22 #include "ui/display/display.h"
23 #include "ui/display/screen.h"
24 
25 using display::Display;
26 
27 namespace media_router {
28 
29 namespace {
30 
IsPresentationSource(const std::string & media_source)31 bool IsPresentationSource(const std::string& media_source) {
32   const GURL source_url(media_source);
33   return source_url.is_valid() && source_url.SchemeIsHTTPOrHTTPS() &&
34          !base::StartsWith(source_url.spec(), kLegacyCastPresentationUrlPrefix,
35                            base::CompareCase::INSENSITIVE_ASCII);
36 }
37 
CreateSinkForDisplay(const Display & display,int display_index)38 MediaSinkInternal CreateSinkForDisplay(const Display& display,
39                                        int display_index) {
40   const std::string sink_id =
41       WiredDisplayMediaRouteProvider::GetSinkIdForDisplay(display);
42   const std::string sink_name =
43       l10n_util::GetStringFUTF8(IDS_MEDIA_ROUTER_WIRED_DISPLAY_SINK_NAME,
44                                 base::FormatNumber(display_index));
45   MediaSink sink(sink_id, sink_name, SinkIconType::WIRED_DISPLAY,
46                  MediaRouteProviderId::WIRED_DISPLAY);
47   MediaSinkInternal sink_internal;
48   sink_internal.set_sink(sink);
49   return sink_internal;
50 }
51 
52 // Returns true if |display1| should come before |display2| when displays are
53 // sorted. Primary displays and displays to the top-left take priority, in
54 // that order.
CompareDisplays(int64_t primary_id,const Display & display1,const Display & display2)55 bool CompareDisplays(int64_t primary_id,
56                      const Display& display1,
57                      const Display& display2) {
58   if (display1.id() == primary_id)
59     return true;
60   if (display2.id() == primary_id)
61     return false;
62   return display1.bounds().y() < display2.bounds().y() ||
63          (display1.bounds().y() == display2.bounds().y() &&
64           display1.bounds().x() < display2.bounds().x());
65 }
66 
67 }  // namespace
68 
69 // static
70 const MediaRouteProviderId WiredDisplayMediaRouteProvider::kProviderId =
71     MediaRouteProviderId::WIRED_DISPLAY;
72 
73 // static
GetSinkIdForDisplay(const Display & display)74 std::string WiredDisplayMediaRouteProvider::GetSinkIdForDisplay(
75     const Display& display) {
76   return "wired_display_" + std::to_string(display.id());
77 }
78 
79 // static
GetRouteDescription(const std::string & media_source)80 std::string WiredDisplayMediaRouteProvider::GetRouteDescription(
81     const std::string& media_source) {
82   return l10n_util::GetStringFUTF8(
83       IDS_MEDIA_ROUTER_WIRED_DISPLAY_ROUTE_DESCRIPTION,
84       base::UTF8ToUTF16(url::Origin::Create(GURL(media_source)).host()));
85 }
86 
WiredDisplayMediaRouteProvider(mojo::PendingReceiver<mojom::MediaRouteProvider> receiver,mojo::PendingRemote<mojom::MediaRouter> media_router,Profile * profile)87 WiredDisplayMediaRouteProvider::WiredDisplayMediaRouteProvider(
88     mojo::PendingReceiver<mojom::MediaRouteProvider> receiver,
89     mojo::PendingRemote<mojom::MediaRouter> media_router,
90     Profile* profile)
91     : receiver_(this, std::move(receiver)),
92       media_router_(std::move(media_router)),
93       profile_(profile) {
94   media_router_->OnSinkAvailabilityUpdated(
95       kProviderId, mojom::MediaRouter::SinkAvailability::PER_SOURCE);
96 }
97 
~WiredDisplayMediaRouteProvider()98 WiredDisplayMediaRouteProvider::~WiredDisplayMediaRouteProvider() {
99   if (is_observing_displays_) {
100     display::Screen::GetScreen()->RemoveObserver(this);
101     is_observing_displays_ = false;
102   }
103 }
104 
CreateRoute(const std::string & media_source,const std::string & sink_id,const std::string & presentation_id,const url::Origin & origin,int32_t tab_id,base::TimeDelta timeout,bool off_the_record,CreateRouteCallback callback)105 void WiredDisplayMediaRouteProvider::CreateRoute(
106     const std::string& media_source,
107     const std::string& sink_id,
108     const std::string& presentation_id,
109     const url::Origin& origin,
110     int32_t tab_id,
111     base::TimeDelta timeout,
112     bool off_the_record,
113     CreateRouteCallback callback) {
114   DCHECK(!base::Contains(presentations_, presentation_id));
115 #if defined(OS_BSD)
116   std::move(callback).Run(base::nullopt, nullptr,
117 		          std::string("Not implemented"),
118 			  RouteRequestResult::UNKNOWN_ERROR);
119   return;
120 #else
121   base::Optional<Display> display = GetDisplayBySinkId(sink_id);
122   if (!display) {
123     std::move(callback).Run(base::nullopt, nullptr,
124                             std::string("Display not found"),
125                             RouteRequestResult::SINK_NOT_FOUND);
126     return;
127   }
128 
129   // If there already is a presentation on |display|, terminate it.
130   TerminatePresentationsOnDisplay(*display);
131   // Use |presentation_id| as the route ID. This MRP creates only one route per
132   // presentation ID.
133   MediaRoute route(presentation_id, MediaSource(media_source), sink_id,
134                    GetRouteDescription(media_source), true, true);
135   route.set_local_presentation(true);
136   route.set_off_the_record(profile_->IsOffTheRecord());
137   route.set_controller_type(RouteControllerType::kGeneric);
138 
139   Presentation& presentation =
140       presentations_.emplace(presentation_id, route).first->second;
141   presentation.set_receiver(
142       CreatePresentationReceiver(presentation_id, &presentation, *display));
143   presentation.receiver()->Start(presentation_id, GURL(media_source));
144   std::move(callback).Run(route, nullptr, base::nullopt,
145                           RouteRequestResult::OK);
146   NotifyRouteObservers();
147 #endif
148 }
149 
JoinRoute(const std::string & media_source,const std::string & presentation_id,const url::Origin & origin,int32_t tab_id,base::TimeDelta timeout,bool off_the_record,JoinRouteCallback callback)150 void WiredDisplayMediaRouteProvider::JoinRoute(
151     const std::string& media_source,
152     const std::string& presentation_id,
153     const url::Origin& origin,
154     int32_t tab_id,
155     base::TimeDelta timeout,
156     bool off_the_record,
157     JoinRouteCallback callback) {
158   std::move(callback).Run(
159       base::nullopt, nullptr,
160       std::string("Join should be handled by the presentation manager"),
161       RouteRequestResult::UNKNOWN_ERROR);
162 }
163 
ConnectRouteByRouteId(const std::string & media_source,const std::string & route_id,const std::string & presentation_id,const url::Origin & origin,int32_t tab_id,base::TimeDelta timeout,bool off_the_record,ConnectRouteByRouteIdCallback callback)164 void WiredDisplayMediaRouteProvider::ConnectRouteByRouteId(
165     const std::string& media_source,
166     const std::string& route_id,
167     const std::string& presentation_id,
168     const url::Origin& origin,
169     int32_t tab_id,
170     base::TimeDelta timeout,
171     bool off_the_record,
172     ConnectRouteByRouteIdCallback callback) {
173   std::move(callback).Run(
174       base::nullopt, nullptr,
175       std::string("Connect should be handled by the presentation manager"),
176       RouteRequestResult::UNKNOWN_ERROR);
177 }
178 
TerminateRoute(const std::string & route_id,TerminateRouteCallback callback)179 void WiredDisplayMediaRouteProvider::TerminateRoute(
180     const std::string& route_id,
181     TerminateRouteCallback callback) {
182   auto it = presentations_.find(route_id);
183   if (it == presentations_.end()) {
184     std::move(callback).Run(std::string("Presentation not found"),
185                             RouteRequestResult::ROUTE_NOT_FOUND);
186     return;
187   }
188 
189   // The presentation will be removed from |presentations_| in the termination
190   // callback of its receiver.
191   it->second.receiver()->Terminate();
192   std::move(callback).Run(base::nullopt, RouteRequestResult::OK);
193 }
194 
SendRouteMessage(const std::string & media_route_id,const std::string & message)195 void WiredDisplayMediaRouteProvider::SendRouteMessage(
196     const std::string& media_route_id,
197     const std::string& message) {
198   // Messages should be handled by LocalPresentationManager.
199   NOTREACHED();
200 }
201 
SendRouteBinaryMessage(const std::string & media_route_id,const std::vector<uint8_t> & data)202 void WiredDisplayMediaRouteProvider::SendRouteBinaryMessage(
203     const std::string& media_route_id,
204     const std::vector<uint8_t>& data) {
205   // Messages should be handled by LocalPresentationManager.
206   NOTREACHED();
207 }
208 
StartObservingMediaSinks(const std::string & media_source)209 void WiredDisplayMediaRouteProvider::StartObservingMediaSinks(
210     const std::string& media_source) {
211   if (!IsPresentationSource(media_source))
212     return;
213 
214   // Start observing displays if |this| isn't already observing.
215   if (!is_observing_displays_) {
216     display::Screen::GetScreen()->AddObserver(this);
217     is_observing_displays_ = true;
218   }
219   sink_queries_.insert(media_source);
220   UpdateMediaSinks(media_source);
221 }
222 
StopObservingMediaSinks(const std::string & media_source)223 void WiredDisplayMediaRouteProvider::StopObservingMediaSinks(
224     const std::string& media_source) {
225   sink_queries_.erase(media_source);
226 }
227 
StartObservingMediaRoutes(const std::string & media_source)228 void WiredDisplayMediaRouteProvider::StartObservingMediaRoutes(
229     const std::string& media_source) {
230   route_queries_.insert(media_source);
231   std::vector<MediaRoute> route_list;
232   for (const auto& presentation : presentations_)
233     route_list.push_back(presentation.second.route());
234   media_router_->OnRoutesUpdated(kProviderId, route_list, media_source, {});
235 }
236 
StopObservingMediaRoutes(const std::string & media_source)237 void WiredDisplayMediaRouteProvider::StopObservingMediaRoutes(
238     const std::string& media_source) {
239   route_queries_.erase(media_source);
240 }
241 
StartListeningForRouteMessages(const std::string & route_id)242 void WiredDisplayMediaRouteProvider::StartListeningForRouteMessages(
243     const std::string& route_id) {
244   // Messages should be handled by LocalPresentationManager.
245 }
246 
StopListeningForRouteMessages(const std::string & route_id)247 void WiredDisplayMediaRouteProvider::StopListeningForRouteMessages(
248     const std::string& route_id) {
249   // Messages should be handled by LocalPresentationManager.
250 }
251 
DetachRoute(const std::string & route_id)252 void WiredDisplayMediaRouteProvider::DetachRoute(const std::string& route_id) {
253   // Detaching should be handled by LocalPresentationManager.
254   NOTREACHED();
255 }
256 
EnableMdnsDiscovery()257 void WiredDisplayMediaRouteProvider::EnableMdnsDiscovery() {}
258 
UpdateMediaSinks(const std::string & media_source)259 void WiredDisplayMediaRouteProvider::UpdateMediaSinks(
260     const std::string& media_source) {
261   if (IsPresentationSource(media_source))
262     media_router_->OnSinksReceived(kProviderId, media_source, GetSinks(), {});
263 }
264 
ProvideSinks(const std::string & provider_name,const std::vector<media_router::MediaSinkInternal> & sinks)265 void WiredDisplayMediaRouteProvider::ProvideSinks(
266     const std::string& provider_name,
267     const std::vector<media_router::MediaSinkInternal>& sinks) {
268   NOTREACHED();
269 }
270 
CreateMediaRouteController(const std::string & route_id,mojo::PendingReceiver<mojom::MediaController> media_controller,mojo::PendingRemote<mojom::MediaStatusObserver> observer,CreateMediaRouteControllerCallback callback)271 void WiredDisplayMediaRouteProvider::CreateMediaRouteController(
272     const std::string& route_id,
273     mojo::PendingReceiver<mojom::MediaController> media_controller,
274     mojo::PendingRemote<mojom::MediaStatusObserver> observer,
275     CreateMediaRouteControllerCallback callback) {
276   // Local screens do not support media controls.
277   auto it = presentations_.find(route_id);
278   if (it == presentations_.end()) {
279     std::move(callback).Run(false);
280     return;
281   }
282   it->second.SetMojoConnections(std::move(media_controller),
283                                 std::move(observer));
284   std::move(callback).Run(true);
285 }
286 
GetState(GetStateCallback callback)287 void WiredDisplayMediaRouteProvider::GetState(GetStateCallback callback) {
288   NOTIMPLEMENTED();
289   std::move(callback).Run(mojom::ProviderStatePtr());
290 }
291 
OnDidProcessDisplayChanges()292 void WiredDisplayMediaRouteProvider::OnDidProcessDisplayChanges() {
293   NotifySinkObservers();
294 }
295 
OnDisplayAdded(const Display & new_display)296 void WiredDisplayMediaRouteProvider::OnDisplayAdded(
297     const Display& new_display) {
298   NotifySinkObservers();
299 }
300 
OnDisplayRemoved(const Display & old_display)301 void WiredDisplayMediaRouteProvider::OnDisplayRemoved(
302     const Display& old_display) {
303   const std::string sink_id =
304       WiredDisplayMediaRouteProvider::GetSinkIdForDisplay(old_display);
305   auto it = std::find_if(
306       presentations_.begin(), presentations_.end(),
307       [&sink_id](
308           const std::pair<const std::string, Presentation>& presentation) {
309         return presentation.second.route().media_sink_id() == sink_id;
310       });
311   if (it != presentations_.end())
312     it->second.receiver()->ExitFullscreen();
313   NotifySinkObservers();
314 }
315 
OnDisplayMetricsChanged(const Display & display,uint32_t changed_metrics)316 void WiredDisplayMediaRouteProvider::OnDisplayMetricsChanged(
317     const Display& display,
318     uint32_t changed_metrics) {
319   NotifySinkObservers();
320 }
321 
GetAllDisplays() const322 std::vector<Display> WiredDisplayMediaRouteProvider::GetAllDisplays() const {
323   return display::Screen::GetScreen()->GetAllDisplays();
324 }
325 
GetPrimaryDisplay() const326 Display WiredDisplayMediaRouteProvider::GetPrimaryDisplay() const {
327   return display::Screen::GetScreen()->GetPrimaryDisplay();
328 }
329 
Presentation(const MediaRoute & route)330 WiredDisplayMediaRouteProvider::Presentation::Presentation(
331     const MediaRoute& route)
332     : route_(route), status_(base::in_place) {}
333 
334 WiredDisplayMediaRouteProvider::Presentation::Presentation(
335     Presentation&& other) = default;
336 
337 WiredDisplayMediaRouteProvider::Presentation::~Presentation() = default;
338 
UpdatePresentationTitle(const std::string & title)339 void WiredDisplayMediaRouteProvider::Presentation::UpdatePresentationTitle(
340     const std::string& title) {
341   if (status_->title == title)
342     return;
343 
344   status_->title = title;
345   if (media_status_observer_)
346     media_status_observer_->OnMediaStatusUpdated(status_.Clone());
347 }
348 
SetMojoConnections(mojo::PendingReceiver<mojom::MediaController> media_controller,mojo::PendingRemote<mojom::MediaStatusObserver> observer)349 void WiredDisplayMediaRouteProvider::Presentation::SetMojoConnections(
350     mojo::PendingReceiver<mojom::MediaController> media_controller,
351     mojo::PendingRemote<mojom::MediaStatusObserver> observer) {
352   // This provider does not support media controls, so we do not bind
353   // |media_controller| to a controller implementation.
354   media_controller_receiver_ = std::move(media_controller);
355 
356   media_status_observer_.reset();
357   media_status_observer_.Bind(std::move(observer));
358   media_status_observer_->OnMediaStatusUpdated(status_.Clone());
359   media_status_observer_.set_disconnect_handler(base::BindOnce(
360       &WiredDisplayMediaRouteProvider::Presentation::ResetMojoConnections,
361       base::Unretained(this)));
362 }
363 
ResetMojoConnections()364 void WiredDisplayMediaRouteProvider::Presentation::ResetMojoConnections() {
365   media_controller_receiver_.reset();
366   media_status_observer_.reset();
367 }
368 
NotifyRouteObservers() const369 void WiredDisplayMediaRouteProvider::NotifyRouteObservers() const {
370   std::vector<MediaRoute> route_list;
371   for (const auto& presentation : presentations_)
372     route_list.push_back(presentation.second.route());
373   for (const auto& route_query : route_queries_)
374     media_router_->OnRoutesUpdated(kProviderId, route_list, route_query, {});
375 }
376 
NotifySinkObservers()377 void WiredDisplayMediaRouteProvider::NotifySinkObservers() {
378   std::vector<MediaSinkInternal> sinks = GetSinks();
379   device_count_metrics_.RecordDeviceCountsIfNeeded(sinks.size(), sinks.size());
380   ReportSinkAvailability(sinks);
381   for (const auto& sink_query : sink_queries_)
382     media_router_->OnSinksReceived(kProviderId, sink_query, sinks, {});
383 }
384 
GetSinks() const385 std::vector<MediaSinkInternal> WiredDisplayMediaRouteProvider::GetSinks()
386     const {
387   std::vector<MediaSinkInternal> sinks;
388   std::vector<Display> displays = GetAvailableDisplays();
389   for (size_t i = 0; i < displays.size(); i++)
390     sinks.push_back(CreateSinkForDisplay(displays[i], i + 1));
391   return sinks;
392 }
393 
GetAvailableDisplays() const394 std::vector<Display> WiredDisplayMediaRouteProvider::GetAvailableDisplays()
395     const {
396   std::vector<Display> displays = GetAllDisplays();
397   // If there is only one display, the user should not be able to present to it.
398   // If there are no displays, GetPrimaryDisplay() below fails.
399   if (displays.size() <= 1)
400     return std::vector<Display>();
401 
402   const Display primary_display = GetPrimaryDisplay();
403   std::sort(
404       displays.begin(), displays.end(),
405       [&primary_display](const Display& display1, const Display& display2) {
406         return CompareDisplays(primary_display.id(), display1, display2);
407       });
408 
409   // Remove displays that mirror the primary display. On some platforms such as
410   // Windows, mirrored displays are reported as one display. On others, mirrored
411   // displays are reported separately but with the same bounds.
412   base::EraseIf(displays, [&primary_display](const Display& display) {
413     return display.id() != primary_display.id() &&
414            display.bounds() == primary_display.bounds();
415   });
416   // If all the displays are mirrored, the user should not be able to present to
417   // them.
418   return displays.size() == 1 ? std::vector<Display>() : displays;
419 }
420 
ReportSinkAvailability(const std::vector<MediaSinkInternal> & sinks)421 void WiredDisplayMediaRouteProvider::ReportSinkAvailability(
422     const std::vector<MediaSinkInternal>& sinks) {
423   mojom::MediaRouter::SinkAvailability sink_availability =
424       sinks.empty() ? mojom::MediaRouter::SinkAvailability::UNAVAILABLE
425                     : mojom::MediaRouter::SinkAvailability::PER_SOURCE;
426   media_router_->OnSinkAvailabilityUpdated(kProviderId, sink_availability);
427 }
428 
RemovePresentationById(const std::string & presentation_id)429 void WiredDisplayMediaRouteProvider::RemovePresentationById(
430     const std::string& presentation_id) {
431   auto entry = presentations_.find(presentation_id);
432   if (entry == presentations_.end())
433     return;
434   media_router_->OnPresentationConnectionStateChanged(
435       entry->second.route().media_route_id(),
436       blink::mojom::PresentationConnectionState::TERMINATED);
437   presentations_.erase(entry);
438   NotifyRouteObservers();
439 }
440 
441 std::unique_ptr<WiredDisplayPresentationReceiver>
CreatePresentationReceiver(const std::string & presentation_id,Presentation * presentation,const Display & display)442 WiredDisplayMediaRouteProvider::CreatePresentationReceiver(
443     const std::string& presentation_id,
444     Presentation* presentation,
445     const Display& display) {
446   return WiredDisplayPresentationReceiverFactory::Create(
447       profile_, display.bounds(),
448       base::BindOnce(&WiredDisplayMediaRouteProvider::RemovePresentationById,
449                      base::Unretained(this), presentation_id),
450       base::BindRepeating(&WiredDisplayMediaRouteProvider::Presentation::
451                               UpdatePresentationTitle,
452                           base::Unretained(presentation)));
453 }
454 
TerminatePresentationsOnDisplay(const display::Display & display)455 void WiredDisplayMediaRouteProvider::TerminatePresentationsOnDisplay(
456     const display::Display& display) {
457   std::vector<WiredDisplayPresentationReceiver*> presentations_to_terminate;
458   // We cannot call Terminate() on the receiver while iterating over
459   // |presentations_| because that might invoke a callback to delete the
460   // presentation from |presentations_|.
461   for (const auto& presentation : presentations_) {
462     if (presentation.second.route().media_sink_id() ==
463         GetSinkIdForDisplay(display)) {
464       presentations_to_terminate.push_back(presentation.second.receiver());
465     }
466   }
467   for (auto* presentation_to_terminate : presentations_to_terminate)
468     presentation_to_terminate->Terminate();
469 }
470 
GetDisplayBySinkId(const std::string & sink_id) const471 base::Optional<Display> WiredDisplayMediaRouteProvider::GetDisplayBySinkId(
472     const std::string& sink_id) const {
473   std::vector<Display> displays = GetAllDisplays();
474   auto it = std::find_if(displays.begin(), displays.end(),
475                          [&sink_id](const Display& display) {
476                            return GetSinkIdForDisplay(display) == sink_id;
477                          });
478   return it == displays.end() ? base::nullopt
479                               : base::make_optional<Display>(std::move(*it));
480 }
481 
482 }  // namespace media_router
483