blob: cfcb774ea31a839bf880bb6f0b33fb8f3423dc49 [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/webui/media_router/media_router_webui_message_handler.h"
#include <algorithm>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/media/router/media_router_metrics.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/ui/webui/media_router/media_router_ui.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "components/browser_sync/profile_sync_service.h"
#include "components/prefs/pref_service.h"
#include "components/signin/core/browser/account_tracker_service.h"
#include "content/public/browser/web_ui.h"
#include "extensions/common/constants.h"
#include "services/identity/public/cpp/identity_manager.h"
#include "ui/base/l10n/l10n_util.h"
namespace media_router {
namespace {
const char kCastLearnMorePageUrl[] =
"https://www.google.com/chrome/devices/chromecast/learn.html";
const char kHelpPageUrlPrefix[] =
"https://support.google.com/chromecast/answer/%d";
// Message names.
const char kRequestInitialData[] = "requestInitialData";
const char kCreateRoute[] = "requestRoute";
const char kAcknowledgeFirstRunFlow[] = "acknowledgeFirstRunFlow";
const char kActOnIssue[] = "actOnIssue";
const char kCloseRoute[] = "closeRoute";
const char kJoinRoute[] = "joinRoute";
const char kCloseDialog[] = "closeDialog";
const char kReportBlur[] = "reportBlur";
const char kReportClickedSinkIndex[] = "reportClickedSinkIndex";
const char kReportFilter[] = "reportFilter";
const char kReportInitialAction[] = "reportInitialAction";
const char kReportInitialState[] = "reportInitialState";
const char kReportNavigateToView[] = "reportNavigateToView";
const char kReportRouteCreationOutcome[] = "reportRouteCreationOutcome";
const char kReportRouteCreation[] = "reportRouteCreation";
const char kReportSelectedCastMode[] = "reportSelectedCastMode";
const char kReportSinkCount[] = "reportSinkCount";
const char kReportTimeToClickSink[] = "reportTimeToClickSink";
const char kReportTimeToInitialActionClose[] = "reportTimeToInitialActionClose";
const char kReportWebUIRouteControllerLoaded[] =
"reportWebUIRouteControllerLoaded";
const char kSearchSinksAndCreateRoute[] = "searchSinksAndCreateRoute";
const char kOnInitialDataReceived[] = "onInitialDataReceived";
const char kOnMediaControllerAvailable[] = "onMediaControllerAvailable";
const char kOnMediaControllerClosed[] = "onMediaControllerClosed";
const char kPauseCurrentMedia[] = "pauseCurrentMedia";
const char kPlayCurrentMedia[] = "playCurrentMedia";
const char kSeekCurrentMedia[] = "seekCurrentMedia";
const char kSelectLocalMediaFile[] = "selectLocalMediaFile";
const char kSetCurrentMediaMute[] = "setCurrentMediaMute";
const char kSetCurrentMediaVolume[] = "setCurrentMediaVolume";
const char kSetMediaRemotingEnabled[] = "setMediaRemotingEnabled";
const char kHangoutsSetLocalPresent[] = "hangouts.setLocalPresent";
// JS function names.
const char kSetInitialData[] = "media_router.ui.setInitialData";
const char kOnCreateRouteResponseReceived[] =
"media_router.ui.onCreateRouteResponseReceived";
const char kOnRouteControllerInvalidated[] =
"media_router.ui.onRouteControllerInvalidated";
const char kReceiveSearchResult[] = "media_router.ui.receiveSearchResult";
const char kSetFirstRunFlowData[] = "media_router.ui.setFirstRunFlowData";
const char kSetIssue[] = "media_router.ui.setIssue";
const char kSetSinkListAndIdentity[] = "media_router.ui.setSinkListAndIdentity";
const char kSetRouteList[] = "media_router.ui.setRouteList";
const char kSetCastModeList[] = "media_router.ui.setCastModeList";
const char kUpdateMaxHeight[] = "media_router.ui.updateMaxHeight";
const char kUpdateRouteStatus[] = "media_router.ui.updateRouteStatus";
const char kUserSelectedLocalMediaFile[] =
"media_router.ui.userSelectedLocalMediaFile";
const char kWindowOpen[] = "window.open";
std::unique_ptr<base::DictionaryValue> SinksAndIdentityToValue(
const std::vector<MediaSinkWithCastModes>& sinks,
const AccountInfo& account_info) {
auto sink_list_and_identity = std::make_unique<base::DictionaryValue>();
bool show_email = false;
bool show_domain = false;
std::string user_domain;
if (account_info.IsValid()) {
user_domain = account_info.hosted_domain;
sink_list_and_identity->SetString("userEmail", account_info.email);
}
auto sinks_val = std::make_unique<base::ListValue>();
for (const MediaSinkWithCastModes& sink_with_cast_modes : sinks) {
auto sink_val = std::make_unique<base::DictionaryValue>();
const MediaSink& sink = sink_with_cast_modes.sink;
sink_val->SetString("id", sink.id());
sink_val->SetString("name", sink.name());
sink_val->SetInteger("iconType", static_cast<int>(sink.icon_type()));
if (sink.description())
sink_val->SetString("description", *sink.description());
bool is_pseudo_sink =
base::StartsWith(sink.id(), "pseudo:", base::CompareCase::SENSITIVE);
if (!user_domain.empty() && sink.domain() && !sink.domain()->empty()) {
std::string domain = *sink.domain();
// Convert default domains to user domain
if (domain == "default") {
domain = user_domain;
if (domain == AccountTrackerService::kNoHostedDomainFound) {
// Default domain will be empty for non-dasher accounts.
domain.clear();
}
}
sink_val->SetString("domain", domain);
show_email = show_email || !is_pseudo_sink;
if (!domain.empty() && domain != user_domain) {
show_domain = true;
}
}
int cast_mode_bits = 0;
for (MediaCastMode cast_mode : sink_with_cast_modes.cast_modes)
cast_mode_bits |= cast_mode;
sink_val->SetInteger("castModes", cast_mode_bits);
sink_val->SetBoolean("isPseudoSink", is_pseudo_sink);
sinks_val->Append(std::move(sink_val));
}
sink_list_and_identity->Set("sinks", std::move(sinks_val));
sink_list_and_identity->SetBoolean("showEmail", show_email);
sink_list_and_identity->SetBoolean("showDomain", show_domain);
return sink_list_and_identity;
}
std::unique_ptr<base::DictionaryValue> RouteToValue(
const MediaRoute& route,
bool can_join,
bool incognito,
int current_cast_mode) {
auto dictionary = std::make_unique<base::DictionaryValue>();
dictionary->SetString("id", route.media_route_id());
dictionary->SetString("sinkId", route.media_sink_id());
dictionary->SetString("description", route.description());
dictionary->SetBoolean("isLocal", route.is_local());
dictionary->SetBoolean("supportsWebUiController",
route.controller_type() != RouteControllerType::kNone);
dictionary->SetBoolean("canJoin", can_join);
if (current_cast_mode > 0) {
dictionary->SetInteger("currentCastMode", current_cast_mode);
}
return dictionary;
}
std::unique_ptr<base::ListValue> CastModesToValue(
const CastModeSet& cast_modes,
const std::string& source_host,
base::Optional<MediaCastMode> forced_cast_mode) {
auto value = std::make_unique<base::ListValue>();
for (const MediaCastMode& cast_mode : cast_modes) {
auto cast_mode_val = std::make_unique<base::DictionaryValue>();
cast_mode_val->SetInteger("type", cast_mode);
cast_mode_val->SetString(
"description", MediaCastModeToDescription(cast_mode, source_host));
cast_mode_val->SetString("host", source_host);
cast_mode_val->SetBoolean(
"isForced", forced_cast_mode && forced_cast_mode == cast_mode);
value->Append(std::move(cast_mode_val));
}
return value;
}
// Returns an Issue dictionary created from |issue| that can be used in WebUI.
std::unique_ptr<base::DictionaryValue> IssueToValue(const Issue& issue) {
const IssueInfo& issue_info = issue.info();
auto dictionary = std::make_unique<base::DictionaryValue>();
dictionary->SetInteger("id", issue.id());
dictionary->SetString("title", issue_info.title);
dictionary->SetString("message", issue_info.message);
dictionary->SetInteger("defaultActionType",
static_cast<int>(issue_info.default_action));
if (!issue_info.secondary_actions.empty()) {
DCHECK_EQ(1u, issue_info.secondary_actions.size());
dictionary->SetInteger("secondaryActionType",
static_cast<int>(issue_info.secondary_actions[0]));
}
if (!issue_info.route_id.empty())
dictionary->SetString("routeId", issue_info.route_id);
dictionary->SetBoolean("isBlocking", issue_info.is_blocking);
if (issue_info.help_page_id > 0)
dictionary->SetInteger("helpPageId", issue_info.help_page_id);
return dictionary;
}
bool IsValidIssueActionTypeNum(int issue_action_type_num) {
return issue_action_type_num >= 0 &&
issue_action_type_num <=
static_cast<int>(IssueInfo::Action::NUM_VALUES);
}
// Composes a "learn more" URL. The URL depends on template arguments in |args|.
// Returns an empty string if |args| is invalid.
std::string GetLearnMoreUrl(const base::DictionaryValue* args) {
// TODO(imcheng): The template arguments for determining the learn more URL
// should come from the Issue object in the browser, not from WebUI.
int help_page_id = -1;
if (!args->GetInteger("helpPageId", &help_page_id) || help_page_id < 0) {
DVLOG(1) << "Invalid help page id.";
return std::string();
}
std::string help_url = base::StringPrintf(kHelpPageUrlPrefix, help_page_id);
if (!GURL(help_url).is_valid()) {
DVLOG(1) << "Error: URL is invalid and cannot be opened.";
return std::string();
}
return help_url;
}
} // namespace
MediaRouterWebUIMessageHandler::MediaRouterWebUIMessageHandler(
MediaRouterUI* media_router_ui)
: incognito_(
Profile::FromWebUI(media_router_ui->web_ui())->IsOffTheRecord()),
dialog_closing_(false),
media_router_ui_(media_router_ui) {}
MediaRouterWebUIMessageHandler::~MediaRouterWebUIMessageHandler() {}
void MediaRouterWebUIMessageHandler::UpdateSinks(
const std::vector<MediaSinkWithCastModes>& sinks) {
DVLOG(2) << "UpdateSinks";
std::unique_ptr<base::DictionaryValue> sinks_and_identity_val(
SinksAndIdentityToValue(sinks, GetAccountInfo()));
web_ui()->CallJavascriptFunctionUnsafe(kSetSinkListAndIdentity,
*sinks_and_identity_val);
}
void MediaRouterWebUIMessageHandler::UpdateRoutes(
const std::vector<MediaRoute>& routes,
const std::vector<MediaRoute::Id>& joinable_route_ids,
const std::unordered_map<MediaRoute::Id, MediaCastMode>&
current_cast_modes) {
std::unique_ptr<base::ListValue> routes_val(
RoutesToValue(routes, joinable_route_ids, current_cast_modes));
web_ui()->CallJavascriptFunctionUnsafe(kSetRouteList, *routes_val);
}
void MediaRouterWebUIMessageHandler::UpdateCastModes(
const CastModeSet& cast_modes,
const std::string& source_host,
base::Optional<MediaCastMode> forced_cast_mode) {
DVLOG(2) << "UpdateCastModes";
std::unique_ptr<base::ListValue> cast_modes_val(
CastModesToValue(cast_modes, source_host, forced_cast_mode));
web_ui()->CallJavascriptFunctionUnsafe(kSetCastModeList, *cast_modes_val);
}
void MediaRouterWebUIMessageHandler::OnCreateRouteResponseReceived(
const MediaSink::Id& sink_id,
const MediaRoute* route) {
DVLOG(2) << "OnCreateRouteResponseReceived";
if (route) {
int current_cast_mode = CurrentCastModeForRouteId(
route->media_route_id(), media_router_ui_->routes_and_cast_modes());
std::unique_ptr<base::DictionaryValue> route_value(
RouteToValue(*route, false, incognito_, current_cast_mode));
web_ui()->CallJavascriptFunctionUnsafe(kOnCreateRouteResponseReceived,
base::Value(sink_id), *route_value,
base::Value(route->for_display()));
} else {
web_ui()->CallJavascriptFunctionUnsafe(kOnCreateRouteResponseReceived,
base::Value(sink_id), base::Value(),
base::Value(false));
}
}
void MediaRouterWebUIMessageHandler::ReturnSearchResult(
const std::string& sink_id) {
DVLOG(2) << "ReturnSearchResult";
web_ui()->CallJavascriptFunctionUnsafe(kReceiveSearchResult,
base::Value(sink_id));
}
void MediaRouterWebUIMessageHandler::UpdateIssue(const Issue& issue) {
DVLOG(2) << "UpdateIssue";
web_ui()->CallJavascriptFunctionUnsafe(kSetIssue, *IssueToValue(issue));
}
void MediaRouterWebUIMessageHandler::ClearIssue() {
DVLOG(2) << "ClearIssue";
web_ui()->CallJavascriptFunctionUnsafe(kSetIssue, base::Value());
}
void MediaRouterWebUIMessageHandler::UpdateMaxDialogHeight(int height) {
DVLOG(2) << "UpdateMaxDialogHeight";
web_ui()->CallJavascriptFunctionUnsafe(kUpdateMaxHeight, base::Value(height));
}
void MediaRouterWebUIMessageHandler::UpdateMediaRouteStatus(
const MediaStatus& status) {
current_media_status_ = base::make_optional<MediaStatus>(MediaStatus(status));
base::DictionaryValue status_value;
status_value.SetString("title", status.title);
status_value.SetBoolean("canPlayPause", status.can_play_pause);
status_value.SetBoolean("canMute", status.can_mute);
status_value.SetBoolean("canSetVolume", status.can_set_volume);
status_value.SetBoolean("canSeek", status.can_seek);
status_value.SetInteger("playState", static_cast<int>(status.play_state));
status_value.SetBoolean("isMuted", status.is_muted);
status_value.SetInteger("duration", status.duration.InSeconds());
status_value.SetInteger("currentTime", status.current_time.InSeconds());
status_value.SetDouble("volume", status.volume);
if (status.hangouts_extra_data) {
base::Value hangouts_extra_data(base::Value::Type::DICTIONARY);
hangouts_extra_data.SetKey(
"localPresent", base::Value(status.hangouts_extra_data->local_present));
status_value.SetKey("hangoutsExtraData", std::move(hangouts_extra_data));
}
if (status.mirroring_extra_data) {
base::Value mirroring_extra_data(base::Value::Type::DICTIONARY);
mirroring_extra_data.SetKey(
"mediaRemotingEnabled",
base::Value(status.mirroring_extra_data->media_remoting_enabled));
status_value.SetKey("mirroringExtraData", std::move(mirroring_extra_data));
}
web_ui()->CallJavascriptFunctionUnsafe(kUpdateRouteStatus,
std::move(status_value));
}
void MediaRouterWebUIMessageHandler::OnRouteControllerInvalidated() {
web_ui()->CallJavascriptFunctionUnsafe(kOnRouteControllerInvalidated);
}
void MediaRouterWebUIMessageHandler::UserSelectedLocalMediaFile(
base::FilePath::StringType file_name) {
DVLOG(2) << "UserSelectedLocalMediaFile";
web_ui()->CallJavascriptFunctionUnsafe(kUserSelectedLocalMediaFile,
base::Value(file_name));
}
void MediaRouterWebUIMessageHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
kRequestInitialData,
base::BindRepeating(&MediaRouterWebUIMessageHandler::OnRequestInitialData,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kCreateRoute,
base::BindRepeating(&MediaRouterWebUIMessageHandler::OnCreateRoute,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kAcknowledgeFirstRunFlow,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnAcknowledgeFirstRunFlow,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kActOnIssue,
base::BindRepeating(&MediaRouterWebUIMessageHandler::OnActOnIssue,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kCloseRoute,
base::BindRepeating(&MediaRouterWebUIMessageHandler::OnCloseRoute,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kJoinRoute,
base::BindRepeating(&MediaRouterWebUIMessageHandler::OnJoinRoute,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kCloseDialog,
base::BindRepeating(&MediaRouterWebUIMessageHandler::OnCloseDialog,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportBlur,
base::BindRepeating(&MediaRouterWebUIMessageHandler::OnReportBlur,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportClickedSinkIndex,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnReportClickedSinkIndex,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportFilter,
base::BindRepeating(&MediaRouterWebUIMessageHandler::OnReportFilter,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportInitialState,
base::BindRepeating(&MediaRouterWebUIMessageHandler::OnReportInitialState,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportInitialAction,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnReportInitialAction,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportRouteCreation,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnReportRouteCreation,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportRouteCreationOutcome,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnReportRouteCreationOutcome,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportSelectedCastMode,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnReportSelectedCastMode,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportNavigateToView,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnReportNavigateToView,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportSinkCount,
base::BindRepeating(&MediaRouterWebUIMessageHandler::OnReportSinkCount,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportTimeToClickSink,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnReportTimeToClickSink,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportTimeToInitialActionClose,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnReportTimeToInitialActionClose,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportWebUIRouteControllerLoaded,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnReportWebUIRouteControllerLoaded,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kSearchSinksAndCreateRoute,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnSearchSinksAndCreateRoute,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kOnInitialDataReceived,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnInitialDataReceived,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kOnMediaControllerAvailable,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnMediaControllerAvailable,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kOnMediaControllerClosed,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnMediaControllerClosed,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kPauseCurrentMedia,
base::BindRepeating(&MediaRouterWebUIMessageHandler::OnPauseCurrentMedia,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kPlayCurrentMedia,
base::BindRepeating(&MediaRouterWebUIMessageHandler::OnPlayCurrentMedia,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kSeekCurrentMedia,
base::BindRepeating(&MediaRouterWebUIMessageHandler::OnSeekCurrentMedia,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kSelectLocalMediaFile,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnSelectLocalMediaFile,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kSetCurrentMediaMute,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnSetCurrentMediaMute,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kSetCurrentMediaVolume,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnSetCurrentMediaVolume,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kSetMediaRemotingEnabled,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnSetMediaRemotingEnabled,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kHangoutsSetLocalPresent,
base::BindRepeating(
&MediaRouterWebUIMessageHandler::OnSetHangoutsLocalPresent,
base::Unretained(this)));
}
void MediaRouterWebUIMessageHandler::OnRequestInitialData(
const base::ListValue* args) {
DVLOG(1) << "OnRequestInitialData";
media_router_ui_->OnUIInitiallyLoaded();
base::DictionaryValue initial_data;
// "No Cast devices found?" Chromecast help center page.
initial_data.SetString("deviceMissingUrl",
base::StringPrintf(kHelpPageUrlPrefix, 3249268));
std::unique_ptr<base::DictionaryValue> sinks_and_identity(
SinksAndIdentityToValue(media_router_ui_->GetEnabledSinks(),
GetAccountInfo()));
initial_data.Set("sinksAndIdentity", std::move(sinks_and_identity));
std::unique_ptr<base::ListValue> routes(RoutesToValue(
media_router_ui_->routes(), media_router_ui_->joinable_route_ids(),
media_router_ui_->routes_and_cast_modes()));
initial_data.Set("routes", std::move(routes));
const std::set<MediaCastMode>& cast_modes = media_router_ui_->GetCastModes();
std::unique_ptr<base::ListValue> cast_modes_list(CastModesToValue(
cast_modes, media_router_ui_->GetPresentationRequestSourceName(),
media_router_ui_->forced_cast_mode()));
initial_data.Set("castModes", std::move(cast_modes_list));
// If the cast mode last chosen for the current origin is tab mirroring,
// that should be the cast mode initially selected in the dialog. Otherwise
// the initial cast mode should be chosen automatically by the dialog.
bool use_tab_mirroring =
base::ContainsKey(cast_modes, MediaCastMode::TAB_MIRROR) &&
media_router_ui_->UserSelectedTabMirroringForCurrentOrigin();
initial_data.SetBoolean("useTabMirroring", use_tab_mirroring);
web_ui()->CallJavascriptFunctionUnsafe(kSetInitialData, initial_data);
media_router_ui_->OnUIInitialized();
}
void MediaRouterWebUIMessageHandler::OnCreateRoute(
const base::ListValue* args) {
DVLOG(1) << "OnCreateRoute";
const base::DictionaryValue* args_dict = nullptr;
std::string sink_id;
int cast_mode_num = -1;
if (!args->GetDictionary(0, &args_dict) ||
!args_dict->GetString("sinkId", &sink_id) ||
!args_dict->GetInteger("selectedCastMode", &cast_mode_num)) {
DVLOG(1) << "Unable to extract args.";
return;
}
if (sink_id.empty()) {
DVLOG(1) << "Media Route UI did not respond with a "
<< "valid sink ID. Aborting.";
return;
}
if (!IsValidCastModeNum(cast_mode_num)) {
// TODO(imcheng): Record error condition with UMA.
DVLOG(1) << "Invalid cast mode: " << cast_mode_num << ". Aborting.";
return;
}
MediaRouterUI* media_router_ui =
static_cast<MediaRouterUI*>(web_ui()->GetController());
if (media_router_ui->HasPendingRouteRequest()) {
DVLOG(1) << "UI already has pending route request. Ignoring.";
IssueInfo issue(
l10n_util::GetStringUTF8(IDS_MEDIA_ROUTER_ISSUE_PENDING_ROUTE),
IssueInfo::Action::DISMISS, IssueInfo::Severity::NOTIFICATION);
media_router_ui_->AddIssue(issue);
return;
}
DVLOG(2) << __func__ << ": sink id: " << sink_id
<< ", cast mode: " << cast_mode_num;
// TODO(haibinlu): Pass additional parameters into the CreateRoute request,
// e.g. low-fps-mirror, user-override. (crbug.com/490364)
if (!media_router_ui->CreateRoute(
sink_id, static_cast<MediaCastMode>(cast_mode_num))) {
DVLOG(1) << "Error initiating route request.";
}
}
void MediaRouterWebUIMessageHandler::OnAcknowledgeFirstRunFlow(
const base::ListValue* args) {
DVLOG(1) << "OnAcknowledgeFirstRunFlow";
Profile::FromWebUI(web_ui())->GetPrefs()->SetBoolean(
::prefs::kMediaRouterFirstRunFlowAcknowledged, true);
bool enabled_cloud_services = false;
// Do not set the relevant cloud services prefs if the user was not shown
// the cloud services prompt.
if (!args->GetBoolean(0, &enabled_cloud_services)) {
DVLOG(1) << "User was not shown the enable cloud services prompt.";
return;
}
PrefService* pref_service = Profile::FromWebUI(web_ui())->GetPrefs();
pref_service->SetBoolean(::prefs::kMediaRouterEnableCloudServices,
enabled_cloud_services);
pref_service->SetBoolean(::prefs::kMediaRouterCloudServicesPrefSet, true);
}
void MediaRouterWebUIMessageHandler::OnActOnIssue(const base::ListValue* args) {
DVLOG(1) << "OnActOnIssue";
const base::DictionaryValue* args_dict = nullptr;
Issue::Id issue_id;
int action_type_num = -1;
if (!args->GetDictionary(0, &args_dict) ||
!args_dict->GetInteger("issueId", &issue_id) ||
!args_dict->GetInteger("actionType", &action_type_num)) {
DVLOG(1) << "Unable to extract args.";
return;
}
if (!IsValidIssueActionTypeNum(action_type_num)) {
DVLOG(1) << "Invalid action type: " << action_type_num;
return;
}
IssueInfo::Action action_type =
static_cast<IssueInfo::Action>(action_type_num);
if (ActOnIssueType(action_type, args_dict))
DVLOG(1) << "ActOnIssueType failed for Issue ID " << issue_id;
media_router_ui_->RemoveIssue(issue_id);
}
void MediaRouterWebUIMessageHandler::OnJoinRoute(const base::ListValue* args) {
DVLOG(1) << "OnJoinRoute";
const base::DictionaryValue* args_dict = nullptr;
std::string route_id;
std::string sink_id;
if (!args->GetDictionary(0, &args_dict) ||
!args_dict->GetString("sinkId", &sink_id) ||
!args_dict->GetString("routeId", &route_id)) {
DVLOG(1) << "Unable to extract args.";
return;
}
if (sink_id.empty()) {
DVLOG(1) << "Media Route UI did not respond with a "
<< "valid sink ID. Aborting.";
return;
}
if (route_id.empty()) {
DVLOG(1) << "Media Route UI did not respond with a "
<< "valid route ID. Aborting.";
return;
}
MediaRouterUI* media_router_ui =
static_cast<MediaRouterUI*>(web_ui()->GetController());
if (media_router_ui->HasPendingRouteRequest()) {
DVLOG(1) << "UI already has pending route request. Ignoring.";
IssueInfo issue(
l10n_util::GetStringUTF8(IDS_MEDIA_ROUTER_ISSUE_PENDING_ROUTE),
IssueInfo::Action::DISMISS, IssueInfo::Severity::NOTIFICATION);
media_router_ui_->AddIssue(issue);
return;
}
if (!media_router_ui_->ConnectRoute(sink_id, route_id)) {
DVLOG(1) << "Error initiating route join request.";
}
}
void MediaRouterWebUIMessageHandler::OnCloseRoute(const base::ListValue* args) {
DVLOG(1) << "OnCloseRoute";
const base::DictionaryValue* args_dict = nullptr;
std::string route_id;
bool is_local = false;
if (!args->GetDictionary(0, &args_dict) ||
!args_dict->GetString("routeId", &route_id) ||
!args_dict->GetBoolean("isLocal", &is_local)) {
DVLOG(1) << "Unable to extract args.";
return;
}
media_router_ui_->TerminateRoute(route_id);
if (is_local) {
MediaRouterMetrics::RecordStopLocalRoute();
} else {
MediaRouterMetrics::RecordStopRemoteRoute();
}
}
void MediaRouterWebUIMessageHandler::OnCloseDialog(
const base::ListValue* args) {
DVLOG(1) << "OnCloseDialog";
if (dialog_closing_)
return;
bool used_esc_to_close_dialog = false;
if (!args->GetBoolean(0, &used_esc_to_close_dialog)) {
DVLOG(1) << "Unable to extract args.";
return;
}
if (used_esc_to_close_dialog) {
base::RecordAction(
base::UserMetricsAction("MediaRouter_Ui_Dialog_ESCToClose"));
}
dialog_closing_ = true;
media_router_ui_->Close();
}
void MediaRouterWebUIMessageHandler::OnReportBlur(const base::ListValue* args) {
DVLOG(1) << "OnReportBlur";
base::RecordAction(base::UserMetricsAction("MediaRouter_Ui_Dialog_Blur"));
}
void MediaRouterWebUIMessageHandler::OnReportClickedSinkIndex(
const base::ListValue* args) {
DVLOG(1) << "OnReportClickedSinkIndex";
int index;
if (!args->GetInteger(0, &index)) {
DVLOG(1) << "Unable to extract args.";
return;
}
MediaRouterMetrics::RecordStartRouteDeviceIndex(index);
}
void MediaRouterWebUIMessageHandler::OnReportFilter(const base::ListValue*) {
DVLOG(1) << "OnReportFilter";
base::RecordAction(base::UserMetricsAction("MediaRouter_Ui_Action_Filter"));
}
void MediaRouterWebUIMessageHandler::OnReportInitialAction(
const base::ListValue* args) {
DVLOG(1) << "OnReportInitialAction";
int action;
if (!args->GetInteger(0, &action)) {
DVLOG(1) << "Unable to extract args.";
return;
}
media_router::MediaRouterMetrics::RecordMediaRouterInitialUserAction(
static_cast<MediaRouterUserAction>(action));
}
void MediaRouterWebUIMessageHandler::OnReportInitialState(
const base::ListValue* args) {
DVLOG(1) << "OnReportInitialState";
std::string initial_view;
if (!args->GetString(0, &initial_view)) {
DVLOG(1) << "Unable to extract args.";
return;
}
bool sink_list_state = initial_view == "sink-list";
DCHECK(sink_list_state || (initial_view == "route-details"));
UMA_HISTOGRAM_BOOLEAN("MediaRouter.Ui.InitialState", sink_list_state);
}
void MediaRouterWebUIMessageHandler::OnReportNavigateToView(
const base::ListValue* args) {
DVLOG(1) << "OnReportNavigateToView";
std::string view;
if (!args->GetString(0, &view)) {
DVLOG(1) << "Unable to extract args.";
return;
}
if (view == "cast-mode-list") {
base::RecordAction(
base::UserMetricsAction("MediaRouter_Ui_Navigate_SinkListToSource"));
} else if (view == "route-details") {
base::RecordAction(base::UserMetricsAction(
"MediaRouter_Ui_Navigate_SinkListToRouteDetails"));
} else if (view == "sink-list") {
base::RecordAction(base::UserMetricsAction(
"MediaRouter_Ui_Navigate_RouteDetailsToSinkList"));
}
}
void MediaRouterWebUIMessageHandler::OnReportRouteCreation(
const base::ListValue* args) {
DVLOG(1) << "OnReportRouteCreation";
bool route_created_successfully;
if (!args->GetBoolean(0, &route_created_successfully)) {
DVLOG(1) << "Unable to extract args.";
return;
}
MediaRouterMetrics::RecordStartLocalSessionSuccessful(
route_created_successfully);
}
void MediaRouterWebUIMessageHandler::OnReportRouteCreationOutcome(
const base::ListValue* args) {
DVLOG(1) << "OnReportRouteCreationOutcome";
int outcome;
if (!args->GetInteger(0, &outcome)) {
DVLOG(1) << "Unable to extract args.";
return;
}
media_router::MediaRouterMetrics::RecordRouteCreationOutcome(
static_cast<MediaRouterRouteCreationOutcome>(outcome));
}
void MediaRouterWebUIMessageHandler::OnReportSelectedCastMode(
const base::ListValue* args) {
DVLOG(1) << "OnReportSelectedCastMode";
int cast_mode_type;
if (!args->GetInteger(0, &cast_mode_type)) {
DVLOG(1) << "Unable to extract args.";
return;
}
DCHECK(IsValidCastModeNum(cast_mode_type));
base::UmaHistogramSparse("MediaRouter.Ui.Navigate.SourceSelection",
cast_mode_type);
media_router_ui_->RecordCastModeSelection(
static_cast<MediaCastMode>(cast_mode_type));
}
void MediaRouterWebUIMessageHandler::OnReportSinkCount(
const base::ListValue* args) {
DVLOG(1) << "OnReportSinkCount";
int sink_count;
if (!args->GetInteger(0, &sink_count)) {
DVLOG(1) << "Unable to extract args.";
return;
}
MediaRouterMetrics::RecordDeviceCount(sink_count);
}
void MediaRouterWebUIMessageHandler::OnReportTimeToClickSink(
const base::ListValue* args) {
DVLOG(1) << "OnReportTimeToClickSink";
double time_to_click;
if (!args->GetDouble(0, &time_to_click)) {
DVLOG(1) << "Unable to extract args.";
return;
}
MediaRouterMetrics::RecordStartLocalSessionLatency(
base::TimeDelta::FromMillisecondsD(time_to_click));
}
void MediaRouterWebUIMessageHandler::OnReportWebUIRouteControllerLoaded(
const base::ListValue* args) {
DVLOG(1) << "OnReportWebUIRouteControllerLoaded";
double load_time;
if (!args->GetDouble(0, &load_time)) {
DVLOG(1) << "Unable to extract args.";
return;
}
UMA_HISTOGRAM_TIMES("MediaRouter.Ui.Dialog.LoadedWebUiRouteController",
base::TimeDelta::FromMillisecondsD(load_time));
}
void MediaRouterWebUIMessageHandler::OnReportTimeToInitialActionClose(
const base::ListValue* args) {
DVLOG(1) << "OnReportTimeToInitialActionClose";
double time_to_close;
if (!args->GetDouble(0, &time_to_close)) {
DVLOG(1) << "Unable to extract args.";
return;
}
MediaRouterMetrics::RecordCloseDialogLatency(
base::TimeDelta::FromMillisecondsD(time_to_close));
}
void MediaRouterWebUIMessageHandler::OnSearchSinksAndCreateRoute(
const base::ListValue* args) {
DVLOG(1) << "OnSearchSinksAndCreateRoute";
const base::DictionaryValue* args_dict = nullptr;
std::string sink_id;
std::string search_criteria;
std::string domain;
int cast_mode_num = -1;
if (!args->GetDictionary(0, &args_dict) ||
!args_dict->GetString("sinkId", &sink_id) ||
!args_dict->GetString("searchCriteria", &search_criteria) ||
!args_dict->GetString("domain", &domain) ||
!args_dict->GetInteger("selectedCastMode", &cast_mode_num)) {
DVLOG(1) << "Unable to extract args";
return;
}
if (search_criteria.empty()) {
DVLOG(1) << "Media Router UI did not provide valid search criteria. "
"Aborting.";
return;
}
if (!IsValidCastModeNum(cast_mode_num)) {
DVLOG(1) << "Invalid cast mode: " << cast_mode_num << ". Aborting.";
return;
}
media_router_ui_->SearchSinksAndCreateRoute(
sink_id, search_criteria, domain,
static_cast<MediaCastMode>(cast_mode_num));
}
void MediaRouterWebUIMessageHandler::OnSelectLocalMediaFile(
const base::ListValue* args) {
media_router_ui_->OpenFileDialog();
}
void MediaRouterWebUIMessageHandler::OnInitialDataReceived(
const base::ListValue* args) {
DVLOG(1) << "OnInitialDataReceived";
media_router_ui_->OnUIInitialDataReceived();
MaybeUpdateFirstRunFlowData();
}
void MediaRouterWebUIMessageHandler::OnMediaControllerAvailable(
const base::ListValue* args) {
const base::DictionaryValue* args_dict = nullptr;
std::string route_id;
if (!args->GetDictionary(0, &args_dict) ||
!args_dict->GetString("routeId", &route_id)) {
DVLOG(1) << "Unable to extract media route ID";
return;
}
media_router_ui_->OnMediaControllerUIAvailable(route_id);
}
void MediaRouterWebUIMessageHandler::OnMediaControllerClosed(
const base::ListValue* args) {
current_media_status_.reset();
media_router_ui_->OnMediaControllerUIClosed();
}
void MediaRouterWebUIMessageHandler::OnPauseCurrentMedia(
const base::ListValue* args) {
MediaRouteController* route_controller =
media_router_ui_->GetMediaRouteController();
if (route_controller)
route_controller->Pause();
}
void MediaRouterWebUIMessageHandler::OnPlayCurrentMedia(
const base::ListValue* args) {
MediaRouteController* route_controller =
media_router_ui_->GetMediaRouteController();
if (route_controller)
route_controller->Play();
}
void MediaRouterWebUIMessageHandler::OnSeekCurrentMedia(
const base::ListValue* args) {
const base::DictionaryValue* args_dict = nullptr;
double time;
if (!args->GetDictionary(0, &args_dict) ||
!args_dict->GetDouble("time", &time)) {
DVLOG(1) << "Unable to extract time";
return;
}
base::TimeDelta time_delta = base::TimeDelta::FromSecondsD(time);
MediaRouteController* route_controller =
media_router_ui_->GetMediaRouteController();
if (route_controller && current_media_status_ &&
time_delta >= base::TimeDelta() &&
time_delta <= current_media_status_->duration) {
route_controller->Seek(time_delta);
}
}
void MediaRouterWebUIMessageHandler::OnSetCurrentMediaMute(
const base::ListValue* args) {
const base::DictionaryValue* args_dict = nullptr;
bool mute;
if (!args->GetDictionary(0, &args_dict) ||
!args_dict->GetBoolean("mute", &mute)) {
DVLOG(1) << "Unable to extract mute";
return;
}
MediaRouteController* route_controller =
media_router_ui_->GetMediaRouteController();
if (route_controller)
route_controller->SetMute(mute);
}
void MediaRouterWebUIMessageHandler::OnSetCurrentMediaVolume(
const base::ListValue* args) {
const base::DictionaryValue* args_dict = nullptr;
double volume;
if (!args->GetDictionary(0, &args_dict) ||
!args_dict->GetDouble("volume", &volume)) {
DVLOG(1) << "Unable to extract volume";
return;
}
MediaRouteController* route_controller =
media_router_ui_->GetMediaRouteController();
if (route_controller && volume >= 0 && volume <= 1)
route_controller->SetVolume(volume);
}
void MediaRouterWebUIMessageHandler::OnSetMediaRemotingEnabled(
const base::ListValue* args) {
bool media_remoting_enabled;
if (!args->GetBoolean(0, &media_remoting_enabled)) {
DVLOG(1) << "Unable to extract media remoting value";
return;
}
MirroringMediaRouteController* mirroring_controller =
MirroringMediaRouteController::From(
media_router_ui_->GetMediaRouteController());
if (!mirroring_controller) {
DVLOG(1) << "Unable to get mirroring controller";
return;
}
mirroring_controller->SetMediaRemotingEnabled(media_remoting_enabled);
}
void MediaRouterWebUIMessageHandler::OnSetHangoutsLocalPresent(
const base::ListValue* args) {
bool local_present;
if (!args->GetBoolean(0, &local_present)) {
DVLOG(1) << "Unable to extract local present";
return;
}
HangoutsMediaRouteController* hangouts_controller =
HangoutsMediaRouteController::From(
media_router_ui_->GetMediaRouteController());
if (!hangouts_controller) {
DVLOG(1) << "Unable to get hangouts controller";
return;
}
hangouts_controller->SetLocalPresent(local_present);
}
bool MediaRouterWebUIMessageHandler::ActOnIssueType(
IssueInfo::Action action_type,
const base::DictionaryValue* args) {
if (action_type == IssueInfo::Action::LEARN_MORE) {
std::string learn_more_url = GetLearnMoreUrl(args);
if (learn_more_url.empty())
return false;
auto open_args = std::make_unique<base::ListValue>();
open_args->AppendString(learn_more_url);
web_ui()->CallJavascriptFunctionUnsafe(kWindowOpen, *open_args);
return true;
} else {
// Do nothing; no other issue action types require any other action.
return true;
}
}
void MediaRouterWebUIMessageHandler::MaybeUpdateFirstRunFlowData() {
base::DictionaryValue first_run_flow_data;
Profile* profile = Profile::FromWebUI(web_ui());
PrefService* pref_service = profile->GetPrefs();
bool first_run_flow_acknowledged =
pref_service->GetBoolean(::prefs::kMediaRouterFirstRunFlowAcknowledged);
bool show_cloud_pref = false;
// Cloud services preference is shown if user is logged in. If the user
// enables sync after acknowledging the first run flow, this is treated as
// the user opting into Google services, including cloud services, if the
// browser is a Chrome branded build.
if (!pref_service->GetBoolean(::prefs::kMediaRouterCloudServicesPrefSet)) {
identity::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
if (identity_manager && identity_manager->HasPrimaryAccount()) {
// If the user had previously acknowledged the first run flow without
// being shown the cloud services option, and is now logged in with sync
// enabled, turn on cloud services.
if (first_run_flow_acknowledged &&
ProfileSyncServiceFactory::GetForProfile(profile)
->IsSyncFeatureActive()) {
pref_service->SetBoolean(::prefs::kMediaRouterEnableCloudServices,
true);
pref_service->SetBoolean(::prefs::kMediaRouterCloudServicesPrefSet,
true);
// Return early since the first run flow won't be surfaced.
return;
}
show_cloud_pref = true;
// "Casting to a Hangout from Chrome" Chromecast help center page.
first_run_flow_data.SetString(
"firstRunFlowCloudPrefLearnMoreUrl",
base::StringPrintf(kHelpPageUrlPrefix, 6320939));
}
}
// Return early if the first run flow won't be surfaced.
if (first_run_flow_acknowledged && !show_cloud_pref)
return;
// General Chromecast learn more page.
first_run_flow_data.SetString("firstRunFlowLearnMoreUrl",
kCastLearnMorePageUrl);
first_run_flow_data.SetBoolean("wasFirstRunFlowAcknowledged",
first_run_flow_acknowledged);
first_run_flow_data.SetBoolean("showFirstRunFlowCloudPref", show_cloud_pref);
web_ui()->CallJavascriptFunctionUnsafe(kSetFirstRunFlowData,
first_run_flow_data);
}
AccountInfo MediaRouterWebUIMessageHandler::GetAccountInfo() {
identity::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(Profile::FromWebUI(web_ui()));
return identity_manager ? identity_manager->GetPrimaryAccountInfo()
: AccountInfo();
}
int MediaRouterWebUIMessageHandler::CurrentCastModeForRouteId(
const MediaRoute::Id& route_id,
const std::unordered_map<MediaRoute::Id, MediaCastMode>& current_cast_modes)
const {
auto current_cast_mode_entry = current_cast_modes.find(route_id);
int current_cast_mode = current_cast_mode_entry != current_cast_modes.end()
? current_cast_mode_entry->second
: -1;
return current_cast_mode;
}
std::unique_ptr<base::ListValue> MediaRouterWebUIMessageHandler::RoutesToValue(
const std::vector<MediaRoute>& routes,
const std::vector<MediaRoute::Id>& joinable_route_ids,
const std::unordered_map<MediaRoute::Id, MediaCastMode>& current_cast_modes)
const {
auto value = std::make_unique<base::ListValue>();
for (const MediaRoute& route : routes) {
bool can_join =
base::ContainsValue(joinable_route_ids, route.media_route_id());
int current_cast_mode =
CurrentCastModeForRouteId(route.media_route_id(), current_cast_modes);
std::unique_ptr<base::DictionaryValue> route_val(
RouteToValue(route, can_join, incognito_, current_cast_mode));
value->Append(std::move(route_val));
}
return value;
}
void MediaRouterWebUIMessageHandler::SetWebUIForTest(content::WebUI* web_ui) {
set_web_ui(web_ui);
}
} // namespace media_router