[GCM] Fetching OAuth2 tokens periodically in account tracker

BUG=374969
[email protected]

Review URL: https://codereview.chromium.org/631343002

Cr-Commit-Position: refs/heads/master@{#303340}
diff --git a/chrome/browser/services/gcm/gcm_account_tracker.cc b/chrome/browser/services/gcm/gcm_account_tracker.cc
index a2cdba0..0521688 100644
--- a/chrome/browser/services/gcm/gcm_account_tracker.cc
+++ b/chrome/browser/services/gcm/gcm_account_tracker.cc
@@ -15,11 +15,18 @@
 namespace gcm {
 
 namespace {
+
+// Scopes needed by the OAuth2 access tokens.
 const char kGCMGroupServerScope[] = "https://www.googleapis.com/auth/gcm";
 const char kGCMCheckinServerScope[] =
     "https://www.googleapis.com/auth/android_checkin";
+// Name of the GCM account tracker for the OAuth2TokenService.
 const char kGCMAccountTrackerName[] = "gcm_account_tracker";
+// Minimum token validity when sending to GCM groups server.
 const int64 kMinimumTokenValidityMs = 500;
+// Token reporting interval, when no account changes are detected.
+const int64 kTokenReportingIntervalMs = 12 * 60 * 60 * 1000;  // 12 hours in ms.
+
 }  // namespace
 
 GCMAccountTracker::AccountInfo::AccountInfo(const std::string& email,
@@ -36,7 +43,8 @@
     : OAuth2TokenService::Consumer(kGCMAccountTrackerName),
       account_tracker_(account_tracker.release()),
       driver_(driver),
-      shutdown_called_(false) {
+      shutdown_called_(false),
+      reporting_weak_ptr_factory_(this) {
 }
 
 GCMAccountTracker::~GCMAccountTracker() {
@@ -56,11 +64,6 @@
   driver_->AddConnectionObserver(this);
 
   std::vector<gaia::AccountIds> accounts = account_tracker_->GetAccounts();
-  if (accounts.empty()) {
-    CompleteCollectingTokens();
-    return;
-  }
-
   for (std::vector<gaia::AccountIds>::const_iterator iter = accounts.begin();
        iter != accounts.end();
        ++iter) {
@@ -70,7 +73,22 @@
     }
   }
 
-  GetAllNeededTokens();
+  if (IsTokenReportingRequired())
+    ReportTokens();
+  else
+    ScheduleReportTokens();
+}
+
+void GCMAccountTracker::ScheduleReportTokens() {
+  DVLOG(1) << "Deferring the token reporting for: "
+           << GetTimeToNextTokenReporting().InSeconds() << " seconds.";
+
+  reporting_weak_ptr_factory_.InvalidateWeakPtrs();
+  base::MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&GCMAccountTracker::ReportTokens,
+                 reporting_weak_ptr_factory_.GetWeakPtr()),
+      GetTimeToNextTokenReporting());
 }
 
 void GCMAccountTracker::OnAccountAdded(const gaia::AccountIds& ids) {
@@ -115,7 +133,7 @@
   }
 
   DeleteTokenRequest(request);
-  CompleteCollectingTokens();
+  ReportTokens();
 }
 
 void GCMAccountTracker::OnGetTokenFailure(
@@ -137,21 +155,22 @@
   }
 
   DeleteTokenRequest(request);
-  CompleteCollectingTokens();
+  ReportTokens();
 }
 
 void GCMAccountTracker::OnConnected(const net::IPEndPoint& ip_endpoint) {
-  if (SanitizeTokens())
-    GetAllNeededTokens();
+  if (IsTokenReportingRequired())
+    ReportTokens();
 }
 
 void GCMAccountTracker::OnDisconnected() {
   // We are disconnected, so no point in trying to work with tokens.
 }
 
-void GCMAccountTracker::CompleteCollectingTokens() {
+void GCMAccountTracker::ReportTokens() {
+  SanitizeTokens();
   // Make sure all tokens are valid.
-  if (SanitizeTokens()) {
+  if (IsTokenFetchingRequired()) {
     GetAllNeededTokens();
     return;
   }
@@ -198,13 +217,14 @@
   if (!account_tokens.empty() || account_removed) {
     DVLOG(1) << "Reporting the tokens to driver: " << account_tokens.size();
     driver_->SetAccountTokens(account_tokens);
+    driver_->SetLastTokenFetchTime(base::Time::Now());
+    ScheduleReportTokens();
   } else {
     DVLOG(1) << "No tokens and nothing removed. Skipping callback.";
   }
 }
 
-bool GCMAccountTracker::SanitizeTokens() {
-  bool tokens_needed = false;
+void GCMAccountTracker::SanitizeTokens() {
   for (AccountInfos::iterator iter = account_infos_.begin();
        iter != account_infos_.end();
        ++iter) {
@@ -216,12 +236,43 @@
       iter->second.state = TOKEN_NEEDED;
       iter->second.expiration_time = base::Time();
     }
+  }
+}
 
-    if (iter->second.state == TOKEN_NEEDED)
-      tokens_needed = true;
+bool GCMAccountTracker::IsTokenReportingRequired() const {
+  if (GetTimeToNextTokenReporting() == base::TimeDelta())
+    return true;
+
+  bool reporting_required = false;
+  for (AccountInfos::const_iterator iter = account_infos_.begin();
+       iter != account_infos_.end();
+       ++iter) {
+    if (iter->second.state == ACCOUNT_REMOVED)
+      reporting_required = true;
   }
 
-  return tokens_needed;
+  return reporting_required;
+}
+
+bool GCMAccountTracker::IsTokenFetchingRequired() const {
+  bool token_needed = false;
+  for (AccountInfos::const_iterator iter = account_infos_.begin();
+       iter != account_infos_.end();
+       ++iter) {
+    if (iter->second.state == TOKEN_NEEDED)
+      token_needed = true;
+  }
+
+  return token_needed;
+}
+
+base::TimeDelta GCMAccountTracker::GetTimeToNextTokenReporting() const {
+  base::TimeDelta time_till_next_reporting =
+      driver_->GetLastTokenFetchTime() +
+      base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs) -
+      base::Time::Now();
+  return time_till_next_reporting < base::TimeDelta() ?
+             base::TimeDelta() : time_till_next_reporting;
 }
 
 void GCMAccountTracker::DeleteTokenRequest(
@@ -235,6 +286,9 @@
 void GCMAccountTracker::GetAllNeededTokens() {
   // Only start fetching tokens if driver is running, they have a limited
   // validity time and GCM connection is a good indication of network running.
+  // If the GetAllNeededTokens was called as part of periodic schedule, it may
+  // not have network. In that case the next network change will trigger token
+  // fetching.
   if (!driver_->IsConnected())
     return;
 
@@ -282,7 +336,7 @@
 
   iter->second.access_token.clear();
   iter->second.state = ACCOUNT_REMOVED;
-  CompleteCollectingTokens();
+  ReportTokens();
 }
 
 OAuth2TokenService* GCMAccountTracker::GetTokenService() {