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 "base/bind.h"
     12 #include "base/file_util.h"
     13 #include "base/logging.h"
     14 #include "base/memory/ref_counted_memory.h"
     15 #include "base/strings/stringprintf.h"
     16 #include "base/strings/utf_string_conversions.h"
     17 #include "base/threading/sequenced_worker_pool.h"
     18 #include "base/time/time.h"
     19 #include "chrome/browser/browser_process.h"
     20 #include "chrome/browser/download/download_prefs.h"
     21 #include "chrome/browser/notifications/notification_ui_manager.h"
     22 #include "chrome/browser/profiles/profile.h"
     23 #include "chrome/browser/profiles/profile_manager.h"
     24 #include "chrome/browser/ui/webui/screenshot_source.h"
     25 #include "chrome/browser/ui/window_snapshot/window_snapshot.h"
     26 #include "content/public/browser/browser_thread.h"
     27 #include "grit/ash_strings.h"
     28 #include "grit/theme_resources.h"
     29 #include "grit/ui_strings.h"
     30 #include "ui/aura/root_window.h"
     31 #include "ui/aura/window.h"
     32 #include "ui/base/l10n/l10n_util.h"
     33 #include "ui/base/resource/resource_bundle.h"
     34 #include "ui/gfx/image/image.h"
     35 
     36 #if defined(OS_CHROMEOS)
     37 #include "chrome/browser/chromeos/drive/file_system_util.h"
     38 #include "chrome/browser/chromeos/extensions/file_manager/file_manager_util.h"
     39 #include "chrome/browser/chromeos/login/user_manager.h"
     40 #include "chrome/browser/notifications/desktop_notification_service.h"
     41 #include "chrome/browser/notifications/desktop_notification_service_factory.h"
     42 #include "chromeos/login/login_state.h"
     43 #endif
     44 
     45 namespace {
     46 // The minimum interval between two screenshot commands.  It has to be
     47 // more than 1000 to prevent the conflict of filenames.
     48 const int kScreenshotMinimumIntervalInMS = 1000;
     49 
     50 const char kNotificationId[] = "screenshot";
     51 
     52 const char kNotificationOriginUrl[] = "chrome://screenshot";
     53 
     54 // Delegate for a notification. This class has two roles: to implement callback
     55 // methods for notification, and to provide an identity of the associated
     56 // notification.
     57 class ScreenshotTakerNotificationDelegate : public NotificationDelegate {
     58  public:
     59   ScreenshotTakerNotificationDelegate(bool success,
     60                                       const base::FilePath& screenshot_path)
     61       : success_(success),
     62         screenshot_path_(screenshot_path) {
     63   }
     64 
     65   // Overridden from NotificationDelegate:
     66   virtual void Display() OVERRIDE {}
     67   virtual void Error() OVERRIDE {}
     68   virtual void Close(bool by_user) OVERRIDE {}
     69   virtual void Click() OVERRIDE {
     70     if (!success_)
     71       return;
     72 #if defined(OS_CHROMEOS)
     73     file_manager::util::ShowFileInFolder(screenshot_path_);
     74 #else
     75     // TODO(sschmitz): perhaps add similar action for Windows.
     76 #endif
     77   }
     78   virtual bool HasClickedListener() OVERRIDE { return success_; }
     79   virtual std::string id() const OVERRIDE {
     80     return std::string(kNotificationId);
     81   }
     82   virtual content::RenderViewHost* GetRenderViewHost() const OVERRIDE {
     83     return NULL;
     84   }
     85 
     86  private:
     87   virtual ~ScreenshotTakerNotificationDelegate() {}
     88 
     89   const bool success_;
     90   const base::FilePath screenshot_path_;
     91 
     92   DISALLOW_COPY_AND_ASSIGN(ScreenshotTakerNotificationDelegate);
     93 };
     94 
     95 typedef base::Callback<
     96   void(ScreenshotTakerObserver::Result screenshot_result,
     97        const base::FilePath& screenshot_path)> ShowNotificationCallback;
     98 
     99 void SaveScreenshotInternal(const ShowNotificationCallback& callback,
    100                             const base::FilePath& screenshot_path,
    101                             const base::FilePath& local_path,
    102                             scoped_refptr<base::RefCountedBytes> png_data) {
    103   DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
    104   DCHECK(!local_path.empty());
    105   ScreenshotTakerObserver::Result result =
    106       ScreenshotTakerObserver::SCREENSHOT_SUCCESS;
    107   if (static_cast<size_t>(file_util::WriteFile(
    108           local_path,
    109           reinterpret_cast<char*>(&(png_data->data()[0])),
    110           png_data->size())) != png_data->size()) {
    111     LOG(ERROR) << "Failed to save to " << local_path.value();
    112     result = ScreenshotTakerObserver::SCREENSHOT_WRITE_FILE_FAILED;
    113   }
    114   content::BrowserThread::PostTask(
    115       content::BrowserThread::UI, FROM_HERE,
    116       base::Bind(callback, result, screenshot_path));
    117 }
    118 
    119 void SaveScreenshot(const ShowNotificationCallback& callback,
    120                     const base::FilePath& screenshot_path,
    121                     scoped_refptr<base::RefCountedBytes> png_data) {
    122   DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
    123   DCHECK(!screenshot_path.empty());
    124 
    125   if (!file_util::CreateDirectory(screenshot_path.DirName())) {
    126     LOG(ERROR) << "Failed to ensure the existence of "
    127                << screenshot_path.DirName().value();
    128     content::BrowserThread::PostTask(
    129         content::BrowserThread::UI, FROM_HERE,
    130         base::Bind(callback,
    131                    ScreenshotTakerObserver::SCREENSHOT_CREATE_DIR_FAILED,
    132                    screenshot_path));
    133     return;
    134   }
    135   SaveScreenshotInternal(callback, screenshot_path, screenshot_path, png_data);
    136 }
    137 
    138 // TODO(kinaba): crbug.com/140425, remove this ungly #ifdef dispatch.
    139 #if defined(OS_CHROMEOS)
    140 void SaveScreenshotToDrive(const ShowNotificationCallback& callback,
    141                            const base::FilePath& screenshot_path,
    142                            scoped_refptr<base::RefCountedBytes> png_data,
    143                            drive::FileError error,
    144                            const base::FilePath& local_path) {
    145   // |screenshot_path| is used in the notification callback.
    146   // |local_path| is a temporary file in a hidden cache directory used for
    147   // internal work generated by drive::util::PrepareWritableFileAndRun.
    148   if (error != drive::FILE_ERROR_OK) {
    149     LOG(ERROR) << "Failed to write screenshot image to Google Drive: " << error;
    150     content::BrowserThread::PostTask(
    151         content::BrowserThread::UI, FROM_HERE,
    152         base::Bind(callback,
    153                    ScreenshotTakerObserver::SCREENSHOT_CREATE_FILE_FAILED,
    154                    screenshot_path));
    155     return;
    156   }
    157   SaveScreenshotInternal(callback, screenshot_path, local_path, png_data);
    158 }
    159 
    160 void EnsureDirectoryExistsCallback(
    161     const ShowNotificationCallback& callback,
    162     Profile* profile,
    163     const base::FilePath& screenshot_path,
    164     scoped_refptr<base::RefCountedBytes> png_data,
    165     drive::FileError error) {
    166   // It is okay to fail with FILE_ERROR_EXISTS since anyway the directory
    167   // of the target file exists.
    168   if (error == drive::FILE_ERROR_OK ||
    169       error == drive::FILE_ERROR_EXISTS) {
    170     drive::util::PrepareWritableFileAndRun(
    171         profile,
    172         screenshot_path,
    173         base::Bind(&SaveScreenshotToDrive,
    174                    callback,
    175                    screenshot_path,
    176                    png_data));
    177   } else {
    178     LOG(ERROR) << "Failed to ensure the existence of the specified directory "
    179                << "in Google Drive: " << error;
    180     callback.Run(ScreenshotTakerObserver::SCREENSHOT_CHECK_DIR_FAILED,
    181                  screenshot_path);
    182   }
    183 }
    184 
    185 void PostSaveScreenshotTask(const ShowNotificationCallback& callback,
    186                             Profile* profile,
    187                             const base::FilePath& screenshot_path,
    188                             scoped_refptr<base::RefCountedBytes> png_data) {
    189   if (drive::util::IsUnderDriveMountPoint(screenshot_path)) {
    190     drive::util::EnsureDirectoryExists(
    191         profile,
    192         screenshot_path.DirName(),
    193         base::Bind(&EnsureDirectoryExistsCallback,
    194                    callback,
    195                    profile,
    196                    screenshot_path,
    197                    png_data));
    198   } else {
    199     content::BrowserThread::GetBlockingPool()->PostTask(
    200         FROM_HERE, base::Bind(&SaveScreenshot,
    201                               callback,
    202                               screenshot_path,
    203                               png_data));
    204   }
    205 }
    206 #else
    207 void PostSaveScreenshotTask(const ShowNotificationCallback& callback,
    208                             Profile* profile,
    209                             const base::FilePath& screenshot_path,
    210                             scoped_refptr<base::RefCountedBytes> png_data) {
    211   content::BrowserThread::GetBlockingPool()->PostTask(
    212       FROM_HERE, base::Bind(&SaveScreenshot,
    213                             callback,
    214                             screenshot_path,
    215                             png_data));
    216 }
    217 #endif
    218 
    219 bool GrabWindowSnapshot(aura::Window* window,
    220                         const gfx::Rect& snapshot_bounds,
    221                         std::vector<unsigned char>* png_data) {
    222   return chrome::GrabWindowSnapshotForUser(window, png_data, snapshot_bounds);
    223 }
    224 
    225 }  // namespace
    226 
    227 ScreenshotTaker::ScreenshotTaker()
    228     : factory_(this),
    229       profile_for_test_(NULL) {
    230 }
    231 
    232 ScreenshotTaker::~ScreenshotTaker() {
    233 }
    234 
    235 void ScreenshotTaker::HandleTakeScreenshotForAllRootWindows() {
    236   base::FilePath screenshot_directory;
    237   if (!screenshot_directory_for_test_.empty()) {
    238     screenshot_directory = screenshot_directory_for_test_;
    239   } else if (!ScreenshotSource::GetScreenshotDirectory(&screenshot_directory)) {
    240     ShowNotification(ScreenshotTakerObserver::SCREENSHOT_GET_DIR_FAILED,
    241                      base::FilePath());
    242     return;
    243   }
    244   std::string screenshot_basename = !screenshot_basename_for_test_.empty() ?
    245       screenshot_basename_for_test_ :
    246       ScreenshotSource::GetScreenshotBaseFilename();
    247 
    248   ash::Shell::RootWindowList root_windows = ash::Shell::GetAllRootWindows();
    249   // Reorder root_windows to take the primary root window's snapshot at first.
    250   aura::RootWindow* primary_root = ash::Shell::GetPrimaryRootWindow();
    251   if (*(root_windows.begin()) != primary_root) {
    252     root_windows.erase(std::find(
    253         root_windows.begin(), root_windows.end(), primary_root));
    254     root_windows.insert(root_windows.begin(), primary_root);
    255   }
    256   for (size_t i = 0; i < root_windows.size(); ++i) {
    257     aura::RootWindow* root_window = root_windows[i];
    258     scoped_refptr<base::RefCountedBytes> png_data(new base::RefCountedBytes);
    259     std::string basename = screenshot_basename;
    260     gfx::Rect rect = root_window->bounds();
    261     if (root_windows.size() > 1)
    262       basename += base::StringPrintf(" - Display %d", static_cast<int>(i + 1));
    263     base::FilePath screenshot_path =
    264         screenshot_directory.AppendASCII(basename + ".png");
    265     if (GrabWindowSnapshot(root_window, rect, &png_data->data())) {
    266       PostSaveScreenshotTask(
    267           base::Bind(&ScreenshotTaker::ShowNotification, factory_.GetWeakPtr()),
    268           GetProfile(),
    269           screenshot_path,
    270           png_data);
    271     } else {
    272       LOG(ERROR) << "Failed to grab the window screenshot for " << i;
    273       ShowNotification(
    274           ScreenshotTakerObserver::SCREENSHOT_GRABWINDOW_FULL_FAILED,
    275           screenshot_path);
    276     }
    277   }
    278   last_screenshot_timestamp_ = base::Time::Now();
    279 }
    280 
    281 void ScreenshotTaker::HandleTakePartialScreenshot(
    282     aura::Window* window, const gfx::Rect& rect) {
    283   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    284 
    285   base::FilePath screenshot_directory;
    286   if (!screenshot_directory_for_test_.empty()) {
    287     screenshot_directory = screenshot_directory_for_test_;
    288   } else if (!ScreenshotSource::GetScreenshotDirectory(&screenshot_directory)) {
    289     ShowNotification(ScreenshotTakerObserver::SCREENSHOT_GET_DIR_FAILED,
    290                      base::FilePath());
    291     return;
    292   }
    293 
    294   scoped_refptr<base::RefCountedBytes> png_data(new base::RefCountedBytes);
    295 
    296   std::string screenshot_basename = !screenshot_basename_for_test_.empty() ?
    297       screenshot_basename_for_test_ :
    298       ScreenshotSource::GetScreenshotBaseFilename();
    299   base::FilePath screenshot_path =
    300       screenshot_directory.AppendASCII(screenshot_basename + ".png");
    301   if (GrabWindowSnapshot(window, rect, &png_data->data())) {
    302     last_screenshot_timestamp_ = base::Time::Now();
    303     PostSaveScreenshotTask(
    304         base::Bind(&ScreenshotTaker::ShowNotification, factory_.GetWeakPtr()),
    305         GetProfile(),
    306         screenshot_path,
    307         png_data);
    308   } else {
    309     LOG(ERROR) << "Failed to grab the window screenshot";
    310     ShowNotification(
    311         ScreenshotTakerObserver::SCREENSHOT_GRABWINDOW_PARTIAL_FAILED,
    312         screenshot_path);
    313   }
    314 }
    315 
    316 bool ScreenshotTaker::CanTakeScreenshot() {
    317   return last_screenshot_timestamp_.is_null() ||
    318       base::Time::Now() - last_screenshot_timestamp_ >
    319       base::TimeDelta::FromMilliseconds(
    320           kScreenshotMinimumIntervalInMS);
    321 }
    322 
    323 Notification* ScreenshotTaker::CreateNotification(
    324     ScreenshotTakerObserver::Result screenshot_result,
    325     const base::FilePath& screenshot_path) {
    326   const std::string notification_id(kNotificationId);
    327   // We cancel a previous screenshot notification, if any, to ensure we get
    328   // a fresh notification pop-up.
    329   g_browser_process->notification_ui_manager()->CancelById(notification_id);
    330   const string16 replace_id(UTF8ToUTF16(notification_id));
    331   bool success =
    332       (screenshot_result == ScreenshotTakerObserver::SCREENSHOT_SUCCESS);
    333   return new Notification(
    334       GURL(kNotificationOriginUrl),
    335       ui::ResourceBundle::GetSharedInstance().GetImageNamed(
    336           IDR_SCREENSHOT_NOTIFICATION_ICON),
    337       l10n_util::GetStringUTF16(
    338           success ?
    339           IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_SUCCESS :
    340           IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_FAIL),
    341       l10n_util::GetStringUTF16(
    342           success ?
    343           IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_SUCCESS :
    344           IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_FAIL),
    345       WebKit::WebTextDirectionDefault,
    346       l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_NOTIFIER_SCREENSHOT_NAME),
    347       replace_id,
    348       new ScreenshotTakerNotificationDelegate(success, screenshot_path));
    349 }
    350 
    351 void ScreenshotTaker::ShowNotification(
    352     ScreenshotTakerObserver::Result screenshot_result,
    353     const base::FilePath& screenshot_path) {
    354   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    355 #if defined(OS_CHROMEOS)
    356   // Do not show a notification that a screenshot was taken while no user is
    357   // logged in, since it is confusing for the user to get a message about it
    358   // after he logs in (crbug.com/235217).
    359   if (!chromeos::LoginState::Get()->IsUserLoggedIn())
    360     return;
    361 
    362   // TODO(sschmitz): make this work for Windows.
    363   DesktopNotificationService* const service =
    364       DesktopNotificationServiceFactory::GetForProfile(GetProfile());
    365   if (service->IsNotifierEnabled(
    366           message_center::NotifierId(message_center::NotifierId::SCREENSHOT))) {
    367     scoped_ptr<Notification> notification(
    368         CreateNotification(screenshot_result, screenshot_path));
    369     g_browser_process->notification_ui_manager()->Add(*notification,
    370                                                       GetProfile());
    371   }
    372 #endif
    373   FOR_EACH_OBSERVER(ScreenshotTakerObserver, observers_,
    374                     OnScreenshotCompleted(screenshot_result, screenshot_path));
    375 }
    376 
    377 void ScreenshotTaker::AddObserver(ScreenshotTakerObserver* observer) {
    378   observers_.AddObserver(observer);
    379 }
    380 
    381 void ScreenshotTaker::RemoveObserver(ScreenshotTakerObserver* observer) {
    382   observers_.RemoveObserver(observer);
    383 }
    384 
    385 bool ScreenshotTaker::HasObserver(ScreenshotTakerObserver* observer) const {
    386   return observers_.HasObserver(observer);
    387 }
    388 
    389 Profile* ScreenshotTaker::GetProfile() {
    390   if (profile_for_test_)
    391     return profile_for_test_;
    392   return ProfileManager::GetDefaultProfileOrOffTheRecord();
    393 }
    394 
    395 void ScreenshotTaker::SetScreenshotDirectoryForTest(
    396     const base::FilePath& directory) {
    397   screenshot_directory_for_test_ = directory;
    398 }
    399 
    400 void ScreenshotTaker::SetScreenshotBasenameForTest(
    401     const std::string& basename) {
    402   screenshot_basename_for_test_ = basename;
    403 }
    404 
    405 void ScreenshotTaker::SetScreenshotProfileForTest(Profile* profile) {
    406   profile_for_test_ = profile;
    407 }
    408