| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chromeos/login/user_image_manager_impl.h" |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/debug/trace_event.h" |
| #include "base/file_util.h" |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram.h" |
| #include "base/path_service.h" |
| #include "base/prefs/pref_registry_simple.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/prefs/scoped_user_pref_update.h" |
| #include "base/rand_util.h" |
| #include "base/threading/worker_pool.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/chromeos/login/default_user_images.h" |
| #include "chrome/browser/chromeos/login/helper.h" |
| #include "chrome/browser/chromeos/login/user_image.h" |
| #include "chrome/browser/chromeos/login/user_image_sync_observer.h" |
| #include "chrome/browser/chromeos/login/user_manager.h" |
| #include "chrome/browser/profiles/profile_downloader.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chromeos/chromeos_switches.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/common/url_constants.h" |
| #include "ui/base/webui/web_ui_util.h" |
| #include "ui/gfx/image/image_skia.h" |
| |
| using content::BrowserThread; |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| // A dictionary that maps usernames to old user image data with images stored in |
| // PNG format. Deprecated. |
| // TODO(ivankr): remove this const char after migration is gone. |
| const char kUserImages[] = "UserImages"; |
| |
| // A dictionary that maps usernames to user image data with images stored in |
| // JPEG format. |
| const char kUserImageProperties[] = "user_image_info"; |
| |
| // Names of user image properties. |
| const char kImagePathNodeName[] = "path"; |
| const char kImageIndexNodeName[] = "index"; |
| const char kImageURLNodeName[] = "url"; |
| |
| // Delay betweeen user login and user image migration. |
| const int kUserImageMigrationDelaySec = 50; |
| |
| // Delay betweeen user login and attempt to update user's profile data. |
| const int kProfileDataDownloadDelaySec = 10; |
| |
| // Interval betweeen retries to update user's profile data. |
| const int kProfileDataDownloadRetryIntervalSec = 300; |
| |
| // Delay betweeen subsequent profile refresh attempts (24 hrs). |
| const int kProfileRefreshIntervalSec = 24 * 3600; |
| |
| const char kSafeImagePathExtension[] = ".jpg"; |
| |
| // Enum for reporting histograms about profile picture download. |
| enum ProfileDownloadResult { |
| kDownloadSuccessChanged, |
| kDownloadSuccess, |
| kDownloadFailure, |
| kDownloadDefault, |
| kDownloadCached, |
| |
| // Must be the last, convenient count. |
| kDownloadResultsCount |
| }; |
| |
| // Time histogram prefix for a cached profile image download. |
| const char kProfileDownloadCachedTime[] = |
| "UserImage.ProfileDownloadTime.Cached"; |
| // Time histogram prefix for the default profile image download. |
| const char kProfileDownloadDefaultTime[] = |
| "UserImage.ProfileDownloadTime.Default"; |
| // Time histogram prefix for a failed profile image download. |
| const char kProfileDownloadFailureTime[] = |
| "UserImage.ProfileDownloadTime.Failure"; |
| // Time histogram prefix for a successful profile image download. |
| const char kProfileDownloadSuccessTime[] = |
| "UserImage.ProfileDownloadTime.Success"; |
| // Time histogram suffix for a profile image download after login. |
| const char kProfileDownloadReasonLoggedIn[] = "LoggedIn"; |
| // Time histogram suffix for a scheduled profile image download. |
| const char kProfileDownloadReasonScheduled[] = "Scheduled"; |
| // Time histogram suffix for a profile image download retry. |
| const char kProfileDownloadReasonRetry[] = "Retry"; |
| |
| // Add a histogram showing the time it takes to download a profile image. |
| // Separate histograms are reported for each download |reason| and |result|. |
| void AddProfileImageTimeHistogram(ProfileDownloadResult result, |
| const std::string& download_reason, |
| const base::TimeDelta& time_delta) { |
| std::string histogram_name; |
| switch (result) { |
| case kDownloadFailure: |
| histogram_name = kProfileDownloadFailureTime; |
| break; |
| case kDownloadDefault: |
| histogram_name = kProfileDownloadDefaultTime; |
| break; |
| case kDownloadSuccess: |
| histogram_name = kProfileDownloadSuccessTime; |
| break; |
| case kDownloadCached: |
| histogram_name = kProfileDownloadCachedTime; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| if (!download_reason.empty()) { |
| histogram_name += "."; |
| histogram_name += download_reason; |
| } |
| |
| static const base::TimeDelta min_time = base::TimeDelta::FromMilliseconds(1); |
| static const base::TimeDelta max_time = base::TimeDelta::FromSeconds(50); |
| const size_t bucket_count(50); |
| |
| base::HistogramBase* counter = base::Histogram::FactoryTimeGet( |
| histogram_name, min_time, max_time, bucket_count, |
| base::HistogramBase::kUmaTargetedHistogramFlag); |
| counter->AddTime(time_delta); |
| |
| DVLOG(1) << "Profile image download time: " << time_delta.InSecondsF(); |
| } |
| |
| // Deletes image file. |
| void DeleteImageFile(const std::string& image_path) { |
| if (image_path.empty()) |
| return; |
| base::FilePath fp(image_path); |
| BrowserThread::PostTask( |
| BrowserThread::FILE, |
| FROM_HERE, |
| base::Bind(base::IgnoreResult(&base::DeleteFile), |
| fp, /* recursive= */ false)); |
| } |
| |
| // Converts |image_index| to UMA histogram value. |
| int ImageIndexToHistogramIndex(int image_index) { |
| switch (image_index) { |
| case User::kExternalImageIndex: |
| // TODO(ivankr): Distinguish this from selected from file. |
| return kHistogramImageFromCamera; |
| case User::kProfileImageIndex: |
| return kHistogramImageFromProfile; |
| default: |
| return image_index; |
| } |
| } |
| |
| } // namespace |
| |
| // static |
| int UserImageManagerImpl::user_image_migration_delay_sec = |
| kUserImageMigrationDelaySec; |
| |
| // static |
| void UserImageManager::RegisterPrefs(PrefRegistrySimple* registry) { |
| registry->RegisterDictionaryPref(kUserImages); |
| registry->RegisterDictionaryPref(kUserImageProperties); |
| } |
| |
| UserImageManagerImpl::UserImageManagerImpl() |
| : image_loader_(new UserImageLoader(ImageDecoder::ROBUST_JPEG_CODEC)), |
| unsafe_image_loader_(new UserImageLoader(ImageDecoder::DEFAULT_CODEC)), |
| last_image_set_async_(false), |
| downloaded_profile_image_data_url_(content::kAboutBlankURL), |
| downloading_profile_image_(false), |
| migrate_current_user_on_load_(false) { |
| } |
| |
| UserImageManagerImpl::~UserImageManagerImpl() { |
| } |
| |
| void UserImageManagerImpl::LoadUserImages(const UserList& users) { |
| PrefService* local_state = g_browser_process->local_state(); |
| const DictionaryValue* prefs_images_unsafe = |
| local_state->GetDictionary(kUserImages); |
| const DictionaryValue* prefs_images = |
| local_state->GetDictionary(kUserImageProperties); |
| if (!prefs_images && !prefs_images_unsafe) |
| return; |
| |
| for (UserList::const_iterator it = users.begin(); it != users.end(); ++it) { |
| User* user = *it; |
| const base::DictionaryValue* image_properties = NULL; |
| bool needs_migration = false; // |true| if user has image in old format. |
| bool safe_source = false; // |true| if image loaded from safe source. |
| |
| if (prefs_images_unsafe) { |
| needs_migration = prefs_images_unsafe->GetDictionaryWithoutPathExpansion( |
| user->email(), &image_properties); |
| } |
| if (prefs_images) { |
| safe_source = prefs_images->GetDictionaryWithoutPathExpansion( |
| user->email(), &image_properties); |
| } |
| |
| if (needs_migration) |
| users_to_migrate_.insert(user->email()); |
| |
| if (!image_properties) { |
| SetInitialUserImage(user->email()); |
| } else { |
| int image_index = User::kInvalidImageIndex; |
| image_properties->GetInteger(kImageIndexNodeName, &image_index); |
| |
| if (image_index >= 0 && image_index < kDefaultImagesCount) { |
| user->SetImage(UserImage(GetDefaultImage(image_index)), |
| image_index); |
| } else if (image_index == User::kExternalImageIndex || |
| image_index == User::kProfileImageIndex) { |
| std::string image_path; |
| image_properties->GetString(kImagePathNodeName, &image_path); |
| // Path may be empty for profile images (meaning that the image |
| // hasn't been downloaded for the first time yet, in which case a |
| // download will be scheduled for |kProfileDataDownloadDelayMs| |
| // after user logs in). |
| DCHECK(!image_path.empty() || image_index == User::kProfileImageIndex); |
| std::string image_url; |
| image_properties->GetString(kImageURLNodeName, &image_url); |
| GURL image_gurl(image_url); |
| // Until image has been loaded, use the stub image (gray avatar). |
| user->SetStubImage(image_index, true); |
| user->SetImageURL(image_gurl); |
| if (!image_path.empty()) { |
| if (needs_migration) { |
| // Non-JPG image will be migrated once user logs in. |
| // Stub image will be used for now. Continue with other users. |
| continue; |
| } |
| DCHECK(safe_source); |
| if (!safe_source) |
| continue; |
| |
| // Load user image asynchronously - at this point we are able to use |
| // JPEG image loaded since image comes from safe pref source |
| // i.e. converted to JPEG. |
| image_loader_->Start( |
| image_path, 0 /* no resize */, |
| base::Bind(&UserImageManagerImpl::SetUserImage, |
| base::Unretained(this), |
| user->email(), image_index, image_gurl)); |
| } |
| } else { |
| NOTREACHED(); |
| } |
| } |
| } |
| } |
| |
| void UserImageManagerImpl::UserLoggedIn(const std::string& email, |
| bool user_is_new, |
| bool user_is_local) { |
| User* user = UserManager::Get()->GetLoggedInUser(); |
| if (user_is_new) { |
| if (!user_is_local) |
| SetInitialUserImage(email); |
| } else { |
| if (!user_is_local) { |
| // If current user image is profile image, it needs to be refreshed. |
| bool download_profile_image = |
| user->image_index() == User::kProfileImageIndex; |
| if (download_profile_image) |
| InitDownloadedProfileImage(); |
| |
| // Download user's profile data (full name and optionally image) to see if |
| // it has changed. |
| BrowserThread::PostDelayedTask( |
| BrowserThread::UI, |
| FROM_HERE, |
| base::Bind(&UserImageManagerImpl::DownloadProfileData, |
| base::Unretained(this), |
| kProfileDownloadReasonLoggedIn, |
| download_profile_image), |
| base::TimeDelta::FromSeconds(kProfileDataDownloadDelaySec)); |
| } |
| |
| UMA_HISTOGRAM_ENUMERATION("UserImage.LoggedIn", |
| ImageIndexToHistogramIndex(user->image_index()), |
| kHistogramImagesCount); |
| |
| if (users_to_migrate_.count(email)) { |
| const DictionaryValue* prefs_images_unsafe = |
| g_browser_process->local_state()->GetDictionary(kUserImages); |
| const base::DictionaryValue* image_properties = NULL; |
| if (prefs_images_unsafe->GetDictionaryWithoutPathExpansion( |
| user->email(), &image_properties)) { |
| std::string image_path; |
| image_properties->GetString(kImagePathNodeName, &image_path); |
| if (!image_path.empty()) { |
| // User needs image format migration but |
| // first we need to load and decode that image. |
| LOG(INFO) << "Waiting for user image to load before migration"; |
| migrate_current_user_on_load_ = true; |
| unsafe_image_loader_->Start( |
| image_path, 0 /* no resize */, |
| base::Bind(&UserImageManagerImpl::SetUserImage, |
| base::Unretained(this), |
| user->email(), |
| user->image_index(), |
| user->image_url())); |
| } else { |
| // Otherwise migrate user image properties right away. |
| BrowserThread::PostDelayedTask( |
| BrowserThread::UI, |
| FROM_HERE, |
| base::Bind(&UserImageManagerImpl::MigrateUserImage, |
| base::Unretained(this)), |
| base::TimeDelta::FromSeconds(user_image_migration_delay_sec)); |
| } |
| } |
| } |
| } |
| |
| if (!user_is_local) { |
| // Set up a repeating timer for refreshing the profile data. |
| profile_download_timer_.Start( |
| FROM_HERE, base::TimeDelta::FromSeconds(kProfileRefreshIntervalSec), |
| this, &UserImageManagerImpl::DownloadProfileDataScheduled); |
| } |
| CommandLine* command_line = CommandLine::ForCurrentProcess(); |
| if (user->CanSyncImage() && |
| !command_line->HasSwitch(chromeos::switches::kDisableUserImageSync)) { |
| if (user_image_sync_observer_.get() && |
| !command_line->HasSwitch(::switches::kMultiProfiles)) |
| NOTREACHED() << "User logged in second time."; |
| user_image_sync_observer_.reset(new UserImageSyncObserver(user)); |
| } |
| } |
| |
| void UserImageManagerImpl::SaveUserDefaultImageIndex( |
| const std::string& username, |
| int image_index) { |
| DCHECK(image_index >= 0 && image_index < kDefaultImagesCount); |
| SetUserImage(username, image_index, GURL(), |
| UserImage(GetDefaultImage(image_index))); |
| SaveImageToLocalState(username, "", image_index, GURL(), false); |
| } |
| |
| void UserImageManagerImpl::SaveUserImage(const std::string& username, |
| const UserImage& user_image) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| SaveUserImageInternal(username, User::kExternalImageIndex, |
| GURL(), user_image); |
| } |
| |
| void UserImageManagerImpl::SaveUserImageFromFile(const std::string& username, |
| const base::FilePath& path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| // Always use unsafe image loader because we resize the image when saving |
| // anyway. |
| unsafe_image_loader_->Start( |
| path.value(), login::kMaxUserImageSize, |
| base::Bind(&UserImageManagerImpl::SaveUserImage, |
| base::Unretained(this), username)); |
| } |
| |
| void UserImageManagerImpl::SaveUserImageFromProfileImage( |
| const std::string& username) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (!downloaded_profile_image_.isNull()) { |
| // Profile image has already been downloaded, so save it to file right now. |
| DCHECK(profile_image_url_.is_valid()); |
| SaveUserImageInternal( |
| username, |
| User::kProfileImageIndex, profile_image_url_, |
| UserImage::CreateAndEncode(downloaded_profile_image_)); |
| } else { |
| // No profile image - use the stub image (gray avatar). |
| SetUserImage(username, User::kProfileImageIndex, GURL(), UserImage()); |
| SaveImageToLocalState(username, "", User::kProfileImageIndex, |
| GURL(), false); |
| } |
| } |
| |
| void UserImageManagerImpl::DeleteUserImage(const std::string& username) { |
| // Delete from the old dictionary, if present. |
| DeleteOldUserImage(username); |
| |
| PrefService* prefs = g_browser_process->local_state(); |
| DictionaryPrefUpdate prefs_images_update(prefs, kUserImageProperties); |
| const base::DictionaryValue* image_properties; |
| if (prefs_images_update->GetDictionaryWithoutPathExpansion( |
| username, &image_properties)) { |
| std::string image_path; |
| image_properties->GetString(kImageURLNodeName, &image_path); |
| prefs_images_update->RemoveWithoutPathExpansion(username, NULL); |
| DeleteImageFile(image_path); |
| } |
| } |
| |
| void UserImageManagerImpl::DownloadProfileImage(const std::string& reason) { |
| DownloadProfileData(reason, true); |
| } |
| |
| UserImageSyncObserver* UserImageManagerImpl::GetSyncObserver() const { |
| return user_image_sync_observer_.get(); |
| } |
| |
| void UserImageManagerImpl::Shutdown() { |
| profile_image_downloader_.reset(); |
| user_image_sync_observer_.reset(); |
| } |
| |
| const gfx::ImageSkia& UserImageManagerImpl::DownloadedProfileImage() const { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| return downloaded_profile_image_; |
| } |
| |
| base::FilePath UserImageManagerImpl::GetImagePathForUser( |
| const std::string& username) { |
| std::string filename = username + kSafeImagePathExtension; |
| base::FilePath user_data_dir; |
| PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); |
| return user_data_dir.AppendASCII(filename); |
| } |
| |
| void UserImageManagerImpl::SetInitialUserImage(const std::string& username) { |
| // Choose a random default image. |
| int image_id = |
| base::RandInt(kFirstDefaultImageIndex, kDefaultImagesCount - 1); |
| SaveUserDefaultImageIndex(username, image_id); |
| } |
| |
| void UserImageManagerImpl::SetUserImage(const std::string& username, |
| int image_index, |
| const GURL& image_url, |
| const UserImage& user_image) { |
| User* user = const_cast<User*>(UserManager::Get()->FindUser(username)); |
| // User may have been removed by now. |
| if (user) { |
| bool image_changed = user->image_index() != User::kInvalidImageIndex; |
| bool is_current_user = user == UserManager::Get()->GetLoggedInUser(); |
| if (!user_image.image().isNull()) |
| user->SetImage(user_image, image_index); |
| else |
| user->SetStubImage(image_index, false); |
| user->SetImageURL(image_url); |
| // For the logged-in user with a profile picture, initialize |
| // |downloaded_profile_picture_|. |
| if (is_current_user && image_index == User::kProfileImageIndex) { |
| InitDownloadedProfileImage(); |
| } |
| if (image_changed) { |
| // Unless this is first-time setting with |SetInitialUserImage|, |
| // send a notification about image change. |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED, |
| content::Source<UserImageManager>(this), |
| content::Details<const User>(user)); |
| } |
| if (is_current_user && migrate_current_user_on_load_) |
| MigrateUserImage(); |
| } |
| } |
| |
| void UserImageManagerImpl::SaveUserImageInternal(const std::string& username, |
| int image_index, |
| const GURL& image_url, |
| const UserImage& user_image) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| SetUserImage(username, image_index, image_url, user_image); |
| |
| // Ignore if data stored or cached outside the user's cryptohome is to be |
| // treated as ephemeral. |
| if (UserManager::Get()->IsUserNonCryptohomeDataEphemeral(username)) |
| return; |
| |
| base::FilePath image_path = GetImagePathForUser(username); |
| DVLOG(1) << "Saving user image to " << image_path.value(); |
| |
| last_image_set_async_ = true; |
| |
| base::WorkerPool::PostTask( |
| FROM_HERE, |
| base::Bind(&UserImageManagerImpl::SaveImageToFile, |
| base::Unretained(this), |
| username, user_image, image_path, image_index, image_url), |
| /* is_slow= */ false); |
| } |
| |
| void UserImageManagerImpl::SaveImageToFile(const std::string& username, |
| const UserImage& user_image, |
| const base::FilePath& image_path, |
| int image_index, |
| const GURL& image_url) { |
| if (!SaveBitmapToFile(user_image, image_path)) |
| return; |
| |
| BrowserThread::PostTask( |
| BrowserThread::UI, |
| FROM_HERE, |
| base::Bind(&UserImageManagerImpl::SaveImageToLocalState, |
| base::Unretained(this), |
| username, image_path.value(), image_index, image_url, true)); |
| } |
| |
| void UserImageManagerImpl::SaveImageToLocalState(const std::string& username, |
| const std::string& image_path, |
| int image_index, |
| const GURL& image_url, |
| bool is_async) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Ignore if data stored or cached outside the user's cryptohome is to be |
| // treated as ephemeral. |
| if (UserManager::Get()->IsUserNonCryptohomeDataEphemeral(username)) |
| return; |
| |
| // TODO(ivankr): use unique filenames for user images each time |
| // a new image is set so that only the last image update is saved |
| // to Local State and notified. |
| if (is_async && !last_image_set_async_) { |
| DVLOG(1) << "Ignoring saved image because it has changed"; |
| return; |
| } else if (!is_async) { |
| // Reset the async image save flag if called directly from the UI thread. |
| last_image_set_async_ = false; |
| } |
| |
| PrefService* local_state = g_browser_process->local_state(); |
| DictionaryPrefUpdate images_update(local_state, kUserImageProperties); |
| base::DictionaryValue* image_properties = new base::DictionaryValue(); |
| image_properties->Set(kImagePathNodeName, new StringValue(image_path)); |
| image_properties->Set(kImageIndexNodeName, |
| new base::FundamentalValue(image_index)); |
| if (!image_url.is_empty()) { |
| image_properties->Set(kImageURLNodeName, |
| new StringValue(image_url.spec())); |
| } else { |
| image_properties->Remove(kImageURLNodeName, NULL); |
| } |
| images_update->SetWithoutPathExpansion(username, image_properties); |
| DVLOG(1) << "Saving path to user image in Local State."; |
| |
| if (users_to_migrate_.count(username)) { |
| DeleteOldUserImage(username); |
| users_to_migrate_.erase(username); |
| } |
| |
| UserManager::Get()->NotifyLocalStateChanged(); |
| } |
| |
| bool UserImageManagerImpl::SaveBitmapToFile(const UserImage& user_image, |
| const base::FilePath& image_path) { |
| UserImage safe_image; |
| const UserImage::RawImage* encoded_image = NULL; |
| if (!user_image.is_safe_format()) { |
| safe_image = UserImage::CreateAndEncode(user_image.image()); |
| encoded_image = &safe_image.raw_image(); |
| UMA_HISTOGRAM_MEMORY_KB("UserImage.RecodedJpegSize", encoded_image->size()); |
| } else if (user_image.has_raw_image()) { |
| encoded_image = &user_image.raw_image(); |
| } else { |
| NOTREACHED() << "Raw image missing."; |
| return false; |
| } |
| |
| if (file_util::WriteFile(image_path, |
| reinterpret_cast<const char*>(&(*encoded_image)[0]), |
| encoded_image->size()) == -1) { |
| LOG(ERROR) << "Failed to save image to file."; |
| return false; |
| } |
| return true; |
| } |
| |
| void UserImageManagerImpl::InitDownloadedProfileImage() { |
| const User* logged_in_user = UserManager::Get()->GetLoggedInUser(); |
| DCHECK_EQ(logged_in_user->image_index(), User::kProfileImageIndex); |
| if (downloaded_profile_image_.isNull() && !logged_in_user->image_is_stub()) { |
| VLOG(1) << "Profile image initialized"; |
| downloaded_profile_image_ = logged_in_user->image(); |
| downloaded_profile_image_data_url_ = |
| webui::GetBitmapDataUrl(*downloaded_profile_image_.bitmap()); |
| profile_image_url_ = logged_in_user->image_url(); |
| } |
| } |
| |
| void UserImageManagerImpl::DownloadProfileData(const std::string& reason, |
| bool download_image) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // GAIA profiles exist for regular users only. |
| if (!UserManager::Get()->IsLoggedInAsRegularUser()) |
| return; |
| |
| // Mark profile picture as needed. |
| downloading_profile_image_ |= download_image; |
| |
| // Another download is already in progress |
| if (profile_image_downloader_.get()) |
| return; |
| |
| profile_image_download_reason_ = reason; |
| profile_image_load_start_time_ = base::Time::Now(); |
| profile_image_downloader_.reset(new ProfileDownloader(this)); |
| profile_image_downloader_->Start(); |
| } |
| |
| void UserImageManagerImpl::DownloadProfileDataScheduled() { |
| const User* logged_in_user = UserManager::Get()->GetLoggedInUser(); |
| // If current user image is profile image, it needs to be refreshed. |
| bool download_profile_image = |
| logged_in_user->image_index() == User::kProfileImageIndex; |
| DownloadProfileData(kProfileDownloadReasonScheduled, download_profile_image); |
| } |
| |
| void UserImageManagerImpl::DownloadProfileDataRetry(bool download_image) { |
| DownloadProfileData(kProfileDownloadReasonRetry, download_image); |
| } |
| |
| // ProfileDownloaderDelegate override. |
| bool UserImageManagerImpl::NeedsProfilePicture() const { |
| return downloading_profile_image_; |
| } |
| |
| // ProfileDownloaderDelegate override. |
| int UserImageManagerImpl::GetDesiredImageSideLength() const { |
| return GetCurrentUserImageSize(); |
| } |
| |
| // ProfileDownloaderDelegate override. |
| std::string UserImageManagerImpl::GetCachedPictureURL() const { |
| return profile_image_url_.spec(); |
| } |
| |
| Profile* UserImageManagerImpl::GetBrowserProfile() { |
| return ProfileManager::GetDefaultProfile(); |
| } |
| |
| void UserImageManagerImpl::OnProfileDownloadSuccess( |
| ProfileDownloader* downloader) { |
| // Make sure that |ProfileDownloader| gets deleted after return. |
| scoped_ptr<ProfileDownloader> profile_image_downloader( |
| profile_image_downloader_.release()); |
| DCHECK_EQ(downloader, profile_image_downloader.get()); |
| |
| UserManager* user_manager = UserManager::Get(); |
| const User* user = user_manager->GetLoggedInUser(); |
| |
| user_manager->UpdateUserAccountData(user->email(), |
| downloader->GetProfileFullName(), |
| downloader->GetProfileLocale()); |
| |
| bool requested_image = downloading_profile_image_; |
| downloading_profile_image_ = false; |
| if (!requested_image) |
| return; |
| |
| ProfileDownloadResult result = kDownloadFailure; |
| switch (downloader->GetProfilePictureStatus()) { |
| case ProfileDownloader::PICTURE_SUCCESS: |
| result = kDownloadSuccess; |
| break; |
| case ProfileDownloader::PICTURE_CACHED: |
| result = kDownloadCached; |
| break; |
| case ProfileDownloader::PICTURE_DEFAULT: |
| result = kDownloadDefault; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| UMA_HISTOGRAM_ENUMERATION("UserImage.ProfileDownloadResult", |
| result, kDownloadResultsCount); |
| |
| DCHECK(!profile_image_load_start_time_.is_null()); |
| base::TimeDelta delta = base::Time::Now() - profile_image_load_start_time_; |
| AddProfileImageTimeHistogram(result, profile_image_download_reason_, delta); |
| |
| if (result == kDownloadDefault) { |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_PROFILE_IMAGE_UPDATE_FAILED, |
| content::Source<UserImageManager>(this), |
| content::NotificationService::NoDetails()); |
| } |
| |
| // Nothing to do if picture is cached or the default avatar. |
| if (result != kDownloadSuccess) |
| return; |
| |
| // Check if this image is not the same as already downloaded. |
| SkBitmap new_bitmap(downloader->GetProfilePicture()); |
| std::string new_image_data_url = webui::GetBitmapDataUrl(new_bitmap); |
| if (!downloaded_profile_image_data_url_.empty() && |
| new_image_data_url == downloaded_profile_image_data_url_) |
| return; |
| |
| downloaded_profile_image_data_url_ = new_image_data_url; |
| downloaded_profile_image_ = gfx::ImageSkia::CreateFrom1xBitmap( |
| downloader->GetProfilePicture()); |
| profile_image_url_ = GURL(downloader->GetProfilePictureURL()); |
| |
| if (user->image_index() == User::kProfileImageIndex) { |
| VLOG(1) << "Updating profile image for logged-in user"; |
| UMA_HISTOGRAM_ENUMERATION("UserImage.ProfileDownloadResult", |
| kDownloadSuccessChanged, |
| kDownloadResultsCount); |
| // This will persist |downloaded_profile_image_| to file. |
| SaveUserImageFromProfileImage(user->email()); |
| } |
| |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_PROFILE_IMAGE_UPDATED, |
| content::Source<UserImageManager>(this), |
| content::Details<const gfx::ImageSkia>(&downloaded_profile_image_)); |
| } |
| |
| void UserImageManagerImpl::OnProfileDownloadFailure( |
| ProfileDownloader* downloader, |
| ProfileDownloaderDelegate::FailureReason reason) { |
| DCHECK_EQ(downloader, profile_image_downloader_.get()); |
| profile_image_downloader_.reset(); |
| |
| UMA_HISTOGRAM_ENUMERATION("UserImage.ProfileDownloadResult", |
| kDownloadFailure, kDownloadResultsCount); |
| |
| DCHECK(!profile_image_load_start_time_.is_null()); |
| base::TimeDelta delta = base::Time::Now() - profile_image_load_start_time_; |
| AddProfileImageTimeHistogram(kDownloadFailure, profile_image_download_reason_, |
| delta); |
| |
| UserManager* user_manager = UserManager::Get(); |
| const User* user = user_manager->GetLoggedInUser(); |
| |
| // Need note that at least one attempt finished. |
| user_manager->UpdateUserAccountData(user->email(), string16(), ""); |
| |
| // Retry download after some time if a network error has occured. |
| if (reason == ProfileDownloaderDelegate::NETWORK_ERROR) { |
| BrowserThread::PostDelayedTask( |
| BrowserThread::UI, |
| FROM_HERE, |
| base::Bind(&UserImageManagerImpl::DownloadProfileDataRetry, |
| base::Unretained(this), |
| downloading_profile_image_), |
| base::TimeDelta::FromSeconds(kProfileDataDownloadRetryIntervalSec)); |
| } |
| |
| downloading_profile_image_ = false; |
| |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_PROFILE_IMAGE_UPDATE_FAILED, |
| content::Source<UserImageManager>(this), |
| content::NotificationService::NoDetails()); |
| } |
| |
| void UserImageManagerImpl::MigrateUserImage() { |
| User* user = UserManager::Get()->GetLoggedInUser(); |
| if (user->image_is_loading()) { |
| LOG(INFO) << "Waiting for user image to load before migration"; |
| migrate_current_user_on_load_ = true; |
| return; |
| } |
| migrate_current_user_on_load_ = false; |
| if (user->has_raw_image() && user->image_is_safe_format()) { |
| // Nothing to migrate already, make sure we delete old image. |
| DeleteOldUserImage(user->email()); |
| users_to_migrate_.erase(user->email()); |
| return; |
| } |
| if (user->HasDefaultImage()) { |
| SaveUserDefaultImageIndex(user->email(), user->image_index()); |
| } else { |
| SaveUserImageInternal(user->email(), user->image_index(), |
| user->image_url(), user->user_image()); |
| } |
| UMA_HISTOGRAM_ENUMERATION("UserImage.Migration", |
| ImageIndexToHistogramIndex(user->image_index()), |
| kHistogramImagesCount); |
| } |
| |
| void UserImageManagerImpl::DeleteOldUserImage(const std::string& username) { |
| PrefService* prefs = g_browser_process->local_state(); |
| DictionaryPrefUpdate prefs_images_update(prefs, kUserImages); |
| const base::DictionaryValue* image_properties; |
| if (prefs_images_update->GetDictionaryWithoutPathExpansion( |
| username, &image_properties)) { |
| std::string image_path; |
| image_properties->GetString(kImagePathNodeName, &image_path); |
| prefs_images_update->RemoveWithoutPathExpansion(username, NULL); |
| DeleteImageFile(image_path); |
| } |
| } |
| |
| } // namespace chromeos |