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 DVLOG(1) << "Deferring the token reporting for: "
84 << GetTimeToNextTokenReporting().InSeconds() << " seconds.";
86 reporting_weak_ptr_factory_.InvalidateWeakPtrs();
87 base::MessageLoop::current()->PostDelayedTask(
89 base::Bind(&GCMAccountTracker::ReportTokens,
90 reporting_weak_ptr_factory_.GetWeakPtr()),
91 GetTimeToNextTokenReporting());
94 void GCMAccountTracker::OnAccountAdded(const gaia::AccountIds& ids) {
95 DVLOG(1) << "Account added: " << ids.email;
96 // We listen for the account signing in, which happens after account is added.
99 void GCMAccountTracker::OnAccountRemoved(const gaia::AccountIds& ids) {
100 DVLOG(1) << "Account removed: " << ids.email;
101 // We listen for the account signing out, which happens before account is
105 void GCMAccountTracker::OnAccountSignInChanged(const gaia::AccountIds& ids,
108 OnAccountSignedIn(ids);
110 OnAccountSignedOut(ids);
113 void GCMAccountTracker::OnGetTokenSuccess(
114 const OAuth2TokenService::Request* request,
115 const std::string& access_token,
116 const base::Time& expiration_time) {
118 DCHECK(!request->GetAccountId().empty());
119 DVLOG(1) << "Get token success: " << request->GetAccountId();
121 AccountInfos::iterator iter = account_infos_.find(request->GetAccountId());
122 DCHECK(iter != account_infos_.end());
123 if (iter != account_infos_.end()) {
124 DCHECK(iter->second.state == GETTING_TOKEN ||
125 iter->second.state == ACCOUNT_REMOVED);
126 // If OnAccountSignedOut(..) was called most recently, account is kept in
127 // ACCOUNT_REMOVED state.
128 if (iter->second.state == GETTING_TOKEN) {
129 iter->second.state = TOKEN_PRESENT;
130 iter->second.access_token = access_token;
131 iter->second.expiration_time = expiration_time;
135 DeleteTokenRequest(request);
139 void GCMAccountTracker::OnGetTokenFailure(
140 const OAuth2TokenService::Request* request,
141 const GoogleServiceAuthError& error) {
143 DCHECK(!request->GetAccountId().empty());
144 DVLOG(1) << "Get token failure: " << request->GetAccountId();
146 AccountInfos::iterator iter = account_infos_.find(request->GetAccountId());
147 DCHECK(iter != account_infos_.end());
148 if (iter != account_infos_.end()) {
149 DCHECK(iter->second.state == GETTING_TOKEN ||
150 iter->second.state == ACCOUNT_REMOVED);
151 // If OnAccountSignedOut(..) was called most recently, account is kept in
152 // ACCOUNT_REMOVED state.
153 if (iter->second.state == GETTING_TOKEN)
154 iter->second.state = TOKEN_NEEDED;
157 DeleteTokenRequest(request);
161 void GCMAccountTracker::OnConnected(const net::IPEndPoint& ip_endpoint) {
162 if (IsTokenReportingRequired())
166 void GCMAccountTracker::OnDisconnected() {
167 // We are disconnected, so no point in trying to work with tokens.
170 void GCMAccountTracker::ReportTokens() {
172 // Make sure all tokens are valid.
173 if (IsTokenFetchingRequired()) {
174 GetAllNeededTokens();
178 // Wait for gaia::AccountTracker to be done with fetching the user info, as
179 // well as all of the pending token requests from GCMAccountTracker to be done
180 // before you report the results.
181 if (!account_tracker_->IsAllUserInfoFetched() ||
182 !pending_token_requests_.empty()) {
186 bool account_removed = false;
187 // Stop tracking the accounts, that were removed, as it will be reported to
189 for (AccountInfos::iterator iter = account_infos_.begin();
190 iter != account_infos_.end();) {
191 if (iter->second.state == ACCOUNT_REMOVED) {
192 account_removed = true;
193 account_infos_.erase(iter++);
199 std::vector<GCMClient::AccountTokenInfo> account_tokens;
200 for (AccountInfos::iterator iter = account_infos_.begin();
201 iter != account_infos_.end(); ++iter) {
202 if (iter->second.state == TOKEN_PRESENT) {
203 GCMClient::AccountTokenInfo token_info;
204 token_info.account_id = iter->first;
205 token_info.email = iter->second.email;
206 token_info.access_token = iter->second.access_token;
207 account_tokens.push_back(token_info);
209 // This should not happen, as we are making a check that there are no
210 // pending requests above, stopping tracking of removed accounts, or start
216 // Make sure that there is something to report, otherwise bail out.
217 if (!account_tokens.empty() || account_removed) {
218 DVLOG(1) << "Reporting the tokens to driver: " << account_tokens.size();
219 driver_->SetAccountTokens(account_tokens);
220 driver_->SetLastTokenFetchTime(base::Time::Now());
221 ScheduleReportTokens();
223 DVLOG(1) << "No tokens and nothing removed. Skipping callback.";
227 void GCMAccountTracker::SanitizeTokens() {
228 for (AccountInfos::iterator iter = account_infos_.begin();
229 iter != account_infos_.end();
231 if (iter->second.state == TOKEN_PRESENT &&
232 iter->second.expiration_time <
234 base::TimeDelta::FromMilliseconds(kMinimumTokenValidityMs)) {
235 iter->second.access_token.clear();
236 iter->second.state = TOKEN_NEEDED;
237 iter->second.expiration_time = base::Time();
242 bool GCMAccountTracker::IsTokenReportingRequired() const {
243 if (GetTimeToNextTokenReporting() == base::TimeDelta())
246 bool reporting_required = false;
247 for (AccountInfos::const_iterator iter = account_infos_.begin();
248 iter != account_infos_.end();
250 if (iter->second.state == ACCOUNT_REMOVED)
251 reporting_required = true;
254 return reporting_required;
257 bool GCMAccountTracker::IsTokenFetchingRequired() const {
258 bool token_needed = false;
259 for (AccountInfos::const_iterator iter = account_infos_.begin();
260 iter != account_infos_.end();
262 if (iter->second.state == TOKEN_NEEDED)
269 base::TimeDelta GCMAccountTracker::GetTimeToNextTokenReporting() const {
270 base::TimeDelta time_till_next_reporting =
271 driver_->GetLastTokenFetchTime() +
272 base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs) -
274 return time_till_next_reporting < base::TimeDelta() ?
275 base::TimeDelta() : time_till_next_reporting;
278 void GCMAccountTracker::DeleteTokenRequest(
279 const OAuth2TokenService::Request* request) {
280 ScopedVector<OAuth2TokenService::Request>::iterator iter = std::find(
281 pending_token_requests_.begin(), pending_token_requests_.end(), request);
282 if (iter != pending_token_requests_.end())
283 pending_token_requests_.erase(iter);
286 void GCMAccountTracker::GetAllNeededTokens() {
287 // Only start fetching tokens if driver is running, they have a limited
288 // validity time and GCM connection is a good indication of network running.
289 // If the GetAllNeededTokens was called as part of periodic schedule, it may
290 // not have network. In that case the next network change will trigger token
292 if (!driver_->IsConnected())
295 for (AccountInfos::iterator iter = account_infos_.begin();
296 iter != account_infos_.end();
298 if (iter->second.state == TOKEN_NEEDED)
303 void GCMAccountTracker::GetToken(AccountInfos::iterator& account_iter) {
304 DCHECK(GetTokenService());
305 DCHECK_EQ(account_iter->second.state, TOKEN_NEEDED);
307 OAuth2TokenService::ScopeSet scopes;
308 scopes.insert(kGCMGroupServerScope);
309 scopes.insert(kGCMCheckinServerScope);
310 scoped_ptr<OAuth2TokenService::Request> request =
311 GetTokenService()->StartRequest(account_iter->first, scopes, this);
313 pending_token_requests_.push_back(request.release());
314 account_iter->second.state = GETTING_TOKEN;
317 void GCMAccountTracker::OnAccountSignedIn(const gaia::AccountIds& ids) {
318 DVLOG(1) << "Account signed in: " << ids.email;
319 AccountInfos::iterator iter = account_infos_.find(ids.account_key);
320 if (iter == account_infos_.end()) {
321 DCHECK(!ids.email.empty());
322 account_infos_.insert(
323 std::make_pair(ids.account_key, AccountInfo(ids.email, TOKEN_NEEDED)));
324 } else if (iter->second.state == ACCOUNT_REMOVED) {
325 iter->second.state = TOKEN_NEEDED;
328 GetAllNeededTokens();
331 void GCMAccountTracker::OnAccountSignedOut(const gaia::AccountIds& ids) {
332 DVLOG(1) << "Account signed out: " << ids.email;
333 AccountInfos::iterator iter = account_infos_.find(ids.account_key);
334 if (iter == account_infos_.end())
337 iter->second.access_token.clear();
338 iter->second.state = ACCOUNT_REMOVED;
342 OAuth2TokenService* GCMAccountTracker::GetTokenService() {
343 DCHECK(account_tracker_->identity_provider());
344 return account_tracker_->identity_provider()->GetTokenService();