Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / services / gcm / gcm_account_tracker.cc
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.
4
5 #include "chrome/browser/services/gcm/gcm_account_tracker.h"
6
7 #include <algorithm>
8 #include <vector>
9
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"
14
15 namespace gcm {
16
17 namespace {
18
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.
29
30 }  // namespace
31
32 GCMAccountTracker::AccountInfo::AccountInfo(const std::string& email,
33                                             AccountState state)
34     : email(email), state(state) {
35 }
36
37 GCMAccountTracker::AccountInfo::~AccountInfo() {
38 }
39
40 GCMAccountTracker::GCMAccountTracker(
41     scoped_ptr<gaia::AccountTracker> account_tracker,
42     GCMDriver* driver)
43     : OAuth2TokenService::Consumer(kGCMAccountTrackerName),
44       account_tracker_(account_tracker.release()),
45       driver_(driver),
46       shutdown_called_(false),
47       reporting_weak_ptr_factory_(this) {
48 }
49
50 GCMAccountTracker::~GCMAccountTracker() {
51   DCHECK(shutdown_called_);
52 }
53
54 void GCMAccountTracker::Shutdown() {
55   shutdown_called_ = true;
56   driver_->RemoveConnectionObserver(this);
57   account_tracker_->RemoveObserver(this);
58   account_tracker_->Shutdown();
59 }
60
61 void GCMAccountTracker::Start() {
62   DCHECK(!shutdown_called_);
63   account_tracker_->AddObserver(this);
64   driver_->AddConnectionObserver(this);
65
66   std::vector<gaia::AccountIds> accounts = account_tracker_->GetAccounts();
67   for (std::vector<gaia::AccountIds>::const_iterator iter = accounts.begin();
68        iter != accounts.end();
69        ++iter) {
70     if (!iter->email.empty()) {
71       account_infos_.insert(std::make_pair(
72           iter->account_key, AccountInfo(iter->email, TOKEN_NEEDED)));
73     }
74   }
75
76   if (IsTokenReportingRequired())
77     ReportTokens();
78   else
79     ScheduleReportTokens();
80 }
81
82 void GCMAccountTracker::ScheduleReportTokens() {
83   DVLOG(1) << "Deferring the token reporting for: "
84            << GetTimeToNextTokenReporting().InSeconds() << " seconds.";
85
86   reporting_weak_ptr_factory_.InvalidateWeakPtrs();
87   base::MessageLoop::current()->PostDelayedTask(
88       FROM_HERE,
89       base::Bind(&GCMAccountTracker::ReportTokens,
90                  reporting_weak_ptr_factory_.GetWeakPtr()),
91       GetTimeToNextTokenReporting());
92 }
93
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.
97 }
98
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
102   // removed.
103 }
104
105 void GCMAccountTracker::OnAccountSignInChanged(const gaia::AccountIds& ids,
106                                                bool is_signed_in) {
107   if (is_signed_in)
108     OnAccountSignedIn(ids);
109   else
110     OnAccountSignedOut(ids);
111 }
112
113 void GCMAccountTracker::OnGetTokenSuccess(
114     const OAuth2TokenService::Request* request,
115     const std::string& access_token,
116     const base::Time& expiration_time) {
117   DCHECK(request);
118   DCHECK(!request->GetAccountId().empty());
119   DVLOG(1) << "Get token success: " << request->GetAccountId();
120
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;
132     }
133   }
134
135   DeleteTokenRequest(request);
136   ReportTokens();
137 }
138
139 void GCMAccountTracker::OnGetTokenFailure(
140     const OAuth2TokenService::Request* request,
141     const GoogleServiceAuthError& error) {
142   DCHECK(request);
143   DCHECK(!request->GetAccountId().empty());
144   DVLOG(1) << "Get token failure: " << request->GetAccountId();
145
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;
155   }
156
157   DeleteTokenRequest(request);
158   ReportTokens();
159 }
160
161 void GCMAccountTracker::OnConnected(const net::IPEndPoint& ip_endpoint) {
162   if (IsTokenReportingRequired())
163     ReportTokens();
164 }
165
166 void GCMAccountTracker::OnDisconnected() {
167   // We are disconnected, so no point in trying to work with tokens.
168 }
169
170 void GCMAccountTracker::ReportTokens() {
171   SanitizeTokens();
172   // Make sure all tokens are valid.
173   if (IsTokenFetchingRequired()) {
174     GetAllNeededTokens();
175     return;
176   }
177
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()) {
183     return;
184   }
185
186   bool account_removed = false;
187   // Stop tracking the accounts, that were removed, as it will be reported to
188   // the driver.
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++);
194     } else {
195       ++iter;
196     }
197   }
198
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);
208     } else {
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
211       // fetching tokens.
212       NOTREACHED();
213     }
214   }
215
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();
222   } else {
223     DVLOG(1) << "No tokens and nothing removed. Skipping callback.";
224   }
225 }
226
227 void GCMAccountTracker::SanitizeTokens() {
228   for (AccountInfos::iterator iter = account_infos_.begin();
229        iter != account_infos_.end();
230        ++iter) {
231     if (iter->second.state == TOKEN_PRESENT &&
232         iter->second.expiration_time <
233             base::Time::Now() +
234                 base::TimeDelta::FromMilliseconds(kMinimumTokenValidityMs)) {
235       iter->second.access_token.clear();
236       iter->second.state = TOKEN_NEEDED;
237       iter->second.expiration_time = base::Time();
238     }
239   }
240 }
241
242 bool GCMAccountTracker::IsTokenReportingRequired() const {
243   if (GetTimeToNextTokenReporting() == base::TimeDelta())
244     return true;
245
246   bool reporting_required = false;
247   for (AccountInfos::const_iterator iter = account_infos_.begin();
248        iter != account_infos_.end();
249        ++iter) {
250     if (iter->second.state == ACCOUNT_REMOVED)
251       reporting_required = true;
252   }
253
254   return reporting_required;
255 }
256
257 bool GCMAccountTracker::IsTokenFetchingRequired() const {
258   bool token_needed = false;
259   for (AccountInfos::const_iterator iter = account_infos_.begin();
260        iter != account_infos_.end();
261        ++iter) {
262     if (iter->second.state == TOKEN_NEEDED)
263       token_needed = true;
264   }
265
266   return token_needed;
267 }
268
269 base::TimeDelta GCMAccountTracker::GetTimeToNextTokenReporting() const {
270   base::TimeDelta time_till_next_reporting =
271       driver_->GetLastTokenFetchTime() +
272       base::TimeDelta::FromMilliseconds(kTokenReportingIntervalMs) -
273       base::Time::Now();
274   return time_till_next_reporting < base::TimeDelta() ?
275              base::TimeDelta() : time_till_next_reporting;
276 }
277
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);
284 }
285
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
291   // fetching.
292   if (!driver_->IsConnected())
293     return;
294
295   for (AccountInfos::iterator iter = account_infos_.begin();
296        iter != account_infos_.end();
297        ++iter) {
298     if (iter->second.state == TOKEN_NEEDED)
299       GetToken(iter);
300   }
301 }
302
303 void GCMAccountTracker::GetToken(AccountInfos::iterator& account_iter) {
304   DCHECK(GetTokenService());
305   DCHECK_EQ(account_iter->second.state, TOKEN_NEEDED);
306
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);
312
313   pending_token_requests_.push_back(request.release());
314   account_iter->second.state = GETTING_TOKEN;
315 }
316
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;
326   }
327
328   GetAllNeededTokens();
329 }
330
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())
335     return;
336
337   iter->second.access_token.clear();
338   iter->second.state = ACCOUNT_REMOVED;
339   ReportTokens();
340 }
341
342 OAuth2TokenService* GCMAccountTracker::GetTokenService() {
343   DCHECK(account_tracker_->identity_provider());
344   return account_tracker_->identity_provider()->GetTokenService();
345 }
346
347 }  // namespace gcm