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/utf_string_conversions.h"
14 #include "base/values.h"
15 #include "chrome/browser/local_discovery/cloud_device_list.h"
16 #include "chrome/browser/local_discovery/privet_confirm_api_flow.h"
17 #include "chrome/browser/local_discovery/privet_constants.h"
18 #include "chrome/browser/local_discovery/privet_device_lister_impl.h"
19 #include "chrome/browser/local_discovery/privet_http_asynchronous_factory.h"
20 #include "chrome/browser/local_discovery/service_discovery_shared_client.h"
21 #include "chrome/browser/printing/cloud_print/cloud_print_proxy_service.h"
22 #include "chrome/browser/printing/cloud_print/cloud_print_proxy_service_factory.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
25 #include "chrome/browser/signin/signin_manager_factory.h"
26 #include "chrome/browser/ui/browser_finder.h"
27 #include "chrome/browser/ui/browser_tabstrip.h"
28 #include "chrome/browser/ui/chrome_pages.h"
29 #include "chrome/common/chrome_switches.h"
30 #include "chrome/common/pref_names.h"
31 #include "chrome/grit/generated_resources.h"
32 #include "components/cloud_devices/common/cloud_devices_urls.h"
33 #include "components/signin/core/browser/profile_oauth2_token_service.h"
34 #include "content/public/browser/user_metrics.h"
35 #include "content/public/browser/web_ui.h"
36 #include "ui/base/l10n/l10n_util.h"
38 #if defined(ENABLE_PRINT_PREVIEW) && !defined(OS_CHROMEOS)
39 #define CLOUD_PRINT_CONNECTOR_UI_AVAILABLE
42 namespace local_discovery
{
46 const char kDictionaryKeyServiceName
[] = "service_name";
47 const char kDictionaryKeyDisplayName
[] = "display_name";
48 const char kDictionaryKeyDescription
[] = "description";
49 const char kDictionaryKeyType
[] = "type";
50 const char kDictionaryKeyIsWifi
[] = "is_wifi";
51 const char kDictionaryKeyID
[] = "id";
53 const char kKeyPrefixMDns
[] = "MDns:";
55 int g_num_visible
= 0;
57 const int kCloudDevicesPrivetVersion
= 3;
59 scoped_ptr
<base::DictionaryValue
> CreateDeviceInfo(
60 const CloudDeviceListDelegate::Device
& description
) {
61 scoped_ptr
<base::DictionaryValue
> return_value(new base::DictionaryValue
);
63 return_value
->SetString(kDictionaryKeyID
, description
.id
);
64 return_value
->SetString(kDictionaryKeyDisplayName
, description
.display_name
);
65 return_value
->SetString(kDictionaryKeyDescription
, description
.description
);
66 return_value
->SetString(kDictionaryKeyType
, description
.type
);
68 return return_value
.Pass();
72 const std::vector
<CloudDeviceListDelegate::Device
>& devices
,
73 const std::set
<std::string
>& local_ids
,
74 base::ListValue
* devices_list
) {
75 for (CloudDeviceList::iterator i
= devices
.begin(); i
!= devices
.end(); i
++) {
76 if (local_ids
.count(i
->id
) > 0) {
77 devices_list
->Append(CreateDeviceInfo(*i
).release());
81 for (CloudDeviceList::iterator i
= devices
.begin(); i
!= devices
.end(); i
++) {
82 if (local_ids
.count(i
->id
) == 0) {
83 devices_list
->Append(CreateDeviceInfo(*i
).release());
90 LocalDiscoveryUIHandler::LocalDiscoveryUIHandler()
92 failed_list_count_(0),
93 succeded_list_count_(0) {
94 #if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
95 #if !defined(GOOGLE_CHROME_BUILD) && defined(OS_WIN)
96 // On Windows, we need the PDF plugin which is only guaranteed to exist on
97 // Google Chrome builds. Use a command-line switch for Windows non-Google
99 cloud_print_connector_ui_enabled_
=
100 base::CommandLine::ForCurrentProcess()->HasSwitch(
101 switches::kEnableCloudPrintProxy
);
103 // Always enabled for Linux and Google Chrome Windows builds.
104 // Never enabled for Chrome OS, we don't even need to indicate it.
105 cloud_print_connector_ui_enabled_
= true;
107 #endif // defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
110 LocalDiscoveryUIHandler::~LocalDiscoveryUIHandler() {
111 Profile
* profile
= Profile::FromWebUI(web_ui());
112 SigninManagerBase
* signin_manager
=
113 SigninManagerFactory::GetInstance()->GetForProfile(profile
);
115 signin_manager
->RemoveObserver(this);
116 ResetCurrentRegistration();
121 bool LocalDiscoveryUIHandler::GetHasVisible() {
122 return g_num_visible
!= 0;
125 void LocalDiscoveryUIHandler::RegisterMessages() {
126 web_ui()->RegisterMessageCallback("start", base::Bind(
127 &LocalDiscoveryUIHandler::HandleStart
,
128 base::Unretained(this)));
129 web_ui()->RegisterMessageCallback("isVisible", base::Bind(
130 &LocalDiscoveryUIHandler::HandleIsVisible
,
131 base::Unretained(this)));
132 web_ui()->RegisterMessageCallback("registerDevice", base::Bind(
133 &LocalDiscoveryUIHandler::HandleRegisterDevice
,
134 base::Unretained(this)));
135 web_ui()->RegisterMessageCallback("cancelRegistration", base::Bind(
136 &LocalDiscoveryUIHandler::HandleCancelRegistration
,
137 base::Unretained(this)));
138 web_ui()->RegisterMessageCallback(
140 base::Bind(&LocalDiscoveryUIHandler::HandleRequestDeviceList
,
141 base::Unretained(this)));
142 web_ui()->RegisterMessageCallback("openCloudPrintURL", base::Bind(
143 &LocalDiscoveryUIHandler::HandleOpenCloudPrintURL
,
144 base::Unretained(this)));
145 web_ui()->RegisterMessageCallback("showSyncUI", base::Bind(
146 &LocalDiscoveryUIHandler::HandleShowSyncUI
,
147 base::Unretained(this)));
149 // Cloud print connector related messages
150 #if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
151 if (cloud_print_connector_ui_enabled_
) {
152 web_ui()->RegisterMessageCallback(
153 "showCloudPrintSetupDialog",
154 base::Bind(&LocalDiscoveryUIHandler::ShowCloudPrintSetupDialog
,
155 base::Unretained(this)));
156 web_ui()->RegisterMessageCallback(
157 "disableCloudPrintConnector",
158 base::Bind(&LocalDiscoveryUIHandler::HandleDisableCloudPrintConnector
,
159 base::Unretained(this)));
161 #endif // defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
164 void LocalDiscoveryUIHandler::HandleStart(const base::ListValue
* args
) {
165 Profile
* profile
= Profile::FromWebUI(web_ui());
167 // If privet_lister_ is already set, it is a mock used for tests or the result
169 if (!privet_lister_
) {
170 service_discovery_client_
= ServiceDiscoverySharedClient::GetInstance();
171 privet_lister_
.reset(
172 new PrivetDeviceListerImpl(service_discovery_client_
.get(), this));
173 privet_http_factory_
= PrivetHTTPAsynchronousFactory::CreateInstance(
174 profile
->GetRequestContext());
176 SigninManagerBase
* signin_manager
=
177 SigninManagerFactory::GetInstance()->GetForProfile(profile
);
179 signin_manager
->AddObserver(this);
182 privet_lister_
->Start();
183 privet_lister_
->DiscoverNewDevices(false);
185 #if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
186 StartCloudPrintConnector();
192 void LocalDiscoveryUIHandler::HandleIsVisible(const base::ListValue
* args
) {
193 bool is_visible
= false;
194 bool rv
= args
->GetBoolean(0, &is_visible
);
196 SetIsVisible(is_visible
);
199 void LocalDiscoveryUIHandler::HandleRegisterDevice(
200 const base::ListValue
* args
) {
203 bool rv
= args
->GetString(0, &device
);
206 DeviceDescriptionMap::iterator found
= device_descriptions_
.find(device
);
207 if (found
== device_descriptions_
.end()) {
212 if (found
->second
.version
< kCloudDevicesPrivetVersion
) {
213 privet_resolution_
= privet_http_factory_
->CreatePrivetHTTP(device
);
214 privet_resolution_
->Start(
215 found
->second
.address
,
216 base::Bind(&LocalDiscoveryUIHandler::StartRegisterHTTP
,
217 base::Unretained(this)));
223 void LocalDiscoveryUIHandler::HandleCancelRegistration(
224 const base::ListValue
* args
) {
225 ResetCurrentRegistration();
228 void LocalDiscoveryUIHandler::HandleRequestDeviceList(
229 const base::ListValue
* args
) {
230 failed_list_count_
= 0;
231 succeded_list_count_
= 0;
232 cloud_devices_
.clear();
234 cloud_print_printer_list_
= CreateApiFlow();
236 if (cloud_print_printer_list_
) {
237 cloud_print_printer_list_
->Start(
238 make_scoped_ptr
<GCDApiFlow::Request
>(new CloudPrintPrinterList(this)));
244 void LocalDiscoveryUIHandler::HandleOpenCloudPrintURL(
245 const base::ListValue
* args
) {
247 bool rv
= args
->GetString(0, &id
);
250 Browser
* browser
= chrome::FindBrowserWithWebContents(
251 web_ui()->GetWebContents());
254 chrome::AddSelectedTabWithURL(browser
,
255 cloud_devices::GetCloudPrintManageDeviceURL(id
),
256 ui::PAGE_TRANSITION_FROM_API
);
259 void LocalDiscoveryUIHandler::HandleShowSyncUI(
260 const base::ListValue
* args
) {
261 Browser
* browser
= chrome::FindBrowserWithWebContents(
262 web_ui()->GetWebContents());
264 chrome::ShowBrowserSignin(browser
, signin_metrics::SOURCE_DEVICES_PAGE
);
267 void LocalDiscoveryUIHandler::StartRegisterHTTP(
268 scoped_ptr
<PrivetHTTPClient
> http_client
) {
269 current_http_client_
= PrivetV1HTTPClient::CreateDefault(http_client
.Pass());
271 std::string user
= GetSyncAccount();
273 if (!current_http_client_
) {
278 current_register_operation_
=
279 current_http_client_
->CreateRegisterOperation(user
, this);
280 current_register_operation_
->Start();
283 void LocalDiscoveryUIHandler::OnPrivetRegisterClaimToken(
284 PrivetRegisterOperation
* operation
,
285 const std::string
& token
,
287 web_ui()->CallJavascriptFunction(
288 "local_discovery.onRegistrationConfirmedOnPrinter");
289 if (device_descriptions_
.count(current_http_client_
->GetName()) == 0) {
294 confirm_api_call_flow_
= CreateApiFlow();
295 if (!confirm_api_call_flow_
) {
299 confirm_api_call_flow_
->Start(
300 make_scoped_ptr
<GCDApiFlow::Request
>(new PrivetConfirmApiCallFlow(
302 base::Bind(&LocalDiscoveryUIHandler::OnConfirmDone
,
303 base::Unretained(this)))));
306 void LocalDiscoveryUIHandler::OnPrivetRegisterError(
307 PrivetRegisterOperation
* operation
,
308 const std::string
& action
,
309 PrivetRegisterOperation::FailureReason reason
,
310 int printer_http_code
,
311 const base::DictionaryValue
* json
) {
314 if (reason
== PrivetRegisterOperation::FAILURE_JSON_ERROR
&&
315 json
->GetString(kPrivetKeyError
, &error
)) {
316 if (error
== kPrivetErrorTimeout
) {
317 web_ui()->CallJavascriptFunction(
318 "local_discovery.onRegistrationTimeout");
320 } else if (error
== kPrivetErrorCancel
) {
321 web_ui()->CallJavascriptFunction(
322 "local_discovery.onRegistrationCanceledPrinter");
330 void LocalDiscoveryUIHandler::OnPrivetRegisterDone(
331 PrivetRegisterOperation
* operation
,
332 const std::string
& device_id
) {
333 std::string name
= operation
->GetHTTPClient()->GetName();
334 current_register_operation_
.reset();
335 current_http_client_
.reset();
336 SendRegisterDone(name
);
339 void LocalDiscoveryUIHandler::OnSetupError() {
340 ResetCurrentRegistration();
344 void LocalDiscoveryUIHandler::OnConfirmDone(GCDApiFlow::Status status
) {
345 if (status
== GCDApiFlow::SUCCESS
) {
346 confirm_api_call_flow_
.reset();
347 current_register_operation_
->CompleteRegistration();
353 void LocalDiscoveryUIHandler::DeviceChanged(
355 const std::string
& name
,
356 const DeviceDescription
& description
) {
357 device_descriptions_
[name
] = description
;
359 base::DictionaryValue info
;
361 base::StringValue
service_key(kKeyPrefixMDns
+ name
);
363 if (description
.id
.empty()) {
364 info
.SetString(kDictionaryKeyServiceName
, name
);
365 info
.SetString(kDictionaryKeyDisplayName
, description
.name
);
366 info
.SetString(kDictionaryKeyDescription
, description
.description
);
367 info
.SetString(kDictionaryKeyType
, description
.type
);
368 info
.SetBoolean(kDictionaryKeyIsWifi
, false);
370 web_ui()->CallJavascriptFunction(
371 "local_discovery.onUnregisteredDeviceUpdate", service_key
, info
);
373 scoped_ptr
<base::Value
> null_value
= base::Value::CreateNullValue();
375 web_ui()->CallJavascriptFunction(
376 "local_discovery.onUnregisteredDeviceUpdate", service_key
, *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(kKeyPrefixMDns
+ 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::OnDeviceListReady(
395 const std::vector
<Device
>& devices
) {
396 cloud_devices_
.insert(cloud_devices_
.end(), devices
.begin(), devices
.end());
397 ++succeded_list_count_
;
401 void LocalDiscoveryUIHandler::OnDeviceListUnavailable() {
402 ++failed_list_count_
;
406 void LocalDiscoveryUIHandler::GoogleSigninSucceeded(
407 const std::string
& account_id
,
408 const std::string
& username
,
409 const std::string
& password
) {
413 void LocalDiscoveryUIHandler::GoogleSignedOut(const std::string
& account_id
,
414 const std::string
& username
) {
418 void LocalDiscoveryUIHandler::SendRegisterError() {
419 web_ui()->CallJavascriptFunction("local_discovery.onRegistrationFailed");
422 void LocalDiscoveryUIHandler::SendRegisterDone(
423 const std::string
& service_name
) {
424 // HACK(noamsml): Generate network traffic so the Windows firewall doesn't
425 // block the printer's announcement.
426 privet_lister_
->DiscoverNewDevices(false);
428 DeviceDescriptionMap::iterator found
=
429 device_descriptions_
.find(service_name
);
431 if (found
== device_descriptions_
.end()) {
432 // TODO(noamsml): Handle the case where a printer's record is not present at
433 // the end of registration.
438 const DeviceDescription
& device
= found
->second
;
439 base::DictionaryValue device_value
;
441 device_value
.SetString(kDictionaryKeyType
, device
.type
);
442 device_value
.SetString(kDictionaryKeyID
, device
.id
);
443 device_value
.SetString(kDictionaryKeyDisplayName
, device
.name
);
444 device_value
.SetString(kDictionaryKeyDescription
, device
.description
);
445 device_value
.SetString(kDictionaryKeyServiceName
, 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 // TODO(noamsml): Create master object for registration flow.
471 void LocalDiscoveryUIHandler::ResetCurrentRegistration() {
472 if (current_register_operation_
) {
473 current_register_operation_
->Cancel();
474 current_register_operation_
.reset();
477 confirm_api_call_flow_
.reset();
478 privet_resolution_
.reset();
479 current_http_client_
.reset();
482 void LocalDiscoveryUIHandler::CheckUserLoggedIn() {
483 base::FundamentalValue
logged_in_value(!GetSyncAccount().empty());
484 base::FundamentalValue
is_supervised_value(IsUserSupervisedOrOffTheRecord());
485 web_ui()->CallJavascriptFunction(
486 "local_discovery.setUserLoggedIn", logged_in_value
, is_supervised_value
);
489 void LocalDiscoveryUIHandler::CheckListingDone() {
491 if (cloud_print_printer_list_
)
494 if (started
> failed_list_count_
+ succeded_list_count_
)
497 if (succeded_list_count_
<= 0) {
498 web_ui()->CallJavascriptFunction(
499 "local_discovery.onCloudDeviceListUnavailable");
503 base::ListValue devices_list
;
504 std::set
<std::string
> local_ids
;
506 for (DeviceDescriptionMap::iterator i
= device_descriptions_
.begin();
507 i
!= device_descriptions_
.end(); i
++) {
508 local_ids
.insert(i
->second
.id
);
511 ReadDevicesList(cloud_devices_
, local_ids
, &devices_list
);
513 web_ui()->CallJavascriptFunction(
514 "local_discovery.onCloudDeviceListAvailable", devices_list
);
515 cloud_print_printer_list_
.reset();
518 scoped_ptr
<GCDApiFlow
> LocalDiscoveryUIHandler::CreateApiFlow() {
519 Profile
* profile
= Profile::FromWebUI(web_ui());
521 return scoped_ptr
<GCDApiFlow
>();
522 ProfileOAuth2TokenService
* token_service
=
523 ProfileOAuth2TokenServiceFactory::GetForProfile(profile
);
525 return scoped_ptr
<GCDApiFlow
>();
526 SigninManagerBase
* signin_manager
=
527 SigninManagerFactory::GetInstance()->GetForProfile(profile
);
529 return scoped_ptr
<GCDApiFlow
>();
530 return GCDApiFlow::Create(profile
->GetRequestContext(),
532 signin_manager
->GetAuthenticatedAccountId());
535 bool LocalDiscoveryUIHandler::IsUserSupervisedOrOffTheRecord() {
536 Profile
* profile
= Profile::FromWebUI(web_ui());
538 return profile
->IsSupervised() || profile
->IsOffTheRecord();
541 #if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
542 void LocalDiscoveryUIHandler::StartCloudPrintConnector() {
543 Profile
* profile
= Profile::FromWebUI(web_ui());
545 base::Closure cloud_print_callback
= base::Bind(
546 &LocalDiscoveryUIHandler::OnCloudPrintPrefsChanged
,
547 base::Unretained(this));
549 if (cloud_print_connector_email_
.GetPrefName().empty()) {
550 cloud_print_connector_email_
.Init(
551 prefs::kCloudPrintEmail
, profile
->GetPrefs(), cloud_print_callback
);
554 if (cloud_print_connector_enabled_
.GetPrefName().empty()) {
555 cloud_print_connector_enabled_
.Init(
556 prefs::kCloudPrintProxyEnabled
, profile
->GetPrefs(),
557 cloud_print_callback
);
560 if (cloud_print_connector_ui_enabled_
) {
561 SetupCloudPrintConnectorSection();
562 RefreshCloudPrintStatusFromService();
564 RemoveCloudPrintConnectorSection();
568 void LocalDiscoveryUIHandler::OnCloudPrintPrefsChanged() {
569 if (cloud_print_connector_ui_enabled_
)
570 SetupCloudPrintConnectorSection();
573 void LocalDiscoveryUIHandler::ShowCloudPrintSetupDialog(
574 const base::ListValue
* args
) {
575 content::RecordAction(
576 base::UserMetricsAction("Options_EnableCloudPrintProxy"));
577 // Open the connector enable page in the current tab.
578 Profile
* profile
= Profile::FromWebUI(web_ui());
579 content::OpenURLParams
params(
580 cloud_devices::GetCloudPrintEnableURL(
581 CloudPrintProxyServiceFactory::GetForProfile(profile
)->proxy_id()),
584 ui::PAGE_TRANSITION_LINK
,
586 web_ui()->GetWebContents()->OpenURL(params
);
589 void LocalDiscoveryUIHandler::HandleDisableCloudPrintConnector(
590 const base::ListValue
* args
) {
591 content::RecordAction(
592 base::UserMetricsAction("Options_DisableCloudPrintProxy"));
593 CloudPrintProxyServiceFactory::GetForProfile(Profile::FromWebUI(web_ui()))->
597 void LocalDiscoveryUIHandler::SetupCloudPrintConnectorSection() {
598 Profile
* profile
= Profile::FromWebUI(web_ui());
600 if (!CloudPrintProxyServiceFactory::GetForProfile(profile
)) {
601 cloud_print_connector_ui_enabled_
= false;
602 RemoveCloudPrintConnectorSection();
606 bool cloud_print_connector_allowed
=
607 !cloud_print_connector_enabled_
.IsManaged() ||
608 cloud_print_connector_enabled_
.GetValue();
609 base::FundamentalValue
allowed(cloud_print_connector_allowed
);
612 if (profile
->GetPrefs()->HasPrefPath(prefs::kCloudPrintEmail
) &&
613 cloud_print_connector_allowed
) {
614 email
= profile
->GetPrefs()->GetString(prefs::kCloudPrintEmail
);
616 base::FundamentalValue
disabled(email
.empty());
618 base::string16 label_str
;
620 label_str
= l10n_util::GetStringFUTF16(
621 IDS_LOCAL_DISCOVERY_CLOUD_PRINT_CONNECTOR_DISABLED_LABEL
,
622 l10n_util::GetStringUTF16(IDS_GOOGLE_CLOUD_PRINT
));
624 label_str
= l10n_util::GetStringFUTF16(
625 IDS_OPTIONS_CLOUD_PRINT_CONNECTOR_ENABLED_LABEL
,
626 l10n_util::GetStringUTF16(IDS_GOOGLE_CLOUD_PRINT
),
627 base::UTF8ToUTF16(email
));
629 base::StringValue
label(label_str
);
631 web_ui()->CallJavascriptFunction(
632 "local_discovery.setupCloudPrintConnectorSection", disabled
, label
,
636 void LocalDiscoveryUIHandler::RemoveCloudPrintConnectorSection() {
637 web_ui()->CallJavascriptFunction(
638 "local_discovery.removeCloudPrintConnectorSection");
641 void LocalDiscoveryUIHandler::RefreshCloudPrintStatusFromService() {
642 if (cloud_print_connector_ui_enabled_
)
643 CloudPrintProxyServiceFactory::GetForProfile(Profile::FromWebUI(web_ui()))->
644 RefreshStatusFromService();
647 #endif // cloud print connector option stuff
649 } // namespace local_discovery