1 // Copyright (c) 2012 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/ash/screenshot_taker.h"
10 #include "ash/shell.h"
11 #include "ash/system/system_notifier.h"
12 #include "base/base64.h"
13 #include "base/bind.h"
14 #include "base/file_util.h"
15 #include "base/i18n/time_formatting.h"
16 #include "base/logging.h"
17 #include "base/memory/ref_counted_memory.h"
18 #include "base/prefs/pref_service.h"
19 #include "base/strings/stringprintf.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/threading/sequenced_worker_pool.h"
22 #include "base/time/time.h"
23 #include "chrome/browser/browser_process.h"
24 #include "chrome/browser/download/download_prefs.h"
25 #include "chrome/browser/notifications/notification_ui_manager.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/profiles/profile_manager.h"
28 #include "chrome/common/pref_names.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "content/public/browser/user_metrics.h"
31 #include "grit/ash_strings.h"
32 #include "grit/theme_resources.h"
33 #include "grit/ui_strings.h"
34 #include "ui/aura/root_window.h"
35 #include "ui/aura/window.h"
36 #include "ui/base/clipboard/clipboard.h"
37 #include "ui/base/clipboard/scoped_clipboard_writer.h"
38 #include "ui/base/l10n/l10n_util.h"
39 #include "ui/base/resource/resource_bundle.h"
40 #include "ui/gfx/image/image.h"
41 #include "ui/snapshot/snapshot.h"
44 #include "ash/shell.h"
45 #include "ash/shell_delegate.h"
48 #if defined(OS_CHROMEOS)
49 #include "chrome/browser/chromeos/drive/file_system_interface.h"
50 #include "chrome/browser/chromeos/drive/file_system_util.h"
51 #include "chrome/browser/chromeos/file_manager/open_util.h"
52 #include "chrome/browser/chromeos/login/user_manager.h"
53 #include "chrome/browser/notifications/desktop_notification_service.h"
54 #include "chrome/browser/notifications/desktop_notification_service_factory.h"
55 #include "chromeos/login/login_state.h"
59 // The minimum interval between two screenshot commands. It has to be
60 // more than 1000 to prevent the conflict of filenames.
61 const int kScreenshotMinimumIntervalInMS
= 1000;
63 const char kNotificationId
[] = "screenshot";
65 const char kNotificationOriginUrl
[] = "chrome://screenshot";
67 const char kImageClipboardFormatPrefix
[] = "<img src='data:image/png;base64,";
68 const char kImageClipboardFormatSuffix
[] = "'>";
70 void CopyScreenshotToClipboard(scoped_refptr
<base::RefCountedString
> png_data
) {
71 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
74 base::Base64Encode(png_data
->data(), &encoded
);
76 // Only cares about HTML because ChromeOS doesn't need other formats.
77 // TODO(dcheng): Why don't we take advantage of the ability to write bitmaps
78 // to the clipboard here?
80 ui::ScopedClipboardWriter
scw(ui::Clipboard::GetForCurrentThread(),
81 ui::CLIPBOARD_TYPE_COPY_PASTE
);
82 std::string
html(kImageClipboardFormatPrefix
);
84 html
+= kImageClipboardFormatSuffix
;
85 scw
.WriteHTML(base::UTF8ToUTF16(html
), std::string());
87 content::RecordAction(base::UserMetricsAction("Screenshot_CopyClipboard"));
90 void ReadFileAndCopyToClipboardLocal(const base::FilePath
& screenshot_path
) {
91 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
93 scoped_refptr
<base::RefCountedString
> png_data(new base::RefCountedString());
94 if (!base::ReadFileToString(screenshot_path
, &(png_data
->data()))) {
95 LOG(ERROR
) << "Failed to read the screenshot file: "
96 << screenshot_path
.value();
100 content::BrowserThread::PostTask(
101 content::BrowserThread::UI
, FROM_HERE
,
102 base::Bind(CopyScreenshotToClipboard
, png_data
));
105 #if defined(OS_CHROMEOS)
106 void ReadFileAndCopyToClipboardDrive(drive::FileError error
,
107 const base::FilePath
& file_path
,
108 scoped_ptr
<drive::ResourceEntry
> entry
) {
109 if (error
!= drive::FILE_ERROR_OK
) {
110 LOG(ERROR
) << "Failed to read the screenshot path on drive: "
111 << drive::FileErrorToString(error
);
114 content::BrowserThread::GetBlockingPool()->PostTask(
116 base::Bind(&ReadFileAndCopyToClipboardLocal
, file_path
));
120 // Delegate for a notification. This class has two roles: to implement callback
121 // methods for notification, and to provide an identity of the associated
123 class ScreenshotTakerNotificationDelegate
: public NotificationDelegate
{
125 ScreenshotTakerNotificationDelegate(bool success
,
127 const base::FilePath
& screenshot_path
)
130 screenshot_path_(screenshot_path
) {
133 // Overridden from NotificationDelegate:
134 virtual void Display() OVERRIDE
{}
135 virtual void Error() OVERRIDE
{}
136 virtual void Close(bool by_user
) OVERRIDE
{}
137 virtual void Click() OVERRIDE
{
140 #if defined(OS_CHROMEOS)
141 file_manager::util::ShowItemInFolder(profile_
, screenshot_path_
);
143 // TODO(sschmitz): perhaps add similar action for Windows.
146 virtual void ButtonClick(int button_index
) OVERRIDE
{
147 DCHECK(success_
&& button_index
== 0);
149 // To avoid keeping the screenshot image on memory, it will re-read the
150 // screenshot file and copy it to the clipboard.
151 #if defined(OS_CHROMEOS)
152 if (drive::util::IsUnderDriveMountPoint(screenshot_path_
)) {
153 drive::FileSystemInterface
* file_system
=
154 drive::util::GetFileSystemByProfile(profile_
);
155 file_system
->GetFile(
156 drive::util::ExtractDrivePath(screenshot_path_
),
157 base::Bind(&ReadFileAndCopyToClipboardDrive
));
161 content::BrowserThread::GetBlockingPool()->PostTask(
162 FROM_HERE
, base::Bind(
163 &ReadFileAndCopyToClipboardLocal
, screenshot_path_
));
165 virtual bool HasClickedListener() OVERRIDE
{ return success_
; }
166 virtual std::string
id() const OVERRIDE
{
167 return std::string(kNotificationId
);
169 virtual content::RenderViewHost
* GetRenderViewHost() const OVERRIDE
{
174 virtual ~ScreenshotTakerNotificationDelegate() {}
178 const base::FilePath screenshot_path_
;
180 DISALLOW_COPY_AND_ASSIGN(ScreenshotTakerNotificationDelegate
);
183 typedef base::Callback
<
184 void(ScreenshotTakerObserver::Result screenshot_result
,
185 const base::FilePath
& screenshot_path
)> ShowNotificationCallback
;
187 void SaveScreenshotInternal(const ShowNotificationCallback
& callback
,
188 const base::FilePath
& screenshot_path
,
189 const base::FilePath
& local_path
,
190 scoped_refptr
<base::RefCountedBytes
> png_data
) {
191 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
192 DCHECK(!local_path
.empty());
193 ScreenshotTakerObserver::Result result
=
194 ScreenshotTakerObserver::SCREENSHOT_SUCCESS
;
195 if (static_cast<size_t>(file_util::WriteFile(
197 reinterpret_cast<char*>(&(png_data
->data()[0])),
198 png_data
->size())) != png_data
->size()) {
199 LOG(ERROR
) << "Failed to save to " << local_path
.value();
200 result
= ScreenshotTakerObserver::SCREENSHOT_WRITE_FILE_FAILED
;
202 content::BrowserThread::PostTask(
203 content::BrowserThread::UI
, FROM_HERE
,
204 base::Bind(callback
, result
, screenshot_path
));
207 void SaveScreenshot(const ShowNotificationCallback
& callback
,
208 const base::FilePath
& screenshot_path
,
209 scoped_refptr
<base::RefCountedBytes
> png_data
) {
210 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
211 DCHECK(!screenshot_path
.empty());
213 if (!base::CreateDirectory(screenshot_path
.DirName())) {
214 LOG(ERROR
) << "Failed to ensure the existence of "
215 << screenshot_path
.DirName().value();
216 content::BrowserThread::PostTask(
217 content::BrowserThread::UI
, FROM_HERE
,
219 ScreenshotTakerObserver::SCREENSHOT_CREATE_DIR_FAILED
,
223 SaveScreenshotInternal(callback
, screenshot_path
, screenshot_path
, png_data
);
226 // TODO(kinaba): crbug.com/140425, remove this ungly #ifdef dispatch.
227 #if defined(OS_CHROMEOS)
228 void SaveScreenshotToDrive(const ShowNotificationCallback
& callback
,
229 const base::FilePath
& screenshot_path
,
230 scoped_refptr
<base::RefCountedBytes
> png_data
,
231 drive::FileError error
,
232 const base::FilePath
& local_path
) {
233 // |screenshot_path| is used in the notification callback.
234 // |local_path| is a temporary file in a hidden cache directory used for
235 // internal work generated by drive::util::PrepareWritableFileAndRun.
236 if (error
!= drive::FILE_ERROR_OK
) {
237 LOG(ERROR
) << "Failed to write screenshot image to Google Drive: " << error
;
238 content::BrowserThread::PostTask(
239 content::BrowserThread::UI
, FROM_HERE
,
241 ScreenshotTakerObserver::SCREENSHOT_CREATE_FILE_FAILED
,
245 SaveScreenshotInternal(callback
, screenshot_path
, local_path
, png_data
);
248 void EnsureDirectoryExistsCallback(
249 const ShowNotificationCallback
& callback
,
251 const base::FilePath
& screenshot_path
,
252 scoped_refptr
<base::RefCountedBytes
> png_data
,
253 drive::FileError error
) {
254 // It is okay to fail with FILE_ERROR_EXISTS since anyway the directory
255 // of the target file exists.
256 if (error
== drive::FILE_ERROR_OK
||
257 error
== drive::FILE_ERROR_EXISTS
) {
258 drive::util::PrepareWritableFileAndRun(
261 base::Bind(&SaveScreenshotToDrive
,
266 LOG(ERROR
) << "Failed to ensure the existence of the specified directory "
267 << "in Google Drive: " << error
;
268 callback
.Run(ScreenshotTakerObserver::SCREENSHOT_CHECK_DIR_FAILED
,
273 void PostSaveScreenshotTask(const ShowNotificationCallback
& callback
,
275 const base::FilePath
& screenshot_path
,
276 scoped_refptr
<base::RefCountedBytes
> png_data
) {
277 if (drive::util::IsUnderDriveMountPoint(screenshot_path
)) {
278 drive::util::EnsureDirectoryExists(
280 screenshot_path
.DirName(),
281 base::Bind(&EnsureDirectoryExistsCallback
,
287 content::BrowserThread::GetBlockingPool()->PostTask(
288 FROM_HERE
, base::Bind(&SaveScreenshot
,
295 void PostSaveScreenshotTask(const ShowNotificationCallback
& callback
,
297 const base::FilePath
& screenshot_path
,
298 scoped_refptr
<base::RefCountedBytes
> png_data
) {
299 content::BrowserThread::GetBlockingPool()->PostTask(
300 FROM_HERE
, base::Bind(&SaveScreenshot
,
307 bool ShouldUse24HourClock() {
308 #if defined(OS_CHROMEOS)
309 Profile
* profile
= ProfileManager::GetActiveUserProfile();
311 return profile
->GetPrefs()->GetBoolean(prefs::kUse24HourClock
);
314 return base::GetHourClockType() == base::k24HourClock
;
317 std::string
GetScreenshotBaseFilename() {
318 base::Time::Exploded now
;
319 base::Time::Now().LocalExplode(&now
);
321 // We don't use base/i18n/time_formatting.h here because it doesn't
322 // support our format. Don't use ICU either to avoid i18n file names
323 // for non-English locales.
324 // TODO(mukai): integrate this logic somewhere time_formatting.h
325 std::string file_name
= base::StringPrintf(
326 "Screenshot %d-%02d-%02d at ", now
.year
, now
.month
, now
.day_of_month
);
328 if (ShouldUse24HourClock()) {
329 file_name
.append(base::StringPrintf(
330 "%02d.%02d.%02d", now
.hour
, now
.minute
, now
.second
));
335 } else if (hour
== 0) {
338 file_name
.append(base::StringPrintf(
339 "%d.%02d.%02d ", hour
, now
.minute
, now
.second
));
340 file_name
.append((now
.hour
>= 12) ? "PM" : "AM");
346 bool GetScreenshotDirectory(base::FilePath
* directory
) {
347 bool is_logged_in
= true;
349 #if defined(OS_CHROMEOS)
350 is_logged_in
= chromeos::LoginState::Get()->IsUserLoggedIn();
354 DownloadPrefs
* download_prefs
= DownloadPrefs::FromBrowserContext(
355 ProfileManager::GetActiveUserProfile());
356 *directory
= download_prefs
->DownloadPath();
358 if (!base::GetTempDir(directory
)) {
359 LOG(ERROR
) << "Failed to find temporary directory.";
366 const int GetScreenshotNotificationTitle(
367 ScreenshotTakerObserver::Result screenshot_result
) {
368 switch (screenshot_result
) {
369 case ScreenshotTakerObserver::SCREENSHOTS_DISABLED
:
370 return IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_DISABLED
;
371 case ScreenshotTakerObserver::SCREENSHOT_SUCCESS
:
372 return IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_SUCCESS
;
374 return IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_FAIL
;
378 const int GetScreenshotNotificationText(
379 ScreenshotTakerObserver::Result screenshot_result
) {
380 switch (screenshot_result
) {
381 case ScreenshotTakerObserver::SCREENSHOTS_DISABLED
:
382 return IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_DISABLED
;
383 case ScreenshotTakerObserver::SCREENSHOT_SUCCESS
:
384 return IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_SUCCESS
;
386 return IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_FAIL
;
392 ScreenshotTaker::ScreenshotTaker()
394 profile_for_test_(NULL
) {
397 ScreenshotTaker::~ScreenshotTaker() {
400 void ScreenshotTaker::HandleTakeScreenshotForAllRootWindows() {
401 if (g_browser_process
->local_state()->
402 GetBoolean(prefs::kDisableScreenshots
)) {
403 ShowNotification(ScreenshotTakerObserver::SCREENSHOTS_DISABLED
,
407 base::FilePath screenshot_directory
;
408 if (!screenshot_directory_for_test_
.empty()) {
409 screenshot_directory
= screenshot_directory_for_test_
;
410 } else if (!GetScreenshotDirectory(&screenshot_directory
)) {
411 ShowNotification(ScreenshotTakerObserver::SCREENSHOT_GET_DIR_FAILED
,
415 std::string screenshot_basename
= !screenshot_basename_for_test_
.empty() ?
416 screenshot_basename_for_test_
: GetScreenshotBaseFilename();
418 aura::Window::Windows root_windows
= ash::Shell::GetAllRootWindows();
419 // Reorder root_windows to take the primary root window's snapshot at first.
420 aura::Window
* primary_root
= ash::Shell::GetPrimaryRootWindow();
421 if (*(root_windows
.begin()) != primary_root
) {
422 root_windows
.erase(std::find(
423 root_windows
.begin(), root_windows
.end(), primary_root
));
424 root_windows
.insert(root_windows
.begin(), primary_root
);
426 for (size_t i
= 0; i
< root_windows
.size(); ++i
) {
427 aura::Window
* root_window
= root_windows
[i
];
428 std::string basename
= screenshot_basename
;
429 gfx::Rect rect
= root_window
->bounds();
430 if (root_windows
.size() > 1)
431 basename
+= base::StringPrintf(" - Display %d", static_cast<int>(i
+ 1));
432 base::FilePath screenshot_path
=
433 screenshot_directory
.AppendASCII(basename
+ ".png");
434 GrabFullWindowSnapshotAsync(
435 root_window
, rect
, GetProfile(), screenshot_path
, i
);
437 content::RecordAction(base::UserMetricsAction("Screenshot_TakeFull"));
440 void ScreenshotTaker::HandleTakePartialScreenshot(
441 aura::Window
* window
, const gfx::Rect
& rect
) {
442 if (g_browser_process
->local_state()->
443 GetBoolean(prefs::kDisableScreenshots
)) {
444 ShowNotification(ScreenshotTakerObserver::SCREENSHOTS_DISABLED
,
448 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
450 base::FilePath screenshot_directory
;
451 if (!screenshot_directory_for_test_
.empty()) {
452 screenshot_directory
= screenshot_directory_for_test_
;
453 } else if (!GetScreenshotDirectory(&screenshot_directory
)) {
454 ShowNotification(ScreenshotTakerObserver::SCREENSHOT_GET_DIR_FAILED
,
459 std::string screenshot_basename
= !screenshot_basename_for_test_
.empty() ?
460 screenshot_basename_for_test_
: GetScreenshotBaseFilename();
461 base::FilePath screenshot_path
=
462 screenshot_directory
.AppendASCII(screenshot_basename
+ ".png");
463 GrabPartialWindowSnapshotAsync(window
, rect
, GetProfile(), screenshot_path
);
464 content::RecordAction(base::UserMetricsAction("Screenshot_TakePartial"));
467 bool ScreenshotTaker::CanTakeScreenshot() {
468 return last_screenshot_timestamp_
.is_null() ||
469 base::Time::Now() - last_screenshot_timestamp_
>
470 base::TimeDelta::FromMilliseconds(
471 kScreenshotMinimumIntervalInMS
);
474 Notification
* ScreenshotTaker::CreateNotification(
475 ScreenshotTakerObserver::Result screenshot_result
,
476 const base::FilePath
& screenshot_path
) {
477 const std::string
notification_id(kNotificationId
);
478 // We cancel a previous screenshot notification, if any, to ensure we get
479 // a fresh notification pop-up.
480 g_browser_process
->notification_ui_manager()->CancelById(notification_id
);
481 const base::string16
replace_id(base::UTF8ToUTF16(notification_id
));
483 (screenshot_result
== ScreenshotTakerObserver::SCREENSHOT_SUCCESS
);
484 message_center::RichNotificationData optional_field
;
486 const base::string16 label
= l10n_util::GetStringUTF16(
487 IDS_MESSAGE_CENTER_NOTIFICATION_BUTTON_COPY_SCREENSHOT_TO_CLIPBOARD
);
488 optional_field
.buttons
.push_back(message_center::ButtonInfo(label
));
490 return new Notification(
491 message_center::NOTIFICATION_TYPE_SIMPLE
,
492 GURL(kNotificationOriginUrl
),
493 l10n_util::GetStringUTF16(
494 GetScreenshotNotificationTitle(screenshot_result
)),
495 l10n_util::GetStringUTF16(
496 GetScreenshotNotificationText(screenshot_result
)),
497 ui::ResourceBundle::GetSharedInstance().GetImageNamed(
498 IDR_SCREENSHOT_NOTIFICATION_ICON
),
499 blink::WebTextDirectionDefault
,
500 message_center::NotifierId(
501 message_center::NotifierId::SYSTEM_COMPONENT
,
502 ash::system_notifier::kNotifierScreenshot
),
503 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_NOTIFIER_SCREENSHOT_NAME
),
506 new ScreenshotTakerNotificationDelegate(
507 success
, GetProfile(), screenshot_path
));
510 void ScreenshotTaker::ShowNotification(
511 ScreenshotTakerObserver::Result screenshot_result
,
512 const base::FilePath
& screenshot_path
) {
513 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
514 #if defined(OS_CHROMEOS)
515 // Do not show a notification that a screenshot was taken while no user is
516 // logged in, since it is confusing for the user to get a message about it
517 // after he logs in (crbug.com/235217).
518 if (!chromeos::LoginState::Get()->IsUserLoggedIn())
521 // TODO(sschmitz): make this work for Windows.
522 DesktopNotificationService
* const service
=
523 DesktopNotificationServiceFactory::GetForProfile(GetProfile());
524 if (service
->IsNotifierEnabled(message_center::NotifierId(
525 message_center::NotifierId::SYSTEM_COMPONENT
,
526 ash::system_notifier::kNotifierScreenshot
))) {
527 scoped_ptr
<Notification
> notification(
528 CreateNotification(screenshot_result
, screenshot_path
));
529 g_browser_process
->notification_ui_manager()->Add(*notification
,
533 FOR_EACH_OBSERVER(ScreenshotTakerObserver
, observers_
,
534 OnScreenshotCompleted(screenshot_result
, screenshot_path
));
537 void ScreenshotTaker::AddObserver(ScreenshotTakerObserver
* observer
) {
538 observers_
.AddObserver(observer
);
541 void ScreenshotTaker::RemoveObserver(ScreenshotTakerObserver
* observer
) {
542 observers_
.RemoveObserver(observer
);
545 bool ScreenshotTaker::HasObserver(ScreenshotTakerObserver
* observer
) const {
546 return observers_
.HasObserver(observer
);
549 void ScreenshotTaker::GrabWindowSnapshotAsyncCallback(
550 base::FilePath screenshot_path
,
553 scoped_refptr
<base::RefCountedBytes
> png_data
) {
556 LOG(ERROR
) << "Failed to grab the window screenshot";
558 ScreenshotTakerObserver::SCREENSHOT_GRABWINDOW_PARTIAL_FAILED
,
561 LOG(ERROR
) << "Failed to grab the window screenshot for " << window_idx
;
563 ScreenshotTakerObserver::SCREENSHOT_GRABWINDOW_FULL_FAILED
,
569 PostSaveScreenshotTask(
570 base::Bind(&ScreenshotTaker::ShowNotification
, factory_
.GetWeakPtr()),
576 void ScreenshotTaker::GrabPartialWindowSnapshotAsync(
577 aura::Window
* window
,
578 const gfx::Rect
& snapshot_bounds
,
580 base::FilePath screenshot_path
) {
581 last_screenshot_timestamp_
= base::Time::Now();
583 bool is_partial
= true;
584 int window_idx
= -1; // unused
585 ui::GrabWindowSnapshotAsync(
588 content::BrowserThread::GetBlockingPool(),
589 base::Bind(&ScreenshotTaker::GrabWindowSnapshotAsyncCallback
,
590 factory_
.GetWeakPtr(),
596 void ScreenshotTaker::GrabFullWindowSnapshotAsync(
597 aura::Window
* window
,
598 const gfx::Rect
& snapshot_bounds
,
600 base::FilePath screenshot_path
,
602 last_screenshot_timestamp_
= base::Time::Now();
604 bool is_partial
= false;
605 ui::GrabWindowSnapshotAsync(
608 content::BrowserThread::GetBlockingPool(),
609 base::Bind(&ScreenshotTaker::GrabWindowSnapshotAsyncCallback
,
610 factory_
.GetWeakPtr(),
616 Profile
* ScreenshotTaker::GetProfile() {
617 if (profile_for_test_
)
618 return profile_for_test_
;
619 return ProfileManager::GetActiveUserProfile();
622 void ScreenshotTaker::SetScreenshotDirectoryForTest(
623 const base::FilePath
& directory
) {
624 screenshot_directory_for_test_
= directory
;
627 void ScreenshotTaker::SetScreenshotBasenameForTest(
628 const std::string
& basename
) {
629 screenshot_basename_for_test_
= basename
;
632 void ScreenshotTaker::SetScreenshotProfileForTest(Profile
* profile
) {
633 profile_for_test_
= profile
;