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/chrome_notification_types.h" 17 #include "chrome/browser/local_discovery/cloud_print_account_manager.h" 18 #include "chrome/browser/local_discovery/privet_confirm_api_flow.h" 19 #include "chrome/browser/local_discovery/privet_constants.h" 20 #include "chrome/browser/local_discovery/privet_device_lister_impl.h" 21 #include "chrome/browser/local_discovery/privet_http_asynchronous_factory.h" 22 #include "chrome/browser/local_discovery/privet_http_impl.h" 23 #include "chrome/browser/local_discovery/service_discovery_shared_client.h" 24 #include "chrome/browser/printing/cloud_print/cloud_print_proxy_service.h" 25 #include "chrome/browser/printing/cloud_print/cloud_print_proxy_service_factory.h" 26 #include "chrome/browser/printing/cloud_print/cloud_print_url.h" 27 #include "chrome/browser/profiles/profile.h" 28 #include "chrome/browser/signin/profile_oauth2_token_service.h" 29 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" 30 #include "chrome/browser/signin/signin_manager.h" 31 #include "chrome/browser/signin/signin_manager_base.h" 32 #include "chrome/browser/signin/signin_manager_factory.h" 33 #include "chrome/browser/signin/signin_promo.h" 34 #include "chrome/browser/ui/browser_finder.h" 35 #include "chrome/browser/ui/browser_tabstrip.h" 36 #include "chrome/common/chrome_switches.h" 37 #include "chrome/common/pref_names.h" 38 #include "content/public/browser/notification_source.h" 39 #include "content/public/browser/user_metrics.h" 40 #include "content/public/browser/user_metrics.h" 41 #include "content/public/browser/web_ui.h" 42 #include "content/public/common/page_transition_types.h" 43 #include "grit/generated_resources.h" 44 #include "net/base/host_port_pair.h" 45 #include "net/base/net_util.h" 46 #include "net/http/http_status_code.h" 47 #include "ui/base/l10n/l10n_util.h" 48 49 #if defined(ENABLE_FULL_PRINTING) && !defined(OS_CHROMEOS) && \ 50 !defined(OS_MACOSX) 51 #define CLOUD_PRINT_CONNECTOR_UI_AVAILABLE 52 #endif 53 54 namespace local_discovery { 55 56 namespace { 57 const char kPrivetAutomatedClaimURLFormat[] = "%s/confirm?token=%s"; 58 59 int g_num_visible = 0; 60 } // namespace 61 62 LocalDiscoveryUIHandler::LocalDiscoveryUIHandler() : is_visible_(false) { 63 #if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE) 64 #if !defined(GOOGLE_CHROME_BUILD) && defined(OS_WIN) 65 // On Windows, we need the PDF plugin which is only guaranteed to exist on 66 // Google Chrome builds. Use a command-line switch for Windows non-Google 67 // Chrome builds. 68 cloud_print_connector_ui_enabled_ = 69 CommandLine::ForCurrentProcess()->HasSwitch( 70 switches::kEnableCloudPrintProxy); 71 #elif !defined(OS_CHROMEOS) 72 // Always enabled for Linux and Google Chrome Windows builds. 73 // Never enabled for Chrome OS, we don't even need to indicate it. 74 cloud_print_connector_ui_enabled_ = true; 75 #endif 76 #endif // !defined(OS_MACOSX) 77 } 78 79 LocalDiscoveryUIHandler::~LocalDiscoveryUIHandler() { 80 ResetCurrentRegistration(); 81 SetIsVisible(false); 82 } 83 84 // static 85 bool LocalDiscoveryUIHandler::GetHasVisible() { 86 return g_num_visible != 0; 87 } 88 89 void LocalDiscoveryUIHandler::RegisterMessages() { 90 web_ui()->RegisterMessageCallback("start", base::Bind( 91 &LocalDiscoveryUIHandler::HandleStart, 92 base::Unretained(this))); 93 web_ui()->RegisterMessageCallback("isVisible", base::Bind( 94 &LocalDiscoveryUIHandler::HandleIsVisible, 95 base::Unretained(this))); 96 web_ui()->RegisterMessageCallback("registerDevice", base::Bind( 97 &LocalDiscoveryUIHandler::HandleRegisterDevice, 98 base::Unretained(this))); 99 web_ui()->RegisterMessageCallback("cancelRegistration", base::Bind( 100 &LocalDiscoveryUIHandler::HandleCancelRegistration, 101 base::Unretained(this))); 102 web_ui()->RegisterMessageCallback("requestPrinterList", base::Bind( 103 &LocalDiscoveryUIHandler::HandleRequestPrinterList, 104 base::Unretained(this))); 105 web_ui()->RegisterMessageCallback("openCloudPrintURL", base::Bind( 106 &LocalDiscoveryUIHandler::HandleOpenCloudPrintURL, 107 base::Unretained(this))); 108 web_ui()->RegisterMessageCallback("showSyncUI", base::Bind( 109 &LocalDiscoveryUIHandler::HandleShowSyncUI, 110 base::Unretained(this))); 111 112 // Cloud print connector related messages 113 #if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE) 114 if (cloud_print_connector_ui_enabled_) { 115 web_ui()->RegisterMessageCallback( 116 "showCloudPrintSetupDialog", 117 base::Bind(&LocalDiscoveryUIHandler::ShowCloudPrintSetupDialog, 118 base::Unretained(this))); 119 web_ui()->RegisterMessageCallback( 120 "disableCloudPrintConnector", 121 base::Bind(&LocalDiscoveryUIHandler::HandleDisableCloudPrintConnector, 122 base::Unretained(this))); 123 } 124 #endif // defined(ENABLE_FULL_PRINTING) 125 } 126 127 void LocalDiscoveryUIHandler::HandleStart(const base::ListValue* args) { 128 Profile* profile = Profile::FromWebUI(web_ui()); 129 130 // If privet_lister_ is already set, it is a mock used for tests or the result 131 // of a reload. 132 if (!privet_lister_) { 133 service_discovery_client_ = ServiceDiscoverySharedClient::GetInstance(); 134 privet_lister_.reset(new PrivetDeviceListerImpl( 135 service_discovery_client_.get(), this)); 136 privet_http_factory_ = 137 PrivetHTTPAsynchronousFactory::CreateInstance( 138 service_discovery_client_.get(), profile->GetRequestContext()); 139 } 140 141 privet_lister_->Start(); 142 privet_lister_->DiscoverNewDevices(false); 143 144 #if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE) 145 StartCloudPrintConnector(); 146 #endif 147 148 CheckUserLoggedIn(); 149 150 notification_registrar_.RemoveAll(); 151 notification_registrar_.Add(this, 152 chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL, 153 content::Source<Profile>(profile)); 154 notification_registrar_.Add(this, chrome::NOTIFICATION_GOOGLE_SIGNED_OUT, 155 content::Source<Profile>(profile)); 156 } 157 158 void LocalDiscoveryUIHandler::HandleIsVisible(const base::ListValue* args) { 159 bool is_visible = false; 160 bool rv = args->GetBoolean(0, &is_visible); 161 DCHECK(rv); 162 SetIsVisible(is_visible); 163 } 164 165 void LocalDiscoveryUIHandler::HandleRegisterDevice( 166 const base::ListValue* args) { 167 std::string device; 168 169 bool rv = args->GetString(0, &device); 170 DCHECK(rv); 171 172 privet_resolution_ = privet_http_factory_->CreatePrivetHTTP( 173 device, 174 device_descriptions_[device].address, 175 base::Bind(&LocalDiscoveryUIHandler::StartRegisterHTTP, 176 base::Unretained(this))); 177 privet_resolution_->Start(); 178 } 179 180 void LocalDiscoveryUIHandler::HandleCancelRegistration( 181 const base::ListValue* args) { 182 ResetCurrentRegistration(); 183 } 184 185 void LocalDiscoveryUIHandler::HandleRequestPrinterList( 186 const base::ListValue* args) { 187 Profile* profile = Profile::FromWebUI(web_ui()); 188 ProfileOAuth2TokenService* token_service = 189 ProfileOAuth2TokenServiceFactory::GetForProfile(profile); 190 191 cloud_print_printer_list_.reset(new CloudPrintPrinterList( 192 profile->GetRequestContext(), 193 GetCloudPrintBaseUrl(), 194 token_service, 195 token_service->GetPrimaryAccountId(), 196 this)); 197 cloud_print_printer_list_->Start(); 198 } 199 200 void LocalDiscoveryUIHandler::HandleOpenCloudPrintURL( 201 const base::ListValue* args) { 202 std::string url; 203 bool rv = args->GetString(0, &url); 204 DCHECK(rv); 205 206 GURL url_full(GetCloudPrintBaseUrl() + url); 207 208 Browser* browser = chrome::FindBrowserWithWebContents( 209 web_ui()->GetWebContents()); 210 DCHECK(browser); 211 212 chrome::AddSelectedTabWithURL(browser, 213 url_full, 214 content::PAGE_TRANSITION_FROM_API); 215 } 216 217 void LocalDiscoveryUIHandler::HandleShowSyncUI( 218 const base::ListValue* args) { 219 Browser* browser = chrome::FindBrowserWithWebContents( 220 web_ui()->GetWebContents()); 221 DCHECK(browser); 222 223 // We use SOURCE_SETTINGS because the URL for SOURCE_SETTINGS is detected on 224 // redirect. 225 GURL url(signin::GetPromoURL(signin::SOURCE_SETTINGS, 226 true)); // auto close after success. 227 228 browser->OpenURL( 229 content::OpenURLParams(url, content::Referrer(), SINGLETON_TAB, 230 content::PAGE_TRANSITION_AUTO_BOOKMARK, false)); 231 } 232 233 void LocalDiscoveryUIHandler::StartRegisterHTTP( 234 scoped_ptr<PrivetHTTPClient> http_client) { 235 current_http_client_.swap(http_client); 236 237 std::string user = GetSyncAccount(); 238 239 if (!current_http_client_) { 240 SendRegisterError(); 241 return; 242 } 243 244 current_register_operation_ = 245 current_http_client_->CreateRegisterOperation(user, this); 246 current_register_operation_->Start(); 247 } 248 249 void LocalDiscoveryUIHandler::OnPrivetRegisterClaimToken( 250 PrivetRegisterOperation* operation, 251 const std::string& token, 252 const GURL& url) { 253 web_ui()->CallJavascriptFunction( 254 "local_discovery.onRegistrationConfirmedOnPrinter"); 255 if (device_descriptions_.count(current_http_client_->GetName()) == 0) { 256 SendRegisterError(); 257 return; 258 } 259 260 std::string base_url = GetCloudPrintBaseUrl(); 261 262 GURL automated_claim_url(base::StringPrintf( 263 kPrivetAutomatedClaimURLFormat, 264 base_url.c_str(), 265 token.c_str())); 266 267 Profile* profile = Profile::FromWebUI(web_ui()); 268 269 ProfileOAuth2TokenService* token_service = 270 ProfileOAuth2TokenServiceFactory::GetForProfile(profile); 271 272 if (!token_service) { 273 SendRegisterError(); 274 return; 275 } 276 277 confirm_api_call_flow_.reset(new PrivetConfirmApiCallFlow( 278 profile->GetRequestContext(), 279 token_service, 280 token_service->GetPrimaryAccountId(), 281 automated_claim_url, 282 base::Bind(&LocalDiscoveryUIHandler::OnConfirmDone, 283 base::Unretained(this)))); 284 confirm_api_call_flow_->Start(); 285 } 286 287 void LocalDiscoveryUIHandler::OnPrivetRegisterError( 288 PrivetRegisterOperation* operation, 289 const std::string& action, 290 PrivetRegisterOperation::FailureReason reason, 291 int printer_http_code, 292 const 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 311 void 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 335 void LocalDiscoveryUIHandler::OnConfirmDone( 336 CloudPrintBaseApiFlow::Status status) { 337 if (status == CloudPrintBaseApiFlow::SUCCESS) { 338 confirm_api_call_flow_.reset(); 339 current_register_operation_->CompleteRegistration(); 340 } else { 341 SendRegisterError(); 342 } 343 } 344 345 void LocalDiscoveryUIHandler::DeviceChanged( 346 bool added, 347 const std::string& name, 348 const DeviceDescription& description) { 349 device_descriptions_[name] = description; 350 351 base::DictionaryValue info; 352 353 base::StringValue service_name(name); 354 scoped_ptr<base::Value> null_value(base::Value::CreateNullValue()); 355 356 if (description.id.empty()) { 357 info.SetString("service_name", name); 358 info.SetString("human_readable_name", description.name); 359 info.SetString("description", description.description); 360 361 web_ui()->CallJavascriptFunction( 362 "local_discovery.onUnregisteredDeviceUpdate", 363 service_name, info); 364 } else { 365 web_ui()->CallJavascriptFunction( 366 "local_discovery.onUnregisteredDeviceUpdate", 367 service_name, *null_value); 368 } 369 } 370 371 void LocalDiscoveryUIHandler::DeviceRemoved(const std::string& name) { 372 device_descriptions_.erase(name); 373 scoped_ptr<base::Value> null_value(base::Value::CreateNullValue()); 374 base::StringValue name_value(name); 375 376 web_ui()->CallJavascriptFunction("local_discovery.onUnregisteredDeviceUpdate", 377 name_value, *null_value); 378 } 379 380 void LocalDiscoveryUIHandler::DeviceCacheFlushed() { 381 web_ui()->CallJavascriptFunction("local_discovery.onDeviceCacheFlushed"); 382 privet_lister_->DiscoverNewDevices(false); 383 } 384 385 void LocalDiscoveryUIHandler::OnCloudPrintPrinterListReady() { 386 base::ListValue printer_object_list; 387 std::set<std::string> local_ids; 388 389 for (DeviceDescriptionMap::iterator i = device_descriptions_.begin(); 390 i != device_descriptions_.end(); 391 i++) { 392 std::string device_id = i->second.id; 393 if (!device_id.empty()) { 394 const CloudPrintPrinterList::PrinterDetails* details = 395 cloud_print_printer_list_->GetDetailsFor(device_id); 396 397 if (details) { 398 local_ids.insert(device_id); 399 printer_object_list.Append(CreatePrinterInfo(*details).release()); 400 } 401 } 402 } 403 404 for (CloudPrintPrinterList::iterator i = cloud_print_printer_list_->begin(); 405 i != cloud_print_printer_list_->end(); i++) { 406 if (local_ids.count(i->id) == 0) { 407 printer_object_list.Append(CreatePrinterInfo(*i).release()); 408 } 409 } 410 411 web_ui()->CallJavascriptFunction( 412 "local_discovery.onCloudDeviceListAvailable", printer_object_list); 413 } 414 415 void LocalDiscoveryUIHandler::OnCloudPrintPrinterListUnavailable() { 416 web_ui()->CallJavascriptFunction( 417 "local_discovery.onCloudDeviceListUnavailable"); 418 } 419 420 void LocalDiscoveryUIHandler::Observe( 421 int type, 422 const content::NotificationSource& source, 423 const content::NotificationDetails& details) { 424 switch (type) { 425 case chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL: 426 case chrome::NOTIFICATION_GOOGLE_SIGNED_OUT: 427 CheckUserLoggedIn(); 428 break; 429 default: 430 NOTREACHED(); 431 } 432 } 433 434 void LocalDiscoveryUIHandler::SendRegisterError() { 435 web_ui()->CallJavascriptFunction("local_discovery.onRegistrationFailed"); 436 } 437 438 void LocalDiscoveryUIHandler::SendRegisterDone( 439 const std::string& service_name, const DeviceDescription& device) { 440 base::DictionaryValue printer_value; 441 442 printer_value.SetString("id", device.id); 443 printer_value.SetString("display_name", device.name); 444 printer_value.SetString("description", device.description); 445 printer_value.SetString("service_name", service_name); 446 447 web_ui()->CallJavascriptFunction("local_discovery.onRegistrationSuccess", 448 printer_value); 449 } 450 451 void LocalDiscoveryUIHandler::SetIsVisible(bool visible) { 452 if (visible != is_visible_) { 453 g_num_visible += visible ? 1 : -1; 454 is_visible_ = visible; 455 } 456 } 457 458 std::string LocalDiscoveryUIHandler::GetSyncAccount() { 459 Profile* profile = Profile::FromWebUI(web_ui()); 460 SigninManagerBase* signin_manager = 461 SigninManagerFactory::GetForProfileIfExists(profile); 462 463 if (!signin_manager) { 464 return ""; 465 } 466 467 return signin_manager->GetAuthenticatedUsername(); 468 } 469 470 std::string LocalDiscoveryUIHandler::GetCloudPrintBaseUrl() { 471 CloudPrintURL cloud_print_url(Profile::FromWebUI(web_ui())); 472 473 return cloud_print_url.GetCloudPrintServiceURL().spec(); 474 } 475 476 // TODO(noamsml): Create master object for registration flow. 477 void LocalDiscoveryUIHandler::ResetCurrentRegistration() { 478 if (current_register_operation_.get()) { 479 current_register_operation_->Cancel(); 480 current_register_operation_.reset(); 481 } 482 483 confirm_api_call_flow_.reset(); 484 privet_resolution_.reset(); 485 current_http_client_.reset(); 486 } 487 488 scoped_ptr<base::DictionaryValue> LocalDiscoveryUIHandler::CreatePrinterInfo( 489 const CloudPrintPrinterList::PrinterDetails& description) { 490 scoped_ptr<base::DictionaryValue> return_value(new base::DictionaryValue); 491 492 return_value->SetString("id", description.id); 493 return_value->SetString("display_name", description.display_name); 494 return_value->SetString("description", description.description); 495 496 return return_value.Pass(); 497 } 498 499 void LocalDiscoveryUIHandler::CheckUserLoggedIn() { 500 base::FundamentalValue logged_in_value(!GetSyncAccount().empty()); 501 web_ui()->CallJavascriptFunction("local_discovery.setUserLoggedIn", 502 logged_in_value); 503 } 504 505 #if defined(CLOUD_PRINT_CONNECTOR_UI_AVAILABLE) 506 void LocalDiscoveryUIHandler::StartCloudPrintConnector() { 507 Profile* profile = Profile::FromWebUI(web_ui()); 508 509 base::Closure cloud_print_callback = base::Bind( 510 &LocalDiscoveryUIHandler::OnCloudPrintPrefsChanged, 511 base::Unretained(this)); 512 513 if (cloud_print_connector_email_.GetPrefName().empty()) { 514 cloud_print_connector_email_.Init( 515 prefs::kCloudPrintEmail, profile->GetPrefs(), cloud_print_callback); 516 } 517 518 if (cloud_print_connector_enabled_.GetPrefName().empty()) { 519 cloud_print_connector_enabled_.Init( 520 prefs::kCloudPrintProxyEnabled, profile->GetPrefs(), 521 cloud_print_callback); 522 } 523 524 if (cloud_print_connector_ui_enabled_) { 525 SetupCloudPrintConnectorSection(); 526 RefreshCloudPrintStatusFromService(); 527 } else { 528 RemoveCloudPrintConnectorSection(); 529 } 530 } 531 532 void LocalDiscoveryUIHandler::OnCloudPrintPrefsChanged() { 533 if (cloud_print_connector_ui_enabled_) 534 SetupCloudPrintConnectorSection(); 535 } 536 537 void LocalDiscoveryUIHandler::ShowCloudPrintSetupDialog(const ListValue* args) { 538 content::RecordAction( 539 content::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 549 void LocalDiscoveryUIHandler::HandleDisableCloudPrintConnector( 550 const ListValue* args) { 551 content::RecordAction( 552 content::UserMetricsAction("Options_DisableCloudPrintProxy")); 553 CloudPrintProxyServiceFactory::GetForProfile(Profile::FromWebUI(web_ui()))-> 554 DisableForUser(); 555 } 556 557 void 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 UTF8ToUTF16(email)); 588 } 589 StringValue label(label_str); 590 591 web_ui()->CallJavascriptFunction( 592 "local_discovery.setupCloudPrintConnectorSection", disabled, label, 593 allowed); 594 } 595 596 void LocalDiscoveryUIHandler::RemoveCloudPrintConnectorSection() { 597 web_ui()->CallJavascriptFunction( 598 "local_discovery.removeCloudPrintConnectorSection"); 599 } 600 601 void 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