local_discovery_ui_handler.cc revision e5d81f57cb97b3b6b7fccc9c5610d21eb81db09d
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.
4
5#include "chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.h"
6
7#include <set>
8
9#include "base/bind.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/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/privet_http_impl.h"
21#include "chrome/browser/local_discovery/service_discovery_shared_client.h"
22#include "chrome/browser/printing/cloud_print/cloud_print_proxy_service.h"
23#include "chrome/browser/printing/cloud_print/cloud_print_proxy_service_factory.h"
24#include "chrome/browser/printing/cloud_print/cloud_print_url.h"
25#include "chrome/browser/profiles/profile.h"
26#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
27#include "chrome/browser/signin/signin_manager_factory.h"
28#include "chrome/browser/signin/signin_promo.h"
29#include "chrome/browser/ui/browser_finder.h"
30#include "chrome/browser/ui/browser_tabstrip.h"
31#include "chrome/common/chrome_switches.h"
32#include "chrome/common/pref_names.h"
33#include "components/signin/core/browser/profile_oauth2_token_service.h"
34#include "components/signin/core/browser/signin_manager_base.h"
35#include "content/public/browser/user_metrics.h"
36#include "content/public/browser/user_metrics.h"
37#include "content/public/browser/web_ui.h"
38#include "content/public/common/page_transition_types.h"
39#include "grit/generated_resources.h"
40#include "net/base/host_port_pair.h"
41#include "net/base/net_util.h"
42#include "net/http/http_status_code.h"
43#include "ui/base/l10n/l10n_util.h"
44
45#if defined(ENABLE_FULL_PRINTING) && !defined(OS_CHROMEOS)
46#define CLOUD_PRINT_CONNECTOR_UI_AVAILABLE
47#endif
48
49namespace local_discovery {
50
51namespace {
52const char kPrivetAutomatedClaimURLFormat[] = "%s/confirm?token=%s";
53
54int g_num_visible = 0;
55}  // namespace
56
57LocalDiscoveryUIHandler::LocalDiscoveryUIHandler() : is_visible_(false) {
58#if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
59#if !defined(GOOGLE_CHROME_BUILD) && defined(OS_WIN)
60  // On Windows, we need the PDF plugin which is only guaranteed to exist on
61  // Google Chrome builds. Use a command-line switch for Windows non-Google
62  //  Chrome builds.
63  cloud_print_connector_ui_enabled_ =
64      CommandLine::ForCurrentProcess()->HasSwitch(
65      switches::kEnableCloudPrintProxy);
66#else
67  // Always enabled for Linux and Google Chrome Windows builds.
68  // Never enabled for Chrome OS, we don't even need to indicate it.
69  cloud_print_connector_ui_enabled_ = true;
70#endif
71#endif  // defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
72}
73
74LocalDiscoveryUIHandler::~LocalDiscoveryUIHandler() {
75  Profile* profile = Profile::FromWebUI(web_ui());
76  SigninManagerBase* signin_manager =
77      SigninManagerFactory::GetInstance()->GetForProfile(profile);
78  if (signin_manager)
79    signin_manager->RemoveObserver(this);
80  ResetCurrentRegistration();
81  SetIsVisible(false);
82}
83
84// static
85bool LocalDiscoveryUIHandler::GetHasVisible() {
86  return g_num_visible != 0;
87}
88
89void 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)));
111
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)));
123  }
124#endif  // defined(ENABLE_FULL_PRINTING)
125}
126
127void LocalDiscoveryUIHandler::HandleStart(const base::ListValue* args) {
128  Profile* profile = Profile::FromWebUI(web_ui());
129
130  // If privet_lister_ is already set, it is a mock used for tests or the result
131  // of a reload.
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());
139
140    SigninManagerBase* signin_manager =
141        SigninManagerFactory::GetInstance()->GetForProfile(profile);
142    if (signin_manager)
143      signin_manager->AddObserver(this);
144  }
145
146  privet_lister_->Start();
147  privet_lister_->DiscoverNewDevices(false);
148
149#if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
150  StartCloudPrintConnector();
151#endif
152
153  CheckUserLoggedIn();
154}
155
156void LocalDiscoveryUIHandler::HandleIsVisible(const base::ListValue* args) {
157  bool is_visible = false;
158  bool rv = args->GetBoolean(0, &is_visible);
159  DCHECK(rv);
160  SetIsVisible(is_visible);
161}
162
163void LocalDiscoveryUIHandler::HandleRegisterDevice(
164    const base::ListValue* args) {
165  std::string device;
166
167  bool rv = args->GetString(0, &device);
168  DCHECK(rv);
169
170  privet_resolution_ = privet_http_factory_->CreatePrivetHTTP(
171      device,
172      device_descriptions_[device].address,
173      base::Bind(&LocalDiscoveryUIHandler::StartRegisterHTTP,
174                 base::Unretained(this)));
175  privet_resolution_->Start();
176}
177
178void LocalDiscoveryUIHandler::HandleCancelRegistration(
179    const base::ListValue* args) {
180  ResetCurrentRegistration();
181}
182
183void LocalDiscoveryUIHandler::HandleRequestPrinterList(
184    const base::ListValue* args) {
185  Profile* profile = Profile::FromWebUI(web_ui());
186  ProfileOAuth2TokenService* token_service =
187      ProfileOAuth2TokenServiceFactory::GetForProfile(profile);
188
189  SigninManagerBase* signin_manager =
190      SigninManagerFactory::GetInstance()->GetForProfile(profile);
191
192  cloud_print_printer_list_.reset(new CloudPrintPrinterList(
193      profile->GetRequestContext(),
194      GetCloudPrintBaseUrl(),
195      token_service,
196      signin_manager->GetAuthenticatedAccountId(),
197      this));
198  cloud_print_printer_list_->Start();
199}
200
201void LocalDiscoveryUIHandler::HandleOpenCloudPrintURL(
202    const base::ListValue* args) {
203  std::string url;
204  bool rv = args->GetString(0, &url);
205  DCHECK(rv);
206
207  GURL url_full(GetCloudPrintBaseUrl() + url);
208
209  Browser* browser = chrome::FindBrowserWithWebContents(
210      web_ui()->GetWebContents());
211  DCHECK(browser);
212
213  chrome::AddSelectedTabWithURL(browser,
214                                url_full,
215                                content::PAGE_TRANSITION_FROM_API);
216}
217
218void LocalDiscoveryUIHandler::HandleShowSyncUI(
219    const base::ListValue* args) {
220  Browser* browser = chrome::FindBrowserWithWebContents(
221      web_ui()->GetWebContents());
222  DCHECK(browser);
223
224  GURL url(signin::GetPromoURL(signin::SOURCE_DEVICES_PAGE,
225                               true));  // auto close after success.
226
227  browser->OpenURL(
228      content::OpenURLParams(url, content::Referrer(), SINGLETON_TAB,
229                             content::PAGE_TRANSITION_AUTO_BOOKMARK, false));
230}
231
232void LocalDiscoveryUIHandler::StartRegisterHTTP(
233    scoped_ptr<PrivetHTTPClient> http_client) {
234  current_http_client_.swap(http_client);
235
236  std::string user = GetSyncAccount();
237
238  if (!current_http_client_) {
239    SendRegisterError();
240    return;
241  }
242
243  current_register_operation_ =
244      current_http_client_->CreateRegisterOperation(user, this);
245  current_register_operation_->Start();
246}
247
248void LocalDiscoveryUIHandler::OnPrivetRegisterClaimToken(
249    PrivetRegisterOperation* operation,
250    const std::string& token,
251    const GURL& url) {
252  web_ui()->CallJavascriptFunction(
253      "local_discovery.onRegistrationConfirmedOnPrinter");
254  if (device_descriptions_.count(current_http_client_->GetName()) == 0) {
255    SendRegisterError();
256    return;
257  }
258
259  std::string base_url = GetCloudPrintBaseUrl();
260
261  GURL automated_claim_url(base::StringPrintf(
262      kPrivetAutomatedClaimURLFormat,
263      base_url.c_str(),
264      token.c_str()));
265
266  Profile* profile = Profile::FromWebUI(web_ui());
267
268  ProfileOAuth2TokenService* token_service =
269      ProfileOAuth2TokenServiceFactory::GetForProfile(profile);
270
271  if (!token_service) {
272    SendRegisterError();
273    return;
274  }
275
276  SigninManagerBase* signin_manager =
277      SigninManagerFactory::GetInstance()->GetForProfile(profile);
278  if (!signin_manager) {
279    SendRegisterError();
280    return;
281  }
282
283  confirm_api_call_flow_.reset(new PrivetConfirmApiCallFlow(
284      profile->GetRequestContext(),
285      token_service,
286      signin_manager->GetAuthenticatedAccountId(),
287      automated_claim_url,
288      base::Bind(&LocalDiscoveryUIHandler::OnConfirmDone,
289                 base::Unretained(this))));
290  confirm_api_call_flow_->Start();
291}
292
293void LocalDiscoveryUIHandler::OnPrivetRegisterError(
294    PrivetRegisterOperation* operation,
295    const std::string& action,
296    PrivetRegisterOperation::FailureReason reason,
297    int printer_http_code,
298    const base::DictionaryValue* json) {
299  std::string error;
300
301  if (reason == PrivetRegisterOperation::FAILURE_JSON_ERROR &&
302      json->GetString(kPrivetKeyError, &error)) {
303    if (error == kPrivetErrorTimeout) {
304        web_ui()->CallJavascriptFunction(
305            "local_discovery.onRegistrationTimeout");
306      return;
307    } else if (error == kPrivetErrorCancel) {
308      web_ui()->CallJavascriptFunction(
309            "local_discovery.onRegistrationCanceledPrinter");
310      return;
311    }
312  }
313
314  SendRegisterError();
315}
316
317void LocalDiscoveryUIHandler::OnPrivetRegisterDone(
318    PrivetRegisterOperation* operation,
319    const std::string& device_id) {
320  std::string name = operation->GetHTTPClient()->GetName();
321
322  current_register_operation_.reset();
323  current_http_client_.reset();
324
325  // HACK(noamsml): Generate network traffic so the Windows firewall doesn't
326  // block the printer's announcement.
327  privet_lister_->DiscoverNewDevices(false);
328
329  DeviceDescriptionMap::iterator found = device_descriptions_.find(name);
330
331  if (found == device_descriptions_.end()) {
332    // TODO(noamsml): Handle the case where a printer's record is not present at
333    // the end of registration.
334    SendRegisterError();
335    return;
336  }
337
338  SendRegisterDone(found->first, found->second);
339}
340
341void LocalDiscoveryUIHandler::OnConfirmDone(
342    CloudPrintBaseApiFlow::Status status) {
343  if (status == CloudPrintBaseApiFlow::SUCCESS) {
344    confirm_api_call_flow_.reset();
345    current_register_operation_->CompleteRegistration();
346  } else {
347    SendRegisterError();
348  }
349}
350
351void LocalDiscoveryUIHandler::DeviceChanged(
352    bool added,
353    const std::string& name,
354    const DeviceDescription& description) {
355  device_descriptions_[name] = description;
356
357  base::DictionaryValue info;
358
359  base::StringValue service_name(name);
360  scoped_ptr<base::Value> null_value(base::Value::CreateNullValue());
361
362  if (description.id.empty()) {
363    info.SetString("service_name", name);
364    info.SetString("human_readable_name", description.name);
365    info.SetString("description", description.description);
366
367    web_ui()->CallJavascriptFunction(
368        "local_discovery.onUnregisteredDeviceUpdate",
369        service_name, info);
370  } else {
371    web_ui()->CallJavascriptFunction(
372        "local_discovery.onUnregisteredDeviceUpdate",
373        service_name, *null_value);
374  }
375}
376
377void LocalDiscoveryUIHandler::DeviceRemoved(const std::string& name) {
378  device_descriptions_.erase(name);
379  scoped_ptr<base::Value> null_value(base::Value::CreateNullValue());
380  base::StringValue name_value(name);
381
382  web_ui()->CallJavascriptFunction("local_discovery.onUnregisteredDeviceUpdate",
383                                   name_value, *null_value);
384}
385
386void LocalDiscoveryUIHandler::DeviceCacheFlushed() {
387  web_ui()->CallJavascriptFunction("local_discovery.onDeviceCacheFlushed");
388  privet_lister_->DiscoverNewDevices(false);
389}
390
391void LocalDiscoveryUIHandler::OnCloudPrintPrinterListReady() {
392  base::ListValue printer_object_list;
393  std::set<std::string> local_ids;
394
395  for (DeviceDescriptionMap::iterator i = device_descriptions_.begin();
396       i != device_descriptions_.end();
397       i++) {
398    std::string device_id = i->second.id;
399    if (!device_id.empty()) {
400      const CloudPrintPrinterList::PrinterDetails* details =
401          cloud_print_printer_list_->GetDetailsFor(device_id);
402
403      if (details) {
404        local_ids.insert(device_id);
405        printer_object_list.Append(CreatePrinterInfo(*details).release());
406      }
407    }
408  }
409
410  for (CloudPrintPrinterList::iterator i = cloud_print_printer_list_->begin();
411       i != cloud_print_printer_list_->end(); i++) {
412    if (local_ids.count(i->id) == 0) {
413      printer_object_list.Append(CreatePrinterInfo(*i).release());
414    }
415  }
416
417  web_ui()->CallJavascriptFunction(
418      "local_discovery.onCloudDeviceListAvailable", printer_object_list);
419}
420
421void LocalDiscoveryUIHandler::OnCloudPrintPrinterListUnavailable() {
422  web_ui()->CallJavascriptFunction(
423      "local_discovery.onCloudDeviceListUnavailable");
424}
425
426void LocalDiscoveryUIHandler::GoogleSigninSucceeded(
427    const std::string& username,
428    const std::string& password) {
429  CheckUserLoggedIn();
430}
431
432void LocalDiscoveryUIHandler::GoogleSignedOut(const std::string& username) {
433  CheckUserLoggedIn();
434}
435
436void LocalDiscoveryUIHandler::SendRegisterError() {
437  web_ui()->CallJavascriptFunction("local_discovery.onRegistrationFailed");
438}
439
440void LocalDiscoveryUIHandler::SendRegisterDone(
441    const std::string& service_name, const DeviceDescription& device) {
442  base::DictionaryValue printer_value;
443
444  printer_value.SetString("id", device.id);
445  printer_value.SetString("display_name", device.name);
446  printer_value.SetString("description", device.description);
447  printer_value.SetString("service_name", service_name);
448
449  web_ui()->CallJavascriptFunction("local_discovery.onRegistrationSuccess",
450                                   printer_value);
451}
452
453void LocalDiscoveryUIHandler::SetIsVisible(bool visible) {
454  if (visible != is_visible_) {
455    g_num_visible += visible ? 1 : -1;
456    is_visible_ = visible;
457  }
458}
459
460std::string LocalDiscoveryUIHandler::GetSyncAccount() {
461  Profile* profile = Profile::FromWebUI(web_ui());
462  SigninManagerBase* signin_manager =
463      SigninManagerFactory::GetForProfileIfExists(profile);
464
465  if (!signin_manager) {
466    return "";
467  }
468
469  return signin_manager->GetAuthenticatedUsername();
470}
471
472std::string LocalDiscoveryUIHandler::GetCloudPrintBaseUrl() {
473  CloudPrintURL cloud_print_url(Profile::FromWebUI(web_ui()));
474
475  return cloud_print_url.GetCloudPrintServiceURL().spec();
476}
477
478// TODO(noamsml): Create master object for registration flow.
479void LocalDiscoveryUIHandler::ResetCurrentRegistration() {
480  if (current_register_operation_.get()) {
481    current_register_operation_->Cancel();
482    current_register_operation_.reset();
483  }
484
485  confirm_api_call_flow_.reset();
486  privet_resolution_.reset();
487  current_http_client_.reset();
488}
489
490scoped_ptr<base::DictionaryValue> LocalDiscoveryUIHandler::CreatePrinterInfo(
491    const CloudPrintPrinterList::PrinterDetails& description) {
492  scoped_ptr<base::DictionaryValue> return_value(new base::DictionaryValue);
493
494  return_value->SetString("id", description.id);
495  return_value->SetString("display_name", description.display_name);
496  return_value->SetString("description", description.description);
497
498  return return_value.Pass();
499}
500
501void LocalDiscoveryUIHandler::CheckUserLoggedIn() {
502  base::FundamentalValue logged_in_value(!GetSyncAccount().empty());
503  web_ui()->CallJavascriptFunction("local_discovery.setUserLoggedIn",
504                                   logged_in_value);
505}
506
507#if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE)
508void LocalDiscoveryUIHandler::StartCloudPrintConnector() {
509  Profile* profile = Profile::FromWebUI(web_ui());
510
511  base::Closure cloud_print_callback = base::Bind(
512      &LocalDiscoveryUIHandler::OnCloudPrintPrefsChanged,
513          base::Unretained(this));
514
515  if (cloud_print_connector_email_.GetPrefName().empty()) {
516    cloud_print_connector_email_.Init(
517        prefs::kCloudPrintEmail, profile->GetPrefs(), cloud_print_callback);
518  }
519
520  if (cloud_print_connector_enabled_.GetPrefName().empty()) {
521    cloud_print_connector_enabled_.Init(
522        prefs::kCloudPrintProxyEnabled, profile->GetPrefs(),
523        cloud_print_callback);
524  }
525
526  if (cloud_print_connector_ui_enabled_) {
527    SetupCloudPrintConnectorSection();
528    RefreshCloudPrintStatusFromService();
529  } else {
530    RemoveCloudPrintConnectorSection();
531  }
532}
533
534void LocalDiscoveryUIHandler::OnCloudPrintPrefsChanged() {
535  if (cloud_print_connector_ui_enabled_)
536    SetupCloudPrintConnectorSection();
537}
538
539void LocalDiscoveryUIHandler::ShowCloudPrintSetupDialog(
540    const base::ListValue* args) {
541  content::RecordAction(
542      base::UserMetricsAction("Options_EnableCloudPrintProxy"));
543  // Open the connector enable page in the current tab.
544  Profile* profile = Profile::FromWebUI(web_ui());
545  content::OpenURLParams params(
546      CloudPrintURL(profile).GetCloudPrintServiceEnableURL(
547          CloudPrintProxyServiceFactory::GetForProfile(profile)->proxy_id()),
548      content::Referrer(), CURRENT_TAB, content::PAGE_TRANSITION_LINK, false);
549  web_ui()->GetWebContents()->OpenURL(params);
550}
551
552void LocalDiscoveryUIHandler::HandleDisableCloudPrintConnector(
553    const base::ListValue* args) {
554  content::RecordAction(
555      base::UserMetricsAction("Options_DisableCloudPrintProxy"));
556  CloudPrintProxyServiceFactory::GetForProfile(Profile::FromWebUI(web_ui()))->
557      DisableForUser();
558}
559
560void LocalDiscoveryUIHandler::SetupCloudPrintConnectorSection() {
561  Profile* profile = Profile::FromWebUI(web_ui());
562
563  if (!CloudPrintProxyServiceFactory::GetForProfile(profile)) {
564    cloud_print_connector_ui_enabled_ = false;
565    RemoveCloudPrintConnectorSection();
566    return;
567  }
568
569  bool cloud_print_connector_allowed =
570      !cloud_print_connector_enabled_.IsManaged() ||
571      cloud_print_connector_enabled_.GetValue();
572  base::FundamentalValue allowed(cloud_print_connector_allowed);
573
574  std::string email;
575  if (profile->GetPrefs()->HasPrefPath(prefs::kCloudPrintEmail) &&
576      cloud_print_connector_allowed) {
577    email = profile->GetPrefs()->GetString(prefs::kCloudPrintEmail);
578  }
579  base::FundamentalValue disabled(email.empty());
580
581  base::string16 label_str;
582  if (email.empty()) {
583    label_str = l10n_util::GetStringFUTF16(
584        IDS_LOCAL_DISCOVERY_CLOUD_PRINT_CONNECTOR_DISABLED_LABEL,
585        l10n_util::GetStringUTF16(IDS_GOOGLE_CLOUD_PRINT));
586  } else {
587    label_str = l10n_util::GetStringFUTF16(
588        IDS_OPTIONS_CLOUD_PRINT_CONNECTOR_ENABLED_LABEL,
589        l10n_util::GetStringUTF16(IDS_GOOGLE_CLOUD_PRINT),
590        base::UTF8ToUTF16(email));
591  }
592  base::StringValue label(label_str);
593
594  web_ui()->CallJavascriptFunction(
595      "local_discovery.setupCloudPrintConnectorSection", disabled, label,
596      allowed);
597}
598
599void LocalDiscoveryUIHandler::RemoveCloudPrintConnectorSection() {
600  web_ui()->CallJavascriptFunction(
601      "local_discovery.removeCloudPrintConnectorSection");
602}
603
604void LocalDiscoveryUIHandler::RefreshCloudPrintStatusFromService() {
605  if (cloud_print_connector_ui_enabled_)
606    CloudPrintProxyServiceFactory::GetForProfile(Profile::FromWebUI(web_ui()))->
607        RefreshStatusFromService();
608}
609#endif // cloud print connector option stuff
610
611}  // namespace local_discovery
612