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 "google_apis/gaia/google_service_auth_error.h"
16 const char kGCMGroupServerScope
[] = "https://www.googleapis.com/auth/gcm";
17 const char kGCMCheckinServerScope
[] =
18 "https://www.googleapis.com/auth/android_checkin";
19 const char kGCMAccountTrackerName
[] = "gcm_account_tracker";
22 GCMAccountTracker::AccountInfo::AccountInfo(const std::string
& email
,
24 : email(email
), state(state
) {
27 GCMAccountTracker::AccountInfo::~AccountInfo() {
30 GCMAccountTracker::GCMAccountTracker(
31 scoped_ptr
<gaia::AccountTracker
> account_tracker
,
32 const UpdateAccountsCallback
& callback
)
33 : OAuth2TokenService::Consumer(kGCMAccountTrackerName
),
34 account_tracker_(account_tracker
.release()),
36 shutdown_called_(false) {
37 DCHECK(!callback_
.is_null());
40 GCMAccountTracker::~GCMAccountTracker() {
41 DCHECK(shutdown_called_
);
44 void GCMAccountTracker::Shutdown() {
46 shutdown_called_
= true;
47 account_tracker_
->Shutdown();
50 void GCMAccountTracker::Start() {
51 DCHECK(!shutdown_called_
);
52 account_tracker_
->AddObserver(this);
54 std::vector
<gaia::AccountIds
> accounts
= account_tracker_
->GetAccounts();
55 if (accounts
.empty()) {
56 CompleteCollectingTokens();
60 for (std::vector
<gaia::AccountIds
>::const_iterator iter
= accounts
.begin();
61 iter
!= accounts
.end();
63 if (!iter
->email
.empty()) {
64 account_infos_
.insert(std::make_pair(
65 iter
->account_key
, AccountInfo(iter
->email
, TOKEN_NEEDED
)));
72 void GCMAccountTracker::Stop() {
73 DCHECK(!shutdown_called_
);
74 account_tracker_
->RemoveObserver(this);
75 pending_token_requests_
.clear();
78 void GCMAccountTracker::OnAccountAdded(const gaia::AccountIds
& ids
) {
79 DVLOG(1) << "Account added: " << ids
.email
;
80 // We listen for the account signing in, which happens after account is added.
83 void GCMAccountTracker::OnAccountRemoved(const gaia::AccountIds
& ids
) {
84 DVLOG(1) << "Account removed: " << ids
.email
;
85 // We listen for the account signing out, which happens before account is
89 void GCMAccountTracker::OnAccountSignInChanged(const gaia::AccountIds
& ids
,
92 OnAccountSignedIn(ids
);
94 OnAccountSignedOut(ids
);
97 void GCMAccountTracker::OnGetTokenSuccess(
98 const OAuth2TokenService::Request
* request
,
99 const std::string
& access_token
,
100 const base::Time
& expiration_time
) {
102 DCHECK(!request
->GetAccountId().empty());
103 DVLOG(1) << "Get token success: " << request
->GetAccountId();
105 AccountInfos::iterator iter
= account_infos_
.find(request
->GetAccountId());
106 DCHECK(iter
!= account_infos_
.end());
107 if (iter
!= account_infos_
.end()) {
108 DCHECK(iter
->second
.state
== GETTING_TOKEN
||
109 iter
->second
.state
== ACCOUNT_REMOVED
);
110 // If OnAccountSignedOut(..) was called most recently, account is kept in
111 // ACCOUNT_REMOVED state.
112 if (iter
->second
.state
== GETTING_TOKEN
) {
113 iter
->second
.state
= TOKEN_PRESENT
;
114 iter
->second
.access_token
= access_token
;
118 DeleteTokenRequest(request
);
119 CompleteCollectingTokens();
122 void GCMAccountTracker::OnGetTokenFailure(
123 const OAuth2TokenService::Request
* request
,
124 const GoogleServiceAuthError
& error
) {
126 DCHECK(!request
->GetAccountId().empty());
127 DVLOG(1) << "Get token failure: " << 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_NEEDED
;
140 DeleteTokenRequest(request
);
141 CompleteCollectingTokens();
144 void GCMAccountTracker::CompleteCollectingTokens() {
145 DCHECK(!callback_
.is_null());
146 // Wait for gaia::AccountTracker to be done with fetching the user info, as
147 // well as all of the pending token requests from GCMAccountTracker to be done
148 // before you report the results.
149 if (!account_tracker_
->IsAllUserInfoFetched() ||
150 !pending_token_requests_
.empty()) {
154 bool account_removed
= false;
155 std::map
<std::string
, std::string
> account_tokens
;
156 for (AccountInfos::iterator iter
= account_infos_
.begin();
157 iter
!= account_infos_
.end();) {
158 switch (iter
->second
.state
) {
159 case ACCOUNT_REMOVED
:
160 // We only mark accounts as removed when there was an account that was
161 // explicitly signed out.
162 account_removed
= true;
163 // We also stop tracking the account, now that it will be reported as
165 account_infos_
.erase(iter
++);
169 account_tokens
[iter
->second
.email
] = iter
->second
.access_token
;
174 // This should not happen, as we are making a check that there are no
175 // pending requests above.
181 // We failed to fetch an access token for the account, but it has not
182 // been signed out (perhaps there is a network issue). We don't report
183 // it, but next time there is a sign-in change we will update its state.
189 // Make sure that there is something to report, otherwise bail out.
190 if (!account_tokens
.empty() || account_removed
) {
191 DVLOG(1) << "Calling callback: " << account_tokens
.size();
192 callback_
.Run(account_tokens
);
194 DVLOG(1) << "No tokens and nothing removed. Skipping callback.";
198 void GCMAccountTracker::DeleteTokenRequest(
199 const OAuth2TokenService::Request
* request
) {
200 ScopedVector
<OAuth2TokenService::Request
>::iterator iter
= std::find(
201 pending_token_requests_
.begin(), pending_token_requests_
.end(), request
);
202 if (iter
!= pending_token_requests_
.end())
203 pending_token_requests_
.erase(iter
);
206 void GCMAccountTracker::GetAllNeededTokens() {
207 for (AccountInfos::iterator iter
= account_infos_
.begin();
208 iter
!= account_infos_
.end();
210 if (iter
->second
.state
== TOKEN_NEEDED
)
215 void GCMAccountTracker::GetToken(AccountInfos::iterator
& account_iter
) {
216 DCHECK(GetTokenService());
217 DCHECK_EQ(account_iter
->second
.state
, TOKEN_NEEDED
);
219 OAuth2TokenService::ScopeSet scopes
;
220 scopes
.insert(kGCMGroupServerScope
);
221 scopes
.insert(kGCMCheckinServerScope
);
222 scoped_ptr
<OAuth2TokenService::Request
> request
=
223 GetTokenService()->StartRequest(account_iter
->first
, scopes
, this);
225 pending_token_requests_
.push_back(request
.release());
226 account_iter
->second
.state
= GETTING_TOKEN
;
229 void GCMAccountTracker::OnAccountSignedIn(const gaia::AccountIds
& ids
) {
230 DVLOG(1) << "Account signed in: " << ids
.email
;
231 AccountInfos::iterator iter
= account_infos_
.find(ids
.account_key
);
232 if (iter
== account_infos_
.end()) {
233 DCHECK(!ids
.email
.empty());
234 account_infos_
.insert(
235 std::make_pair(ids
.account_key
, AccountInfo(ids
.email
, TOKEN_NEEDED
)));
236 } else if (iter
->second
.state
== ACCOUNT_REMOVED
) {
237 iter
->second
.state
= TOKEN_NEEDED
;
240 GetAllNeededTokens();
243 void GCMAccountTracker::OnAccountSignedOut(const gaia::AccountIds
& ids
) {
244 DVLOG(1) << "Account signed out: " << ids
.email
;
245 AccountInfos::iterator iter
= account_infos_
.find(ids
.account_key
);
246 if (iter
== account_infos_
.end())
249 iter
->second
.access_token
.clear();
250 iter
->second
.state
= ACCOUNT_REMOVED
;
251 CompleteCollectingTokens();
254 OAuth2TokenService
* GCMAccountTracker::GetTokenService() {
255 DCHECK(account_tracker_
->identity_provider());
256 return account_tracker_
->identity_provider()->GetTokenService();