Add ICU message format support
[chromium-blink-merge.git] / chrome / browser / services / gcm / gcm_account_tracker.cc
blobe9d5d0982cb4cef3cc309b43ec4e6bf70f78d723
1 // Copyright 2014 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.
5 #include "chrome/browser/services/gcm/gcm_account_tracker.h"
7 #include <algorithm>
8 #include <vector>
10 #include "base/bind.h"
11 #include "base/location.h"
12 #include "base/single_thread_task_runner.h"
13 #include "base/thread_task_runner_handle.h"
14 #include "base/time/time.h"
15 #include "components/gcm_driver/gcm_driver.h"
16 #include "google_apis/gaia/google_service_auth_error.h"
17 #include "net/base/ip_endpoint.h"
19 namespace gcm {
21 namespace {
23 // Scopes needed by the OAuth2 access tokens.
24 const char kGCMGroupServerScope[] = "https://www.googleapis.com/auth/gcm";
25 const char kGCMCheckinServerScope[] =
26 "https://www.googleapis.com/auth/android_checkin";
27 // Name of the GCM account tracker for the OAuth2TokenService.
28 const char kGCMAccountTrackerName[] = "gcm_account_tracker";
29 // Minimum token validity when sending to GCM groups server.
30 const int64 kMinimumTokenValidityMs = 500;
31 // Token reporting interval, when no account changes are detected.
32 const int64 kTokenReportingIntervalMs = 12 * 60 * 60 * 1000; // 12 hours in ms.
34 } // namespace
36 GCMAccountTracker::AccountInfo::AccountInfo(const std::string& email,
37 AccountState state)
38 : email(email), state(state) {
41 GCMAccountTracker::AccountInfo::~AccountInfo() {
44 GCMAccountTracker::GCMAccountTracker(
45 scoped_ptr<gaia::AccountTracker> account_tracker,
46 GCMDriver* driver)
47 : OAuth2TokenService::Consumer(kGCMAccountTrackerName),
48 account_tracker_(account_tracker.release()),
49 driver_(driver),
50 shutdown_called_(false),
51 reporting_weak_ptr_factory_(this) {
54 GCMAccountTracker::~GCMAccountTracker() {
55 DCHECK(shutdown_called_);
58 void GCMAccountTracker::Shutdown() {
59 shutdown_called_ = true;
60 driver_->RemoveConnectionObserver(this);
61 account_tracker_->RemoveObserver(this);
62 account_tracker_->Shutdown();
65 void GCMAccountTracker::Start() {
66 DCHECK(!shutdown_called_);
67 account_tracker_->AddObserver(this);
68 driver_->AddConnectionObserver(this);
70 std::vector<gaia::AccountIds> accounts = account_tracker_->GetAccounts();
71 for (std::vector<gaia::AccountIds>::const_iterator iter = accounts.begin();
72 iter != accounts.end();
73 ++iter) {
74 if (!iter->email.empty()) {
75 account_infos_.insert(std::make_pair(
76 iter->account_key, AccountInfo(iter->email, TOKEN_NEEDED)));
80 if (IsTokenReportingRequired())
81 ReportTokens();
82 else
83 ScheduleReportTokens();
86 void GCMAccountTracker::ScheduleReportTokens() {
87 // Shortcutting here, in case GCM Driver is not yet connected. In that case
88 // reporting will be scheduled/started when the connection is made.
89 if (!driver_->IsConnected())
90 return;
92 DVLOG(1) << "Deferring the token reporting for: "
93 << GetTimeToNextTokenReporting().InSeconds() << " seconds.";
95 reporting_weak_ptr_factory_.InvalidateWeakPtrs();
96 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
97 FROM_HERE, base::Bind(&GCMAccountTracker::ReportTokens,
98 reporting_weak_ptr_factory_.GetWeakPtr()),
99 GetTimeToNextTokenReporting());
102 void GCMAccountTracker::OnAccountAdded(const gaia::AccountIds& ids) {
103 DVLOG(1) << "Account added: " << ids.email;
104 // We listen for the account signing in, which happens after account is added.
107 void GCMAccountTracker::OnAccountRemoved(const gaia::AccountIds& ids) {
108 DVLOG(1) << "Account removed: " << ids.email;
109 // We listen for the account signing out, which happens before account is
110 // removed.
113 void GCMAccountTracker::OnAccountSignInChanged(const gaia::AccountIds& ids,
114 bool is_signed_in) {
115 if (is_signed_in)
116 OnAccountSignedIn(ids);
117 else
118 OnAccountSignedOut(ids);
121 void GCMAccountTracker::OnGetTokenSuccess(
122 const OAuth2TokenService::Request* request,
123 const std::string& access_token,
124 const base::Time& expiration_time) {
125 DCHECK(request);
126 DCHECK(!request->GetAccountId().empty());
127 DVLOG(1) << "Get token success: " << request->GetAccountId();
129 AccountInfos::iterator iter = account_infos_.find(request->GetAccountId());
130 DCHECK(iter != account_infos_.end());
131 if (iter != account_infos_.end()) {
132 DCHECK(iter->second.state == GETTING_TOKEN ||
133 iter->second.state == ACCOUNT_REMOVED);
134 // If OnAccountSignedOut(..) was called most recently, account is kept in
135 // ACCOUNT_REMOVED state.
136 if (iter->second.state == GETTING_TOKEN) {
137 iter->second.state = TOKEN_PRESENT;
138 iter->second.access_token = access_token;
139 iter->second.expiration_time = expiration_time;
143 DeleteTokenRequest(request);
144 ReportTokens();
147 void GCMAccountTracker::OnGetTokenFailure(
148 const OAuth2TokenService::Request* request,
149 const GoogleServiceAuthError& error) {
150 DCHECK(request);
151 DCHECK(!request->GetAccountId().empty());
152 DVLOG(1) << "Get token failure: " << request->GetAccountId();
154 AccountInfos::iterator iter = account_infos_.find(request->GetAccountId());
155 DCHECK(iter != account_infos_.end());
156 if (iter != account_infos_.end()) {
157 DCHECK(iter->second.state == GETTING_TOKEN ||
158 iter->second.state == ACCOUNT_REMOVED);
159 // If OnAccountSignedOut(..) was called most recently, account is kept in
160 // ACCOUNT_REMOVED state.
161 if (iter->second.state == GETTING_TOKEN) {
162 // Given the fetcher has a built in retry logic, consider this situation
163 // to be invalid refresh token, that is only fixed when user signs in.
164 // Once the users signs in properly the minting will retry.
165 iter->second.access_token.clear();
166 iter->second.state = ACCOUNT_REMOVED;
170 DeleteTokenRequest(request);
171 ReportTokens();
174 void GCMAccountTracker::OnConnected(const net::IPEndPoint& ip_endpoint) {
175 // We are sure here, that GCM is running and connected. We can start reporting
176 // tokens if reporting is due now, or schedule reporting for later.
177 if (IsTokenReportingRequired())
178 ReportTokens();
179 else
180 ScheduleReportTokens();
183 void GCMAccountTracker::OnDisconnected() {
184 // We are disconnected, so no point in trying to work with tokens.
187 void GCMAccountTracker::ReportTokens() {
188 SanitizeTokens();
189 // Make sure all tokens are valid.
190 if (IsTokenFetchingRequired()) {
191 GetAllNeededTokens();
192 return;
195 // Wait for gaia::AccountTracker to be done with fetching the user info, as
196 // well as all of the pending token requests from GCMAccountTracker to be done
197 // before you report the results.
198 if (!account_tracker_->IsAllUserInfoFetched() ||
199 !pending_token_requests_.empty()) {
200 return;
203 bool account_removed = false;
204 // Stop tracking the accounts, that were removed, as it will be reported to
205 // the driver.
206 for (AccountInfos::iterator iter = account_infos_.begin();
207 iter != account_infos_.end();) {
208 if (iter->second.state == ACCOUNT_REMOVED) {
209 account_removed = true;
210 account_infos_.erase(iter++);
211 } else {
212 ++iter;
216 std::vector<GCMClient::AccountTokenInfo> account_tokens;
217 for (AccountInfos::iterator iter = account_infos_.begin();
218 iter != account_infos_.end(); ++iter) {
219 if (iter->second.state == TOKEN_PRESENT) {
220 GCMClient::AccountTokenInfo token_info;
221 token_info.account_id = iter->first;
222 token_info.email = iter->second.email;
223 token_info.access_token = iter->second.access_token;
224 account_tokens.push_back(token_info);
225 } else {
226 // This should not happen, as we are making a check that there are no
227 // pending requests above, stopping tracking of removed accounts, or start
228 // fetching tokens.
229 NOTREACHED();
233 // Make sure that there is something to report, otherwise bail out.
234 if (!account_tokens.empty() || account_removed) {
235 DVLOG(1) << "Reporting the tokens to driver: " << account_tokens.size();
236 driver_->SetAccountTokens(account_tokens);
237 driver_->SetLastTokenFetchTime(base::Time::Now());
238 ScheduleReportTokens();
239 } else {
240 DVLOG(1) << "No tokens and nothing removed. Skipping callback.";
244 void GCMAccountTracker::SanitizeTokens() {
245 for (AccountInfos::iterator iter = account_infos_.begin();
246 iter != account_infos_.end();
247 ++iter) {
248 if (iter->second.state == TOKEN_PRESENT &&
249 iter->second.expiration_time <
250 base::Time::Now() +
251 base::TimeDelta::FromMilliseconds(kMinimumTokenValidityMs)) {
252 iter->second.access_token.clear();
253 iter->second.state = TOKEN_NEEDED;
254 iter->second.expiration_time = base::Time();
259 bool GCMAccountTracker::IsTokenReportingRequired() const {
260 if (GetTimeToNextTokenReporting() == base::TimeDelta())
261 return true;
263 bool reporting_required = false;
264 for (AccountInfos::const_iterator iter = account_infos_.begin();
265 iter != account_infos_.end();
266 ++iter) {
267 if (iter->second.state == ACCOUNT_REMOVED)
268 reporting_required = true;
271 return reporting_required;
274 bool GCMAccountTracker::IsTokenFetchingRequired() const {
275 bool token_needed = false;
276 for (AccountInfos::const_iterator iter = account_infos_.begin();
277 iter != account_infos_.end();
278 ++iter) {
279 if (iter->second.state == TOKEN_NEEDED)
280 token_needed = true;
283 return token_needed;
286 base::TimeDelta GCMAccountTracker::GetTimeToNextTokenReporting() const {
287 base::TimeDelta time_till_next_reporting =
288 driver_->GetLastTokenFetchTime() +
289 base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs) -
290 base::Time::Now();
292 // Case when token fetching is overdue.
293 if (time_till_next_reporting < base::TimeDelta())
294 return base::TimeDelta();
296 // Case when calculated period is larger than expected, including the
297 // situation when the method is called before GCM driver is completely
298 // initialized.
299 if (time_till_next_reporting >
300 base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs)) {
301 return base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs);
304 return time_till_next_reporting;
307 void GCMAccountTracker::DeleteTokenRequest(
308 const OAuth2TokenService::Request* request) {
309 ScopedVector<OAuth2TokenService::Request>::iterator iter = std::find(
310 pending_token_requests_.begin(), pending_token_requests_.end(), request);
311 if (iter != pending_token_requests_.end())
312 pending_token_requests_.erase(iter);
315 void GCMAccountTracker::GetAllNeededTokens() {
316 // Only start fetching tokens if driver is running, they have a limited
317 // validity time and GCM connection is a good indication of network running.
318 // If the GetAllNeededTokens was called as part of periodic schedule, it may
319 // not have network. In that case the next network change will trigger token
320 // fetching.
321 if (!driver_->IsConnected())
322 return;
324 for (AccountInfos::iterator iter = account_infos_.begin();
325 iter != account_infos_.end();
326 ++iter) {
327 if (iter->second.state == TOKEN_NEEDED)
328 GetToken(iter);
332 void GCMAccountTracker::GetToken(AccountInfos::iterator& account_iter) {
333 DCHECK(GetTokenService());
334 DCHECK_EQ(account_iter->second.state, TOKEN_NEEDED);
336 OAuth2TokenService::ScopeSet scopes;
337 scopes.insert(kGCMGroupServerScope);
338 scopes.insert(kGCMCheckinServerScope);
339 scoped_ptr<OAuth2TokenService::Request> request =
340 GetTokenService()->StartRequest(account_iter->first, scopes, this);
342 pending_token_requests_.push_back(request.release());
343 account_iter->second.state = GETTING_TOKEN;
346 void GCMAccountTracker::OnAccountSignedIn(const gaia::AccountIds& ids) {
347 DVLOG(1) << "Account signed in: " << ids.email;
348 AccountInfos::iterator iter = account_infos_.find(ids.account_key);
349 if (iter == account_infos_.end()) {
350 DCHECK(!ids.email.empty());
351 account_infos_.insert(
352 std::make_pair(ids.account_key, AccountInfo(ids.email, TOKEN_NEEDED)));
353 } else if (iter->second.state == ACCOUNT_REMOVED) {
354 iter->second.state = TOKEN_NEEDED;
357 GetAllNeededTokens();
360 void GCMAccountTracker::OnAccountSignedOut(const gaia::AccountIds& ids) {
361 DVLOG(1) << "Account signed out: " << ids.email;
362 AccountInfos::iterator iter = account_infos_.find(ids.account_key);
363 if (iter == account_infos_.end())
364 return;
366 iter->second.access_token.clear();
367 iter->second.state = ACCOUNT_REMOVED;
368 ReportTokens();
371 OAuth2TokenService* GCMAccountTracker::GetTokenService() {
372 DCHECK(account_tracker_->identity_provider());
373 return account_tracker_->identity_provider()->GetTokenService();
376 } // namespace gcm