1 // Copyright 2015 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/settings/chromeos/change_picture_handler.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "base/base64.h"
11 #include "base/bind.h"
12 #include "base/callback_helpers.h"
13 #include "base/command_line.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/no_destructor.h"
16 #include "base/path_service.h"
17 #include "base/stl_util.h"
18 #include "base/strings/string_util.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/task/post_task.h"
21 #include "base/task/thread_pool.h"
22 #include "base/values.h"
23 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
24 #include "chrome/browser/chromeos/camera_presence_notifier.h"
25 #include "chrome/browser/chromeos/login/users/avatar/user_image_manager.h"
26 #include "chrome/browser/chromeos/login/users/chrome_user_manager.h"
27 #include "chrome/browser/chromeos/login/users/default_user_image/default_user_images.h"
28 #include "chrome/browser/chromeos/profiles/profile_helper.h"
29 #include "chrome/browser/ui/browser_finder.h"
30 #include "chrome/browser/ui/browser_window.h"
31 #include "chrome/browser/ui/chrome_select_file_policy.h"
32 #include "chrome/common/chrome_paths.h"
33 #include "chrome/common/chrome_switches.h"
34 #include "chrome/common/url_constants.h"
35 #include "chrome/grit/browser_resources.h"
36 #include "chrome/grit/generated_resources.h"
37 #include "chromeos/audio/chromeos_sounds.h"
38 #include "components/user_manager/user.h"
39 #include "components/user_manager/user_image/user_image.h"
40 #include "components/user_manager/user_manager.h"
41 #include "content/public/browser/browser_thread.h"
42 #include "content/public/browser/web_ui.h"
43 #include "content/public/common/url_constants.h"
44 #include "net/base/data_url.h"
45 #include "services/audio/public/cpp/sounds/sounds_manager.h"
46 #include "ui/base/l10n/l10n_util.h"
47 #include "ui/base/resource/resource_bundle.h"
48 #include "ui/base/webui/web_ui_util.h"
49 #include "ui/views/widget/widget.h"
50 #include "url/gurl.h"
51 
52 using content::BrowserThread;
53 
54 namespace chromeos {
55 namespace settings {
56 
57 namespace {
58 
59 // Returns info about extensions for files we support as user images.
GetUserImageFileTypeInfo()60 ui::SelectFileDialog::FileTypeInfo GetUserImageFileTypeInfo() {
61   ui::SelectFileDialog::FileTypeInfo file_type_info;
62   file_type_info.extensions.resize(1);
63 
64   file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("bmp"));
65 
66   file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("jpg"));
67   file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("jpeg"));
68 
69   file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("png"));
70 
71   file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("tif"));
72   file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("tiff"));
73 
74   file_type_info.extension_description_overrides.resize(1);
75   file_type_info.extension_description_overrides[0] =
76       l10n_util::GetStringUTF16(IDS_IMAGE_FILES);
77 
78   return file_type_info;
79 }
80 
81 }  // namespace
82 
ChangePictureHandler()83 ChangePictureHandler::ChangePictureHandler()
84     : previous_image_index_(user_manager::User::USER_IMAGE_INVALID) {
85   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
86   audio::SoundsManager* manager = audio::SoundsManager::Get();
87   manager->Initialize(SOUND_OBJECT_DELETE,
88                       bundle.GetRawDataResource(IDR_SOUND_OBJECT_DELETE_WAV));
89   manager->Initialize(SOUND_CAMERA_SNAP,
90                       bundle.GetRawDataResource(IDR_SOUND_CAMERA_SNAP_WAV));
91 }
92 
~ChangePictureHandler()93 ChangePictureHandler::~ChangePictureHandler() {
94   if (select_file_dialog_.get())
95     select_file_dialog_->ListenerDestroyed();
96 }
97 
RegisterMessages()98 void ChangePictureHandler::RegisterMessages() {
99   web_ui()->RegisterMessageCallback(
100       "chooseFile", base::BindRepeating(&ChangePictureHandler::HandleChooseFile,
101                                         base::Unretained(this)));
102   web_ui()->RegisterMessageCallback(
103       "photoTaken", base::BindRepeating(&ChangePictureHandler::HandlePhotoTaken,
104                                         base::Unretained(this)));
105   web_ui()->RegisterMessageCallback(
106       "discardPhoto",
107       base::BindRepeating(&ChangePictureHandler::HandleDiscardPhoto,
108                           base::Unretained(this)));
109   web_ui()->RegisterMessageCallback(
110       "onChangePicturePageInitialized",
111       base::BindRepeating(&ChangePictureHandler::HandlePageInitialized,
112                           base::Unretained(this)));
113   web_ui()->RegisterMessageCallback(
114       "selectImage",
115       base::BindRepeating(&ChangePictureHandler::HandleSelectImage,
116                           base::Unretained(this)));
117   web_ui()->RegisterMessageCallback(
118       "requestSelectedImage",
119       base::BindRepeating(&ChangePictureHandler::HandleRequestSelectedImage,
120                           base::Unretained(this)));
121 }
122 
OnJavascriptAllowed()123 void ChangePictureHandler::OnJavascriptAllowed() {
124   user_manager_observer_.Add(user_manager::UserManager::Get());
125   camera_observer_.Add(CameraPresenceNotifier::GetInstance());
126 }
127 
OnJavascriptDisallowed()128 void ChangePictureHandler::OnJavascriptDisallowed() {
129   user_manager_observer_.Remove(user_manager::UserManager::Get());
130   camera_observer_.Remove(CameraPresenceNotifier::GetInstance());
131 }
132 
SendDefaultImages()133 void ChangePictureHandler::SendDefaultImages() {
134   base::DictionaryValue result;
135   result.SetInteger("first", default_user_image::GetFirstDefaultImage());
136   std::unique_ptr<base::ListValue> default_images =
137       default_user_image::GetAsDictionary(true /* all */);
138   result.Set("images", std::move(default_images));
139   FireWebUIListener("default-images-changed", result);
140 }
141 
HandleChooseFile(const base::ListValue * args)142 void ChangePictureHandler::HandleChooseFile(const base::ListValue* args) {
143   DCHECK(args && args->empty());
144   select_file_dialog_ = ui::SelectFileDialog::Create(
145       this,
146       std::make_unique<ChromeSelectFilePolicy>(web_ui()->GetWebContents()));
147 
148   base::FilePath downloads_path;
149   if (!base::PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &downloads_path)) {
150     NOTREACHED();
151     return;
152   }
153 
154   // Static so we initialize it only once.
155   static base::NoDestructor<ui::SelectFileDialog::FileTypeInfo> file_type_info(
156       GetUserImageFileTypeInfo());
157 
158   select_file_dialog_->SelectFile(
159       ui::SelectFileDialog::SELECT_OPEN_FILE,
160       l10n_util::GetStringUTF16(IDS_DOWNLOAD_TITLE), downloads_path,
161       file_type_info.get(), 0, FILE_PATH_LITERAL(""), GetBrowserWindow(), NULL);
162 }
163 
HandleDiscardPhoto(const base::ListValue * args)164 void ChangePictureHandler::HandleDiscardPhoto(const base::ListValue* args) {
165   DCHECK(args->empty());
166   AccessibilityManager::Get()->PlayEarcon(
167       SOUND_OBJECT_DELETE, PlaySoundOption::ONLY_IF_SPOKEN_FEEDBACK_ENABLED);
168 }
169 
HandlePhotoTaken(const base::ListValue * args)170 void ChangePictureHandler::HandlePhotoTaken(const base::ListValue* args) {
171   DCHECK_CURRENTLY_ON(BrowserThread::UI);
172   AccessibilityManager::Get()->PlayEarcon(
173       SOUND_CAMERA_SNAP, PlaySoundOption::ONLY_IF_SPOKEN_FEEDBACK_ENABLED);
174 
175   std::string image_url;
176   if (!args || args->GetSize() != 1 || !args->GetString(0, &image_url))
177     NOTREACHED();
178   DCHECK(!image_url.empty());
179 
180   std::string raw_data;
181   base::StringPiece url(image_url);
182   const char kDataUrlPrefix[] = "data:image/png;base64,";
183   const size_t kDataUrlPrefixLength = base::size(kDataUrlPrefix) - 1;
184   if (!base::StartsWith(url, kDataUrlPrefix) ||
185       !base::Base64Decode(url.substr(kDataUrlPrefixLength), &raw_data)) {
186     LOG(WARNING) << "Invalid image URL";
187     return;
188   }
189 
190   // Use |raw_data| as image but first verify that it can be decoded.
191   user_photo_ = gfx::ImageSkia();
192   std::vector<unsigned char> photo_data(raw_data.begin(), raw_data.end());
193   user_photo_data_ = base::RefCountedBytes::TakeVector(&photo_data);
194 
195   ImageDecoder::Cancel(this);
196   ImageDecoder::Start(this, raw_data);
197 }
198 
HandlePageInitialized(const base::ListValue * args)199 void ChangePictureHandler::HandlePageInitialized(const base::ListValue* args) {
200   DCHECK(args && args->empty());
201 
202   AllowJavascript();
203 
204   SendDefaultImages();
205   SendSelectedImage();
206   UpdateProfileImage();
207 }
208 
SendSelectedImage()209 void ChangePictureHandler::SendSelectedImage() {
210   const user_manager::User* user = GetUser();
211   DCHECK(user->GetAccountId().is_valid());
212 
213   previous_image_index_ = user->image_index();
214   switch (previous_image_index_) {
215     case user_manager::User::USER_IMAGE_EXTERNAL: {
216       // User has image from camera/file, record it and add to the image list.
217       previous_image_ = user->GetImage();
218       previous_image_format_ = user->image_format();
219       if (previous_image_format_ == user_manager::UserImage::FORMAT_PNG &&
220           user->has_image_bytes()) {
221         previous_image_bytes_ = user->image_bytes();
222         SendOldImage(webui::GetPngDataUrl(previous_image_bytes_->front(),
223                                           previous_image_bytes_->size()));
224       } else {
225         previous_image_bytes_ = nullptr;
226         DCHECK(previous_image_.IsThreadSafe());
227         // Post a task because GetBitmapDataUrl does PNG encoding, which is
228         // slow for large images.
229         base::ThreadPool::PostTaskAndReplyWithResult(
230             FROM_HERE, {base::TaskPriority::USER_BLOCKING},
231             base::BindOnce(&webui::GetBitmapDataUrl, *previous_image_.bitmap()),
232             base::BindOnce(&ChangePictureHandler::SendOldImage,
233                            weak_ptr_factory_.GetWeakPtr()));
234       }
235       break;
236     }
237     case user_manager::User::USER_IMAGE_PROFILE: {
238       // User has their Profile image as the current image.
239       SendProfileImage(user->GetImage(), true);
240       break;
241     }
242     default: {
243       if (default_user_image::IsInCurrentImageSet(previous_image_index_)) {
244         // User has image from the current set of default images.
245         base::Value image_url(
246             default_user_image::GetDefaultImageUrl(previous_image_index_));
247         FireWebUIListener("selected-image-changed", image_url);
248       } else {
249         // User has an old default image, so present it in the same manner as a
250         // previous image from file.
251         previous_image_ = user->GetImage();
252         previous_image_bytes_ = nullptr;
253         previous_image_format_ = user_manager::UserImage::FORMAT_UNKNOWN;
254         SendOldImageWithIndex(
255             default_user_image::GetDefaultImageUrl(previous_image_index_),
256             previous_image_index_);
257       }
258     }
259   }
260 }
261 
SendProfileImage(const gfx::ImageSkia & image,bool should_select)262 void ChangePictureHandler::SendProfileImage(const gfx::ImageSkia& image,
263                                             bool should_select) {
264   base::Value data_url(webui::GetBitmapDataUrl(*image.bitmap()));
265   base::Value select(should_select);
266   FireWebUIListener("profile-image-changed", data_url, select);
267 }
268 
UpdateProfileImage()269 void ChangePictureHandler::UpdateProfileImage() {
270   UserImageManager* user_image_manager =
271       ChromeUserManager::Get()->GetUserImageManager(GetUser()->GetAccountId());
272   // If we have a downloaded profile image and haven't sent it in
273   // |SendSelectedImage|, send it now (without selecting).
274   if (previous_image_index_ != user_manager::User::USER_IMAGE_PROFILE &&
275       !user_image_manager->DownloadedProfileImage().isNull()) {
276     SendProfileImage(user_image_manager->DownloadedProfileImage(), false);
277   }
278   user_image_manager->DownloadProfileImage();
279 }
280 
SendOldImage(std::string && image_url)281 void ChangePictureHandler::SendOldImage(std::string&& image_url) {
282   SendOldImageWithIndex(std::move(image_url), -1);
283 }
284 
SendOldImageWithIndex(std::string && image_url,int image_index)285 void ChangePictureHandler::SendOldImageWithIndex(std::string&& image_url,
286                                                  int image_index) {
287   base::DictionaryValue result;
288   result.SetStringPath("url", std::move(image_url));
289   result.SetIntPath("index", image_index);
290   FireWebUIListener("old-image-changed", result);
291 }
292 
HandleSelectImage(const base::ListValue * args)293 void ChangePictureHandler::HandleSelectImage(const base::ListValue* args) {
294   std::string image_url;
295   std::string image_type;
296   if (!args || args->GetSize() != 2 || !args->GetString(0, &image_url) ||
297       !args->GetString(1, &image_type)) {
298     NOTREACHED();
299     return;
300   }
301   // |image_url| may be empty unless |image_type| is "default".
302   DCHECK(!image_type.empty());
303 
304   UserImageManager* user_image_manager =
305       ChromeUserManager::Get()->GetUserImageManager(GetUser()->GetAccountId());
306   bool waiting_for_camera_photo = false;
307 
308   if (image_type == "old") {
309     // Previous image (from camera or manually uploaded) re-selected.
310     DCHECK(!previous_image_.isNull());
311     std::unique_ptr<user_manager::UserImage> user_image;
312     if (previous_image_format_ == user_manager::UserImage::FORMAT_PNG &&
313         previous_image_bytes_) {
314       user_image = std::make_unique<user_manager::UserImage>(
315           previous_image_, previous_image_bytes_, previous_image_format_);
316       user_image->MarkAsSafe();
317     } else {
318       user_image = user_manager::UserImage::CreateAndEncode(
319           previous_image_, user_manager::UserImage::FORMAT_JPEG);
320     }
321     user_image_manager->SaveUserImage(std::move(user_image));
322 
323     VLOG(1) << "Selected old user image";
324   } else if (image_type == "default") {
325     int image_index = user_manager::User::USER_IMAGE_INVALID;
326     if (default_user_image::IsDefaultImageUrl(image_url, &image_index)) {
327       // One of the default user images.
328       user_image_manager->SaveUserDefaultImageIndex(image_index);
329 
330       VLOG(1) << "Selected default user image: " << image_index;
331     } else {
332       LOG(WARNING) << "Invalid image_url for default image type: " << image_url;
333     }
334   } else if (image_type == "camera") {
335     // Camera image is selected.
336     if (user_photo_.isNull()) {
337       waiting_for_camera_photo = true;
338       VLOG(1) << "Still waiting for camera image to decode";
339     } else {
340       SetImageFromCamera(user_photo_, user_photo_data_.get());
341     }
342   } else if (image_type == "profile") {
343     // Profile image selected. Could be previous (old) user image.
344     user_image_manager->SaveUserImageFromProfileImage();
345   } else {
346     NOTREACHED() << "Unexpected image type: " << image_type;
347   }
348 
349   // Ignore the result of the previous decoding if it's no longer needed.
350   if (!waiting_for_camera_photo)
351     ImageDecoder::Cancel(this);
352 }
353 
HandleRequestSelectedImage(const base::ListValue * args)354 void ChangePictureHandler::HandleRequestSelectedImage(
355     const base::ListValue* args) {
356   SendSelectedImage();
357 }
358 
FileSelected(const base::FilePath & path,int index,void * params)359 void ChangePictureHandler::FileSelected(const base::FilePath& path,
360                                         int index,
361                                         void* params) {
362   ChromeUserManager::Get()
363       ->GetUserImageManager(GetUser()->GetAccountId())
364       ->SaveUserImageFromFile(path);
365   VLOG(1) << "Selected image from file";
366 }
367 
SetImageFromCamera(const gfx::ImageSkia & photo,base::RefCountedBytes * photo_bytes)368 void ChangePictureHandler::SetImageFromCamera(
369     const gfx::ImageSkia& photo,
370     base::RefCountedBytes* photo_bytes) {
371   std::unique_ptr<user_manager::UserImage> user_image =
372       std::make_unique<user_manager::UserImage>(
373           photo, photo_bytes, user_manager::UserImage::FORMAT_PNG);
374   user_image->MarkAsSafe();
375   ChromeUserManager::Get()
376       ->GetUserImageManager(GetUser()->GetAccountId())
377       ->SaveUserImage(std::move(user_image));
378   VLOG(1) << "Selected camera photo";
379 }
380 
SetCameraPresent(bool present)381 void ChangePictureHandler::SetCameraPresent(bool present) {
382   FireWebUIListener("camera-presence-changed", base::Value(present));
383 }
384 
OnCameraPresenceCheckDone(bool is_camera_present)385 void ChangePictureHandler::OnCameraPresenceCheckDone(bool is_camera_present) {
386   SetCameraPresent(is_camera_present);
387 }
388 
OnUserImageChanged(const user_manager::User & user)389 void ChangePictureHandler::OnUserImageChanged(const user_manager::User& user) {
390   // Not initialized yet.
391   if (previous_image_index_ == user_manager::User::USER_IMAGE_INVALID)
392     return;
393   SendSelectedImage();
394 }
395 
OnUserProfileImageUpdated(const user_manager::User & user,const gfx::ImageSkia & profile_image)396 void ChangePictureHandler::OnUserProfileImageUpdated(
397     const user_manager::User& user,
398     const gfx::ImageSkia& profile_image) {
399   // User profile image has been updated.
400   SendProfileImage(profile_image, false);
401 }
402 
GetBrowserWindow() const403 gfx::NativeWindow ChangePictureHandler::GetBrowserWindow() const {
404   Browser* browser =
405       chrome::FindBrowserWithWebContents(web_ui()->GetWebContents());
406   return browser->window()->GetNativeWindow();
407 }
408 
OnImageDecoded(const SkBitmap & decoded_image)409 void ChangePictureHandler::OnImageDecoded(const SkBitmap& decoded_image) {
410   user_photo_ = gfx::ImageSkia::CreateFrom1xBitmap(decoded_image);
411   SetImageFromCamera(user_photo_, user_photo_data_.get());
412 }
413 
OnDecodeImageFailed()414 void ChangePictureHandler::OnDecodeImageFailed() {
415   NOTREACHED() << "Failed to decode PNG image from WebUI";
416 }
417 
GetUser() const418 const user_manager::User* ChangePictureHandler::GetUser() const {
419   Profile* profile = Profile::FromWebUI(web_ui());
420   const user_manager::User* user =
421       ProfileHelper::Get()->GetUserByProfile(profile);
422   if (!user)
423     return user_manager::UserManager::Get()->GetActiveUser();
424   return user;
425 }
426 
427 }  // namespace settings
428 }  // namespace chromeos
429