1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/webui/media_router/media_router_webui_message_handler.h"
10 #include "base/strings/stringprintf.h"
11 #include "chrome/browser/media/router/issue.h"
12 #include "chrome/browser/ui/webui/media_router/media_router_ui.h"
13 #include "chrome/grit/generated_resources.h"
14 #include "extensions/common/constants.h"
15 #include "ui/base/l10n/l10n_util.h"
17 namespace media_router
{
21 const char kHelpPageUrlPrefix
[] =
22 "https://support.google.com/chromecast/answer/%d";
25 const char kRequestInitialData
[] = "requestInitialData";
26 const char kCreateRoute
[] = "requestRoute";
27 const char kActOnIssue
[] = "actOnIssue";
28 const char kCloseRoute
[] = "closeRoute";
29 const char kCloseDialog
[] = "closeDialog";
32 const char kSetInitialData
[] = "media_router.ui.setInitialData";
33 const char kOnCreateRouteResponseReceived
[] =
34 "media_router.ui.onCreateRouteResponseReceived";
35 const char kSetIssue
[] = "media_router.ui.setIssue";
36 const char kSetSinkList
[] = "media_router.ui.setSinkList";
37 const char kSetRouteList
[] = "media_router.ui.setRouteList";
38 const char kSetCastModeList
[] = "media_router.ui.setCastModeList";
39 const char kWindowOpen
[] = "window.open";
41 scoped_ptr
<base::ListValue
> SinksToValue(
42 const std::vector
<MediaSinkWithCastModes
>& sinks
) {
43 scoped_ptr
<base::ListValue
> value(new base::ListValue
);
45 for (const MediaSinkWithCastModes
& sink_with_cast_modes
: sinks
) {
46 scoped_ptr
<base::DictionaryValue
> sink_val(new base::DictionaryValue
);
48 const MediaSink
& sink
= sink_with_cast_modes
.sink
;
49 sink_val
->SetString("id", sink
.id());
50 sink_val
->SetString("name", sink
.name());
51 sink_val
->SetInteger("iconType", sink
.icon_type());
52 sink_val
->SetBoolean("isLaunching", sink
.is_launching());
54 scoped_ptr
<base::ListValue
> cast_modes_val(new base::ListValue
);
55 for (MediaCastMode cast_mode
: sink_with_cast_modes
.cast_modes
)
56 cast_modes_val
->AppendInteger(cast_mode
);
57 sink_val
->Set("castModes", cast_modes_val
.Pass());
59 value
->Append(sink_val
.release());
65 scoped_ptr
<base::DictionaryValue
> RouteToValue(
66 const MediaRoute
& route
, const std::string
& extension_id
) {
67 scoped_ptr
<base::DictionaryValue
> dictionary(new base::DictionaryValue
);
69 dictionary
->SetString("id", route
.media_route_id());
70 dictionary
->SetString("sinkId", route
.media_sink_id());
71 dictionary
->SetString("description", route
.description());
72 dictionary
->SetBoolean("isLocal", route
.is_local());
74 const std::string
& custom_path
= route
.custom_controller_path();
76 if (!custom_path
.empty()) {
77 std::string full_custom_controller_path
= base::StringPrintf("%s://%s/%s",
78 extensions::kExtensionScheme
, extension_id
.c_str(),
80 DCHECK(GURL(full_custom_controller_path
).is_valid());
81 dictionary
->SetString("customControllerPath",
82 full_custom_controller_path
);
85 return dictionary
.Pass();
88 scoped_ptr
<base::ListValue
> RoutesToValue(
89 const std::vector
<MediaRoute
>& routes
, const std::string
& extension_id
) {
90 scoped_ptr
<base::ListValue
> value(new base::ListValue
);
92 for (const MediaRoute
& route
: routes
) {
93 scoped_ptr
<base::DictionaryValue
> route_val(RouteToValue(route
,
95 value
->Append(route_val
.release());
101 scoped_ptr
<base::ListValue
> CastModesToValue(const CastModeSet
& cast_modes
,
102 const std::string
& source_host
) {
103 scoped_ptr
<base::ListValue
> value(new base::ListValue
);
105 for (const MediaCastMode
& cast_mode
: cast_modes
) {
106 scoped_ptr
<base::DictionaryValue
> cast_mode_val(new base::DictionaryValue
);
107 cast_mode_val
->SetInteger("type", cast_mode
);
108 cast_mode_val
->SetString(
109 "description", MediaCastModeToDescription(cast_mode
, source_host
));
110 cast_mode_val
->SetString("host", source_host
);
111 value
->Append(cast_mode_val
.release());
117 // Returns an Issue dictionary created from |issue| that can be used in WebUI.
118 scoped_ptr
<base::DictionaryValue
> IssueToValue(const Issue
& issue
) {
119 scoped_ptr
<base::DictionaryValue
> dictionary(new base::DictionaryValue
);
120 dictionary
->SetString("id", issue
.id());
121 dictionary
->SetString("title", issue
.title());
122 dictionary
->SetString("message", issue
.message());
123 dictionary
->SetInteger("defaultActionType", issue
.default_action().type());
124 if (!issue
.secondary_actions().empty()) {
125 dictionary
->SetInteger("secondaryActionType",
126 issue
.secondary_actions().begin()->type());
128 if (!issue
.route_id().empty())
129 dictionary
->SetString("routeId", issue
.route_id());
130 dictionary
->SetBoolean("isBlocking", issue
.is_blocking());
132 return dictionary
.Pass();
135 bool IsValidIssueActionTypeNum(int issue_action_type_num
) {
136 return issue_action_type_num
>= 0 &&
137 issue_action_type_num
< IssueAction::TYPE_MAX
;
140 // Composes a "learn more" URL. The URL depends on template arguments in |args|.
141 // Returns an empty string if |args| is invalid.
142 std::string
GetLearnMoreUrl(const base::DictionaryValue
* args
) {
143 // TODO(imcheng): The template arguments for determining the learn more URL
144 // should come from the Issue object in the browser, not from WebUI.
145 int help_page_id
= -1;
146 if (!args
->GetInteger("helpPageId", &help_page_id
) || help_page_id
< 0) {
147 DVLOG(1) << "Invalid help page id.";
148 return std::string();
151 std::string help_url
= base::StringPrintf(kHelpPageUrlPrefix
, help_page_id
);
152 if (!GURL(help_url
).is_valid()) {
153 DVLOG(1) << "Error: URL is invalid and cannot be opened.";
154 return std::string();
161 MediaRouterWebUIMessageHandler::MediaRouterWebUIMessageHandler(
162 MediaRouterUI
* media_router_ui
)
163 : dialog_closing_(false),
164 media_router_ui_(media_router_ui
) {
165 DCHECK(media_router_ui_
);
168 MediaRouterWebUIMessageHandler::~MediaRouterWebUIMessageHandler() {
171 void MediaRouterWebUIMessageHandler::UpdateSinks(
172 const std::vector
<MediaSinkWithCastModes
>& sinks
) {
173 DVLOG(2) << "UpdateSinks";
174 scoped_ptr
<base::ListValue
> sinks_val(SinksToValue(sinks
));
175 web_ui()->CallJavascriptFunction(kSetSinkList
, *sinks_val
);
178 void MediaRouterWebUIMessageHandler::UpdateRoutes(
179 const std::vector
<MediaRoute
>& routes
) {
180 DVLOG(2) << "UpdateRoutes";
181 scoped_ptr
<base::ListValue
> routes_val(RoutesToValue(routes
,
182 media_router_ui_
->GetRouteProviderExtensionId()));
183 web_ui()->CallJavascriptFunction(kSetRouteList
, *routes_val
);
186 void MediaRouterWebUIMessageHandler::UpdateCastModes(
187 const CastModeSet
& cast_modes
,
188 const std::string
& source_host
) {
189 DVLOG(2) << "UpdateCastModes";
190 scoped_ptr
<base::ListValue
> cast_modes_val(
191 CastModesToValue(cast_modes
, source_host
));
192 web_ui()->CallJavascriptFunction(kSetCastModeList
, *cast_modes_val
);
195 void MediaRouterWebUIMessageHandler::OnCreateRouteResponseReceived(
196 const MediaSink::Id
& sink_id
,
197 const MediaRoute
* route
) {
198 DVLOG(2) << "OnCreateRouteResponseReceived";
200 scoped_ptr
<base::DictionaryValue
> route_value(RouteToValue(*route
,
201 media_router_ui_
->GetRouteProviderExtensionId()));
202 web_ui()->CallJavascriptFunction(kOnCreateRouteResponseReceived
,
203 base::StringValue(sink_id
), *route_value
);
205 web_ui()->CallJavascriptFunction(kOnCreateRouteResponseReceived
,
206 base::StringValue(sink_id
),
207 *base::Value::CreateNullValue());
211 void MediaRouterWebUIMessageHandler::UpdateIssue(const Issue
* issue
) {
212 DVLOG(2) << "UpdateIssue";
214 scoped_ptr
<base::DictionaryValue
> issue_val(IssueToValue(*issue
));
215 web_ui()->CallJavascriptFunction(kSetIssue
, *issue_val
);
217 // Clears the issue in the WebUI.
218 web_ui()->CallJavascriptFunction(kSetIssue
);
222 void MediaRouterWebUIMessageHandler::RegisterMessages() {
223 web_ui()->RegisterMessageCallback(
225 base::Bind(&MediaRouterWebUIMessageHandler::OnRequestInitialData
,
226 base::Unretained(this)));
227 web_ui()->RegisterMessageCallback(
229 base::Bind(&MediaRouterWebUIMessageHandler::OnCreateRoute
,
230 base::Unretained(this)));
231 web_ui()->RegisterMessageCallback(
233 base::Bind(&MediaRouterWebUIMessageHandler::OnActOnIssue
,
234 base::Unretained(this)));
235 web_ui()->RegisterMessageCallback(
237 base::Bind(&MediaRouterWebUIMessageHandler::OnCloseRoute
,
238 base::Unretained(this)));
239 web_ui()->RegisterMessageCallback(
241 base::Bind(&MediaRouterWebUIMessageHandler::OnCloseDialog
,
242 base::Unretained(this)));
245 void MediaRouterWebUIMessageHandler::OnRequestInitialData(
246 const base::ListValue
* args
) {
247 DVLOG(1) << "OnRequestInitialData";
248 base::DictionaryValue initial_data
;
250 initial_data
.SetString("headerText",
251 media_router_ui_
->GetInitialHeaderText());
252 initial_data
.SetString("headerTextTooltip",
253 media_router_ui_
->GetInitialHeaderTextTooltip());
255 // "No Cast devices found?" Chromecast help center page.
256 initial_data
.SetString("deviceMissingUrl",
257 base::StringPrintf(kHelpPageUrlPrefix
, 3249268));
259 scoped_ptr
<base::ListValue
> sinks(SinksToValue(media_router_ui_
->sinks()));
260 initial_data
.Set("sinks", sinks
.release());
262 scoped_ptr
<base::ListValue
> routes(RoutesToValue(media_router_ui_
->routes(),
263 media_router_ui_
->GetRouteProviderExtensionId()));
264 initial_data
.Set("routes", routes
.release());
266 scoped_ptr
<base::ListValue
> cast_modes(CastModesToValue(
267 media_router_ui_
->cast_modes(),
268 media_router_ui_
->GetFrameURLHost()));
269 initial_data
.Set("castModes", cast_modes
.release());
271 web_ui()->CallJavascriptFunction(kSetInitialData
, initial_data
);
272 media_router_ui_
->UIInitialized();
275 void MediaRouterWebUIMessageHandler::OnCreateRoute(
276 const base::ListValue
* args
) {
277 DVLOG(1) << "OnCreateRoute";
278 const base::DictionaryValue
* args_dict
= nullptr;
280 int cast_mode_num
= -1;
281 if (!args
->GetDictionary(0, &args_dict
) ||
282 !args_dict
->GetString("sinkId", &sink_id
) ||
283 !args_dict
->GetInteger("selectedCastMode", &cast_mode_num
)) {
284 DVLOG(1) << "Unable to extract args.";
288 if (sink_id
.empty()) {
289 DVLOG(1) << "Media Route UI did not respond with a "
290 << "valid sink ID. Aborting.";
294 MediaRouterUI
* media_router_ui
=
295 static_cast<MediaRouterUI
*>(web_ui()->GetController());
296 if (media_router_ui
->has_pending_route_request()) {
297 DVLOG(1) << "UI already has pending route request. Ignoring.";
299 l10n_util::GetStringUTF8(IDS_MEDIA_ROUTER_ISSUE_PENDING_ROUTE
),
300 std::string(), IssueAction(IssueAction::TYPE_DISMISS
),
301 std::vector
<IssueAction
>(), std::string(), Issue::NOTIFICATION
,
302 false, std::string());
303 media_router_ui_
->AddIssue(issue
);
307 DVLOG(2) << "sink id: " << sink_id
<< ", cast mode: " << cast_mode_num
;
309 // TODO(haibinlu): Pass additional parameters into the CreateRoute request,
310 // e.g. low-fps-mirror, user-override. (crbug.com/490364)
311 bool success
= false;
312 if (IsValidCastModeNum(cast_mode_num
)) {
313 // User explicitly selected cast mode.
314 DVLOG(2) << "Cast mode override: " << cast_mode_num
;
315 success
= media_router_ui
->CreateRouteWithCastModeOverride(
316 sink_id
, static_cast<MediaCastMode
>(cast_mode_num
));
318 success
= media_router_ui
->CreateRoute(sink_id
);
322 // The provider will handle sending an issue for a failed route request.
323 DVLOG(1) << "Error initiating route request.";
327 void MediaRouterWebUIMessageHandler::OnActOnIssue(
328 const base::ListValue
* args
) {
329 DVLOG(1) << "OnActOnIssue";
330 const base::DictionaryValue
* args_dict
= nullptr;
332 int action_type_num
= -1;
333 if (!args
->GetDictionary(0, &args_dict
) ||
334 !args_dict
->GetString("issueId", &issue_id
) ||
335 !args_dict
->GetInteger("actionType", &action_type_num
)) {
336 DVLOG(1) << "Unable to extract args.";
339 if (!IsValidIssueActionTypeNum(action_type_num
)) {
340 DVLOG(1) << "Invalid action type: " << action_type_num
;
343 IssueAction::Type action_type
=
344 static_cast<IssueAction::Type
>(action_type_num
);
345 if (ActOnIssueType(action_type
, args_dict
))
346 DVLOG(1) << "ActOnIssueType failed for Issue ID " << issue_id
;
347 media_router_ui_
->ClearIssue(issue_id
);
350 void MediaRouterWebUIMessageHandler::OnCloseRoute(
351 const base::ListValue
* args
) {
352 DVLOG(1) << "OnCloseRoute";
353 const base::DictionaryValue
* args_dict
= nullptr;
354 std::string route_id
;
355 if (!args
->GetDictionary(0, &args_dict
) ||
356 !args_dict
->GetString("routeId", &route_id
)) {
357 DVLOG(1) << "Unable to extract args.";
360 media_router_ui_
->CloseRoute(route_id
);
363 void MediaRouterWebUIMessageHandler::OnCloseDialog(
364 const base::ListValue
* args
) {
365 DVLOG(1) << "OnCloseDialog";
369 dialog_closing_
= true;
370 media_router_ui_
->Close();
373 bool MediaRouterWebUIMessageHandler::ActOnIssueType(
374 const IssueAction::Type
& action_type
,
375 const base::DictionaryValue
* args
) {
376 if (action_type
== IssueAction::TYPE_LEARN_MORE
) {
377 std::string learn_more_url
= GetLearnMoreUrl(args
);
378 if (learn_more_url
.empty())
380 scoped_ptr
<base::ListValue
> open_args(new base::ListValue
);
381 open_args
->AppendString(learn_more_url
);
382 web_ui()->CallJavascriptFunction(kWindowOpen
, *open_args
);
385 // Do nothing; no other issue action types require any other action.
390 } // namespace media_router