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