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/chrome_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 "chrome/grit/generated_resources.h" 28 #include "chromeos/audio/chromeos_sounds.h" 29 #include "components/user_manager/user.h" 30 #include "components/user_manager/user_image/default_user_images.h" 31 #include "components/user_manager/user_image/user_image.h" 32 #include "components/user_manager/user_manager.h" 33 #include "content/public/browser/browser_thread.h" 34 #include "content/public/browser/notification_service.h" 35 #include "content/public/browser/web_ui.h" 36 #include "content/public/common/url_constants.h" 37 #include "grit/browser_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 45 using content::BrowserThread; 46 47 namespace chromeos { 48 namespace options { 49 50 namespace { 51 52 // Returns info about extensions for files we support as user images. 53 ui::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. 75 const char kProfileDownloadReason[] = "Preferences"; 76 77 } // namespace 78 79 ChangePictureOptionsHandler::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 97 ChangePictureOptionsHandler::~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 105 void 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 141 void 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 168 void 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 188 void 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 215 void ChangePictureOptionsHandler::HandleTakePhoto( 216 const base::ListValue* args) { 217 DCHECK(args->empty()); 218 #if !defined(USE_ATHENA) 219 // crbug.com/408733 220 ash::PlaySystemSoundIfSpokenFeedback(SOUND_CAMERA_SNAP); 221 #endif 222 } 223 224 void ChangePictureOptionsHandler::HandleDiscardPhoto( 225 const base::ListValue* args) { 226 DCHECK(args->empty()); 227 #if !defined(USE_ATHENA) 228 ash::PlaySystemSoundIfSpokenFeedback(SOUND_OBJECT_DELETE); 229 #endif 230 } 231 232 void ChangePictureOptionsHandler::HandlePhotoTaken( 233 const base::ListValue* args) { 234 DCHECK_CURRENTLY_ON(BrowserThread::UI); 235 std::string image_url; 236 if (!args || args->GetSize() != 1 || !args->GetString(0, &image_url)) 237 NOTREACHED(); 238 DCHECK(!image_url.empty()); 239 240 std::string mime_type, charset, raw_data; 241 if (!net::DataURL::Parse(GURL(image_url), &mime_type, &charset, &raw_data)) 242 NOTREACHED(); 243 DCHECK_EQ("image/png", mime_type); 244 245 user_photo_ = gfx::ImageSkia(); 246 user_photo_data_url_ = image_url; 247 248 if (image_decoder_.get()) 249 image_decoder_->set_delegate(NULL); 250 image_decoder_ = new ImageDecoder(this, raw_data, 251 ImageDecoder::DEFAULT_CODEC); 252 scoped_refptr<base::MessageLoopProxy> task_runner = 253 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI); 254 image_decoder_->Start(task_runner); 255 } 256 257 void ChangePictureOptionsHandler::HandlePageInitialized( 258 const base::ListValue* args) { 259 DCHECK(args && args->empty()); 260 SendDefaultImages(); 261 } 262 263 void ChangePictureOptionsHandler::HandlePageShown(const base::ListValue* args) { 264 DCHECK(args && args->empty()); 265 SendSelectedImage(); 266 UpdateProfileImage(); 267 CameraPresenceNotifier::GetInstance()->AddObserver(this); 268 } 269 270 void ChangePictureOptionsHandler::HandlePageHidden( 271 const base::ListValue* args) { 272 CameraPresenceNotifier::GetInstance()->RemoveObserver(this); 273 } 274 275 void ChangePictureOptionsHandler::SendSelectedImage() { 276 const user_manager::User* user = GetUser(); 277 DCHECK(!user->email().empty()); 278 279 previous_image_index_ = user->image_index(); 280 switch (previous_image_index_) { 281 case user_manager::User::USER_IMAGE_EXTERNAL: { 282 // User has image from camera/file, record it and add to the image list. 283 previous_image_ = user->GetImage(); 284 SendOldImage(webui::GetBitmapDataUrl(*previous_image_.bitmap())); 285 break; 286 } 287 case user_manager::User::USER_IMAGE_PROFILE: { 288 // User has his/her Profile image as the current image. 289 SendProfileImage(user->GetImage(), true); 290 break; 291 } 292 default: { 293 DCHECK(previous_image_index_ >= 0 && 294 previous_image_index_ < user_manager::kDefaultImagesCount); 295 if (previous_image_index_ >= user_manager::kFirstDefaultImageIndex) { 296 // User has image from the current set of default images. 297 base::StringValue image_url( 298 user_manager::GetDefaultImageUrl(previous_image_index_)); 299 web_ui()->CallJavascriptFunction( 300 "ChangePictureOptions.setSelectedImage", image_url); 301 } else { 302 // User has an old default image, so present it in the same manner as a 303 // previous image from file. 304 SendOldImage(user_manager::GetDefaultImageUrl(previous_image_index_)); 305 } 306 } 307 } 308 } 309 310 void ChangePictureOptionsHandler::SendProfileImage(const gfx::ImageSkia& image, 311 bool should_select) { 312 base::StringValue data_url(webui::GetBitmapDataUrl(*image.bitmap())); 313 base::FundamentalValue select(should_select); 314 web_ui()->CallJavascriptFunction("ChangePictureOptions.setProfileImage", 315 data_url, select); 316 } 317 318 void ChangePictureOptionsHandler::UpdateProfileImage() { 319 UserImageManager* user_image_manager = 320 ChromeUserManager::Get()->GetUserImageManager(GetUser()->email()); 321 // If we have a downloaded profile image and haven't sent it in 322 // |SendSelectedImage|, send it now (without selecting). 323 if (previous_image_index_ != user_manager::User::USER_IMAGE_PROFILE && 324 !user_image_manager->DownloadedProfileImage().isNull()) 325 SendProfileImage(user_image_manager->DownloadedProfileImage(), false); 326 327 user_image_manager->DownloadProfileImage(kProfileDownloadReason); 328 } 329 330 void ChangePictureOptionsHandler::SendOldImage(const std::string& image_url) { 331 previous_image_url_ = image_url; 332 base::StringValue url(image_url); 333 web_ui()->CallJavascriptFunction("ChangePictureOptions.setOldImage", url); 334 } 335 336 void ChangePictureOptionsHandler::HandleSelectImage( 337 const base::ListValue* args) { 338 std::string image_url; 339 std::string image_type; 340 if (!args || 341 args->GetSize() != 2 || 342 !args->GetString(0, &image_url) || 343 !args->GetString(1, &image_type)) { 344 NOTREACHED(); 345 return; 346 } 347 DCHECK(!image_url.empty()); 348 DCHECK(!image_type.empty()); 349 350 UserImageManager* user_image_manager = 351 ChromeUserManager::Get()->GetUserImageManager(GetUser()->email()); 352 int image_index = user_manager::User::USER_IMAGE_INVALID; 353 bool waiting_for_camera_photo = false; 354 355 if (image_type == "old") { 356 // Previous image (from camera or manually uploaded) re-selected. 357 DCHECK(!previous_image_.isNull()); 358 user_image_manager->SaveUserImage( 359 user_manager::UserImage::CreateAndEncode(previous_image_)); 360 361 UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", 362 user_manager::kHistogramImageOld, 363 user_manager::kHistogramImagesCount); 364 VLOG(1) << "Selected old user image"; 365 } else if (image_type == "default" && 366 user_manager::IsDefaultImageUrl(image_url, &image_index)) { 367 // One of the default user images. 368 user_image_manager->SaveUserDefaultImageIndex(image_index); 369 370 UMA_HISTOGRAM_ENUMERATION( 371 "UserImage.ChangeChoice", 372 user_manager::GetDefaultImageHistogramValue(image_index), 373 user_manager::kHistogramImagesCount); 374 VLOG(1) << "Selected default user image: " << image_index; 375 } else if (image_type == "camera") { 376 // Camera image is selected. 377 if (user_photo_.isNull()) { 378 DCHECK(image_decoder_.get()); 379 waiting_for_camera_photo = true; 380 VLOG(1) << "Still waiting for camera image to decode"; 381 } else { 382 SetImageFromCamera(user_photo_); 383 } 384 } else if (image_type == "profile") { 385 // Profile image selected. Could be previous (old) user image. 386 user_image_manager->SaveUserImageFromProfileImage(); 387 388 if (previous_image_index_ == user_manager::User::USER_IMAGE_PROFILE) { 389 UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", 390 user_manager::kHistogramImageOld, 391 user_manager::kHistogramImagesCount); 392 VLOG(1) << "Selected old (profile) user image"; 393 } else { 394 UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", 395 user_manager::kHistogramImageFromProfile, 396 user_manager::kHistogramImagesCount); 397 VLOG(1) << "Selected profile image"; 398 } 399 } else { 400 NOTREACHED() << "Unexpected image type: " << image_type; 401 } 402 403 // Ignore the result of the previous decoding if it's no longer needed. 404 if (!waiting_for_camera_photo && image_decoder_.get()) 405 image_decoder_->set_delegate(NULL); 406 } 407 408 void ChangePictureOptionsHandler::FileSelected(const base::FilePath& path, 409 int index, 410 void* params) { 411 ChromeUserManager::Get() 412 ->GetUserImageManager(GetUser()->email()) 413 ->SaveUserImageFromFile(path); 414 UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", 415 user_manager::kHistogramImageFromFile, 416 user_manager::kHistogramImagesCount); 417 VLOG(1) << "Selected image from file"; 418 } 419 420 void ChangePictureOptionsHandler::SetImageFromCamera( 421 const gfx::ImageSkia& photo) { 422 ChromeUserManager::Get() 423 ->GetUserImageManager(GetUser()->email()) 424 ->SaveUserImage(user_manager::UserImage::CreateAndEncode(photo)); 425 UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice", 426 user_manager::kHistogramImageFromCamera, 427 user_manager::kHistogramImagesCount); 428 VLOG(1) << "Selected camera photo"; 429 } 430 431 void ChangePictureOptionsHandler::SetCameraPresent(bool present) { 432 base::FundamentalValue present_value(present); 433 434 web_ui()->CallJavascriptFunction("ChangePictureOptions.setCameraPresent", 435 present_value); 436 } 437 438 void ChangePictureOptionsHandler::OnCameraPresenceCheckDone( 439 bool is_camera_present) { 440 SetCameraPresent(is_camera_present); 441 } 442 443 void ChangePictureOptionsHandler::Observe( 444 int type, 445 const content::NotificationSource& source, 446 const content::NotificationDetails& details) { 447 if (type == chrome::NOTIFICATION_PROFILE_IMAGE_UPDATED) { 448 // User profile image has been updated. 449 SendProfileImage(*content::Details<const gfx::ImageSkia>(details).ptr(), 450 false); 451 } else if (type == chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED) { 452 // Not initialized yet. 453 if (previous_image_index_ == user_manager::User::USER_IMAGE_INVALID) 454 return; 455 SendSelectedImage(); 456 } 457 } 458 459 gfx::NativeWindow ChangePictureOptionsHandler::GetBrowserWindow() const { 460 Browser* browser = 461 chrome::FindBrowserWithWebContents(web_ui()->GetWebContents()); 462 return browser->window()->GetNativeWindow(); 463 } 464 465 void ChangePictureOptionsHandler::OnImageDecoded( 466 const ImageDecoder* decoder, 467 const SkBitmap& decoded_image) { 468 DCHECK_EQ(image_decoder_.get(), decoder); 469 image_decoder_ = NULL; 470 user_photo_ = gfx::ImageSkia::CreateFrom1xBitmap(decoded_image); 471 SetImageFromCamera(user_photo_); 472 } 473 474 void ChangePictureOptionsHandler::OnDecodeImageFailed( 475 const ImageDecoder* decoder) { 476 NOTREACHED() << "Failed to decode PNG image from WebUI"; 477 } 478 479 user_manager::User* ChangePictureOptionsHandler::GetUser() const { 480 Profile* profile = Profile::FromWebUI(web_ui()); 481 user_manager::User* user = ProfileHelper::Get()->GetUserByProfile(profile); 482 if (!user) 483 return user_manager::UserManager::Get()->GetActiveUser(); 484 return user; 485 } 486 487 } // namespace options 488 } // namespace chromeos 489