blob: 916daa5a9f5aabb09bb6db3d4a8306bec999bd25 [file] [log] [blame]
// Copyright 2018 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/views/media_router/media_router_views_ui.h"
#include <string>
#include <utility>
#include <vector>
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/media/router/media_router_metrics.h"
#include "chrome/browser/ui/media_router/media_sink_with_cast_modes.h"
#include "chrome/browser/ui/media_router/ui_media_sink.h"
#include "chrome/common/media_router/route_request_result.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/browser_context.h"
#include "ui/base/l10n/l10n_util.h"
namespace media_router {
namespace {
// Returns true if |issue| is associated with |ui_sink|.
bool IssueMatches(const Issue& issue, const UIMediaSink& ui_sink) {
return issue.info().sink_id == ui_sink.id ||
(!issue.info().route_id.empty() && ui_sink.route &&
issue.info().route_id == ui_sink.route->media_route_id());
}
base::string16 GetSinkFriendlyName(const MediaSink& sink) {
// Use U+2010 (HYPHEN) instead of ASCII hyphen to avoid problems with RTL
// languages.
const char* separator = u8" \u2010 ";
return base::UTF8ToUTF16(sink.description() ? sink.name() + separator +
sink.description().value()
: sink.name());
}
} // namespace
MediaRouterViewsUI::MediaRouterViewsUI() = default;
MediaRouterViewsUI::~MediaRouterViewsUI() {
for (CastDialogController::Observer& observer : observers_)
observer.OnControllerInvalidated();
}
void MediaRouterViewsUI::AddObserver(CastDialogController::Observer* observer) {
observers_.AddObserver(observer);
// TODO(takumif): Update the header when this object is initialized instead.
UpdateModelHeader();
}
void MediaRouterViewsUI::RemoveObserver(
CastDialogController::Observer* observer) {
observers_.RemoveObserver(observer);
}
void MediaRouterViewsUI::StartCasting(const std::string& sink_id,
MediaCastMode cast_mode) {
CreateRoute(sink_id, cast_mode);
UpdateSinks();
}
void MediaRouterViewsUI::StopCasting(const std::string& route_id) {
terminating_route_id_ = route_id;
// |route_id| may become invalid after UpdateSinks(), so we cannot refer to
// |route_id| below this line.
UpdateSinks();
TerminateRoute(terminating_route_id_.value());
}
void MediaRouterViewsUI::ChooseLocalFile(
base::OnceCallback<void(const ui::SelectedFileInfo*)> callback) {
file_selection_callback_ = std::move(callback);
OpenFileDialog();
}
void MediaRouterViewsUI::ClearIssue(const Issue::Id& issue_id) {
RemoveIssue(issue_id);
}
std::vector<MediaSinkWithCastModes> MediaRouterViewsUI::GetEnabledSinks()
const {
std::vector<MediaSinkWithCastModes> sinks =
MediaRouterUIBase::GetEnabledSinks();
// Remove the pseudo-sink, since it's only used in the WebUI dialog.
// TODO(takumif): Remove this once we've removed pseudo-sink from Cloud MRP.
base::EraseIf(sinks, [](const MediaSinkWithCastModes& sink) {
return base::StartsWith(sink.sink.id(),
"pseudo:", base::CompareCase::SENSITIVE);
});
// Filter out cloud sinks if the window is incognito. Casting to cloud sinks
// from incognito is not currently supported by the Cloud MRP. This is not
// the best place to do this, but the Media Router browser service and
// extension process are shared between normal and incognito, so incognito
// behaviors around sink availability have to be handled at the UI layer.
if (initiator()->GetBrowserContext()->IsOffTheRecord()) {
base::EraseIf(sinks, [](const MediaSinkWithCastModes& sink) {
return sink.sink.IsMaybeCloudSink();
});
}
return sinks;
}
void MediaRouterViewsUI::InitCommon(content::WebContents* initiator) {
MediaRouterUIBase::InitCommon(initiator);
// We don't start observing issues in MediaRouterUIBase::InitCommon() because
// in the WebUI dialog, we need to wait for the WebUI to load before
// starting to observe.
StartObservingIssues();
}
void MediaRouterViewsUI::OnRoutesUpdated(
const std::vector<MediaRoute>& routes,
const std::vector<MediaRoute::Id>& joinable_route_ids) {
MediaRouterUIBase::OnRoutesUpdated(routes, joinable_route_ids);
if (terminating_route_id_ &&
std::find_if(
routes.begin(), routes.end(), [this](const MediaRoute& route) {
return route.media_route_id() == terminating_route_id_.value();
}) == routes.end()) {
terminating_route_id_.reset();
}
UpdateSinks();
}
void MediaRouterViewsUI::UpdateSinks() {
std::vector<UIMediaSink> media_sinks;
for (const MediaSinkWithCastModes& sink : GetEnabledSinks()) {
auto route_it = std::find_if(
routes().begin(), routes().end(), [&sink](const MediaRoute& route) {
return route.media_sink_id() == sink.sink.id();
});
const MediaRoute* route = route_it == routes().end() ? nullptr : &*route_it;
media_sinks.push_back(ConvertToUISink(sink, route, issue_));
}
model_.set_media_sinks(std::move(media_sinks));
for (CastDialogController::Observer& observer : observers_)
observer.OnModelUpdated(model_);
}
UIMediaSink MediaRouterViewsUI::ConvertToUISink(
const MediaSinkWithCastModes& sink,
const MediaRoute* route,
const base::Optional<Issue>& issue) {
UIMediaSink ui_sink;
ui_sink.id = sink.sink.id();
ui_sink.friendly_name = GetSinkFriendlyName(sink.sink);
ui_sink.icon_type = sink.sink.icon_type();
if (route) {
ui_sink.status_text = base::UTF8ToUTF16(route->description());
ui_sink.route = *route;
ui_sink.state = terminating_route_id_ && route->media_route_id() ==
terminating_route_id_.value()
? UIMediaSinkState::DISCONNECTING
: UIMediaSinkState::CONNECTED;
} else {
ui_sink.state = current_route_request() &&
sink.sink.id() == current_route_request()->sink_id
? UIMediaSinkState::CONNECTING
: UIMediaSinkState::AVAILABLE;
ui_sink.cast_modes = sink.cast_modes;
}
if (ui_sink.icon_type == SinkIconType::HANGOUT &&
ui_sink.state == UIMediaSinkState::AVAILABLE && sink.sink.domain()) {
ui_sink.status_text = base::UTF8ToUTF16(*sink.sink.domain());
}
if (issue && IssueMatches(*issue, ui_sink))
ui_sink.issue = issue;
return ui_sink;
}
void MediaRouterViewsUI::OnIssue(const Issue& issue) {
issue_ = issue;
UpdateSinks();
}
void MediaRouterViewsUI::OnIssueCleared() {
issue_ = base::nullopt;
UpdateSinks();
}
void MediaRouterViewsUI::OnDefaultPresentationChanged(
const content::PresentationRequest& presentation_request) {
// This sets the default cast mode to presentation when the dialog is opened.
// So we need to update the header to reflect that.
MediaRouterUIBase::OnDefaultPresentationChanged(presentation_request);
UpdateModelHeader();
}
void MediaRouterViewsUI::OnDefaultPresentationRemoved() {
MediaRouterUIBase::OnDefaultPresentationRemoved();
UpdateModelHeader();
}
void MediaRouterViewsUI::UpdateModelHeader() {
const base::string16 source_name = GetPresentationRequestSourceName();
const base::string16 header_text =
source_name.empty()
? l10n_util::GetStringUTF16(IDS_MEDIA_ROUTER_TAB_MIRROR_CAST_MODE)
: l10n_util::GetStringFUTF16(IDS_MEDIA_ROUTER_PRESENTATION_CAST_MODE,
source_name);
model_.set_dialog_header(header_text);
for (CastDialogController::Observer& observer : observers_)
observer.OnModelUpdated(model_);
}
void MediaRouterViewsUI::FileDialogFileSelected(
const ui::SelectedFileInfo& file_info) {
std::move(file_selection_callback_).Run(&file_info);
}
void MediaRouterViewsUI::FileDialogSelectionFailed(const IssueInfo& issue) {
MediaRouterUIBase::FileDialogSelectionFailed(issue);
std::move(file_selection_callback_).Run(nullptr);
}
void MediaRouterViewsUI::FileDialogSelectionCanceled() {
MediaRouterUIBase::FileDialogSelectionCanceled();
std::move(file_selection_callback_).Run(nullptr);
}
} // namespace media_router