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