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/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/browser/ui/window_snapshot/window_snapshot.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 "grit/ui_strings.h"
     35 #include "ui/aura/root_window.h"
     36 #include "ui/aura/window.h"
     37 #include "ui/base/clipboard/clipboard.h"
     38 #include "ui/base/clipboard/scoped_clipboard_writer.h"
     39 #include "ui/base/l10n/l10n_util.h"
     40 #include "ui/base/resource/resource_bundle.h"
     41 #include "ui/gfx/image/image.h"
     42 
     43 #if defined(USE_ASH)
     44 #include "ash/shell.h"
     45 #include "ash/shell_delegate.h"
     46 #endif
     47 
     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"
     56 #endif
     57 
     58 namespace {
     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;
     62 
     63 const char kNotificationId[] = "screenshot";
     64 
     65 const char kNotificationOriginUrl[] = "chrome://screenshot";
     66 
     67 const char kImageClipboardFormatPrefix[] = "<img src='data:image/png;base64,";
     68 const char kImageClipboardFormatSuffix[] = "'>";
     69 
     70 void CopyScreenshotToClipboard(scoped_refptr<base::RefCountedString> png_data) {
     71   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
     72 
     73   std::string encoded;
     74   base::Base64Encode(png_data->data(), &encoded);
     75 
     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?
     79   {
     80     ui::ScopedClipboardWriter scw(ui::Clipboard::GetForCurrentThread(),
     81                                   ui::CLIPBOARD_TYPE_COPY_PASTE);
     82     std::string html(kImageClipboardFormatPrefix);
     83     html += encoded;
     84     html += kImageClipboardFormatSuffix;
     85     scw.WriteHTML(UTF8ToUTF16(html), std::string());
     86   }
     87   content::RecordAction(content::UserMetricsAction("Screenshot_CopyClipboard"));
     88 }
     89 
     90 void ReadFileAndCopyToClipboardLocal(const base::FilePath& screenshot_path) {
     91   DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
     92 
     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();
     97     return;
     98   }
     99 
    100   content::BrowserThread::PostTask(
    101       content::BrowserThread::UI, FROM_HERE,
    102       base::Bind(CopyScreenshotToClipboard, png_data));
    103 }
    104 
    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);
    112     return;
    113   }
    114   content::BrowserThread::GetBlockingPool()->PostTask(
    115       FROM_HERE,
    116       base::Bind(&ReadFileAndCopyToClipboardLocal, file_path));
    117 }
    118 #endif
    119 
    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
    122 // notification.
    123 class ScreenshotTakerNotificationDelegate : public NotificationDelegate {
    124  public:
    125   ScreenshotTakerNotificationDelegate(bool success,
    126                                       Profile* profile,
    127                                       const base::FilePath& screenshot_path)
    128       : success_(success),
    129         profile_(profile),
    130         screenshot_path_(screenshot_path) {
    131   }
    132 
    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 {
    138     if (!success_)
    139       return;
    140 #if defined(OS_CHROMEOS)
    141     file_manager::util::ShowItemInFolder(profile_, screenshot_path_);
    142 #else
    143     // TODO(sschmitz): perhaps add similar action for Windows.
    144 #endif
    145   }
    146   virtual void ButtonClick(int button_index) OVERRIDE {
    147     DCHECK(success_ && button_index == 0);
    148 
    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));
    158     return;
    159   }
    160 #endif
    161     content::BrowserThread::GetBlockingPool()->PostTask(
    162         FROM_HERE, base::Bind(
    163             &ReadFileAndCopyToClipboardLocal, screenshot_path_));
    164   }
    165   virtual bool HasClickedListener() OVERRIDE { return success_; }
    166   virtual std::string id() const OVERRIDE {
    167     return std::string(kNotificationId);
    168   }
    169   virtual content::RenderViewHost* GetRenderViewHost() const OVERRIDE {
    170     return NULL;
    171   }
    172 
    173  private:
    174   virtual ~ScreenshotTakerNotificationDelegate() {}
    175 
    176   const bool success_;
    177   Profile* profile_;
    178   const base::FilePath screenshot_path_;
    179 
    180   DISALLOW_COPY_AND_ASSIGN(ScreenshotTakerNotificationDelegate);
    181 };
    182 
    183 typedef base::Callback<
    184   void(ScreenshotTakerObserver::Result screenshot_result,
    185        const base::FilePath& screenshot_path)> ShowNotificationCallback;
    186 
    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(
    196           local_path,
    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;
    201   }
    202   content::BrowserThread::PostTask(
    203       content::BrowserThread::UI, FROM_HERE,
    204       base::Bind(callback, result, screenshot_path));
    205 }
    206 
    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());
    212 
    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,
    218         base::Bind(callback,
    219                    ScreenshotTakerObserver::SCREENSHOT_CREATE_DIR_FAILED,
    220                    screenshot_path));
    221     return;
    222   }
    223   SaveScreenshotInternal(callback, screenshot_path, screenshot_path, png_data);
    224 }
    225 
    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,
    240         base::Bind(callback,
    241                    ScreenshotTakerObserver::SCREENSHOT_CREATE_FILE_FAILED,
    242                    screenshot_path));
    243     return;
    244   }
    245   SaveScreenshotInternal(callback, screenshot_path, local_path, png_data);
    246 }
    247 
    248 void EnsureDirectoryExistsCallback(
    249     const ShowNotificationCallback& callback,
    250     Profile* profile,
    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(
    259         profile,
    260         screenshot_path,
    261         base::Bind(&SaveScreenshotToDrive,
    262                    callback,
    263                    screenshot_path,
    264                    png_data));
    265   } else {
    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,
    269                  screenshot_path);
    270   }
    271 }
    272 
    273 void PostSaveScreenshotTask(const ShowNotificationCallback& callback,
    274                             Profile* profile,
    275                             const base::FilePath& screenshot_path,
    276                             scoped_refptr<base::RefCountedBytes> png_data) {
    277   if (drive::util::IsUnderDriveMountPoint(screenshot_path)) {
    278     drive::util::EnsureDirectoryExists(
    279         profile,
    280         screenshot_path.DirName(),
    281         base::Bind(&EnsureDirectoryExistsCallback,
    282                    callback,
    283                    profile,
    284                    screenshot_path,
    285                    png_data));
    286   } else {
    287     content::BrowserThread::GetBlockingPool()->PostTask(
    288         FROM_HERE, base::Bind(&SaveScreenshot,
    289                               callback,
    290                               screenshot_path,
    291                               png_data));
    292   }
    293 }
    294 #else
    295 void PostSaveScreenshotTask(const ShowNotificationCallback& callback,
    296                             Profile* profile,
    297                             const base::FilePath& screenshot_path,
    298                             scoped_refptr<base::RefCountedBytes> png_data) {
    299   content::BrowserThread::GetBlockingPool()->PostTask(
    300       FROM_HERE, base::Bind(&SaveScreenshot,
    301                             callback,
    302                             screenshot_path,
    303                             png_data));
    304 }
    305 #endif
    306 
    307 bool GrabWindowSnapshot(aura::Window* window,
    308                         const gfx::Rect& snapshot_bounds,
    309                         std::vector<unsigned char>* png_data) {
    310   return chrome::GrabWindowSnapshotForUser(window, png_data, snapshot_bounds);
    311 }
    312 
    313 bool ShouldUse24HourClock() {
    314 #if defined(OS_CHROMEOS)
    315   Profile* profile = ProfileManager::GetActiveUserProfileOrOffTheRecord();
    316   if (profile) {
    317     return profile->GetPrefs()->GetBoolean(prefs::kUse24HourClock);
    318   }
    319 #endif
    320   return base::GetHourClockType() == base::k24HourClock;
    321 }
    322 
    323 std::string GetScreenshotBaseFilename() {
    324   base::Time::Exploded now;
    325   base::Time::Now().LocalExplode(&now);
    326 
    327   // We don't use base/i18n/time_formatting.h here because it doesn't
    328   // support our format.  Don't use ICU either to avoid i18n file names
    329   // for non-English locales.
    330   // TODO(mukai): integrate this logic somewhere time_formatting.h
    331   std::string file_name = base::StringPrintf(
    332       "Screenshot %d-%02d-%02d at ", now.year, now.month, now.day_of_month);
    333 
    334   if (ShouldUse24HourClock()) {
    335     file_name.append(base::StringPrintf(
    336         "%02d.%02d.%02d", now.hour, now.minute, now.second));
    337   } else {
    338     int hour = now.hour;
    339     if (hour > 12) {
    340       hour -= 12;
    341     } else if (hour == 0) {
    342       hour = 12;
    343     }
    344     file_name.append(base::StringPrintf(
    345         "%d.%02d.%02d ", hour, now.minute, now.second));
    346     file_name.append((now.hour >= 12) ? "PM" : "AM");
    347   }
    348 
    349   return file_name;
    350 }
    351 
    352 bool GetScreenshotDirectory(base::FilePath* directory) {
    353   bool is_logged_in = true;
    354 
    355 #if defined(OS_CHROMEOS)
    356   is_logged_in = chromeos::LoginState::Get()->IsUserLoggedIn();
    357 #endif
    358 
    359   if (is_logged_in) {
    360     DownloadPrefs* download_prefs = DownloadPrefs::FromBrowserContext(
    361         ProfileManager::GetActiveUserProfileOrOffTheRecord());
    362     *directory = download_prefs->DownloadPath();
    363   } else  {
    364     if (!base::GetTempDir(directory)) {
    365       LOG(ERROR) << "Failed to find temporary directory.";
    366       return false;
    367     }
    368   }
    369   return true;
    370 }
    371 
    372 const int GetScreenshotNotificationTitle(
    373     ScreenshotTakerObserver::Result screenshot_result) {
    374   switch (screenshot_result) {
    375     case ScreenshotTakerObserver::SCREENSHOTS_DISABLED:
    376       return IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_DISABLED;
    377     case ScreenshotTakerObserver::SCREENSHOT_SUCCESS:
    378       return IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_SUCCESS;
    379     default:
    380       return IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_FAIL;
    381   }
    382 }
    383 
    384 const int GetScreenshotNotificationText(
    385     ScreenshotTakerObserver::Result screenshot_result) {
    386   switch (screenshot_result) {
    387     case ScreenshotTakerObserver::SCREENSHOTS_DISABLED:
    388       return IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_DISABLED;
    389     case ScreenshotTakerObserver::SCREENSHOT_SUCCESS:
    390       return IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_SUCCESS;
    391     default:
    392       return IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_FAIL;
    393   }
    394 }
    395 
    396 }  // namespace
    397 
    398 ScreenshotTaker::ScreenshotTaker()
    399     : factory_(this),
    400       profile_for_test_(NULL) {
    401 }
    402 
    403 ScreenshotTaker::~ScreenshotTaker() {
    404 }
    405 
    406 void ScreenshotTaker::HandleTakeScreenshotForAllRootWindows() {
    407   if (g_browser_process->local_state()->
    408           GetBoolean(prefs::kDisableScreenshots)) {
    409     ShowNotification(ScreenshotTakerObserver::SCREENSHOTS_DISABLED,
    410                      base::FilePath());
    411     return;
    412   }
    413   base::FilePath screenshot_directory;
    414   if (!screenshot_directory_for_test_.empty()) {
    415     screenshot_directory = screenshot_directory_for_test_;
    416   } else if (!GetScreenshotDirectory(&screenshot_directory)) {
    417     ShowNotification(ScreenshotTakerObserver::SCREENSHOT_GET_DIR_FAILED,
    418                      base::FilePath());
    419     return;
    420   }
    421   std::string screenshot_basename = !screenshot_basename_for_test_.empty() ?
    422       screenshot_basename_for_test_ : GetScreenshotBaseFilename();
    423 
    424   aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
    425   // Reorder root_windows to take the primary root window's snapshot at first.
    426   aura::Window* primary_root = ash::Shell::GetPrimaryRootWindow();
    427   if (*(root_windows.begin()) != primary_root) {
    428     root_windows.erase(std::find(
    429         root_windows.begin(), root_windows.end(), primary_root));
    430     root_windows.insert(root_windows.begin(), primary_root);
    431   }
    432   for (size_t i = 0; i < root_windows.size(); ++i) {
    433     aura::Window* root_window = root_windows[i];
    434     scoped_refptr<base::RefCountedBytes> png_data(new base::RefCountedBytes);
    435     std::string basename = screenshot_basename;
    436     gfx::Rect rect = root_window->bounds();
    437     if (root_windows.size() > 1)
    438       basename += base::StringPrintf(" - Display %d", static_cast<int>(i + 1));
    439     base::FilePath screenshot_path =
    440         screenshot_directory.AppendASCII(basename + ".png");
    441     if (GrabWindowSnapshot(root_window, rect, &png_data->data())) {
    442       PostSaveScreenshotTask(
    443           base::Bind(&ScreenshotTaker::ShowNotification, factory_.GetWeakPtr()),
    444           GetProfile(),
    445           screenshot_path,
    446           png_data);
    447     } else {
    448       LOG(ERROR) << "Failed to grab the window screenshot for " << i;
    449       ShowNotification(
    450           ScreenshotTakerObserver::SCREENSHOT_GRABWINDOW_FULL_FAILED,
    451           screenshot_path);
    452     }
    453   }
    454   content::RecordAction(content::UserMetricsAction("Screenshot_TakeFull"));
    455   last_screenshot_timestamp_ = base::Time::Now();
    456 }
    457 
    458 void ScreenshotTaker::HandleTakePartialScreenshot(
    459     aura::Window* window, const gfx::Rect& rect) {
    460   if (g_browser_process->local_state()->
    461           GetBoolean(prefs::kDisableScreenshots)) {
    462     ShowNotification(ScreenshotTakerObserver::SCREENSHOTS_DISABLED,
    463                      base::FilePath());
    464     return;
    465   }
    466   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    467 
    468   base::FilePath screenshot_directory;
    469   if (!screenshot_directory_for_test_.empty()) {
    470     screenshot_directory = screenshot_directory_for_test_;
    471   } else if (!GetScreenshotDirectory(&screenshot_directory)) {
    472     ShowNotification(ScreenshotTakerObserver::SCREENSHOT_GET_DIR_FAILED,
    473                      base::FilePath());
    474     return;
    475   }
    476 
    477   scoped_refptr<base::RefCountedBytes> png_data(new base::RefCountedBytes);
    478 
    479   std::string screenshot_basename = !screenshot_basename_for_test_.empty() ?
    480       screenshot_basename_for_test_ : GetScreenshotBaseFilename();
    481   base::FilePath screenshot_path =
    482       screenshot_directory.AppendASCII(screenshot_basename + ".png");
    483   if (GrabWindowSnapshot(window, rect, &png_data->data())) {
    484     last_screenshot_timestamp_ = base::Time::Now();
    485     PostSaveScreenshotTask(
    486         base::Bind(&ScreenshotTaker::ShowNotification, factory_.GetWeakPtr()),
    487         GetProfile(),
    488         screenshot_path,
    489         png_data);
    490   } else {
    491     LOG(ERROR) << "Failed to grab the window screenshot";
    492     ShowNotification(
    493         ScreenshotTakerObserver::SCREENSHOT_GRABWINDOW_PARTIAL_FAILED,
    494         screenshot_path);
    495   }
    496   content::RecordAction(content::UserMetricsAction("Screenshot_TakePartial"));
    497 }
    498 
    499 bool ScreenshotTaker::CanTakeScreenshot() {
    500   return last_screenshot_timestamp_.is_null() ||
    501       base::Time::Now() - last_screenshot_timestamp_ >
    502       base::TimeDelta::FromMilliseconds(
    503           kScreenshotMinimumIntervalInMS);
    504 }
    505 
    506 Notification* ScreenshotTaker::CreateNotification(
    507     ScreenshotTakerObserver::Result screenshot_result,
    508     const base::FilePath& screenshot_path) {
    509   const std::string notification_id(kNotificationId);
    510   // We cancel a previous screenshot notification, if any, to ensure we get
    511   // a fresh notification pop-up.
    512   g_browser_process->notification_ui_manager()->CancelById(notification_id);
    513   const base::string16 replace_id(UTF8ToUTF16(notification_id));
    514   bool success =
    515       (screenshot_result == ScreenshotTakerObserver::SCREENSHOT_SUCCESS);
    516   message_center::RichNotificationData optional_field;
    517   if (success) {
    518     const base::string16 label = l10n_util::GetStringUTF16(
    519         IDS_MESSAGE_CENTER_NOTIFICATION_BUTTON_COPY_SCREENSHOT_TO_CLIPBOARD);
    520     optional_field.buttons.push_back(message_center::ButtonInfo(label));
    521   }
    522   return new Notification(
    523       message_center::NOTIFICATION_TYPE_SIMPLE,
    524       GURL(kNotificationOriginUrl),
    525       l10n_util::GetStringUTF16(
    526           GetScreenshotNotificationTitle(screenshot_result)),
    527       l10n_util::GetStringUTF16(
    528           GetScreenshotNotificationText(screenshot_result)),
    529       ui::ResourceBundle::GetSharedInstance().GetImageNamed(
    530           IDR_SCREENSHOT_NOTIFICATION_ICON),
    531       blink::WebTextDirectionDefault,
    532       message_center::NotifierId(
    533           message_center::NotifierId::SYSTEM_COMPONENT,
    534           ash::system_notifier::kNotifierScreenshot),
    535       l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_NOTIFIER_SCREENSHOT_NAME),
    536       replace_id,
    537       optional_field,
    538       new ScreenshotTakerNotificationDelegate(
    539           success, GetProfile(), screenshot_path));
    540 }
    541 
    542 void ScreenshotTaker::ShowNotification(
    543     ScreenshotTakerObserver::Result screenshot_result,
    544     const base::FilePath& screenshot_path) {
    545   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    546 #if defined(OS_CHROMEOS)
    547   // Do not show a notification that a screenshot was taken while no user is
    548   // logged in, since it is confusing for the user to get a message about it
    549   // after he logs in (crbug.com/235217).
    550   if (!chromeos::LoginState::Get()->IsUserLoggedIn())
    551     return;
    552 
    553   // TODO(sschmitz): make this work for Windows.
    554   DesktopNotificationService* const service =
    555       DesktopNotificationServiceFactory::GetForProfile(GetProfile());
    556   if (service->IsNotifierEnabled(message_center::NotifierId(
    557           message_center::NotifierId::SYSTEM_COMPONENT,
    558           ash::system_notifier::kNotifierScreenshot))) {
    559     scoped_ptr<Notification> notification(
    560         CreateNotification(screenshot_result, screenshot_path));
    561     g_browser_process->notification_ui_manager()->Add(*notification,
    562                                                       GetProfile());
    563   }
    564 #endif
    565   FOR_EACH_OBSERVER(ScreenshotTakerObserver, observers_,
    566                     OnScreenshotCompleted(screenshot_result, screenshot_path));
    567 }
    568 
    569 void ScreenshotTaker::AddObserver(ScreenshotTakerObserver* observer) {
    570   observers_.AddObserver(observer);
    571 }
    572 
    573 void ScreenshotTaker::RemoveObserver(ScreenshotTakerObserver* observer) {
    574   observers_.RemoveObserver(observer);
    575 }
    576 
    577 bool ScreenshotTaker::HasObserver(ScreenshotTakerObserver* observer) const {
    578   return observers_.HasObserver(observer);
    579 }
    580 
    581 Profile* ScreenshotTaker::GetProfile() {
    582   if (profile_for_test_)
    583     return profile_for_test_;
    584   return ProfileManager::GetActiveUserProfileOrOffTheRecord();
    585 }
    586 
    587 void ScreenshotTaker::SetScreenshotDirectoryForTest(
    588     const base::FilePath& directory) {
    589   screenshot_directory_for_test_ = directory;
    590 }
    591 
    592 void ScreenshotTaker::SetScreenshotBasenameForTest(
    593     const std::string& basename) {
    594   screenshot_basename_for_test_ = basename;
    595 }
    596 
    597 void ScreenshotTaker::SetScreenshotProfileForTest(Profile* profile) {
    598   profile_for_test_ = profile;
    599 }
    600