// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/download/notification/download_item_notification.h" #include #include #include "base/files/file_util.h" #include "base/prefs/pref_service.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/download/download_crx_util.h" #include "chrome/browser/download/download_item_model.h" #include "chrome/browser/download/notification/download_notification_manager.h" #include "chrome/browser/notifications/notification.h" #include "chrome/browser/notifications/notification_ui_manager.h" #include "chrome/browser/notifications/profile_notification.h" #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "chrome/grit/chromium_strings.h" #include "chrome/grit/generated_resources.h" #include "components/mime_util/mime_util.h" #include "components/url_formatter/elide_url.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/download_interrupt_reasons.h" #include "content/public/browser/download_item.h" #include "content/public/browser/page_navigator.h" #include "content/public/browser/user_metrics.h" #include "content/public/browser/web_contents.h" #include "grit/theme_resources.h" #include "net/base/mime_util.h" #include "third_party/skia/include/core/SkCanvas.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/time_format.h" #include "ui/base/resource/resource_bundle.h" #include "ui/base/text/bytes_formatting.h" #include "ui/gfx/codec/jpeg_codec.h" #include "ui/gfx/color_palette.h" #include "ui/gfx/image/image.h" #include "ui/gfx/paint_vector_icon.h" #include "ui/gfx/vector_icons_public.h" #include "ui/message_center/message_center.h" #include "ui/message_center/message_center_style.h" using base::UserMetricsAction; namespace { const char kDownloadNotificationNotifierId[] = "chrome://downloads/notification/id-notifier"; // Background color of the preview images const SkColor kImageBackgroundColor = SK_ColorWHITE; // Maximum size of preview image. If the image exceeds this size, don't show the // preview image. const int64_t kMaxImagePreviewSize = 10 * 1024 * 1024; // 10 MB std::string ReadNotificationImage(const base::FilePath& file_path) { DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); std::string data; bool ret = base::ReadFileToString(file_path, &data); if (!ret) return std::string(); DCHECK_LE(data.size(), static_cast(kMaxImagePreviewSize)); return data; } SkBitmap CropImage(const SkBitmap& original_bitmap) { DCHECK_NE(0, original_bitmap.width()); DCHECK_NE(0, original_bitmap.height()); const SkSize container_size = SkSize::Make( message_center::kNotificationPreferredImageWidth, message_center::kNotificationPreferredImageHeight); const float container_aspect_ratio = static_cast(message_center::kNotificationPreferredImageWidth) / message_center::kNotificationPreferredImageHeight; const float image_aspect_ratio = static_cast(original_bitmap.width()) / original_bitmap.height(); SkRect source_rect; if (image_aspect_ratio > container_aspect_ratio) { float width = original_bitmap.height() * container_aspect_ratio; source_rect = SkRect::MakeXYWH((original_bitmap.width() - width) / 2, 0, width, original_bitmap.height()); } else { float height = original_bitmap.width() / container_aspect_ratio; source_rect = SkRect::MakeXYWH(0, (original_bitmap.height() - height) / 2, original_bitmap.width(), height); } SkBitmap container_bitmap; container_bitmap.allocN32Pixels(container_size.width(), container_size.height()); SkPaint paint; paint.setFilterQuality(kHigh_SkFilterQuality); SkCanvas container_image(container_bitmap); container_image.drawColor(kImageBackgroundColor); container_image.drawBitmapRect( original_bitmap, source_rect, SkRect::MakeSize(container_size), &paint); return container_bitmap; } void RecordButtonClickAction(DownloadCommands::Command command) { switch (command) { case DownloadCommands::SHOW_IN_FOLDER: content::RecordAction( UserMetricsAction("DownloadNotification.Button_ShowInFolder")); break; case DownloadCommands::OPEN_WHEN_COMPLETE: content::RecordAction( UserMetricsAction("DownloadNotification.Button_OpenWhenComplete")); break; case DownloadCommands::ALWAYS_OPEN_TYPE: content::RecordAction( UserMetricsAction("DownloadNotification.Button_AlwaysOpenType")); break; case DownloadCommands::PLATFORM_OPEN: content::RecordAction( UserMetricsAction("DownloadNotification.Button_PlatformOpen")); break; case DownloadCommands::CANCEL: content::RecordAction( UserMetricsAction("DownloadNotification.Button_Cancel")); break; case DownloadCommands::DISCARD: content::RecordAction( UserMetricsAction("DownloadNotification.Button_Discard")); break; case DownloadCommands::KEEP: content::RecordAction( UserMetricsAction("DownloadNotification.Button_Keep")); break; case DownloadCommands::LEARN_MORE_SCANNING: content::RecordAction( UserMetricsAction("DownloadNotification.Button_LearnScanning")); break; case DownloadCommands::LEARN_MORE_INTERRUPTED: content::RecordAction( UserMetricsAction("DownloadNotification.Button_LearnInterrupted")); break; case DownloadCommands::PAUSE: content::RecordAction( UserMetricsAction("DownloadNotification.Button_Pause")); break; case DownloadCommands::RESUME: content::RecordAction( UserMetricsAction("DownloadNotification.Button_Resume")); break; case DownloadCommands::COPY_TO_CLIPBOARD: content::RecordAction( UserMetricsAction("DownloadNotification.Button_CopyToClipboard")); break; } } } // anonymous namespace DownloadItemNotification::DownloadItemNotification( content::DownloadItem* item, DownloadNotificationManagerForProfile* manager) : item_(item), message_center_(manager->message_center()), weak_factory_(this) { // Creates the notification instance. |title|, |body| and |icon| will be // overridden by UpdateNotificationData() below. notification_.reset(new Notification( message_center::NOTIFICATION_TYPE_PROGRESS, base::string16(), // title base::string16(), // body gfx::Image(), // icon message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT, kDownloadNotificationNotifierId), base::string16(), // display_source GURL(kDownloadNotificationOrigin), // origin_url base::UintToString(item_->GetId()), // tag message_center::RichNotificationData(), watcher())); notification_->set_progress(0); notification_->set_never_timeout(false); notification_->set_adjust_icon(false); Update(); } DownloadItemNotification::~DownloadItemNotification() { if (image_decode_status_ == IN_PROGRESS) ImageDecoder::Cancel(this); } bool DownloadItemNotification::HasNotificationClickedListener() { if (item_->IsDangerous()) { // Dangerous notifications don't have a click handler. return false; } return true; } void DownloadItemNotification::OnNotificationClose() { if (item_ && item_->IsDangerous() && !item_->IsDone()) { content::RecordAction( UserMetricsAction("DownloadNotification.Close_Dangerous")); closed_ = true; // Should be set before cancelling the download. item_->Cancel(true /* by_user */); return; } if (image_decode_status_ == IN_PROGRESS) { image_decode_status_ = NOT_STARTED; ImageDecoder::Cancel(this); } } void DownloadItemNotification::OnNotificationClick() { if (item_->IsDangerous()) { content::RecordAction( UserMetricsAction("DownloadNotification.Click_Dangerous")); // Do nothing. return; } switch (item_->GetState()) { case content::DownloadItem::IN_PROGRESS: content::RecordAction( UserMetricsAction("DownloadNotification.Click_InProgress")); item_->SetOpenWhenComplete(!item_->GetOpenWhenComplete()); // Toggle break; case content::DownloadItem::CANCELLED: case content::DownloadItem::INTERRUPTED: content::RecordAction( UserMetricsAction("DownloadNotification.Click_Stopped")); GetBrowser()->OpenURL(content::OpenURLParams( GURL(chrome::kChromeUIDownloadsURL), content::Referrer(), NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_LINK, false /* is_renderer_initiated */)); CloseNotificationByUser(); break; case content::DownloadItem::COMPLETE: content::RecordAction( UserMetricsAction("DownloadNotification.Click_Completed")); item_->OpenDownload(); CloseNotificationByUser(); break; case content::DownloadItem::MAX_DOWNLOAD_STATE: NOTREACHED(); } } void DownloadItemNotification::OnNotificationButtonClick(int button_index) { if (button_index < 0 || static_cast(button_index) >= button_actions_->size()) { // Out of boundary. NOTREACHED(); return; } DownloadCommands::Command command = button_actions_->at(button_index); RecordButtonClickAction(command); DownloadCommands(item_).ExecuteCommand(command); if (command != DownloadCommands::PAUSE && command != DownloadCommands::RESUME) { CloseNotificationByUser(); } // Shows the notification again after clicking "Keep" on dangerous download. if (command == DownloadCommands::KEEP) { show_next_ = true; Update(); } } // DownloadItem::Observer methods void DownloadItemNotification::OnDownloadUpdated(content::DownloadItem* item) { DCHECK_EQ(item, item_); Update(); } std::string DownloadItemNotification::GetNotificationId() const { return base::UintToString(item_->GetId()); } void DownloadItemNotification::CloseNotificationByNonUser() { const std::string& notification_id = watcher()->id(); const ProfileID profile_id = NotificationUIManager::GetProfileID(profile()); g_browser_process->notification_ui_manager()-> CancelById(notification_id, profile_id); } void DownloadItemNotification::CloseNotificationByUser() { // Item may be already removed. if (!item_) return; const std::string& notification_id = watcher()->id(); const ProfileID profile_id = NotificationUIManager::GetProfileID(profile()); const std::string notification_id_in_message_center = ProfileNotification::GetProfileNotificationId(notification_id, profile_id); g_browser_process->notification_ui_manager()-> CancelById(notification_id, profile_id); // When the message center is visible, |NotificationUIManager::CancelByID()| // delays the close hence the notification is not closed at this time. But // from the viewpoint of UX of MessageCenter, we should close it immediately // because it's by user action. So, we request closing of it directlly to // MessageCenter instance. // Note that: this calling has no side-effect even when the message center // is not opened. message_center_->RemoveNotification( notification_id_in_message_center, true /* by_user */); } void DownloadItemNotification::Update() { auto download_state = item_->GetState(); // When the download is just completed, interrupted or transitions to // dangerous, close the notification once and re-show it immediately so // it'll pop up. bool popup = ((item_->IsDangerous() && !previous_dangerous_state_) || (download_state == content::DownloadItem::COMPLETE && previous_download_state_ != content::DownloadItem::COMPLETE) || (download_state == content::DownloadItem::INTERRUPTED && previous_download_state_ != content::DownloadItem::INTERRUPTED)); if (IsNotificationVisible() && !closed_) { UpdateNotificationData(popup ? UPDATE_AND_POPUP : UPDATE); } else { if (show_next_ || popup) UpdateNotificationData(ADD); } show_next_ = false; previous_download_state_ = item_->GetState(); previous_dangerous_state_ = item_->IsDangerous(); } void DownloadItemNotification::UpdateNotificationData( NotificationUpdateType type) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DownloadItemModel model(item_); DownloadCommands command(item_); notification_->set_title(GetTitle()); notification_->set_message(GetStatusString()); if (item_->IsDangerous()) { notification_->set_type(message_center::NOTIFICATION_TYPE_BASE_FORMAT); if (!model.MightBeMalicious()) notification_->set_priority(message_center::HIGH_PRIORITY); else notification_->set_priority(message_center::DEFAULT_PRIORITY); } else { switch (item_->GetState()) { case content::DownloadItem::IN_PROGRESS: { int percent_complete = item_->PercentComplete(); if (percent_complete >= 0) { notification_->set_progress(percent_complete); } else { // Negative progress value shows an indeterminate progress bar. notification_->set_progress(-1); } notification_->set_type(message_center::NOTIFICATION_TYPE_PROGRESS); break; } case content::DownloadItem::COMPLETE: DCHECK(item_->IsDone()); notification_->set_priority(message_center::DEFAULT_PRIORITY); notification_->set_type(message_center::NOTIFICATION_TYPE_BASE_FORMAT); notification_->set_progress(100); break; case content::DownloadItem::CANCELLED: // Confgirms that a download is cancelled by user action. DCHECK(item_->GetLastReason() == content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED || item_->GetLastReason() == content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN); CloseNotificationByUser(); return; // Skips the remaining since the notification has closed. case content::DownloadItem::INTERRUPTED: // Shows a notifiation as progress type once so the visible content will // be updated. (same as the case of type = COMPLETE) notification_->set_type(message_center::NOTIFICATION_TYPE_BASE_FORMAT); notification_->set_progress(0); notification_->set_priority(message_center::DEFAULT_PRIORITY); break; case content::DownloadItem::MAX_DOWNLOAD_STATE: // sentinel NOTREACHED(); } } UpdateNotificationIcon(); std::vector notification_actions; scoped_ptr> actions(GetExtraActions()); button_actions_.reset(new std::vector); for (auto it = actions->begin(); it != actions->end(); it++) { button_actions_->push_back(*it); message_center::ButtonInfo button_info = message_center::ButtonInfo(GetCommandLabel(*it)); button_info.icon = command.GetCommandIcon(*it); notification_actions.push_back(button_info); } notification_->set_buttons(notification_actions); if (type == ADD) { closed_ = false; g_browser_process->notification_ui_manager()-> Add(*notification_, profile()); } else if (type == UPDATE || // If the notification is already visible as popup or in the // notification center, doesn't pop it up. (type == UPDATE_AND_POPUP && IsNotificationVisible())) { // Shows a notifiation as progress type once so the visible content will be // updated. Only progress-type notification's content will be updated // immediately when the message center is visible. // See the comment in MessageCenterImpl::UpdateNotification() for detail. if (type == UPDATE_AND_POPUP && message_center_->IsMessageCenterVisible() && (item_->GetState() == content::DownloadItem::COMPLETE || item_->GetState() == content::DownloadItem::INTERRUPTED)) { DCHECK_EQ(notification_->type(), message_center::NOTIFICATION_TYPE_BASE_FORMAT); notification_->set_type(message_center::NOTIFICATION_TYPE_PROGRESS); g_browser_process->notification_ui_manager()-> Update(*notification_, profile()); notification_->set_type(message_center::NOTIFICATION_TYPE_BASE_FORMAT); } g_browser_process->notification_ui_manager()-> Update(*notification_, profile()); } else if (type == UPDATE_AND_POPUP) { CloseNotificationByNonUser(); closed_ = false; g_browser_process->notification_ui_manager()-> Add(*notification_, profile()); } else { NOTREACHED(); } if (item_->IsDone() && image_decode_status_ == NOT_STARTED) { // TODO(yoshiki): Add an UMA to collect statistics of image file sizes. if (item_->GetReceivedBytes() > kMaxImagePreviewSize) return; DCHECK(notification_->image().IsEmpty()); image_decode_status_ = IN_PROGRESS; bool maybe_image = false; if (mime_util::IsSupportedImageMimeType(item_->GetMimeType())) { maybe_image = true; } else { std::string mime; base::FilePath::StringType extension_with_dot = item_->GetTargetFilePath().FinalExtension(); if (!extension_with_dot.empty() && net::GetWellKnownMimeTypeFromExtension(extension_with_dot.substr(1), &mime) && mime_util::IsSupportedImageMimeType(mime)) { maybe_image = true; } } if (maybe_image) { base::FilePath file_path = item_->GetFullPath(); base::PostTaskAndReplyWithResult( content::BrowserThread::GetBlockingPool(), FROM_HERE, base::Bind(&ReadNotificationImage, file_path), base::Bind(&DownloadItemNotification::OnImageLoaded, weak_factory_.GetWeakPtr())); } } } void DownloadItemNotification::UpdateNotificationIcon() { if (item_->IsDangerous()) { DownloadItemModel model(item_); #if defined(OS_MACOSX) SetNotificationIcon(model.MightBeMalicious() ? IDR_DOWNLOAD_NOTIFICATION_WARNING_BAD : IDR_DOWNLOAD_NOTIFICATION_WARNING_UNWANTED); #else SetNotificationVectorIcon( gfx::VectorIconId::WARNING, model.MightBeMalicious() ? gfx::kGoogleRed700 : gfx::kGoogleYellow700); #endif return; } bool is_off_the_record = item_->GetBrowserContext() && item_->GetBrowserContext()->IsOffTheRecord(); switch (item_->GetState()) { case content::DownloadItem::IN_PROGRESS: case content::DownloadItem::COMPLETE: if (is_off_the_record) { #if defined(OS_MACOSX) SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_INCOGNITO); #else SetNotificationVectorIcon(gfx::VectorIconId::FILE_DOWNLOAD_INCOGNITO, gfx::kChromeIconGrey); #endif } else { SetNotificationVectorIcon(gfx::VectorIconId::FILE_DOWNLOAD, gfx::kGoogleBlue500); } break; case content::DownloadItem::INTERRUPTED: #if defined(OS_MACOSX) SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_ERROR); #else SetNotificationVectorIcon(gfx::VectorIconId::ERROR_CIRCLE, gfx::kGoogleRed700); #endif break; case content::DownloadItem::CANCELLED: break; case content::DownloadItem::MAX_DOWNLOAD_STATE: NOTREACHED(); break; } } void DownloadItemNotification::OnDownloadRemoved(content::DownloadItem* item) { // The given |item| may be already free'd. DCHECK_EQ(item, item_); // Removing the notification causes calling |NotificationDelegate::Close()|. if (g_browser_process->notification_ui_manager()) { g_browser_process->notification_ui_manager()->CancelById( watcher()->id(), NotificationUIManager::GetProfileID(profile())); } item_ = nullptr; } void DownloadItemNotification::SetNotificationIcon(int resource_id) { if (image_resource_id_ == resource_id) return; ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); image_resource_id_ = resource_id; notification_->set_icon(bundle.GetImageNamed(image_resource_id_)); } void DownloadItemNotification::SetNotificationVectorIcon(gfx::VectorIconId id, SkColor color) { if (vector_icon_params_ == std::make_pair(id, color)) return; vector_icon_params_ = std::make_pair(id, color); image_resource_id_ = 0; notification_->set_icon(gfx::Image(gfx::CreateVectorIcon(id, 40, color))); } void DownloadItemNotification::DisablePopup() { if (notification_->priority() == message_center::LOW_PRIORITY) return; // Hides a notification from popup notifications if it's a pop-up, by // decreasing its priority and reshowing itself. Low-priority notifications // doesn't pop-up itself so this logic works as disabling pop-up. CloseNotificationByNonUser(); notification_->set_priority(message_center::LOW_PRIORITY); closed_ = false; g_browser_process->notification_ui_manager()->Add(*notification_, profile()); } void DownloadItemNotification::OnImageLoaded(const std::string& image_data) { if (image_data.empty()) return; // TODO(yoshiki): Set option to reduce the image size to supress memory usage. ImageDecoder::Start(this, image_data); } void DownloadItemNotification::OnImageDecoded(const SkBitmap& decoded_bitmap) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (decoded_bitmap.drawsNothing()) { OnDecodeImageFailed(); return; } base::PostTaskAndReplyWithResult( content::BrowserThread::GetBlockingPool(), FROM_HERE, base::Bind(&CropImage, decoded_bitmap), base::Bind(&DownloadItemNotification::OnImageCropped, weak_factory_.GetWeakPtr())); } void DownloadItemNotification::OnImageCropped(const SkBitmap& bitmap) { gfx::Image image = gfx::Image::CreateFrom1xBitmap(bitmap); notification_->set_image(image); image_decode_status_ = DONE; UpdateNotificationData(UPDATE); } void DownloadItemNotification::OnDecodeImageFailed() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK(notification_->image().IsEmpty()); image_decode_status_ = FAILED; UpdateNotificationData(UPDATE); } scoped_ptr> DownloadItemNotification::GetExtraActions() const { scoped_ptr> actions( new std::vector()); if (item_->IsDangerous()) { DownloadItemModel model(item_); if (model.MightBeMalicious()) { actions->push_back(DownloadCommands::LEARN_MORE_SCANNING); } else { actions->push_back(DownloadCommands::DISCARD); actions->push_back(DownloadCommands::KEEP); } return actions; } switch (item_->GetState()) { case content::DownloadItem::IN_PROGRESS: if (!item_->IsPaused()) actions->push_back(DownloadCommands::PAUSE); else actions->push_back(DownloadCommands::RESUME); actions->push_back(DownloadCommands::CANCEL); break; case content::DownloadItem::CANCELLED: case content::DownloadItem::INTERRUPTED: if (item_->CanResume()) actions->push_back(DownloadCommands::RESUME); break; case content::DownloadItem::COMPLETE: actions->push_back(DownloadCommands::SHOW_IN_FOLDER); if (!notification_->image().IsEmpty()) actions->push_back(DownloadCommands::COPY_TO_CLIPBOARD); break; case content::DownloadItem::MAX_DOWNLOAD_STATE: NOTREACHED(); } return actions; } base::string16 DownloadItemNotification::GetTitle() const { base::string16 title_text; DownloadItemModel model(item_); if (item_->IsDangerous()) { if (model.MightBeMalicious()) { return l10n_util::GetStringUTF16( IDS_PROMPT_BLOCKED_MALICIOUS_DOWNLOAD_TITLE); } else { return l10n_util::GetStringUTF16( IDS_CONFIRM_KEEP_DANGEROUS_DOWNLOAD_TITLE); } } base::string16 file_name = item_->GetFileNameToReportUser().LossyDisplayName(); switch (item_->GetState()) { case content::DownloadItem::IN_PROGRESS: if (!item_->IsPaused()) { title_text = l10n_util::GetStringFUTF16( IDS_DOWNLOAD_STATUS_IN_PROGRESS_TITLE, file_name); } else { title_text = l10n_util::GetStringFUTF16( IDS_DOWNLOAD_STATUS_PAUSED_TITLE, file_name); } break; case content::DownloadItem::COMPLETE: title_text = l10n_util::GetStringFUTF16( IDS_DOWNLOAD_STATUS_DOWNLOADED_TITLE, file_name); break; case content::DownloadItem::INTERRUPTED: title_text = l10n_util::GetStringFUTF16( IDS_DOWNLOAD_STATUS_DOWNLOAD_FAILED_TITLE, file_name); break; case content::DownloadItem::CANCELLED: title_text = l10n_util::GetStringFUTF16( IDS_DOWNLOAD_STATUS_DOWNLOAD_FAILED_TITLE, file_name); break; case content::DownloadItem::MAX_DOWNLOAD_STATE: NOTREACHED(); } return title_text; } base::string16 DownloadItemNotification::GetCommandLabel( DownloadCommands::Command command) const { int id = -1; switch (command) { case DownloadCommands::OPEN_WHEN_COMPLETE: if (item_ && !item_->IsDone()) id = IDS_DOWNLOAD_NOTIFICATION_LABEL_OPEN_WHEN_COMPLETE; else id = IDS_DOWNLOAD_NOTIFICATION_LABEL_OPEN; break; case DownloadCommands::PAUSE: // Only for non menu. id = IDS_DOWNLOAD_LINK_PAUSE; break; case DownloadCommands::RESUME: // Only for non menu. id = IDS_DOWNLOAD_LINK_RESUME; break; case DownloadCommands::SHOW_IN_FOLDER: id = IDS_DOWNLOAD_LINK_SHOW; break; case DownloadCommands::DISCARD: id = IDS_DISCARD_DOWNLOAD; break; case DownloadCommands::KEEP: id = IDS_CONFIRM_DOWNLOAD; break; case DownloadCommands::CANCEL: id = IDS_DOWNLOAD_LINK_CANCEL; break; case DownloadCommands::LEARN_MORE_SCANNING: id = IDS_DOWNLOAD_LINK_LEARN_MORE_SCANNING; break; case DownloadCommands::COPY_TO_CLIPBOARD: id = IDS_DOWNLOAD_NOTIFICATION_COPY_TO_CLIPBOARD; break; case DownloadCommands::ALWAYS_OPEN_TYPE: case DownloadCommands::PLATFORM_OPEN: case DownloadCommands::LEARN_MORE_INTERRUPTED: // Only for menu. NOTREACHED(); return base::string16(); } CHECK(id != -1); return l10n_util::GetStringUTF16(id); } base::string16 DownloadItemNotification::GetWarningStatusString() const { // Should only be called if IsDangerous(). DCHECK(item_->IsDangerous()); base::string16 elided_filename = item_->GetFileNameToReportUser().LossyDisplayName(); switch (item_->GetDangerType()) { case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: { return l10n_util::GetStringUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_URL); } case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: { if (download_crx_util::IsExtensionDownload(*item_)) { return l10n_util::GetStringUTF16( IDS_PROMPT_DANGEROUS_DOWNLOAD_EXTENSION); } else { return l10n_util::GetStringFUTF16(IDS_PROMPT_DANGEROUS_DOWNLOAD, elided_filename); } } case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: { return l10n_util::GetStringFUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_CONTENT, elided_filename); } case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: { return l10n_util::GetStringFUTF16(IDS_PROMPT_UNCOMMON_DOWNLOAD_CONTENT, elided_filename); } case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: { return l10n_util::GetStringFUTF16(IDS_PROMPT_DOWNLOAD_CHANGES_SETTINGS, elided_filename); } case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS: case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT: case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED: case content::DOWNLOAD_DANGER_TYPE_MAX: { break; } } NOTREACHED(); return base::string16(); } base::string16 DownloadItemNotification::GetInProgressSubStatusString() const { // "Paused" if (item_->IsPaused()) return l10n_util::GetStringUTF16(IDS_DOWNLOAD_PROGRESS_PAUSED); base::TimeDelta time_remaining; // time_remaining is only known if the download isn't paused. bool time_remaining_known = (!item_->IsPaused() && item_->TimeRemaining(&time_remaining)); // A download scheduled to be opened when complete. if (item_->GetOpenWhenComplete()) { // "Opening when complete" if (!time_remaining_known) return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE); // "Opening in 10 secs" return l10n_util::GetStringFUTF16( IDS_DOWNLOAD_STATUS_OPEN_IN, ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_DURATION, ui::TimeFormat::LENGTH_SHORT, time_remaining)); } // In progress download with known time left: "10 secs left" if (time_remaining_known) { return ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_REMAINING, ui::TimeFormat::LENGTH_SHORT, time_remaining); } // "In progress" if (item_->GetReceivedBytes() > 0) return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_IN_PROGRESS_SHORT); // "Starting..." return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING); } base::string16 DownloadItemNotification::GetStatusString() const { if (item_->IsDangerous()) return GetWarningStatusString(); // The hostname. (E.g.:"example.com" or "127.0.0.1") base::string16 host_name = url_formatter::FormatUrlForSecurityDisplayOmitScheme( item_->GetURL(), profile()->GetPrefs()->GetString(prefs::kAcceptLanguages)); DownloadItemModel model(item_); base::string16 sub_status_text; bool show_size_ratio = true; switch (item_->GetState()) { case content::DownloadItem::IN_PROGRESS: // The download is a CRX (app, extension, theme, ...) and it is being // unpacked and validated. if (item_->AllDataSaved() && download_crx_util::IsExtensionDownload(*item_)) { show_size_ratio = false; sub_status_text = l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CRX_INSTALL_RUNNING); } else { sub_status_text = GetInProgressSubStatusString(); } break; case content::DownloadItem::COMPLETE: // If the file has been removed: Removed if (item_->GetFileExternallyRemoved()) { show_size_ratio = false; sub_status_text = l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_REMOVED); } else { // Otherwise, the download should be completed. // "3.4 MB from example.com" base::string16 size = ui::FormatBytes(item_->GetReceivedBytes()); return l10n_util::GetStringFUTF16( IDS_DOWNLOAD_NOTIFICATION_STATUS_COMPLETED, size, host_name); } break; case content::DownloadItem::CANCELLED: // "Cancelled" sub_status_text = l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED); break; case content::DownloadItem::INTERRUPTED: { content::DownloadInterruptReason reason = item_->GetLastReason(); if (reason != content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED) { // "Failed - " base::string16 interrupt_reason = model.GetInterruptReasonText(); DCHECK(!interrupt_reason.empty()); sub_status_text = l10n_util::GetStringFUTF16( IDS_DOWNLOAD_STATUS_INTERRUPTED, interrupt_reason); } else { // Same as DownloadItem::CANCELLED. sub_status_text = l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED); } break; } default: NOTREACHED(); } // Indication of progress (E.g.:"100/200 MB" or "100 MB"), or just the // received bytes if the |show_size_ratio| flag is false. base::string16 size = show_size_ratio ? model.GetProgressSizesString() : ui::FormatBytes(item_->GetReceivedBytes()); // Download is not completed yet: "3.4/5.6 MB, \nFrom example.com" return l10n_util::GetStringFUTF16( IDS_DOWNLOAD_NOTIFICATION_STATUS, size, sub_status_text, host_name); } Browser* DownloadItemNotification::GetBrowser() const { chrome::ScopedTabbedBrowserDisplayer browser_displayer( profile(), chrome::GetActiveDesktop()); DCHECK(browser_displayer.browser()); return browser_displayer.browser(); } Profile* DownloadItemNotification::profile() const { return Profile::FromBrowserContext(item_->GetBrowserContext()); } bool DownloadItemNotification::IsNotificationVisible() const { const std::string& notification_id = watcher()->id(); const ProfileID profile_id = NotificationUIManager::GetProfileID(profile()); if (!g_browser_process->notification_ui_manager()) return false; const Notification* notification = g_browser_process-> notification_ui_manager()->FindById(notification_id, profile_id); if (!notification) return false; const std::string notification_id_in_message_center = notification->id(); message_center::NotificationList::Notifications visible_notifications = message_center_->GetVisibleNotifications(); for (const auto& notification : visible_notifications) { if (notification->id() == notification_id_in_message_center) return true; } return false; }