local_discovery_ui_handler.cc revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
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/message_loop/message_loop.h"
11#include "base/strings/stringprintf.h"
12#include "base/values.h"
13#include "chrome/browser/local_discovery/cloud_print_account_manager.h"
14#include "chrome/browser/local_discovery/privet_confirm_api_flow.h"
15#include "chrome/browser/local_discovery/privet_constants.h"
16#include "chrome/browser/local_discovery/privet_device_lister_impl.h"
17#include "chrome/browser/local_discovery/privet_http_asynchronous_factory.h"
18#include "chrome/browser/local_discovery/privet_http_asynchronous_factory.h"
19#include "chrome/browser/local_discovery/privet_http_impl.h"
20#include "chrome/browser/local_discovery/service_discovery_host_client.h"
21#include "chrome/browser/printing/cloud_print/cloud_print_url.h"
22#include "chrome/browser/profiles/profile.h"
23#include "chrome/browser/signin/profile_oauth2_token_service.h"
24#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
25#include "chrome/browser/signin/signin_manager.h"
26#include "chrome/browser/signin/signin_manager_base.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 "content/public/browser/user_metrics.h"
32#include "content/public/browser/web_ui.h"
33#include "content/public/common/page_transition_types.h"
34#include "net/base/host_port_pair.h"
35#include "net/base/net_util.h"
36#include "net/http/http_status_code.h"
37
38namespace local_discovery {
39
40namespace {
41const char kPrivetAutomatedClaimURLFormat[] = "%s/confirm?token=%s";
42const int kRegistrationAnnouncementTimeoutSeconds = 5;
43
44LocalDiscoveryUIHandler::Factory* g_factory = NULL;
45int g_num_visible = 0;
46
47}  // namespace
48
49LocalDiscoveryUIHandler::LocalDiscoveryUIHandler() : is_visible_(false) {
50}
51
52LocalDiscoveryUIHandler::LocalDiscoveryUIHandler(
53    scoped_ptr<PrivetDeviceLister> privet_lister) : is_visible_(false) {
54  privet_lister.swap(privet_lister_);
55}
56
57LocalDiscoveryUIHandler::~LocalDiscoveryUIHandler() {
58  ResetCurrentRegistration();
59  SetIsVisible(false);
60}
61
62// static
63LocalDiscoveryUIHandler* LocalDiscoveryUIHandler::Create() {
64  if (g_factory) return g_factory->CreateLocalDiscoveryUIHandler();
65  return new LocalDiscoveryUIHandler();
66}
67
68// static
69void LocalDiscoveryUIHandler::SetFactory(Factory* factory) {
70  g_factory = factory;
71}
72
73// static
74bool LocalDiscoveryUIHandler::GetHasVisible() {
75  return g_num_visible != 0;
76}
77
78void LocalDiscoveryUIHandler::RegisterMessages() {
79  web_ui()->RegisterMessageCallback("start", base::Bind(
80      &LocalDiscoveryUIHandler::HandleStart,
81      base::Unretained(this)));
82  web_ui()->RegisterMessageCallback("isVisible", base::Bind(
83      &LocalDiscoveryUIHandler::HandleIsVisible,
84      base::Unretained(this)));
85  web_ui()->RegisterMessageCallback("registerDevice", base::Bind(
86      &LocalDiscoveryUIHandler::HandleRegisterDevice,
87      base::Unretained(this)));
88  web_ui()->RegisterMessageCallback("cancelRegistration", base::Bind(
89      &LocalDiscoveryUIHandler::HandleCancelRegistration,
90      base::Unretained(this)));
91  web_ui()->RegisterMessageCallback("requestPrinterList", base::Bind(
92      &LocalDiscoveryUIHandler::HandleRequestPrinterList,
93      base::Unretained(this)));
94  web_ui()->RegisterMessageCallback("openCloudPrintURL", base::Bind(
95      &LocalDiscoveryUIHandler::HandleOpenCloudPrintURL,
96      base::Unretained(this)));
97  web_ui()->RegisterMessageCallback("showSyncUI", base::Bind(
98      &LocalDiscoveryUIHandler::HandleShowSyncUI,
99      base::Unretained(this)));
100}
101
102void LocalDiscoveryUIHandler::HandleStart(const base::ListValue* args) {
103  Profile* profile = Profile::FromWebUI(web_ui());
104
105  // If privet_lister_ is already set, it is a mock used for tests or the result
106  // of a reload.
107  if (!privet_lister_) {
108    service_discovery_client_ = ServiceDiscoverySharedClient::GetInstance();
109    privet_lister_.reset(new PrivetDeviceListerImpl(
110        service_discovery_client_.get(), this));
111    privet_http_factory_ =
112        PrivetHTTPAsynchronousFactory::CreateInstance(
113            service_discovery_client_.get(), profile->GetRequestContext());
114  }
115
116  privet_lister_->Start();
117  privet_lister_->DiscoverNewDevices(false);
118
119  CheckUserLoggedIn();
120}
121
122void LocalDiscoveryUIHandler::HandleIsVisible(const base::ListValue* args) {
123  bool is_visible = false;
124  bool rv = args->GetBoolean(0, &is_visible);
125  DCHECK(rv);
126  SetIsVisible(is_visible);
127}
128
129void LocalDiscoveryUIHandler::HandleRegisterDevice(
130    const base::ListValue* args) {
131  std::string device;
132
133  bool rv = args->GetString(0, &device);
134  DCHECK(rv);
135
136  privet_resolution_ = privet_http_factory_->CreatePrivetHTTP(
137      device,
138      device_descriptions_[device].address,
139      base::Bind(&LocalDiscoveryUIHandler::StartRegisterHTTP,
140                 base::Unretained(this)));
141  privet_resolution_->Start();
142}
143
144void LocalDiscoveryUIHandler::HandleCancelRegistration(
145    const base::ListValue* args) {
146  ResetCurrentRegistration();
147}
148
149void LocalDiscoveryUIHandler::HandleRequestPrinterList(
150    const base::ListValue* args) {
151  Profile* profile = Profile::FromWebUI(web_ui());
152  OAuth2TokenService* token_service =
153      ProfileOAuth2TokenServiceFactory::GetForProfile(profile);
154
155  cloud_print_printer_list_.reset(new CloudPrintPrinterList(
156      profile->GetRequestContext(),
157      GetCloudPrintBaseUrl(),
158      token_service,
159      this));
160  cloud_print_printer_list_->Start();
161}
162
163void LocalDiscoveryUIHandler::HandleOpenCloudPrintURL(
164    const base::ListValue* args) {
165  std::string url;
166  bool rv = args->GetString(0, &url);
167  DCHECK(rv);
168
169  GURL url_full(GetCloudPrintBaseUrl() + url);
170
171  Browser* browser = chrome::FindBrowserWithWebContents(
172      web_ui()->GetWebContents());
173  DCHECK(browser);
174
175  chrome::AddSelectedTabWithURL(browser,
176                                url_full,
177                                content::PAGE_TRANSITION_FROM_API);
178}
179
180void LocalDiscoveryUIHandler::HandleShowSyncUI(
181    const base::ListValue* args) {
182  Browser* browser = chrome::FindBrowserWithWebContents(
183      web_ui()->GetWebContents());
184  DCHECK(browser);
185
186  // We use SOURCE_SETTINGS because the URL for SOURCE_SETTINGS is detected on
187  // redirect.
188  GURL url(signin::GetPromoURL(signin::SOURCE_SETTINGS,
189                               true));  // auto close after success.
190
191  browser->OpenURL(
192      content::OpenURLParams(url, content::Referrer(), SINGLETON_TAB,
193                             content::PAGE_TRANSITION_AUTO_BOOKMARK, false));
194}
195
196void LocalDiscoveryUIHandler::StartRegisterHTTP(
197    scoped_ptr<PrivetHTTPClient> http_client) {
198  current_http_client_.swap(http_client);
199
200  std::string user = GetSyncAccount();
201
202  if (!current_http_client_) {
203    SendRegisterError();
204    return;
205  }
206
207  current_register_operation_ =
208      current_http_client_->CreateRegisterOperation(user, this);
209  current_register_operation_->Start();
210}
211
212void LocalDiscoveryUIHandler::OnPrivetRegisterClaimToken(
213    PrivetRegisterOperation* operation,
214    const std::string& token,
215    const GURL& url) {
216  web_ui()->CallJavascriptFunction(
217      "local_discovery.onRegistrationConfirmedOnPrinter");
218  if (device_descriptions_.count(current_http_client_->GetName()) == 0) {
219    SendRegisterError();
220    return;
221  }
222
223  std::string base_url = GetCloudPrintBaseUrl();
224
225  GURL automated_claim_url(base::StringPrintf(
226      kPrivetAutomatedClaimURLFormat,
227      base_url.c_str(),
228      token.c_str()));
229
230  Profile* profile = Profile::FromWebUI(web_ui());
231
232  OAuth2TokenService* token_service =
233      ProfileOAuth2TokenServiceFactory::GetForProfile(profile);
234
235  if (!token_service) {
236    SendRegisterError();
237    return;
238  }
239
240  confirm_api_call_flow_.reset(new PrivetConfirmApiCallFlow(
241      profile->GetRequestContext(),
242      token_service,
243      automated_claim_url,
244      base::Bind(&LocalDiscoveryUIHandler::OnConfirmDone,
245                 base::Unretained(this))));
246  confirm_api_call_flow_->Start();
247}
248
249void LocalDiscoveryUIHandler::OnPrivetRegisterError(
250    PrivetRegisterOperation* operation,
251    const std::string& action,
252    PrivetRegisterOperation::FailureReason reason,
253    int printer_http_code,
254    const DictionaryValue* json) {
255  // TODO(noamsml): Add detailed error message.
256  SendRegisterError();
257}
258
259void LocalDiscoveryUIHandler::OnPrivetRegisterDone(
260    PrivetRegisterOperation* operation,
261    const std::string& device_id) {
262  std::string name = operation->GetHTTPClient()->GetName();
263
264  current_register_operation_.reset();
265  current_http_client_.reset();
266
267  DeviceDescriptionMap::iterator found = device_descriptions_.find(name);
268
269  if (found == device_descriptions_.end() || found->second.id.empty()) {
270    new_register_device_ = name;
271    registration_announce_timeout_.Reset(base::Bind(
272        &LocalDiscoveryUIHandler::OnAnnouncementTimeoutReached,
273        base::Unretained(this)));
274
275    base::MessageLoop::current()->PostDelayedTask(
276        FROM_HERE,
277        registration_announce_timeout_.callback(),
278        base::TimeDelta::FromSeconds(kRegistrationAnnouncementTimeoutSeconds));
279  }
280}
281
282void LocalDiscoveryUIHandler::OnAnnouncementTimeoutReached() {
283  new_register_device_.clear();
284  registration_announce_timeout_.Cancel();
285
286  SendRegisterError();
287}
288
289void LocalDiscoveryUIHandler::OnConfirmDone(
290    CloudPrintBaseApiFlow::Status status) {
291  if (status == CloudPrintBaseApiFlow::SUCCESS) {
292    confirm_api_call_flow_.reset();
293    current_register_operation_->CompleteRegistration();
294  } else {
295    // TODO(noamsml): Add detailed error message.
296    SendRegisterError();
297  }
298}
299
300void LocalDiscoveryUIHandler::DeviceChanged(
301    bool added,
302    const std::string& name,
303    const DeviceDescription& description) {
304  device_descriptions_[name] = description;
305
306  base::DictionaryValue info;
307
308  base::StringValue service_name(name);
309  scoped_ptr<base::Value> null_value(base::Value::CreateNullValue());
310
311  if (description.id.empty()) {
312    info.SetString("service_name", name);
313    info.SetString("human_readable_name", description.name);
314    info.SetString("description", description.description);
315
316    web_ui()->CallJavascriptFunction(
317        "local_discovery.onUnregisteredDeviceUpdate",
318        service_name, info);
319  } else {
320    web_ui()->CallJavascriptFunction(
321        "local_discovery.onUnregisteredDeviceUpdate",
322        service_name, *null_value);
323
324    if (name == new_register_device_) {
325      new_register_device_.clear();
326      SendRegisterDone();
327    }
328  }
329}
330
331void LocalDiscoveryUIHandler::DeviceRemoved(const std::string& name) {
332  device_descriptions_.erase(name);
333  scoped_ptr<base::Value> null_value(base::Value::CreateNullValue());
334  base::StringValue name_value(name);
335
336  web_ui()->CallJavascriptFunction("local_discovery.onUnregisteredDeviceUpdate",
337                                   name_value, *null_value);
338}
339
340void LocalDiscoveryUIHandler::DeviceCacheFlushed() {
341  web_ui()->CallJavascriptFunction("local_discovery.onDeviceCacheFlushed");
342  privet_lister_->DiscoverNewDevices(false);
343}
344
345void LocalDiscoveryUIHandler::OnCloudPrintPrinterListReady() {
346  base::ListValue printer_object_list;
347  std::set<std::string> local_ids;
348
349  for (DeviceDescriptionMap::iterator i = device_descriptions_.begin();
350       i != device_descriptions_.end();
351       i++) {
352    std::string device_id = i->second.id;
353    if (!device_id.empty()) {
354      const CloudPrintPrinterList::PrinterDetails* details =
355          cloud_print_printer_list_->GetDetailsFor(device_id);
356
357      if (details) {
358        local_ids.insert(device_id);
359        printer_object_list.Append(CreatePrinterInfo(*details).release());
360      }
361    }
362  }
363
364  for (CloudPrintPrinterList::iterator i = cloud_print_printer_list_->begin();
365       i != cloud_print_printer_list_->end(); i++) {
366    if (local_ids.count(i->id) == 0) {
367      printer_object_list.Append(CreatePrinterInfo(*i).release());
368    }
369  }
370
371  web_ui()->CallJavascriptFunction(
372      "local_discovery.onCloudDeviceListAvailable", printer_object_list);
373}
374
375void LocalDiscoveryUIHandler::OnCloudPrintPrinterListUnavailable() {
376  web_ui()->CallJavascriptFunction(
377      "local_discovery.onCloudDeviceListUnavailable");
378}
379
380
381void LocalDiscoveryUIHandler::SendRegisterError() {
382  web_ui()->CallJavascriptFunction("local_discovery.onRegistrationFailed");
383}
384
385void LocalDiscoveryUIHandler::SendRegisterDone() {
386  web_ui()->CallJavascriptFunction("local_discovery.onRegistrationSuccess");
387}
388
389void LocalDiscoveryUIHandler::SetIsVisible(bool visible) {
390  if (visible != is_visible_) {
391    g_num_visible += visible ? 1 : -1;
392    is_visible_ = visible;
393  }
394}
395
396std::string LocalDiscoveryUIHandler::GetSyncAccount() {
397  Profile* profile = Profile::FromWebUI(web_ui());
398  SigninManagerBase* signin_manager =
399      SigninManagerFactory::GetForProfileIfExists(profile);
400
401  if (!signin_manager) {
402    return "";
403  }
404
405  return signin_manager->GetAuthenticatedUsername();
406}
407
408std::string LocalDiscoveryUIHandler::GetCloudPrintBaseUrl() {
409  CloudPrintURL cloud_print_url(Profile::FromWebUI(web_ui()));
410
411  return cloud_print_url.GetCloudPrintServiceURL().spec();
412}
413
414// TODO(noamsml): Create master object for registration flow.
415void LocalDiscoveryUIHandler::ResetCurrentRegistration() {
416  if (current_register_operation_.get()) {
417    current_register_operation_->Cancel();
418    current_register_operation_.reset();
419  }
420
421  confirm_api_call_flow_.reset();
422  privet_resolution_.reset();
423  current_http_client_.reset();
424}
425
426scoped_ptr<base::DictionaryValue> LocalDiscoveryUIHandler::CreatePrinterInfo(
427    const CloudPrintPrinterList::PrinterDetails& description) {
428  scoped_ptr<base::DictionaryValue> return_value(new base::DictionaryValue);
429
430  return_value->SetString("id", description.id);
431  return_value->SetString("display_name", description.display_name);
432  return_value->SetString("description", description.description);
433
434  return return_value.Pass();
435}
436
437void LocalDiscoveryUIHandler::CheckUserLoggedIn() {
438  base::FundamentalValue logged_in_value(!GetSyncAccount().empty());
439  web_ui()->CallJavascriptFunction("local_discovery.setUserLoggedIn",
440                                   logged_in_value);
441}
442
443}  // namespace local_discovery
444