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