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