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/ui/webui/media_router/media_router_ui.h"
12 #include "extensions/common/constants.h"
14 namespace media_router
{
18 const char kHelpPageUrlPrefix
[] =
19 "https://support.google.com/chromecast/answer/%d";
22 const char kRequestInitialData
[] = "requestInitialData";
23 const char kCreateRoute
[] = "requestRoute";
24 const char kActOnIssue
[] = "actOnIssue";
25 const char kCloseRoute
[] = "closeRoute";
26 const char kCloseDialog
[] = "closeDialog";
29 const char kSetInitialData
[] = "media_router.ui.setInitialData";
30 const char kOnCreateRouteResponseReceived
[] =
31 "media_router.ui.onCreateRouteResponseReceived";
32 const char kSetIssue
[] = "media_router.ui.setIssue";
33 const char kSetSinkList
[] = "media_router.ui.setSinkList";
34 const char kSetRouteList
[] = "media_router.ui.setRouteList";
35 const char kSetCastModeList
[] = "media_router.ui.setCastModeList";
36 const char kWindowOpen
[] = "window.open";
38 scoped_ptr
<base::ListValue
> SinksToValue(
39 const std::vector
<MediaSinkWithCastModes
>& sinks
) {
40 scoped_ptr
<base::ListValue
> value(new base::ListValue
);
42 for (const MediaSinkWithCastModes
& sink_with_cast_modes
: sinks
) {
43 scoped_ptr
<base::DictionaryValue
> sink_val(new base::DictionaryValue
);
45 const MediaSink
& sink
= sink_with_cast_modes
.sink
;
46 sink_val
->SetString("id", sink
.id());
47 sink_val
->SetString("name", sink
.name());
48 sink_val
->SetInteger("iconType", sink
.icon_type());
49 sink_val
->SetBoolean("isLaunching", sink
.is_launching());
51 scoped_ptr
<base::ListValue
> cast_modes_val(new base::ListValue
);
52 for (MediaCastMode cast_mode
: sink_with_cast_modes
.cast_modes
)
53 cast_modes_val
->AppendInteger(cast_mode
);
54 sink_val
->Set("castModes", cast_modes_val
.Pass());
56 value
->Append(sink_val
.release());
62 scoped_ptr
<base::DictionaryValue
> RouteToValue(
63 const MediaRoute
& route
, const std::string
& extension_id
) {
64 scoped_ptr
<base::DictionaryValue
> dictionary(new base::DictionaryValue
);
66 dictionary
->SetString("id", route
.media_route_id());
67 dictionary
->SetString("sinkId", route
.media_sink_id());
68 dictionary
->SetString("description", route
.description());
69 dictionary
->SetBoolean("isLocal", route
.is_local());
71 const std::string
& custom_path
= route
.custom_controller_path();
73 if (!custom_path
.empty()) {
74 std::string full_custom_controller_path
= base::StringPrintf("%s://%s/%s",
75 extensions::kExtensionScheme
, extension_id
.c_str(),
77 DCHECK(GURL(full_custom_controller_path
).is_valid());
78 dictionary
->SetString("customControllerPath",
79 full_custom_controller_path
);
82 return dictionary
.Pass();
85 scoped_ptr
<base::ListValue
> RoutesToValue(
86 const std::vector
<MediaRoute
>& routes
, const std::string
& extension_id
) {
87 scoped_ptr
<base::ListValue
> value(new base::ListValue
);
89 for (const MediaRoute
& route
: routes
) {
90 scoped_ptr
<base::DictionaryValue
> route_val(RouteToValue(route
,
92 value
->Append(route_val
.release());
98 scoped_ptr
<base::ListValue
> CastModesToValue(const CastModeSet
& cast_modes
,
99 const std::string
& source_host
) {
100 scoped_ptr
<base::ListValue
> value(new base::ListValue
);
102 for (const MediaCastMode
& cast_mode
: cast_modes
) {
103 scoped_ptr
<base::DictionaryValue
> cast_mode_val(new base::DictionaryValue
);
104 cast_mode_val
->SetInteger("type", cast_mode
);
105 cast_mode_val
->SetString(
106 "description", MediaCastModeToDescription(cast_mode
, source_host
));
107 cast_mode_val
->SetString("host", source_host
);
108 value
->Append(cast_mode_val
.release());
114 // Returns an Issue dictionary created from |issue| that can be used in WebUI.
115 scoped_ptr
<base::DictionaryValue
> IssueToValue(const Issue
& issue
) {
116 scoped_ptr
<base::DictionaryValue
> dictionary(new base::DictionaryValue
);
117 dictionary
->SetString("id", issue
.id());
118 dictionary
->SetString("title", issue
.title());
119 dictionary
->SetString("message", issue
.message());
120 dictionary
->SetInteger("defaultActionType", issue
.default_action().type());
121 if (!issue
.secondary_actions().empty()) {
122 dictionary
->SetInteger("secondaryActionType",
123 issue
.secondary_actions().begin()->type());
125 if (!issue
.route_id().empty())
126 dictionary
->SetString("routeId", issue
.route_id());
127 dictionary
->SetBoolean("isBlocking", issue
.is_blocking());
129 return dictionary
.Pass();
132 bool IsValidIssueActionTypeNum(int issue_action_type_num
) {
133 return issue_action_type_num
>= 0 &&
134 issue_action_type_num
< IssueAction::TYPE_MAX
;
137 // Composes a "learn more" URL. The URL depends on template arguments in |args|.
138 // Returns an empty string if |args| is invalid.
139 std::string
GetLearnMoreUrl(const base::DictionaryValue
* args
) {
140 // TODO(imcheng): The template arguments for determining the learn more URL
141 // should come from the Issue object in the browser, not from WebUI.
142 int help_page_id
= -1;
143 if (!args
->GetInteger("helpPageId", &help_page_id
) || help_page_id
< 0) {
144 DVLOG(1) << "Invalid help page id.";
145 return std::string();
148 std::string help_url
= base::StringPrintf(kHelpPageUrlPrefix
, help_page_id
);
149 if (!GURL(help_url
).is_valid()) {
150 DVLOG(1) << "Error: URL is invalid and cannot be opened.";
151 return std::string();
158 MediaRouterWebUIMessageHandler::MediaRouterWebUIMessageHandler(
159 MediaRouterUI
* media_router_ui
)
160 : dialog_closing_(false),
161 media_router_ui_(media_router_ui
) {
162 DCHECK(media_router_ui_
);
165 MediaRouterWebUIMessageHandler::~MediaRouterWebUIMessageHandler() {
168 void MediaRouterWebUIMessageHandler::UpdateSinks(
169 const std::vector
<MediaSinkWithCastModes
>& sinks
) {
170 DVLOG(2) << "UpdateSinks";
171 scoped_ptr
<base::ListValue
> sinks_val(SinksToValue(sinks
));
172 web_ui()->CallJavascriptFunction(kSetSinkList
, *sinks_val
);
175 void MediaRouterWebUIMessageHandler::UpdateRoutes(
176 const std::vector
<MediaRoute
>& routes
) {
177 DVLOG(2) << "UpdateRoutes";
178 scoped_ptr
<base::ListValue
> routes_val(RoutesToValue(routes
,
179 media_router_ui_
->GetRouteProviderExtensionId()));
180 web_ui()->CallJavascriptFunction(kSetRouteList
, *routes_val
);
183 void MediaRouterWebUIMessageHandler::UpdateCastModes(
184 const CastModeSet
& cast_modes
,
185 const std::string
& source_host
) {
186 DVLOG(2) << "UpdateCastModes";
187 scoped_ptr
<base::ListValue
> cast_modes_val(
188 CastModesToValue(cast_modes
, source_host
));
189 web_ui()->CallJavascriptFunction(kSetCastModeList
, *cast_modes_val
);
192 void MediaRouterWebUIMessageHandler::OnCreateRouteResponseReceived(
193 const MediaSink::Id
& sink_id
,
194 const MediaRoute
* route
) {
195 DVLOG(2) << "OnCreateRouteResponseReceived";
197 scoped_ptr
<base::DictionaryValue
> route_value(RouteToValue(*route
,
198 media_router_ui_
->GetRouteProviderExtensionId()));
199 web_ui()->CallJavascriptFunction(kOnCreateRouteResponseReceived
,
200 base::StringValue(sink_id
), *route_value
);
202 web_ui()->CallJavascriptFunction(kOnCreateRouteResponseReceived
,
203 base::StringValue(sink_id
),
204 *base::Value::CreateNullValue());
208 void MediaRouterWebUIMessageHandler::UpdateIssue(const Issue
* issue
) {
209 DVLOG(2) << "UpdateIssue";
211 scoped_ptr
<base::DictionaryValue
> issue_val(IssueToValue(*issue
));
212 web_ui()->CallJavascriptFunction(kSetIssue
, *issue_val
);
214 // Clears the issue in the WebUI.
215 web_ui()->CallJavascriptFunction(kSetIssue
);
219 void MediaRouterWebUIMessageHandler::RegisterMessages() {
220 web_ui()->RegisterMessageCallback(
222 base::Bind(&MediaRouterWebUIMessageHandler::OnRequestInitialData
,
223 base::Unretained(this)));
224 web_ui()->RegisterMessageCallback(
226 base::Bind(&MediaRouterWebUIMessageHandler::OnCreateRoute
,
227 base::Unretained(this)));
228 web_ui()->RegisterMessageCallback(
230 base::Bind(&MediaRouterWebUIMessageHandler::OnActOnIssue
,
231 base::Unretained(this)));
232 web_ui()->RegisterMessageCallback(
234 base::Bind(&MediaRouterWebUIMessageHandler::OnCloseRoute
,
235 base::Unretained(this)));
236 web_ui()->RegisterMessageCallback(
238 base::Bind(&MediaRouterWebUIMessageHandler::OnCloseDialog
,
239 base::Unretained(this)));
242 void MediaRouterWebUIMessageHandler::OnRequestInitialData(
243 const base::ListValue
* args
) {
244 DVLOG(1) << "OnRequestInitialData";
245 base::DictionaryValue initial_data
;
247 initial_data
.SetString("headerText",
248 media_router_ui_
->GetInitialHeaderText());
249 initial_data
.SetString("headerTextTooltip",
250 media_router_ui_
->GetInitialHeaderTextTooltip());
252 // "No Cast devices found?" Chromecast help center page.
253 initial_data
.SetString("deviceMissingUrl",
254 base::StringPrintf(kHelpPageUrlPrefix
, 3249268));
256 scoped_ptr
<base::ListValue
> sinks(SinksToValue(media_router_ui_
->sinks()));
257 initial_data
.Set("sinks", sinks
.release());
259 scoped_ptr
<base::ListValue
> routes(RoutesToValue(media_router_ui_
->routes(),
260 media_router_ui_
->GetRouteProviderExtensionId()));
261 initial_data
.Set("routes", routes
.release());
263 scoped_ptr
<base::ListValue
> cast_modes(CastModesToValue(
264 media_router_ui_
->cast_modes(),
265 media_router_ui_
->GetFrameURLHost()));
266 initial_data
.Set("castModes", cast_modes
.release());
268 web_ui()->CallJavascriptFunction(kSetInitialData
, initial_data
);
269 media_router_ui_
->UIInitialized();
272 void MediaRouterWebUIMessageHandler::OnCreateRoute(
273 const base::ListValue
* args
) {
274 DVLOG(1) << "OnCreateRoute";
275 const base::DictionaryValue
* args_dict
= nullptr;
277 int cast_mode_num
= -1;
278 if (!args
->GetDictionary(0, &args_dict
) ||
279 !args_dict
->GetString("sinkId", &sink_id
) ||
280 !args_dict
->GetInteger("selectedCastMode", &cast_mode_num
)) {
281 DVLOG(1) << "Unable to extract args.";
285 if (sink_id
.empty()) {
286 DVLOG(1) << "Media Route Provider Manager did not respond with a "
287 << "valid sink ID. Aborting.";
291 MediaRouterUI
* media_router_ui
=
292 static_cast<MediaRouterUI
*>(web_ui()->GetController());
293 if (media_router_ui
->has_pending_route_request()) {
294 DVLOG(1) << "UI already has pending route request. Ignoring.";
298 DVLOG(2) << "sink id: " << sink_id
<< ", cast mode: " << cast_mode_num
;
300 // TODO(haibinlu): Pass additional parameters into the CreateRoute request,
301 // e.g. low-fps-mirror, user-override. (crbug.com/490364)
302 bool success
= false;
303 if (IsValidCastModeNum(cast_mode_num
)) {
304 // User explicitly selected cast mode.
305 DVLOG(2) << "Cast mode override: " << cast_mode_num
;
306 success
= media_router_ui
->CreateRouteWithCastModeOverride(
307 sink_id
, static_cast<MediaCastMode
>(cast_mode_num
));
309 success
= media_router_ui
->CreateRoute(sink_id
);
312 // TODO(imcheng): Display error in UI. (crbug.com/490372)
314 DVLOG(1) << "Error initiating route request.";
317 void MediaRouterWebUIMessageHandler::OnActOnIssue(
318 const base::ListValue
* args
) {
319 DVLOG(1) << "OnActOnIssue";
320 const base::DictionaryValue
* args_dict
= nullptr;
322 int action_type_num
= -1;
323 if (!args
->GetDictionary(0, &args_dict
) ||
324 !args_dict
->GetString("issueId", &issue_id
) ||
325 !args_dict
->GetInteger("actionType", &action_type_num
)) {
326 DVLOG(1) << "Unable to extract args.";
329 if (!IsValidIssueActionTypeNum(action_type_num
)) {
330 DVLOG(1) << "Invalid action type: " << action_type_num
;
333 IssueAction::Type action_type
=
334 static_cast<IssueAction::Type
>(action_type_num
);
335 if (ActOnIssueType(action_type
, args_dict
))
336 DVLOG(1) << "ActOnIssueType failed for Issue ID " << issue_id
;
337 media_router_ui_
->ClearIssue(issue_id
);
340 void MediaRouterWebUIMessageHandler::OnCloseRoute(
341 const base::ListValue
* args
) {
342 DVLOG(1) << "OnCloseRoute";
343 const base::DictionaryValue
* args_dict
= nullptr;
344 std::string route_id
;
345 if (!args
->GetDictionary(0, &args_dict
) ||
346 !args_dict
->GetString("routeId", &route_id
)) {
347 DVLOG(1) << "Unable to extract args.";
350 media_router_ui_
->CloseRoute(route_id
);
353 void MediaRouterWebUIMessageHandler::OnCloseDialog(
354 const base::ListValue
* args
) {
355 DVLOG(1) << "OnCloseDialog";
359 dialog_closing_
= true;
360 media_router_ui_
->Close();
363 bool MediaRouterWebUIMessageHandler::ActOnIssueType(
364 const IssueAction::Type
& action_type
,
365 const base::DictionaryValue
* args
) {
366 if (action_type
== IssueAction::TYPE_LEARN_MORE
) {
367 std::string learn_more_url
= GetLearnMoreUrl(args
);
368 if (learn_more_url
.empty())
370 scoped_ptr
<base::ListValue
> open_args(new base::ListValue
);
371 open_args
->AppendString(learn_more_url
);
372 web_ui()->CallJavascriptFunction(kWindowOpen
, *open_args
);
375 // Do nothing; no other issue action types require any other action.
380 } // namespace media_router