blob: 732f5bfc827b633e11de3b24e8348c3842217b19 [file] [log] [blame]
// 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 <stddef.h>
#include <stdint.h>
#include "base/files/file_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task_scheduler/post_task.h"
#include "build/build_config.h"
#include "chrome/app/vector_icons/vector_icons.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/url_constants.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/theme_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 "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/message_center/message_center.h"
#include "ui/message_center/message_center_style.h"
#include "ui/vector_icons/vector_icons.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/note_taking_helper.h"
#endif // defined(OS_CHROMEOS)
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) {
std::string data;
bool ret = base::ReadFileToString(file_path, &data);
if (!ret)
return std::string();
DCHECK_LE(data.size(), static_cast<size_t>(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<float>(message_center::kNotificationPreferredImageWidth) /
message_center::kNotificationPreferredImageHeight;
const float image_aspect_ratio =
static_cast<float>(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;
case DownloadCommands::ANNOTATE:
content::RecordAction(
UserMetricsAction("DownloadNotification.Button_Annotate"));
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.
message_center::RichNotificationData rich_notification_data;
rich_notification_data.should_make_spoken_feedback_for_popup_updates = false;
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
rich_notification_data, watcher()));
notification_->set_progress(0);
notification_->set_never_timeout(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(),
WindowOpenDisposition::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<size_t>(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<message_center::ButtonInfo> notification_actions;
std::unique_ptr<std::vector<DownloadCommands::Command>> actions(
GetExtraActions());
button_actions_.reset(new std::vector<DownloadCommands::Command>);
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;
if (model.HasSupportedImageMimeType()) {
base::FilePath file_path = item_->GetFullPath();
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, base::TaskTraits().MayBlock().WithPriority(
base::TaskPriority::BACKGROUND),
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(ui::kWarningIcon, 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(kFileDownloadIncognitoIcon,
gfx::kChromeIconGrey);
#endif
} else {
SetNotificationVectorIcon(kFileDownloadIcon, gfx::kGoogleBlue500);
}
break;
case content::DownloadItem::INTERRUPTED:
#if defined(OS_MACOSX)
SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_ERROR);
#else
SetNotificationVectorIcon(ui::kErrorCircleIcon, 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) {
ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
notification_->set_icon(bundle.GetImageNamed(resource_id));
}
void DownloadItemNotification::SetNotificationVectorIcon(
const gfx::VectorIcon& icon,
SkColor color) {
notification_->set_icon(gfx::Image(gfx::CreateVectorIcon(icon, 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::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, base::TaskTraits().MayBlock().WithPriority(
base::TaskPriority::BACKGROUND),
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);
}
std::unique_ptr<std::vector<DownloadCommands::Command>>
DownloadItemNotification::GetExtraActions() const {
std::unique_ptr<std::vector<DownloadCommands::Command>> actions(
new std::vector<DownloadCommands::Command>());
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);
#if defined(OS_CHROMEOS)
if (chromeos::NoteTakingHelper::Get()->IsAppAvailable(profile()))
actions->push_back(DownloadCommands::ANNOTATE);
#endif // defined(OS_CHROMEOS)
}
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::ANNOTATE:
id = IDS_DOWNLOAD_NOTIFICATION_ANNOTATE;
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::FormatUrlForSecurityDisplay(
item_->GetURL(), url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS);
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 - <REASON>"
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, <SUB STATUS>\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());
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 (auto* notification : visible_notifications) {
if (notification->id() == notification_id_in_message_center)
return true;
}
return false;
}