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