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"
18 const char kGCMGroupServerScope
[] = "https://www.googleapis.com/auth/gcm";
19 const char kGCMCheckinServerScope
[] =
20 "https://www.googleapis.com/auth/android_checkin";
21 const char kGCMAccountTrackerName
[] = "gcm_account_tracker";
22 const int64 kMinimumTokenValidityMs
= 500;
25 GCMAccountTracker::AccountInfo::AccountInfo(const std::string
& email
,
27 : email(email
), state(state
) {
30 GCMAccountTracker::AccountInfo::~AccountInfo() {
33 GCMAccountTracker::GCMAccountTracker(
34 scoped_ptr
<gaia::AccountTracker
> account_tracker
,
36 : OAuth2TokenService::Consumer(kGCMAccountTrackerName
),
37 account_tracker_(account_tracker
.release()),
39 shutdown_called_(false) {
42 GCMAccountTracker::~GCMAccountTracker() {
43 DCHECK(shutdown_called_
);
46 void GCMAccountTracker::Shutdown() {
47 shutdown_called_
= true;
48 driver_
->RemoveConnectionObserver(this);
49 account_tracker_
->RemoveObserver(this);
50 account_tracker_
->Shutdown();
53 void GCMAccountTracker::Start() {
54 DCHECK(!shutdown_called_
);
55 account_tracker_
->AddObserver(this);
56 driver_
->AddConnectionObserver(this);
58 std::vector
<gaia::AccountIds
> accounts
= account_tracker_
->GetAccounts();
59 if (accounts
.empty()) {
60 CompleteCollectingTokens();
64 for (std::vector
<gaia::AccountIds
>::const_iterator iter
= accounts
.begin();
65 iter
!= accounts
.end();
67 if (!iter
->email
.empty()) {
68 account_infos_
.insert(std::make_pair(
69 iter
->account_key
, AccountInfo(iter
->email
, TOKEN_NEEDED
)));
76 void GCMAccountTracker::OnAccountAdded(const gaia::AccountIds
& ids
) {
77 DVLOG(1) << "Account added: " << ids
.email
;
78 // We listen for the account signing in, which happens after account is added.
81 void GCMAccountTracker::OnAccountRemoved(const gaia::AccountIds
& ids
) {
82 DVLOG(1) << "Account removed: " << ids
.email
;
83 // We listen for the account signing out, which happens before account is
87 void GCMAccountTracker::OnAccountSignInChanged(const gaia::AccountIds
& ids
,
90 OnAccountSignedIn(ids
);
92 OnAccountSignedOut(ids
);
95 void GCMAccountTracker::OnGetTokenSuccess(
96 const OAuth2TokenService::Request
* request
,
97 const std::string
& access_token
,
98 const base::Time
& expiration_time
) {
100 DCHECK(!request
->GetAccountId().empty());
101 DVLOG(1) << "Get token success: " << request
->GetAccountId();
103 AccountInfos::iterator iter
= account_infos_
.find(request
->GetAccountId());
104 DCHECK(iter
!= account_infos_
.end());
105 if (iter
!= account_infos_
.end()) {
106 DCHECK(iter
->second
.state
== GETTING_TOKEN
||
107 iter
->second
.state
== ACCOUNT_REMOVED
);
108 // If OnAccountSignedOut(..) was called most recently, account is kept in
109 // ACCOUNT_REMOVED state.
110 if (iter
->second
.state
== GETTING_TOKEN
) {
111 iter
->second
.state
= TOKEN_PRESENT
;
112 iter
->second
.access_token
= access_token
;
113 iter
->second
.expiration_time
= expiration_time
;
117 DeleteTokenRequest(request
);
118 CompleteCollectingTokens();
121 void GCMAccountTracker::OnGetTokenFailure(
122 const OAuth2TokenService::Request
* request
,
123 const GoogleServiceAuthError
& error
) {
125 DCHECK(!request
->GetAccountId().empty());
126 DVLOG(1) << "Get token failure: " << request
->GetAccountId();
128 AccountInfos::iterator iter
= account_infos_
.find(request
->GetAccountId());
129 DCHECK(iter
!= account_infos_
.end());
130 if (iter
!= account_infos_
.end()) {
131 DCHECK(iter
->second
.state
== GETTING_TOKEN
||
132 iter
->second
.state
== ACCOUNT_REMOVED
);
133 // If OnAccountSignedOut(..) was called most recently, account is kept in
134 // ACCOUNT_REMOVED state.
135 if (iter
->second
.state
== GETTING_TOKEN
)
136 iter
->second
.state
= TOKEN_NEEDED
;
139 DeleteTokenRequest(request
);
140 CompleteCollectingTokens();
143 void GCMAccountTracker::OnConnected(const net::IPEndPoint
& ip_endpoint
) {
144 if (SanitizeTokens())
145 GetAllNeededTokens();
148 void GCMAccountTracker::OnDisconnected() {
149 // We are disconnected, so no point in trying to work with tokens.
152 void GCMAccountTracker::CompleteCollectingTokens() {
153 // Make sure all tokens are valid.
154 if (SanitizeTokens()) {
155 GetAllNeededTokens();
159 // Wait for gaia::AccountTracker to be done with fetching the user info, as
160 // well as all of the pending token requests from GCMAccountTracker to be done
161 // before you report the results.
162 if (!account_tracker_
->IsAllUserInfoFetched() ||
163 !pending_token_requests_
.empty()) {
167 bool account_removed
= false;
168 // Stop tracking the accounts, that were removed, as it will be reported to
170 for (AccountInfos::iterator iter
= account_infos_
.begin();
171 iter
!= account_infos_
.end();) {
172 if (iter
->second
.state
== ACCOUNT_REMOVED
) {
173 account_removed
= true;
174 account_infos_
.erase(iter
++);
180 std::vector
<GCMClient::AccountTokenInfo
> account_tokens
;
181 for (AccountInfos::iterator iter
= account_infos_
.begin();
182 iter
!= account_infos_
.end(); ++iter
) {
183 if (iter
->second
.state
== TOKEN_PRESENT
) {
184 GCMClient::AccountTokenInfo token_info
;
185 token_info
.account_id
= iter
->first
;
186 token_info
.email
= iter
->second
.email
;
187 token_info
.access_token
= iter
->second
.access_token
;
188 account_tokens
.push_back(token_info
);
190 // This should not happen, as we are making a check that there are no
191 // pending requests above, stopping tracking of removed accounts, or start
197 // Make sure that there is something to report, otherwise bail out.
198 if (!account_tokens
.empty() || account_removed
) {
199 DVLOG(1) << "Reporting the tokens to driver: " << account_tokens
.size();
200 driver_
->SetAccountTokens(account_tokens
);
202 DVLOG(1) << "No tokens and nothing removed. Skipping callback.";
206 bool GCMAccountTracker::SanitizeTokens() {
207 bool tokens_needed
= false;
208 for (AccountInfos::iterator iter
= account_infos_
.begin();
209 iter
!= account_infos_
.end();
211 if (iter
->second
.state
== TOKEN_PRESENT
&&
212 iter
->second
.expiration_time
<
214 base::TimeDelta::FromMilliseconds(kMinimumTokenValidityMs
)) {
215 iter
->second
.access_token
.clear();
216 iter
->second
.state
= TOKEN_NEEDED
;
217 iter
->second
.expiration_time
= base::Time();
220 if (iter
->second
.state
== TOKEN_NEEDED
)
221 tokens_needed
= true;
224 return tokens_needed
;
227 void GCMAccountTracker::DeleteTokenRequest(
228 const OAuth2TokenService::Request
* request
) {
229 ScopedVector
<OAuth2TokenService::Request
>::iterator iter
= std::find(
230 pending_token_requests_
.begin(), pending_token_requests_
.end(), request
);
231 if (iter
!= pending_token_requests_
.end())
232 pending_token_requests_
.erase(iter
);
235 void GCMAccountTracker::GetAllNeededTokens() {
236 // Only start fetching tokens if driver is running, they have a limited
237 // validity time and GCM connection is a good indication of network running.
238 if (!driver_
->IsConnected())
241 for (AccountInfos::iterator iter
= account_infos_
.begin();
242 iter
!= account_infos_
.end();
244 if (iter
->second
.state
== TOKEN_NEEDED
)
249 void GCMAccountTracker::GetToken(AccountInfos::iterator
& account_iter
) {
250 DCHECK(GetTokenService());
251 DCHECK_EQ(account_iter
->second
.state
, TOKEN_NEEDED
);
253 OAuth2TokenService::ScopeSet scopes
;
254 scopes
.insert(kGCMGroupServerScope
);
255 scopes
.insert(kGCMCheckinServerScope
);
256 scoped_ptr
<OAuth2TokenService::Request
> request
=
257 GetTokenService()->StartRequest(account_iter
->first
, scopes
, this);
259 pending_token_requests_
.push_back(request
.release());
260 account_iter
->second
.state
= GETTING_TOKEN
;
263 void GCMAccountTracker::OnAccountSignedIn(const gaia::AccountIds
& ids
) {
264 DVLOG(1) << "Account signed in: " << ids
.email
;
265 AccountInfos::iterator iter
= account_infos_
.find(ids
.account_key
);
266 if (iter
== account_infos_
.end()) {
267 DCHECK(!ids
.email
.empty());
268 account_infos_
.insert(
269 std::make_pair(ids
.account_key
, AccountInfo(ids
.email
, TOKEN_NEEDED
)));
270 } else if (iter
->second
.state
== ACCOUNT_REMOVED
) {
271 iter
->second
.state
= TOKEN_NEEDED
;
274 GetAllNeededTokens();
277 void GCMAccountTracker::OnAccountSignedOut(const gaia::AccountIds
& ids
) {
278 DVLOG(1) << "Account signed out: " << ids
.email
;
279 AccountInfos::iterator iter
= account_infos_
.find(ids
.account_key
);
280 if (iter
== account_infos_
.end())
283 iter
->second
.access_token
.clear();
284 iter
->second
.state
= ACCOUNT_REMOVED
;
285 CompleteCollectingTokens();
288 OAuth2TokenService
* GCMAccountTracker::GetTokenService() {
289 DCHECK(account_tracker_
->identity_provider());
290 return account_tracker_
->identity_provider()->GetTokenService();