OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 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/chrome_screenshot_grabber.h" |
| 6 |
| 7 #include "ash/shell.h" |
| 8 #include "ash/system/system_notifier.h" |
| 9 #include "base/base64.h" |
| 10 #include "base/bind.h" |
| 11 #include "base/callback.h" |
| 12 #include "base/files/file_util.h" |
| 13 #include "base/i18n/time_formatting.h" |
| 14 #include "base/prefs/pref_service.h" |
| 15 #include "base/strings/stringprintf.h" |
| 16 #include "base/strings/utf_string_conversions.h" |
| 17 #include "chrome/browser/browser_process.h" |
| 18 #include "chrome/browser/download/download_prefs.h" |
| 19 #include "chrome/browser/notifications/notification_ui_manager.h" |
| 20 #include "chrome/browser/profiles/profile.h" |
| 21 #include "chrome/browser/profiles/profile_manager.h" |
| 22 #include "chrome/common/pref_names.h" |
| 23 #include "content/public/browser/browser_thread.h" |
| 24 #include "content/public/browser/user_metrics.h" |
| 25 #include "grit/ash_strings.h" |
| 26 #include "grit/theme_resources.h" |
| 27 #include "ui/base/clipboard/clipboard.h" |
| 28 #include "ui/base/clipboard/scoped_clipboard_writer.h" |
| 29 #include "ui/base/l10n/l10n_util.h" |
| 30 #include "ui/base/resource/resource_bundle.h" |
| 31 #include "ui/strings/grit/ui_strings.h" |
| 32 |
| 33 #if defined(OS_CHROMEOS) |
| 34 #include "chrome/browser/chromeos/drive/file_system_interface.h" |
| 35 #include "chrome/browser/chromeos/drive/file_system_util.h" |
| 36 #include "chrome/browser/chromeos/file_manager/open_util.h" |
| 37 #include "chrome/browser/notifications/desktop_notification_service.h" |
| 38 #include "chrome/browser/notifications/desktop_notification_service_factory.h" |
| 39 #include "chromeos/login/login_state.h" |
| 40 #endif |
| 41 |
| 42 namespace { |
| 43 |
| 44 const char kNotificationId[] = "screenshot"; |
| 45 |
| 46 #if defined(OS_CHROMEOS) |
| 47 const char kNotificationOriginUrl[] = "chrome://screenshot"; |
| 48 #endif |
| 49 |
| 50 const char kImageClipboardFormatPrefix[] = "<img src='data:image/png;base64,"; |
| 51 const char kImageClipboardFormatSuffix[] = "'>"; |
| 52 |
| 53 void CopyScreenshotToClipboard(scoped_refptr<base::RefCountedString> png_data) { |
| 54 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| 55 |
| 56 std::string encoded; |
| 57 base::Base64Encode(png_data->data(), &encoded); |
| 58 |
| 59 // Only cares about HTML because ChromeOS doesn't need other formats. |
| 60 // TODO(dcheng): Why don't we take advantage of the ability to write bitmaps |
| 61 // to the clipboard here? |
| 62 { |
| 63 ui::ScopedClipboardWriter scw(ui::CLIPBOARD_TYPE_COPY_PASTE); |
| 64 std::string html(kImageClipboardFormatPrefix); |
| 65 html += encoded; |
| 66 html += kImageClipboardFormatSuffix; |
| 67 scw.WriteHTML(base::UTF8ToUTF16(html), std::string()); |
| 68 } |
| 69 content::RecordAction(base::UserMetricsAction("Screenshot_CopyClipboard")); |
| 70 } |
| 71 |
| 72 void ReadFileAndCopyToClipboardLocal(const base::FilePath& screenshot_path) { |
| 73 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); |
| 74 |
| 75 scoped_refptr<base::RefCountedString> png_data(new base::RefCountedString()); |
| 76 if (!base::ReadFileToString(screenshot_path, &(png_data->data()))) { |
| 77 LOG(ERROR) << "Failed to read the screenshot file: " |
| 78 << screenshot_path.value(); |
| 79 return; |
| 80 } |
| 81 |
| 82 content::BrowserThread::PostTask( |
| 83 content::BrowserThread::UI, FROM_HERE, |
| 84 base::Bind(CopyScreenshotToClipboard, png_data)); |
| 85 } |
| 86 |
| 87 #if defined(OS_CHROMEOS) |
| 88 void ReadFileAndCopyToClipboardDrive(drive::FileError error, |
| 89 const base::FilePath& file_path, |
| 90 scoped_ptr<drive::ResourceEntry> entry) { |
| 91 if (error != drive::FILE_ERROR_OK) { |
| 92 LOG(ERROR) << "Failed to read the screenshot path on drive: " |
| 93 << drive::FileErrorToString(error); |
| 94 return; |
| 95 } |
| 96 content::BrowserThread::GetBlockingPool()->PostTask( |
| 97 FROM_HERE, base::Bind(&ReadFileAndCopyToClipboardLocal, file_path)); |
| 98 } |
| 99 #endif |
| 100 |
| 101 // Delegate for a notification. This class has two roles: to implement callback |
| 102 // methods for notification, and to provide an identity of the associated |
| 103 // notification. |
| 104 class ScreenshotGrabberNotificationDelegate : public NotificationDelegate { |
| 105 public: |
| 106 ScreenshotGrabberNotificationDelegate(bool success, |
| 107 Profile* profile, |
| 108 const base::FilePath& screenshot_path) |
| 109 : success_(success), |
| 110 profile_(profile), |
| 111 screenshot_path_(screenshot_path) {} |
| 112 |
| 113 // Overridden from NotificationDelegate: |
| 114 void Click() override { |
| 115 if (!success_) |
| 116 return; |
| 117 #if defined(OS_CHROMEOS) |
| 118 file_manager::util::ShowItemInFolder(profile_, screenshot_path_); |
| 119 #else |
| 120 // TODO(sschmitz): perhaps add similar action for Windows. |
| 121 #endif |
| 122 } |
| 123 void ButtonClick(int button_index) override { |
| 124 DCHECK(success_ && button_index == 0); |
| 125 |
| 126 // To avoid keeping the screenshot image on memory, it will re-read the |
| 127 // screenshot file and copy it to the clipboard. |
| 128 #if defined(OS_CHROMEOS) |
| 129 if (drive::util::IsUnderDriveMountPoint(screenshot_path_)) { |
| 130 drive::FileSystemInterface* file_system = |
| 131 drive::util::GetFileSystemByProfile(profile_); |
| 132 file_system->GetFile(drive::util::ExtractDrivePath(screenshot_path_), |
| 133 base::Bind(&ReadFileAndCopyToClipboardDrive)); |
| 134 return; |
| 135 } |
| 136 #endif |
| 137 content::BrowserThread::GetBlockingPool()->PostTask( |
| 138 FROM_HERE, |
| 139 base::Bind(&ReadFileAndCopyToClipboardLocal, screenshot_path_)); |
| 140 } |
| 141 bool HasClickedListener() override { return success_; } |
| 142 std::string id() const override { return std::string(kNotificationId); } |
| 143 |
| 144 private: |
| 145 ~ScreenshotGrabberNotificationDelegate() override {} |
| 146 |
| 147 const bool success_; |
| 148 Profile* profile_; |
| 149 const base::FilePath screenshot_path_; |
| 150 |
| 151 DISALLOW_COPY_AND_ASSIGN(ScreenshotGrabberNotificationDelegate); |
| 152 }; |
| 153 |
| 154 #if defined(OS_CHROMEOS) |
| 155 int GetScreenshotNotificationTitle( |
| 156 ui::ScreenshotGrabberObserver::Result screenshot_result) { |
| 157 switch (screenshot_result) { |
| 158 case ui::ScreenshotGrabberObserver::SCREENSHOTS_DISABLED: |
| 159 return IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_DISABLED; |
| 160 case ui::ScreenshotGrabberObserver::SCREENSHOT_SUCCESS: |
| 161 return IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_SUCCESS; |
| 162 default: |
| 163 return IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_FAIL; |
| 164 } |
| 165 } |
| 166 |
| 167 int GetScreenshotNotificationText( |
| 168 ui::ScreenshotGrabberObserver::Result screenshot_result) { |
| 169 switch (screenshot_result) { |
| 170 case ui::ScreenshotGrabberObserver::SCREENSHOTS_DISABLED: |
| 171 return IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_DISABLED; |
| 172 case ui::ScreenshotGrabberObserver::SCREENSHOT_SUCCESS: |
| 173 return IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_SUCCESS; |
| 174 default: |
| 175 return IDS_ASH_SCREENSHOT_NOTIFICATION_TEXT_FAIL; |
| 176 } |
| 177 } |
| 178 |
| 179 void PrepareWritableFileCallback( |
| 180 const ChromeScreenshotGrabber::FileCallback& callback, |
| 181 drive::FileError error, |
| 182 const base::FilePath& local_path) { |
| 183 callback.Run(error == drive::FILE_ERROR_OK |
| 184 ? ui::ScreenshotGrabberDelegate::FILE_SUCCESS |
| 185 : ui::ScreenshotGrabberDelegate::FILE_CREATE_FAILED, |
| 186 local_path); |
| 187 } |
| 188 |
| 189 void EnsureDirectoryExistsCallback( |
| 190 const ChromeScreenshotGrabber::FileCallback& callback, |
| 191 Profile* profile, |
| 192 const base::FilePath& path, |
| 193 drive::FileError error) { |
| 194 // It is okay to fail with FILE_ERROR_EXISTS since anyway the directory |
| 195 // of the target file exists. |
| 196 if (error == drive::FILE_ERROR_OK || error == drive::FILE_ERROR_EXISTS) { |
| 197 drive::util::PrepareWritableFileAndRun( |
| 198 profile, path, base::Bind(&PrepareWritableFileCallback, callback)); |
| 199 } else { |
| 200 LOG(ERROR) << "Failed to ensure the existence of the specified directory " |
| 201 << "in Google Drive: " << error; |
| 202 content::BrowserThread::GetBlockingPool()->PostTask( |
| 203 FROM_HERE, |
| 204 base::Bind(callback, |
| 205 ui::ScreenshotGrabberDelegate::FILE_CHECK_DIR_FAILED, |
| 206 base::FilePath())); |
| 207 } |
| 208 } |
| 209 #endif |
| 210 |
| 211 bool ScreenshotsDisabled() { |
| 212 return g_browser_process->local_state()->GetBoolean( |
| 213 prefs::kDisableScreenshots); |
| 214 } |
| 215 |
| 216 bool ShouldUse24HourClock() { |
| 217 #if defined(OS_CHROMEOS) |
| 218 Profile* profile = ProfileManager::GetActiveUserProfile(); |
| 219 if (profile) { |
| 220 return profile->GetPrefs()->GetBoolean(prefs::kUse24HourClock); |
| 221 } |
| 222 #endif |
| 223 return base::GetHourClockType() == base::k24HourClock; |
| 224 } |
| 225 |
| 226 bool GetScreenshotDirectory(base::FilePath* directory) { |
| 227 bool is_logged_in = true; |
| 228 |
| 229 #if defined(OS_CHROMEOS) |
| 230 is_logged_in = chromeos::LoginState::Get()->IsUserLoggedIn(); |
| 231 #endif |
| 232 |
| 233 if (is_logged_in) { |
| 234 DownloadPrefs* download_prefs = DownloadPrefs::FromBrowserContext( |
| 235 ProfileManager::GetActiveUserProfile()); |
| 236 *directory = download_prefs->DownloadPath(); |
| 237 } else { |
| 238 if (!base::GetTempDir(directory)) { |
| 239 LOG(ERROR) << "Failed to find temporary directory."; |
| 240 return false; |
| 241 } |
| 242 } |
| 243 return true; |
| 244 } |
| 245 |
| 246 std::string GetScreenshotBaseFilename() { |
| 247 base::Time::Exploded now; |
| 248 base::Time::Now().LocalExplode(&now); |
| 249 |
| 250 // We don't use base/i18n/time_formatting.h here because it doesn't |
| 251 // support our format. Don't use ICU either to avoid i18n file names |
| 252 // for non-English locales. |
| 253 // TODO(mukai): integrate this logic somewhere time_formatting.h |
| 254 std::string file_name = base::StringPrintf( |
| 255 "Screenshot %d-%02d-%02d at ", now.year, now.month, now.day_of_month); |
| 256 |
| 257 if (ShouldUse24HourClock()) { |
| 258 file_name.append( |
| 259 base::StringPrintf("%02d.%02d.%02d", now.hour, now.minute, now.second)); |
| 260 } else { |
| 261 int hour = now.hour; |
| 262 if (hour > 12) { |
| 263 hour -= 12; |
| 264 } else if (hour == 0) { |
| 265 hour = 12; |
| 266 } |
| 267 file_name.append( |
| 268 base::StringPrintf("%d.%02d.%02d ", hour, now.minute, now.second)); |
| 269 file_name.append((now.hour >= 12) ? "PM" : "AM"); |
| 270 } |
| 271 |
| 272 return file_name; |
| 273 } |
| 274 |
| 275 } // namespace |
| 276 |
| 277 ChromeScreenshotGrabber::ChromeScreenshotGrabber() |
| 278 : screenshot_grabber_(new ui::ScreenshotGrabber( |
| 279 this, |
| 280 content::BrowserThread::GetBlockingPool() |
| 281 ->GetTaskRunnerWithShutdownBehavior( |
| 282 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN))), |
| 283 profile_for_test_(NULL) { |
| 284 screenshot_grabber_->AddObserver(this); |
| 285 } |
| 286 |
| 287 ChromeScreenshotGrabber::~ChromeScreenshotGrabber() { |
| 288 screenshot_grabber_->RemoveObserver(this); |
| 289 } |
| 290 |
| 291 void ChromeScreenshotGrabber::HandleTakeScreenshotForAllRootWindows() { |
| 292 if (ScreenshotsDisabled()) { |
| 293 screenshot_grabber_->NotifyScreenshotCompleted( |
| 294 ui::ScreenshotGrabberObserver::SCREENSHOTS_DISABLED, base::FilePath()); |
| 295 return; |
| 296 } |
| 297 |
| 298 base::FilePath screenshot_directory; |
| 299 if (!GetScreenshotDirectory(&screenshot_directory)) { |
| 300 screenshot_grabber_->NotifyScreenshotCompleted( |
| 301 ui::ScreenshotGrabberObserver::SCREENSHOT_GET_DIR_FAILED, |
| 302 base::FilePath()); |
| 303 return; |
| 304 } |
| 305 |
| 306 std::string screenshot_basename = GetScreenshotBaseFilename(); |
| 307 aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows(); |
| 308 // Reorder root_windows to take the primary root window's snapshot at first. |
| 309 aura::Window* primary_root = ash::Shell::GetPrimaryRootWindow(); |
| 310 if (*(root_windows.begin()) != primary_root) { |
| 311 root_windows.erase( |
| 312 std::find(root_windows.begin(), root_windows.end(), primary_root)); |
| 313 root_windows.insert(root_windows.begin(), primary_root); |
| 314 } |
| 315 std::vector<base::FilePath> filenames; |
| 316 for (size_t i = 0; i < root_windows.size(); ++i) { |
| 317 aura::Window* root_window = root_windows[i]; |
| 318 std::string basename = screenshot_basename; |
| 319 gfx::Rect rect = root_window->bounds(); |
| 320 if (root_windows.size() > 1) |
| 321 basename += base::StringPrintf(" - Display %d", static_cast<int>(i + 1)); |
| 322 base::FilePath screenshot_path = |
| 323 screenshot_directory.AppendASCII(basename + ".png"); |
| 324 screenshot_grabber_->TakeScreenshot(root_window, rect, screenshot_path); |
| 325 } |
| 326 content::RecordAction(base::UserMetricsAction("Screenshot_TakeFull")); |
| 327 } |
| 328 |
| 329 void ChromeScreenshotGrabber::HandleTakePartialScreenshot( |
| 330 aura::Window* window, |
| 331 const gfx::Rect& rect) { |
| 332 if (ScreenshotsDisabled()) { |
| 333 screenshot_grabber_->NotifyScreenshotCompleted( |
| 334 ui::ScreenshotGrabberObserver::SCREENSHOTS_DISABLED, base::FilePath()); |
| 335 return; |
| 336 } |
| 337 |
| 338 base::FilePath screenshot_directory; |
| 339 if (!GetScreenshotDirectory(&screenshot_directory)) { |
| 340 screenshot_grabber_->NotifyScreenshotCompleted( |
| 341 ui::ScreenshotGrabberObserver::SCREENSHOT_GET_DIR_FAILED, |
| 342 base::FilePath()); |
| 343 return; |
| 344 } |
| 345 |
| 346 base::FilePath screenshot_path = |
| 347 screenshot_directory.AppendASCII(GetScreenshotBaseFilename() + ".png"); |
| 348 screenshot_grabber_->TakeScreenshot(window, rect, screenshot_path); |
| 349 content::RecordAction(base::UserMetricsAction("Screenshot_TakePartial")); |
| 350 } |
| 351 |
| 352 bool ChromeScreenshotGrabber::CanTakeScreenshot() { |
| 353 return screenshot_grabber_->CanTakeScreenshot(); |
| 354 } |
| 355 |
| 356 void ChromeScreenshotGrabber::PrepareFileAndRunOnBlockingPool( |
| 357 const base::FilePath& path, |
| 358 scoped_refptr<base::TaskRunner> blocking_task_runner, |
| 359 const FileCallback& callback) { |
| 360 #if defined(OS_CHROMEOS) |
| 361 Profile* profile = ProfileManager::GetActiveUserProfile(); |
| 362 if (drive::util::IsUnderDriveMountPoint(path)) { |
| 363 drive::util::EnsureDirectoryExists( |
| 364 profile, path.DirName(), |
| 365 base::Bind(&EnsureDirectoryExistsCallback, callback, profile, path)); |
| 366 return; |
| 367 } |
| 368 #endif |
| 369 ui::ScreenshotGrabberDelegate::PrepareFileAndRunOnBlockingPool( |
| 370 path, blocking_task_runner, callback); |
| 371 } |
| 372 |
| 373 void ChromeScreenshotGrabber::OnScreenshotCompleted( |
| 374 ui::ScreenshotGrabberObserver::Result result, |
| 375 const base::FilePath& screenshot_path) { |
| 376 #if defined(OS_CHROMEOS) |
| 377 // Do not show a notification that a screenshot was taken while no user is |
| 378 // logged in, since it is confusing for the user to get a message about it |
| 379 // after he logs in (crbug.com/235217). |
| 380 if (!chromeos::LoginState::Get()->IsUserLoggedIn()) |
| 381 return; |
| 382 |
| 383 // TODO(sschmitz): make this work for Windows. |
| 384 DesktopNotificationService* const service = |
| 385 DesktopNotificationServiceFactory::GetForProfile(GetProfile()); |
| 386 if (service->IsNotifierEnabled(message_center::NotifierId( |
| 387 message_center::NotifierId::SYSTEM_COMPONENT, |
| 388 ash::system_notifier::kNotifierScreenshot))) { |
| 389 scoped_ptr<Notification> notification( |
| 390 CreateNotification(result, screenshot_path)); |
| 391 g_browser_process->notification_ui_manager()->Add(*notification, |
| 392 GetProfile()); |
| 393 } |
| 394 #endif |
| 395 } |
| 396 |
| 397 #if defined(OS_CHROMEOS) |
| 398 Notification* ChromeScreenshotGrabber::CreateNotification( |
| 399 ui::ScreenshotGrabberObserver::Result screenshot_result, |
| 400 const base::FilePath& screenshot_path) { |
| 401 const std::string notification_id(kNotificationId); |
| 402 // We cancel a previous screenshot notification, if any, to ensure we get |
| 403 // a fresh notification pop-up. |
| 404 g_browser_process->notification_ui_manager()->CancelById( |
| 405 notification_id, NotificationUIManager::GetProfileID(GetProfile())); |
| 406 const base::string16 replace_id(base::UTF8ToUTF16(notification_id)); |
| 407 bool success = |
| 408 (screenshot_result == ui::ScreenshotGrabberObserver::SCREENSHOT_SUCCESS); |
| 409 message_center::RichNotificationData optional_field; |
| 410 if (success) { |
| 411 const base::string16 label = l10n_util::GetStringUTF16( |
| 412 IDS_MESSAGE_CENTER_NOTIFICATION_BUTTON_COPY_SCREENSHOT_TO_CLIPBOARD); |
| 413 optional_field.buttons.push_back(message_center::ButtonInfo(label)); |
| 414 } |
| 415 return new Notification( |
| 416 message_center::NOTIFICATION_TYPE_SIMPLE, GURL(kNotificationOriginUrl), |
| 417 l10n_util::GetStringUTF16( |
| 418 GetScreenshotNotificationTitle(screenshot_result)), |
| 419 l10n_util::GetStringUTF16( |
| 420 GetScreenshotNotificationText(screenshot_result)), |
| 421 ui::ResourceBundle::GetSharedInstance().GetImageNamed( |
| 422 IDR_SCREENSHOT_NOTIFICATION_ICON), |
| 423 blink::WebTextDirectionDefault, |
| 424 message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT, |
| 425 ash::system_notifier::kNotifierScreenshot), |
| 426 l10n_util::GetStringUTF16(IDS_MESSAGE_CENTER_NOTIFIER_SCREENSHOT_NAME), |
| 427 replace_id, optional_field, new ScreenshotGrabberNotificationDelegate( |
| 428 success, GetProfile(), screenshot_path)); |
| 429 } |
| 430 #endif |
| 431 |
| 432 void ChromeScreenshotGrabber::SetProfileForTest(Profile* profile) { |
| 433 profile_for_test_ = profile; |
| 434 } |
| 435 |
| 436 Profile* ChromeScreenshotGrabber::GetProfile() { |
| 437 return profile_for_test_ ? profile_for_test_ |
| 438 : ProfileManager::GetActiveUserProfile(); |
| 439 } |
OLD | NEW |