| // 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/debug/trace_event.h" |
| #include "base/file_util.h" |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/message_loop/message_loop_proxy.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/sequenced_task_runner.h" |
| #include "base/task_runner_util.h" |
| #include "base/threading/sequenced_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/chromeos/policy/device_local_account_policy_service.h" |
| #include "chrome/browser/profiles/profile_downloader.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/notification_service.h" |
| #include "policy/policy_constants.h" |
| #include "ui/gfx/image/image_skia.h" |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| // A dictionary that maps user_ids 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 user_ids 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 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 profile image download when the user chooses the |
| // profile image but it has not been downloaded yet. |
| const char kProfileDownloadReasonProfileImageChosen[] = "ProfileImageChosen"; |
| // 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"; |
| |
| static bool g_ignore_profile_data_download_delay_ = false; |
| |
| // Add a histogram showing the time it takes to download 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(); |
| } |
| |
| // 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; |
| } |
| } |
| |
| bool SaveImage(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 (!encoded_image->size() || |
| base::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; |
| } |
| |
| } // namespace |
| |
| // static |
| void UserImageManager::RegisterPrefs(PrefRegistrySimple* registry) { |
| registry->RegisterDictionaryPref(kUserImages); |
| registry->RegisterDictionaryPref(kUserImageProperties); |
| } |
| |
| // Every image load or update is encapsulated by a Job. The Job is allowed to |
| // perform tasks on background threads or in helper processes but: |
| // * Changes to User objects and local state as well as any calls to the |
| // |parent_| must be performed on the thread that the Job is created on only. |
| // * File writes and deletions must be performed via the |parent_|'s |
| // |background_task_runner_| only. |
| // |
| // Only one of the Load*() and Set*() methods may be called per Job. |
| class UserImageManagerImpl::Job { |
| public: |
| // The |Job| will update the user object corresponding to |parent|. |
| explicit Job(UserImageManagerImpl* parent); |
| ~Job(); |
| |
| // Loads the image at |image_path| or one of the default images, |
| // depending on |image_index|, and updates the user object with the |
| // new image. |
| void LoadImage(base::FilePath image_path, |
| const int image_index, |
| const GURL& image_url); |
| |
| // Sets the user image in local state to the default image indicated |
| // by |default_image_index|. Also updates the user object with the |
| // new image. |
| void SetToDefaultImage(int default_image_index); |
| |
| // Saves the |user_image| to disk and sets the user image in local |
| // state to that image. Also updates the user with the new image. |
| void SetToImage(int image_index, const UserImage& user_image); |
| |
| // Decodes the JPEG image |data|, crops and resizes the image, saves |
| // it to disk and sets the user image in local state to that image. |
| // Also updates the user object with the new image. |
| void SetToImageData(scoped_ptr<std::string> data); |
| |
| // Loads the image at |path|, transcodes it to JPEG format, saves |
| // the image to disk and sets the user image in local state to that |
| // image. If |resize| is true, the image is cropped and resized |
| // before transcoding. Also updates the user object with the new |
| // image. |
| void SetToPath(const base::FilePath& path, |
| int image_index, |
| const GURL& image_url, |
| bool resize); |
| |
| private: |
| // Called back after an image has been loaded from disk. |
| void OnLoadImageDone(bool save, const UserImage& user_image); |
| |
| // Updates the user object with |user_image_|. |
| void UpdateUser(); |
| |
| // Saves |user_image_| to disk in JPEG format. Local state will be updated |
| // when a callback indicates that the image has been saved. |
| void SaveImageAndUpdateLocalState(); |
| |
| // Called back after the |user_image_| has been saved to |
| // disk. Updates the user image information in local state. The |
| // information is only updated if |success| is true (indicating that |
| // the image was saved successfully) or the user image is the |
| // profile image (indicating that even if the image could not be |
| // saved because it is not available right now, it will be |
| // downloaded eventually). |
| void OnSaveImageDone(bool success); |
| |
| // Updates the user image in local state, setting it to one of the |
| // default images or the saved |user_image_|, depending on |
| // |image_index_|. |
| void UpdateLocalState(); |
| |
| // Notifies the |parent_| that the Job is done. |
| void NotifyJobDone(); |
| |
| const std::string& user_id() const { return parent_->user_id(); } |
| |
| UserImageManagerImpl* parent_; |
| |
| // Whether one of the Load*() or Set*() methods has been run already. |
| bool run_; |
| |
| int image_index_; |
| GURL image_url_; |
| base::FilePath image_path_; |
| |
| UserImage user_image_; |
| |
| base::WeakPtrFactory<Job> weak_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Job); |
| }; |
| |
| UserImageManagerImpl::Job::Job(UserImageManagerImpl* parent) |
| : parent_(parent), |
| run_(false), |
| weak_factory_(this) { |
| } |
| |
| UserImageManagerImpl::Job::~Job() { |
| } |
| |
| void UserImageManagerImpl::Job::LoadImage(base::FilePath image_path, |
| const int image_index, |
| const GURL& image_url) { |
| DCHECK(!run_); |
| run_ = true; |
| |
| image_index_ = image_index; |
| image_url_ = image_url; |
| image_path_ = image_path; |
| |
| if (image_index_ >= 0 && image_index_ < kDefaultImagesCount) { |
| // Load one of the default images. This happens synchronously. |
| user_image_ = UserImage(GetDefaultImage(image_index_)); |
| UpdateUser(); |
| NotifyJobDone(); |
| } else if (image_index_ == User::kExternalImageIndex || |
| image_index_ == User::kProfileImageIndex) { |
| // Load the user image from a file referenced by |image_path|. This happens |
| // asynchronously. The JPEG image loader can be used here because |
| // LoadImage() is called only for users whose user image has previously |
| // been set by one of the Set*() methods, which transcode to JPEG format. |
| DCHECK(!image_path_.empty()); |
| parent_->image_loader_->Start(image_path_.value(), |
| 0, |
| base::Bind(&Job::OnLoadImageDone, |
| weak_factory_.GetWeakPtr(), |
| false)); |
| } else { |
| NOTREACHED(); |
| NotifyJobDone(); |
| } |
| } |
| |
| void UserImageManagerImpl::Job::SetToDefaultImage(int default_image_index) { |
| DCHECK(!run_); |
| run_ = true; |
| |
| DCHECK_LE(0, default_image_index); |
| DCHECK_GT(kDefaultImagesCount, default_image_index); |
| |
| image_index_ = default_image_index; |
| user_image_ = UserImage(GetDefaultImage(image_index_)); |
| |
| UpdateUser(); |
| UpdateLocalState(); |
| NotifyJobDone(); |
| } |
| |
| void UserImageManagerImpl::Job::SetToImage(int image_index, |
| const UserImage& user_image) { |
| DCHECK(!run_); |
| run_ = true; |
| |
| DCHECK(image_index == User::kExternalImageIndex || |
| image_index == User::kProfileImageIndex); |
| |
| image_index_ = image_index; |
| user_image_ = user_image; |
| |
| UpdateUser(); |
| SaveImageAndUpdateLocalState(); |
| } |
| |
| void UserImageManagerImpl::Job::SetToImageData(scoped_ptr<std::string> data) { |
| DCHECK(!run_); |
| run_ = true; |
| |
| image_index_ = User::kExternalImageIndex; |
| |
| // This method uses the image_loader_, not the unsafe_image_loader_: |
| // * This is necessary because the method is used to update the user image |
| // whenever the policy for a user is set. In the case of device-local |
| // accounts, policy may change at any time, even if the user is not |
| // currently logged in (and thus, the unsafe_image_loader_ may not be used). |
| // * This is possible because only JPEG |data| is accepted. No support for |
| // other image file formats is needed. |
| // * This is safe because the image_loader_ employs a hardened JPEG decoder |
| // that protects against malicious invalid image data being used to attack |
| // the login screen or another user session currently in progress. |
| parent_->image_loader_->Start(data.Pass(), |
| login::kMaxUserImageSize, |
| base::Bind(&Job::OnLoadImageDone, |
| weak_factory_.GetWeakPtr(), |
| true)); |
| } |
| |
| void UserImageManagerImpl::Job::SetToPath(const base::FilePath& path, |
| int image_index, |
| const GURL& image_url, |
| bool resize) { |
| DCHECK(!run_); |
| run_ = true; |
| |
| image_index_ = image_index; |
| image_url_ = image_url; |
| |
| DCHECK(!path.empty()); |
| parent_->unsafe_image_loader_->Start(path.value(), |
| resize ? login::kMaxUserImageSize : 0, |
| base::Bind(&Job::OnLoadImageDone, |
| weak_factory_.GetWeakPtr(), |
| true)); |
| } |
| |
| void UserImageManagerImpl::Job::OnLoadImageDone(bool save, |
| const UserImage& user_image) { |
| user_image_ = user_image; |
| UpdateUser(); |
| if (save) |
| SaveImageAndUpdateLocalState(); |
| else |
| NotifyJobDone(); |
| } |
| |
| void UserImageManagerImpl::Job::UpdateUser() { |
| User* user = parent_->user_manager_->FindUserAndModify(user_id()); |
| if (!user) |
| return; |
| |
| if (!user_image_.image().isNull()) |
| user->SetImage(user_image_, image_index_); |
| else |
| user->SetStubImage(image_index_, false); |
| user->SetImageURL(image_url_); |
| |
| parent_->OnJobChangedUserImage(); |
| } |
| |
| void UserImageManagerImpl::Job::SaveImageAndUpdateLocalState() { |
| base::FilePath user_data_dir; |
| PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); |
| image_path_ = user_data_dir.Append(user_id() + kSafeImagePathExtension); |
| |
| base::PostTaskAndReplyWithResult( |
| parent_->background_task_runner_, |
| FROM_HERE, |
| base::Bind(&SaveImage, user_image_, image_path_), |
| base::Bind(&Job::OnSaveImageDone, weak_factory_.GetWeakPtr())); |
| } |
| |
| void UserImageManagerImpl::Job::OnSaveImageDone(bool success) { |
| if (success || image_index_ == User::kProfileImageIndex) |
| UpdateLocalState(); |
| NotifyJobDone(); |
| } |
| |
| void UserImageManagerImpl::Job::UpdateLocalState() { |
| // Ignore if data stored or cached outside the user's cryptohome is to be |
| // treated as ephemeral. |
| if (parent_->user_manager_->IsUserNonCryptohomeDataEphemeral(user_id())) |
| return; |
| |
| scoped_ptr<base::DictionaryValue> entry(new base::DictionaryValue); |
| entry->Set(kImagePathNodeName, new base::StringValue(image_path_.value())); |
| entry->Set(kImageIndexNodeName, new base::FundamentalValue(image_index_)); |
| if (!image_url_.is_empty()) |
| entry->Set(kImageURLNodeName, new base::StringValue(image_url_.spec())); |
| DictionaryPrefUpdate update(g_browser_process->local_state(), |
| kUserImageProperties); |
| update->SetWithoutPathExpansion(user_id(), entry.release()); |
| |
| parent_->user_manager_->NotifyLocalStateChanged(); |
| } |
| |
| void UserImageManagerImpl::Job::NotifyJobDone() { |
| parent_->OnJobDone(); |
| } |
| |
| UserImageManagerImpl::UserImageManagerImpl(const std::string& user_id, |
| UserManager* user_manager) |
| : UserImageManager(user_id), |
| user_manager_(user_manager), |
| downloading_profile_image_(false), |
| profile_image_requested_(false), |
| has_managed_image_(false), |
| user_needs_migration_(false), |
| weak_factory_(this) { |
| base::SequencedWorkerPool* blocking_pool = |
| content::BrowserThread::GetBlockingPool(); |
| background_task_runner_ = |
| blocking_pool->GetSequencedTaskRunnerWithShutdownBehavior( |
| blocking_pool->GetSequenceToken(), |
| base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN); |
| image_loader_ = new UserImageLoader(ImageDecoder::ROBUST_JPEG_CODEC, |
| background_task_runner_); |
| unsafe_image_loader_ = new UserImageLoader(ImageDecoder::DEFAULT_CODEC, |
| background_task_runner_); |
| } |
| |
| UserImageManagerImpl::~UserImageManagerImpl() {} |
| |
| void UserImageManagerImpl::LoadUserImage() { |
| PrefService* local_state = g_browser_process->local_state(); |
| const base::DictionaryValue* prefs_images_unsafe = |
| local_state->GetDictionary(kUserImages); |
| const base::DictionaryValue* prefs_images = |
| local_state->GetDictionary(kUserImageProperties); |
| if (!prefs_images && !prefs_images_unsafe) |
| return; |
| User* user = GetUserAndModify(); |
| bool needs_migration = false; |
| |
| // If entries are found in both |prefs_images_unsafe| and |prefs_images|, |
| // |prefs_images| is honored for now but |prefs_images_unsafe| will be |
| // migrated, overwriting the |prefs_images| entry, when the user logs in. |
| const base::DictionaryValue* image_properties = NULL; |
| if (prefs_images_unsafe) { |
| needs_migration = prefs_images_unsafe->GetDictionaryWithoutPathExpansion( |
| user_id(), &image_properties); |
| if (needs_migration) |
| user_needs_migration_ = true; |
| } |
| if (prefs_images) { |
| prefs_images->GetDictionaryWithoutPathExpansion(user_id(), |
| &image_properties); |
| } |
| |
| // If the user image for |user_id| is managed by policy and the policy-set |
| // image is being loaded and persisted right now, let that job continue. It |
| // will update the user image when done. |
| if (IsUserImageManaged() && job_.get()) |
| return; |
| |
| if (!image_properties) { |
| SetInitialUserImage(); |
| return; |
| } |
| |
| 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); |
| return; |
| } |
| |
| if (image_index != User::kExternalImageIndex && |
| image_index != User::kProfileImageIndex) { |
| NOTREACHED(); |
| return; |
| } |
| |
| std::string image_url_string; |
| image_properties->GetString(kImageURLNodeName, &image_url_string); |
| GURL image_url(image_url_string); |
| std::string image_path; |
| image_properties->GetString(kImagePathNodeName, &image_path); |
| |
| user->SetImageURL(image_url); |
| user->SetStubImage(image_index, true); |
| DCHECK(!image_path.empty() || image_index == User::kProfileImageIndex); |
| if (image_path.empty() || needs_migration) { |
| // Return if either of the following is true: |
| // * The profile image is to be used but has not been downloaded yet. The |
| // profile image will be downloaded after login. |
| // * The image needs migration. Migration will be performed after login. |
| return; |
| } |
| |
| job_.reset(new Job(this)); |
| job_->LoadImage(base::FilePath(image_path), image_index, image_url); |
| } |
| |
| void UserImageManagerImpl::UserLoggedIn(bool user_is_new, |
| bool user_is_local) { |
| const User* user = GetUser(); |
| if (user_is_new) { |
| if (!user_is_local) |
| SetInitialUserImage(); |
| } else { |
| UMA_HISTOGRAM_ENUMERATION("UserImage.LoggedIn", |
| ImageIndexToHistogramIndex(user->image_index()), |
| kHistogramImagesCount); |
| |
| if (!IsUserImageManaged() && user_needs_migration_) { |
| const base::DictionaryValue* prefs_images_unsafe = |
| g_browser_process->local_state()->GetDictionary(kUserImages); |
| const base::DictionaryValue* image_properties = NULL; |
| if (prefs_images_unsafe->GetDictionaryWithoutPathExpansion( |
| user_id(), &image_properties)) { |
| std::string image_path; |
| image_properties->GetString(kImagePathNodeName, &image_path); |
| job_.reset(new Job(this)); |
| if (!image_path.empty()) { |
| VLOG(0) << "Loading old user image, then migrating it."; |
| job_->SetToPath(base::FilePath(image_path), |
| user->image_index(), |
| user->image_url(), |
| false); |
| } else { |
| job_->SetToDefaultImage(user->image_index()); |
| } |
| } |
| } |
| } |
| |
| // Reset the downloaded profile image as a new user logged in. |
| downloaded_profile_image_ = gfx::ImageSkia(); |
| profile_image_url_ = GURL(); |
| profile_image_requested_ = false; |
| |
| if (IsUserLoggedInAndRegular()) { |
| TryToInitDownloadedProfileImage(); |
| |
| // Schedule an initial download of the profile data (full name and |
| // optionally image). |
| profile_download_one_shot_timer_.Start( |
| FROM_HERE, |
| g_ignore_profile_data_download_delay_ ? |
| base::TimeDelta() : |
| base::TimeDelta::FromSeconds(kProfileDataDownloadDelaySec), |
| base::Bind(&UserImageManagerImpl::DownloadProfileData, |
| base::Unretained(this), |
| kProfileDownloadReasonLoggedIn)); |
| // Schedule periodic refreshes of the profile data. |
| profile_download_periodic_timer_.Start( |
| FROM_HERE, |
| base::TimeDelta::FromSeconds(kProfileRefreshIntervalSec), |
| base::Bind(&UserImageManagerImpl::DownloadProfileData, |
| base::Unretained(this), |
| kProfileDownloadReasonScheduled)); |
| } else { |
| profile_download_one_shot_timer_.Stop(); |
| profile_download_periodic_timer_.Stop(); |
| } |
| |
| user_image_sync_observer_.reset(); |
| TryToCreateImageSyncObserver(); |
| } |
| |
| void UserImageManagerImpl::SaveUserDefaultImageIndex(int default_image_index) { |
| if (IsUserImageManaged()) |
| return; |
| job_.reset(new Job(this)); |
| job_->SetToDefaultImage(default_image_index); |
| } |
| |
| void UserImageManagerImpl::SaveUserImage(const UserImage& user_image) { |
| if (IsUserImageManaged()) |
| return; |
| job_.reset(new Job(this)); |
| job_->SetToImage(User::kExternalImageIndex, user_image); |
| } |
| |
| void UserImageManagerImpl::SaveUserImageFromFile(const base::FilePath& path) { |
| if (IsUserImageManaged()) |
| return; |
| job_.reset(new Job(this)); |
| job_->SetToPath(path, User::kExternalImageIndex, GURL(), true); |
| } |
| |
| void UserImageManagerImpl::SaveUserImageFromProfileImage() { |
| if (IsUserImageManaged()) |
| return; |
| // Use the profile image if it has been downloaded already. Otherwise, use a |
| // stub image (gray avatar). |
| job_.reset(new Job(this)); |
| job_->SetToImage(User::kProfileImageIndex, |
| downloaded_profile_image_.isNull() ? |
| UserImage() : |
| UserImage::CreateAndEncode(downloaded_profile_image_)); |
| // If no profile image has been downloaded yet, ensure that a download is |
| // started. |
| if (downloaded_profile_image_.isNull()) |
| DownloadProfileData(kProfileDownloadReasonProfileImageChosen); |
| } |
| |
| void UserImageManagerImpl::DeleteUserImage() { |
| job_.reset(); |
| DeleteUserImageAndLocalStateEntry(kUserImages); |
| DeleteUserImageAndLocalStateEntry(kUserImageProperties); |
| } |
| |
| void UserImageManagerImpl::DownloadProfileImage(const std::string& reason) { |
| profile_image_requested_ = true; |
| DownloadProfileData(reason); |
| } |
| |
| const gfx::ImageSkia& UserImageManagerImpl::DownloadedProfileImage() const { |
| return downloaded_profile_image_; |
| } |
| |
| UserImageSyncObserver* UserImageManagerImpl::GetSyncObserver() const { |
| return user_image_sync_observer_.get(); |
| } |
| |
| void UserImageManagerImpl::Shutdown() { |
| profile_downloader_.reset(); |
| user_image_sync_observer_.reset(); |
| } |
| |
| void UserImageManagerImpl::OnExternalDataSet(const std::string& policy) { |
| DCHECK_EQ(policy::key::kUserAvatarImage, policy); |
| if (IsUserImageManaged()) |
| return; |
| |
| has_managed_image_ = true; |
| job_.reset(); |
| |
| const User* user = GetUser(); |
| // If the user image for the currently logged-in user became managed, stop the |
| // sync observer so that the policy-set image does not get synced out. |
| if (user && user->is_logged_in()) |
| user_image_sync_observer_.reset(); |
| } |
| |
| void UserImageManagerImpl::OnExternalDataCleared(const std::string& policy) { |
| DCHECK_EQ(policy::key::kUserAvatarImage, policy); |
| has_managed_image_ = false; |
| TryToCreateImageSyncObserver(); |
| } |
| |
| void UserImageManagerImpl::OnExternalDataFetched(const std::string& policy, |
| scoped_ptr<std::string> data) { |
| DCHECK_EQ(policy::key::kUserAvatarImage, policy); |
| DCHECK(IsUserImageManaged()); |
| if (data) { |
| job_.reset(new Job(this)); |
| job_->SetToImageData(data.Pass()); |
| } |
| } |
| |
| // static |
| void UserImageManagerImpl::IgnoreProfileDataDownloadDelayForTesting() { |
| g_ignore_profile_data_download_delay_ = true; |
| } |
| |
| bool UserImageManagerImpl::NeedsProfilePicture() const { |
| return downloading_profile_image_; |
| } |
| |
| int UserImageManagerImpl::GetDesiredImageSideLength() const { |
| return GetCurrentUserImageSize(); |
| } |
| |
| Profile* UserImageManagerImpl::GetBrowserProfile() { |
| return user_manager_->GetProfileByUser(GetUser()); |
| } |
| |
| std::string UserImageManagerImpl::GetCachedPictureURL() const { |
| return profile_image_url_.spec(); |
| } |
| |
| void UserImageManagerImpl::OnProfileDownloadSuccess( |
| ProfileDownloader* downloader) { |
| // Ensure that the |profile_downloader_| is deleted when this method returns. |
| scoped_ptr<ProfileDownloader> profile_downloader( |
| profile_downloader_.release()); |
| DCHECK_EQ(downloader, profile_downloader.get()); |
| |
| user_manager_->UpdateUserAccountData( |
| user_id(), |
| UserManager::UserAccountData(downloader->GetProfileFullName(), |
| downloader->GetProfileGivenName(), |
| downloader->GetProfileLocale())); |
| if (!downloading_profile_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()); |
| AddProfileImageTimeHistogram( |
| result, |
| profile_image_download_reason_, |
| base::TimeTicks::Now() - profile_image_load_start_time_); |
| |
| // Ignore the image if it is no longer needed. |
| if (!NeedProfileImage()) |
| return; |
| |
| if (result == kDownloadDefault) { |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_PROFILE_IMAGE_UPDATE_FAILED, |
| content::Source<UserImageManager>(this), |
| content::NotificationService::NoDetails()); |
| } else { |
| profile_image_requested_ = false; |
| } |
| |
| // Nothing to do if the picture is cached or is the default avatar. |
| if (result != kDownloadSuccess) |
| return; |
| |
| downloaded_profile_image_ = gfx::ImageSkia::CreateFrom1xBitmap( |
| downloader->GetProfilePicture()); |
| profile_image_url_ = GURL(downloader->GetProfilePictureURL()); |
| |
| const User* user = GetUser(); |
| 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 disk. |
| SaveUserImageFromProfileImage(); |
| } |
| |
| 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_downloader_.get()); |
| profile_downloader_.reset(); |
| |
| if (downloading_profile_image_) { |
| UMA_HISTOGRAM_ENUMERATION("UserImage.ProfileDownloadResult", |
| kDownloadFailure, |
| kDownloadResultsCount); |
| DCHECK(!profile_image_load_start_time_.is_null()); |
| AddProfileImageTimeHistogram( |
| kDownloadFailure, |
| profile_image_download_reason_, |
| base::TimeTicks::Now() - profile_image_load_start_time_); |
| } |
| |
| if (reason == ProfileDownloaderDelegate::NETWORK_ERROR) { |
| // Retry download after a delay if a network error occurred. |
| profile_download_one_shot_timer_.Start( |
| FROM_HERE, |
| base::TimeDelta::FromSeconds(kProfileDataDownloadRetryIntervalSec), |
| base::Bind(&UserImageManagerImpl::DownloadProfileData, |
| base::Unretained(this), |
| kProfileDownloadReasonRetry)); |
| } |
| |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_PROFILE_IMAGE_UPDATE_FAILED, |
| content::Source<UserImageManager>(this), |
| content::NotificationService::NoDetails()); |
| } |
| |
| bool UserImageManagerImpl::IsUserImageManaged() const { |
| return has_managed_image_; |
| } |
| |
| void UserImageManagerImpl::SetInitialUserImage() { |
| // Choose a random default image. |
| SaveUserDefaultImageIndex(base::RandInt(kFirstDefaultImageIndex, |
| kDefaultImagesCount - 1)); |
| } |
| |
| void UserImageManagerImpl::TryToInitDownloadedProfileImage() { |
| const User* user = GetUser(); |
| if (user->image_index() == User::kProfileImageIndex && |
| downloaded_profile_image_.isNull() && |
| !user->image_is_stub()) { |
| // Initialize the |downloaded_profile_image_| for the currently logged-in |
| // user if it has not been initialized already, the user image is the |
| // profile image and the user image has been loaded successfully. |
| VLOG(1) << "Profile image initialized from disk."; |
| downloaded_profile_image_ = user->GetImage(); |
| profile_image_url_ = user->image_url(); |
| } |
| } |
| |
| bool UserImageManagerImpl::NeedProfileImage() const { |
| const User* user = GetUser(); |
| return IsUserLoggedInAndRegular() && |
| (user->image_index() == User::kProfileImageIndex || |
| profile_image_requested_); |
| } |
| |
| void UserImageManagerImpl::DownloadProfileData(const std::string& reason) { |
| // GAIA profiles exist for regular users only. |
| if (!IsUserLoggedInAndRegular()) |
| return; |
| |
| // If a download is already in progress, allow it to continue, with one |
| // exception: If the current download does not include the profile image but |
| // the image has since become necessary, start a new download that includes |
| // the profile image. |
| if (profile_downloader_ && |
| (downloading_profile_image_ || !NeedProfileImage())) { |
| return; |
| } |
| |
| downloading_profile_image_ = NeedProfileImage(); |
| profile_image_download_reason_ = reason; |
| profile_image_load_start_time_ = base::TimeTicks::Now(); |
| profile_downloader_.reset(new ProfileDownloader(this)); |
| profile_downloader_->Start(); |
| } |
| |
| void UserImageManagerImpl::DeleteUserImageAndLocalStateEntry( |
| const char* prefs_dict_root) { |
| DictionaryPrefUpdate update(g_browser_process->local_state(), |
| prefs_dict_root); |
| const base::DictionaryValue* image_properties; |
| if (!update->GetDictionaryWithoutPathExpansion(user_id(), &image_properties)) |
| return; |
| |
| std::string image_path; |
| image_properties->GetString(kImagePathNodeName, &image_path); |
| if (!image_path.empty()) { |
| background_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(base::IgnoreResult(&base::DeleteFile), |
| base::FilePath(image_path), |
| false)); |
| } |
| update->RemoveWithoutPathExpansion(user_id(), NULL); |
| } |
| |
| void UserImageManagerImpl::OnJobChangedUserImage() { |
| if (GetUser()->is_logged_in()) |
| TryToInitDownloadedProfileImage(); |
| |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED, |
| content::Source<UserImageManagerImpl>(this), |
| content::Details<const User>(GetUser())); |
| } |
| |
| void UserImageManagerImpl::OnJobDone() { |
| if (job_.get()) |
| base::MessageLoopProxy::current()->DeleteSoon(FROM_HERE, job_.release()); |
| else |
| NOTREACHED(); |
| |
| if (!user_needs_migration_) |
| return; |
| // Migration completed for |user_id|. |
| user_needs_migration_ = false; |
| |
| const base::DictionaryValue* prefs_images_unsafe = |
| g_browser_process->local_state()->GetDictionary(kUserImages); |
| const base::DictionaryValue* image_properties = NULL; |
| if (!prefs_images_unsafe->GetDictionaryWithoutPathExpansion( |
| user_id(), &image_properties)) { |
| NOTREACHED(); |
| return; |
| } |
| |
| int image_index = User::kInvalidImageIndex; |
| image_properties->GetInteger(kImageIndexNodeName, &image_index); |
| UMA_HISTOGRAM_ENUMERATION("UserImage.Migration", |
| ImageIndexToHistogramIndex(image_index), |
| kHistogramImagesCount); |
| |
| std::string image_path; |
| image_properties->GetString(kImagePathNodeName, &image_path); |
| if (!image_path.empty()) { |
| // If an old image exists, delete it and remove |user_id| from the old prefs |
| // dictionary only after the deletion has completed. This ensures that no |
| // orphaned image is left behind if the browser crashes before the deletion |
| // has been performed: In that case, local state will be unchanged and the |
| // migration will be run again on the user's next login. |
| background_task_runner_->PostTaskAndReply( |
| FROM_HERE, |
| base::Bind(base::IgnoreResult(&base::DeleteFile), |
| base::FilePath(image_path), |
| false), |
| base::Bind(&UserImageManagerImpl::UpdateLocalStateAfterMigration, |
| weak_factory_.GetWeakPtr())); |
| } else { |
| // If no old image exists, remove |user_id| from the old prefs dictionary. |
| UpdateLocalStateAfterMigration(); |
| } |
| } |
| |
| void UserImageManagerImpl::UpdateLocalStateAfterMigration() { |
| DictionaryPrefUpdate update(g_browser_process->local_state(), |
| kUserImages); |
| update->RemoveWithoutPathExpansion(user_id(), NULL); |
| } |
| |
| void UserImageManagerImpl::TryToCreateImageSyncObserver() { |
| const User* user = GetUser(); |
| // If the currently logged-in user's user image is managed, the sync observer |
| // must not be started so that the policy-set image does not get synced out. |
| if (!user_image_sync_observer_ && |
| user && user->CanSyncImage() && |
| !IsUserImageManaged()) { |
| user_image_sync_observer_.reset(new UserImageSyncObserver(user)); |
| } |
| } |
| |
| const User* UserImageManagerImpl::GetUser() const { |
| return user_manager_->FindUser(user_id()); |
| } |
| |
| User* UserImageManagerImpl::GetUserAndModify() const { |
| return user_manager_->FindUserAndModify(user_id()); |
| } |
| |
| bool UserImageManagerImpl::IsUserLoggedInAndRegular() const { |
| const User* user = GetUser(); |
| if (!user) |
| return false; |
| return user->is_logged_in() && user->GetType() == User::USER_TYPE_REGULAR; |
| } |
| |
| } // namespace chromeos |