change_picture_options_handler.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
1// Copyright (c) 2013 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/ui/webui/options/chromeos/change_picture_options_handler.h" 6 7#include "ash/audio/sounds.h" 8#include "base/bind.h" 9#include "base/bind_helpers.h" 10#include "base/command_line.h" 11#include "base/metrics/histogram.h" 12#include "base/path_service.h" 13#include "base/strings/string_util.h" 14#include "base/strings/utf_string_conversions.h" 15#include "base/values.h" 16#include "chrome/browser/chrome_notification_types.h" 17#include "chrome/browser/chromeos/camera_presence_notifier.h" 18#include "chrome/browser/chromeos/login/users/avatar/user_image_manager.h" 19#include "chrome/browser/chromeos/login/users/user_manager.h" 20#include "chrome/browser/chromeos/profiles/profile_helper.h" 21#include "chrome/browser/ui/browser_finder.h" 22#include "chrome/browser/ui/browser_window.h" 23#include "chrome/browser/ui/chrome_select_file_policy.h" 24#include "chrome/common/chrome_paths.h" 25#include "chrome/common/chrome_switches.h" 26#include "chrome/common/url_constants.h" 27#include "chromeos/audio/chromeos_sounds.h" 28#include "components/user_manager/user.h" 29#include "components/user_manager/user_image/default_user_images.h" 30#include "components/user_manager/user_image/user_image.h" 31#include "content/public/browser/browser_thread.h" 32#include "content/public/browser/notification_service.h" 33#include "content/public/browser/web_ui.h" 34#include "content/public/common/url_constants.h" 35#include "grit/browser_resources.h" 36#include "grit/generated_resources.h" 37#include "grit/theme_resources.h" 38#include "net/base/data_url.h" 39#include "ui/base/l10n/l10n_util.h" 40#include "ui/base/resource/resource_bundle.h" 41#include "ui/base/webui/web_ui_util.h" 42#include "ui/views/widget/widget.h" 43#include "url/gurl.h" 44 45using content::BrowserThread; 46 47namespace chromeos { 48namespace options { 49 50namespace { 51 52// Returns info about extensions for files we support as user images. 53ui::SelectFileDialog::FileTypeInfo GetUserImageFileTypeInfo() { 54 ui::SelectFileDialog::FileTypeInfo file_type_info; 55 file_type_info.extensions.resize(1); 56 57 file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("bmp")); 58 59 file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("jpg")); 60 file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("jpeg")); 61 62 file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("png")); 63 64 file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("tif")); 65 file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("tiff")); 66 67 file_type_info.extension_description_overrides.resize(1); 68 file_type_info.extension_description_overrides[0] = 69 l10n_util::GetStringUTF16(IDS_IMAGE_FILES); 70 71 return file_type_info; 72} 73 74// Time histogram suffix for profile image download. 75const char kProfileDownloadReason[] = "Preferences"; 76 77} // namespace 78 79ChangePictureOptionsHandler::ChangePictureOptionsHandler() 80 : previous_image_url_(url::kAboutBlankURL), 81 previous_image_index_(user_manager::User::USER_IMAGE_INVALID) { 82 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_IMAGE_UPDATED, 83 content::NotificationService::AllSources()); 84 registrar_.Add(this, chrome::NOTIFICATION_PROFILE_IMAGE_UPDATE_FAILED, 85 content::NotificationService::AllSources()); 86 registrar_.Add(this, chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED, 87 content::NotificationService::AllSources()); 88 89 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); 90 media::SoundsManager* manager = media::SoundsManager::Get(); 91 manager->Initialize(SOUND_OBJECT_DELETE, 92 bundle.GetRawDataResource(IDR_SOUND_OBJECT_DELETE_WAV)); 93 manager->Initialize(SOUND_CAMERA_SNAP, 94 bundle.GetRawDataResource(IDR_SOUND_CAMERA_SNAP_WAV)); 95} 96 97ChangePictureOptionsHandler::~ChangePictureOptionsHandler() { 98 CameraPresenceNotifier::GetInstance()->RemoveObserver(this); 99 if (select_file_dialog_.get()) 100 select_file_dialog_->ListenerDestroyed(); 101 if (image_decoder_.get()) 102 image_decoder_->set_delegate(NULL); 103} 104 105void ChangePictureOptionsHandler::GetLocalizedValues( 106 base::DictionaryValue* localized_strings) { 107 DCHECK(localized_strings); 108 localized_strings->SetString("changePicturePage", 109 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_DIALOG_TITLE)); 110 localized_strings->SetString("changePicturePageDescription", 111 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_DIALOG_TEXT)); 112 localized_strings->SetString("takePhoto", 113 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_TAKE_PHOTO)); 114 localized_strings->SetString("discardPhoto", 115 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_DISCARD_PHOTO)); 116 localized_strings->SetString("flipPhoto", 117 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_FLIP_PHOTO)); 118 localized_strings->SetString("chooseFile", 119 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_CHOOSE_FILE)); 120 localized_strings->SetString("profilePhoto", 121 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_PROFILE_PHOTO)); 122 localized_strings->SetString("profilePhotoLoading", 123 l10n_util::GetStringUTF16( 124 IDS_OPTIONS_CHANGE_PICTURE_PROFILE_LOADING_PHOTO)); 125 localized_strings->SetString("previewAltText", 126 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_PREVIEW_ALT)); 127 localized_strings->SetString("authorCredit", 128 l10n_util::GetStringUTF16(IDS_OPTIONS_SET_WALLPAPER_AUTHOR_TEXT)); 129 localized_strings->SetString("photoFromCamera", 130 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_PHOTO_FROM_CAMERA)); 131 localized_strings->SetString("photoFlippedAccessibleText", 132 l10n_util::GetStringUTF16(IDS_OPTIONS_PHOTO_FLIP_ACCESSIBLE_TEXT)); 133 localized_strings->SetString("photoFlippedBackAccessibleText", 134 l10n_util::GetStringUTF16(IDS_OPTIONS_PHOTO_FLIPBACK_ACCESSIBLE_TEXT)); 135 localized_strings->SetString("photoCaptureAccessibleText", 136 l10n_util::GetStringUTF16(IDS_OPTIONS_PHOTO_CAPTURE_ACCESSIBLE_TEXT)); 137 localized_strings->SetString("photoDiscardAccessibleText", 138 l10n_util::GetStringUTF16(IDS_OPTIONS_PHOTO_DISCARD_ACCESSIBLE_TEXT)); 139} 140 141void ChangePictureOptionsHandler::RegisterMessages() { 142 web_ui()->RegisterMessageCallback("chooseFile", 143 base::Bind(&ChangePictureOptionsHandler::HandleChooseFile, 144 base::Unretained(this))); 145 web_ui()->RegisterMessageCallback("takePhoto", 146 base::Bind(&ChangePictureOptionsHandler::HandleTakePhoto, 147 base::Unretained(this))); 148 web_ui()->RegisterMessageCallback("photoTaken", 149 base::Bind(&ChangePictureOptionsHandler::HandlePhotoTaken, 150 base::Unretained(this))); 151 web_ui()->RegisterMessageCallback("discardPhoto", 152 base::Bind(&ChangePictureOptionsHandler::HandleDiscardPhoto, 153 base::Unretained(this))); 154 web_ui()->RegisterMessageCallback("onChangePicturePageShown", 155 base::Bind(&ChangePictureOptionsHandler::HandlePageShown, 156 base::Unretained(this))); 157 web_ui()->RegisterMessageCallback("onChangePicturePageHidden", 158 base::Bind(&ChangePictureOptionsHandler::HandlePageHidden, 159 base::Unretained(this))); 160 web_ui()->RegisterMessageCallback("onChangePicturePageInitialized", 161 base::Bind(&ChangePictureOptionsHandler::HandlePageInitialized, 162 base::Unretained(this))); 163 web_ui()->RegisterMessageCallback("selectImage", 164 base::Bind(&ChangePictureOptionsHandler::HandleSelectImage, 165 base::Unretained(this))); 166} 167 168void ChangePictureOptionsHandler::SendDefaultImages() { 169 base::ListValue image_urls; 170 for (int i = user_manager::kFirstDefaultImageIndex; 171 i < user_manager::kDefaultImagesCount; 172 ++i) { 173 scoped_ptr<base::DictionaryValue> image_data(new base::DictionaryValue); 174 image_data->SetString("url", user_manager::GetDefaultImageUrl(i)); 175 image_data->SetString( 176 "author", 177 l10n_util::GetStringUTF16(user_manager::kDefaultImageAuthorIDs[i])); 178 image_data->SetString( 179 "website", 180 l10n_util::GetStringUTF16(user_manager::kDefaultImageWebsiteIDs[i])); 181 image_data->SetString("title", user_manager::GetDefaultImageDescription(i)); 182 image_urls.Append(image_data.release()); 183 } 184 web_ui()->CallJavascriptFunction("ChangePictureOptions.setDefaultImages", 185 image_urls); 186} 187 188void ChangePictureOptionsHandler::HandleChooseFile( 189 const base::ListValue* args) { 190 DCHECK(args && args->empty()); 191 select_file_dialog_ = ui::SelectFileDialog::Create( 192 this, new ChromeSelectFilePolicy(web_ui()->GetWebContents())); 193 194 base::FilePath downloads_path; 195 if (!PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &downloads_path)) { 196 NOTREACHED(); 197 return; 198 } 199 200 // Static so we initialize it only once. 201 CR_DEFINE_STATIC_LOCAL(ui::SelectFileDialog::FileTypeInfo, file_type_info, 202 (GetUserImageFileTypeInfo())); 203 204 select_file_dialog_->SelectFile( 205 ui::SelectFileDialog::SELECT_OPEN_FILE, 206 l10n_util::GetStringUTF16(IDS_DOWNLOAD_TITLE), 207 downloads_path, 208 &file_type_info, 209 0, 210 FILE_PATH_LITERAL(""), 211 GetBrowserWindow(), 212 NULL); 213} 214 215void ChangePictureOptionsHandler::HandleTakePhoto( 216 const base::ListValue* args) { 217 DCHECK(args->empty()); 218 ash::PlaySystemSoundIfSpokenFeedback(SOUND_CAMERA_SNAP); 219} 220 221void ChangePictureOptionsHandler::HandleDiscardPhoto( 222 const base::ListValue* args) { 223 DCHECK(args->empty()); 224 ash::PlaySystemSoundIfSpokenFeedback(SOUND_OBJECT_DELETE); 225} 226 227void ChangePictureOptionsHandler::HandlePhotoTaken( 228 const base::ListValue* args) { 229 DCHECK_CURRENTLY_ON(BrowserThread::UI); 230 std::string image_url; 231 if (!args || args->GetSize() != 1 || !args->GetString(0, &image_url)) 232 NOTREACHED(); 233 DCHECK(!image_url.empty()); 234 235 std::string mime_type, charset, raw_data; 236 if (!net::DataURL::Parse(GURL(image_url), &mime_type, &charset, &raw_data)) 237 NOTREACHED(); 238 DCHECK_EQ("image/png", mime_type); 239 240 user_photo_ = gfx::ImageSkia(); 241 user_photo_data_url_ = image_url; 242 243 if (image_decoder_.get()) 244 image_decoder_->set_delegate(NULL); 245 image_decoder_ = new ImageDecoder(this, raw_data, 246 ImageDecoder::DEFAULT_CODEC); 247 scoped_refptr<base::MessageLoopProxy> task_runner = 248 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI); 249 image_decoder_->Start(task_runner); 250} 251 252void ChangePictureOptionsHandler::HandlePageInitialized( 253 const base::ListValue* args) { 254 DCHECK(args && args->empty()); 255 SendDefaultImages(); 256} 257 258void ChangePictureOptionsHandler::HandlePageShown(const base::ListValue* args) { 259 DCHECK(args && args->empty()); 260 SendSelectedImage(); 261 UpdateProfileImage(); 262 CameraPresenceNotifier::GetInstance()->AddObserver(this); 263} 264 265void ChangePictureOptionsHandler::HandlePageHidden( 266 const base::ListValue* args) { 267 CameraPresenceNotifier::GetInstance()->RemoveObserver(this); 268} 269 270void ChangePictureOptionsHandler::SendSelectedImage() { 271 const user_manager::User* user = GetUser(); 272 DCHECK(!user->email().empty()); 273 274 previous_image_index_ = user->image_index(); 275 switch (previous_image_index_) { 276 case user_manager::User::USER_IMAGE_EXTERNAL: { 277 // User has image from camera/file, record it and add to the image list. 278 previous_image_ = user->GetImage(); 279 SendOldImage(webui::GetBitmapDataUrl(*previous_image_.bitmap())); 280 break; 281 } 282 case user_manager::User::USER_IMAGE_PROFILE: { 283 // User has his/her Profile image as the current image. 284 SendProfileImage(user->GetImage(), true); 285 break; 286 } 287 default: { 288 DCHECK(previous_image_index_ >= 0 && 289 previous_image_index_ < user_manager::kDefaultImagesCount); 290 if (previous_image_index_ >= user_manager::kFirstDefaultImageIndex) { 291 // User has image from the current set of default images. 292 base::StringValue image_url( 293 user_manager::GetDefaultImageUrl(previous_image_index_)); 294 web_ui()->CallJavascriptFunction( 295 "ChangePictureOptions.setSelectedImage", image_url); 296 } else { 297 // User has an old default image, so present it in the same manner as a 298 // previous image from file. 299 SendOldImage(user_manager::GetDefaultImageUrl(previous_image_index_)); 300 } 301 } 302 } 303} 304 305void ChangePictureOptionsHandler::SendProfileImage(const gfx::ImageSkia& image, 306 bool should_select) { 307 base::StringValue data_url(webui::GetBitmapDataUrl(*image.bitmap())); 308 base::FundamentalValue select(should_select); 309 web_ui()->CallJavascriptFunction("ChangePictureOptions.setProfileImage", 310 data_url, select); 311} 312 313void ChangePictureOptionsHandler::UpdateProfileImage() { 314 UserImageManager* user_image_manager = 315 UserManager::Get()->GetUserImageManager(GetUser()->email()); 316 // If we have a downloaded profile image and haven't sent it in 317 // |SendSelectedImage|, send it now (without selecting). 318 if (previous_image_index_ != user_manager::User::USER_IMAGE_PROFILE && 319 !user_image_manager->DownloadedProfileImage().isNull()) 320 SendProfileImage(user_image_manager->DownloadedProfileImage(), false); 321 322 user_image_manager->DownloadProfileImage(kProfileDownloadReason); 323} 324 325void ChangePictureOptionsHandler::SendOldImage(const std::string& image_url) { 326 previous_image_url_ = image_url; 327 base::StringValue url(image_url); 328 web_ui()->CallJavascriptFunction("ChangePictureOptions.setOldImage", url); 329} 330 331void ChangePictureOptionsHandler::HandleSelectImage( 332 const base::ListValue* args) { 333 std::string image_url; 334 std::string image_type; 335 if (!args || 336 args->GetSize() != 2 || 337 !args->GetString(0, &image_url) || 338 !args->GetString(1, &image_type)) { 339 NOTREACHED(); 340 return; 341 } 342 DCHECK(!image_url.empty()); 343 DCHECK(!image_type.empty()); 344 345 UserImageManager* user_image_manager = 346 UserManager::Get()->GetUserImageManager(GetUser()->email()); 347 int image_index = user_manager::User::USER_IMAGE_INVALID; 348 bool waiting_for_camera_photo = false; 349 350 if (image_type == "old") { 351 // Previous image (from camera or manually uploaded) re-selected. 352 DCHECK(!previous_image_.isNull()); 353 user_image_manager->SaveUserImage( 354 user_manager::UserImage::CreateAndEncode(previous_image_)); 355 356 UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", 357 user_manager::kHistogramImageOld, 358 user_manager::kHistogramImagesCount); 359 VLOG(1) << "Selected old user image"; 360 } else if (image_type == "default" && 361 user_manager::IsDefaultImageUrl(image_url, &image_index)) { 362 // One of the default user images. 363 user_image_manager->SaveUserDefaultImageIndex(image_index); 364 365 UMA_HISTOGRAM_ENUMERATION( 366 "UserImage.ChangeChoice", 367 user_manager::GetDefaultImageHistogramValue(image_index), 368 user_manager::kHistogramImagesCount); 369 VLOG(1) << "Selected default user image: " << image_index; 370 } else if (image_type == "camera") { 371 // Camera image is selected. 372 if (user_photo_.isNull()) { 373 DCHECK(image_decoder_.get()); 374 waiting_for_camera_photo = true; 375 VLOG(1) << "Still waiting for camera image to decode"; 376 } else { 377 SetImageFromCamera(user_photo_); 378 } 379 } else if (image_type == "profile") { 380 // Profile image selected. Could be previous (old) user image. 381 user_image_manager->SaveUserImageFromProfileImage(); 382 383 if (previous_image_index_ == user_manager::User::USER_IMAGE_PROFILE) { 384 UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", 385 user_manager::kHistogramImageOld, 386 user_manager::kHistogramImagesCount); 387 VLOG(1) << "Selected old (profile) user image"; 388 } else { 389 UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", 390 user_manager::kHistogramImageFromProfile, 391 user_manager::kHistogramImagesCount); 392 VLOG(1) << "Selected profile image"; 393 } 394 } else { 395 NOTREACHED() << "Unexpected image type: " << image_type; 396 } 397 398 // Ignore the result of the previous decoding if it's no longer needed. 399 if (!waiting_for_camera_photo && image_decoder_.get()) 400 image_decoder_->set_delegate(NULL); 401} 402 403void ChangePictureOptionsHandler::FileSelected(const base::FilePath& path, 404 int index, 405 void* params) { 406 UserManager* user_manager = UserManager::Get(); 407 user_manager->GetUserImageManager(GetUser()->email())-> 408 SaveUserImageFromFile(path); 409 UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", 410 user_manager::kHistogramImageFromFile, 411 user_manager::kHistogramImagesCount); 412 VLOG(1) << "Selected image from file"; 413} 414 415void ChangePictureOptionsHandler::SetImageFromCamera( 416 const gfx::ImageSkia& photo) { 417 UserManager* user_manager = UserManager::Get(); 418 user_manager->GetUserImageManager(GetUser()->email()) 419 ->SaveUserImage(user_manager::UserImage::CreateAndEncode(photo)); 420 UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", 421 user_manager::kHistogramImageFromCamera, 422 user_manager::kHistogramImagesCount); 423 VLOG(1) << "Selected camera photo"; 424} 425 426void ChangePictureOptionsHandler::SetCameraPresent(bool present) { 427 base::FundamentalValue present_value(present); 428 429 web_ui()->CallJavascriptFunction("ChangePictureOptions.setCameraPresent", 430 present_value); 431} 432 433void ChangePictureOptionsHandler::OnCameraPresenceCheckDone( 434 bool is_camera_present) { 435 SetCameraPresent(is_camera_present); 436} 437 438void ChangePictureOptionsHandler::Observe( 439 int type, 440 const content::NotificationSource& source, 441 const content::NotificationDetails& details) { 442 if (type == chrome::NOTIFICATION_PROFILE_IMAGE_UPDATED) { 443 // User profile image has been updated. 444 SendProfileImage(*content::Details<const gfx::ImageSkia>(details).ptr(), 445 false); 446 } else if (type == chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED) { 447 // Not initialized yet. 448 if (previous_image_index_ == user_manager::User::USER_IMAGE_INVALID) 449 return; 450 SendSelectedImage(); 451 } 452} 453 454gfx::NativeWindow ChangePictureOptionsHandler::GetBrowserWindow() const { 455 Browser* browser = 456 chrome::FindBrowserWithWebContents(web_ui()->GetWebContents()); 457 return browser->window()->GetNativeWindow(); 458} 459 460void ChangePictureOptionsHandler::OnImageDecoded( 461 const ImageDecoder* decoder, 462 const SkBitmap& decoded_image) { 463 DCHECK_EQ(image_decoder_.get(), decoder); 464 image_decoder_ = NULL; 465 user_photo_ = gfx::ImageSkia::CreateFrom1xBitmap(decoded_image); 466 SetImageFromCamera(user_photo_); 467} 468 469void ChangePictureOptionsHandler::OnDecodeImageFailed( 470 const ImageDecoder* decoder) { 471 NOTREACHED() << "Failed to decode PNG image from WebUI"; 472} 473 474user_manager::User* ChangePictureOptionsHandler::GetUser() const { 475 Profile* profile = Profile::FromWebUI(web_ui()); 476 user_manager::User* user = ProfileHelper::Get()->GetUserByProfile(profile); 477 if (!user) 478 return UserManager::Get()->GetActiveUser(); 479 return user; 480} 481 482} // namespace options 483} // namespace chromeos 484