OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/chrome_to_mobile_service.h" | 5 #include "chrome/browser/chrome_to_mobile_service.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "base/command_line.h" | 8 #include "base/command_line.h" |
9 #include "base/file_util.h" | 9 #include "base/file_util.h" |
10 #include "base/guid.h" | 10 #include "base/guid.h" |
(...skipping 24 matching lines...) Expand all Loading... | |
35 #include "chrome/common/pref_names.h" | 35 #include "chrome/common/pref_names.h" |
36 #include "chrome/common/url_constants.h" | 36 #include "chrome/common/url_constants.h" |
37 #include "content/public/browser/browser_thread.h" | 37 #include "content/public/browser/browser_thread.h" |
38 #include "content/public/browser/notification_details.h" | 38 #include "content/public/browser/notification_details.h" |
39 #include "content/public/browser/notification_source.h" | 39 #include "content/public/browser/notification_source.h" |
40 #include "content/public/browser/web_contents.h" | 40 #include "content/public/browser/web_contents.h" |
41 #include "google/cacheinvalidation/include/types.h" | 41 #include "google/cacheinvalidation/include/types.h" |
42 #include "google/cacheinvalidation/types.pb.h" | 42 #include "google/cacheinvalidation/types.pb.h" |
43 #include "net/base/escape.h" | 43 #include "net/base/escape.h" |
44 #include "net/base/load_flags.h" | 44 #include "net/base/load_flags.h" |
45 #include "net/http/http_status_code.h" | |
45 #include "net/url_request/url_fetcher.h" | 46 #include "net/url_request/url_fetcher.h" |
46 #include "net/url_request/url_request_context_getter.h" | 47 #include "net/url_request/url_request_context_getter.h" |
47 #include "sync/notifier/invalidation_util.h" | 48 #include "sync/notifier/invalidation_util.h" |
48 | 49 |
49 namespace { | 50 namespace { |
50 | 51 |
51 // The maximum number of retries for the URLFetcher requests. | 52 // The maximum number of retries for the URLFetcher requests. |
52 const size_t kMaxRetries = 1; | 53 const size_t kMaxRetries = 5; |
53 | 54 |
54 // The number of hours to delay before retrying authentication on failure. | 55 // The number of hours to delay before retrying certain failed operations. |
55 const size_t kAuthRetryDelayHours = 6; | 56 const size_t kDelayHours = 1; |
56 | |
57 // The number of hours before subsequent search requests are allowed. | |
58 // This value is used to throttle expensive cloud print search requests. | |
59 // Note that this limitation does not hold across application restarts. | |
60 const int kSearchRequestDelayHours = 24; | |
61 | 57 |
62 // The sync invalidation object ID for Chrome to Mobile's mobile device list. | 58 // The sync invalidation object ID for Chrome to Mobile's mobile device list. |
63 // This corresponds with cloud print's server-side invalidation object ID. | 59 // This corresponds with cloud print's server-side invalidation object ID. |
64 // Meaning: "U" == "User", "CM" == "Chrome to Mobile", "MLST" == "Mobile LiST". | 60 // Meaning: "U" == "User", "CM" == "Chrome to Mobile", "MLST" == "Mobile LiST". |
65 const char kSyncInvalidationObjectIdChromeToMobileDeviceList[] = "UCMMLST"; | 61 const char kSyncInvalidationObjectIdChromeToMobileDeviceList[] = "UCMMLST"; |
66 | 62 |
67 // The cloud print OAuth2 scope and 'printer' type of compatible mobile devices. | 63 // The cloud print OAuth2 scope and 'printer' type of compatible mobile devices. |
68 const char kCloudPrintAuth[] = "https://www.googleapis.com/auth/cloudprint"; | 64 const char kCloudPrintAuth[] = "https://www.googleapis.com/auth/cloudprint"; |
69 const char kTypeAndroid[] = "ANDROID_CHROME_SNAPSHOT"; | 65 const char kTypeAndroid[] = "ANDROID_CHROME_SNAPSHOT"; |
70 const char kTypeIOS[] = "IOS_CHROME_SNAPSHOT"; | 66 const char kTypeIOS[] = "IOS_CHROME_SNAPSHOT"; |
(...skipping 244 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
315 } | 311 } |
316 | 312 |
317 void ChromeToMobileService::Observe( | 313 void ChromeToMobileService::Observe( |
318 int type, | 314 int type, |
319 const content::NotificationSource& source, | 315 const content::NotificationSource& source, |
320 const content::NotificationDetails& details) { | 316 const content::NotificationDetails& details) { |
321 DCHECK_EQ(type, chrome::NOTIFICATION_TOKEN_AVAILABLE); | 317 DCHECK_EQ(type, chrome::NOTIFICATION_TOKEN_AVAILABLE); |
322 TokenService::TokenAvailableDetails* token_details = | 318 TokenService::TokenAvailableDetails* token_details = |
323 content::Details<TokenService::TokenAvailableDetails>(details).ptr(); | 319 content::Details<TokenService::TokenAvailableDetails>(details).ptr(); |
324 // Invalidate the cloud print access token on Gaia login token updates. | 320 // Invalidate the cloud print access token on Gaia login token updates. |
325 if (token_details->service() == GaiaConstants::kGaiaOAuth2LoginRefreshToken) | 321 if (token_details->service() == GaiaConstants::kGaiaOAuth2LoginRefreshToken || |
322 token_details->service() == GaiaConstants::kGaiaOAuth2LoginAccessToken) | |
326 access_token_.clear(); | 323 access_token_.clear(); |
327 } | 324 } |
328 | 325 |
329 void ChromeToMobileService::OnGetTokenSuccess( | 326 void ChromeToMobileService::OnGetTokenSuccess( |
330 const std::string& access_token, | 327 const std::string& access_token, |
331 const base::Time& expiration_time) { | 328 const base::Time& expiration_time) { |
332 DCHECK(!access_token.empty()); | 329 DCHECK(!access_token.empty()); |
333 access_token_fetcher_.reset(); | 330 access_token_fetcher_.reset(); |
334 auth_retry_timer_.Stop(); | 331 auth_retry_timer_.Stop(); |
335 access_token_ = access_token; | 332 access_token_ = access_token; |
336 | 333 |
337 while (!task_queue_.empty()) { | 334 while (!task_queue_.empty()) { |
338 // Post all tasks that were queued and waiting on a valid access token. | 335 // Post all tasks that were queued and waiting on a valid access token. |
339 if (!content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, | 336 if (!content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, |
340 task_queue_.front())) { | 337 task_queue_.front())) { |
341 NOTREACHED(); | 338 NOTREACHED(); |
342 } | 339 } |
343 task_queue_.pop(); | 340 task_queue_.pop(); |
344 } | 341 } |
345 } | 342 } |
346 | 343 |
347 void ChromeToMobileService::OnGetTokenFailure( | 344 void ChromeToMobileService::OnGetTokenFailure( |
348 const GoogleServiceAuthError& error) { | 345 const GoogleServiceAuthError& error) { |
346 LogMetric(BAD_TOKEN); | |
347 access_token_.clear(); | |
349 access_token_fetcher_.reset(); | 348 access_token_fetcher_.reset(); |
350 auth_retry_timer_.Stop(); | 349 auth_retry_timer_.Stop(); |
351 | 350 |
352 auth_retry_timer_.Start( | 351 base::TimeDelta delay = std::max(base::TimeDelta::FromHours(kDelayHours), |
353 FROM_HERE, base::TimeDelta::FromHours(kAuthRetryDelayHours), | 352 auth_retry_timer_.GetCurrentDelay() * 2); |
354 this, &ChromeToMobileService::RequestAccessToken); | 353 auth_retry_timer_.Start(FROM_HERE, delay, this, |
354 &ChromeToMobileService::RequestAccessToken); | |
355 | |
356 // Clear the mobile list, which may be (or become) out of date. | |
357 ListValue empty; | |
358 profile_->GetPrefs()->Set(prefs::kChromeToMobileDeviceList, empty); | |
359 UpdateCommandState(); | |
355 } | 360 } |
356 | 361 |
357 void ChromeToMobileService::OnNotificationsEnabled() { | 362 void ChromeToMobileService::OnNotificationsEnabled() { |
358 sync_invalidation_enabled_ = true; | 363 sync_invalidation_enabled_ = true; |
359 UpdateCommandState(); | 364 UpdateCommandState(); |
360 } | 365 } |
361 | 366 |
362 void ChromeToMobileService::OnNotificationsDisabled( | 367 void ChromeToMobileService::OnNotificationsDisabled( |
363 syncer::NotificationsDisabledReason reason) { | 368 syncer::NotificationsDisabledReason reason) { |
364 sync_invalidation_enabled_ = false; | 369 sync_invalidation_enabled_ = false; |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
479 request->Start(); | 484 request->Start(); |
480 } | 485 } |
481 | 486 |
482 void ChromeToMobileService::RequestAccessToken() { | 487 void ChromeToMobileService::RequestAccessToken() { |
483 // Register to observe Gaia login refresh token updates. | 488 // Register to observe Gaia login refresh token updates. |
484 TokenService* token_service = TokenServiceFactory::GetForProfile(profile_); | 489 TokenService* token_service = TokenServiceFactory::GetForProfile(profile_); |
485 if (registrar_.IsEmpty()) | 490 if (registrar_.IsEmpty()) |
486 registrar_.Add(this, chrome::NOTIFICATION_TOKEN_AVAILABLE, | 491 registrar_.Add(this, chrome::NOTIFICATION_TOKEN_AVAILABLE, |
487 content::Source<TokenService>(token_service)); | 492 content::Source<TokenService>(token_service)); |
488 | 493 |
489 // Deny concurrent requests and bail without a valid Gaia login refresh token. | 494 // Deny concurrent requests. |
490 if (access_token_fetcher_.get() || !token_service->HasOAuthLoginToken()) | 495 if (access_token_fetcher_.get()) |
491 return; | 496 return; |
492 | 497 |
498 // Handle invalid login refresh tokens as a failure. | |
499 if (!token_service->HasOAuthLoginToken()) { | |
500 OnGetTokenFailure(GoogleServiceAuthError(GoogleServiceAuthError::NONE)); | |
501 return; | |
502 } | |
503 | |
493 auth_retry_timer_.Stop(); | 504 auth_retry_timer_.Stop(); |
494 access_token_fetcher_.reset( | 505 access_token_fetcher_.reset( |
495 new OAuth2AccessTokenFetcher(this, profile_->GetRequestContext())); | 506 new OAuth2AccessTokenFetcher(this, profile_->GetRequestContext())); |
496 GaiaUrls* gaia_urls = GaiaUrls::GetInstance(); | 507 GaiaUrls* gaia_urls = GaiaUrls::GetInstance(); |
497 access_token_fetcher_->Start(gaia_urls->oauth2_chrome_client_id(), | 508 access_token_fetcher_->Start(gaia_urls->oauth2_chrome_client_id(), |
498 gaia_urls->oauth2_chrome_client_secret(), | 509 gaia_urls->oauth2_chrome_client_secret(), |
499 token_service->GetOAuth2LoginRefreshToken(), | 510 token_service->GetOAuth2LoginRefreshToken(), |
500 std::vector<std::string>(1, kCloudPrintAuth)); | 511 std::vector<std::string>(1, kCloudPrintAuth)); |
501 } | 512 } |
502 | 513 |
503 void ChromeToMobileService::RequestDeviceSearch() { | 514 void ChromeToMobileService::RequestDeviceSearch() { |
515 search_retry_timer_.Stop(); | |
504 if (access_token_.empty()) { | 516 if (access_token_.empty()) { |
505 // Enqueue this task to perform after obtaining an access token. | 517 // Enqueue this task to perform after obtaining an access token. |
506 task_queue_.push(base::Bind(&ChromeToMobileService::RequestDeviceSearch, | 518 task_queue_.push(base::Bind(&ChromeToMobileService::RequestDeviceSearch, |
507 weak_ptr_factory_.GetWeakPtr())); | 519 weak_ptr_factory_.GetWeakPtr())); |
508 RequestAccessToken(); | 520 RequestAccessToken(); |
509 return; | 521 return; |
510 } | 522 } |
511 | 523 |
512 LogMetric(DEVICES_REQUESTED); | 524 LogMetric(DEVICES_REQUESTED); |
513 | 525 |
514 net::URLFetcher* search_request = net::URLFetcher::Create( | 526 net::URLFetcher* search_request = net::URLFetcher::Create( |
515 GetSearchURL(cloud_print_url_), net::URLFetcher::GET, this); | 527 GetSearchURL(cloud_print_url_), net::URLFetcher::GET, this); |
516 InitRequest(search_request); | 528 InitRequest(search_request); |
517 search_request->Start(); | 529 search_request->Start(); |
518 } | 530 } |
519 | 531 |
520 void ChromeToMobileService::HandleSearchResponse( | 532 void ChromeToMobileService::HandleSearchResponse( |
521 const net::URLFetcher* source) { | 533 const net::URLFetcher* source) { |
522 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 534 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
523 DCHECK_EQ(source->GetURL(), GetSearchURL(cloud_print_url_)); | 535 DCHECK_EQ(source->GetURL(), GetSearchURL(cloud_print_url_)); |
524 | 536 |
537 ListValue mobiles; | |
525 std::string data; | 538 std::string data; |
526 ListValue* list = NULL; | 539 ListValue* list = NULL; |
527 DictionaryValue* dictionary = NULL; | 540 DictionaryValue* dictionary = NULL; |
528 source->GetResponseAsString(&data); | 541 source->GetResponseAsString(&data); |
529 scoped_ptr<Value> json(base::JSONReader::Read(data)); | 542 scoped_ptr<Value> json(base::JSONReader::Read(data)); |
530 if (json.get() && json->GetAsDictionary(&dictionary) && dictionary && | 543 if (json.get() && json->GetAsDictionary(&dictionary) && dictionary && |
531 dictionary->GetList(cloud_print::kPrinterListValue, &list)) { | 544 dictionary->GetList(cloud_print::kPrinterListValue, &list)) { |
532 ListValue mobiles; | |
533 std::string type, name, id; | 545 std::string type, name, id; |
534 DictionaryValue* printer = NULL; | 546 DictionaryValue* printer = NULL; |
535 DictionaryValue* mobile = NULL; | 547 DictionaryValue* mobile = NULL; |
536 for (size_t index = 0; index < list->GetSize(); ++index) { | 548 for (size_t index = 0; index < list->GetSize(); ++index) { |
537 if (list->GetDictionary(index, &printer) && | 549 if (list->GetDictionary(index, &printer) && |
538 printer->GetString("type", &type) && | 550 printer->GetString("type", &type) && |
539 (type.compare(kTypeAndroid) == 0 || type.compare(kTypeIOS) == 0)) { | 551 (type.compare(kTypeAndroid) == 0 || type.compare(kTypeIOS) == 0)) { |
540 // Copy just the requisite values from the full |printer| definition. | 552 // Copy just the requisite values from the full |printer| definition. |
541 if (printer->GetString("displayName", &name) && | 553 if (printer->GetString("displayName", &name) && |
542 printer->GetString("id", &id)) { | 554 printer->GetString("id", &id)) { |
543 mobile = new DictionaryValue(); | 555 mobile = new DictionaryValue(); |
544 mobile->SetString("type", type); | 556 mobile->SetString("type", type); |
545 mobile->SetString("name", name); | 557 mobile->SetString("name", name); |
546 mobile->SetString("id", id); | 558 mobile->SetString("id", id); |
547 mobiles.Append(mobile); | 559 mobiles.Append(mobile); |
548 } else { | 560 } else { |
549 NOTREACHED(); | 561 NOTREACHED(); |
550 } | 562 } |
551 } | 563 } |
552 } | 564 } |
565 } else if (source->GetResponseCode() == net::HTTP_FORBIDDEN) { | |
566 LogMetric(BAD_SEARCH_AUTH); | |
567 // Invalidate the access token and retry a delayed search on access errors. | |
568 access_token_.clear(); | |
569 search_retry_timer_.Stop(); | |
570 base::TimeDelta delay = std::max(base::TimeDelta::FromHours(kDelayHours), | |
571 auth_retry_timer_.GetCurrentDelay() * 2); | |
nyquist
2012/09/06 19:09:15
Should this not be for search_retry_timer_?
msw
2012/09/06 20:07:10
Done.
| |
572 search_retry_timer_.Start(FROM_HERE, delay, this, | |
573 &ChromeToMobileService::RequestDeviceSearch); | |
574 } else { | |
575 LogMetric(BAD_SEARCH_OTHER); | |
576 } | |
553 | 577 |
554 // Update the cached mobile device list in profile prefs. | 578 // Update the cached mobile device list in profile prefs. |
555 profile_->GetPrefs()->Set(prefs::kChromeToMobileDeviceList, mobiles); | 579 profile_->GetPrefs()->Set(prefs::kChromeToMobileDeviceList, mobiles); |
556 | 580 if (HasMobiles()) |
557 if (HasMobiles()) | 581 LogMetric(DEVICES_AVAILABLE); |
558 LogMetric(DEVICES_AVAILABLE); | 582 UpdateCommandState(); |
559 UpdateCommandState(); | |
560 } | |
561 } | 583 } |
562 | 584 |
563 void ChromeToMobileService::HandleSubmitResponse( | 585 void ChromeToMobileService::HandleSubmitResponse( |
564 const net::URLFetcher* source) { | 586 const net::URLFetcher* source) { |
565 // Get the success value from the cloud print server response data. | 587 // Get the success value from the cloud print server response data. |
566 std::string data; | 588 std::string data; |
589 bool success = false; | |
567 source->GetResponseAsString(&data); | 590 source->GetResponseAsString(&data); |
568 bool success = false; | |
569 DictionaryValue* dictionary = NULL; | 591 DictionaryValue* dictionary = NULL; |
570 scoped_ptr<Value> json(base::JSONReader::Read(data)); | 592 scoped_ptr<Value> json(base::JSONReader::Read(data)); |
571 if (json.get() && json->GetAsDictionary(&dictionary) && dictionary) | 593 if (json.get() && json->GetAsDictionary(&dictionary) && dictionary) { |
572 dictionary->GetBoolean("success", &success); | 594 dictionary->GetBoolean("success", &success); |
595 int error_code = -1; | |
596 if (dictionary->GetInteger("errorCode", &error_code)) | |
597 LogMetric(error_code == 407 ? BAD_SEND_407 : BAD_SEND_ERROR); | |
598 } else if (source->GetResponseCode() == net::HTTP_FORBIDDEN) { | |
599 LogMetric(BAD_SEND_AUTH); | |
600 } else { | |
601 LogMetric(BAD_SEND_OTHER); | |
602 } | |
573 | 603 |
574 // Log each URL and [DELAYED_]SNAPSHOT job submission response. | 604 // Log each URL and [DELAYED_]SNAPSHOT job submission response. |
575 LogMetric(success ? SEND_SUCCESS : SEND_ERROR); | 605 LogMetric(success ? SEND_SUCCESS : SEND_ERROR); |
606 LOG_IF(INFO, !success) << "ChromeToMobile send failed (" << | |
607 source->GetResponseCode() << "): " << data; | |
576 | 608 |
577 // Get the observer for this job submission response. | 609 // Get the observer for this job submission response. |
578 base::WeakPtr<Observer> observer; | 610 base::WeakPtr<Observer> observer; |
579 RequestObserverMap::iterator i = request_observer_map_.find(source); | 611 RequestObserverMap::iterator i = request_observer_map_.find(source); |
580 if (i != request_observer_map_.end()) { | 612 if (i != request_observer_map_.end()) { |
581 observer = i->second; | 613 observer = i->second; |
582 request_observer_map_.erase(i); | 614 request_observer_map_.erase(i); |
583 } | 615 } |
584 | 616 |
585 // Check if the observer is waiting on a second response (url or snapshot). | 617 // Check if the observer is waiting on a second response (url or snapshot). |
586 for (RequestObserverMap::iterator other = request_observer_map_.begin(); | 618 for (RequestObserverMap::iterator other = request_observer_map_.begin(); |
587 observer.get() && (other != request_observer_map_.end()); ++other) { | 619 observer.get() && (other != request_observer_map_.end()); ++other) { |
588 if (other->second == observer) { | 620 if (other->second == observer) { |
589 // Delay reporting success until the second response is received. | 621 // Delay reporting success until the second response is received. |
590 if (success) | 622 if (success) |
591 return; | 623 return; |
592 | 624 |
593 // Report failure below and ignore the second response. | 625 // Report failure below and ignore the second response. |
594 request_observer_map_.erase(other); | 626 request_observer_map_.erase(other); |
595 break; | 627 break; |
596 } | 628 } |
597 } | 629 } |
598 | 630 |
599 if (observer.get()) | 631 if (observer.get()) |
600 observer->OnSendComplete(success); | 632 observer->OnSendComplete(success); |
601 } | 633 } |
OLD | NEW |