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.
5 #include "chrome/browser/ui/webui/options/chromeos/change_picture_options_handler.h"
7 #include "ash/audio/sounds.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/default_user_images.h"
19 #include "chrome/browser/chromeos/login/user_image.h"
20 #include "chrome/browser/chromeos/login/user_image_manager.h"
21 #include "chrome/browser/chromeos/login/user_manager.h"
22 #include "chrome/browser/ui/browser_finder.h"
23 #include "chrome/browser/ui/browser_window.h"
24 #include "chrome/browser/ui/chrome_select_file_policy.h"
25 #include "chrome/common/chrome_paths.h"
26 #include "chrome/common/chrome_switches.h"
27 #include "chrome/common/url_constants.h"
28 #include "chromeos/audio/chromeos_sounds.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "content/public/browser/notification_service.h"
31 #include "content/public/browser/web_ui.h"
32 #include "content/public/common/url_constants.h"
33 #include "grit/browser_resources.h"
34 #include "grit/generated_resources.h"
35 #include "grit/theme_resources.h"
36 #include "net/base/data_url.h"
37 #include "ui/base/l10n/l10n_util.h"
38 #include "ui/base/resource/resource_bundle.h"
39 #include "ui/base/webui/web_ui_util.h"
40 #include "ui/views/widget/widget.h"
43 using content::BrowserThread
;
50 // Returns info about extensions for files we support as user images.
51 ui::SelectFileDialog::FileTypeInfo
GetUserImageFileTypeInfo() {
52 ui::SelectFileDialog::FileTypeInfo file_type_info
;
53 file_type_info
.extensions
.resize(1);
55 file_type_info
.extensions
[0].push_back(FILE_PATH_LITERAL("bmp"));
57 file_type_info
.extensions
[0].push_back(FILE_PATH_LITERAL("jpg"));
58 file_type_info
.extensions
[0].push_back(FILE_PATH_LITERAL("jpeg"));
60 file_type_info
.extensions
[0].push_back(FILE_PATH_LITERAL("png"));
62 file_type_info
.extensions
[0].push_back(FILE_PATH_LITERAL("tif"));
63 file_type_info
.extensions
[0].push_back(FILE_PATH_LITERAL("tiff"));
65 file_type_info
.extension_description_overrides
.resize(1);
66 file_type_info
.extension_description_overrides
[0] =
67 l10n_util::GetStringUTF16(IDS_IMAGE_FILES
);
69 return file_type_info
;
72 // Time histogram suffix for profile image download.
73 const char kProfileDownloadReason
[] = "Preferences";
77 ChangePictureOptionsHandler::ChangePictureOptionsHandler()
78 : previous_image_url_(content::kAboutBlankURL
),
79 previous_image_index_(User::kInvalidImageIndex
) {
80 registrar_
.Add(this, chrome::NOTIFICATION_PROFILE_IMAGE_UPDATED
,
81 content::NotificationService::AllSources());
82 registrar_
.Add(this, chrome::NOTIFICATION_PROFILE_IMAGE_UPDATE_FAILED
,
83 content::NotificationService::AllSources());
84 registrar_
.Add(this, chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED
,
85 content::NotificationService::AllSources());
87 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
88 media::SoundsManager
* manager
= media::SoundsManager::Get();
89 manager
->Initialize(SOUND_OBJECT_DELETE
,
90 bundle
.GetRawDataResource(IDR_SOUND_OBJECT_DELETE_WAV
));
91 manager
->Initialize(SOUND_CAMERA_SNAP
,
92 bundle
.GetRawDataResource(IDR_SOUND_CAMERA_SNAP_WAV
));
95 ChangePictureOptionsHandler::~ChangePictureOptionsHandler() {
96 CameraPresenceNotifier::GetInstance()->RemoveObserver(this);
97 if (select_file_dialog_
.get())
98 select_file_dialog_
->ListenerDestroyed();
99 if (image_decoder_
.get())
100 image_decoder_
->set_delegate(NULL
);
103 void ChangePictureOptionsHandler::GetLocalizedValues(
104 base::DictionaryValue
* localized_strings
) {
105 DCHECK(localized_strings
);
106 localized_strings
->SetString("changePicturePage",
107 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_DIALOG_TITLE
));
108 localized_strings
->SetString("changePicturePageDescription",
109 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_DIALOG_TEXT
));
110 localized_strings
->SetString("takePhoto",
111 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_TAKE_PHOTO
));
112 localized_strings
->SetString("discardPhoto",
113 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_DISCARD_PHOTO
));
114 localized_strings
->SetString("flipPhoto",
115 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_FLIP_PHOTO
));
116 localized_strings
->SetString("chooseFile",
117 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_CHOOSE_FILE
));
118 localized_strings
->SetString("profilePhoto",
119 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_PROFILE_PHOTO
));
120 localized_strings
->SetString("profilePhotoLoading",
121 l10n_util::GetStringUTF16(
122 IDS_OPTIONS_CHANGE_PICTURE_PROFILE_LOADING_PHOTO
));
123 localized_strings
->SetString("previewAltText",
124 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_PREVIEW_ALT
));
125 localized_strings
->SetString("authorCredit",
126 l10n_util::GetStringUTF16(IDS_OPTIONS_SET_WALLPAPER_AUTHOR_TEXT
));
127 localized_strings
->SetString("photoFromCamera",
128 l10n_util::GetStringUTF16(IDS_OPTIONS_CHANGE_PICTURE_PHOTO_FROM_CAMERA
));
129 localized_strings
->SetString("photoFlippedAccessibleText",
130 l10n_util::GetStringUTF16(IDS_OPTIONS_PHOTO_FLIP_ACCESSIBLE_TEXT
));
131 localized_strings
->SetString("photoFlippedBackAccessibleText",
132 l10n_util::GetStringUTF16(IDS_OPTIONS_PHOTO_FLIPBACK_ACCESSIBLE_TEXT
));
133 localized_strings
->SetString("photoCaptureAccessibleText",
134 l10n_util::GetStringUTF16(IDS_OPTIONS_PHOTO_CAPTURE_ACCESSIBLE_TEXT
));
135 localized_strings
->SetString("photoDiscardAccessibleText",
136 l10n_util::GetStringUTF16(IDS_OPTIONS_PHOTO_DISCARD_ACCESSIBLE_TEXT
));
139 void ChangePictureOptionsHandler::RegisterMessages() {
140 web_ui()->RegisterMessageCallback("chooseFile",
141 base::Bind(&ChangePictureOptionsHandler::HandleChooseFile
,
142 base::Unretained(this)));
143 web_ui()->RegisterMessageCallback("takePhoto",
144 base::Bind(&ChangePictureOptionsHandler::HandleTakePhoto
,
145 base::Unretained(this)));
146 web_ui()->RegisterMessageCallback("photoTaken",
147 base::Bind(&ChangePictureOptionsHandler::HandlePhotoTaken
,
148 base::Unretained(this)));
149 web_ui()->RegisterMessageCallback("discardPhoto",
150 base::Bind(&ChangePictureOptionsHandler::HandleDiscardPhoto
,
151 base::Unretained(this)));
152 web_ui()->RegisterMessageCallback("onChangePicturePageShown",
153 base::Bind(&ChangePictureOptionsHandler::HandlePageShown
,
154 base::Unretained(this)));
155 web_ui()->RegisterMessageCallback("onChangePicturePageHidden",
156 base::Bind(&ChangePictureOptionsHandler::HandlePageHidden
,
157 base::Unretained(this)));
158 web_ui()->RegisterMessageCallback("onChangePicturePageInitialized",
159 base::Bind(&ChangePictureOptionsHandler::HandlePageInitialized
,
160 base::Unretained(this)));
161 web_ui()->RegisterMessageCallback("selectImage",
162 base::Bind(&ChangePictureOptionsHandler::HandleSelectImage
,
163 base::Unretained(this)));
166 void ChangePictureOptionsHandler::SendDefaultImages() {
167 base::ListValue image_urls
;
168 for (int i
= kFirstDefaultImageIndex
; i
< kDefaultImagesCount
; ++i
) {
169 scoped_ptr
<base::DictionaryValue
> image_data(new base::DictionaryValue
);
170 image_data
->SetString("url", GetDefaultImageUrl(i
));
171 image_data
->SetString(
172 "author", l10n_util::GetStringUTF16(kDefaultImageAuthorIDs
[i
]));
173 image_data
->SetString(
174 "website", l10n_util::GetStringUTF16(kDefaultImageWebsiteIDs
[i
]));
175 image_data
->SetString("title", GetDefaultImageDescription(i
));
176 image_urls
.Append(image_data
.release());
178 web_ui()->CallJavascriptFunction("ChangePictureOptions.setDefaultImages",
182 void ChangePictureOptionsHandler::HandleChooseFile(
183 const base::ListValue
* args
) {
184 DCHECK(args
&& args
->empty());
185 select_file_dialog_
= ui::SelectFileDialog::Create(
186 this, new ChromeSelectFilePolicy(web_ui()->GetWebContents()));
188 base::FilePath downloads_path
;
189 if (!PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS
, &downloads_path
)) {
194 // Static so we initialize it only once.
195 CR_DEFINE_STATIC_LOCAL(ui::SelectFileDialog::FileTypeInfo
, file_type_info
,
196 (GetUserImageFileTypeInfo()));
198 select_file_dialog_
->SelectFile(
199 ui::SelectFileDialog::SELECT_OPEN_FILE
,
200 l10n_util::GetStringUTF16(IDS_DOWNLOAD_TITLE
),
204 FILE_PATH_LITERAL(""),
209 void ChangePictureOptionsHandler::HandleTakePhoto(
210 const base::ListValue
* args
) {
211 DCHECK(args
->empty());
212 ash::PlaySystemSoundIfSpokenFeedback(SOUND_CAMERA_SNAP
);
215 void ChangePictureOptionsHandler::HandleDiscardPhoto(
216 const base::ListValue
* args
) {
217 DCHECK(args
->empty());
218 ash::PlaySystemSoundIfSpokenFeedback(SOUND_OBJECT_DELETE
);
221 void ChangePictureOptionsHandler::HandlePhotoTaken(
222 const base::ListValue
* args
) {
223 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
224 std::string image_url
;
225 if (!args
|| args
->GetSize() != 1 || !args
->GetString(0, &image_url
))
227 DCHECK(!image_url
.empty());
229 std::string mime_type
, charset
, raw_data
;
230 if (!net::DataURL::Parse(GURL(image_url
), &mime_type
, &charset
, &raw_data
))
232 DCHECK_EQ("image/png", mime_type
);
234 user_photo_
= gfx::ImageSkia();
235 user_photo_data_url_
= image_url
;
237 if (image_decoder_
.get())
238 image_decoder_
->set_delegate(NULL
);
239 image_decoder_
= new ImageDecoder(this, raw_data
,
240 ImageDecoder::DEFAULT_CODEC
);
241 scoped_refptr
<base::MessageLoopProxy
> task_runner
=
242 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI
);
243 image_decoder_
->Start(task_runner
);
246 void ChangePictureOptionsHandler::HandlePageInitialized(
247 const base::ListValue
* args
) {
248 DCHECK(args
&& args
->empty());
252 void ChangePictureOptionsHandler::HandlePageShown(const base::ListValue
* args
) {
253 DCHECK(args
&& args
->empty());
255 UpdateProfileImage();
256 CameraPresenceNotifier::GetInstance()->AddObserver(this);
259 void ChangePictureOptionsHandler::HandlePageHidden(
260 const base::ListValue
* args
) {
261 CameraPresenceNotifier::GetInstance()->RemoveObserver(this);
264 void ChangePictureOptionsHandler::SendSelectedImage() {
265 const User
* user
= GetUser();
266 DCHECK(!user
->email().empty());
268 previous_image_index_
= user
->image_index();
269 switch (previous_image_index_
) {
270 case User::kExternalImageIndex
: {
271 // User has image from camera/file, record it and add to the image list.
272 previous_image_
= user
->GetImage();
273 SendOldImage(webui::GetBitmapDataUrl(*previous_image_
.bitmap()));
276 case User::kProfileImageIndex
: {
277 // User has his/her Profile image as the current image.
278 SendProfileImage(user
->GetImage(), true);
282 DCHECK(previous_image_index_
>= 0 &&
283 previous_image_index_
< kDefaultImagesCount
);
284 if (previous_image_index_
>= kFirstDefaultImageIndex
) {
285 // User has image from the current set of default images.
286 base::StringValue
image_url(GetDefaultImageUrl(previous_image_index_
));
287 web_ui()->CallJavascriptFunction(
288 "ChangePictureOptions.setSelectedImage", image_url
);
290 // User has an old default image, so present it in the same manner as a
291 // previous image from file.
292 SendOldImage(GetDefaultImageUrl(previous_image_index_
));
298 void ChangePictureOptionsHandler::SendProfileImage(const gfx::ImageSkia
& image
,
299 bool should_select
) {
300 base::StringValue
data_url(webui::GetBitmapDataUrl(*image
.bitmap()));
301 base::FundamentalValue
select(should_select
);
302 web_ui()->CallJavascriptFunction("ChangePictureOptions.setProfileImage",
306 void ChangePictureOptionsHandler::UpdateProfileImage() {
307 UserImageManager
* user_image_manager
=
308 UserManager::Get()->GetUserImageManager(GetUser()->email());
309 // If we have a downloaded profile image and haven't sent it in
310 // |SendSelectedImage|, send it now (without selecting).
311 if (previous_image_index_
!= User::kProfileImageIndex
&&
312 !user_image_manager
->DownloadedProfileImage().isNull())
313 SendProfileImage(user_image_manager
->DownloadedProfileImage(), false);
315 user_image_manager
->DownloadProfileImage(kProfileDownloadReason
);
318 void ChangePictureOptionsHandler::SendOldImage(const std::string
& image_url
) {
319 previous_image_url_
= image_url
;
320 base::StringValue
url(image_url
);
321 web_ui()->CallJavascriptFunction("ChangePictureOptions.setOldImage", url
);
324 void ChangePictureOptionsHandler::HandleSelectImage(
325 const base::ListValue
* args
) {
326 std::string image_url
;
327 std::string image_type
;
329 args
->GetSize() != 2 ||
330 !args
->GetString(0, &image_url
) ||
331 !args
->GetString(1, &image_type
)) {
335 DCHECK(!image_url
.empty());
336 DCHECK(!image_type
.empty());
338 UserImageManager
* user_image_manager
=
339 UserManager::Get()->GetUserImageManager(GetUser()->email());
340 int image_index
= User::kInvalidImageIndex
;
341 bool waiting_for_camera_photo
= false;
343 if (image_type
== "old") {
344 // Previous image (from camera or manually uploaded) re-selected.
345 DCHECK(!previous_image_
.isNull());
346 user_image_manager
->SaveUserImage(
347 UserImage::CreateAndEncode(previous_image_
));
349 UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice",
351 kHistogramImagesCount
);
352 VLOG(1) << "Selected old user image";
353 } else if (image_type
== "default" &&
354 IsDefaultImageUrl(image_url
, &image_index
)) {
355 // One of the default user images.
356 user_image_manager
->SaveUserDefaultImageIndex(image_index
);
358 UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice",
359 GetDefaultImageHistogramValue(image_index
),
360 kHistogramImagesCount
);
361 VLOG(1) << "Selected default user image: " << image_index
;
362 } else if (image_type
== "camera") {
363 // Camera image is selected.
364 if (user_photo_
.isNull()) {
365 DCHECK(image_decoder_
.get());
366 waiting_for_camera_photo
= true;
367 VLOG(1) << "Still waiting for camera image to decode";
369 SetImageFromCamera(user_photo_
);
371 } else if (image_type
== "profile") {
372 // Profile image selected. Could be previous (old) user image.
373 user_image_manager
->SaveUserImageFromProfileImage();
375 if (previous_image_index_
== User::kProfileImageIndex
) {
376 UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice",
378 kHistogramImagesCount
);
379 VLOG(1) << "Selected old (profile) user image";
381 UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice",
382 kHistogramImageFromProfile
,
383 kHistogramImagesCount
);
384 VLOG(1) << "Selected profile image";
387 NOTREACHED() << "Unexpected image type: " << image_type
;
390 // Ignore the result of the previous decoding if it's no longer needed.
391 if (!waiting_for_camera_photo
&& image_decoder_
.get())
392 image_decoder_
->set_delegate(NULL
);
395 void ChangePictureOptionsHandler::FileSelected(const base::FilePath
& path
,
398 UserManager
* user_manager
= UserManager::Get();
399 user_manager
->GetUserImageManager(GetUser()->email())->
400 SaveUserImageFromFile(path
);
401 UMA_HISTOGRAM_ENUMERATION(
402 "UserImage.ChangeChoice", kHistogramImageFromFile
, kHistogramImagesCount
);
403 VLOG(1) << "Selected image from file";
406 void ChangePictureOptionsHandler::SetImageFromCamera(
407 const gfx::ImageSkia
& photo
) {
408 UserManager
* user_manager
= UserManager::Get();
409 user_manager
->GetUserImageManager(GetUser()->email())->SaveUserImage(
410 UserImage::CreateAndEncode(photo
));
411 UMA_HISTOGRAM_ENUMERATION("UserImage.ChangeChoice",
412 kHistogramImageFromCamera
,
413 kHistogramImagesCount
);
414 VLOG(1) << "Selected camera photo";
417 void ChangePictureOptionsHandler::SetCameraPresent(bool present
) {
418 base::FundamentalValue
present_value(present
);
420 web_ui()->CallJavascriptFunction("ChangePictureOptions.setCameraPresent",
424 void ChangePictureOptionsHandler::OnCameraPresenceCheckDone(
425 bool is_camera_present
) {
426 SetCameraPresent(is_camera_present
);
429 void ChangePictureOptionsHandler::Observe(
431 const content::NotificationSource
& source
,
432 const content::NotificationDetails
& details
) {
433 if (type
== chrome::NOTIFICATION_PROFILE_IMAGE_UPDATED
) {
434 // User profile image has been updated.
435 SendProfileImage(*content::Details
<const gfx::ImageSkia
>(details
).ptr(),
437 } else if (type
== chrome::NOTIFICATION_LOGIN_USER_IMAGE_CHANGED
) {
438 // Not initialized yet.
439 if (previous_image_index_
== User::kInvalidImageIndex
)
445 gfx::NativeWindow
ChangePictureOptionsHandler::GetBrowserWindow() const {
447 chrome::FindBrowserWithWebContents(web_ui()->GetWebContents());
448 return browser
->window()->GetNativeWindow();
451 void ChangePictureOptionsHandler::OnImageDecoded(
452 const ImageDecoder
* decoder
,
453 const SkBitmap
& decoded_image
) {
454 DCHECK_EQ(image_decoder_
.get(), decoder
);
455 image_decoder_
= NULL
;
456 user_photo_
= gfx::ImageSkia::CreateFrom1xBitmap(decoded_image
);
457 SetImageFromCamera(user_photo_
);
460 void ChangePictureOptionsHandler::OnDecodeImageFailed(
461 const ImageDecoder
* decoder
) {
462 NOTREACHED() << "Failed to decode PNG image from WebUI";
465 User
* ChangePictureOptionsHandler::GetUser() const {
466 Profile
* profile
= Profile::FromWebUI(web_ui());
467 User
* user
= UserManager::Get()->GetUserByProfile(profile
);
469 return UserManager::Get()->GetActiveUser();
473 } // namespace options
474 } // namespace chromeos