1 // Copyright 2013 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/local_discovery/local_discovery_ui_handler.h"
10 #include "base/command_line.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/prefs/pref_service.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/values.h"
16 #include "chrome/browser/chrome_notification_types.h"
17 #include "chrome/browser/local_discovery/privet_confirm_api_flow.h"
18 #include "chrome/browser/local_discovery/privet_constants.h"
19 #include "chrome/browser/local_discovery/privet_device_lister_impl.h"
20 #include "chrome/browser/local_discovery/privet_http_asynchronous_factory.h"
21 #include "chrome/browser/local_discovery/privet_http_impl.h"
22 #include "chrome/browser/local_discovery/service_discovery_shared_client.h"
23 #include "chrome/browser/printing/cloud_print/cloud_print_proxy_service.h"
24 #include "chrome/browser/printing/cloud_print/cloud_print_proxy_service_factory.h"
25 #include "chrome/browser/printing/cloud_print/cloud_print_url.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/signin/profile_oauth2_token_service.h"
28 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
29 #include "chrome/browser/signin/signin_manager.h"
30 #include "chrome/browser/signin/signin_manager_base.h"
31 #include "chrome/browser/signin/signin_manager_factory.h"
32 #include "chrome/browser/signin/signin_promo.h"
33 #include "chrome/browser/ui/browser_finder.h"
34 #include "chrome/browser/ui/browser_tabstrip.h"
35 #include "chrome/common/chrome_switches.h"
36 #include "chrome/common/pref_names.h"
37 #include "content/public/browser/notification_source.h"
38 #include "content/public/browser/user_metrics.h"
39 #include "content/public/browser/user_metrics.h"
40 #include "content/public/browser/web_ui.h"
41 #include "content/public/common/page_transition_types.h"
42 #include "grit/generated_resources.h"
43 #include "net/base/host_port_pair.h"
44 #include "net/base/net_util.h"
45 #include "net/http/http_status_code.h"
46 #include "ui/base/l10n/l10n_util.h"
48 #if defined(ENABLE_FULL_PRINTING) && !defined(OS_CHROMEOS) && \
50 #define CLOUD_PRINT_CONNECTOR_UI_AVAILABLE
53 namespace local_discovery
{
56 const char kPrivetAutomatedClaimURLFormat
[] = "%s/confirm?token=%s";
58 int g_num_visible
= 0;
61 LocalDiscoveryUIHandler::LocalDiscoveryUIHandler() : is_visible_(false) {
62 #if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
63 #if !defined(GOOGLE_CHROME_BUILD) && defined(OS_WIN)
64 // On Windows, we need the PDF plugin which is only guaranteed to exist on
65 // Google Chrome builds. Use a command-line switch for Windows non-Google
67 cloud_print_connector_ui_enabled_
=
68 CommandLine::ForCurrentProcess()->HasSwitch(
69 switches::kEnableCloudPrintProxy
);
70 #elif !defined(OS_CHROMEOS)
71 // Always enabled for Linux and Google Chrome Windows builds.
72 // Never enabled for Chrome OS, we don't even need to indicate it.
73 cloud_print_connector_ui_enabled_
= true;
75 #endif // !defined(OS_MACOSX)
78 LocalDiscoveryUIHandler::~LocalDiscoveryUIHandler() {
79 ResetCurrentRegistration();
84 bool LocalDiscoveryUIHandler::GetHasVisible() {
85 return g_num_visible
!= 0;
88 void LocalDiscoveryUIHandler::RegisterMessages() {
89 web_ui()->RegisterMessageCallback("start", base::Bind(
90 &LocalDiscoveryUIHandler::HandleStart
,
91 base::Unretained(this)));
92 web_ui()->RegisterMessageCallback("isVisible", base::Bind(
93 &LocalDiscoveryUIHandler::HandleIsVisible
,
94 base::Unretained(this)));
95 web_ui()->RegisterMessageCallback("registerDevice", base::Bind(
96 &LocalDiscoveryUIHandler::HandleRegisterDevice
,
97 base::Unretained(this)));
98 web_ui()->RegisterMessageCallback("cancelRegistration", base::Bind(
99 &LocalDiscoveryUIHandler::HandleCancelRegistration
,
100 base::Unretained(this)));
101 web_ui()->RegisterMessageCallback("requestPrinterList", base::Bind(
102 &LocalDiscoveryUIHandler::HandleRequestPrinterList
,
103 base::Unretained(this)));
104 web_ui()->RegisterMessageCallback("openCloudPrintURL", base::Bind(
105 &LocalDiscoveryUIHandler::HandleOpenCloudPrintURL
,
106 base::Unretained(this)));
107 web_ui()->RegisterMessageCallback("showSyncUI", base::Bind(
108 &LocalDiscoveryUIHandler::HandleShowSyncUI
,
109 base::Unretained(this)));
111 // Cloud print connector related messages
112 #if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
113 if (cloud_print_connector_ui_enabled_
) {
114 web_ui()->RegisterMessageCallback(
115 "showCloudPrintSetupDialog",
116 base::Bind(&LocalDiscoveryUIHandler::ShowCloudPrintSetupDialog
,
117 base::Unretained(this)));
118 web_ui()->RegisterMessageCallback(
119 "disableCloudPrintConnector",
120 base::Bind(&LocalDiscoveryUIHandler::HandleDisableCloudPrintConnector
,
121 base::Unretained(this)));
123 #endif // defined(ENABLE_FULL_PRINTING)
126 void LocalDiscoveryUIHandler::HandleStart(const base::ListValue
* args
) {
127 Profile
* profile
= Profile::FromWebUI(web_ui());
129 // If privet_lister_ is already set, it is a mock used for tests or the result
131 if (!privet_lister_
) {
132 service_discovery_client_
= ServiceDiscoverySharedClient::GetInstance();
133 privet_lister_
.reset(new PrivetDeviceListerImpl(
134 service_discovery_client_
.get(), this));
135 privet_http_factory_
=
136 PrivetHTTPAsynchronousFactory::CreateInstance(
137 service_discovery_client_
.get(), profile
->GetRequestContext());
140 privet_lister_
->Start();
141 privet_lister_
->DiscoverNewDevices(false);
143 #if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
144 StartCloudPrintConnector();
149 notification_registrar_
.RemoveAll();
150 notification_registrar_
.Add(this,
151 chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL
,
152 content::Source
<Profile
>(profile
));
153 notification_registrar_
.Add(this, chrome::NOTIFICATION_GOOGLE_SIGNED_OUT
,
154 content::Source
<Profile
>(profile
));
157 void LocalDiscoveryUIHandler::HandleIsVisible(const base::ListValue
* args
) {
158 bool is_visible
= false;
159 bool rv
= args
->GetBoolean(0, &is_visible
);
161 SetIsVisible(is_visible
);
164 void LocalDiscoveryUIHandler::HandleRegisterDevice(
165 const base::ListValue
* args
) {
168 bool rv
= args
->GetString(0, &device
);
171 privet_resolution_
= privet_http_factory_
->CreatePrivetHTTP(
173 device_descriptions_
[device
].address
,
174 base::Bind(&LocalDiscoveryUIHandler::StartRegisterHTTP
,
175 base::Unretained(this)));
176 privet_resolution_
->Start();
179 void LocalDiscoveryUIHandler::HandleCancelRegistration(
180 const base::ListValue
* args
) {
181 ResetCurrentRegistration();
184 void LocalDiscoveryUIHandler::HandleRequestPrinterList(
185 const base::ListValue
* args
) {
186 Profile
* profile
= Profile::FromWebUI(web_ui());
187 ProfileOAuth2TokenService
* token_service
=
188 ProfileOAuth2TokenServiceFactory::GetForProfile(profile
);
190 SigninManagerBase
* signin_manager
=
191 SigninManagerFactory::GetInstance()->GetForProfile(profile
);
193 cloud_print_printer_list_
.reset(new CloudPrintPrinterList(
194 profile
->GetRequestContext(),
195 GetCloudPrintBaseUrl(),
197 signin_manager
->GetAuthenticatedAccountId(),
199 cloud_print_printer_list_
->Start();
202 void LocalDiscoveryUIHandler::HandleOpenCloudPrintURL(
203 const base::ListValue
* args
) {
205 bool rv
= args
->GetString(0, &url
);
208 GURL
url_full(GetCloudPrintBaseUrl() + url
);
210 Browser
* browser
= chrome::FindBrowserWithWebContents(
211 web_ui()->GetWebContents());
214 chrome::AddSelectedTabWithURL(browser
,
216 content::PAGE_TRANSITION_FROM_API
);
219 void LocalDiscoveryUIHandler::HandleShowSyncUI(
220 const base::ListValue
* args
) {
221 Browser
* browser
= chrome::FindBrowserWithWebContents(
222 web_ui()->GetWebContents());
225 // We use SOURCE_SETTINGS because the URL for SOURCE_SETTINGS is detected on
227 GURL
url(signin::GetPromoURL(signin::SOURCE_SETTINGS
,
228 true)); // auto close after success.
231 content::OpenURLParams(url
, content::Referrer(), SINGLETON_TAB
,
232 content::PAGE_TRANSITION_AUTO_BOOKMARK
, false));
235 void LocalDiscoveryUIHandler::StartRegisterHTTP(
236 scoped_ptr
<PrivetHTTPClient
> http_client
) {
237 current_http_client_
.swap(http_client
);
239 std::string user
= GetSyncAccount();
241 if (!current_http_client_
) {
246 current_register_operation_
=
247 current_http_client_
->CreateRegisterOperation(user
, this);
248 current_register_operation_
->Start();
251 void LocalDiscoveryUIHandler::OnPrivetRegisterClaimToken(
252 PrivetRegisterOperation
* operation
,
253 const std::string
& token
,
255 web_ui()->CallJavascriptFunction(
256 "local_discovery.onRegistrationConfirmedOnPrinter");
257 if (device_descriptions_
.count(current_http_client_
->GetName()) == 0) {
262 std::string base_url
= GetCloudPrintBaseUrl();
264 GURL
automated_claim_url(base::StringPrintf(
265 kPrivetAutomatedClaimURLFormat
,
269 Profile
* profile
= Profile::FromWebUI(web_ui());
271 ProfileOAuth2TokenService
* token_service
=
272 ProfileOAuth2TokenServiceFactory::GetForProfile(profile
);
274 if (!token_service
) {
279 SigninManagerBase
* signin_manager
=
280 SigninManagerFactory::GetInstance()->GetForProfile(profile
);
281 if (!signin_manager
) {
286 confirm_api_call_flow_
.reset(new PrivetConfirmApiCallFlow(
287 profile
->GetRequestContext(),
289 signin_manager
->GetAuthenticatedAccountId(),
291 base::Bind(&LocalDiscoveryUIHandler::OnConfirmDone
,
292 base::Unretained(this))));
293 confirm_api_call_flow_
->Start();
296 void LocalDiscoveryUIHandler::OnPrivetRegisterError(
297 PrivetRegisterOperation
* operation
,
298 const std::string
& action
,
299 PrivetRegisterOperation::FailureReason reason
,
300 int printer_http_code
,
301 const base::DictionaryValue
* json
) {
304 if (reason
== PrivetRegisterOperation::FAILURE_JSON_ERROR
&&
305 json
->GetString(kPrivetKeyError
, &error
)) {
306 if (error
== kPrivetErrorTimeout
) {
307 web_ui()->CallJavascriptFunction(
308 "local_discovery.onRegistrationTimeout");
310 } else if (error
== kPrivetErrorCancel
) {
311 web_ui()->CallJavascriptFunction(
312 "local_discovery.onRegistrationCanceledPrinter");
320 void LocalDiscoveryUIHandler::OnPrivetRegisterDone(
321 PrivetRegisterOperation
* operation
,
322 const std::string
& device_id
) {
323 std::string name
= operation
->GetHTTPClient()->GetName();
325 current_register_operation_
.reset();
326 current_http_client_
.reset();
328 // HACK(noamsml): Generate network traffic so the Windows firewall doesn't
329 // block the printer's announcement.
330 privet_lister_
->DiscoverNewDevices(false);
332 DeviceDescriptionMap::iterator found
= device_descriptions_
.find(name
);
334 if (found
== device_descriptions_
.end()) {
335 // TODO(noamsml): Handle the case where a printer's record is not present at
336 // the end of registration.
341 SendRegisterDone(found
->first
, found
->second
);
344 void LocalDiscoveryUIHandler::OnConfirmDone(
345 CloudPrintBaseApiFlow::Status status
) {
346 if (status
== CloudPrintBaseApiFlow::SUCCESS
) {
347 confirm_api_call_flow_
.reset();
348 current_register_operation_
->CompleteRegistration();
354 void LocalDiscoveryUIHandler::DeviceChanged(
356 const std::string
& name
,
357 const DeviceDescription
& description
) {
358 device_descriptions_
[name
] = description
;
360 base::DictionaryValue info
;
362 base::StringValue
service_name(name
);
363 scoped_ptr
<base::Value
> null_value(base::Value::CreateNullValue());
365 if (description
.id
.empty()) {
366 info
.SetString("service_name", name
);
367 info
.SetString("human_readable_name", description
.name
);
368 info
.SetString("description", description
.description
);
370 web_ui()->CallJavascriptFunction(
371 "local_discovery.onUnregisteredDeviceUpdate",
374 web_ui()->CallJavascriptFunction(
375 "local_discovery.onUnregisteredDeviceUpdate",
376 service_name
, *null_value
);
380 void LocalDiscoveryUIHandler::DeviceRemoved(const std::string
& name
) {
381 device_descriptions_
.erase(name
);
382 scoped_ptr
<base::Value
> null_value(base::Value::CreateNullValue());
383 base::StringValue
name_value(name
);
385 web_ui()->CallJavascriptFunction("local_discovery.onUnregisteredDeviceUpdate",
386 name_value
, *null_value
);
389 void LocalDiscoveryUIHandler::DeviceCacheFlushed() {
390 web_ui()->CallJavascriptFunction("local_discovery.onDeviceCacheFlushed");
391 privet_lister_
->DiscoverNewDevices(false);
394 void LocalDiscoveryUIHandler::OnCloudPrintPrinterListReady() {
395 base::ListValue printer_object_list
;
396 std::set
<std::string
> local_ids
;
398 for (DeviceDescriptionMap::iterator i
= device_descriptions_
.begin();
399 i
!= device_descriptions_
.end();
401 std::string device_id
= i
->second
.id
;
402 if (!device_id
.empty()) {
403 const CloudPrintPrinterList::PrinterDetails
* details
=
404 cloud_print_printer_list_
->GetDetailsFor(device_id
);
407 local_ids
.insert(device_id
);
408 printer_object_list
.Append(CreatePrinterInfo(*details
).release());
413 for (CloudPrintPrinterList::iterator i
= cloud_print_printer_list_
->begin();
414 i
!= cloud_print_printer_list_
->end(); i
++) {
415 if (local_ids
.count(i
->id
) == 0) {
416 printer_object_list
.Append(CreatePrinterInfo(*i
).release());
420 web_ui()->CallJavascriptFunction(
421 "local_discovery.onCloudDeviceListAvailable", printer_object_list
);
424 void LocalDiscoveryUIHandler::OnCloudPrintPrinterListUnavailable() {
425 web_ui()->CallJavascriptFunction(
426 "local_discovery.onCloudDeviceListUnavailable");
429 void LocalDiscoveryUIHandler::Observe(
431 const content::NotificationSource
& source
,
432 const content::NotificationDetails
& details
) {
434 case chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL
:
435 case chrome::NOTIFICATION_GOOGLE_SIGNED_OUT
:
443 void LocalDiscoveryUIHandler::SendRegisterError() {
444 web_ui()->CallJavascriptFunction("local_discovery.onRegistrationFailed");
447 void LocalDiscoveryUIHandler::SendRegisterDone(
448 const std::string
& service_name
, const DeviceDescription
& device
) {
449 base::DictionaryValue printer_value
;
451 printer_value
.SetString("id", device
.id
);
452 printer_value
.SetString("display_name", device
.name
);
453 printer_value
.SetString("description", device
.description
);
454 printer_value
.SetString("service_name", service_name
);
456 web_ui()->CallJavascriptFunction("local_discovery.onRegistrationSuccess",
460 void LocalDiscoveryUIHandler::SetIsVisible(bool visible
) {
461 if (visible
!= is_visible_
) {
462 g_num_visible
+= visible
? 1 : -1;
463 is_visible_
= visible
;
467 std::string
LocalDiscoveryUIHandler::GetSyncAccount() {
468 Profile
* profile
= Profile::FromWebUI(web_ui());
469 SigninManagerBase
* signin_manager
=
470 SigninManagerFactory::GetForProfileIfExists(profile
);
472 if (!signin_manager
) {
476 return signin_manager
->GetAuthenticatedUsername();
479 std::string
LocalDiscoveryUIHandler::GetCloudPrintBaseUrl() {
480 CloudPrintURL
cloud_print_url(Profile::FromWebUI(web_ui()));
482 return cloud_print_url
.GetCloudPrintServiceURL().spec();
485 // TODO(noamsml): Create master object for registration flow.
486 void LocalDiscoveryUIHandler::ResetCurrentRegistration() {
487 if (current_register_operation_
.get()) {
488 current_register_operation_
->Cancel();
489 current_register_operation_
.reset();
492 confirm_api_call_flow_
.reset();
493 privet_resolution_
.reset();
494 current_http_client_
.reset();
497 scoped_ptr
<base::DictionaryValue
> LocalDiscoveryUIHandler::CreatePrinterInfo(
498 const CloudPrintPrinterList::PrinterDetails
& description
) {
499 scoped_ptr
<base::DictionaryValue
> return_value(new base::DictionaryValue
);
501 return_value
->SetString("id", description
.id
);
502 return_value
->SetString("display_name", description
.display_name
);
503 return_value
->SetString("description", description
.description
);
505 return return_value
.Pass();
508 void LocalDiscoveryUIHandler::CheckUserLoggedIn() {
509 base::FundamentalValue
logged_in_value(!GetSyncAccount().empty());
510 web_ui()->CallJavascriptFunction("local_discovery.setUserLoggedIn",
514 #if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
515 void LocalDiscoveryUIHandler::StartCloudPrintConnector() {
516 Profile
* profile
= Profile::FromWebUI(web_ui());
518 base::Closure cloud_print_callback
= base::Bind(
519 &LocalDiscoveryUIHandler::OnCloudPrintPrefsChanged
,
520 base::Unretained(this));
522 if (cloud_print_connector_email_
.GetPrefName().empty()) {
523 cloud_print_connector_email_
.Init(
524 prefs::kCloudPrintEmail
, profile
->GetPrefs(), cloud_print_callback
);
527 if (cloud_print_connector_enabled_
.GetPrefName().empty()) {
528 cloud_print_connector_enabled_
.Init(
529 prefs::kCloudPrintProxyEnabled
, profile
->GetPrefs(),
530 cloud_print_callback
);
533 if (cloud_print_connector_ui_enabled_
) {
534 SetupCloudPrintConnectorSection();
535 RefreshCloudPrintStatusFromService();
537 RemoveCloudPrintConnectorSection();
541 void LocalDiscoveryUIHandler::OnCloudPrintPrefsChanged() {
542 if (cloud_print_connector_ui_enabled_
)
543 SetupCloudPrintConnectorSection();
546 void LocalDiscoveryUIHandler::ShowCloudPrintSetupDialog(
547 const base::ListValue
* args
) {
548 content::RecordAction(
549 base::UserMetricsAction("Options_EnableCloudPrintProxy"));
550 // Open the connector enable page in the current tab.
551 Profile
* profile
= Profile::FromWebUI(web_ui());
552 content::OpenURLParams
params(
553 CloudPrintURL(profile
).GetCloudPrintServiceEnableURL(
554 CloudPrintProxyServiceFactory::GetForProfile(profile
)->proxy_id()),
555 content::Referrer(), CURRENT_TAB
, content::PAGE_TRANSITION_LINK
, false);
556 web_ui()->GetWebContents()->OpenURL(params
);
559 void LocalDiscoveryUIHandler::HandleDisableCloudPrintConnector(
560 const base::ListValue
* args
) {
561 content::RecordAction(
562 base::UserMetricsAction("Options_DisableCloudPrintProxy"));
563 CloudPrintProxyServiceFactory::GetForProfile(Profile::FromWebUI(web_ui()))->
567 void LocalDiscoveryUIHandler::SetupCloudPrintConnectorSection() {
568 Profile
* profile
= Profile::FromWebUI(web_ui());
570 if (!CloudPrintProxyServiceFactory::GetForProfile(profile
)) {
571 cloud_print_connector_ui_enabled_
= false;
572 RemoveCloudPrintConnectorSection();
576 bool cloud_print_connector_allowed
=
577 !cloud_print_connector_enabled_
.IsManaged() ||
578 cloud_print_connector_enabled_
.GetValue();
579 base::FundamentalValue
allowed(cloud_print_connector_allowed
);
582 if (profile
->GetPrefs()->HasPrefPath(prefs::kCloudPrintEmail
) &&
583 cloud_print_connector_allowed
) {
584 email
= profile
->GetPrefs()->GetString(prefs::kCloudPrintEmail
);
586 base::FundamentalValue
disabled(email
.empty());
588 base::string16 label_str
;
590 label_str
= l10n_util::GetStringFUTF16(
591 IDS_LOCAL_DISCOVERY_CLOUD_PRINT_CONNECTOR_DISABLED_LABEL
,
592 l10n_util::GetStringUTF16(IDS_GOOGLE_CLOUD_PRINT
));
594 label_str
= l10n_util::GetStringFUTF16(
595 IDS_OPTIONS_CLOUD_PRINT_CONNECTOR_ENABLED_LABEL
,
596 l10n_util::GetStringUTF16(IDS_GOOGLE_CLOUD_PRINT
),
597 base::UTF8ToUTF16(email
));
599 base::StringValue
label(label_str
);
601 web_ui()->CallJavascriptFunction(
602 "local_discovery.setupCloudPrintConnectorSection", disabled
, label
,
606 void LocalDiscoveryUIHandler::RemoveCloudPrintConnectorSection() {
607 web_ui()->CallJavascriptFunction(
608 "local_discovery.removeCloudPrintConnectorSection");
611 void LocalDiscoveryUIHandler::RefreshCloudPrintStatusFromService() {
612 if (cloud_print_connector_ui_enabled_
)
613 CloudPrintProxyServiceFactory::GetForProfile(Profile::FromWebUI(web_ui()))->
614 RefreshStatusFromService();
616 #endif // cloud print connector option stuff
618 } // namespace local_discovery