1 // Copyright 2015 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.
5 #include "chrome/browser/download/notification/download_item_notification.h"
7 #include "base/files/file_util.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/browser/browser_process.h"
10 #include "chrome/browser/download/download_crx_util.h"
11 #include "chrome/browser/download/download_item_model.h"
12 #include "chrome/browser/download/notification/download_notification_manager.h"
13 #include "chrome/browser/notifications/notification.h"
14 #include "chrome/browser/notifications/notification_ui_manager.h"
15 #include "chrome/browser/notifications/profile_notification.h"
16 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
17 #include "chrome/common/url_constants.h"
18 #include "chrome/grit/chromium_strings.h"
19 #include "chrome/grit/generated_resources.h"
20 #include "components/mime_util/mime_util.h"
21 #include "content/public/browser/browser_context.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/download_interrupt_reasons.h"
24 #include "content/public/browser/download_item.h"
25 #include "content/public/browser/page_navigator.h"
26 #include "content/public/browser/user_metrics.h"
27 #include "content/public/browser/web_contents.h"
28 #include "grit/theme_resources.h"
29 #include "net/base/mime_util.h"
30 #include "third_party/skia/include/core/SkCanvas.h"
31 #include "ui/base/l10n/l10n_util.h"
32 #include "ui/base/l10n/time_format.h"
33 #include "ui/base/resource/resource_bundle.h"
34 #include "ui/gfx/codec/jpeg_codec.h"
35 #include "ui/gfx/image/image.h"
36 #include "ui/message_center/message_center.h"
37 #include "ui/message_center/message_center_style.h"
39 #if !defined(OS_MACOSX)
40 #include "ui/gfx/paint_vector_icon.h"
41 #include "ui/gfx/vector_icons_public.h"
42 #include "ui/native_theme/common_theme.h"
45 using base::UserMetricsAction
;
49 const char kDownloadNotificationNotifierId
[] =
50 "chrome://downloads/notification/id-notifier";
52 // Background color of the preview images
53 const SkColor kImageBackgroundColor
= SK_ColorWHITE
;
55 // Maximum size of preview image. If the image exceeds this size, don't show the
57 const int64 kMaxImagePreviewSize
= 10 * 1024 * 1024; // 10 MB
59 std::string
ReadNotificationImage(const base::FilePath
& file_path
) {
60 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
63 bool ret
= base::ReadFileToString(file_path
, &data
);
67 DCHECK_LE(data
.size(), static_cast<size_t>(kMaxImagePreviewSize
));
72 SkBitmap
CropImage(const SkBitmap
& original_bitmap
) {
73 DCHECK_NE(0, original_bitmap
.width());
74 DCHECK_NE(0, original_bitmap
.height());
76 const SkSize container_size
= SkSize::Make(
77 message_center::kNotificationPreferredImageWidth
,
78 message_center::kNotificationPreferredImageHeight
);
79 const float container_aspect_ratio
=
80 static_cast<float>(message_center::kNotificationPreferredImageWidth
) /
81 message_center::kNotificationPreferredImageHeight
;
82 const float image_aspect_ratio
=
83 static_cast<float>(original_bitmap
.width()) / original_bitmap
.height();
86 if (image_aspect_ratio
> container_aspect_ratio
) {
87 float width
= original_bitmap
.height() * container_aspect_ratio
;
88 source_rect
= SkRect::MakeXYWH((original_bitmap
.width() - width
) / 2,
91 original_bitmap
.height());
93 float height
= original_bitmap
.width() / container_aspect_ratio
;
94 source_rect
= SkRect::MakeXYWH(0,
95 (original_bitmap
.height() - height
) / 2,
96 original_bitmap
.width(),
101 SkBitmap container_bitmap
;
102 container_bitmap
.allocN32Pixels(container_size
.width(),
103 container_size
.height());
105 paint
.setFilterQuality(kHigh_SkFilterQuality
);
106 SkCanvas
container_image(container_bitmap
);
107 container_image
.drawColor(kImageBackgroundColor
);
108 container_image
.drawBitmapRect(
109 original_bitmap
, source_rect
, SkRect::MakeSize(container_size
), &paint
);
111 return container_bitmap
;
114 void RecordButtonClickAction(DownloadCommands::Command command
) {
116 case DownloadCommands::SHOW_IN_FOLDER
:
117 content::RecordAction(
118 UserMetricsAction("DownloadNotification.Button_ShowInFolder"));
120 case DownloadCommands::OPEN_WHEN_COMPLETE
:
121 content::RecordAction(
122 UserMetricsAction("DownloadNotification.Button_OpenWhenComplete"));
124 case DownloadCommands::ALWAYS_OPEN_TYPE
:
125 content::RecordAction(
126 UserMetricsAction("DownloadNotification.Button_AlwaysOpenType"));
128 case DownloadCommands::PLATFORM_OPEN
:
129 content::RecordAction(
130 UserMetricsAction("DownloadNotification.Button_PlatformOpen"));
132 case DownloadCommands::CANCEL
:
133 content::RecordAction(
134 UserMetricsAction("DownloadNotification.Button_Cancel"));
136 case DownloadCommands::DISCARD
:
137 content::RecordAction(
138 UserMetricsAction("DownloadNotification.Button_Discard"));
140 case DownloadCommands::KEEP
:
141 content::RecordAction(
142 UserMetricsAction("DownloadNotification.Button_Keep"));
144 case DownloadCommands::LEARN_MORE_SCANNING
:
145 content::RecordAction(
146 UserMetricsAction("DownloadNotification.Button_LearnScanning"));
148 case DownloadCommands::LEARN_MORE_INTERRUPTED
:
149 content::RecordAction(
150 UserMetricsAction("DownloadNotification.Button_LearnInterrupted"));
152 case DownloadCommands::PAUSE
:
153 content::RecordAction(
154 UserMetricsAction("DownloadNotification.Button_Pause"));
156 case DownloadCommands::RESUME
:
157 content::RecordAction(
158 UserMetricsAction("DownloadNotification.Button_Resume"));
163 #if !defined(OS_MACOSX)
164 gfx::Image
GetVectorIcon(gfx::VectorIconId id
, ui::NativeTheme::ColorId color
) {
166 ui::CommonThemeGetSystemColor(color
, &icon_color
);
167 return gfx::Image(gfx::CreateVectorIcon(id
, 40, icon_color
));
171 } // anonymous namespace
173 DownloadItemNotification::DownloadItemNotification(
174 content::DownloadItem
* item
,
175 DownloadNotificationManagerForProfile
* manager
)
177 weak_factory_(this) {
178 // Creates the notification instance. |title|, |body| and |icon| will be
179 // overridden by UpdateNotificationData() below.
180 notification_
.reset(new Notification(
181 message_center::NOTIFICATION_TYPE_PROGRESS
,
182 base::string16(), // title
183 base::string16(), // body
184 gfx::Image(), // icon
185 message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT
,
186 kDownloadNotificationNotifierId
),
187 base::string16(), // display_source
188 GURL(kDownloadNotificationOrigin
), // origin_url
189 base::UintToString(item_
->GetId()), // tag
190 message_center::RichNotificationData(), watcher()));
192 notification_
->set_progress(0);
193 notification_
->set_never_timeout(false);
194 notification_
->set_adjust_icon(false);
197 DownloadItemNotification::~DownloadItemNotification() {
198 if (image_decode_status_
== IN_PROGRESS
)
199 ImageDecoder::Cancel(this);
202 bool DownloadItemNotification::HasNotificationClickedListener() {
203 if (item_
->IsDangerous()) {
204 // Dangerous notifications don't have a click handler.
210 void DownloadItemNotification::OnNotificationClose() {
213 if (item_
&& item_
->IsDangerous() && !item_
->IsDone()) {
214 // TODO(yoshiki): Add metrics.
215 item_
->Cancel(true /* by_user */);
219 if (image_decode_status_
== IN_PROGRESS
) {
220 image_decode_status_
= NOT_STARTED
;
221 ImageDecoder::Cancel(this);
225 void DownloadItemNotification::OnNotificationClick() {
226 if (item_
->IsDangerous()) {
227 content::RecordAction(
228 UserMetricsAction("DownloadNotification.Click_Dangerous"));
233 switch (item_
->GetState()) {
234 case content::DownloadItem::IN_PROGRESS
:
235 content::RecordAction(
236 UserMetricsAction("DownloadNotification.Click_InProgress"));
237 item_
->SetOpenWhenComplete(!item_
->GetOpenWhenComplete()); // Toggle
239 case content::DownloadItem::CANCELLED
:
240 case content::DownloadItem::INTERRUPTED
:
241 content::RecordAction(
242 UserMetricsAction("DownloadNotification.Click_Stopped"));
243 GetBrowser()->OpenURL(content::OpenURLParams(
244 GURL(chrome::kChromeUIDownloadsURL
), content::Referrer(),
245 NEW_FOREGROUND_TAB
, ui::PAGE_TRANSITION_LINK
,
246 false /* is_renderer_initiated */));
247 CloseNotificationByUser();
249 case content::DownloadItem::COMPLETE
:
250 content::RecordAction(
251 UserMetricsAction("DownloadNotification.Click_Completed"));
252 item_
->OpenDownload();
253 CloseNotificationByUser();
255 case content::DownloadItem::MAX_DOWNLOAD_STATE
:
260 void DownloadItemNotification::OnNotificationButtonClick(int button_index
) {
261 if (button_index
< 0 ||
262 static_cast<size_t>(button_index
) >= button_actions_
->size()) {
268 DownloadCommands::Command command
= button_actions_
->at(button_index
);
269 RecordButtonClickAction(command
);
271 DownloadCommands(item_
).ExecuteCommand(command
);
273 if (command
!= DownloadCommands::PAUSE
&&
274 command
!= DownloadCommands::RESUME
) {
275 CloseNotificationByUser();
278 // Shows the notification again after clicking "Keep" on dangerous download.
279 if (command
== DownloadCommands::KEEP
) {
285 // DownloadItem::Observer methods
286 void DownloadItemNotification::OnDownloadUpdated(content::DownloadItem
* item
) {
287 DCHECK_EQ(item
, item_
);
292 std::string
DownloadItemNotification::GetNotificationId() const {
293 return base::UintToString(item_
->GetId());
296 void DownloadItemNotification::CloseNotificationByNonUser() {
297 const std::string
& notification_id
= watcher()->id();
298 const ProfileID profile_id
= NotificationUIManager::GetProfileID(profile());
300 g_browser_process
->notification_ui_manager()->
301 CancelById(notification_id
, profile_id
);
304 void DownloadItemNotification::CloseNotificationByUser() {
305 // Item may be already removed.
309 const std::string
& notification_id
= watcher()->id();
310 const ProfileID profile_id
= NotificationUIManager::GetProfileID(profile());
311 const std::string notification_id_in_message_center
=
312 ProfileNotification::GetProfileNotificationId(notification_id
,
315 g_browser_process
->notification_ui_manager()->
316 CancelById(notification_id
, profile_id
);
318 // When the message center is visible, |NotificationUIManager::CancelByID()|
319 // delays the close hence the notification is not closed at this time. But
320 // from the viewpoint of UX of MessageCenter, we should close it immediately
321 // because it's by user action. So, we request closing of it directlly to
322 // MessageCenter instance.
323 // Note that: this calling has no side-effect even when the message center
325 g_browser_process
->message_center()->RemoveNotification(
326 notification_id_in_message_center
, true /* by_user */);
329 void DownloadItemNotification::Update() {
330 auto download_state
= item_
->GetState();
332 // When the download is just completed, interrupted or transitions to
333 // dangerous, close the notification once and re-show it immediately so
336 ((item_
->IsDangerous() && !previous_dangerous_state_
) ||
337 (download_state
== content::DownloadItem::COMPLETE
&&
338 previous_download_state_
!= content::DownloadItem::COMPLETE
) ||
339 (download_state
== content::DownloadItem::INTERRUPTED
&&
340 previous_download_state_
!= content::DownloadItem::INTERRUPTED
));
343 UpdateNotificationData(popup
? UPDATE_AND_POPUP
: UPDATE
);
345 if (show_next_
|| popup
) {
346 UpdateNotificationData(ADD
);
352 previous_download_state_
= item_
->GetState();
353 previous_dangerous_state_
= item_
->IsDangerous();
356 void DownloadItemNotification::UpdateNotificationData(
357 NotificationUpdateType type
) {
358 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
360 DownloadItemModel
model(item_
);
361 DownloadCommands
command(item_
);
363 notification_
->set_title(GetTitle());
364 notification_
->set_message(GetStatusString());
366 if (item_
->IsDangerous()) {
367 notification_
->set_type(message_center::NOTIFICATION_TYPE_BASE_FORMAT
);
370 if (model
.MightBeMalicious()) {
371 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_WARNING_BAD
);
372 notification_
->set_priority(message_center::DEFAULT_PRIORITY
);
374 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_WARNING_UNWANTED
);
375 notification_
->set_priority(message_center::HIGH_PRIORITY
);
378 notification_
->set_priority(message_center::DEFAULT_PRIORITY
);
380 switch (item_
->GetState()) {
381 case content::DownloadItem::IN_PROGRESS
: {
382 int percent_complete
= item_
->PercentComplete();
383 if (percent_complete
>= 0) {
384 notification_
->set_type(message_center::NOTIFICATION_TYPE_PROGRESS
);
385 notification_
->set_progress(percent_complete
);
387 notification_
->set_type(
388 message_center::NOTIFICATION_TYPE_BASE_FORMAT
);
389 notification_
->set_progress(0);
391 UpdateNotificationIcon();
394 case content::DownloadItem::COMPLETE
:
395 DCHECK(item_
->IsDone());
396 notification_
->set_type(message_center::NOTIFICATION_TYPE_BASE_FORMAT
);
397 notification_
->set_progress(100);
398 UpdateNotificationIcon();
400 case content::DownloadItem::CANCELLED
:
401 // Confgirms that a download is cancelled by user action.
402 DCHECK(item_
->GetLastReason() ==
403 content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED
||
404 item_
->GetLastReason() ==
405 content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN
);
407 CloseNotificationByUser();
408 return; // Skips the remaining since the notification has closed.
409 case content::DownloadItem::INTERRUPTED
:
410 // Shows a notifiation as progress type once so the visible content will
411 // be updated. (same as the case of type = COMPLETE)
412 notification_
->set_type(message_center::NOTIFICATION_TYPE_BASE_FORMAT
);
413 notification_
->set_progress(0);
414 UpdateNotificationIcon();
416 case content::DownloadItem::MAX_DOWNLOAD_STATE
: // sentinel
421 std::vector
<message_center::ButtonInfo
> notification_actions
;
422 scoped_ptr
<std::vector
<DownloadCommands::Command
>> actions(
423 GetExtraActions().Pass());
425 button_actions_
.reset(new std::vector
<DownloadCommands::Command
>);
426 for (auto it
= actions
->begin(); it
!= actions
->end(); it
++) {
427 button_actions_
->push_back(*it
);
428 message_center::ButtonInfo button_info
=
429 message_center::ButtonInfo(GetCommandLabel(*it
));
430 button_info
.icon
= command
.GetCommandIcon(*it
);
431 notification_actions
.push_back(button_info
);
433 notification_
->set_buttons(notification_actions
);
436 g_browser_process
->notification_ui_manager()->
437 Add(*notification_
, profile());
438 } else if (type
== UPDATE
||
439 // If the notification is already visible as popup or in the
440 // notification center, doesn't pop it up.
441 (type
== UPDATE_AND_POPUP
&& IsNotificationVisible())) {
442 // Shows a notifiation as progress type once so the visible content will be
443 // updated. Only progress-type notification's content will be updated
444 // immediately when the message center is visible.
445 // See the comment in MessageCenterImpl::UpdateNotification() for detail.
446 if (type
== UPDATE_AND_POPUP
&&
447 g_browser_process
->message_center()->IsMessageCenterVisible() &&
448 (item_
->GetState() == content::DownloadItem::COMPLETE
||
449 item_
->GetState() == content::DownloadItem::INTERRUPTED
)) {
450 DCHECK_EQ(notification_
->type(),
451 message_center::NOTIFICATION_TYPE_BASE_FORMAT
);
453 notification_
->set_type(message_center::NOTIFICATION_TYPE_PROGRESS
);
454 g_browser_process
->notification_ui_manager()->
455 Update(*notification_
, profile());
456 notification_
->set_type(message_center::NOTIFICATION_TYPE_BASE_FORMAT
);
459 g_browser_process
->notification_ui_manager()->
460 Update(*notification_
, profile());
461 } else if (type
== UPDATE_AND_POPUP
) {
462 CloseNotificationByNonUser();
463 g_browser_process
->notification_ui_manager()->
464 Add(*notification_
, profile());
469 if (item_
->IsDone() && image_decode_status_
== NOT_STARTED
) {
470 // TODO(yoshiki): Add an UMA to collect statistics of image file sizes.
472 if (item_
->GetReceivedBytes() > kMaxImagePreviewSize
)
475 DCHECK(notification_
->image().IsEmpty());
477 image_decode_status_
= IN_PROGRESS
;
479 bool maybe_image
= false;
480 if (mime_util::IsSupportedImageMimeType(item_
->GetMimeType())) {
484 base::FilePath::StringType extension_with_dot
=
485 item_
->GetTargetFilePath().FinalExtension();
486 if (!extension_with_dot
.empty() &&
487 net::GetWellKnownMimeTypeFromExtension(extension_with_dot
.substr(1),
489 mime_util::IsSupportedImageMimeType(mime
)) {
495 base::FilePath file_path
= item_
->GetFullPath();
496 base::PostTaskAndReplyWithResult(
497 content::BrowserThread::GetBlockingPool(), FROM_HERE
,
498 base::Bind(&ReadNotificationImage
, file_path
),
499 base::Bind(&DownloadItemNotification::OnImageLoaded
,
500 weak_factory_
.GetWeakPtr()));
505 void DownloadItemNotification::UpdateNotificationIcon() {
506 bool is_off_the_record
= item_
->GetBrowserContext() &&
507 item_
->GetBrowserContext()->IsOffTheRecord();
508 switch (item_
->GetState()) {
509 case content::DownloadItem::IN_PROGRESS
:
510 case content::DownloadItem::COMPLETE
:
511 if (is_off_the_record
) {
512 // TODO(estade): vectorize.
513 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_INCOGNITO
);
515 #if defined(OS_MACOSX)
516 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_DOWNLOADING
);
518 SetNotificationVectorIcon(gfx::VectorIconId::FILE_DOWNLOAD
,
519 ui::NativeTheme::kColorId_GoogleBlue
);
524 case content::DownloadItem::INTERRUPTED
:
525 // TODO(estade): vectorize.
526 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_ERROR
);
529 case content::DownloadItem::CANCELLED
:
532 case content::DownloadItem::MAX_DOWNLOAD_STATE
:
538 void DownloadItemNotification::OnDownloadRemoved(content::DownloadItem
* item
) {
539 // The given |item| may be already free'd.
540 DCHECK_EQ(item
, item_
);
542 // Removing the notification causes calling |NotificationDelegate::Close()|.
543 if (g_browser_process
->notification_ui_manager()) {
544 g_browser_process
->notification_ui_manager()->CancelById(
545 watcher()->id(), NotificationUIManager::GetProfileID(profile()));
551 void DownloadItemNotification::SetNotificationIcon(int resource_id
) {
552 if (image_resource_id_
== resource_id
)
554 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
555 image_resource_id_
= resource_id
;
556 notification_
->set_icon(bundle
.GetImageNamed(image_resource_id_
));
559 #if !defined(OS_MACOSX)
560 void DownloadItemNotification::SetNotificationVectorIcon(
561 gfx::VectorIconId id
,
562 ui::NativeTheme::ColorId color
) {
563 if (vector_icon_params_
== std::make_pair(id
, color
))
565 vector_icon_params_
= std::make_pair(id
, color
);
566 image_resource_id_
= 0;
567 notification_
->set_icon(GetVectorIcon(id
, color
));
571 void DownloadItemNotification::OnImageLoaded(const std::string
& image_data
) {
572 if (image_data
.empty())
575 // TODO(yoshiki): Set option to reduce the image size to supress memory usage.
576 ImageDecoder::Start(this, image_data
);
579 void DownloadItemNotification::OnImageDecoded(const SkBitmap
& decoded_bitmap
) {
580 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
582 if (decoded_bitmap
.drawsNothing()) {
583 OnDecodeImageFailed();
587 base::PostTaskAndReplyWithResult(
588 content::BrowserThread::GetBlockingPool(), FROM_HERE
,
589 base::Bind(&CropImage
, decoded_bitmap
),
590 base::Bind(&DownloadItemNotification::OnImageCropped
,
591 weak_factory_
.GetWeakPtr()));
594 void DownloadItemNotification::OnImageCropped(const SkBitmap
& bitmap
) {
595 gfx::Image image
= gfx::Image::CreateFrom1xBitmap(bitmap
);
596 notification_
->set_image(image
);
597 image_decode_status_
= DONE
;
598 UpdateNotificationData(UPDATE
);
601 void DownloadItemNotification::OnDecodeImageFailed() {
602 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
603 DCHECK(notification_
->image().IsEmpty());
605 image_decode_status_
= FAILED
;
606 UpdateNotificationData(UPDATE
);
609 scoped_ptr
<std::vector
<DownloadCommands::Command
>>
610 DownloadItemNotification::GetExtraActions() const {
611 scoped_ptr
<std::vector
<DownloadCommands::Command
>> actions(
612 new std::vector
<DownloadCommands::Command
>());
614 if (item_
->IsDangerous()) {
615 DownloadItemModel
model(item_
);
616 if (model
.MightBeMalicious()) {
617 actions
->push_back(DownloadCommands::LEARN_MORE_SCANNING
);
619 actions
->push_back(DownloadCommands::DISCARD
);
620 actions
->push_back(DownloadCommands::KEEP
);
622 return actions
.Pass();
625 switch (item_
->GetState()) {
626 case content::DownloadItem::IN_PROGRESS
:
627 if (!item_
->IsPaused())
628 actions
->push_back(DownloadCommands::PAUSE
);
630 actions
->push_back(DownloadCommands::RESUME
);
631 actions
->push_back(DownloadCommands::CANCEL
);
633 case content::DownloadItem::CANCELLED
:
634 case content::DownloadItem::INTERRUPTED
:
635 if (item_
->CanResume())
636 actions
->push_back(DownloadCommands::RESUME
);
638 case content::DownloadItem::COMPLETE
:
639 actions
->push_back(DownloadCommands::SHOW_IN_FOLDER
);
641 case content::DownloadItem::MAX_DOWNLOAD_STATE
:
644 return actions
.Pass();
647 base::string16
DownloadItemNotification::GetTitle() const {
648 base::string16 title_text
;
649 DownloadItemModel
model(item_
);
651 if (item_
->IsDangerous()) {
652 if (model
.MightBeMalicious()) {
653 return l10n_util::GetStringUTF16(
654 IDS_PROMPT_BLOCKED_MALICIOUS_DOWNLOAD_TITLE
);
656 return l10n_util::GetStringUTF16(
657 IDS_CONFIRM_KEEP_DANGEROUS_DOWNLOAD_TITLE
);
661 base::string16 file_name
=
662 item_
->GetFileNameToReportUser().LossyDisplayName();
663 switch (item_
->GetState()) {
664 case content::DownloadItem::IN_PROGRESS
:
665 title_text
= l10n_util::GetStringFUTF16(
666 IDS_DOWNLOAD_STATUS_IN_PROGRESS_TITLE
, file_name
);
668 case content::DownloadItem::COMPLETE
:
669 title_text
= l10n_util::GetStringFUTF16(
670 IDS_DOWNLOAD_STATUS_DOWNLOADED_TITLE
, file_name
);
672 case content::DownloadItem::INTERRUPTED
:
673 title_text
= l10n_util::GetStringFUTF16(
674 IDS_DOWNLOAD_STATUS_DOWNLOAD_FAILED_TITLE
, file_name
);
676 case content::DownloadItem::CANCELLED
:
677 title_text
= l10n_util::GetStringFUTF16(
678 IDS_DOWNLOAD_STATUS_DOWNLOAD_FAILED_TITLE
, file_name
);
680 case content::DownloadItem::MAX_DOWNLOAD_STATE
:
686 base::string16
DownloadItemNotification::GetCommandLabel(
687 DownloadCommands::Command command
) const {
690 case DownloadCommands::OPEN_WHEN_COMPLETE
:
691 if (item_
&& !item_
->IsDone())
692 id
= IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE
;
694 id
= IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE
;
696 case DownloadCommands::PAUSE
:
697 // Only for non menu.
698 id
= IDS_DOWNLOAD_LINK_PAUSE
;
700 case DownloadCommands::RESUME
:
701 // Only for non menu.
702 id
= IDS_DOWNLOAD_LINK_RESUME
;
704 case DownloadCommands::SHOW_IN_FOLDER
:
705 id
= IDS_DOWNLOAD_LINK_SHOW
;
707 case DownloadCommands::DISCARD
:
708 id
= IDS_DISCARD_DOWNLOAD
;
710 case DownloadCommands::KEEP
:
711 id
= IDS_CONFIRM_DOWNLOAD
;
713 case DownloadCommands::CANCEL
:
714 id
= IDS_DOWNLOAD_LINK_CANCEL
;
716 case DownloadCommands::LEARN_MORE_SCANNING
:
717 id
= IDS_DOWNLOAD_LINK_LEARN_MORE_SCANNING
;
719 case DownloadCommands::ALWAYS_OPEN_TYPE
:
720 case DownloadCommands::PLATFORM_OPEN
:
721 case DownloadCommands::LEARN_MORE_INTERRUPTED
:
724 return base::string16();
727 return l10n_util::GetStringUTF16(id
);
730 base::string16
DownloadItemNotification::GetWarningStatusString() const {
731 // Should only be called if IsDangerous().
732 DCHECK(item_
->IsDangerous());
733 base::string16 elided_filename
=
734 item_
->GetFileNameToReportUser().LossyDisplayName();
735 switch (item_
->GetDangerType()) {
736 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL
: {
737 return l10n_util::GetStringUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_URL
);
739 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE
: {
740 if (download_crx_util::IsExtensionDownload(*item_
)) {
741 return l10n_util::GetStringUTF16(
742 IDS_PROMPT_DANGEROUS_DOWNLOAD_EXTENSION
);
744 return l10n_util::GetStringFUTF16(IDS_PROMPT_DANGEROUS_DOWNLOAD
,
748 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT
:
749 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST
: {
750 return l10n_util::GetStringFUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_CONTENT
,
753 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT
: {
754 return l10n_util::GetStringFUTF16(IDS_PROMPT_UNCOMMON_DOWNLOAD_CONTENT
,
757 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED
: {
758 return l10n_util::GetStringFUTF16(IDS_PROMPT_DOWNLOAD_CHANGES_SETTINGS
,
761 case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS
:
762 case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT
:
763 case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED
:
764 case content::DOWNLOAD_DANGER_TYPE_MAX
: {
769 return base::string16();
772 base::string16
DownloadItemNotification::GetInProgressSubStatusString() const {
773 // The download is a CRX (app, extension, theme, ...) and it is being
774 // unpacked and validated.
775 if (item_
->AllDataSaved() &&
776 download_crx_util::IsExtensionDownload(*item_
)) {
777 return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CRX_INSTALL_RUNNING
);
781 if (item_
->IsPaused())
782 return l10n_util::GetStringUTF16(IDS_DOWNLOAD_PROGRESS_PAUSED
);
784 base::TimeDelta time_remaining
;
785 // time_remaining is only known if the download isn't paused.
786 bool time_remaining_known
= (!item_
->IsPaused() &&
787 item_
->TimeRemaining(&time_remaining
));
790 // A download scheduled to be opened when complete.
791 if (item_
->GetOpenWhenComplete()) {
792 // "Opening when complete"
793 if (!time_remaining_known
)
794 return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE
);
796 // "Opening in 10 secs"
797 return l10n_util::GetStringFUTF16(
798 IDS_DOWNLOAD_STATUS_OPEN_IN
,
799 ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_DURATION
,
800 ui::TimeFormat::LENGTH_SHORT
, time_remaining
));
803 // In progress download with known time left: "10 secs left"
804 if (time_remaining_known
) {
805 return ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_REMAINING
,
806 ui::TimeFormat::LENGTH_SHORT
, time_remaining
);
810 if (item_
->GetReceivedBytes() > 0)
811 return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_IN_PROGRESS_SHORT
);
814 return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING
);
817 base::string16
DownloadItemNotification::GetStatusString() const {
818 if (item_
->IsDangerous())
819 return GetWarningStatusString();
821 DownloadItemModel
model(item_
);
822 base::string16 sub_status_text
;
823 switch (item_
->GetState()) {
824 case content::DownloadItem::IN_PROGRESS
:
825 sub_status_text
= GetInProgressSubStatusString();
827 case content::DownloadItem::COMPLETE
:
828 // If the file has been removed: Removed
829 if (item_
->GetFileExternallyRemoved()) {
831 l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_REMOVED
);
833 // Otherwise, no sub status text.
835 case content::DownloadItem::CANCELLED
:
838 l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED
);
840 case content::DownloadItem::INTERRUPTED
: {
841 content::DownloadInterruptReason reason
= item_
->GetLastReason();
842 if (reason
!= content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED
) {
843 // "Failed - <REASON>"
844 base::string16 interrupt_reason
= model
.GetInterruptReasonText();
845 DCHECK(!interrupt_reason
.empty());
846 sub_status_text
= l10n_util::GetStringFUTF16(
847 IDS_DOWNLOAD_STATUS_INTERRUPTED
, interrupt_reason
);
849 // Same as DownloadItem::CANCELLED.
851 l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED
);
859 // The hostname. (E.g.:"example.com" or "127.0.0.1")
860 base::string16 host_name
= base::UTF8ToUTF16(item_
->GetURL().host());
862 // Indication of progress. (E.g.:"100/200 MB" or "100 MB")
863 base::string16 size_ratio
= model
.GetProgressSizesString();
865 if (sub_status_text
.empty()) {
866 // Download completed: "3.4/3.4 MB from example.com"
867 DCHECK_EQ(item_
->GetState(), content::DownloadItem::COMPLETE
);
868 return l10n_util::GetStringFUTF16(
869 IDS_DOWNLOAD_NOTIFICATION_STATUS_COMPLETED
,
870 size_ratio
, host_name
);
873 // Download is not completed: "3.4/3.4 MB from example.com, <SUB STATUS>"
874 return l10n_util::GetStringFUTF16(
875 IDS_DOWNLOAD_NOTIFICATION_STATUS
,
876 size_ratio
, host_name
, sub_status_text
);
879 Browser
* DownloadItemNotification::GetBrowser() const {
880 chrome::ScopedTabbedBrowserDisplayer
browser_displayer(
881 profile(), chrome::GetActiveDesktop());
882 DCHECK(browser_displayer
.browser());
883 return browser_displayer
.browser();
886 Profile
* DownloadItemNotification::profile() const {
887 return Profile::FromBrowserContext(item_
->GetBrowserContext());
890 bool DownloadItemNotification::IsNotificationVisible() const {
891 const std::string
& notification_id
= watcher()->id();
892 const ProfileID profile_id
= NotificationUIManager::GetProfileID(profile());
893 const std::string notification_id_in_message_center
=
894 ProfileNotification::GetProfileNotificationId(notification_id
,
897 message_center::NotificationList::Notifications visible_notifications
=
898 g_browser_process
->message_center()->GetVisibleNotifications();
899 for (const auto& notification
: visible_notifications
) {
900 if (notification
->id() == notification_id_in_message_center
)