blob: f4ac35ecee5bad3a4bc300535d1001db306df141 [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 <string>
#include <utility>
#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.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/issue.h"
#include "chrome/browser/media/router/media_router_metrics.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/ui/webui/media_router/media_cast_mode.h"
#include "chrome/browser/ui/webui/media_router/media_router_ui.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/signin_manager.h"
#include "content/public/browser/web_ui.h"
#include "extensions/common/constants.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 kSearchSinksAndCreateRoute[] = "searchSinksAndCreateRoute";
const char kOnInitialDataReceived[] = "onInitialDataReceived";
// JS function names.
const char kSetInitialData[] = "media_router.ui.setInitialData";
const char kOnCreateRouteResponseReceived[] =
"media_router.ui.onCreateRouteResponseReceived";
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 kWindowOpen[] = "window.open";
std::unique_ptr<base::DictionaryValue> SinksAndIdentityToValue(
const std::vector<MediaSinkWithCastModes>& sinks,
const AccountInfo& account_info) {
std::unique_ptr<base::DictionaryValue> sink_list_and_identity(
new 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);
}
std::unique_ptr<base::ListValue> sinks_val(new base::ListValue);
for (const MediaSinkWithCastModes& sink_with_cast_modes : sinks) {
std::unique_ptr<base::DictionaryValue> sink_val(new 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", 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 == Profile::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", sinks_val.release());
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,
const std::string& extension_id,
bool incognito,
int current_cast_mode) {
std::unique_ptr<base::DictionaryValue> dictionary(new 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("canJoin", can_join);
if (current_cast_mode > 0) {
dictionary->SetInteger("currentCastMode", current_cast_mode);
}
const std::string& custom_path = route.custom_controller_path();
if (!incognito && !custom_path.empty()) {
std::string full_custom_controller_path = base::StringPrintf("%s://%s/%s",
extensions::kExtensionScheme, extension_id.c_str(),
custom_path.c_str());
DCHECK(GURL(full_custom_controller_path).is_valid());
dictionary->SetString("customControllerPath",
full_custom_controller_path);
}
return dictionary;
}
std::unique_ptr<base::ListValue> CastModesToValue(
const CastModeSet& cast_modes,
const std::string& source_host) {
std::unique_ptr<base::ListValue> value(new base::ListValue);
for (const MediaCastMode& cast_mode : cast_modes) {
std::unique_ptr<base::DictionaryValue> cast_mode_val(
new 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);
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();
std::unique_ptr<base::DictionaryValue> dictionary(new 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) {
DVLOG(2) << "UpdateCastModes";
std::unique_ptr<base::ListValue> cast_modes_val(
CastModesToValue(cast_modes, source_host));
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, media_router_ui_->GetRouteProviderExtensionId(),
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::RegisterMessages() {
web_ui()->RegisterMessageCallback(
kRequestInitialData,
base::Bind(&MediaRouterWebUIMessageHandler::OnRequestInitialData,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kCreateRoute,
base::Bind(&MediaRouterWebUIMessageHandler::OnCreateRoute,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kAcknowledgeFirstRunFlow,
base::Bind(&MediaRouterWebUIMessageHandler::OnAcknowledgeFirstRunFlow,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kActOnIssue,
base::Bind(&MediaRouterWebUIMessageHandler::OnActOnIssue,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kCloseRoute,
base::Bind(&MediaRouterWebUIMessageHandler::OnCloseRoute,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kJoinRoute,
base::Bind(&MediaRouterWebUIMessageHandler::OnJoinRoute,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kCloseDialog,
base::Bind(&MediaRouterWebUIMessageHandler::OnCloseDialog,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportBlur,
base::Bind(&MediaRouterWebUIMessageHandler::OnReportBlur,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportClickedSinkIndex,
base::Bind(&MediaRouterWebUIMessageHandler::OnReportClickedSinkIndex,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportFilter,
base::Bind(&MediaRouterWebUIMessageHandler::OnReportFilter,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportInitialState,
base::Bind(&MediaRouterWebUIMessageHandler::OnReportInitialState,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportInitialAction,
base::Bind(&MediaRouterWebUIMessageHandler::OnReportInitialAction,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportRouteCreation,
base::Bind(&MediaRouterWebUIMessageHandler::OnReportRouteCreation,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportRouteCreationOutcome,
base::Bind(&MediaRouterWebUIMessageHandler::OnReportRouteCreationOutcome,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportSelectedCastMode,
base::Bind(&MediaRouterWebUIMessageHandler::OnReportSelectedCastMode,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportNavigateToView,
base::Bind(&MediaRouterWebUIMessageHandler::OnReportNavigateToView,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportSinkCount,
base::Bind(&MediaRouterWebUIMessageHandler::OnReportSinkCount,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportTimeToClickSink,
base::Bind(&MediaRouterWebUIMessageHandler::OnReportTimeToClickSink,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kReportTimeToInitialActionClose,
base::Bind(
&MediaRouterWebUIMessageHandler::OnReportTimeToInitialActionClose,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kSearchSinksAndCreateRoute,
base::Bind(&MediaRouterWebUIMessageHandler::OnSearchSinksAndCreateRoute,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kOnInitialDataReceived,
base::Bind(&MediaRouterWebUIMessageHandler::OnInitialDataReceived,
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_->sinks(), GetAccountInfo()));
initial_data.Set("sinksAndIdentity", sinks_and_identity.release());
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", routes.release());
const std::set<MediaCastMode> cast_modes = media_router_ui_->cast_modes();
std::unique_ptr<base::ListValue> cast_modes_list(CastModesToValue(
cast_modes, media_router_ui_->GetPresentationRequestSourceName()));
initial_data.Set("castModes", cast_modes_list.release());
// 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_->UIInitialized();
}
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_->ClearIssue(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_->CloseRoute(route_id);
UMA_HISTOGRAM_BOOLEAN("MediaRouter.Ui.Action.StopRoute", !is_local);
}
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;
}
UMA_HISTOGRAM_SPARSE_SLOWLY("MediaRouter.Ui.Action.StartLocalPosition",
std::min(index, 100));
}
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;
}
UMA_HISTOGRAM_BOOLEAN("MediaRouter.Ui.Action.StartLocalSessionSuccessful",
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));
UMA_HISTOGRAM_SPARSE_SLOWLY("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;
}
UMA_HISTOGRAM_COUNTS_100("MediaRouter.Ui.Device.Count", 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;
}
UMA_HISTOGRAM_TIMES("MediaRouter.Ui.Action.StartLocal.Latency",
base::TimeDelta::FromMillisecondsD(time_to_click));
}
void MediaRouterWebUIMessageHandler::OnReportTimeToInitialActionClose(
const base::ListValue* args) {
DVLOG(1) << "OnReportTimeToInitialActionClose";
double time_to_close;
if (!args->GetDouble(0, &time_to_close)) {
VLOG(0) << "Unable to extract args.";
return;
}
UMA_HISTOGRAM_TIMES("MediaRouter.Ui.Action.CloseLatency",
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::OnInitialDataReceived(
const base::ListValue* args) {
DVLOG(1) << "OnInitialDataReceived";
media_router_ui_->OnUIInitialDataReceived();
MaybeUpdateFirstRunFlowData();
}
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;
std::unique_ptr<base::ListValue> open_args(new 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)) {
SigninManagerBase* signin_manager =
SigninManagerFactory::GetForProfile(profile);
if (signin_manager && signin_manager->IsAuthenticated()) {
// 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)->IsSyncActive()) {
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() {
SigninManagerBase* signin_manager =
SigninManagerFactory::GetForProfile(Profile::FromWebUI(web_ui()));
return signin_manager ? signin_manager->GetAuthenticatedAccountInfo()
: 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 {
std::unique_ptr<base::ListValue> value(new base::ListValue);
const std::string& extension_id =
media_router_ui_->GetRouteProviderExtensionId();
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, extension_id, 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