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"
10 #include "base/time/time.h"
11 #include "components/gcm_driver/gcm_driver.h"
12 #include "google_apis/gaia/google_service_auth_error.h"
13 #include "net/base/ip_endpoint.h"
19 // Scopes needed by the OAuth2 access tokens.
20 const char kGCMGroupServerScope
[] = "https://www.googleapis.com/auth/gcm";
21 const char kGCMCheckinServerScope
[] =
22 "https://www.googleapis.com/auth/android_checkin";
23 // Name of the GCM account tracker for the OAuth2TokenService.
24 const char kGCMAccountTrackerName
[] = "gcm_account_tracker";
25 // Minimum token validity when sending to GCM groups server.
26 const int64 kMinimumTokenValidityMs
= 500;
27 // Token reporting interval, when no account changes are detected.
28 const int64 kTokenReportingIntervalMs
= 12 * 60 * 60 * 1000; // 12 hours in ms.
32 GCMAccountTracker::AccountInfo::AccountInfo(const std::string
& email
,
34 : email(email
), state(state
) {
37 GCMAccountTracker::AccountInfo::~AccountInfo() {
40 GCMAccountTracker::GCMAccountTracker(
41 scoped_ptr
<gaia::AccountTracker
> account_tracker
,
43 : OAuth2TokenService::Consumer(kGCMAccountTrackerName
),
44 account_tracker_(account_tracker
.release()),
46 shutdown_called_(false),
47 reporting_weak_ptr_factory_(this) {
50 GCMAccountTracker::~GCMAccountTracker() {
51 DCHECK(shutdown_called_
);
54 void GCMAccountTracker::Shutdown() {
55 shutdown_called_
= true;
56 driver_
->RemoveConnectionObserver(this);
57 account_tracker_
->RemoveObserver(this);
58 account_tracker_
->Shutdown();
61 void GCMAccountTracker::Start() {
62 DCHECK(!shutdown_called_
);
63 account_tracker_
->AddObserver(this);
64 driver_
->AddConnectionObserver(this);
66 std::vector
<gaia::AccountIds
> accounts
= account_tracker_
->GetAccounts();
67 for (std::vector
<gaia::AccountIds
>::const_iterator iter
= accounts
.begin();
68 iter
!= accounts
.end();
70 if (!iter
->email
.empty()) {
71 account_infos_
.insert(std::make_pair(
72 iter
->account_key
, AccountInfo(iter
->email
, TOKEN_NEEDED
)));
76 if (IsTokenReportingRequired())
79 ScheduleReportTokens();
82 void GCMAccountTracker::ScheduleReportTokens() {
83 // Shortcutting here, in case GCM Driver is not yet connected. In that case
84 // reporting will be scheduled/started when the connection is made.
85 if (!driver_
->IsConnected())
88 DVLOG(1) << "Deferring the token reporting for: "
89 << GetTimeToNextTokenReporting().InSeconds() << " seconds.";
91 reporting_weak_ptr_factory_
.InvalidateWeakPtrs();
92 base::MessageLoop::current()->PostDelayedTask(
94 base::Bind(&GCMAccountTracker::ReportTokens
,
95 reporting_weak_ptr_factory_
.GetWeakPtr()),
96 GetTimeToNextTokenReporting());
99 void GCMAccountTracker::OnAccountAdded(const gaia::AccountIds
& ids
) {
100 DVLOG(1) << "Account added: " << ids
.email
;
101 // We listen for the account signing in, which happens after account is added.
104 void GCMAccountTracker::OnAccountRemoved(const gaia::AccountIds
& ids
) {
105 DVLOG(1) << "Account removed: " << ids
.email
;
106 // We listen for the account signing out, which happens before account is
110 void GCMAccountTracker::OnAccountSignInChanged(const gaia::AccountIds
& ids
,
113 OnAccountSignedIn(ids
);
115 OnAccountSignedOut(ids
);
118 void GCMAccountTracker::OnGetTokenSuccess(
119 const OAuth2TokenService::Request
* request
,
120 const std::string
& access_token
,
121 const base::Time
& expiration_time
) {
123 DCHECK(!request
->GetAccountId().empty());
124 DVLOG(1) << "Get token success: " << request
->GetAccountId();
126 AccountInfos::iterator iter
= account_infos_
.find(request
->GetAccountId());
127 DCHECK(iter
!= account_infos_
.end());
128 if (iter
!= account_infos_
.end()) {
129 DCHECK(iter
->second
.state
== GETTING_TOKEN
||
130 iter
->second
.state
== ACCOUNT_REMOVED
);
131 // If OnAccountSignedOut(..) was called most recently, account is kept in
132 // ACCOUNT_REMOVED state.
133 if (iter
->second
.state
== GETTING_TOKEN
) {
134 iter
->second
.state
= TOKEN_PRESENT
;
135 iter
->second
.access_token
= access_token
;
136 iter
->second
.expiration_time
= expiration_time
;
140 DeleteTokenRequest(request
);
144 void GCMAccountTracker::OnGetTokenFailure(
145 const OAuth2TokenService::Request
* request
,
146 const GoogleServiceAuthError
& error
) {
148 DCHECK(!request
->GetAccountId().empty());
149 DVLOG(1) << "Get token failure: " << request
->GetAccountId();
151 AccountInfos::iterator iter
= account_infos_
.find(request
->GetAccountId());
152 DCHECK(iter
!= account_infos_
.end());
153 if (iter
!= account_infos_
.end()) {
154 DCHECK(iter
->second
.state
== GETTING_TOKEN
||
155 iter
->second
.state
== ACCOUNT_REMOVED
);
156 // If OnAccountSignedOut(..) was called most recently, account is kept in
157 // ACCOUNT_REMOVED state.
158 if (iter
->second
.state
== GETTING_TOKEN
) {
159 // Given the fetcher has a built in retry logic, consider this situation
160 // to be invalid refresh token, that is only fixed when user signs in.
161 // Once the users signs in properly the minting will retry.
162 iter
->second
.access_token
.clear();
163 iter
->second
.state
= ACCOUNT_REMOVED
;
167 DeleteTokenRequest(request
);
171 void GCMAccountTracker::OnConnected(const net::IPEndPoint
& ip_endpoint
) {
172 // We are sure here, that GCM is running and connected. We can start reporting
173 // tokens if reporting is due now, or schedule reporting for later.
174 if (IsTokenReportingRequired())
177 ScheduleReportTokens();
180 void GCMAccountTracker::OnDisconnected() {
181 // We are disconnected, so no point in trying to work with tokens.
184 void GCMAccountTracker::ReportTokens() {
186 // Make sure all tokens are valid.
187 if (IsTokenFetchingRequired()) {
188 GetAllNeededTokens();
192 // Wait for gaia::AccountTracker to be done with fetching the user info, as
193 // well as all of the pending token requests from GCMAccountTracker to be done
194 // before you report the results.
195 if (!account_tracker_
->IsAllUserInfoFetched() ||
196 !pending_token_requests_
.empty()) {
200 bool account_removed
= false;
201 // Stop tracking the accounts, that were removed, as it will be reported to
203 for (AccountInfos::iterator iter
= account_infos_
.begin();
204 iter
!= account_infos_
.end();) {
205 if (iter
->second
.state
== ACCOUNT_REMOVED
) {
206 account_removed
= true;
207 account_infos_
.erase(iter
++);
213 std::vector
<GCMClient::AccountTokenInfo
> account_tokens
;
214 for (AccountInfos::iterator iter
= account_infos_
.begin();
215 iter
!= account_infos_
.end(); ++iter
) {
216 if (iter
->second
.state
== TOKEN_PRESENT
) {
217 GCMClient::AccountTokenInfo token_info
;
218 token_info
.account_id
= iter
->first
;
219 token_info
.email
= iter
->second
.email
;
220 token_info
.access_token
= iter
->second
.access_token
;
221 account_tokens
.push_back(token_info
);
223 // This should not happen, as we are making a check that there are no
224 // pending requests above, stopping tracking of removed accounts, or start
230 // Make sure that there is something to report, otherwise bail out.
231 if (!account_tokens
.empty() || account_removed
) {
232 DVLOG(1) << "Reporting the tokens to driver: " << account_tokens
.size();
233 driver_
->SetAccountTokens(account_tokens
);
234 driver_
->SetLastTokenFetchTime(base::Time::Now());
235 ScheduleReportTokens();
237 DVLOG(1) << "No tokens and nothing removed. Skipping callback.";
241 void GCMAccountTracker::SanitizeTokens() {
242 for (AccountInfos::iterator iter
= account_infos_
.begin();
243 iter
!= account_infos_
.end();
245 if (iter
->second
.state
== TOKEN_PRESENT
&&
246 iter
->second
.expiration_time
<
248 base::TimeDelta::FromMilliseconds(kMinimumTokenValidityMs
)) {
249 iter
->second
.access_token
.clear();
250 iter
->second
.state
= TOKEN_NEEDED
;
251 iter
->second
.expiration_time
= base::Time();
256 bool GCMAccountTracker::IsTokenReportingRequired() const {
257 if (GetTimeToNextTokenReporting() == base::TimeDelta())
260 bool reporting_required
= false;
261 for (AccountInfos::const_iterator iter
= account_infos_
.begin();
262 iter
!= account_infos_
.end();
264 if (iter
->second
.state
== ACCOUNT_REMOVED
)
265 reporting_required
= true;
268 return reporting_required
;
271 bool GCMAccountTracker::IsTokenFetchingRequired() const {
272 bool token_needed
= false;
273 for (AccountInfos::const_iterator iter
= account_infos_
.begin();
274 iter
!= account_infos_
.end();
276 if (iter
->second
.state
== TOKEN_NEEDED
)
283 base::TimeDelta
GCMAccountTracker::GetTimeToNextTokenReporting() const {
284 base::TimeDelta time_till_next_reporting
=
285 driver_
->GetLastTokenFetchTime() +
286 base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs
) -
289 // Case when token fetching is overdue.
290 if (time_till_next_reporting
< base::TimeDelta())
291 return base::TimeDelta();
293 // Case when calculated period is larger than expected, including the
294 // situation when the method is called before GCM driver is completely
296 if (time_till_next_reporting
>
297 base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs
)) {
298 return base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs
);
301 return time_till_next_reporting
;
304 void GCMAccountTracker::DeleteTokenRequest(
305 const OAuth2TokenService::Request
* request
) {
306 ScopedVector
<OAuth2TokenService::Request
>::iterator iter
= std::find(
307 pending_token_requests_
.begin(), pending_token_requests_
.end(), request
);
308 if (iter
!= pending_token_requests_
.end())
309 pending_token_requests_
.erase(iter
);
312 void GCMAccountTracker::GetAllNeededTokens() {
313 // Only start fetching tokens if driver is running, they have a limited
314 // validity time and GCM connection is a good indication of network running.
315 // If the GetAllNeededTokens was called as part of periodic schedule, it may
316 // not have network. In that case the next network change will trigger token
318 if (!driver_
->IsConnected())
321 for (AccountInfos::iterator iter
= account_infos_
.begin();
322 iter
!= account_infos_
.end();
324 if (iter
->second
.state
== TOKEN_NEEDED
)
329 void GCMAccountTracker::GetToken(AccountInfos::iterator
& account_iter
) {
330 DCHECK(GetTokenService());
331 DCHECK_EQ(account_iter
->second
.state
, TOKEN_NEEDED
);
333 OAuth2TokenService::ScopeSet scopes
;
334 scopes
.insert(kGCMGroupServerScope
);
335 scopes
.insert(kGCMCheckinServerScope
);
336 scoped_ptr
<OAuth2TokenService::Request
> request
=
337 GetTokenService()->StartRequest(account_iter
->first
, scopes
, this);
339 pending_token_requests_
.push_back(request
.release());
340 account_iter
->second
.state
= GETTING_TOKEN
;
343 void GCMAccountTracker::OnAccountSignedIn(const gaia::AccountIds
& ids
) {
344 DVLOG(1) << "Account signed in: " << ids
.email
;
345 AccountInfos::iterator iter
= account_infos_
.find(ids
.account_key
);
346 if (iter
== account_infos_
.end()) {
347 DCHECK(!ids
.email
.empty());
348 account_infos_
.insert(
349 std::make_pair(ids
.account_key
, AccountInfo(ids
.email
, TOKEN_NEEDED
)));
350 } else if (iter
->second
.state
== ACCOUNT_REMOVED
) {
351 iter
->second
.state
= TOKEN_NEEDED
;
354 GetAllNeededTokens();
357 void GCMAccountTracker::OnAccountSignedOut(const gaia::AccountIds
& ids
) {
358 DVLOG(1) << "Account signed out: " << ids
.email
;
359 AccountInfos::iterator iter
= account_infos_
.find(ids
.account_key
);
360 if (iter
== account_infos_
.end())
363 iter
->second
.access_token
.clear();
364 iter
->second
.state
= ACCOUNT_REMOVED
;
368 OAuth2TokenService
* GCMAccountTracker::GetTokenService() {
369 DCHECK(account_tracker_
->identity_provider());
370 return account_tracker_
->identity_provider()->GetTokenService();