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 "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