blob: d2636ad0229144a8fe0e7d6f1276545fce208639 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/services/gcm/gcm_account_tracker.h"
#include <algorithm>
#include <vector>
#include "base/time/time.h"
#include "google_apis/gaia/google_service_auth_error.h"
namespace gcm {
namespace {
const char kGCMGroupServerScope[] = "https://www.googleapis.com/auth/gcm";
const char kGCMAccountTrackerName[] = "gcm_account_tracker";
} // namespace
GCMAccountTracker::AccountInfo::AccountInfo(const std::string& email,
AccountState state)
: email(email), state(state) {
}
GCMAccountTracker::AccountInfo::~AccountInfo() {
}
GCMAccountTracker::GCMAccountTracker(
scoped_ptr<gaia::AccountTracker> account_tracker,
const UpdateAccountsCallback& callback)
: OAuth2TokenService::Consumer(kGCMAccountTrackerName),
account_tracker_(account_tracker.release()),
callback_(callback),
shutdown_called_(false) {
DCHECK(!callback_.is_null());
}
GCMAccountTracker::~GCMAccountTracker() {
DCHECK(shutdown_called_);
}
void GCMAccountTracker::Shutdown() {
Stop();
shutdown_called_ = true;
account_tracker_->Shutdown();
}
void GCMAccountTracker::Start() {
DCHECK(!shutdown_called_);
account_tracker_->AddObserver(this);
std::vector<gaia::AccountIds> accounts = account_tracker_->GetAccounts();
if (accounts.empty()) {
CompleteCollectingTokens();
return;
}
for (std::vector<gaia::AccountIds>::const_iterator iter = accounts.begin();
iter != accounts.end();
++iter) {
if (!iter->email.empty()) {
account_infos_.insert(std::make_pair(
iter->account_key, AccountInfo(iter->email, TOKEN_NEEDED)));
}
}
GetAllNeededTokens();
}
void GCMAccountTracker::Stop() {
DCHECK(!shutdown_called_);
account_tracker_->RemoveObserver(this);
pending_token_requests_.clear();
}
void GCMAccountTracker::OnAccountAdded(const gaia::AccountIds& ids) {
DVLOG(1) << "Account added: " << ids.email;
// We listen for the account signing in, which happens after account is added.
}
void GCMAccountTracker::OnAccountRemoved(const gaia::AccountIds& ids) {
DVLOG(1) << "Account removed: " << ids.email;
// We listen for the account signing out, which happens before account is
// removed.
}
void GCMAccountTracker::OnAccountSignInChanged(const gaia::AccountIds& ids,
bool is_signed_in) {
if (is_signed_in)
OnAccountSignedIn(ids);
else
OnAccountSignedOut(ids);
}
void GCMAccountTracker::OnGetTokenSuccess(
const OAuth2TokenService::Request* request,
const std::string& access_token,
const base::Time& expiration_time) {
DCHECK(request);
DCHECK(!request->GetAccountId().empty());
DVLOG(1) << "Get token success: " << request->GetAccountId();
AccountInfos::iterator iter = account_infos_.find(request->GetAccountId());
DCHECK(iter != account_infos_.end());
if (iter != account_infos_.end()) {
DCHECK(iter->second.state == GETTING_TOKEN ||
iter->second.state == ACCOUNT_REMOVED);
// If OnAccountSignedOut(..) was called most recently, account is kept in
// ACCOUNT_REMOVED state.
if (iter->second.state == GETTING_TOKEN) {
iter->second.state = TOKEN_PRESENT;
iter->second.access_token = access_token;
}
}
DeleteTokenRequest(request);
CompleteCollectingTokens();
}
void GCMAccountTracker::OnGetTokenFailure(
const OAuth2TokenService::Request* request,
const GoogleServiceAuthError& error) {
DCHECK(request);
DCHECK(!request->GetAccountId().empty());
DVLOG(1) << "Get token failure: " << request->GetAccountId();
AccountInfos::iterator iter = account_infos_.find(request->GetAccountId());
DCHECK(iter != account_infos_.end());
if (iter != account_infos_.end()) {
DCHECK(iter->second.state == GETTING_TOKEN ||
iter->second.state == ACCOUNT_REMOVED);
// If OnAccountSignedOut(..) was called most recently, account is kept in
// ACCOUNT_REMOVED state.
if (iter->second.state == GETTING_TOKEN)
iter->second.state = TOKEN_NEEDED;
}
DeleteTokenRequest(request);
CompleteCollectingTokens();
}
void GCMAccountTracker::CompleteCollectingTokens() {
DCHECK(!callback_.is_null());
// Wait for gaia::AccountTracker to be done with fetching the user info, as
// well as all of the pending token requests from GCMAccountTracker to be done
// before you report the results.
if (!account_tracker_->IsAllUserInfoFetched() ||
!pending_token_requests_.empty()) {
return;
}
bool account_removed = false;
std::map<std::string, std::string> account_tokens;
for (AccountInfos::iterator iter = account_infos_.begin();
iter != account_infos_.end();) {
switch (iter->second.state) {
case ACCOUNT_REMOVED:
// We only mark accounts as removed when there was an account that was
// explicitly signed out.
account_removed = true;
// We also stop tracking the account, now that it will be reported as
// removed.
account_infos_.erase(iter++);
break;
case TOKEN_PRESENT:
account_tokens[iter->second.email] = iter->second.access_token;
++iter;
break;
case GETTING_TOKEN:
// This should not happen, as we are making a check that there are no
// pending requests above.
NOTREACHED();
++iter;
break;
case TOKEN_NEEDED:
// We failed to fetch an access token for the account, but it has not
// been signed out (perhaps there is a network issue). We don't report
// it, but next time there is a sign-in change we will update its state.
++iter;
break;
}
}
// Make sure that there is something to report, otherwise bail out.
if (!account_tokens.empty() || account_removed) {
DVLOG(1) << "Calling callback: " << account_tokens.size();
callback_.Run(account_tokens);
} else {
DVLOG(1) << "No tokens and nothing removed. Skipping callback.";
}
}
void GCMAccountTracker::DeleteTokenRequest(
const OAuth2TokenService::Request* request) {
ScopedVector<OAuth2TokenService::Request>::iterator iter = std::find(
pending_token_requests_.begin(), pending_token_requests_.end(), request);
if (iter != pending_token_requests_.end())
pending_token_requests_.erase(iter);
}
void GCMAccountTracker::GetAllNeededTokens() {
for (AccountInfos::iterator iter = account_infos_.begin();
iter != account_infos_.end();
++iter) {
if (iter->second.state == TOKEN_NEEDED)
GetToken(iter);
}
}
void GCMAccountTracker::GetToken(AccountInfos::iterator& account_iter) {
DCHECK(GetTokenService());
DCHECK_EQ(account_iter->second.state, TOKEN_NEEDED);
OAuth2TokenService::ScopeSet scopes;
scopes.insert(kGCMGroupServerScope);
scoped_ptr<OAuth2TokenService::Request> request =
GetTokenService()->StartRequest(account_iter->first, scopes, this);
pending_token_requests_.push_back(request.release());
account_iter->second.state = GETTING_TOKEN;
}
void GCMAccountTracker::OnAccountSignedIn(const gaia::AccountIds& ids) {
DVLOG(1) << "Account signed in: " << ids.email;
AccountInfos::iterator iter = account_infos_.find(ids.account_key);
if (iter == account_infos_.end()) {
DCHECK(!ids.email.empty());
account_infos_.insert(
std::make_pair(ids.account_key, AccountInfo(ids.email, TOKEN_NEEDED)));
} else if (iter->second.state == ACCOUNT_REMOVED) {
iter->second.state = TOKEN_NEEDED;
}
GetAllNeededTokens();
}
void GCMAccountTracker::OnAccountSignedOut(const gaia::AccountIds& ids) {
DVLOG(1) << "Account signed out: " << ids.email;
AccountInfos::iterator iter = account_infos_.find(ids.account_key);
if (iter == account_infos_.end())
return;
iter->second.access_token.clear();
iter->second.state = ACCOUNT_REMOVED;
CompleteCollectingTokens();
}
OAuth2TokenService* GCMAccountTracker::GetTokenService() {
DCHECK(account_tracker_->identity_provider());
return account_tracker_->identity_provider()->GetTokenService();
}
} // namespace gcm