Home | History | Annotate | Download | only in chromeos
      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