| // 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 |