1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/profiles/profile_attributes_storage.h"
6
7 #include <algorithm>
8 #include <unordered_set>
9 #include <utility>
10
11 #include "base/bind.h"
12 #include "base/files/file_util.h"
13 #include "base/i18n/number_formatting.h"
14 #include "base/i18n/string_compare.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/task/post_task.h"
19 #include "base/task/thread_pool.h"
20 #include "base/task_runner_util.h"
21 #include "base/threading/scoped_blocking_call.h"
22 #include "chrome/browser/profiles/profile_avatar_downloader.h"
23 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
24 #include "chrome/browser/profiles/profile_metrics.h"
25 #include "chrome/grit/generated_resources.h"
26 #include "components/policy/core/browser/browser_policy_connector.h"
27 #include "components/profile_metrics/state.h"
28 #include "content/public/browser/browser_task_traits.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "third_party/icu/source/i18n/unicode/coll.h"
31 #include "ui/base/l10n/l10n_util.h"
32 #include "ui/gfx/image/image.h"
33
34 #if !defined(OS_ANDROID)
35 #include "chrome/browser/ui/browser_list.h"
36 #endif
37
38 namespace {
39
40 using ImageData = std::vector<unsigned char>;
41
42 // First eight are generic icons, which use IDS_NUMBERED_PROFILE_NAME.
43 const int kDefaultNames[] = {
44 IDS_DEFAULT_AVATAR_NAME_8,
45 IDS_DEFAULT_AVATAR_NAME_9,
46 IDS_DEFAULT_AVATAR_NAME_10,
47 IDS_DEFAULT_AVATAR_NAME_11,
48 IDS_DEFAULT_AVATAR_NAME_12,
49 IDS_DEFAULT_AVATAR_NAME_13,
50 IDS_DEFAULT_AVATAR_NAME_14,
51 IDS_DEFAULT_AVATAR_NAME_15,
52 IDS_DEFAULT_AVATAR_NAME_16,
53 IDS_DEFAULT_AVATAR_NAME_17,
54 IDS_DEFAULT_AVATAR_NAME_18,
55 IDS_DEFAULT_AVATAR_NAME_19,
56 IDS_DEFAULT_AVATAR_NAME_20,
57 IDS_DEFAULT_AVATAR_NAME_21,
58 IDS_DEFAULT_AVATAR_NAME_22,
59 IDS_DEFAULT_AVATAR_NAME_23,
60 IDS_DEFAULT_AVATAR_NAME_24,
61 IDS_DEFAULT_AVATAR_NAME_25,
62 IDS_DEFAULT_AVATAR_NAME_26
63 };
64
65 enum class MultiProfileUserType {
66 kSingleProfile, // There is only one profile.
67 kActiveMultiProfile, // Several profiles are actively used.
68 kLatentMultiProfile // There are several profiles, but only one is actively
69 // used.
70 };
71
72 // Reads a PNG from disk and decodes it. If the bitmap was successfully read
73 // from disk then this will return the bitmap image, otherwise it will return
74 // an empty gfx::Image.
ReadBitmap(const base::FilePath & image_path)75 gfx::Image ReadBitmap(const base::FilePath& image_path) {
76 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
77 base::BlockingType::MAY_BLOCK);
78
79 // If the path doesn't exist, don't even try reading it.
80 if (!base::PathExists(image_path))
81 return gfx::Image();
82
83 std::string image_data;
84 if (!base::ReadFileToString(image_path, &image_data)) {
85 LOG(ERROR) << "Failed to read PNG file from disk.";
86 return gfx::Image();
87 }
88
89 gfx::Image image = gfx::Image::CreateFrom1xPNGBytes(
90 base::RefCountedString::TakeString(&image_data));
91 if (image.IsEmpty())
92 LOG(ERROR) << "Failed to decode PNG file.";
93
94 return image;
95 }
96
97 // Writes |data| to disk and takes ownership of the pointer. On successful
98 // completion, it runs |callback|.
SaveBitmap(std::unique_ptr<ImageData> data,const base::FilePath & image_path)99 bool SaveBitmap(std::unique_ptr<ImageData> data,
100 const base::FilePath& image_path) {
101 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
102 base::BlockingType::MAY_BLOCK);
103
104 // Make sure the destination directory exists.
105 base::FilePath dir = image_path.DirName();
106 if (!base::DirectoryExists(dir) && !base::CreateDirectory(dir)) {
107 LOG(ERROR) << "Failed to create parent directory.";
108 return false;
109 }
110
111 if (base::WriteFile(image_path, reinterpret_cast<char*>(&(*data)[0]),
112 data->size()) == -1) {
113 LOG(ERROR) << "Failed to save image to file.";
114 return false;
115 }
116 return true;
117 }
118
RunCallbackIfFileMissing(const base::FilePath & file_path,const base::Closure & callback)119 void RunCallbackIfFileMissing(const base::FilePath& file_path,
120 const base::Closure& callback) {
121 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
122 base::BlockingType::MAY_BLOCK);
123 if (!base::PathExists(file_path))
124 content::GetUIThreadTaskRunner({})->PostTask(FROM_HERE, callback);
125 }
126
127 // Compares two ProfileAttributesEntry using locale-sensitive comparison of
128 // their names. For ties, the profile path is compared next.
129 class ProfileAttributesSortComparator {
130 public:
131 explicit ProfileAttributesSortComparator(icu::Collator* collator);
132 bool operator()(const ProfileAttributesEntry* const a,
133 const ProfileAttributesEntry* const b) const;
134 private:
135 icu::Collator* collator_;
136 };
137
ProfileAttributesSortComparator(icu::Collator * collator)138 ProfileAttributesSortComparator::ProfileAttributesSortComparator(
139 icu::Collator* collator) : collator_(collator) {}
140
operator ()(const ProfileAttributesEntry * const a,const ProfileAttributesEntry * const b) const141 bool ProfileAttributesSortComparator::operator()(
142 const ProfileAttributesEntry* const a,
143 const ProfileAttributesEntry* const b) const {
144 UCollationResult result = base::i18n::CompareString16WithCollator(
145 *collator_, a->GetName(), b->GetName());
146 if (result != UCOL_EQUAL)
147 return result == UCOL_LESS;
148
149 // If the names are the same, then compare the paths, which must be unique.
150 return a->GetPath().value() < b->GetPath().value();
151 }
152
GetMultiProfileUserType(const std::vector<ProfileAttributesEntry * > & entries)153 MultiProfileUserType GetMultiProfileUserType(
154 const std::vector<ProfileAttributesEntry*>& entries) {
155 DCHECK_GT(entries.size(), 0u);
156 if (entries.size() == 1u)
157 return MultiProfileUserType::kSingleProfile;
158
159 int active_count = std::count_if(
160 entries.begin(), entries.end(), [](ProfileAttributesEntry* entry) {
161 return ProfileMetrics::IsProfileActive(entry);
162 });
163
164 if (active_count <= 1)
165 return MultiProfileUserType::kLatentMultiProfile;
166 return MultiProfileUserType::kActiveMultiProfile;
167 }
168
GetAvatarState(ProfileAttributesEntry * entry)169 profile_metrics::AvatarState GetAvatarState(ProfileAttributesEntry* entry) {
170 size_t index = entry->GetAvatarIconIndex();
171 bool is_modern = profiles::IsModernAvatarIconIndex(index);
172 if (entry->GetSigninState() == SigninState::kNotSignedIn) {
173 if (index == profiles::GetPlaceholderAvatarIndex())
174 return profile_metrics::AvatarState::kSignedOutDefault;
175 return is_modern ? profile_metrics::AvatarState::kSignedOutModern
176 : profile_metrics::AvatarState::kSignedOutOld;
177 }
178 if (entry->IsUsingGAIAPicture())
179 return profile_metrics::AvatarState::kSignedInGaia;
180 return is_modern ? profile_metrics::AvatarState::kSignedInModern
181 : profile_metrics::AvatarState::kSignedInOld;
182 }
183
GetNameState(ProfileAttributesEntry * entry)184 profile_metrics::NameState GetNameState(ProfileAttributesEntry* entry) {
185 bool has_default_name = entry->IsUsingDefaultName();
186 switch (entry->GetNameForm()) {
187 case NameForm::kGaiaName:
188 return profile_metrics::NameState::kGaiaName;
189 case NameForm::kLocalName:
190 return has_default_name ? profile_metrics::NameState::kDefaultName
191 : profile_metrics::NameState::kCustomName;
192 case NameForm::kGaiaAndLocalName:
193 return has_default_name ? profile_metrics::NameState::kGaiaAndDefaultName
194 : profile_metrics::NameState::kGaiaAndCustomName;
195 }
196 }
197
GetUnconsentedPrimaryAccountType(ProfileAttributesEntry * entry)198 profile_metrics::UnconsentedPrimaryAccountType GetUnconsentedPrimaryAccountType(
199 ProfileAttributesEntry* entry) {
200 if (entry->GetSigninState() == SigninState::kNotSignedIn)
201 return profile_metrics::UnconsentedPrimaryAccountType::kSignedOut;
202 if (entry->IsChild())
203 return profile_metrics::UnconsentedPrimaryAccountType::kChild;
204 // TODO(crbug.com/1060113): Replace this check by
205 // !entry->GetHostedDomain().has_value() in M84 (once the cache gets
206 // reasonably well populated).
207 if (policy::BrowserPolicyConnector::IsNonEnterpriseUser(
208 base::UTF16ToUTF8(entry->GetUserName()))) {
209 return profile_metrics::UnconsentedPrimaryAccountType::kConsumer;
210 }
211 // TODO(crbug.com/1060113): Figure out how to distinguish EDU accounts from
212 // other enterprise.
213 return profile_metrics::UnconsentedPrimaryAccountType::kEnterprise;
214 }
215
RecordProfileState(ProfileAttributesEntry * entry,profile_metrics::StateSuffix suffix)216 void RecordProfileState(ProfileAttributesEntry* entry,
217 profile_metrics::StateSuffix suffix) {
218 profile_metrics::LogProfileAvatar(GetAvatarState(entry), suffix);
219 profile_metrics::LogProfileName(GetNameState(entry), suffix);
220 profile_metrics::LogProfileAccountType(
221 GetUnconsentedPrimaryAccountType(entry), suffix);
222 profile_metrics::LogProfileSyncEnabled(
223 entry->GetSigninState() ==
224 SigninState::kSignedInWithConsentedPrimaryAccount,
225 suffix);
226 profile_metrics::LogProfileDaysSinceLastUse(
227 (base::Time::Now() - entry->GetActiveTime()).InDays(), suffix);
228 }
229
230 } // namespace
231
ProfileAttributesStorage(PrefService * prefs)232 ProfileAttributesStorage::ProfileAttributesStorage(PrefService* prefs)
233 : prefs_(prefs),
234 file_task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
235 {base::MayBlock(), base::TaskPriority::USER_VISIBLE,
236 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {}
237
~ProfileAttributesStorage()238 ProfileAttributesStorage::~ProfileAttributesStorage() {
239 }
240
241 std::vector<ProfileAttributesEntry*>
GetAllProfilesAttributes()242 ProfileAttributesStorage::GetAllProfilesAttributes() {
243 std::vector<ProfileAttributesEntry*> ret;
244 for (const auto& path_and_entry : profile_attributes_entries_) {
245 ProfileAttributesEntry* entry;
246 // Initialize any entries that are not yet initialized.
247 bool success = GetProfileAttributesWithPath(
248 base::FilePath(path_and_entry.first), &entry);
249 DCHECK(success);
250 ret.push_back(entry);
251 }
252 return ret;
253 }
254
255 std::vector<ProfileAttributesEntry*>
GetAllProfilesAttributesSortedByName()256 ProfileAttributesStorage::GetAllProfilesAttributesSortedByName() {
257 std::vector<ProfileAttributesEntry*> ret = GetAllProfilesAttributes();
258 // Do not allocate the collator and sort if it is not necessary.
259 if (ret.size() < 2)
260 return ret;
261
262 UErrorCode error_code = U_ZERO_ERROR;
263 // Use the default collator. The default locale should have been properly
264 // set by the time this constructor is called.
265 std::unique_ptr<icu::Collator> collator(
266 icu::Collator::createInstance(error_code));
267 DCHECK(U_SUCCESS(error_code));
268
269 std::sort(ret.begin(), ret.end(),
270 ProfileAttributesSortComparator(collator.get()));
271 return ret;
272 }
273
ChooseNameForNewProfile(size_t icon_index) const274 base::string16 ProfileAttributesStorage::ChooseNameForNewProfile(
275 size_t icon_index) const {
276 base::string16 name;
277 for (int name_index = 1; ; ++name_index) {
278 #if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
279 // Using native digits will break IsDefaultProfileName() below because
280 // it uses sscanf.
281 // TODO(jshin): fix IsDefaultProfileName to handle native digits.
282 name = l10n_util::GetStringFUTF16(IDS_NEW_NUMBERED_PROFILE_NAME,
283 base::NumberToString16(name_index));
284 #else
285 // TODO(crbug.com/937834): Clean up this code.
286 if (icon_index < profiles::GetGenericAvatarIconCount() ||
287 profiles::IsModernAvatarIconIndex(icon_index)) {
288 name = l10n_util::GetStringFUTF16Int(IDS_NUMBERED_PROFILE_NAME,
289 name_index);
290 } else {
291 // TODO(jshin): Check with UX if appending |name_index| to the default
292 // name without a space is intended.
293 name = l10n_util::GetStringUTF16(
294 kDefaultNames[icon_index - profiles::GetGenericAvatarIconCount()]);
295 if (name_index > 1)
296 name.append(base::FormatNumber(name_index));
297 }
298 #endif
299
300 // Loop through previously named profiles to ensure we're not duplicating.
301 std::vector<ProfileAttributesEntry*> entries =
302 const_cast<ProfileAttributesStorage*>(this)->GetAllProfilesAttributes();
303
304 if (std::none_of(entries.begin(), entries.end(),
305 [name](ProfileAttributesEntry* entry) {
306 return entry->GetLocalProfileName() == name ||
307 entry->GetName() == name;
308 })) {
309 return name;
310 }
311 }
312 }
313
IsDefaultProfileName(const base::string16 & name,bool include_check_for_legacy_profile_name) const314 bool ProfileAttributesStorage::IsDefaultProfileName(
315 const base::string16& name,
316 bool include_check_for_legacy_profile_name) const {
317 // Check whether it's one of the "Person %d" style names.
318 std::string default_name_format = l10n_util::GetStringFUTF8(
319 IDS_NEW_NUMBERED_PROFILE_NAME, base::ASCIIToUTF16("%d"));
320 int generic_profile_number; // Unused. Just a placeholder for sscanf.
321 int assignments =
322 sscanf(base::UTF16ToUTF8(name).c_str(), default_name_format.c_str(),
323 &generic_profile_number);
324 if (assignments == 1)
325 return true;
326
327 #if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
328 if (!include_check_for_legacy_profile_name)
329 return false;
330 #endif
331
332 // Check if it's a "First user" old-style name.
333 if (name == l10n_util::GetStringUTF16(IDS_DEFAULT_PROFILE_NAME) ||
334 name == l10n_util::GetStringUTF16(IDS_LEGACY_DEFAULT_PROFILE_NAME))
335 return true;
336
337 // Check if it's one of the old-style profile names.
338 for (size_t i = 0; i < base::size(kDefaultNames); ++i) {
339 if (name == l10n_util::GetStringUTF16(kDefaultNames[i]))
340 return true;
341 }
342 return false;
343 }
344
ChooseAvatarIconIndexForNewProfile() const345 size_t ProfileAttributesStorage::ChooseAvatarIconIndexForNewProfile() const {
346 std::unordered_set<size_t> used_icon_indices;
347
348 std::vector<ProfileAttributesEntry*> entries =
349 const_cast<ProfileAttributesStorage*>(this)->GetAllProfilesAttributes();
350 for (const ProfileAttributesEntry* entry : entries)
351 used_icon_indices.insert(entry->GetAvatarIconIndex());
352
353 return profiles::GetRandomAvatarIconIndex(used_icon_indices);
354 }
355
LoadAvatarPictureFromPath(const base::FilePath & profile_path,const std::string & key,const base::FilePath & image_path) const356 const gfx::Image* ProfileAttributesStorage::LoadAvatarPictureFromPath(
357 const base::FilePath& profile_path,
358 const std::string& key,
359 const base::FilePath& image_path) const {
360 // If the picture is already loaded then use it.
361 if (cached_avatar_images_.count(key)) {
362 if (cached_avatar_images_[key].IsEmpty())
363 return nullptr;
364 return &cached_avatar_images_[key];
365 }
366
367 // Don't download the image if downloading is disabled for tests.
368 if (disable_avatar_download_for_testing_)
369 return nullptr;
370
371 // If the picture is already being loaded then don't try loading it again.
372 if (cached_avatar_images_loading_[key])
373 return nullptr;
374 cached_avatar_images_loading_[key] = true;
375
376 base::PostTaskAndReplyWithResult(
377 file_task_runner_.get(), FROM_HERE,
378 base::BindOnce(&ReadBitmap, image_path),
379 base::BindOnce(&ProfileAttributesStorage::OnAvatarPictureLoaded,
380 const_cast<ProfileAttributesStorage*>(this)->AsWeakPtr(),
381 profile_path, key));
382 return nullptr;
383 }
384
AddObserver(Observer * obs)385 void ProfileAttributesStorage::AddObserver(Observer* obs) {
386 observer_list_.AddObserver(obs);
387 }
388
RemoveObserver(Observer * obs)389 void ProfileAttributesStorage::RemoveObserver(Observer* obs) {
390 observer_list_.RemoveObserver(obs);
391 }
392
393 #if !defined(OS_ANDROID)
RecordDeletedProfileState(ProfileAttributesEntry * entry)394 void ProfileAttributesStorage::RecordDeletedProfileState(
395 ProfileAttributesEntry* entry) {
396 DCHECK(entry);
397 RecordProfileState(entry, profile_metrics::StateSuffix::kUponDeletion);
398 bool is_last_profile = GetNumberOfProfiles() <= 1u;
399 // If the profile has windows opened, they are still open at this moment.
400 // Thus, this really means that only the profile manager is open.
401 bool no_browser_windows = BrowserList::GetInstance()->empty();
402 profile_metrics::LogProfileDeletionContext(is_last_profile,
403 no_browser_windows);
404 }
405 #endif
406
RecordProfilesState()407 void ProfileAttributesStorage::RecordProfilesState() {
408 std::vector<ProfileAttributesEntry*> entries = GetAllProfilesAttributes();
409 if (entries.size() == 0)
410 return;
411
412 MultiProfileUserType type = GetMultiProfileUserType(entries);
413
414 for (ProfileAttributesEntry* entry : entries) {
415 RecordProfileState(entry, profile_metrics::StateSuffix::kAll);
416
417 switch (type) {
418 case MultiProfileUserType::kSingleProfile:
419 RecordProfileState(entry, profile_metrics::StateSuffix::kSingleProfile);
420 break;
421 case MultiProfileUserType::kActiveMultiProfile:
422 RecordProfileState(entry,
423 profile_metrics::StateSuffix::kActiveMultiProfile);
424 break;
425 case MultiProfileUserType::kLatentMultiProfile: {
426 RecordProfileState(entry,
427 profile_metrics::StateSuffix::kLatentMultiProfile);
428 if (ProfileMetrics::IsProfileActive(entry)) {
429 RecordProfileState(
430 entry, profile_metrics::StateSuffix::kLatentMultiProfileActive);
431 } else {
432 RecordProfileState(
433 entry, profile_metrics::StateSuffix::kLatentMultiProfileOthers);
434 }
435 break;
436 }
437 }
438 }
439 }
440
NotifyOnProfileAvatarChanged(const base::FilePath & profile_path) const441 void ProfileAttributesStorage::NotifyOnProfileAvatarChanged(
442 const base::FilePath& profile_path) const {
443 for (auto& observer : observer_list_)
444 observer.OnProfileAvatarChanged(profile_path);
445 }
446
NotifyOnProfileHighResAvatarLoaded(const base::FilePath & profile_path) const447 void ProfileAttributesStorage::NotifyOnProfileHighResAvatarLoaded(
448 const base::FilePath& profile_path) const {
449 for (auto& observer : observer_list_)
450 observer.OnProfileHighResAvatarLoaded(profile_path);
451 }
452
DownloadHighResAvatarIfNeeded(size_t icon_index,const base::FilePath & profile_path)453 void ProfileAttributesStorage::DownloadHighResAvatarIfNeeded(
454 size_t icon_index,
455 const base::FilePath& profile_path) {
456 #if defined(OS_ANDROID)
457 return;
458 #endif
459 DCHECK(!disable_avatar_download_for_testing_);
460
461 // If this is the placeholder avatar, it is already included in the
462 // resources, so it doesn't need to be downloaded (and it will never be
463 // requested from disk by GetHighResAvatarOfProfileAtIndex).
464 if (icon_index == profiles::GetPlaceholderAvatarIndex())
465 return;
466
467 const base::FilePath& file_path =
468 profiles::GetPathOfHighResAvatarAtIndex(icon_index);
469 base::Closure callback =
470 base::Bind(&ProfileAttributesStorage::DownloadHighResAvatar, AsWeakPtr(),
471 icon_index, profile_path);
472 file_task_runner_->PostTask(
473 FROM_HERE,
474 base::BindOnce(&RunCallbackIfFileMissing, file_path, callback));
475 }
476
DownloadHighResAvatar(size_t icon_index,const base::FilePath & profile_path)477 void ProfileAttributesStorage::DownloadHighResAvatar(
478 size_t icon_index,
479 const base::FilePath& profile_path) {
480 #if !defined(OS_ANDROID)
481 const char* file_name =
482 profiles::GetDefaultAvatarIconFileNameAtIndex(icon_index);
483 DCHECK(file_name);
484 // If the file is already being downloaded, don't start another download.
485 if (avatar_images_downloads_in_progress_.count(file_name))
486 return;
487
488 // Start the download for this file. The profile attributes storage takes
489 // ownership of the avatar downloader, which will be deleted when the download
490 // completes, or if that never happens, when the storage is destroyed.
491 std::unique_ptr<ProfileAvatarDownloader>& current_downloader =
492 avatar_images_downloads_in_progress_[file_name];
493 current_downloader.reset(new ProfileAvatarDownloader(
494 icon_index,
495 base::BindOnce(&ProfileAttributesStorage::SaveAvatarImageAtPathNoCallback,
496 AsWeakPtr(), profile_path)));
497
498 current_downloader->Start();
499 #endif
500 }
501
SaveAvatarImageAtPath(const base::FilePath & profile_path,gfx::Image image,const std::string & key,const base::FilePath & image_path,base::OnceClosure callback)502 void ProfileAttributesStorage::SaveAvatarImageAtPath(
503 const base::FilePath& profile_path,
504 gfx::Image image,
505 const std::string& key,
506 const base::FilePath& image_path,
507 base::OnceClosure callback) {
508 cached_avatar_images_[key] = image;
509
510 std::unique_ptr<ImageData> data(new ImageData);
511 scoped_refptr<base::RefCountedMemory> png_data = image.As1xPNGBytes();
512 data->assign(png_data->front(), png_data->front() + png_data->size());
513
514 // Remove the file from the list of downloads in progress. Note that this list
515 // only contains the high resolution avatars, and not the Gaia profile images.
516 auto downloader_iter = avatar_images_downloads_in_progress_.find(key);
517 if (downloader_iter != avatar_images_downloads_in_progress_.end()) {
518 // We mustn't delete the avatar downloader right here, since we're being
519 // called by it.
520 content::GetUIThreadTaskRunner({})->DeleteSoon(
521 FROM_HERE, downloader_iter->second.release());
522 avatar_images_downloads_in_progress_.erase(downloader_iter);
523 }
524
525 if (data->empty()) {
526 LOG(ERROR) << "Failed to PNG encode the image.";
527 } else {
528 base::PostTaskAndReplyWithResult(
529 file_task_runner_.get(), FROM_HERE,
530 base::BindOnce(&SaveBitmap, std::move(data), image_path),
531 base::BindOnce(&ProfileAttributesStorage::OnAvatarPictureSaved,
532 AsWeakPtr(), key, profile_path, std::move(callback)));
533 }
534 }
535
OnAvatarPictureLoaded(const base::FilePath & profile_path,const std::string & key,gfx::Image image) const536 void ProfileAttributesStorage::OnAvatarPictureLoaded(
537 const base::FilePath& profile_path,
538 const std::string& key,
539 gfx::Image image) const {
540 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
541 cached_avatar_images_loading_[key] = false;
542 if (cached_avatar_images_.count(key)) {
543 if (!cached_avatar_images_[key].IsEmpty() || image.IsEmpty()) {
544 // If GAIA picture is not empty that means that it has been set with the
545 // most up-to-date value while the picture was being loaded from disk.
546 // If GAIA picture is empty and the image loaded from disk is also empty
547 // then there is no need to update.
548 return;
549 }
550 }
551
552 // Even if the image is empty (e.g. because decoding failed), place it in the
553 // cache to avoid reloading it again.
554 cached_avatar_images_[key] = std::move(image);
555
556 NotifyOnProfileHighResAvatarLoaded(profile_path);
557 }
558
OnAvatarPictureSaved(const std::string & file_name,const base::FilePath & profile_path,base::OnceClosure callback,bool success) const559 void ProfileAttributesStorage::OnAvatarPictureSaved(
560 const std::string& file_name,
561 const base::FilePath& profile_path,
562 base::OnceClosure callback,
563 bool success) const {
564 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
565 if (!success)
566 return;
567
568 if (callback)
569 std::move(callback).Run();
570
571 NotifyOnProfileHighResAvatarLoaded(profile_path);
572 }
573
SaveAvatarImageAtPathNoCallback(const base::FilePath & profile_path,gfx::Image image,const std::string & key,const base::FilePath & image_path)574 void ProfileAttributesStorage::SaveAvatarImageAtPathNoCallback(
575 const base::FilePath& profile_path,
576 gfx::Image image,
577 const std::string& key,
578 const base::FilePath& image_path) {
579 SaveAvatarImageAtPath(profile_path, image, key, image_path,
580 base::OnceClosure());
581 }
582