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