diff --git a/chrome/browser/services/gcm/gcm_account_tracker.cc b/chrome/browser/services/gcm/gcm_account_tracker.cc new file mode 100644 index 00000000000000..00bb15053bfc56 --- /dev/null +++ b/chrome/browser/services/gcm/gcm_account_tracker.cc @@ -0,0 +1,245 @@ +// 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 +#include + +#include "base/time/time.h" +#include "google_apis/gaia/google_service_auth_error.h" + +namespace gcm { + +namespace { +const char kGCMGroupServerScope[] = + "oauth2: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 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 accounts = account_tracker_->GetAccounts(); + if (accounts.empty()) { + CompleteCollectingTokens(); + return; + } + + for (std::vector::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) { + // We listen for the account signing in, which happens after account is added. +} + +void GCMAccountTracker::OnAccountRemoved(const gaia::AccountIds& ids) { + // 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()); + + 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()); + + 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 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; + } + } + + callback_.Run(account_tokens, account_removed); +} + +void GCMAccountTracker::DeleteTokenRequest( + const OAuth2TokenService::Request* request) { + ScopedVector::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 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) { + 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) { + 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 diff --git a/chrome/browser/services/gcm/gcm_account_tracker.h b/chrome/browser/services/gcm/gcm_account_tracker.h new file mode 100644 index 00000000000000..9093ba73c122f8 --- /dev/null +++ b/chrome/browser/services/gcm/gcm_account_tracker.h @@ -0,0 +1,135 @@ +// 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. + +#ifndef CHROME_BROWSER_SERVICES_GCM_GCM_ACCOUNT_TRACKER_H_ +#define CHROME_BROWSER_SERVICES_GCM_GCM_ACCOUNT_TRACKER_H_ + +#include +#include + +#include "base/memory/scoped_vector.h" +#include "google_apis/gaia/account_tracker.h" +#include "google_apis/gaia/oauth2_token_service.h" + +namespace gcm { + +// Class for reporting back which accounts are signed into. It is only meant to +// be used when the user is signed into sync. +class GCMAccountTracker : public gaia::AccountTracker::Observer, + public OAuth2TokenService::Consumer { + public: + // State of the account. + // Allowed transitions: + // TOKEN_NEEDED - account info was created. + // TOKEN_NEEDED -> GETTING_TOKEN - access token was requested. + // GETTING_TOKEN -> TOKEN_NEEDED - access token fetching failed. + // GETTING_TOKEN -> TOKEN_PRESENT - access token fetching succeeded. + // GETTING_TOKEN -> ACCOUNT_REMOVED - account was removed. + // TOKEN_NEEDED -> ACCOUNT_REMOVED - account was removed. + // TOKEN_PRESENT -> ACCOUNT_REMOVED - account was removed. + enum AccountState { + TOKEN_NEEDED, // Needs a token (AccountInfo was recently created or + // token request failed). + GETTING_TOKEN, // There is a pending token request. + TOKEN_PRESENT, // We have a token for the account. + ACCOUNT_REMOVED, // Account was removed, and we didn't report it yet. + }; + + // Stores necessary account information and state of token fetching. + struct AccountInfo { + AccountInfo(const std::string& email, AccountState state); + ~AccountInfo(); + + // Email address of the tracked account. + std::string email; + // OAuth2 access token, when |state| is TOKEN_PRESENT. + std::string access_token; + // Status of the token fetching. + AccountState state; + }; + + // Callback for the GetAccountsForCheckin call. |account_tokens| maps email + // addresses to OAuth2 access tokens. |account_removed| indicates whether an + // account has been removed since the last time the callback was called. + typedef base::Callback< + void(const std::map& account_tokens, + bool account_removed)> UpdateAccountsCallback; + + // Creates an instance of GCMAccountTracker. |account_tracker| is used to + // deliver information about the account, while |callback| will be called + // once all of the accounts have been fetched a necessary OAuth2 token, as + // many times as the list of accounts is stable, meaning that all accounts + // are known and there is no related activity in progress for them, like + // fetching OAuth2 tokens. + GCMAccountTracker(scoped_ptr account_tracker, + const UpdateAccountsCallback& callback); + virtual ~GCMAccountTracker(); + + // Shuts down the tracker ensuring a proper clean up. After Shutdown() is + // called Start() and Stop() should no longer be used. Must be called before + // destruction. + void Shutdown(); + + // Starts tracking accounts. + void Start(); + // Stops tracking accounts. Cancels all of the pending token requests. + void Stop(); + + private: + // Maps account keys to account states. Keyed by account_ids as used by + // OAuth2TokenService. + typedef std::map AccountInfos; + + // AccountTracker::Observer overrides. + virtual void OnAccountAdded(const gaia::AccountIds& ids) OVERRIDE; + virtual void OnAccountRemoved(const gaia::AccountIds& ids) OVERRIDE; + virtual void OnAccountSignInChanged(const gaia::AccountIds& ids, + bool is_signed_in) OVERRIDE; + + // OAuth2TokenService::Consumer overrides. + virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request, + const std::string& access_token, + const base::Time& expiration_time) OVERRIDE; + virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request, + const GoogleServiceAuthError& error) OVERRIDE; + + // Report the list of accounts with OAuth2 tokens back using the |callback_| + // function. If there are token requests in progress, do nothing. + void CompleteCollectingTokens(); + // Deletes a token request. Should be called from OnGetTokenSuccess(..) or + // OnGetTokenFailure(..). + void DeleteTokenRequest(const OAuth2TokenService::Request* request); + // Checks on all known accounts, and calls GetToken(..) for those with + // |state == TOKEN_NEEDED|. + void GetAllNeededTokens(); + // Starts fetching the OAuth2 token for the GCM group scope. + void GetToken(AccountInfos::iterator& account_iter); + + // Handling of actual sign in and sign out for accounts. + void OnAccountSignedIn(const gaia::AccountIds& ids); + void OnAccountSignedOut(const gaia::AccountIds& ids); + + OAuth2TokenService* GetTokenService(); + + // Account tracker. + scoped_ptr account_tracker_; + + // Callback to be called after all of the account and OAuth2 tokens are + // collected. + UpdateAccountsCallback callback_; + + // State of the account. + AccountInfos account_infos_; + + // Indicates whether shutdown has been called. + bool shutdown_called_; + + ScopedVector pending_token_requests_; + + DISALLOW_COPY_AND_ASSIGN(GCMAccountTracker); +}; + +} // namespace gcm + +#endif // CHROME_BROWSER_SERVICES_GCM_GCM_ACCOUNT_TRACKER_H_ diff --git a/chrome/browser/services/gcm/gcm_account_tracker_unittest.cc b/chrome/browser/services/gcm/gcm_account_tracker_unittest.cc new file mode 100644 index 00000000000000..24c3ab3fc46cf0 --- /dev/null +++ b/chrome/browser/services/gcm/gcm_account_tracker_unittest.cc @@ -0,0 +1,322 @@ +// 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 +#include + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "google_apis/gaia/fake_identity_provider.h" +#include "google_apis/gaia/fake_oauth2_token_service.h" +#include "google_apis/gaia/google_service_auth_error.h" +#include "net/http/http_status_code.h" +#include "net/url_request/test_url_fetcher_factory.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gcm { + +namespace { + +const char kAccountId1[] = "account_1"; +const char kAccountId2[] = "account_2"; + +std::string AccountKeyToObfuscatedId(const std::string email) { + return "obfid-" + email; +} + +std::string GetValidTokenInfoResponse(const std::string account_key) { + return std::string("{ \"id\": \"") + AccountKeyToObfuscatedId(account_key) + + "\" }"; +} + +std::string MakeAccessToken(const std::string& account_key) { + return "access_token-" + account_key; +} + +} // namespace + +class GCMAccountTrackerTest : public testing::Test { + public: + GCMAccountTrackerTest(); + virtual ~GCMAccountTrackerTest(); + + // Callback for the account tracker. + void UpdateAccounts(const std::map& accounts, + bool account_removed); + + // Helpers to pass fake events to the tracker. Tests should have either a pair + // of Start/FinishAccountSignIn or SignInAccount per account. Don't mix. + // Call to SignOutAccount is not mandatory. + void StartAccountSignIn(const std::string& account_key); + void FinishAccountSignIn(const std::string& account_key); + void SignInAccount(const std::string& account_key); + void SignOutAccount(const std::string& account_key); + + // Helpers for dealing with OAuth2 access token requests. + void IssueAccessToken(const std::string& account_key); + void IssueError(const std::string& account_key); + + // Test results and helpers. + void ResetResults(); + bool update_accounts_called() const { return update_accounts_called_; } + bool account_removed() const { return account_removed_; } + const std::map& accounts() const { + return accounts_; + } + + // Accessor to account tracker. + GCMAccountTracker* tracker() { return tracker_.get(); } + + private: + std::map accounts_; + bool update_accounts_called_; + bool account_removed_; + + base::MessageLoop message_loop_; + net::TestURLFetcherFactory test_fetcher_factory_; + scoped_ptr fake_token_service_; + scoped_ptr fake_identity_provider_; + scoped_ptr tracker_; +}; + +GCMAccountTrackerTest::GCMAccountTrackerTest() + : update_accounts_called_(false), account_removed_(false) { + fake_token_service_.reset(new FakeOAuth2TokenService()); + + fake_identity_provider_.reset( + new FakeIdentityProvider(fake_token_service_.get())); + + scoped_ptr gaia_account_tracker( + new gaia::AccountTracker(fake_identity_provider_.get(), + new net::TestURLRequestContextGetter( + message_loop_.message_loop_proxy()))); + + tracker_.reset(new GCMAccountTracker( + gaia_account_tracker.Pass(), + base::Bind(&GCMAccountTrackerTest::UpdateAccounts, + base::Unretained(this)))); +} + +GCMAccountTrackerTest::~GCMAccountTrackerTest() { + if (tracker_) + tracker_->Shutdown(); +} + +void GCMAccountTrackerTest::UpdateAccounts( + const std::map& accounts, + bool account_removed) { + update_accounts_called_ = true; + accounts_ = accounts; + account_removed_ = account_removed; +} + +void GCMAccountTrackerTest::ResetResults() { + accounts_.clear(); + update_accounts_called_ = false; + account_removed_ = false; +} + +void GCMAccountTrackerTest::StartAccountSignIn(const std::string& account_key) { + fake_identity_provider_->LogIn(account_key); + fake_token_service_->AddAccount(account_key); +} + +void GCMAccountTrackerTest::FinishAccountSignIn( + const std::string& account_key) { + IssueAccessToken(account_key); + + net::TestURLFetcher* fetcher = test_fetcher_factory_.GetFetcherByID( + gaia::GaiaOAuthClient::kUrlFetcherId); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(GetValidTokenInfoResponse(account_key)); + fetcher->delegate()->OnURLFetchComplete(fetcher); +} + +void GCMAccountTrackerTest::SignInAccount(const std::string& account_key) { + StartAccountSignIn(account_key); + FinishAccountSignIn(account_key); +} + +void GCMAccountTrackerTest::SignOutAccount(const std::string& account_key) { + fake_token_service_->RemoveAccount(account_key); +} + +void GCMAccountTrackerTest::IssueAccessToken(const std::string& account_key) { + fake_token_service_->IssueAllTokensForAccount( + account_key, MakeAccessToken(account_key), base::Time::Max()); +} + +void GCMAccountTrackerTest::IssueError(const std::string& account_key) { + fake_token_service_->IssueErrorForAllPendingRequestsForAccount( + account_key, + GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE)); +} + +TEST_F(GCMAccountTrackerTest, NoAccounts) { + EXPECT_FALSE(update_accounts_called()); + EXPECT_FALSE(account_removed()); + tracker()->Start(); + EXPECT_TRUE(update_accounts_called()); + EXPECT_FALSE(account_removed()); + EXPECT_TRUE(accounts().empty()); + tracker()->Stop(); +} + +// Verifies that callback is called after a token is issued for a single account +// with a specific scope. In this scenario, the underlying account tracker is +// still working when the CompleteCollectingTokens is called for the first time. +TEST_F(GCMAccountTrackerTest, SingleAccount) { + StartAccountSignIn(kAccountId1); + + tracker()->Start(); + // We don't have any accounts to report, but given the inner account tracker + // is still working we don't make a call with empty accounts list. + EXPECT_FALSE(update_accounts_called()); + + // This concludes the work of inner account tracker. + FinishAccountSignIn(kAccountId1); + IssueAccessToken(kAccountId1); + + EXPECT_TRUE(update_accounts_called()); + EXPECT_FALSE(account_removed()); + + std::map expected_accounts; + expected_accounts[kAccountId1] = MakeAccessToken(kAccountId1); + EXPECT_EQ(expected_accounts, accounts()); + tracker()->Stop(); +} + +TEST_F(GCMAccountTrackerTest, MultipleAccounts) { + StartAccountSignIn(kAccountId1); + StartAccountSignIn(kAccountId2); + + tracker()->Start(); + EXPECT_FALSE(update_accounts_called()); + + FinishAccountSignIn(kAccountId1); + IssueAccessToken(kAccountId1); + EXPECT_FALSE(update_accounts_called()); + EXPECT_FALSE(account_removed()); + + FinishAccountSignIn(kAccountId2); + IssueAccessToken(kAccountId2); + EXPECT_TRUE(update_accounts_called()); + EXPECT_FALSE(account_removed()); + + std::map expected_accounts; + expected_accounts[kAccountId1] = MakeAccessToken(kAccountId1); + expected_accounts[kAccountId2] = MakeAccessToken(kAccountId2); + EXPECT_EQ(expected_accounts, accounts()); + + tracker()->Stop(); +} + +TEST_F(GCMAccountTrackerTest, AccountAdded) { + tracker()->Start(); + ResetResults(); + + SignInAccount(kAccountId1); + EXPECT_FALSE(update_accounts_called()); + + IssueAccessToken(kAccountId1); + EXPECT_TRUE(update_accounts_called()); + EXPECT_FALSE(account_removed()); + + std::map expected_accounts; + expected_accounts[kAccountId1] = MakeAccessToken(kAccountId1); + EXPECT_EQ(expected_accounts, accounts()); + + tracker()->Stop(); +} + +TEST_F(GCMAccountTrackerTest, AccountRemoved) { + SignInAccount(kAccountId1); + SignInAccount(kAccountId2); + + tracker()->Start(); + IssueAccessToken(kAccountId1); + IssueAccessToken(kAccountId2); + EXPECT_TRUE(update_accounts_called()); + + ResetResults(); + EXPECT_FALSE(update_accounts_called()); + + SignOutAccount(kAccountId2); + EXPECT_TRUE(update_accounts_called()); + EXPECT_TRUE(account_removed()); + + std::map expected_accounts; + expected_accounts[kAccountId1] = MakeAccessToken(kAccountId1); + EXPECT_EQ(expected_accounts, accounts()); + + tracker()->Stop(); +} + +TEST_F(GCMAccountTrackerTest, GetTokenFailed) { + SignInAccount(kAccountId1); + SignInAccount(kAccountId2); + + tracker()->Start(); + IssueAccessToken(kAccountId1); + EXPECT_FALSE(update_accounts_called()); + + IssueError(kAccountId2); + EXPECT_TRUE(update_accounts_called()); + EXPECT_FALSE(account_removed()); + + std::map expected_accounts; + expected_accounts[kAccountId1] = MakeAccessToken(kAccountId1); + EXPECT_EQ(expected_accounts, accounts()); + + tracker()->Stop(); +} + +TEST_F(GCMAccountTrackerTest, GetTokenFailedAccountRemoved) { + SignInAccount(kAccountId1); + SignInAccount(kAccountId2); + + tracker()->Start(); + IssueAccessToken(kAccountId1); + IssueError(kAccountId2); + + ResetResults(); + SignOutAccount(kAccountId2); + EXPECT_TRUE(update_accounts_called()); + EXPECT_TRUE(account_removed()); + + std::map expected_accounts; + expected_accounts[kAccountId1] = MakeAccessToken(kAccountId1); + EXPECT_EQ(expected_accounts, accounts()); + + tracker()->Stop(); +} + +TEST_F(GCMAccountTrackerTest, AccountRemovedWhileRequestsPending) { + SignInAccount(kAccountId1); + SignInAccount(kAccountId2); + + tracker()->Start(); + IssueAccessToken(kAccountId1); + EXPECT_FALSE(update_accounts_called()); + + SignOutAccount(kAccountId2); + IssueAccessToken(kAccountId2); + EXPECT_TRUE(update_accounts_called()); + EXPECT_TRUE(account_removed()); + + std::map expected_accounts; + expected_accounts[kAccountId1] = MakeAccessToken(kAccountId1); + EXPECT_EQ(expected_accounts, accounts()); + + tracker()->Stop(); +} + +// TODO(fgorski): Add test for adding account after removal >> make sure it does +// not mark removal. + +} // namespace gcm diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 107e390fd75cf0..678786055c6b18 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1233,6 +1233,8 @@ 'browser/search_engines/ui_thread_search_terms_data_android.h', 'browser/search_engines/util.cc', 'browser/search_engines/util.h', + 'browser/services/gcm/gcm_account_tracker.cc', + 'browser/services/gcm/gcm_account_tracker.h', 'browser/services/gcm/gcm_profile_service.cc', 'browser/services/gcm/gcm_profile_service.h', 'browser/services/gcm/gcm_profile_service_factory.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index c87d6bcd4d991e..b0878e6bd8e9c7 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -1293,6 +1293,7 @@ 'browser/search_engines/template_url_unittest.cc', 'browser/services/gcm/fake_signin_manager.cc', 'browser/services/gcm/fake_signin_manager.h', + 'browser/services/gcm/gcm_account_tracker_unittest.cc', 'browser/services/gcm/gcm_profile_service_unittest.cc', 'browser/sessions/persistent_tab_restore_service_unittest.cc', 'browser/sessions/restore_on_startup_policy_handler_unittest.cc', diff --git a/google_apis/gaia/account_tracker.cc b/google_apis/gaia/account_tracker.cc index 65d28369be97d7..1b832aaf09d3c5 100644 --- a/google_apis/gaia/account_tracker.cc +++ b/google_apis/gaia/account_tracker.cc @@ -31,6 +31,10 @@ void AccountTracker::Shutdown() { identity_provider_->RemoveObserver(this); } +bool AccountTracker::IsAllUserInfoFetched() const { + return user_info_requests_.empty(); +} + void AccountTracker::AddObserver(Observer* observer) { observer_list_.AddObserver(observer); } diff --git a/google_apis/gaia/account_tracker.h b/google_apis/gaia/account_tracker.h index 5a29ea624915cc..01546b3b268ecf 100644 --- a/google_apis/gaia/account_tracker.h +++ b/google_apis/gaia/account_tracker.h @@ -83,6 +83,10 @@ class AccountTracker : public OAuth2TokenService::Observer, IdentityProvider* identity_provider() { return identity_provider_; } + // Indicates if all user information has been fetched. If the result is false, + // there are still unfininshed fetchers. + virtual bool IsAllUserInfoFetched() const; + private: struct AccountState { AccountIds ids; diff --git a/google_apis/gaia/fake_oauth2_token_service.cc b/google_apis/gaia/fake_oauth2_token_service.cc index 0b01404bd8735d..44722dd2dd3618 100644 --- a/google_apis/gaia/fake_oauth2_token_service.cc +++ b/google_apis/gaia/fake_oauth2_token_service.cc @@ -77,6 +77,18 @@ void FakeOAuth2TokenService::IssueAllTokensForAccount( } } +void FakeOAuth2TokenService::IssueErrorForAllPendingRequestsForAccount( + const std::string& account_id, + const GoogleServiceAuthError& auth_error) { + // Walk the requests and notify the callbacks. + for (std::vector::iterator it = pending_requests_.begin(); + it != pending_requests_.end(); + ++it) { + if (it->request && (account_id == it->account_id)) { + it->request->InformConsumer(auth_error, std::string(), base::Time()); + } + } +} OAuth2AccessTokenFetcher* FakeOAuth2TokenService::CreateAccessTokenFetcher( const std::string& account_id, diff --git a/google_apis/gaia/fake_oauth2_token_service.h b/google_apis/gaia/fake_oauth2_token_service.h index 3bdb67500a9673..5868b1a6469fdf 100644 --- a/google_apis/gaia/fake_oauth2_token_service.h +++ b/google_apis/gaia/fake_oauth2_token_service.h @@ -27,10 +27,14 @@ class FakeOAuth2TokenService : public OAuth2TokenService { void AddAccount(const std::string& account_id); void RemoveAccount(const std::string& account_id); - // Helper routines to issue tokens for pending requests. + // Helper routines to issue tokens for pending requests or complete them with + // error. void IssueAllTokensForAccount(const std::string& account_id, const std::string& access_token, const base::Time& expiration); + void IssueErrorForAllPendingRequestsForAccount( + const std::string& account_id, + const GoogleServiceAuthError& auth_error); void set_request_context(net::URLRequestContextGetter* request_context) { request_context_ = request_context;