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.
4
5#include "chrome/browser/ui/ash/screenshot_taker.h"
6
7#include <climits>
8#include <string>
9
10#include "ash/shell.h"
11#include "ash/shell_delegate.h"
12#include "ash/system/system_notifier.h"
13#include "base/base64.h"
14#include "base/bind.h"
15#include "base/files/file_util.h"
16#include "base/i18n/time_formatting.h"
17#include "base/logging.h"
18#include "base/memory/ref_counted_memory.h"
19#include "base/prefs/pref_service.h"
20#include "base/strings/stringprintf.h"
21#include "base/strings/utf_string_conversions.h"
22#include "base/threading/sequenced_worker_pool.h"
23#include "base/time/time.h"
24#include "chrome/browser/browser_process.h"
25#include "chrome/browser/download/download_prefs.h"
26#include "chrome/browser/notifications/notification_ui_manager.h"
27#include "chrome/browser/profiles/profile.h"
28#include "chrome/browser/profiles/profile_manager.h"
29#include "chrome/common/pref_names.h"
30#include "content/public/browser/browser_thread.h"
31#include "content/public/browser/user_metrics.h"
32#include "grit/ash_strings.h"
33#include "grit/theme_resources.h"
34#include "ui/aura/window.h"
35#include "ui/aura/window_event_dispatcher.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"
42#include "ui/strings/grit/ui_strings.h"
43
44#if defined(OS_CHROMEOS)
45#include "chrome/browser/chromeos/drive/file_system_interface.h"
46#include "chrome/browser/chromeos/drive/file_system_util.h"
47#include "chrome/browser/chromeos/file_manager/open_util.h"
48#include "chrome/browser/notifications/desktop_notification_service.h"
49#include "chrome/browser/notifications/desktop_notification_service_factory.h"
50#include "chromeos/login/login_state.h"
51#endif
52
53namespace {
54// The minimum interval between two screenshot commands.  It has to be
55// more than 1000 to prevent the conflict of filenames.
56const int kScreenshotMinimumIntervalInMS = 1000;
57
58const char kNotificationId[] = "screenshot";
59
60#if defined(OS_CHROMEOS)
61const char kNotificationOriginUrl[] = "chrome://screenshot";
62#endif
63
64const char kImageClipboardFormatPrefix[] = "<img src='data:image/png;base64,";
65const char kImageClipboardFormatSuffix[] = "'>";
66
67void CopyScreenshotToClipboard(scoped_refptr<base::RefCountedString> png_data) {
68  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
69
70  std::string encoded;
71  base::Base64Encode(png_data->data(), &encoded);
72
73  // Only cares about HTML because ChromeOS doesn't need other formats.
74  // TODO(dcheng): Why don't we take advantage of the ability to write bitmaps
75  // to the clipboard here?
76  {
77    ui::ScopedClipboardWriter scw(ui::CLIPBOARD_TYPE_COPY_PASTE);
78    std::string html(kImageClipboardFormatPrefix);
79    html += encoded;
80    html += kImageClipboardFormatSuffix;
81    scw.WriteHTML(base::UTF8ToUTF16(html), std::string());
82  }
83  content::RecordAction(base::UserMetricsAction("Screenshot_CopyClipboard"));
84}
85
86void ReadFileAndCopyToClipboardLocal(const base::FilePath& screenshot_path) {
87  DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
88
89  scoped_refptr<base::RefCountedString> png_data(new base::RefCountedString());
90  if (!base::ReadFileToString(screenshot_path, &(png_data->data()))) {
91    LOG(ERROR) << "Failed to read the screenshot file: "
92               << screenshot_path.value();
93    return;
94  }
95
96  content::BrowserThread::PostTask(
97      content::BrowserThread::UI, FROM_HERE,
98      base::Bind(CopyScreenshotToClipboard, png_data));
99}
100
101#if defined(OS_CHROMEOS)
102void ReadFileAndCopyToClipboardDrive(drive::FileError error,
103                                     const base::FilePath& file_path,
104                                     scoped_ptr<drive::ResourceEntry> entry) {
105  if (error != drive::FILE_ERROR_OK) {
106    LOG(ERROR) << "Failed to read the screenshot path on drive: "
107               << drive::FileErrorToString(error);
108    return;
109  }
110  content::BrowserThread::GetBlockingPool()->PostTask(
111      FROM_HERE,
112      base::Bind(&ReadFileAndCopyToClipboardLocal, file_path));
113}
114#endif
115
116// Delegate for a notification. This class has two roles: to implement callback
117// methods for notification, and to provide an identity of the associated
118// notification.
119class ScreenshotTakerNotificationDelegate : public NotificationDelegate {
120 public:
121  ScreenshotTakerNotificationDelegate(bool success,
122                                      Profile* profile,
123                                      const base::FilePath& screenshot_path)
124      : success_(success),
125        profile_(profile),
126        screenshot_path_(screenshot_path) {
127  }
128
129  // Overridden from NotificationDelegate:
130  virtual void Display() OVERRIDE {}
131  virtual void Error() OVERRIDE {}
132  virtual void Close(bool by_user) OVERRIDE {}
133  virtual void Click() OVERRIDE {
134    if (!success_)
135      return;
136#if defined(OS_CHROMEOS)
137    file_manager::util::ShowItemInFolder(profile_, screenshot_path_);
138#else
139    // TODO(sschmitz): perhaps add similar action for Windows.
140#endif
141  }
142  virtual void ButtonClick(int button_index) OVERRIDE {
143    DCHECK(success_ && button_index == 0);
144
145    // To avoid keeping the screenshot image on memory, it will re-read the
146    // screenshot file and copy it to the clipboard.
147#if defined(OS_CHROMEOS)
148  if (drive::util::IsUnderDriveMountPoint(screenshot_path_)) {
149    drive::FileSystemInterface* file_system =
150        drive::util::GetFileSystemByProfile(profile_);
151    file_system->GetFile(
152        drive::util::ExtractDrivePath(screenshot_path_),
153        base::Bind(&ReadFileAndCopyToClipboardDrive));
154    return;
155  }
156#endif
157    content::BrowserThread::GetBlockingPool()->PostTask(
158        FROM_HERE, base::Bind(
159            &ReadFileAndCopyToClipboardLocal, screenshot_path_));
160  }
161  virtual bool HasClickedListener() OVERRIDE { return success_; }
162  virtual std::string id() const OVERRIDE {
163    return std::string(kNotificationId);
164  }
165  virtual content::WebContents* GetWebContents() const OVERRIDE {
166    return NULL;
167  }
168
169 private:
170  virtual ~ScreenshotTakerNotificationDelegate() {}
171
172  const bool success_;
173  Profile* profile_;
174  const base::FilePath screenshot_path_;
175
176  DISALLOW_COPY_AND_ASSIGN(ScreenshotTakerNotificationDelegate);
177};
178
179typedef base::Callback<
180  void(ScreenshotTakerObserver::Result screenshot_result,
181       const base::FilePath& screenshot_path)> ShowNotificationCallback;
182
183void SaveScreenshotInternal(const ShowNotificationCallback& callback,
184                            const base::FilePath& screenshot_path,
185                            const base::FilePath& local_path,
186                            scoped_refptr<base::RefCountedBytes> png_data) {
187  DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
188  DCHECK(!local_path.empty());
189  ScreenshotTakerObserver::Result result =
190      ScreenshotTakerObserver::SCREENSHOT_SUCCESS;
191  if (static_cast<size_t>(base::WriteFile(
192          local_path,
193          reinterpret_cast<char*>(&(png_data->data()[0])),
194          png_data->size())) != png_data->size()) {
195    LOG(ERROR) << "Failed to save to " << local_path.value();
196    result = ScreenshotTakerObserver::SCREENSHOT_WRITE_FILE_FAILED;
197  }
198  content::BrowserThread::PostTask(
199      content::BrowserThread::UI, FROM_HERE,
200      base::Bind(callback, result, screenshot_path));
201}
202
203void SaveScreenshot(const ShowNotificationCallback& callback,
204                    const base::FilePath& screenshot_path,
205                    scoped_refptr<base::RefCountedBytes> png_data) {
206  DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
207  DCHECK(!screenshot_path.empty());
208
209  if (!base::CreateDirectory(screenshot_path.DirName())) {
210    LOG(ERROR) << "Failed to ensure the existence of "
211               << screenshot_path.DirName().value();
212    content::BrowserThread::PostTask(
213        content::BrowserThread::UI, FROM_HERE,
214        base::Bind(callback,
215                   ScreenshotTakerObserver::SCREENSHOT_CREATE_DIR_FAILED,
216                   screenshot_path));
217    return;
218  }
219  SaveScreenshotInternal(callback, screenshot_path, screenshot_path, png_data);
220}
221
222// TODO(kinaba): crbug.com/140425, remove this ungly #ifdef dispatch.
223#if defined(OS_CHROMEOS)
224void SaveScreenshotToDrive(const ShowNotificationCallback& callback,
225                           const base::FilePath& screenshot_path,
226                           scoped_refptr<base::RefCountedBytes> png_data,
227                           drive::FileError error,
228                           const base::FilePath& local_path) {
229  // |screenshot_path| is used in the notification callback.
230  // |local_path| is a temporary file in a hidden cache directory used for
231  // internal work generated by drive::util::PrepareWritableFileAndRun.
232  if (error != drive::FILE_ERROR_OK) {
233    LOG(ERROR) << "Failed to write screenshot image to Google Drive: " << error;
234    content::BrowserThread::PostTask(
235        content::BrowserThread::UI, FROM_HERE,
236        base::Bind(callback,
237                   ScreenshotTakerObserver::SCREENSHOT_CREATE_FILE_FAILED,
238                   screenshot_path));
239    return;
240  }
241  SaveScreenshotInternal(callback, screenshot_path, local_path, png_data);
242}
243
244void EnsureDirectoryExistsCallback(
245    const ShowNotificationCallback& callback,
246    Profile* profile,
247    const base::FilePath& screenshot_path,
248    scoped_refptr<base::RefCountedBytes> png_data,
249    drive::FileError error) {
250  // It is okay to fail with FILE_ERROR_EXISTS since anyway the directory
251  // of the target file exists.
252  if (error == drive::FILE_ERROR_OK ||
253      error == drive::FILE_ERROR_EXISTS) {
254    drive::util::PrepareWritableFileAndRun(
255        profile,
256        screenshot_path,
257        base::Bind(&SaveScreenshotToDrive,
258                   callback,
259                   screenshot_path,
260                   png_data));
261  } else {
262    LOG(ERROR) << "Failed to ensure the existence of the specified directory "
263               << "in Google Drive: " << error;
264    callback.Run(ScreenshotTakerObserver::SCREENSHOT_CHECK_DIR_FAILED,
265                 screenshot_path);
266  }
267}
268
269void PostSaveScreenshotTask(const ShowNotificationCallback& callback,
270                            Profile* profile,
271                            const base::FilePath& screenshot_path,
272                            scoped_refptr<base::RefCountedBytes> png_data) {
273  if (drive::util::IsUnderDriveMountPoint(screenshot_path)) {
274    drive::util::EnsureDirectoryExists(
275        profile,
276        screenshot_path.DirName(),
277        base::Bind(&EnsureDirectoryExistsCallback,
278                   callback,
279                   profile,
280                   screenshot_path,
281                   png_data));
282  } else {
283    content::BrowserThread::GetBlockingPool()->PostTask(
284        FROM_HERE, base::Bind(&SaveScreenshot,
285                              callback,
286                              screenshot_path,
287                              png_data));
288  }
289}
290#else
291void PostSaveScreenshotTask(const ShowNotificationCallback& callback,
292                            Profile* profile,
293                            const base::FilePath& screenshot_path,
294                            scoped_refptr<base::RefCountedBytes> png_data) {
295  content::BrowserThread::GetBlockingPool()->PostTask(
296      FROM_HERE, base::Bind(&SaveScreenshot,
297                            callback,
298                            screenshot_path,
299                            png_data));
300}
301#endif
302
303bool ShouldUse24HourClock() {
304#if defined(OS_CHROMEOS)
305  Profile* profile = ProfileManager::GetActiveUserProfile();
306  if (profile) {
307    return profile->GetPrefs()->GetBoolean(prefs::kUse24HourClock);
308  }
309#endif
310  return base::GetHourClockType() == base::k24HourClock;
311}
312
313std::string GetScreenshotBaseFilename() {
314  base::Time::Exploded now;
315  base::Time::Now().LocalExplode(&now);
316
317  // We don't use base/i18n/time_formatting.h here because it doesn't
318  // support our format.  Don't use ICU either to avoid i18n file names
319  // for non-English locales.
320  // TODO(mukai): integrate this logic somewhere time_formatting.h
321  std::string file_name = base::StringPrintf(
322      "Screenshot %d-%02d-%02d at ", now.year, now.month, now.day_of_month);
323
324  if (ShouldUse24HourClock()) {
325    file_name.append(base::StringPrintf(
326        "%02d.%02d.%02d", now.hour, now.minute, now.second));
327  } else {
328    int hour = now.hour;
329    if (hour > 12) {
330      hour -= 12;
331    } else if (hour == 0) {
332      hour = 12;
333    }
334    file_name.append(base::StringPrintf(
335        "%d.%02d.%02d ", hour, now.minute, now.second));
336    file_name.append((now.hour >= 12) ? "PM" : "AM");
337  }
338
339  return file_name;
340}
341
342bool GetScreenshotDirectory(base::FilePath* directory) {
343  bool is_logged_in = true;
344
345#if defined(OS_CHROMEOS)
346  is_logged_in = chromeos::LoginState::Get()->IsUserLoggedIn();
347#endif
348
349  if (is_logged_in) {
350    DownloadPrefs* download_prefs = DownloadPrefs::FromBrowserContext(
351        ProfileManager::GetActiveUserProfile());
352    *directory = download_prefs->DownloadPath();
353  } else  {
354    if (!base::GetTempDir(directory)) {
355      LOG(ERROR) << "Failed to find temporary directory.";
356      return false;
357    }
358  }
359  return true;
360}
361
362#if defined(OS_CHROMEOS)
363const int GetScreenshotNotificationTitle(
364    ScreenshotTakerObserver::Result screenshot_result) {
365  switch (screenshot_result) {
366    case ScreenshotTakerObserver::SCREENSHOTS_DISABLED:
367      return IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_DISABLED;
368    case ScreenshotTakerObserver::SCREENSHOT_SUCCESS:
369      return IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_SUCCESS;
370    default:
371      return IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_FAIL;
372  }
373}
374
375const int GetScreenshotNotificationText(
376    ScreenshotTakerObserver::Result screenshot_result) {
377  switch (screenshot_result) {
378    case ScreenshotTakerObserver::SCREENSHOTS_DISABLED:
379      return IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_DISABLED;
380    case ScreenshotTakerObserver::SCREENSHOT_SUCCESS:
381      return IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_SUCCESS;
382    default:
383      return IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_FAIL;
384  }
385}
386#endif
387
388}  // namespace
389
390ScreenshotTaker::ScreenshotTaker()
391    : profile_for_test_(NULL),
392      factory_(this) {
393}
394
395ScreenshotTaker::~ScreenshotTaker() {
396}
397
398void ScreenshotTaker::HandleTakeScreenshotForAllRootWindows() {
399  if (g_browser_process->local_state()->
400          GetBoolean(prefs::kDisableScreenshots)) {
401    ShowNotification(ScreenshotTakerObserver::SCREENSHOTS_DISABLED,
402                     base::FilePath());
403    return;
404  }
405  base::FilePath screenshot_directory;
406  if (!screenshot_directory_for_test_.empty()) {
407    screenshot_directory = screenshot_directory_for_test_;
408  } else if (!GetScreenshotDirectory(&screenshot_directory)) {
409    ShowNotification(ScreenshotTakerObserver::SCREENSHOT_GET_DIR_FAILED,
410                     base::FilePath());
411    return;
412  }
413  std::string screenshot_basename = !screenshot_basename_for_test_.empty() ?
414      screenshot_basename_for_test_ : GetScreenshotBaseFilename();
415
416  aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
417  // Reorder root_windows to take the primary root window's snapshot at first.
418  aura::Window* primary_root = ash::Shell::GetPrimaryRootWindow();
419  if (*(root_windows.begin()) != primary_root) {
420    root_windows.erase(std::find(
421        root_windows.begin(), root_windows.end(), primary_root));
422    root_windows.insert(root_windows.begin(), primary_root);
423  }
424  for (size_t i = 0; i < root_windows.size(); ++i) {
425    aura::Window* root_window = root_windows[i];
426    std::string basename = screenshot_basename;
427    gfx::Rect rect = root_window->bounds();
428    if (root_windows.size() > 1)
429      basename += base::StringPrintf(" - Display %d", static_cast<int>(i + 1));
430    base::FilePath screenshot_path =
431        screenshot_directory.AppendASCII(basename + ".png");
432    GrabFullWindowSnapshotAsync(
433        root_window, rect, GetProfile(), screenshot_path, i);
434  }
435  content::RecordAction(base::UserMetricsAction("Screenshot_TakeFull"));
436}
437
438void ScreenshotTaker::HandleTakePartialScreenshot(
439    aura::Window* window, const gfx::Rect& rect) {
440  if (g_browser_process->local_state()->
441          GetBoolean(prefs::kDisableScreenshots)) {
442    ShowNotification(ScreenshotTakerObserver::SCREENSHOTS_DISABLED,
443                     base::FilePath());
444    return;
445  }
446  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
447
448  base::FilePath screenshot_directory;
449  if (!screenshot_directory_for_test_.empty()) {
450    screenshot_directory = screenshot_directory_for_test_;
451  } else if (!GetScreenshotDirectory(&screenshot_directory)) {
452    ShowNotification(ScreenshotTakerObserver::SCREENSHOT_GET_DIR_FAILED,
453                     base::FilePath());
454    return;
455  }
456
457  std::string screenshot_basename = !screenshot_basename_for_test_.empty() ?
458      screenshot_basename_for_test_ : GetScreenshotBaseFilename();
459  base::FilePath screenshot_path =
460      screenshot_directory.AppendASCII(screenshot_basename + ".png");
461  GrabPartialWindowSnapshotAsync(window, rect, GetProfile(), screenshot_path);
462  content::RecordAction(base::UserMetricsAction("Screenshot_TakePartial"));
463}
464
465bool ScreenshotTaker::CanTakeScreenshot() {
466  return last_screenshot_timestamp_.is_null() ||
467      base::Time::Now() - last_screenshot_timestamp_ >
468      base::TimeDelta::FromMilliseconds(
469          kScreenshotMinimumIntervalInMS);
470}
471
472#if defined(OS_CHROMEOS)
473Notification* ScreenshotTaker::CreateNotification(
474    ScreenshotTakerObserver::Result screenshot_result,
475    const base::FilePath& screenshot_path) {
476  const std::string notification_id(kNotificationId);
477  // We cancel a previous screenshot notification, if any, to ensure we get
478  // a fresh notification pop-up.
479  g_browser_process->notification_ui_manager()->CancelById(notification_id);
480  const base::string16 replace_id(base::UTF8ToUTF16(notification_id));
481  bool success =
482      (screenshot_result == ScreenshotTakerObserver::SCREENSHOT_SUCCESS);
483  message_center::RichNotificationData optional_field;
484  if (success) {
485    const base::string16 label = l10n_util::GetStringUTF16(
486        IDS_MESSAGE_CENTER_NOTIFICATION_BUTTON_COPY_SCREENSHOT_TO_CLIPBOARD);
487    optional_field.buttons.push_back(message_center::ButtonInfo(label));
488  }
489  return new Notification(
490      message_center::NOTIFICATION_TYPE_SIMPLE,
491      GURL(kNotificationOriginUrl),
492      l10n_util::GetStringUTF16(
493          GetScreenshotNotificationTitle(screenshot_result)),
494      l10n_util::GetStringUTF16(
495          GetScreenshotNotificationText(screenshot_result)),
496      ui::ResourceBundle::GetSharedInstance().GetImageNamed(
497          IDR_SCREENSHOT_NOTIFICATION_ICON),
498      blink::WebTextDirectionDefault,
499      message_center::NotifierId(
500          message_center::NotifierId::SYSTEM_COMPONENT,
501          ash::system_notifier::kNotifierScreenshot),
502      l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_NOTIFIER_SCREENSHOT_NAME),
503      replace_id,
504      optional_field,
505      new ScreenshotTakerNotificationDelegate(
506          success, GetProfile(), screenshot_path));
507}
508#endif
509
510void ScreenshotTaker::ShowNotification(
511    ScreenshotTakerObserver::Result screenshot_result,
512    const base::FilePath& screenshot_path) {
513  DCHECK_CURRENTLY_ON(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())
519    return;
520
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,
530                                                      GetProfile());
531  }
532#endif
533  FOR_EACH_OBSERVER(ScreenshotTakerObserver, observers_,
534                    OnScreenshotCompleted(screenshot_result, screenshot_path));
535}
536
537void ScreenshotTaker::AddObserver(ScreenshotTakerObserver* observer) {
538  observers_.AddObserver(observer);
539}
540
541void ScreenshotTaker::RemoveObserver(ScreenshotTakerObserver* observer) {
542  observers_.RemoveObserver(observer);
543}
544
545bool ScreenshotTaker::HasObserver(ScreenshotTakerObserver* observer) const {
546  return observers_.HasObserver(observer);
547}
548
549void ScreenshotTaker::GrabWindowSnapshotAsyncCallback(
550    base::FilePath screenshot_path,
551    bool is_partial,
552    int window_idx,
553    scoped_refptr<base::RefCountedBytes> png_data) {
554  if (!png_data.get()) {
555    if (is_partial) {
556      LOG(ERROR) << "Failed to grab the window screenshot";
557      ShowNotification(
558          ScreenshotTakerObserver::SCREENSHOT_GRABWINDOW_PARTIAL_FAILED,
559          screenshot_path);
560    } else {
561      LOG(ERROR) << "Failed to grab the window screenshot for " << window_idx;
562      ShowNotification(
563          ScreenshotTakerObserver::SCREENSHOT_GRABWINDOW_FULL_FAILED,
564          screenshot_path);
565    }
566    return;
567  }
568
569  PostSaveScreenshotTask(
570      base::Bind(&ScreenshotTaker::ShowNotification, factory_.GetWeakPtr()),
571      GetProfile(),
572      screenshot_path,
573      png_data);
574}
575
576void ScreenshotTaker::GrabPartialWindowSnapshotAsync(
577    aura::Window* window,
578    const gfx::Rect& snapshot_bounds,
579    Profile* profile,
580    base::FilePath screenshot_path) {
581  last_screenshot_timestamp_ = base::Time::Now();
582
583  bool is_partial = true;
584  int window_idx = -1;  // unused
585  ui::GrabWindowSnapshotAsync(
586      window,
587      snapshot_bounds,
588      content::BrowserThread::GetBlockingPool(),
589      base::Bind(&ScreenshotTaker::GrabWindowSnapshotAsyncCallback,
590                 factory_.GetWeakPtr(),
591                 screenshot_path,
592                 is_partial,
593                 window_idx));
594}
595
596void ScreenshotTaker::GrabFullWindowSnapshotAsync(
597    aura::Window* window,
598    const gfx::Rect& snapshot_bounds,
599    Profile* profile,
600    base::FilePath screenshot_path,
601    int window_idx) {
602  last_screenshot_timestamp_ = base::Time::Now();
603
604  bool is_partial = false;
605  ui::GrabWindowSnapshotAsync(
606      window,
607      snapshot_bounds,
608      content::BrowserThread::GetBlockingPool(),
609      base::Bind(&ScreenshotTaker::GrabWindowSnapshotAsyncCallback,
610                 factory_.GetWeakPtr(),
611                 screenshot_path,
612                 is_partial,
613                 window_idx));
614}
615
616Profile* ScreenshotTaker::GetProfile() {
617  if (profile_for_test_)
618    return profile_for_test_;
619  return ProfileManager::GetActiveUserProfile();
620}
621
622void ScreenshotTaker::SetScreenshotDirectoryForTest(
623    const base::FilePath& directory) {
624  screenshot_directory_for_test_ = directory;
625}
626
627void ScreenshotTaker::SetScreenshotBasenameForTest(
628    const std::string& basename) {
629  screenshot_basename_for_test_ = basename;
630}
631
632void ScreenshotTaker::SetScreenshotProfileForTest(Profile* profile) {
633  profile_for_test_ = profile;
634}
635