Home | History | Annotate | Download | only in ash
      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 
     53 namespace {
     54 // The minimum interval between two screenshot commands.  It has to be
     55 // more than 1000 to prevent the conflict of filenames.
     56 const int kScreenshotMinimumIntervalInMS = 1000;
     57 
     58 const char kNotificationId[] = "screenshot";
     59 
     60 #if defined(OS_CHROMEOS)
     61 const char kNotificationOriginUrl[] = "chrome://screenshot";
     62 #endif
     63 
     64 const char kImageClipboardFormatPrefix[] = "<img src='data:image/png;base64,";
     65 const char kImageClipboardFormatSuffix[] = "'>";
     66 
     67 void 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 
     86 void 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)
    102 void 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.
    119 class 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 
    179 typedef base::Callback<
    180   void(ScreenshotTakerObserver::Result screenshot_result,
    181        const base::FilePath& screenshot_path)> ShowNotificationCallback;
    182 
    183 void 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 
    203 void 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)
    224 void 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 
    244 void 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 
    269 void 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
    291 void 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 
    303 bool 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 
    313 std::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 
    342 bool 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)
    363 const 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 
    375 const 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 
    390 ScreenshotTaker::ScreenshotTaker()
    391     : profile_for_test_(NULL),
    392       factory_(this) {
    393 }
    394 
    395 ScreenshotTaker::~ScreenshotTaker() {
    396 }
    397 
    398 void 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 
    438 void 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 
    465 bool 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)
    473 Notification* 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 
    510 void 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 
    537 void ScreenshotTaker::AddObserver(ScreenshotTakerObserver* observer) {
    538   observers_.AddObserver(observer);
    539 }
    540 
    541 void ScreenshotTaker::RemoveObserver(ScreenshotTakerObserver* observer) {
    542   observers_.RemoveObserver(observer);
    543 }
    544 
    545 bool ScreenshotTaker::HasObserver(ScreenshotTakerObserver* observer) const {
    546   return observers_.HasObserver(observer);
    547 }
    548 
    549 void 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 
    576 void 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 
    596 void 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 
    616 Profile* ScreenshotTaker::GetProfile() {
    617   if (profile_for_test_)
    618     return profile_for_test_;
    619   return ProfileManager::GetActiveUserProfile();
    620 }
    621 
    622 void ScreenshotTaker::SetScreenshotDirectoryForTest(
    623     const base::FilePath& directory) {
    624   screenshot_directory_for_test_ = directory;
    625 }
    626 
    627 void ScreenshotTaker::SetScreenshotBasenameForTest(
    628     const std::string& basename) {
    629   screenshot_basename_for_test_ = basename;
    630 }
    631 
    632 void ScreenshotTaker::SetScreenshotProfileForTest(Profile* profile) {
    633   profile_for_test_ = profile;
    634 }
    635