| // 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 "components/signin/core/browser/account_tracker_service.h" |
| |
| #include <stddef.h> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/post_task.h" |
| #include "base/task/task_traits.h" |
| #include "base/task_runner_util.h" |
| #include "base/threading/scoped_blocking_call.h" |
| #include "base/trace_event/trace_event.h" |
| #include "build/build_config.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| #include "components/signin/core/browser/account_info_util.h" |
| #include "components/signin/core/browser/signin_manager.h" |
| #include "components/signin/core/browser/signin_pref_names.h" |
| |
| #if defined(OS_ANDROID) |
| #include "base/android/jni_array.h" |
| #include "jni/AccountTrackerService_jni.h" |
| #endif |
| |
| namespace { |
| |
| const char kAccountKeyPath[] = "account_id"; |
| const char kAccountEmailPath[] = "email"; |
| const char kAccountGaiaPath[] = "gaia"; |
| const char kAccountHostedDomainPath[] = "hd"; |
| const char kAccountFullNamePath[] = "full_name"; |
| const char kAccountGivenNamePath[] = "given_name"; |
| const char kAccountLocalePath[] = "locale"; |
| const char kAccountPictureURLPath[] = "picture_url"; |
| const char kAccountChildAccountStatusPath[] = "is_child_account"; |
| const char kAdvancedProtectionAccountStatusPath[] = |
| "is_under_advanced_protection"; |
| |
| // TODO(knn): Remove once deprecated service flags have been migrated from |
| // preferences. |
| const char kChildAccountServiceFlag[] = "uca"; |
| |
| // Account folders used for storing account related data at disk. |
| const base::FilePath::CharType kAccountsFolder[] = |
| FILE_PATH_LITERAL("Accounts"); |
| const base::FilePath::CharType kAvatarImagesFolder[] = |
| FILE_PATH_LITERAL("Avatar Images"); |
| |
| // TODO(M48): Remove deprecated preference migration. |
| const char kAccountServiceFlagsPath[] = "service_flags"; |
| |
| void RemoveDeprecatedServiceFlags(PrefService* pref_service) { |
| ListPrefUpdate update(pref_service, prefs::kAccountInfo); |
| for (size_t i = 0; i < update->GetSize(); ++i) { |
| base::DictionaryValue* dict = nullptr; |
| if (update->GetDictionary(i, &dict)) |
| dict->RemoveWithoutPathExpansion(kAccountServiceFlagsPath, nullptr); |
| } |
| } |
| |
| // Reads a PNG image from disk and decodes it. If the reading/decoding attempt |
| // was unsuccessful, an empty image is returned. |
| gfx::Image ReadImage(const base::FilePath& image_path) { |
| base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, |
| base::BlockingType::MAY_BLOCK); |
| |
| if (!base::PathExists(image_path)) |
| return gfx::Image(); |
| std::string image_data; |
| if (!base::ReadFileToString(image_path, &image_data)) { |
| LOG(ERROR) << "Failed to read image from disk: " << image_path; |
| return gfx::Image(); |
| } |
| return gfx::Image::CreateFrom1xPNGBytes( |
| base::RefCountedString::TakeString(&image_data)); |
| } |
| |
| // Saves |png_data| to disk at |image_path|. |
| void SaveImage(scoped_refptr<base::RefCountedMemory> png_data, |
| const base::FilePath& image_path) { |
| base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, |
| base::BlockingType::MAY_BLOCK); |
| // Make sure the destination directory exists. |
| base::FilePath dir = image_path.DirName(); |
| if (!base::DirectoryExists(dir) && !base::CreateDirectory(dir)) { |
| LOG(ERROR) << "Failed to create parent directory of: " << image_path; |
| return; |
| } |
| if (base::WriteFile(image_path, png_data->front_as<char>(), |
| png_data->size()) == -1) { |
| LOG(ERROR) << "Failed to save image to file: " << image_path; |
| } |
| } |
| |
| // Removes the image at path |image_path|. |
| void RemoveImage(const base::FilePath& image_path) { |
| if (!base::DeleteFile(image_path, false /* recursive */)) |
| LOG(ERROR) << "Failed to delete image."; |
| } |
| |
| } // namespace |
| |
| AccountTrackerService::AccountTrackerService() : weak_factory_(this) { |
| #if defined(OS_ANDROID) |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| base::android::ScopedJavaLocalRef<jobject> java_ref = |
| Java_AccountTrackerService_create(env, reinterpret_cast<intptr_t>(this)); |
| java_ref_.Reset(env, java_ref.obj()); |
| #endif |
| } |
| |
| AccountTrackerService::~AccountTrackerService() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| // static |
| void AccountTrackerService::RegisterPrefs(PrefRegistrySimple* registry) { |
| registry->RegisterListPref(prefs::kAccountInfo); |
| registry->RegisterIntegerPref(prefs::kAccountIdMigrationState, |
| AccountTrackerService::MIGRATION_NOT_STARTED); |
| } |
| |
| void AccountTrackerService::Initialize(PrefService* pref_service, |
| base::FilePath user_data_dir) { |
| DCHECK(pref_service); |
| DCHECK(!pref_service_); |
| pref_service_ = pref_service; |
| LoadFromPrefs(); |
| user_data_dir_ = std::move(user_data_dir); |
| if (!user_data_dir_.empty()) { |
| // |image_storage_task_runner_| is a sequenced runner because we want to |
| // avoid read and write operations to the same file at the same time. |
| image_storage_task_runner_ = base::CreateSequencedTaskRunnerWithTraits( |
| {base::MayBlock(), base::TaskPriority::USER_VISIBLE, |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}); |
| LoadAccountImagesFromDisk(); |
| } |
| } |
| |
| void AccountTrackerService::Shutdown() { |
| pref_service_ = nullptr; |
| accounts_.clear(); |
| } |
| |
| void AccountTrackerService::AddObserver(Observer* observer) { |
| observer_list_.AddObserver(observer); |
| } |
| |
| void AccountTrackerService::RemoveObserver(Observer* observer) { |
| observer_list_.RemoveObserver(observer); |
| } |
| |
| std::vector<AccountInfo> AccountTrackerService::GetAccounts() const { |
| std::vector<AccountInfo> accounts; |
| for (const auto& pair : accounts_) { |
| accounts.push_back(pair.second); |
| } |
| return accounts; |
| } |
| |
| AccountInfo AccountTrackerService::GetAccountInfo( |
| const std::string& account_id) const { |
| const auto iterator = accounts_.find(account_id); |
| if (iterator != accounts_.end()) |
| return iterator->second; |
| |
| return AccountInfo(); |
| } |
| |
| AccountInfo AccountTrackerService::FindAccountInfoByGaiaId( |
| const std::string& gaia_id) const { |
| if (!gaia_id.empty()) { |
| const auto iterator = std::find_if( |
| accounts_.begin(), accounts_.end(), |
| [&gaia_id](const auto& pair) { return pair.second.gaia == gaia_id; }); |
| if (iterator != accounts_.end()) |
| return iterator->second; |
| } |
| |
| return AccountInfo(); |
| } |
| |
| AccountInfo AccountTrackerService::FindAccountInfoByEmail( |
| const std::string& email) const { |
| if (!email.empty()) { |
| const auto iterator = std::find_if( |
| accounts_.begin(), accounts_.end(), [&email](const auto& pair) { |
| return gaia::AreEmailsSame(pair.second.email, email); |
| }); |
| if (iterator != accounts_.end()) |
| return iterator->second; |
| } |
| |
| return AccountInfo(); |
| } |
| |
| // static |
| bool AccountTrackerService::IsMigrationSupported() { |
| #if defined(OS_CHROMEOS) |
| return false; |
| #else |
| return true; |
| #endif |
| } |
| |
| AccountTrackerService::AccountIdMigrationState |
| AccountTrackerService::GetMigrationState() const { |
| return GetMigrationState(pref_service_); |
| } |
| |
| void AccountTrackerService::SetMigrationDone() { |
| SetMigrationState(MIGRATION_DONE); |
| } |
| |
| void AccountTrackerService::NotifyAccountUpdated( |
| const AccountInfo& account_info) { |
| DCHECK(!account_info.gaia.empty()); |
| for (auto& observer : observer_list_) |
| observer.OnAccountUpdated(account_info); |
| } |
| |
| void AccountTrackerService::NotifyAccountUpdateFailed( |
| const std::string& account_id) { |
| for (auto& observer : observer_list_) |
| observer.OnAccountUpdateFailed(account_id); |
| } |
| |
| void AccountTrackerService::NotifyAccountRemoved( |
| const AccountInfo& account_info) { |
| DCHECK(!account_info.gaia.empty()); |
| for (auto& observer : observer_list_) |
| observer.OnAccountRemoved(account_info); |
| } |
| |
| void AccountTrackerService::StartTrackingAccount( |
| const std::string& account_id) { |
| if (!base::ContainsKey(accounts_, account_id)) { |
| DVLOG(1) << "StartTracking " << account_id; |
| AccountInfo account_info; |
| account_info.account_id = account_id; |
| account_info.is_child_account = false; |
| accounts_.insert(make_pair(account_id, account_info)); |
| } |
| } |
| |
| void AccountTrackerService::StopTrackingAccount(const std::string& account_id) { |
| DVLOG(1) << "StopTracking " << account_id; |
| if (base::ContainsKey(accounts_, account_id)) { |
| AccountInfo account_info = std::move(accounts_[account_id]); |
| RemoveFromPrefs(account_info); |
| RemoveAccountImageFromDisk(account_id); |
| accounts_.erase(account_id); |
| |
| if (!account_info.gaia.empty()) |
| NotifyAccountRemoved(account_info); |
| } |
| } |
| |
| void AccountTrackerService::SetAccountInfoFromUserInfo( |
| const std::string& account_id, |
| const base::DictionaryValue* user_info) { |
| DCHECK(base::ContainsKey(accounts_, account_id)); |
| AccountInfo& account_info = accounts_[account_id]; |
| |
| base::Optional<AccountInfo> maybe_account_info = |
| AccountInfoFromUserInfo(*user_info); |
| if (maybe_account_info) { |
| // Should we DCHECK that the account stored in |accounts_| has the same |
| // value for |gaia_id| and |email| as the value loaded from |user_info|? |
| // DCHECK(account_info.gaia.empty() |
| // || account_info.gaia == maybe_account_info.value().gaia); |
| // DCHECK(account_info.email.empty() |
| // || account_info.email == maybe_account_info.value().email); |
| maybe_account_info.value().account_id = account_id; |
| account_info.UpdateWith(maybe_account_info.value()); |
| } |
| |
| if (!account_info.gaia.empty()) |
| NotifyAccountUpdated(account_info); |
| SaveToPrefs(account_info); |
| } |
| |
| void AccountTrackerService::SetAccountImage(const std::string& account_id, |
| const gfx::Image& image) { |
| if (!base::ContainsKey(accounts_, account_id)) |
| return; |
| AccountInfo& account_info = accounts_[account_id]; |
| account_info.account_image = image; |
| SaveAccountImageToDisk(account_id, image); |
| NotifyAccountUpdated(account_info); |
| } |
| |
| void AccountTrackerService::SetIsChildAccount(const std::string& account_id, |
| bool is_child_account) { |
| DCHECK(base::ContainsKey(accounts_, account_id)); |
| AccountInfo& account_info = accounts_[account_id]; |
| if (account_info.is_child_account == is_child_account) |
| return; |
| account_info.is_child_account = is_child_account; |
| if (!account_info.gaia.empty()) |
| NotifyAccountUpdated(account_info); |
| SaveToPrefs(account_info); |
| } |
| |
| void AccountTrackerService::SetIsAdvancedProtectionAccount( |
| const std::string& account_id, |
| bool is_under_advanced_protection) { |
| DCHECK(base::ContainsKey(accounts_, account_id)); |
| AccountInfo& account_info = accounts_[account_id]; |
| if (account_info.is_under_advanced_protection == is_under_advanced_protection) |
| return; |
| account_info.is_under_advanced_protection = is_under_advanced_protection; |
| if (!account_info.gaia.empty()) |
| NotifyAccountUpdated(account_info); |
| SaveToPrefs(account_info); |
| } |
| |
| void AccountTrackerService::MigrateToGaiaId() { |
| DCHECK_EQ(GetMigrationState(), MIGRATION_IN_PROGRESS); |
| |
| std::vector<std::string> to_remove; |
| std::vector<AccountInfo> migrated_accounts; |
| for (const auto& pair : accounts_) { |
| const std::string& new_account_id = pair.second.gaia; |
| if (pair.first == new_account_id) |
| continue; |
| |
| to_remove.push_back(pair.first); |
| |
| // If there is already an account keyed to the current account's gaia id, |
| // assume this is the result of a partial migration and skip the account |
| // that is currently inspected. |
| if (base::ContainsKey(accounts_, new_account_id)) |
| continue; |
| |
| AccountInfo new_account_info = pair.second; |
| new_account_info.account_id = new_account_id; |
| SaveToPrefs(new_account_info); |
| migrated_accounts.emplace_back(std::move(new_account_info)); |
| } |
| |
| // Insert the new migrated accounts. |
| for (AccountInfo& new_account_info : migrated_accounts) { |
| // Copy the AccountInfo |gaia| member field so that it is not left in |
| // an undeterminate state in the structure after std::map::emplace call. |
| std::string account_id = new_account_info.gaia; |
| SaveToPrefs(new_account_info); |
| |
| accounts_.emplace(std::move(account_id), std::move(new_account_info)); |
| } |
| |
| // Remove any obsolete account. |
| for (const auto& account_id : to_remove) { |
| DCHECK(base::ContainsKey(accounts_, account_id)); |
| AccountInfo& account_info = accounts_[account_id]; |
| RemoveAccountImageFromDisk(account_id); |
| RemoveFromPrefs(account_info); |
| accounts_.erase(account_id); |
| } |
| } |
| |
| bool AccountTrackerService::IsMigrationDone() const { |
| if (!IsMigrationSupported()) |
| return false; |
| |
| for (const auto& pair : accounts_) { |
| if (pair.first != pair.second.gaia) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| AccountTrackerService::AccountIdMigrationState |
| AccountTrackerService::ComputeNewMigrationState() const { |
| // If migration is not supported, skip migration. |
| if (!IsMigrationSupported()) |
| return MIGRATION_NOT_STARTED; |
| |
| bool migration_required = false; |
| for (const auto& pair : accounts_) { |
| // If there is any non-migratable account, skip migration. |
| if (pair.first.empty() || pair.second.gaia.empty()) |
| return MIGRATION_NOT_STARTED; |
| |
| // Migration is required if at least one account is not keyed to its |
| // gaia id. |
| migration_required |= (pair.first != pair.second.gaia); |
| } |
| |
| return migration_required ? MIGRATION_IN_PROGRESS : MIGRATION_DONE; |
| } |
| |
| void AccountTrackerService::SetMigrationState(AccountIdMigrationState state) { |
| DCHECK(state != MIGRATION_DONE || IsMigrationDone()); |
| pref_service_->SetInteger(prefs::kAccountIdMigrationState, state); |
| } |
| |
| // static |
| AccountTrackerService::AccountIdMigrationState |
| AccountTrackerService::GetMigrationState(const PrefService* pref_service) { |
| return static_cast<AccountTrackerService::AccountIdMigrationState>( |
| pref_service->GetInteger(prefs::kAccountIdMigrationState)); |
| } |
| |
| base::FilePath AccountTrackerService::GetImagePathFor( |
| const std::string& account_id) { |
| return user_data_dir_.Append(kAccountsFolder) |
| .Append(kAvatarImagesFolder) |
| .AppendASCII(account_id); |
| } |
| |
| void AccountTrackerService::OnAccountImageLoaded(const std::string& account_id, |
| gfx::Image image) { |
| if (base::ContainsKey(accounts_, account_id) && |
| accounts_[account_id].account_image.IsEmpty()) { |
| AccountInfo& account_info = accounts_[account_id]; |
| account_info.account_image = image; |
| NotifyAccountUpdated(account_info); |
| } |
| } |
| |
| void AccountTrackerService::LoadAccountImagesFromDisk() { |
| if (!image_storage_task_runner_) |
| return; |
| for (const auto& pair : accounts_) { |
| const std::string& account_id = pair.second.account_id; |
| PostTaskAndReplyWithResult( |
| image_storage_task_runner_.get(), FROM_HERE, |
| base::BindOnce(&ReadImage, GetImagePathFor(account_id)), |
| base::BindOnce(&AccountTrackerService::OnAccountImageLoaded, |
| weak_factory_.GetWeakPtr(), account_id)); |
| } |
| } |
| |
| void AccountTrackerService::SaveAccountImageToDisk( |
| const std::string& account_id, |
| const gfx::Image& image) { |
| if (!image_storage_task_runner_) |
| return; |
| image_storage_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&SaveImage, image.As1xPNGBytes(), |
| GetImagePathFor(account_id))); |
| } |
| |
| void AccountTrackerService::RemoveAccountImageFromDisk( |
| const std::string& account_id) { |
| if (!image_storage_task_runner_) |
| return; |
| image_storage_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&RemoveImage, GetImagePathFor(account_id))); |
| } |
| |
| void AccountTrackerService::LoadFromPrefs() { |
| const base::ListValue* list = pref_service_->GetList(prefs::kAccountInfo); |
| std::set<std::string> to_remove; |
| bool contains_deprecated_service_flags = false; |
| for (size_t i = 0; i < list->GetSize(); ++i) { |
| const base::DictionaryValue* dict; |
| if (list->GetDictionary(i, &dict)) { |
| base::string16 value; |
| if (dict->GetString(kAccountKeyPath, &value)) { |
| std::string account_id = base::UTF16ToUTF8(value); |
| |
| // Ignore incorrectly persisted non-canonical account ids. |
| if (account_id.find('@') != std::string::npos && |
| account_id != gaia::CanonicalizeEmail(account_id)) { |
| to_remove.insert(account_id); |
| continue; |
| } |
| |
| StartTrackingAccount(account_id); |
| AccountInfo& account_info = accounts_[account_id]; |
| |
| if (dict->GetString(kAccountGaiaPath, &value)) |
| account_info.gaia = base::UTF16ToUTF8(value); |
| if (dict->GetString(kAccountEmailPath, &value)) |
| account_info.email = base::UTF16ToUTF8(value); |
| if (dict->GetString(kAccountHostedDomainPath, &value)) |
| account_info.hosted_domain = base::UTF16ToUTF8(value); |
| if (dict->GetString(kAccountFullNamePath, &value)) |
| account_info.full_name = base::UTF16ToUTF8(value); |
| if (dict->GetString(kAccountGivenNamePath, &value)) |
| account_info.given_name = base::UTF16ToUTF8(value); |
| if (dict->GetString(kAccountLocalePath, &value)) |
| account_info.locale = base::UTF16ToUTF8(value); |
| if (dict->GetString(kAccountPictureURLPath, &value)) |
| account_info.picture_url = base::UTF16ToUTF8(value); |
| |
| bool is_child_account = false; |
| // Migrate deprecated service flag preference. |
| const base::ListValue* service_flags_list; |
| if (dict->GetList(kAccountServiceFlagsPath, &service_flags_list)) { |
| contains_deprecated_service_flags = true; |
| std::string flag_string; |
| for (const auto& flag : *service_flags_list) { |
| if (flag.GetAsString(&flag_string) && |
| flag_string == kChildAccountServiceFlag) { |
| is_child_account = true; |
| break; |
| } |
| } |
| account_info.is_child_account = is_child_account; |
| } |
| if (dict->GetBoolean(kAccountChildAccountStatusPath, &is_child_account)) |
| account_info.is_child_account = is_child_account; |
| |
| bool is_under_advanced_protection = false; |
| if (dict->GetBoolean(kAdvancedProtectionAccountStatusPath, |
| &is_under_advanced_protection)) { |
| account_info.is_under_advanced_protection = |
| is_under_advanced_protection; |
| } |
| |
| if (!account_info.gaia.empty()) |
| NotifyAccountUpdated(account_info); |
| } |
| } |
| } |
| |
| UMA_HISTOGRAM_BOOLEAN("Signin.AccountTracker.DeprecatedServiceFlagDeleted", |
| contains_deprecated_service_flags); |
| |
| if (contains_deprecated_service_flags) |
| RemoveDeprecatedServiceFlags(pref_service_); |
| |
| // Remove any obsolete prefs. |
| for (auto account_id : to_remove) { |
| AccountInfo account_info; |
| account_info.account_id = account_id; |
| RemoveFromPrefs(account_info); |
| RemoveAccountImageFromDisk(account_id); |
| } |
| |
| if (IsMigrationSupported()) { |
| if (GetMigrationState() != MIGRATION_DONE) { |
| const AccountIdMigrationState new_state = ComputeNewMigrationState(); |
| SetMigrationState(new_state); |
| |
| if (new_state == MIGRATION_IN_PROGRESS) { |
| MigrateToGaiaId(); |
| } |
| } |
| } else { |
| // ChromeOS running on Linux and Linux share the preferences, so the |
| // migration may have been performed on Linux. Reset the migration |
| // state to ensure that the same code path is used whether ChromeOS |
| // is running on Linux on a dev build or on real ChromeOS device. |
| SetMigrationState(MIGRATION_NOT_STARTED); |
| } |
| |
| DCHECK(GetMigrationState() != MIGRATION_DONE || IsMigrationDone()); |
| UMA_HISTOGRAM_ENUMERATION("Signin.AccountTracker.GaiaIdMigrationState", |
| GetMigrationState(), NUM_MIGRATION_STATES); |
| |
| UMA_HISTOGRAM_COUNTS_100("Signin.AccountTracker.CountOfLoadedAccounts", |
| accounts_.size()); |
| } |
| |
| void AccountTrackerService::SaveToPrefs(const AccountInfo& account_info) { |
| if (!pref_service_) |
| return; |
| |
| base::DictionaryValue* dict = nullptr; |
| base::string16 account_id_16 = base::UTF8ToUTF16(account_info.account_id); |
| ListPrefUpdate update(pref_service_, prefs::kAccountInfo); |
| for (size_t i = 0; i < update->GetSize(); ++i, dict = nullptr) { |
| if (update->GetDictionary(i, &dict)) { |
| base::string16 value; |
| if (dict->GetString(kAccountKeyPath, &value) && value == account_id_16) |
| break; |
| } |
| } |
| |
| if (!dict) { |
| dict = new base::DictionaryValue(); |
| update->Append(base::WrapUnique(dict)); |
| // |dict| is invalidated at this point, so it needs to be reset. |
| update->GetDictionary(update->GetSize() - 1, &dict); |
| dict->SetString(kAccountKeyPath, account_id_16); |
| } |
| |
| dict->SetString(kAccountEmailPath, account_info.email); |
| dict->SetString(kAccountGaiaPath, account_info.gaia); |
| dict->SetString(kAccountHostedDomainPath, account_info.hosted_domain); |
| dict->SetString(kAccountFullNamePath, account_info.full_name); |
| dict->SetString(kAccountGivenNamePath, account_info.given_name); |
| dict->SetString(kAccountLocalePath, account_info.locale); |
| dict->SetString(kAccountPictureURLPath, account_info.picture_url); |
| dict->SetBoolean(kAccountChildAccountStatusPath, |
| account_info.is_child_account); |
| dict->SetBoolean(kAdvancedProtectionAccountStatusPath, |
| account_info.is_under_advanced_protection); |
| } |
| |
| void AccountTrackerService::RemoveFromPrefs(const AccountInfo& account_info) { |
| if (!pref_service_) |
| return; |
| |
| base::string16 account_id_16 = base::UTF8ToUTF16(account_info.account_id); |
| ListPrefUpdate update(pref_service_, prefs::kAccountInfo); |
| for (size_t i = 0; i < update->GetSize(); ++i) { |
| base::DictionaryValue* dict = nullptr; |
| if (update->GetDictionary(i, &dict)) { |
| base::string16 value; |
| if (dict->GetString(kAccountKeyPath, &value) && value == account_id_16) { |
| update->Remove(i, nullptr); |
| break; |
| } |
| } |
| } |
| } |
| |
| std::string AccountTrackerService::PickAccountIdForAccount( |
| const std::string& gaia, |
| const std::string& email) const { |
| return PickAccountIdForAccount(pref_service_, gaia, email); |
| } |
| |
| // static |
| std::string AccountTrackerService::PickAccountIdForAccount( |
| const PrefService* pref_service, |
| const std::string& gaia, |
| const std::string& email) { |
| DCHECK(!gaia.empty() || |
| GetMigrationState(pref_service) == MIGRATION_NOT_STARTED); |
| DCHECK(!email.empty()); |
| switch (GetMigrationState(pref_service)) { |
| case MIGRATION_NOT_STARTED: |
| // Some tests don't use a real email address. To support these cases, |
| // don't try to canonicalize these strings. |
| return (email.find('@') == std::string::npos) |
| ? email |
| : gaia::CanonicalizeEmail(email); |
| case MIGRATION_IN_PROGRESS: |
| case MIGRATION_DONE: |
| return gaia; |
| default: |
| NOTREACHED(); |
| return email; |
| } |
| } |
| |
| std::string AccountTrackerService::SeedAccountInfo(const std::string& gaia, |
| const std::string& email) { |
| const std::string account_id = PickAccountIdForAccount(gaia, email); |
| const bool already_exists = base::ContainsKey(accounts_, account_id); |
| StartTrackingAccount(account_id); |
| AccountInfo& account_info = accounts_[account_id]; |
| DCHECK(!already_exists || account_info.gaia.empty() || |
| account_info.gaia == gaia); |
| account_info.gaia = gaia; |
| account_info.email = email; |
| SaveToPrefs(account_info); |
| |
| DVLOG(1) << "AccountTrackerService::SeedAccountInfo" |
| << " account_id=" << account_id << " gaia_id=" << gaia |
| << " email=" << email; |
| |
| return account_id; |
| } |
| |
| std::string AccountTrackerService::SeedAccountInfo(AccountInfo info) { |
| info.account_id = PickAccountIdForAccount(info.gaia, info.email); |
| |
| if (!base::ContainsKey(accounts_, info.account_id)) { |
| StartTrackingAccount(info.account_id); |
| } |
| |
| AccountInfo& account_info = accounts_[info.account_id]; |
| // Update the missing fields in |account_info| with |info|. |
| if (account_info.UpdateWith(info)) { |
| if (!account_info.gaia.empty()) |
| NotifyAccountUpdated(account_info); |
| |
| SaveToPrefs(account_info); |
| } |
| return info.account_id; |
| } |
| |
| void AccountTrackerService::RemoveAccount(const std::string& account_id) { |
| StopTrackingAccount(account_id); |
| } |
| |
| #if defined(OS_ANDROID) |
| base::android::ScopedJavaLocalRef<jobject> |
| AccountTrackerService::GetJavaObject() { |
| return base::android::ScopedJavaLocalRef<jobject>(java_ref_); |
| } |
| |
| void JNI_AccountTrackerService_SeedAccountsInfo( |
| JNIEnv* env, |
| jlong nativeAccountTrackerService, |
| const base::android::JavaParamRef<jobjectArray>& gaiaIds, |
| const base::android::JavaParamRef<jobjectArray>& accountNames) { |
| AccountTrackerService* service = |
| reinterpret_cast<AccountTrackerService*>(nativeAccountTrackerService); |
| DCHECK(service); |
| |
| std::vector<std::string> gaia_ids; |
| std::vector<std::string> account_names; |
| base::android::AppendJavaStringArrayToStringVector(env, gaiaIds, &gaia_ids); |
| base::android::AppendJavaStringArrayToStringVector(env, accountNames, |
| &account_names); |
| DCHECK_EQ(gaia_ids.size(), account_names.size()); |
| |
| DVLOG(1) << "AccountTrackerService.SeedAccountsInfo: " |
| << " number of accounts " << gaia_ids.size(); |
| for (size_t i = 0; i < gaia_ids.size(); ++i) { |
| service->SeedAccountInfo(gaia_ids[i], account_names[i]); |
| } |
| } |
| |
| jboolean JNI_AccountTrackerService_AreAccountsSeeded( |
| JNIEnv* env, |
| jlong nativeAccountTrackerService, |
| const base::android::JavaParamRef<jobjectArray>& accountNames) { |
| AccountTrackerService* service = |
| reinterpret_cast<AccountTrackerService*>(nativeAccountTrackerService); |
| DCHECK(service); |
| |
| std::vector<std::string> account_names; |
| base::android::AppendJavaStringArrayToStringVector(env, accountNames, |
| &account_names); |
| |
| bool migrated = |
| service->GetMigrationState() == |
| AccountTrackerService::AccountIdMigrationState::MIGRATION_DONE; |
| |
| for (const auto& account_name : account_names) { |
| AccountInfo info = service->FindAccountInfoByEmail(account_name); |
| if (info.account_id.empty()) { |
| return false; |
| } |
| if (migrated && info.gaia.empty()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| #endif |