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