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/cloud_print_account_manager.h"
18 #include "chrome/browser/local_discovery/privet_confirm_api_flow.h"
19 #include "chrome/browser/local_discovery/privet_constants.h"
20 #include "chrome/browser/local_discovery/privet_device_lister_impl.h"
21 #include "chrome/browser/local_discovery/privet_http_asynchronous_factory.h"
22 #include "chrome/browser/local_discovery/privet_http_impl.h"
23 #include "chrome/browser/local_discovery/service_discovery_shared_client.h"
24 #include "chrome/browser/printing/cloud_print/cloud_print_proxy_service.h"
25 #include "chrome/browser/printing/cloud_print/cloud_print_proxy_service_factory.h"
26 #include "chrome/browser/printing/cloud_print/cloud_print_url.h"
27 #include "chrome/browser/profiles/profile.h"
28 #include "chrome/browser/signin/profile_oauth2_token_service.h"
29 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
30 #include "chrome/browser/signin/signin_manager.h"
31 #include "chrome/browser/signin/signin_manager_base.h"
32 #include "chrome/browser/signin/signin_manager_factory.h"
33 #include "chrome/browser/signin/signin_promo.h"
34 #include "chrome/browser/ui/browser_finder.h"
35 #include "chrome/browser/ui/browser_tabstrip.h"
36 #include "chrome/common/chrome_switches.h"
37 #include "chrome/common/pref_names.h"
38 #include "content/public/browser/notification_source.h"
39 #include "content/public/browser/user_metrics.h"
40 #include "content/public/browser/user_metrics.h"
41 #include "content/public/browser/web_ui.h"
42 #include "content/public/common/page_transition_types.h"
43 #include "grit/generated_resources.h"
44 #include "net/base/host_port_pair.h"
45 #include "net/base/net_util.h"
46 #include "net/http/http_status_code.h"
47 #include "ui/base/l10n/l10n_util.h"
49 #if defined(ENABLE_FULL_PRINTING) && !defined(OS_CHROMEOS) && \
51 #define CLOUD_PRINT_CONNECTOR_UI_AVAILABLE
54 namespace local_discovery
{
57 const char kPrivetAutomatedClaimURLFormat
[] = "%s/confirm?token=%s";
59 int g_num_visible
= 0;
62 LocalDiscoveryUIHandler::LocalDiscoveryUIHandler() : is_visible_(false) {
63 #if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
64 #if !defined(GOOGLE_CHROME_BUILD) && defined(OS_WIN)
65 // On Windows, we need the PDF plugin which is only guaranteed to exist on
66 // Google Chrome builds. Use a command-line switch for Windows non-Google
68 cloud_print_connector_ui_enabled_
=
69 CommandLine::ForCurrentProcess()->HasSwitch(
70 switches::kEnableCloudPrintProxy
);
71 #elif !defined(OS_CHROMEOS)
72 // Always enabled for Linux and Google Chrome Windows builds.
73 // Never enabled for Chrome OS, we don't even need to indicate it.
74 cloud_print_connector_ui_enabled_
= true;
76 #endif // !defined(OS_MACOSX)
79 LocalDiscoveryUIHandler::~LocalDiscoveryUIHandler() {
80 ResetCurrentRegistration();
85 bool LocalDiscoveryUIHandler::GetHasVisible() {
86 return g_num_visible
!= 0;
89 void LocalDiscoveryUIHandler::RegisterMessages() {
90 web_ui()->RegisterMessageCallback("start", base::Bind(
91 &LocalDiscoveryUIHandler::HandleStart
,
92 base::Unretained(this)));
93 web_ui()->RegisterMessageCallback("isVisible", base::Bind(
94 &LocalDiscoveryUIHandler::HandleIsVisible
,
95 base::Unretained(this)));
96 web_ui()->RegisterMessageCallback("registerDevice", base::Bind(
97 &LocalDiscoveryUIHandler::HandleRegisterDevice
,
98 base::Unretained(this)));
99 web_ui()->RegisterMessageCallback("cancelRegistration", base::Bind(
100 &LocalDiscoveryUIHandler::HandleCancelRegistration
,
101 base::Unretained(this)));
102 web_ui()->RegisterMessageCallback("requestPrinterList", base::Bind(
103 &LocalDiscoveryUIHandler::HandleRequestPrinterList
,
104 base::Unretained(this)));
105 web_ui()->RegisterMessageCallback("openCloudPrintURL", base::Bind(
106 &LocalDiscoveryUIHandler::HandleOpenCloudPrintURL
,
107 base::Unretained(this)));
108 web_ui()->RegisterMessageCallback("showSyncUI", base::Bind(
109 &LocalDiscoveryUIHandler::HandleShowSyncUI
,
110 base::Unretained(this)));
112 // Cloud print connector related messages
113 #if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
114 if (cloud_print_connector_ui_enabled_
) {
115 web_ui()->RegisterMessageCallback(
116 "showCloudPrintSetupDialog",
117 base::Bind(&LocalDiscoveryUIHandler::ShowCloudPrintSetupDialog
,
118 base::Unretained(this)));
119 web_ui()->RegisterMessageCallback(
120 "disableCloudPrintConnector",
121 base::Bind(&LocalDiscoveryUIHandler::HandleDisableCloudPrintConnector
,
122 base::Unretained(this)));
124 #endif // defined(ENABLE_FULL_PRINTING)
127 void LocalDiscoveryUIHandler::HandleStart(const base::ListValue
* args
) {
128 Profile
* profile
= Profile::FromWebUI(web_ui());
130 // If privet_lister_ is already set, it is a mock used for tests or the result
132 if (!privet_lister_
) {
133 service_discovery_client_
= ServiceDiscoverySharedClient::GetInstance();
134 privet_lister_
.reset(new PrivetDeviceListerImpl(
135 service_discovery_client_
.get(), this));
136 privet_http_factory_
=
137 PrivetHTTPAsynchronousFactory::CreateInstance(
138 service_discovery_client_
.get(), profile
->GetRequestContext());
141 privet_lister_
->Start();
142 privet_lister_
->DiscoverNewDevices(false);
144 #if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
145 StartCloudPrintConnector();
150 notification_registrar_
.RemoveAll();
151 notification_registrar_
.Add(this,
152 chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL
,
153 content::Source
<Profile
>(profile
));
154 notification_registrar_
.Add(this, chrome::NOTIFICATION_GOOGLE_SIGNED_OUT
,
155 content::Source
<Profile
>(profile
));
158 void LocalDiscoveryUIHandler::HandleIsVisible(const base::ListValue
* args
) {
159 bool is_visible
= false;
160 bool rv
= args
->GetBoolean(0, &is_visible
);
162 SetIsVisible(is_visible
);
165 void LocalDiscoveryUIHandler::HandleRegisterDevice(
166 const base::ListValue
* args
) {
169 bool rv
= args
->GetString(0, &device
);
172 privet_resolution_
= privet_http_factory_
->CreatePrivetHTTP(
174 device_descriptions_
[device
].address
,
175 base::Bind(&LocalDiscoveryUIHandler::StartRegisterHTTP
,
176 base::Unretained(this)));
177 privet_resolution_
->Start();
180 void LocalDiscoveryUIHandler::HandleCancelRegistration(
181 const base::ListValue
* args
) {
182 ResetCurrentRegistration();
185 void LocalDiscoveryUIHandler::HandleRequestPrinterList(
186 const base::ListValue
* args
) {
187 Profile
* profile
= Profile::FromWebUI(web_ui());
188 ProfileOAuth2TokenService
* token_service
=
189 ProfileOAuth2TokenServiceFactory::GetForProfile(profile
);
191 cloud_print_printer_list_
.reset(new CloudPrintPrinterList(
192 profile
->GetRequestContext(),
193 GetCloudPrintBaseUrl(),
195 token_service
->GetPrimaryAccountId(),
197 cloud_print_printer_list_
->Start();
200 void LocalDiscoveryUIHandler::HandleOpenCloudPrintURL(
201 const base::ListValue
* args
) {
203 bool rv
= args
->GetString(0, &url
);
206 GURL
url_full(GetCloudPrintBaseUrl() + url
);
208 Browser
* browser
= chrome::FindBrowserWithWebContents(
209 web_ui()->GetWebContents());
212 chrome::AddSelectedTabWithURL(browser
,
214 content::PAGE_TRANSITION_FROM_API
);
217 void LocalDiscoveryUIHandler::HandleShowSyncUI(
218 const base::ListValue
* args
) {
219 Browser
* browser
= chrome::FindBrowserWithWebContents(
220 web_ui()->GetWebContents());
223 // We use SOURCE_SETTINGS because the URL for SOURCE_SETTINGS is detected on
225 GURL
url(signin::GetPromoURL(signin::SOURCE_SETTINGS
,
226 true)); // auto close after success.
229 content::OpenURLParams(url
, content::Referrer(), SINGLETON_TAB
,
230 content::PAGE_TRANSITION_AUTO_BOOKMARK
, false));
233 void LocalDiscoveryUIHandler::StartRegisterHTTP(
234 scoped_ptr
<PrivetHTTPClient
> http_client
) {
235 current_http_client_
.swap(http_client
);
237 std::string user
= GetSyncAccount();
239 if (!current_http_client_
) {
244 current_register_operation_
=
245 current_http_client_
->CreateRegisterOperation(user
, this);
246 current_register_operation_
->Start();
249 void LocalDiscoveryUIHandler::OnPrivetRegisterClaimToken(
250 PrivetRegisterOperation
* operation
,
251 const std::string
& token
,
253 web_ui()->CallJavascriptFunction(
254 "local_discovery.onRegistrationConfirmedOnPrinter");
255 if (device_descriptions_
.count(current_http_client_
->GetName()) == 0) {
260 std::string base_url
= GetCloudPrintBaseUrl();
262 GURL
automated_claim_url(base::StringPrintf(
263 kPrivetAutomatedClaimURLFormat
,
267 Profile
* profile
= Profile::FromWebUI(web_ui());
269 ProfileOAuth2TokenService
* token_service
=
270 ProfileOAuth2TokenServiceFactory::GetForProfile(profile
);
272 if (!token_service
) {
277 confirm_api_call_flow_
.reset(new PrivetConfirmApiCallFlow(
278 profile
->GetRequestContext(),
280 token_service
->GetPrimaryAccountId(),
282 base::Bind(&LocalDiscoveryUIHandler::OnConfirmDone
,
283 base::Unretained(this))));
284 confirm_api_call_flow_
->Start();
287 void LocalDiscoveryUIHandler::OnPrivetRegisterError(
288 PrivetRegisterOperation
* operation
,
289 const std::string
& action
,
290 PrivetRegisterOperation::FailureReason reason
,
291 int printer_http_code
,
292 const base::DictionaryValue
* json
) {
295 if (reason
== PrivetRegisterOperation::FAILURE_JSON_ERROR
&&
296 json
->GetString(kPrivetKeyError
, &error
)) {
297 if (error
== kPrivetErrorTimeout
) {
298 web_ui()->CallJavascriptFunction(
299 "local_discovery.onRegistrationTimeout");
301 } else if (error
== kPrivetErrorCancel
) {
302 web_ui()->CallJavascriptFunction(
303 "local_discovery.onRegistrationCanceledPrinter");
311 void LocalDiscoveryUIHandler::OnPrivetRegisterDone(
312 PrivetRegisterOperation
* operation
,
313 const std::string
& device_id
) {
314 std::string name
= operation
->GetHTTPClient()->GetName();
316 current_register_operation_
.reset();
317 current_http_client_
.reset();
319 // HACK(noamsml): Generate network traffic so the Windows firewall doesn't
320 // block the printer's announcement.
321 privet_lister_
->DiscoverNewDevices(false);
323 DeviceDescriptionMap::iterator found
= device_descriptions_
.find(name
);
325 if (found
== device_descriptions_
.end()) {
326 // TODO(noamsml): Handle the case where a printer's record is not present at
327 // the end of registration.
332 SendRegisterDone(found
->first
, found
->second
);
335 void LocalDiscoveryUIHandler::OnConfirmDone(
336 CloudPrintBaseApiFlow::Status status
) {
337 if (status
== CloudPrintBaseApiFlow::SUCCESS
) {
338 confirm_api_call_flow_
.reset();
339 current_register_operation_
->CompleteRegistration();
345 void LocalDiscoveryUIHandler::DeviceChanged(
347 const std::string
& name
,
348 const DeviceDescription
& description
) {
349 device_descriptions_
[name
] = description
;
351 base::DictionaryValue info
;
353 base::StringValue
service_name(name
);
354 scoped_ptr
<base::Value
> null_value(base::Value::CreateNullValue());
356 if (description
.id
.empty()) {
357 info
.SetString("service_name", name
);
358 info
.SetString("human_readable_name", description
.name
);
359 info
.SetString("description", description
.description
);
361 web_ui()->CallJavascriptFunction(
362 "local_discovery.onUnregisteredDeviceUpdate",
365 web_ui()->CallJavascriptFunction(
366 "local_discovery.onUnregisteredDeviceUpdate",
367 service_name
, *null_value
);
371 void LocalDiscoveryUIHandler::DeviceRemoved(const std::string
& name
) {
372 device_descriptions_
.erase(name
);
373 scoped_ptr
<base::Value
> null_value(base::Value::CreateNullValue());
374 base::StringValue
name_value(name
);
376 web_ui()->CallJavascriptFunction("local_discovery.onUnregisteredDeviceUpdate",
377 name_value
, *null_value
);
380 void LocalDiscoveryUIHandler::DeviceCacheFlushed() {
381 web_ui()->CallJavascriptFunction("local_discovery.onDeviceCacheFlushed");
382 privet_lister_
->DiscoverNewDevices(false);
385 void LocalDiscoveryUIHandler::OnCloudPrintPrinterListReady() {
386 base::ListValue printer_object_list
;
387 std::set
<std::string
> local_ids
;
389 for (DeviceDescriptionMap::iterator i
= device_descriptions_
.begin();
390 i
!= device_descriptions_
.end();
392 std::string device_id
= i
->second
.id
;
393 if (!device_id
.empty()) {
394 const CloudPrintPrinterList::PrinterDetails
* details
=
395 cloud_print_printer_list_
->GetDetailsFor(device_id
);
398 local_ids
.insert(device_id
);
399 printer_object_list
.Append(CreatePrinterInfo(*details
).release());
404 for (CloudPrintPrinterList::iterator i
= cloud_print_printer_list_
->begin();
405 i
!= cloud_print_printer_list_
->end(); i
++) {
406 if (local_ids
.count(i
->id
) == 0) {
407 printer_object_list
.Append(CreatePrinterInfo(*i
).release());
411 web_ui()->CallJavascriptFunction(
412 "local_discovery.onCloudDeviceListAvailable", printer_object_list
);
415 void LocalDiscoveryUIHandler::OnCloudPrintPrinterListUnavailable() {
416 web_ui()->CallJavascriptFunction(
417 "local_discovery.onCloudDeviceListUnavailable");
420 void LocalDiscoveryUIHandler::Observe(
422 const content::NotificationSource
& source
,
423 const content::NotificationDetails
& details
) {
425 case chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL
:
426 case chrome::NOTIFICATION_GOOGLE_SIGNED_OUT
:
434 void LocalDiscoveryUIHandler::SendRegisterError() {
435 web_ui()->CallJavascriptFunction("local_discovery.onRegistrationFailed");
438 void LocalDiscoveryUIHandler::SendRegisterDone(
439 const std::string
& service_name
, const DeviceDescription
& device
) {
440 base::DictionaryValue printer_value
;
442 printer_value
.SetString("id", device
.id
);
443 printer_value
.SetString("display_name", device
.name
);
444 printer_value
.SetString("description", device
.description
);
445 printer_value
.SetString("service_name", service_name
);
447 web_ui()->CallJavascriptFunction("local_discovery.onRegistrationSuccess",
451 void LocalDiscoveryUIHandler::SetIsVisible(bool visible
) {
452 if (visible
!= is_visible_
) {
453 g_num_visible
+= visible
? 1 : -1;
454 is_visible_
= visible
;
458 std::string
LocalDiscoveryUIHandler::GetSyncAccount() {
459 Profile
* profile
= Profile::FromWebUI(web_ui());
460 SigninManagerBase
* signin_manager
=
461 SigninManagerFactory::GetForProfileIfExists(profile
);
463 if (!signin_manager
) {
467 return signin_manager
->GetAuthenticatedUsername();
470 std::string
LocalDiscoveryUIHandler::GetCloudPrintBaseUrl() {
471 CloudPrintURL
cloud_print_url(Profile::FromWebUI(web_ui()));
473 return cloud_print_url
.GetCloudPrintServiceURL().spec();
476 // TODO(noamsml): Create master object for registration flow.
477 void LocalDiscoveryUIHandler::ResetCurrentRegistration() {
478 if (current_register_operation_
.get()) {
479 current_register_operation_
->Cancel();
480 current_register_operation_
.reset();
483 confirm_api_call_flow_
.reset();
484 privet_resolution_
.reset();
485 current_http_client_
.reset();
488 scoped_ptr
<base::DictionaryValue
> LocalDiscoveryUIHandler::CreatePrinterInfo(
489 const CloudPrintPrinterList::PrinterDetails
& description
) {
490 scoped_ptr
<base::DictionaryValue
> return_value(new base::DictionaryValue
);
492 return_value
->SetString("id", description
.id
);
493 return_value
->SetString("display_name", description
.display_name
);
494 return_value
->SetString("description", description
.description
);
496 return return_value
.Pass();
499 void LocalDiscoveryUIHandler::CheckUserLoggedIn() {
500 base::FundamentalValue
logged_in_value(!GetSyncAccount().empty());
501 web_ui()->CallJavascriptFunction("local_discovery.setUserLoggedIn",
505 #if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
506 void LocalDiscoveryUIHandler::StartCloudPrintConnector() {
507 Profile
* profile
= Profile::FromWebUI(web_ui());
509 base::Closure cloud_print_callback
= base::Bind(
510 &LocalDiscoveryUIHandler::OnCloudPrintPrefsChanged
,
511 base::Unretained(this));
513 if (cloud_print_connector_email_
.GetPrefName().empty()) {
514 cloud_print_connector_email_
.Init(
515 prefs::kCloudPrintEmail
, profile
->GetPrefs(), cloud_print_callback
);
518 if (cloud_print_connector_enabled_
.GetPrefName().empty()) {
519 cloud_print_connector_enabled_
.Init(
520 prefs::kCloudPrintProxyEnabled
, profile
->GetPrefs(),
521 cloud_print_callback
);
524 if (cloud_print_connector_ui_enabled_
) {
525 SetupCloudPrintConnectorSection();
526 RefreshCloudPrintStatusFromService();
528 RemoveCloudPrintConnectorSection();
532 void LocalDiscoveryUIHandler::OnCloudPrintPrefsChanged() {
533 if (cloud_print_connector_ui_enabled_
)
534 SetupCloudPrintConnectorSection();
537 void LocalDiscoveryUIHandler::ShowCloudPrintSetupDialog(
538 const base::ListValue
* args
) {
539 content::RecordAction(
540 base::UserMetricsAction("Options_EnableCloudPrintProxy"));
541 // Open the connector enable page in the current tab.
542 Profile
* profile
= Profile::FromWebUI(web_ui());
543 content::OpenURLParams
params(
544 CloudPrintURL(profile
).GetCloudPrintServiceEnableURL(
545 CloudPrintProxyServiceFactory::GetForProfile(profile
)->proxy_id()),
546 content::Referrer(), CURRENT_TAB
, content::PAGE_TRANSITION_LINK
, false);
547 web_ui()->GetWebContents()->OpenURL(params
);
550 void LocalDiscoveryUIHandler::HandleDisableCloudPrintConnector(
551 const base::ListValue
* args
) {
552 content::RecordAction(
553 base::UserMetricsAction("Options_DisableCloudPrintProxy"));
554 CloudPrintProxyServiceFactory::GetForProfile(Profile::FromWebUI(web_ui()))->
558 void LocalDiscoveryUIHandler::SetupCloudPrintConnectorSection() {
559 Profile
* profile
= Profile::FromWebUI(web_ui());
561 if (!CloudPrintProxyServiceFactory::GetForProfile(profile
)) {
562 cloud_print_connector_ui_enabled_
= false;
563 RemoveCloudPrintConnectorSection();
567 bool cloud_print_connector_allowed
=
568 !cloud_print_connector_enabled_
.IsManaged() ||
569 cloud_print_connector_enabled_
.GetValue();
570 base::FundamentalValue
allowed(cloud_print_connector_allowed
);
573 if (profile
->GetPrefs()->HasPrefPath(prefs::kCloudPrintEmail
) &&
574 cloud_print_connector_allowed
) {
575 email
= profile
->GetPrefs()->GetString(prefs::kCloudPrintEmail
);
577 base::FundamentalValue
disabled(email
.empty());
579 base::string16 label_str
;
581 label_str
= l10n_util::GetStringFUTF16(
582 IDS_LOCAL_DISCOVERY_CLOUD_PRINT_CONNECTOR_DISABLED_LABEL
,
583 l10n_util::GetStringUTF16(IDS_GOOGLE_CLOUD_PRINT
));
585 label_str
= l10n_util::GetStringFUTF16(
586 IDS_OPTIONS_CLOUD_PRINT_CONNECTOR_ENABLED_LABEL
,
587 l10n_util::GetStringUTF16(IDS_GOOGLE_CLOUD_PRINT
),
588 base::UTF8ToUTF16(email
));
590 base::StringValue
label(label_str
);
592 web_ui()->CallJavascriptFunction(
593 "local_discovery.setupCloudPrintConnectorSection", disabled
, label
,
597 void LocalDiscoveryUIHandler::RemoveCloudPrintConnectorSection() {
598 web_ui()->CallJavascriptFunction(
599 "local_discovery.removeCloudPrintConnectorSection");
602 void LocalDiscoveryUIHandler::RefreshCloudPrintStatusFromService() {
603 if (cloud_print_connector_ui_enabled_
)
604 CloudPrintProxyServiceFactory::GetForProfile(Profile::FromWebUI(web_ui()))->
605 RefreshStatusFromService();
607 #endif // cloud print connector option stuff
609 } // namespace local_discovery