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