blob: 68f506541acc1b6963ee549756034cbfdea1327a [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include <algorithm>
#include <unordered_set>
#include <utility>
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/i18n/number_formatting.h"
#include "base/i18n/string_compare.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted_memory.h"
#include "base/metrics/histogram_functions.h"
#include "base/observer_list.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/enterprise/browser_management/management_service_factory.h"
#include "chrome/browser/profiles/profile_avatar_downloader.h"
#include "chrome/browser/profiles/profile_avatar_icon_util.h"
#include "chrome/browser/profiles/profile_metrics.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/account_id/account_id.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/profile_metrics/state.h"
#include "components/signin/public/base/persistent_repeating_timer.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/account_managed_status_finder.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "google_apis/gaia/gaia_id.h"
#include "third_party/icu/source/i18n/unicode/coll.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/image/image.h"
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/browser_list.h"
#endif
namespace {
using ImageData = std::vector<unsigned char>;
// First eight are generic icons, which used to be called "User <i>" for i=1..7,
// and are no longer available.
const int kDefaultNames[] = {
IDS_DEFAULT_AVATAR_NAME_8,
IDS_DEFAULT_AVATAR_NAME_9,
IDS_DEFAULT_AVATAR_NAME_10,
IDS_DEFAULT_AVATAR_NAME_11,
IDS_DEFAULT_AVATAR_NAME_12,
IDS_DEFAULT_AVATAR_NAME_13,
IDS_DEFAULT_AVATAR_NAME_14,
IDS_DEFAULT_AVATAR_NAME_15,
IDS_DEFAULT_AVATAR_NAME_16,
IDS_DEFAULT_AVATAR_NAME_17,
IDS_DEFAULT_AVATAR_NAME_18,
IDS_DEFAULT_AVATAR_NAME_19,
IDS_DEFAULT_AVATAR_NAME_20,
IDS_DEFAULT_AVATAR_NAME_21,
IDS_DEFAULT_AVATAR_NAME_22,
IDS_DEFAULT_AVATAR_NAME_23,
IDS_DEFAULT_AVATAR_NAME_24,
IDS_DEFAULT_AVATAR_NAME_25,
IDS_DEFAULT_AVATAR_NAME_26
};
enum class MultiProfileUserType {
kSingleProfile, // There is only one profile.
kActiveMultiProfile, // Several profiles are actively used.
kLatentMultiProfile // There are several profiles, but only one is actively
// used.
};
const char kProfileCountLastUpdatePref[] = "profile.profile_counts_reported";
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
const char kLegacyProfileNameMigrated[] = "legacy.profile.name.migrated";
bool g_migration_enabled_for_testing = false;
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
// Reads a PNG from disk and decodes it. If the bitmap was successfully read
// from disk then this will return the bitmap image, otherwise it will return
// an empty gfx::Image.
gfx::Image ReadBitmap(const base::FilePath& image_path) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
// If the path doesn't exist, don't even try reading it.
if (!base::PathExists(image_path))
return gfx::Image();
std::string image_data;
if (!base::ReadFileToString(image_path, &image_data)) {
LOG(ERROR) << "Failed to read PNG file from disk.";
return gfx::Image();
}
gfx::Image image = gfx::Image::CreateFrom1xPNGBytes(
base::MakeRefCounted<base::RefCountedString>(std::move(image_data)));
if (image.IsEmpty())
LOG(ERROR) << "Failed to decode PNG file.";
return image;
}
// Writes |data| to disk and takes ownership of the pointer. On successful
// completion, it runs |callback|.
bool SaveBitmap(std::unique_ptr<ImageData> 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.";
return false;
}
if (!base::WriteFile(image_path, *data)) {
LOG(ERROR) << "Failed to save image to file.";
return false;
}
return true;
}
void DeleteBitmap(const base::FilePath& image_path) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
base::DeleteFile(image_path);
}
void RunCallbackIfFileMissing(const base::FilePath& file_path,
base::OnceClosure callback) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
if (!base::PathExists(file_path))
content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE,
std::move(callback));
}
// Compares two ProfileAttributesEntry using locale-sensitive comparison of
// their names. For ties, the profile path is compared next.
class ProfileAttributesSortComparator {
public:
ProfileAttributesSortComparator(icu::Collator* collator, bool use_local_name)
: collator_(collator), use_local_name_(use_local_name) {}
bool operator()(const ProfileAttributesEntry* const a,
const ProfileAttributesEntry* const b) const {
UCollationResult result = base::i18n::CompareString16WithCollator(
*collator_, GetValue(a), GetValue(b));
if (result != UCOL_EQUAL)
return result == UCOL_LESS;
// If the names are the same, then compare the paths, which must be unique.
return a->GetPath().value() < b->GetPath().value();
}
private:
std::u16string GetValue(const ProfileAttributesEntry* const entry) const {
if (use_local_name_)
return entry->GetLocalProfileName();
return entry->GetName();
}
raw_ptr<icu::Collator> collator_;
bool use_local_name_;
};
MultiProfileUserType GetMultiProfileUserType(
const std::vector<ProfileAttributesEntry*>& entries) {
DCHECK_GT(entries.size(), 0u);
if (entries.size() == 1u)
return MultiProfileUserType::kSingleProfile;
int active_count =
std::ranges::count_if(entries, &ProfileMetrics::IsProfileActive);
if (active_count <= 1)
return MultiProfileUserType::kLatentMultiProfile;
return MultiProfileUserType::kActiveMultiProfile;
}
profile_metrics::UnconsentedPrimaryAccountType GetUnconsentedPrimaryAccountType(
ProfileAttributesEntry* entry) {
if (entry->GetSigninState() == SigninState::kNotSignedIn)
return profile_metrics::UnconsentedPrimaryAccountType::kSignedOut;
if (entry->IsSupervised()) {
return profile_metrics::UnconsentedPrimaryAccountType::kChild;
}
// TODO(crbug.com/40121889): Replace this check by
// !entry->GetHostedDomain().has_value() in M84 (once the attributes storage
// gets reasonably well populated).
if (!signin::AccountManagedStatusFinder::MayBeEnterpriseUserBasedOnEmail(
base::UTF16ToUTF8(entry->GetUserName()))) {
return profile_metrics::UnconsentedPrimaryAccountType::kConsumer;
}
// TODO(crbug.com/40121889): Figure out how to distinguish EDU accounts from
// other enterprise.
return profile_metrics::UnconsentedPrimaryAccountType::kEnterprise;
}
void RecordProfileState(ProfileAttributesEntry* entry,
profile_metrics::StateSuffix suffix) {
profile_metrics::LogProfileAccountType(
GetUnconsentedPrimaryAccountType(entry), suffix);
profile_metrics::LogProfileSyncEnabled(
entry->GetSigninState() ==
SigninState::kSignedInWithConsentedPrimaryAccount,
suffix);
profile_metrics::LogProfileDaysSinceLastUse(
(base::Time::Now() - entry->GetActiveTime()).InDays(), suffix);
}
// Rotating between `from_index` to `to_index` by 1 step. Rotation is done to
// the left or the right based on the index comparison.
void Rotate(base::Value::List& list, size_t from_index, size_t to_index) {
CHECK_LT(from_index, list.size());
CHECK_LT(to_index, list.size());
// Rotating left.
if (from_index <= to_index) {
std::rotate(list.begin() + from_index, list.begin() + from_index + 1,
list.begin() + to_index + 1);
return;
}
// Rotating right;
// We invert the indices and work with the reverse iterator.
size_t inv_from_index = list.size() - from_index - 1;
size_t inv_to_index = list.size() - to_index - 1;
std::rotate(list.rbegin() + inv_from_index,
list.rbegin() + inv_from_index + 1,
list.rbegin() + inv_to_index + 1);
}
} // namespace
ProfileAttributesStorage::ProfileAttributesStorage(
PrefService* prefs,
const base::FilePath& user_data_dir)
: prefs_(prefs),
file_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})),
user_data_dir_(user_data_dir) {
// Populate the attributes storage.
ScopedDictPrefUpdate update(prefs_, prefs::kProfileAttributes);
base::Value::Dict& attributes = update.Get();
for (auto kv : attributes) {
DCHECK(kv.second.is_dict());
base::Value::Dict& info = kv.second.GetDict();
std::string* name = info.FindString(ProfileAttributesEntry::kNameKey);
std::optional<bool> using_default_name =
info.FindBool(ProfileAttributesEntry::kIsUsingDefaultNameKey);
if (!using_default_name.has_value()) {
// If the preference hasn't been set, and the name is default, assume
// that the user hasn't done this on purpose.
// |include_check_for_legacy_profile_name| is true as this is an old
// pre-existing profile and might have a legacy default profile name.
using_default_name = IsDefaultProfileName(
name ? base::UTF8ToUTF16(*name) : std::u16string(),
/*include_check_for_legacy_profile_name=*/true);
info.Set(ProfileAttributesEntry::kIsUsingDefaultNameKey,
using_default_name.value());
}
// For profiles that don't have the "using default avatar" state set yet,
// assume it's the same as the "using default name" state.
if (!info.FindBool(ProfileAttributesEntry::kIsUsingDefaultAvatarKey)) {
info.Set(ProfileAttributesEntry::kIsUsingDefaultAvatarKey,
using_default_name.value());
}
// `info` may become invalid after this call.
// Profiles loaded from disk can never be omitted.
InitEntryWithKey(kv.first, /*is_omitted=*/false);
}
// A profile name can depend on other profile names. Do an additional pass to
// update last used profile names once all profiles are initialized.
for (ProfileAttributesEntry* entry : GetAllProfilesAttributes()) {
entry->InitializeLastNameToDisplay();
}
// If needed, start downloading the high-res avatars and migrate any legacy
// profile names.
if (!disable_avatar_download_for_testing_)
DownloadAvatars();
#if !BUILDFLAG(IS_ANDROID)
LoadGAIAPictureIfNeeded();
#endif
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
bool migrate_legacy_profile_names =
(!prefs_->GetBoolean(kLegacyProfileNameMigrated) ||
g_migration_enabled_for_testing);
if (migrate_legacy_profile_names) {
MigrateLegacyProfileNamesAndRecomputeIfNeeded();
prefs_->SetBoolean(kLegacyProfileNameMigrated, true);
}
repeating_timer_ = std::make_unique<signin::PersistentRepeatingTimer>(
prefs_, kProfileCountLastUpdatePref, base::Hours(24),
base::BindRepeating(&ProfileMetrics::LogNumberOfProfiles, this));
repeating_timer_->Start();
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
EnsureProfilesOrderPrefIsInitialized();
}
ProfileAttributesStorage::~ProfileAttributesStorage() = default;
// static
void ProfileAttributesStorage::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(prefs::kProfileAttributes);
registry->RegisterListPref(prefs::kProfilesOrder);
registry->RegisterTimePref(kProfileCountLastUpdatePref, base::Time());
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
registry->RegisterBooleanPref(kLegacyProfileNameMigrated, false);
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
}
// static
base::flat_set<std::string> ProfileAttributesStorage::GetAllProfilesKeys(
PrefService* local_prefs) {
base::flat_set<std::string> profile_keys;
const base::Value::Dict& attribute_storage =
local_prefs->GetDict(prefs::kProfileAttributes);
for (std::pair<const std::string&, const base::Value&> attribute_entry :
attribute_storage) {
profile_keys.insert(attribute_entry.first);
}
return profile_keys;
}
void ProfileAttributesStorage::AddProfile(ProfileAttributesInitParams params) {
std::string key = StorageKeyFromProfilePath(params.profile_path);
ScopedDictPrefUpdate update(prefs_, prefs::kProfileAttributes);
base::Value::Dict& attributes = update.Get();
DCHECK(!params.is_consented_primary_account || !params.gaia_id.empty() ||
!params.user_name.empty());
base::Value::Dict info =
base::Value::Dict()
.Set(ProfileAttributesEntry::kNameKey, params.profile_name)
.Set(ProfileAttributesEntry::kGAIAIdKey, params.gaia_id.ToString())
.Set(ProfileAttributesEntry::kUserNameKey, params.user_name)
.Set(ProfileAttributesEntry::kIsConsentedPrimaryAccountKey,
params.is_consented_primary_account)
.Set(ProfileAttributesEntry::kAvatarIconKey,
profiles::GetDefaultAvatarIconUrl(params.icon_index))
// Default value for whether background apps are running is false.
.Set(ProfileAttributesEntry::kBackgroundAppsKey, false)
.Set(ProfileAttributesEntry::kSupervisedUserId,
params.supervised_user_id)
.Set(ProfileAttributesEntry::kProfileIsEphemeral, params.is_ephemeral)
// Either the user has provided a name manually on purpose, and in
// this case we should not check for legacy profile names or this a
// new profile but then it is not a legacy name, so we dont need to
// check for legacy names.
.Set(ProfileAttributesEntry::kIsUsingDefaultNameKey,
IsDefaultProfileName(
params.profile_name,
/*include_check_for_legacy_profile_name*/ false))
// Assume newly created profiles use a default avatar.
.Set(ProfileAttributesEntry::kIsUsingDefaultAvatarKey, true)
.Set(prefs::kSignedInWithCredentialProvider,
params.is_signed_in_with_credential_provider);
if (params.account_id.HasAccountIdKey()) {
info.Set(ProfileAttributesEntry::kAccountIdKey,
params.account_id.GetAccountIdKey());
}
attributes.Set(key, std::move(info));
ScopedListPrefUpdate ordered_list_update(prefs_, prefs::kProfilesOrder);
base::Value::List& ordered_list = ordered_list_update.Get();
ordered_list.Append(key);
ProfileAttributesEntry* entry = InitEntryWithKey(key, params.is_omitted);
entry->InitializeLastNameToDisplay();
// `OnProfileAdded()` must be the first observer method being called right
// after a new profile is added to the storage.
for (auto& observer : observer_list_)
observer.OnProfileAdded(params.profile_path);
if (!disable_avatar_download_for_testing_)
DownloadHighResAvatarIfNeeded(params.icon_index, params.profile_path);
NotifyIfProfileNamesHaveChanged();
}
void ProfileAttributesStorage::RemoveProfileByAccountId(
const AccountId& account_id) {
for (ProfileAttributesEntry* entry : GetAllProfilesAttributes()) {
bool account_id_keys_match =
account_id.HasAccountIdKey() &&
account_id.GetAccountIdKey() == entry->GetAccountIdKey();
bool gaia_ids_match = !entry->GetGAIAId().empty() &&
account_id.GetGaiaId() == entry->GetGAIAId();
bool user_names_match =
!entry->GetUserName().empty() &&
account_id.GetUserEmail() == base::UTF16ToUTF8(entry->GetUserName());
if (account_id_keys_match || gaia_ids_match || user_names_match) {
RemoveProfile(entry->GetPath());
return;
}
}
LOG(ERROR) << "Failed to remove profile.info_cache entry for account type "
<< static_cast<int>(account_id.GetAccountType())
<< ": matching entry not found.";
}
void ProfileAttributesStorage::RemoveProfile(
const base::FilePath& profile_path) {
ProfileAttributesEntry* entry = GetProfileAttributesWithPath(profile_path);
if (!entry) {
NOTREACHED();
}
std::u16string name = entry->GetName();
for (auto& observer : observer_list_)
observer.OnProfileWillBeRemoved(profile_path);
ScopedDictPrefUpdate update(prefs_, prefs::kProfileAttributes);
base::Value::Dict& attributes = update.Get();
std::string key = StorageKeyFromProfilePath(profile_path);
attributes.Remove(key);
profile_attributes_entries_.erase(profile_path.value());
ScopedListPrefUpdate ordered_list_update(prefs_, prefs::kProfilesOrder);
base::Value::List& ordered_list = ordered_list_update.Get();
ordered_list.EraseValue(base::Value(key));
// `OnProfileWasRemoved()` must be the first observer method being called
// right after a profile was removed from the storage.
for (auto& observer : observer_list_) {
observer.OnProfileWasRemoved(profile_path, name);
}
NotifyIfProfileNamesHaveChanged();
}
std::vector<ProfileAttributesEntry*>
ProfileAttributesStorage::GetAllProfilesAttributes() const {
std::vector<ProfileAttributesEntry*> ret;
for (auto& path_and_entry : profile_attributes_entries_) {
ProfileAttributesEntry* entry = &path_and_entry.second;
DCHECK(entry);
ret.push_back(entry);
}
return ret;
}
std::vector<ProfileAttributesEntry*>
ProfileAttributesStorage::GetAllProfilesAttributesSorted(
bool use_local_profile_name) const {
std::vector<ProfileAttributesEntry*> ret = GetAllProfilesAttributes();
// Do not allocate the collator and sort if it is not necessary.
if (ret.size() < 2)
return ret;
UErrorCode error_code = U_ZERO_ERROR;
// Use the default collator. The default locale should have been properly
// set by the time this constructor is called.
std::unique_ptr<icu::Collator> collator(
icu::Collator::createInstance(error_code));
DCHECK(U_SUCCESS(error_code));
std::sort(
ret.begin(), ret.end(),
ProfileAttributesSortComparator(collator.get(), use_local_profile_name));
return ret;
}
bool ProfileAttributesStorage::IsProfilesOrderPrefValid() const {
const base::Value::List& profile_keys_order =
prefs_->GetList(prefs::kProfilesOrder);
// We use this map to validate the values in the prefs.
base::flat_map<std::string, ProfileAttributesEntry*> key_entry_map =
GetStorageKeyEntryMap();
// Make sure the sizes are equal to proceed.
if (profile_keys_order.size() != key_entry_map.size()) {
return false;
}
base::flat_set<ProfileAttributesEntry*> entries_set;
for (const base::Value& keyValue : profile_keys_order) {
const std::string& key = keyValue.GetString();
auto key_entry_it = key_entry_map.find(key);
// If the entry is not found, there is a mismatch.
if (key_entry_it == key_entry_map.end()) {
return false;
}
ProfileAttributesEntry* found_entry = key_entry_it->second;
CHECK(found_entry);
auto inserted_entry = entries_set.insert(found_entry);
// We do not expect the same entry to be repeated or be invalid.
// `inserted_entry.second` is false if the element already exists.
if (!inserted_entry.second) {
return false;
}
}
return true;
}
void ProfileAttributesStorage::EnsureProfilesOrderPrefIsInitialized() {
ScopedListPrefUpdate update(prefs_, prefs::kProfilesOrder);
base::Value::List& profile_keys_order = update.Get();
// If the saved order pref is not valid, we recover by reseting the whole list
// and re-populate it with the profiles ordered by local profile name.
if (!IsProfilesOrderPrefValid()) {
profile_keys_order.clear();
std::vector<ProfileAttributesEntry*> entries =
GetAllProfilesAttributesSortedByLocalProfileName();
for (ProfileAttributesEntry* entry : entries) {
profile_keys_order.Append(StorageKeyFromProfilePath(entry->GetPath()));
}
}
DCHECK_EQ(profile_keys_order.size(), GetNumberOfProfiles());
}
void ProfileAttributesStorage::UpdateProfilesOrderPref(size_t from_index,
size_t to_index) {
if (from_index == to_index) {
return;
}
ScopedListPrefUpdate update(prefs_, prefs::kProfilesOrder);
base::Value::List& profile_keys_order = update.Get();
// Apply the shift by rotating the element based on the indices.
// Element at `from_index` will be placed at `to_index` and the rest will
// shift left or right based on the index comparison.
Rotate(profile_keys_order, from_index, to_index);
base::UmaHistogramBoolean("Profile.ProfilesOrderChanged", true);
}
base::flat_map<std::string, ProfileAttributesEntry*>
ProfileAttributesStorage::GetStorageKeyEntryMap() const {
base::flat_map<std::string, ProfileAttributesEntry*> key_entry_map;
for (auto& path_and_entry : profile_attributes_entries_) {
auto key = StorageKeyFromProfilePath(base::FilePath(path_and_entry.first));
key_entry_map[key] = &path_and_entry.second;
}
return key_entry_map;
}
std::vector<ProfileAttributesEntry*>
ProfileAttributesStorage::GetAllProfilesAttributesSortedForDisplay() const {
std::vector<ProfileAttributesEntry*> ret_ordered_entries;
const base::Value::List& ordered_keys =
prefs_->GetList(prefs::kProfilesOrder);
DCHECK_EQ(ordered_keys.size(), GetNumberOfProfiles());
base::flat_map<std::string, ProfileAttributesEntry*> key_entry_map =
GetStorageKeyEntryMap();
for (const base::Value& key : ordered_keys) {
ProfileAttributesEntry* entry = key_entry_map[key.GetString()];
DCHECK(entry);
ret_ordered_entries.emplace_back(entry);
}
return ret_ordered_entries;
}
std::vector<ProfileAttributesEntry*> ProfileAttributesStorage::
GetAllProfilesAttributesSortedByLocalProfileNameWithCheck() const {
if (base::FeatureList::IsEnabled(switches::kProfilesReordering)) {
return GetAllProfilesAttributesSortedForDisplay();
}
return GetAllProfilesAttributesSortedByLocalProfileName();
}
std::vector<ProfileAttributesEntry*>
ProfileAttributesStorage::GetAllProfilesAttributesSortedByNameWithCheck()
const {
if (base::FeatureList::IsEnabled(switches::kProfilesReordering)) {
return GetAllProfilesAttributesSortedForDisplay();
}
return GetAllProfilesAttributesSortedByName();
}
std::vector<ProfileAttributesEntry*>
ProfileAttributesStorage::GetAllProfilesAttributesSortedByName() const {
return GetAllProfilesAttributesSorted(false);
}
std::vector<ProfileAttributesEntry*>
ProfileAttributesStorage::GetAllProfilesAttributesSortedByLocalProfileName()
const {
return GetAllProfilesAttributesSorted(true);
}
ProfileAttributesEntry* ProfileAttributesStorage::GetProfileAttributesWithPath(
const base::FilePath& path) {
const auto entry_iter = profile_attributes_entries_.find(path.value());
if (entry_iter == profile_attributes_entries_.end()) {
return nullptr;
}
return &entry_iter->second;
}
size_t ProfileAttributesStorage::GetNumberOfProfiles() const {
return profile_attributes_entries_.size();
}
std::u16string ProfileAttributesStorage::ChooseNameForNewProfile() const {
std::u16string name;
for (int name_index = 0;; ++name_index) {
// Try using "Your Chrome" if possible, or use the lowest <i> so that
// "Person <i>" is available.
// Using native digits will break IsDefaultProfileName() below because
// it uses sscanf.
// TODO(jshin): fix IsDefaultProfileName to handle native digits.
name = name_index == 0
? l10n_util::GetStringUTF16(
IDS_PROFILE_MENU_PLACEHOLDER_PROFILE_NAME)
: l10n_util::GetStringFUTF16(IDS_NEW_NUMBERED_PROFILE_NAME,
base::NumberToString16(name_index));
// Loop through previously named profiles to ensure we're not duplicating.
std::vector<ProfileAttributesEntry*> entries =
const_cast<ProfileAttributesStorage*>(this)->GetAllProfilesAttributes();
if (std::ranges::none_of(entries, [name](ProfileAttributesEntry* entry) {
return entry->GetLocalProfileName() == name ||
entry->GetName() == name;
})) {
return name;
}
}
}
bool ProfileAttributesStorage::IsDefaultProfileName(
const std::u16string& name,
bool include_check_for_legacy_profile_name) const {
if (name ==
l10n_util::GetStringUTF16(IDS_PROFILE_MENU_PLACEHOLDER_PROFILE_NAME)) {
// Profile name is "Your Chrome".
return true;
}
// Check whether it's one of the "Person %d" style names.
std::u16string default_name_prefix =
l10n_util::GetStringFUTF16(IDS_NEW_NUMBERED_PROFILE_NAME, u"");
if (base::StartsWith(name, default_name_prefix)) {
int generic_profile_number; // Unused. Just a placeholder for StringToInt.
if (base::StringToInt(name.substr(default_name_prefix.length()),
&generic_profile_number)) {
return true;
}
}
#if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID)
if (!include_check_for_legacy_profile_name)
return false;
#endif
// Check if it's a "First user" old-style name.
if (name == l10n_util::GetStringUTF16(IDS_DEFAULT_PROFILE_NAME) ||
name == l10n_util::GetStringUTF16(IDS_LEGACY_DEFAULT_PROFILE_NAME))
return true;
// Check if it's one of the old-style profile names.
for (int default_name : kDefaultNames) {
if (name == l10n_util::GetStringUTF16(default_name)) {
return true;
}
}
return false;
}
size_t ProfileAttributesStorage::ChooseAvatarIconIndexForNewProfile() const {
std::unordered_set<size_t> used_icon_indices;
std::vector<ProfileAttributesEntry*> entries =
const_cast<ProfileAttributesStorage*>(this)->GetAllProfilesAttributes();
for (const ProfileAttributesEntry* entry : entries)
used_icon_indices.insert(entry->GetAvatarIconIndex());
return profiles::GetRandomAvatarIconIndex(used_icon_indices);
}
const gfx::Image* ProfileAttributesStorage::LoadAvatarPictureFromPath(
const base::FilePath& profile_path,
const std::string& key,
const base::FilePath& image_path) const {
// If the picture is already loaded then use it.
if (cached_avatar_images_.count(key)) {
if (cached_avatar_images_[key].IsEmpty())
return nullptr;
return &cached_avatar_images_[key];
}
// Don't download the image if downloading is disabled for tests.
if (disable_avatar_download_for_testing_)
return nullptr;
// If the picture is already being loaded then don't try loading it again.
if (cached_avatar_images_loading_[key])
return nullptr;
cached_avatar_images_loading_[key] = true;
file_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&ReadBitmap, image_path),
base::BindOnce(&ProfileAttributesStorage::OnAvatarPictureLoaded,
weak_ptr_factory_.GetWeakPtr(), profile_path, key));
return nullptr;
}
bool ProfileAttributesStorage::IsGAIAPictureLoaded(
const std::string& key) const {
return base::Contains(cached_avatar_images_, key);
}
void ProfileAttributesStorage::SaveGAIAImageAtPath(
const base::FilePath& profile_path,
const std::string& key,
gfx::Image image,
const base::FilePath& image_path,
const std::string& image_url_with_size) {
cached_avatar_images_.erase(key);
SaveAvatarImageAtPath(
profile_path, image, key, image_path,
base::BindOnce(&ProfileAttributesStorage::OnGAIAPictureSaved,
weak_ptr_factory_.GetWeakPtr(), image_url_with_size,
profile_path));
}
void ProfileAttributesStorage::DeleteGAIAImageAtPath(
const base::FilePath& profile_path,
const std::string& key,
const base::FilePath& image_path) {
cached_avatar_images_.erase(key);
file_task_runner_->PostTask(FROM_HERE,
base::BindOnce(&DeleteBitmap, image_path));
ProfileAttributesEntry* entry = GetProfileAttributesWithPath(profile_path);
DCHECK(entry);
entry->SetLastDownloadedGAIAPictureUrlWithSize(std::string());
}
void ProfileAttributesStorage::AddObserver(Observer* obs) {
observer_list_.AddObserver(obs);
}
void ProfileAttributesStorage::RemoveObserver(Observer* obs) {
observer_list_.RemoveObserver(obs);
}
#if !BUILDFLAG(IS_ANDROID)
void ProfileAttributesStorage::RecordDeletedProfileState(
ProfileAttributesEntry* entry) {
DCHECK(entry);
RecordProfileState(entry, profile_metrics::StateSuffix::kUponDeletion);
bool is_last_profile = GetNumberOfProfiles() <= 1u;
// If the profile has windows opened, they are still open at this moment.
// Thus, this really means that only the profile manager is open.
bool no_browser_windows = BrowserList::GetInstance()->empty();
profile_metrics::LogProfileDeletionContext(is_last_profile,
no_browser_windows);
}
#endif
void ProfileAttributesStorage::RecordProfilesState() {
std::vector<ProfileAttributesEntry*> entries = GetAllProfilesAttributes();
if (entries.size() == 0)
return;
MultiProfileUserType type = GetMultiProfileUserType(entries);
for (ProfileAttributesEntry* entry : entries) {
RecordProfileState(entry, profile_metrics::StateSuffix::kAll);
if (policy::ManagementServiceFactory::GetForPlatform()->IsManaged()) {
RecordProfileState(entry,
profile_metrics::StateSuffix::kAllManagedDevice);
} else {
RecordProfileState(entry,
profile_metrics::StateSuffix::kAllUnmanagedDevice);
}
if (entry->UserAcceptedAccountManagement()) {
RecordProfileState(
entry, profile_metrics::StateSuffix::kManagementDisclaimerAccepted);
} else {
RecordProfileState(
entry,
profile_metrics::StateSuffix::kManagementDisclaimerNotAccepted);
}
switch (type) {
case MultiProfileUserType::kSingleProfile:
RecordProfileState(entry, profile_metrics::StateSuffix::kSingleProfile);
break;
case MultiProfileUserType::kActiveMultiProfile:
RecordProfileState(entry,
profile_metrics::StateSuffix::kActiveMultiProfile);
break;
case MultiProfileUserType::kLatentMultiProfile: {
RecordProfileState(entry,
profile_metrics::StateSuffix::kLatentMultiProfile);
if (ProfileMetrics::IsProfileActive(entry)) {
RecordProfileState(
entry, profile_metrics::StateSuffix::kLatentMultiProfileActive);
} else {
RecordProfileState(
entry, profile_metrics::StateSuffix::kLatentMultiProfileOthers);
}
break;
}
}
}
}
void ProfileAttributesStorage::NotifyOnProfileAvatarChanged(
const base::FilePath& profile_path) const {
for (auto& observer : observer_list_)
observer.OnProfileAvatarChanged(profile_path);
}
void ProfileAttributesStorage::NotifyIsSigninRequiredChanged(
const base::FilePath& profile_path) const {
for (auto& observer : observer_list_)
observer.OnProfileSigninRequiredChanged(profile_path);
}
void ProfileAttributesStorage::NotifyProfileAuthInfoChanged(
const base::FilePath& profile_path) const {
for (auto& observer : observer_list_)
observer.OnProfileAuthInfoChanged(profile_path);
}
void ProfileAttributesStorage::NotifyIfProfileNamesHaveChanged() const {
std::vector<ProfileAttributesEntry*> entries = GetAllProfilesAttributes();
for (ProfileAttributesEntry* entry : entries) {
std::u16string old_display_name = entry->GetLastNameToDisplay();
if (entry->HasProfileNameChanged()) {
for (auto& observer : observer_list_)
observer.OnProfileNameChanged(entry->GetPath(), old_display_name);
}
}
}
void ProfileAttributesStorage::NotifyProfileSupervisedUserIdChanged(
const base::FilePath& profile_path) const {
for (auto& observer : observer_list_)
observer.OnProfileSupervisedUserIdChanged(profile_path);
}
void ProfileAttributesStorage::NotifyProfileIsOmittedChanged(
const base::FilePath& profile_path) const {
for (auto& observer : observer_list_)
observer.OnProfileIsOmittedChanged(profile_path);
}
void ProfileAttributesStorage::NotifyProfileThemeColorsChanged(
const base::FilePath& profile_path) const {
for (auto& observer : observer_list_)
observer.OnProfileThemeColorsChanged(profile_path);
}
void ProfileAttributesStorage::NotifyProfileHostedDomainChanged(
const base::FilePath& profile_path) const {
for (auto& observer : observer_list_)
observer.OnProfileHostedDomainChanged(profile_path);
}
void ProfileAttributesStorage::NotifyProfileIsManagedChanged(
const base::FilePath& profile_path) const {
for (auto& observer : observer_list_) {
observer.OnProfileIsManagedChanged(profile_path);
}
}
void ProfileAttributesStorage::NotifyOnProfileHighResAvatarLoaded(
const base::FilePath& profile_path) const {
for (auto& observer : observer_list_)
observer.OnProfileHighResAvatarLoaded(profile_path);
}
void ProfileAttributesStorage::NotifyProfileUserManagementAcceptanceChanged(
const base::FilePath& profile_path) const {
for (auto& observer : observer_list_)
observer.OnProfileUserManagementAcceptanceChanged(profile_path);
}
void ProfileAttributesStorage::NotifyProfileManagementEnrollmentTokenChanged(
const base::FilePath& profile_path) const {
for (auto& observer : observer_list_) {
observer.OnProfileManagementEnrollmentTokenChanged(profile_path);
}
}
void ProfileAttributesStorage::NotifyProfileManagementIdChanged(
const base::FilePath& profile_path) const {
for (auto& observer : observer_list_) {
observer.OnProfileManagementIdChanged(profile_path);
}
}
std::string ProfileAttributesStorage::StorageKeyFromProfilePath(
const base::FilePath& profile_path) const {
DCHECK_EQ(user_data_dir_, profile_path.DirName());
return profile_path.BaseName().AsUTF8Unsafe();
}
void ProfileAttributesStorage::DisableProfileMetricsForTesting() {
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
repeating_timer_.reset();
#endif
}
void ProfileAttributesStorage::DownloadHighResAvatarIfNeeded(
size_t icon_index,
const base::FilePath& profile_path) {
#if BUILDFLAG(IS_ANDROID)
return;
#endif
DCHECK(!disable_avatar_download_for_testing_);
// If this is the placeholder avatar, it is already included in the
// resources, so it doesn't need to be downloaded (and it will never be
// requested from disk by `ProfileAttributesEntry::GetHighResAvatar()`).
if (icon_index == profiles::GetPlaceholderAvatarIndex())
return;
const base::FilePath& file_path =
profiles::GetPathOfHighResAvatarAtIndex(icon_index);
base::OnceClosure callback =
base::BindOnce(&ProfileAttributesStorage::DownloadHighResAvatar,
weak_ptr_factory_.GetWeakPtr(), icon_index, profile_path);
file_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&RunCallbackIfFileMissing, file_path,
std::move(callback)));
}
void ProfileAttributesStorage::DownloadHighResAvatar(
size_t icon_index,
const base::FilePath& profile_path) {
#if !BUILDFLAG(IS_ANDROID)
const char* file_name =
profiles::GetDefaultAvatarIconFileNameAtIndex(icon_index);
DCHECK(file_name);
// If the file is already being downloaded, don't start another download.
if (avatar_images_downloads_in_progress_.count(file_name))
return;
// Start the download for this file. The profile attributes storage takes
// ownership of the avatar downloader, which will be deleted when the download
// completes, or if that never happens, when the storage is destroyed.
std::unique_ptr<ProfileAvatarDownloader>& current_downloader =
avatar_images_downloads_in_progress_[file_name];
current_downloader = std::make_unique<ProfileAvatarDownloader>(
icon_index,
base::BindOnce(&ProfileAttributesStorage::SaveAvatarImageAtPathNoCallback,
weak_ptr_factory_.GetWeakPtr(), profile_path));
current_downloader->Start();
#endif
}
void ProfileAttributesStorage::SaveAvatarImageAtPath(
const base::FilePath& profile_path,
gfx::Image image,
const std::string& key,
const base::FilePath& image_path,
base::OnceClosure callback) {
cached_avatar_images_[key] = image;
scoped_refptr<base::RefCountedMemory> png_data = image.As1xPNGBytes();
auto data = std::make_unique<ImageData>(png_data->size());
base::span(*data).copy_from(*png_data);
// Remove the file from the list of downloads in progress. Note that this list
// only contains the high resolution avatars, and not the Gaia profile images.
auto downloader_iter = avatar_images_downloads_in_progress_.find(key);
if (downloader_iter != avatar_images_downloads_in_progress_.end()) {
// We mustn't delete the avatar downloader right here, since we're being
// called by it.
content::GetUIThreadTaskRunner({})->DeleteSoon(
FROM_HERE, downloader_iter->second.release());
avatar_images_downloads_in_progress_.erase(downloader_iter);
}
if (data->empty()) {
LOG(ERROR) << "Failed to PNG encode the image.";
} else {
file_task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&SaveBitmap, std::move(data), image_path),
base::BindOnce(&ProfileAttributesStorage::OnAvatarPictureSaved,
weak_ptr_factory_.GetWeakPtr(), key, profile_path,
std::move(callback)));
}
}
ProfileAttributesEntry* ProfileAttributesStorage::InitEntryWithKey(
const std::string& key,
bool is_omitted) {
base::FilePath path =
user_data_dir_.Append(base::FilePath::FromUTF8Unsafe(key));
DCHECK(!base::Contains(profile_attributes_entries_, path.value()));
ProfileAttributesEntry* new_entry =
&profile_attributes_entries_[path.value()];
new_entry->Initialize(this, path, prefs_);
new_entry->SetIsOmittedInternal(is_omitted);
return new_entry;
}
void ProfileAttributesStorage::DownloadAvatars() {
#if !BUILDFLAG(IS_ANDROID)
std::vector<ProfileAttributesEntry*> entries = GetAllProfilesAttributes();
for (ProfileAttributesEntry* entry : entries) {
DownloadHighResAvatarIfNeeded(entry->GetAvatarIconIndex(),
entry->GetPath());
}
#endif
}
#if !BUILDFLAG(IS_ANDROID)
void ProfileAttributesStorage::LoadGAIAPictureIfNeeded() {
std::vector<ProfileAttributesEntry*> entries = GetAllProfilesAttributes();
for (ProfileAttributesEntry* entry : entries) {
if (entry->GetSigninState() == SigninState::kNotSignedIn)
continue;
bool is_using_GAIA_picture =
entry->GetBool(ProfileAttributesEntry::kUseGAIAPictureKey);
bool is_using_default_avatar = entry->IsUsingDefaultAvatar();
// Load from disk into memory GAIA picture if it exists.
if (is_using_GAIA_picture || is_using_default_avatar)
entry->GetGAIAPicture();
}
}
#endif
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
void ProfileAttributesStorage::MigrateLegacyProfileNamesAndRecomputeIfNeeded() {
std::vector<ProfileAttributesEntry*> entries = GetAllProfilesAttributes();
for (size_t i = 0; i < entries.size(); i++) {
std::u16string profile_name = entries[i]->GetLocalProfileName();
if (!entries[i]->IsUsingDefaultName())
continue;
// Migrate any legacy profile names ("First user", "Default Profile",
// "Saratoga", ...) to new style default names Person %n ("Person 1").
if (!IsDefaultProfileName(
profile_name, /*include_check_for_legacy_profile_name=*/false)) {
entries[i]->SetLocalProfileName(ChooseNameForNewProfile(),
/*is_default_name=*/true);
continue;
}
// Current profile name is Person %n.
// Rename duplicate default profile names, e.g.: Person 1, Person 1 to
// Person 1, Person 2.
for (size_t j = i + 1; j < entries.size(); j++) {
if (profile_name == entries[j]->GetLocalProfileName()) {
entries[j]->SetLocalProfileName(ChooseNameForNewProfile(),
/*is_default_name=*/true);
}
}
}
}
// static
void ProfileAttributesStorage::SetLegacyProfileMigrationForTesting(bool value) {
g_migration_enabled_for_testing = value;
}
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
void ProfileAttributesStorage::OnAvatarPictureLoaded(
const base::FilePath& profile_path,
const std::string& key,
gfx::Image image) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
cached_avatar_images_loading_[key] = false;
if (cached_avatar_images_.count(key)) {
if (!cached_avatar_images_[key].IsEmpty() || image.IsEmpty()) {
// If GAIA picture is not empty that means that it has been set with the
// most up-to-date value while the picture was being loaded from disk.
// If GAIA picture is empty and the image loaded from disk is also empty
// then there is no need to update.
return;
}
}
// Even if the image is empty (e.g. because decoding failed), place it in the
// cache to avoid reloading it again.
cached_avatar_images_[key] = std::move(image);
NotifyOnProfileHighResAvatarLoaded(profile_path);
}
void ProfileAttributesStorage::OnAvatarPictureSaved(
const std::string& file_name,
const base::FilePath& profile_path,
base::OnceClosure callback,
bool success) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!success)
return;
if (callback)
std::move(callback).Run();
NotifyOnProfileHighResAvatarLoaded(profile_path);
}
void ProfileAttributesStorage::OnGAIAPictureSaved(
const std::string& image_url_with_size,
const base::FilePath& profile_path) {
ProfileAttributesEntry* entry = GetProfileAttributesWithPath(profile_path);
// Profile could have been destroyed while saving picture to disk.
if (entry)
entry->SetLastDownloadedGAIAPictureUrlWithSize(image_url_with_size);
}
void ProfileAttributesStorage::SaveAvatarImageAtPathNoCallback(
const base::FilePath& profile_path,
gfx::Image image,
const std::string& key,
const base::FilePath& image_path) {
SaveAvatarImageAtPath(profile_path, image, key, image_path,
base::OnceClosure());
}
void ProfileAttributesStorage::
EnsureProfilesOrderPrefIsInitializedForTesting() {
EnsureProfilesOrderPrefIsInitialized();
}