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 using base::UserMetricsAction
;
43 const char kDownloadNotificationNotifierId
[] =
44 "chrome://downloads/notification/id-notifier";
46 // Background color of the preview images
47 const SkColor kImageBackgroundColor
= SK_ColorWHITE
;
49 // Maximum size of preview image. If the image exceeds this size, don't show the
51 const int64 kMaxImagePreviewSize
= 10 * 1024 * 1024; // 10 MB
53 std::string
ReadNotificationImage(const base::FilePath
& file_path
) {
54 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
57 bool ret
= base::ReadFileToString(file_path
, &data
);
61 DCHECK_LE(data
.size(), static_cast<size_t>(kMaxImagePreviewSize
));
66 SkBitmap
CropImage(const SkBitmap
& original_bitmap
) {
67 DCHECK_NE(0, original_bitmap
.width());
68 DCHECK_NE(0, original_bitmap
.height());
70 const SkSize container_size
= SkSize::Make(
71 message_center::kNotificationPreferredImageWidth
,
72 message_center::kNotificationPreferredImageHeight
);
73 const float container_aspect_ratio
=
74 static_cast<float>(message_center::kNotificationPreferredImageWidth
) /
75 message_center::kNotificationPreferredImageHeight
;
76 const float image_aspect_ratio
=
77 static_cast<float>(original_bitmap
.width()) / original_bitmap
.height();
80 if (image_aspect_ratio
> container_aspect_ratio
) {
81 float width
= original_bitmap
.height() * container_aspect_ratio
;
82 source_rect
= SkRect::MakeXYWH((original_bitmap
.width() - width
) / 2,
85 original_bitmap
.height());
87 float height
= original_bitmap
.width() / container_aspect_ratio
;
88 source_rect
= SkRect::MakeXYWH(0,
89 (original_bitmap
.height() - height
) / 2,
90 original_bitmap
.width(),
95 SkBitmap container_bitmap
;
96 container_bitmap
.allocN32Pixels(container_size
.width(),
97 container_size
.height());
99 paint
.setFilterQuality(kHigh_SkFilterQuality
);
100 SkCanvas
container_image(container_bitmap
);
101 container_image
.drawColor(kImageBackgroundColor
);
102 container_image
.drawBitmapRect(
103 original_bitmap
, source_rect
, SkRect::MakeSize(container_size
), &paint
);
105 return container_bitmap
;
108 void RecordButtonClickAction(DownloadCommands::Command command
) {
110 case DownloadCommands::SHOW_IN_FOLDER
:
111 content::RecordAction(
112 UserMetricsAction("DownloadNotification.Button_ShowInFolder"));
114 case DownloadCommands::OPEN_WHEN_COMPLETE
:
115 content::RecordAction(
116 UserMetricsAction("DownloadNotification.Button_OpenWhenComplete"));
118 case DownloadCommands::ALWAYS_OPEN_TYPE
:
119 content::RecordAction(
120 UserMetricsAction("DownloadNotification.Button_AlwaysOpenType"));
122 case DownloadCommands::PLATFORM_OPEN
:
123 content::RecordAction(
124 UserMetricsAction("DownloadNotification.Button_PlatformOpen"));
126 case DownloadCommands::CANCEL
:
127 content::RecordAction(
128 UserMetricsAction("DownloadNotification.Button_Cancel"));
130 case DownloadCommands::DISCARD
:
131 content::RecordAction(
132 UserMetricsAction("DownloadNotification.Button_Discard"));
134 case DownloadCommands::KEEP
:
135 content::RecordAction(
136 UserMetricsAction("DownloadNotification.Button_Keep"));
138 case DownloadCommands::LEARN_MORE_SCANNING
:
139 content::RecordAction(
140 UserMetricsAction("DownloadNotification.Button_LearnScanning"));
142 case DownloadCommands::LEARN_MORE_INTERRUPTED
:
143 content::RecordAction(
144 UserMetricsAction("DownloadNotification.Button_LearnInterrupted"));
146 case DownloadCommands::PAUSE
:
147 content::RecordAction(
148 UserMetricsAction("DownloadNotification.Button_Pause"));
150 case DownloadCommands::RESUME
:
151 content::RecordAction(
152 UserMetricsAction("DownloadNotification.Button_Resume"));
157 } // anonymous namespace
159 DownloadItemNotification::DownloadItemNotification(
160 content::DownloadItem
* item
,
161 DownloadNotificationManagerForProfile
* manager
)
163 weak_factory_(this) {
164 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
166 message_center::RichNotificationData data
;
167 // Creates the notification instance. |title| and |body| will be overridden
168 // by UpdateNotificationData() below.
169 notification_
.reset(new Notification(
170 message_center::NOTIFICATION_TYPE_PROGRESS
,
171 base::string16(), // title
172 base::string16(), // body
173 bundle
.GetImageNamed(IDR_DOWNLOAD_NOTIFICATION_DOWNLOADING
),
174 message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT
,
175 kDownloadNotificationNotifierId
),
176 base::string16(), // display_source
177 GURL(kDownloadNotificationOrigin
), // origin_url
178 base::UintToString(item_
->GetId()), // tag
181 notification_
->set_progress(0);
182 notification_
->set_never_timeout(false);
185 DownloadItemNotification::~DownloadItemNotification() {
186 if (image_decode_status_
== IN_PROGRESS
)
187 ImageDecoder::Cancel(this);
190 bool DownloadItemNotification::HasNotificationClickedListener() {
191 if (item_
->IsDangerous()) {
192 // Dangerous notifications don't have a click handler.
198 void DownloadItemNotification::OnNotificationClose() {
201 if (item_
&& item_
->IsDangerous() && !item_
->IsDone()) {
202 // TODO(yoshiki): Add metrics.
203 item_
->Cancel(true /* by_user */);
207 if (image_decode_status_
== IN_PROGRESS
) {
208 image_decode_status_
= NOT_STARTED
;
209 ImageDecoder::Cancel(this);
213 void DownloadItemNotification::OnNotificationClick() {
214 if (item_
->IsDangerous()) {
215 content::RecordAction(
216 UserMetricsAction("DownloadNotification.Click_Dangerous"));
221 switch (item_
->GetState()) {
222 case content::DownloadItem::IN_PROGRESS
:
223 content::RecordAction(
224 UserMetricsAction("DownloadNotification.Click_InProgress"));
225 item_
->SetOpenWhenComplete(!item_
->GetOpenWhenComplete()); // Toggle
227 case content::DownloadItem::CANCELLED
:
228 case content::DownloadItem::INTERRUPTED
:
229 content::RecordAction(
230 UserMetricsAction("DownloadNotification.Click_Stopped"));
231 GetBrowser()->OpenURL(content::OpenURLParams(
232 GURL(chrome::kChromeUIDownloadsURL
), content::Referrer(),
233 NEW_FOREGROUND_TAB
, ui::PAGE_TRANSITION_LINK
,
234 false /* is_renderer_initiated */));
235 CloseNotificationByUser();
237 case content::DownloadItem::COMPLETE
:
238 content::RecordAction(
239 UserMetricsAction("DownloadNotification.Click_Completed"));
240 item_
->OpenDownload();
241 CloseNotificationByUser();
243 case content::DownloadItem::MAX_DOWNLOAD_STATE
:
248 void DownloadItemNotification::OnNotificationButtonClick(int button_index
) {
249 if (button_index
< 0 ||
250 static_cast<size_t>(button_index
) >= button_actions_
->size()) {
256 DownloadCommands::Command command
= button_actions_
->at(button_index
);
257 RecordButtonClickAction(command
);
259 DownloadCommands(item_
).ExecuteCommand(command
);
261 if (command
!= DownloadCommands::PAUSE
&&
262 command
!= DownloadCommands::RESUME
) {
263 CloseNotificationByUser();
266 // Shows the notification again after clicking "Keep" on dangerous download.
267 if (command
== DownloadCommands::KEEP
) {
273 // DownloadItem::Observer methods
274 void DownloadItemNotification::OnDownloadUpdated(content::DownloadItem
* item
) {
275 DCHECK_EQ(item
, item_
);
280 std::string
DownloadItemNotification::GetNotificationId() const {
281 return base::UintToString(item_
->GetId());
284 void DownloadItemNotification::CloseNotificationByNonUser() {
285 const std::string
& notification_id
= watcher()->id();
286 const ProfileID profile_id
= NotificationUIManager::GetProfileID(profile());
288 g_browser_process
->notification_ui_manager()->
289 CancelById(notification_id
, profile_id
);
292 void DownloadItemNotification::CloseNotificationByUser() {
293 // Item may be already removed.
297 const std::string
& notification_id
= watcher()->id();
298 const ProfileID profile_id
= NotificationUIManager::GetProfileID(profile());
299 const std::string notification_id_in_message_center
=
300 ProfileNotification::GetProfileNotificationId(notification_id
,
303 g_browser_process
->notification_ui_manager()->
304 CancelById(notification_id
, profile_id
);
306 // When the message center is visible, |NotificationUIManager::CancelByID()|
307 // delays the close hence the notification is not closed at this time. But
308 // from the viewpoint of UX of MessageCenter, we should close it immediately
309 // because it's by user action. So, we request closing of it directlly to
310 // MessageCenter instance.
311 // Note that: this calling has no side-effect even when the message center
313 g_browser_process
->message_center()->RemoveNotification(
314 notification_id_in_message_center
, true /* by_user */);
317 void DownloadItemNotification::Update() {
318 auto download_state
= item_
->GetState();
320 // When the download is just completed, interrupted or transitions to
321 // dangerous, close the notification once and re-show it immediately so
324 ((item_
->IsDangerous() && !previous_dangerous_state_
) ||
325 (download_state
== content::DownloadItem::COMPLETE
&&
326 previous_download_state_
!= content::DownloadItem::COMPLETE
) ||
327 (download_state
== content::DownloadItem::INTERRUPTED
&&
328 previous_download_state_
!= content::DownloadItem::INTERRUPTED
));
331 UpdateNotificationData(popup
? UPDATE_AND_POPUP
: UPDATE
);
333 if (show_next_
|| popup
) {
334 UpdateNotificationData(ADD
);
340 previous_download_state_
= item_
->GetState();
341 previous_dangerous_state_
= item_
->IsDangerous();
344 void DownloadItemNotification::UpdateNotificationData(
345 NotificationUpdateType type
) {
346 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
348 DownloadItemModel
model(item_
);
349 DownloadCommands
command(item_
);
351 notification_
->set_title(GetTitle());
352 notification_
->set_message(GetStatusString());
354 if (item_
->IsDangerous()) {
355 notification_
->set_type(message_center::NOTIFICATION_TYPE_BASE_FORMAT
);
358 if (model
.MightBeMalicious()) {
359 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_WARNING_BAD
);
360 notification_
->set_priority(message_center::DEFAULT_PRIORITY
);
362 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_WARNING_UNWANTED
);
363 notification_
->set_priority(message_center::HIGH_PRIORITY
);
366 notification_
->set_priority(message_center::DEFAULT_PRIORITY
);
368 bool is_off_the_record
= item_
->GetBrowserContext() &&
369 item_
->GetBrowserContext()->IsOffTheRecord();
371 switch (item_
->GetState()) {
372 case content::DownloadItem::IN_PROGRESS
: {
373 int percent_complete
= item_
->PercentComplete();
374 if (percent_complete
>= 0) {
375 notification_
->set_type(message_center::NOTIFICATION_TYPE_PROGRESS
);
376 notification_
->set_progress(percent_complete
);
378 notification_
->set_type(
379 message_center::NOTIFICATION_TYPE_BASE_FORMAT
);
380 notification_
->set_progress(0);
382 if (is_off_the_record
) {
383 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_INCOGNITO
);
385 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_DOWNLOADING
);
389 case content::DownloadItem::COMPLETE
:
390 DCHECK(item_
->IsDone());
392 notification_
->set_type(message_center::NOTIFICATION_TYPE_BASE_FORMAT
);
394 notification_
->set_progress(100);
396 if (is_off_the_record
) {
397 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_INCOGNITO
);
399 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_DOWNLOADING
);
402 case content::DownloadItem::CANCELLED
:
403 // Confgirms that a download is cancelled by user action.
404 DCHECK(item_
->GetLastReason() ==
405 content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED
||
406 item_
->GetLastReason() ==
407 content::DOWNLOAD_INTERRUPT_REASON_USER_SHUTDOWN
);
409 CloseNotificationByUser();
410 return; // Skips the remaining since the notification has closed.
411 case content::DownloadItem::INTERRUPTED
:
412 // Shows a notifiation as progress type once so the visible content will
413 // be updated. (same as the case of type = COMPLETE)
414 notification_
->set_type(message_center::NOTIFICATION_TYPE_BASE_FORMAT
);
416 notification_
->set_progress(0);
417 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_ERROR
);
419 case content::DownloadItem::MAX_DOWNLOAD_STATE
: // sentinel
424 std::vector
<message_center::ButtonInfo
> notification_actions
;
425 scoped_ptr
<std::vector
<DownloadCommands::Command
>> actions(
426 GetExtraActions().Pass());
428 button_actions_
.reset(new std::vector
<DownloadCommands::Command
>);
429 for (auto it
= actions
->begin(); it
!= actions
->end(); it
++) {
430 button_actions_
->push_back(*it
);
431 message_center::ButtonInfo button_info
=
432 message_center::ButtonInfo(GetCommandLabel(*it
));
433 button_info
.icon
= command
.GetCommandIcon(*it
);
434 notification_actions
.push_back(button_info
);
436 notification_
->set_buttons(notification_actions
);
439 g_browser_process
->notification_ui_manager()->
440 Add(*notification_
, profile());
441 } else if (type
== UPDATE
||
442 // If the notification is already visible as popup or in the
443 // notification center, doesn't pop it up.
444 (type
== UPDATE_AND_POPUP
&& IsNotificationVisible())) {
445 // Shows a notifiation as progress type once so the visible content will be
446 // updated. Only progress-type notification's content will be updated
447 // immediately when the message center is visible.
448 // See the comment in MessageCenterImpl::UpdateNotification() for detail.
449 if (type
== UPDATE_AND_POPUP
&&
450 g_browser_process
->message_center()->IsMessageCenterVisible() &&
451 (item_
->GetState() == content::DownloadItem::COMPLETE
||
452 item_
->GetState() == content::DownloadItem::INTERRUPTED
)) {
453 DCHECK_EQ(notification_
->type(),
454 message_center::NOTIFICATION_TYPE_BASE_FORMAT
);
456 notification_
->set_type(message_center::NOTIFICATION_TYPE_PROGRESS
);
457 g_browser_process
->notification_ui_manager()->
458 Update(*notification_
, profile());
459 notification_
->set_type(message_center::NOTIFICATION_TYPE_BASE_FORMAT
);
462 g_browser_process
->notification_ui_manager()->
463 Update(*notification_
, profile());
464 } else if (type
== UPDATE_AND_POPUP
) {
465 CloseNotificationByNonUser();
466 g_browser_process
->notification_ui_manager()->
467 Add(*notification_
, profile());
472 if (item_
->IsDone() && image_decode_status_
== NOT_STARTED
) {
473 // TODO(yoshiki): Add an UMA to collect statistics of image file sizes.
475 if (item_
->GetReceivedBytes() > kMaxImagePreviewSize
)
478 DCHECK(notification_
->image().IsEmpty());
480 image_decode_status_
= IN_PROGRESS
;
482 bool maybe_image
= false;
483 if (mime_util::IsSupportedImageMimeType(item_
->GetMimeType())) {
487 base::FilePath::StringType extension_with_dot
=
488 item_
->GetTargetFilePath().FinalExtension();
489 if (!extension_with_dot
.empty() &&
490 net::GetWellKnownMimeTypeFromExtension(extension_with_dot
.substr(1),
492 mime_util::IsSupportedImageMimeType(mime
)) {
498 base::FilePath file_path
= item_
->GetFullPath();
499 base::PostTaskAndReplyWithResult(
500 content::BrowserThread::GetBlockingPool(), FROM_HERE
,
501 base::Bind(&ReadNotificationImage
, file_path
),
502 base::Bind(&DownloadItemNotification::OnImageLoaded
,
503 weak_factory_
.GetWeakPtr()));
508 void DownloadItemNotification::OnDownloadRemoved(content::DownloadItem
* item
) {
509 // The given |item| may be already free'd.
510 DCHECK_EQ(item
, item_
);
512 // Removing the notification causes calling |NotificationDelegate::Close()|.
513 if (g_browser_process
->notification_ui_manager()) {
514 g_browser_process
->notification_ui_manager()->CancelById(
515 watcher()->id(), NotificationUIManager::GetProfileID(profile()));
521 void DownloadItemNotification::SetNotificationIcon(int resource_id
) {
522 if (image_resource_id_
== resource_id
)
524 ui::ResourceBundle
& bundle
= ui::ResourceBundle::GetSharedInstance();
525 image_resource_id_
= resource_id
;
526 notification_
->set_icon(bundle
.GetImageNamed(image_resource_id_
));
529 void DownloadItemNotification::OnImageLoaded(const std::string
& image_data
) {
530 if (image_data
.empty())
533 // TODO(yoshiki): Set option to reduce the image size to supress memory usage.
534 ImageDecoder::Start(this, image_data
);
537 void DownloadItemNotification::OnImageDecoded(const SkBitmap
& decoded_bitmap
) {
538 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
540 if (decoded_bitmap
.drawsNothing()) {
541 OnDecodeImageFailed();
545 base::PostTaskAndReplyWithResult(
546 content::BrowserThread::GetBlockingPool(), FROM_HERE
,
547 base::Bind(&CropImage
, decoded_bitmap
),
548 base::Bind(&DownloadItemNotification::OnImageCropped
,
549 weak_factory_
.GetWeakPtr()));
552 void DownloadItemNotification::OnImageCropped(const SkBitmap
& bitmap
) {
553 gfx::Image image
= gfx::Image::CreateFrom1xBitmap(bitmap
);
554 notification_
->set_image(image
);
555 image_decode_status_
= DONE
;
556 UpdateNotificationData(UPDATE
);
559 void DownloadItemNotification::OnDecodeImageFailed() {
560 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
561 DCHECK(notification_
->image().IsEmpty());
563 image_decode_status_
= FAILED
;
564 UpdateNotificationData(UPDATE
);
567 scoped_ptr
<std::vector
<DownloadCommands::Command
>>
568 DownloadItemNotification::GetExtraActions() const {
569 scoped_ptr
<std::vector
<DownloadCommands::Command
>> actions(
570 new std::vector
<DownloadCommands::Command
>());
572 if (item_
->IsDangerous()) {
573 DownloadItemModel
model(item_
);
574 if (model
.MightBeMalicious()) {
575 actions
->push_back(DownloadCommands::LEARN_MORE_SCANNING
);
577 actions
->push_back(DownloadCommands::DISCARD
);
578 actions
->push_back(DownloadCommands::KEEP
);
580 return actions
.Pass();
583 switch (item_
->GetState()) {
584 case content::DownloadItem::IN_PROGRESS
:
585 if (!item_
->IsPaused())
586 actions
->push_back(DownloadCommands::PAUSE
);
588 actions
->push_back(DownloadCommands::RESUME
);
589 actions
->push_back(DownloadCommands::CANCEL
);
591 case content::DownloadItem::CANCELLED
:
592 case content::DownloadItem::INTERRUPTED
:
593 if (item_
->CanResume())
594 actions
->push_back(DownloadCommands::RESUME
);
596 case content::DownloadItem::COMPLETE
:
597 actions
->push_back(DownloadCommands::SHOW_IN_FOLDER
);
599 case content::DownloadItem::MAX_DOWNLOAD_STATE
:
602 return actions
.Pass();
605 base::string16
DownloadItemNotification::GetTitle() const {
606 base::string16 title_text
;
607 DownloadItemModel
model(item_
);
609 if (item_
->IsDangerous()) {
610 if (model
.MightBeMalicious()) {
611 return l10n_util::GetStringUTF16(
612 IDS_PROMPT_BLOCKED_MALICIOUS_DOWNLOAD_TITLE
);
614 return l10n_util::GetStringUTF16(
615 IDS_CONFIRM_KEEP_DANGEROUS_DOWNLOAD_TITLE
);
619 base::string16 file_name
=
620 item_
->GetFileNameToReportUser().LossyDisplayName();
621 switch (item_
->GetState()) {
622 case content::DownloadItem::IN_PROGRESS
:
623 title_text
= l10n_util::GetStringFUTF16(
624 IDS_DOWNLOAD_STATUS_IN_PROGRESS_TITLE
, file_name
);
626 case content::DownloadItem::COMPLETE
:
627 title_text
= l10n_util::GetStringFUTF16(
628 IDS_DOWNLOAD_STATUS_DOWNLOADED_TITLE
, file_name
);
630 case content::DownloadItem::INTERRUPTED
:
631 title_text
= l10n_util::GetStringFUTF16(
632 IDS_DOWNLOAD_STATUS_DOWNLOAD_FAILED_TITLE
, file_name
);
634 case content::DownloadItem::CANCELLED
:
635 title_text
= l10n_util::GetStringFUTF16(
636 IDS_DOWNLOAD_STATUS_DOWNLOAD_FAILED_TITLE
, file_name
);
638 case content::DownloadItem::MAX_DOWNLOAD_STATE
:
644 base::string16
DownloadItemNotification::GetCommandLabel(
645 DownloadCommands::Command command
) const {
648 case DownloadCommands::OPEN_WHEN_COMPLETE
:
649 if (item_
&& !item_
->IsDone())
650 id
= IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE
;
652 id
= IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE
;
654 case DownloadCommands::PAUSE
:
655 // Only for non menu.
656 id
= IDS_DOWNLOAD_LINK_PAUSE
;
658 case DownloadCommands::RESUME
:
659 // Only for non menu.
660 id
= IDS_DOWNLOAD_LINK_RESUME
;
662 case DownloadCommands::SHOW_IN_FOLDER
:
663 id
= IDS_DOWNLOAD_LINK_SHOW
;
665 case DownloadCommands::DISCARD
:
666 id
= IDS_DISCARD_DOWNLOAD
;
668 case DownloadCommands::KEEP
:
669 id
= IDS_CONFIRM_DOWNLOAD
;
671 case DownloadCommands::CANCEL
:
672 id
= IDS_DOWNLOAD_LINK_CANCEL
;
674 case DownloadCommands::LEARN_MORE_SCANNING
:
675 id
= IDS_DOWNLOAD_LINK_LEARN_MORE_SCANNING
;
677 case DownloadCommands::ALWAYS_OPEN_TYPE
:
678 case DownloadCommands::PLATFORM_OPEN
:
679 case DownloadCommands::LEARN_MORE_INTERRUPTED
:
682 return base::string16();
685 return l10n_util::GetStringUTF16(id
);
688 base::string16
DownloadItemNotification::GetWarningStatusString() const {
689 // Should only be called if IsDangerous().
690 DCHECK(item_
->IsDangerous());
691 base::string16 elided_filename
=
692 item_
->GetFileNameToReportUser().LossyDisplayName();
693 switch (item_
->GetDangerType()) {
694 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL
: {
695 return l10n_util::GetStringUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_URL
);
697 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE
: {
698 if (download_crx_util::IsExtensionDownload(*item_
)) {
699 return l10n_util::GetStringUTF16(
700 IDS_PROMPT_DANGEROUS_DOWNLOAD_EXTENSION
);
702 return l10n_util::GetStringFUTF16(IDS_PROMPT_DANGEROUS_DOWNLOAD
,
706 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT
:
707 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST
: {
708 return l10n_util::GetStringFUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_CONTENT
,
711 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT
: {
712 return l10n_util::GetStringFUTF16(IDS_PROMPT_UNCOMMON_DOWNLOAD_CONTENT
,
715 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED
: {
716 return l10n_util::GetStringFUTF16(IDS_PROMPT_DOWNLOAD_CHANGES_SETTINGS
,
719 case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS
:
720 case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT
:
721 case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED
:
722 case content::DOWNLOAD_DANGER_TYPE_MAX
: {
727 return base::string16();
730 base::string16
DownloadItemNotification::GetInProgressSubStatusString() const {
731 // The download is a CRX (app, extension, theme, ...) and it is being
732 // unpacked and validated.
733 if (item_
->AllDataSaved() &&
734 download_crx_util::IsExtensionDownload(*item_
)) {
735 return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CRX_INSTALL_RUNNING
);
739 if (item_
->IsPaused())
740 return l10n_util::GetStringUTF16(IDS_DOWNLOAD_PROGRESS_PAUSED
);
742 base::TimeDelta time_remaining
;
743 // time_remaining is only known if the download isn't paused.
744 bool time_remaining_known
= (!item_
->IsPaused() &&
745 item_
->TimeRemaining(&time_remaining
));
748 // A download scheduled to be opened when complete.
749 if (item_
->GetOpenWhenComplete()) {
750 // "Opening when complete"
751 if (!time_remaining_known
)
752 return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE
);
754 // "Opening in 10 secs"
755 return l10n_util::GetStringFUTF16(
756 IDS_DOWNLOAD_STATUS_OPEN_IN
,
757 ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_DURATION
,
758 ui::TimeFormat::LENGTH_SHORT
, time_remaining
));
761 // In progress download with known time left: "10 secs left"
762 if (time_remaining_known
) {
763 return ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_REMAINING
,
764 ui::TimeFormat::LENGTH_SHORT
, time_remaining
);
768 if (item_
->GetReceivedBytes() > 0)
769 return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_IN_PROGRESS_SHORT
);
772 return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_STARTING
);
775 base::string16
DownloadItemNotification::GetStatusString() const {
776 if (item_
->IsDangerous())
777 return GetWarningStatusString();
779 DownloadItemModel
model(item_
);
780 base::string16 sub_status_text
;
781 switch (item_
->GetState()) {
782 case content::DownloadItem::IN_PROGRESS
:
783 sub_status_text
= GetInProgressSubStatusString();
785 case content::DownloadItem::COMPLETE
:
786 // If the file has been removed: Removed
787 if (item_
->GetFileExternallyRemoved()) {
789 l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_REMOVED
);
791 // Otherwise, no sub status text.
793 case content::DownloadItem::CANCELLED
:
796 l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED
);
798 case content::DownloadItem::INTERRUPTED
: {
799 content::DownloadInterruptReason reason
= item_
->GetLastReason();
800 if (reason
!= content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED
) {
801 // "Failed - <REASON>"
802 base::string16 interrupt_reason
= model
.GetInterruptReasonText();
803 DCHECK(!interrupt_reason
.empty());
804 sub_status_text
= l10n_util::GetStringFUTF16(
805 IDS_DOWNLOAD_STATUS_INTERRUPTED
, interrupt_reason
);
807 // Same as DownloadItem::CANCELLED.
809 l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED
);
817 // The hostname. (E.g.:"example.com" or "127.0.0.1")
818 base::string16 host_name
= base::UTF8ToUTF16(item_
->GetURL().host());
820 // Indication of progress. (E.g.:"100/200 MB" or "100 MB")
821 base::string16 size_ratio
= model
.GetProgressSizesString();
823 if (sub_status_text
.empty()) {
824 // Download completed: "3.4/3.4 MB from example.com"
825 DCHECK_EQ(item_
->GetState(), content::DownloadItem::COMPLETE
);
826 return l10n_util::GetStringFUTF16(
827 IDS_DOWNLOAD_NOTIFICATION_STATUS_COMPLETED
,
828 size_ratio
, host_name
);
831 // Download is not completed: "3.4/3.4 MB from example.com, <SUB STATUS>"
832 return l10n_util::GetStringFUTF16(
833 IDS_DOWNLOAD_NOTIFICATION_STATUS
,
834 size_ratio
, host_name
, sub_status_text
);
837 Browser
* DownloadItemNotification::GetBrowser() const {
838 chrome::ScopedTabbedBrowserDisplayer
browser_displayer(
839 profile(), chrome::GetActiveDesktop());
840 DCHECK(browser_displayer
.browser());
841 return browser_displayer
.browser();
844 Profile
* DownloadItemNotification::profile() const {
845 return Profile::FromBrowserContext(item_
->GetBrowserContext());
848 bool DownloadItemNotification::IsNotificationVisible() const {
849 const std::string
& notification_id
= watcher()->id();
850 const ProfileID profile_id
= NotificationUIManager::GetProfileID(profile());
851 const std::string notification_id_in_message_center
=
852 ProfileNotification::GetProfileNotificationId(notification_id
,
855 message_center::NotificationList::Notifications visible_notifications
=
856 g_browser_process
->message_center()->GetVisibleNotifications();
857 for (const auto& notification
: visible_notifications
) {
858 if (notification
->id() == notification_id_in_message_center
)