Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / chrome / browser / download / notification / download_item_notification.cc
blobcdd22caae5c5ce7a38dad1f6b7d740e97e22ceac
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;
41 namespace {
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
50 // preview image.
51 const int64 kMaxImagePreviewSize = 10 * 1024 * 1024; // 10 MB
53 std::string ReadNotificationImage(const base::FilePath& file_path) {
54 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
56 std::string data;
57 bool ret = base::ReadFileToString(file_path, &data);
58 if (!ret)
59 return std::string();
61 DCHECK_LE(data.size(), static_cast<size_t>(kMaxImagePreviewSize));
63 return data;
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();
79 SkRect source_rect;
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,
84 width,
85 original_bitmap.height());
86 } else {
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(),
91 height);
95 SkBitmap container_bitmap;
96 container_bitmap.allocN32Pixels(container_size.width(),
97 container_size.height());
98 SkPaint paint;
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) {
109 switch (command) {
110 case DownloadCommands::SHOW_IN_FOLDER:
111 content::RecordAction(
112 UserMetricsAction("DownloadNotification.Button_ShowInFolder"));
113 break;
114 case DownloadCommands::OPEN_WHEN_COMPLETE:
115 content::RecordAction(
116 UserMetricsAction("DownloadNotification.Button_OpenWhenComplete"));
117 break;
118 case DownloadCommands::ALWAYS_OPEN_TYPE:
119 content::RecordAction(
120 UserMetricsAction("DownloadNotification.Button_AlwaysOpenType"));
121 break;
122 case DownloadCommands::PLATFORM_OPEN:
123 content::RecordAction(
124 UserMetricsAction("DownloadNotification.Button_PlatformOpen"));
125 break;
126 case DownloadCommands::CANCEL:
127 content::RecordAction(
128 UserMetricsAction("DownloadNotification.Button_Cancel"));
129 break;
130 case DownloadCommands::DISCARD:
131 content::RecordAction(
132 UserMetricsAction("DownloadNotification.Button_Discard"));
133 break;
134 case DownloadCommands::KEEP:
135 content::RecordAction(
136 UserMetricsAction("DownloadNotification.Button_Keep"));
137 break;
138 case DownloadCommands::LEARN_MORE_SCANNING:
139 content::RecordAction(
140 UserMetricsAction("DownloadNotification.Button_LearnScanning"));
141 break;
142 case DownloadCommands::LEARN_MORE_INTERRUPTED:
143 content::RecordAction(
144 UserMetricsAction("DownloadNotification.Button_LearnInterrupted"));
145 break;
146 case DownloadCommands::PAUSE:
147 content::RecordAction(
148 UserMetricsAction("DownloadNotification.Button_Pause"));
149 break;
150 case DownloadCommands::RESUME:
151 content::RecordAction(
152 UserMetricsAction("DownloadNotification.Button_Resume"));
153 break;
157 } // anonymous namespace
159 DownloadItemNotification::DownloadItemNotification(
160 content::DownloadItem* item,
161 DownloadNotificationManagerForProfile* manager)
162 : item_(item),
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
179 data, watcher()));
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.
193 return false;
195 return true;
198 void DownloadItemNotification::OnNotificationClose() {
199 visible_ = false;
201 if (item_ && item_->IsDangerous() && !item_->IsDone()) {
202 // TODO(yoshiki): Add metrics.
203 item_->Cancel(true /* by_user */);
204 return;
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"));
217 // Do nothing.
218 return;
221 switch (item_->GetState()) {
222 case content::DownloadItem::IN_PROGRESS:
223 content::RecordAction(
224 UserMetricsAction("DownloadNotification.Click_InProgress"));
225 item_->SetOpenWhenComplete(!item_->GetOpenWhenComplete()); // Toggle
226 break;
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();
236 break;
237 case content::DownloadItem::COMPLETE:
238 content::RecordAction(
239 UserMetricsAction("DownloadNotification.Click_Completed"));
240 item_->OpenDownload();
241 CloseNotificationByUser();
242 break;
243 case content::DownloadItem::MAX_DOWNLOAD_STATE:
244 NOTREACHED();
248 void DownloadItemNotification::OnNotificationButtonClick(int button_index) {
249 if (button_index < 0 ||
250 static_cast<size_t>(button_index) >= button_actions_->size()) {
251 // Out of boundary.
252 NOTREACHED();
253 return;
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) {
268 show_next_ = true;
269 Update();
273 // DownloadItem::Observer methods
274 void DownloadItemNotification::OnDownloadUpdated(content::DownloadItem* item) {
275 DCHECK_EQ(item, item_);
277 Update();
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.
294 if (!item_)
295 return;
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,
301 profile_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
312 // is not opened.
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
322 // it'll pop up.
323 bool popup =
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));
330 if (visible_) {
331 UpdateNotificationData(popup ? UPDATE_AND_POPUP : UPDATE);
332 } else {
333 if (show_next_ || popup) {
334 UpdateNotificationData(ADD);
335 visible_ = true;
339 show_next_ = false;
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);
357 // Show icon.
358 if (model.MightBeMalicious()) {
359 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_WARNING_BAD);
360 notification_->set_priority(message_center::DEFAULT_PRIORITY);
361 } else {
362 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_WARNING_UNWANTED);
363 notification_->set_priority(message_center::HIGH_PRIORITY);
365 } else {
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);
377 } else {
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);
384 } else {
385 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_DOWNLOADING);
387 break;
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);
398 } else {
399 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_DOWNLOADING);
401 break;
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);
418 break;
419 case content::DownloadItem::MAX_DOWNLOAD_STATE: // sentinel
420 NOTREACHED();
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);
438 if (type == ADD) {
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());
468 } else {
469 NOTREACHED();
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)
476 return;
478 DCHECK(notification_->image().IsEmpty());
480 image_decode_status_ = IN_PROGRESS;
482 bool maybe_image = false;
483 if (mime_util::IsSupportedImageMimeType(item_->GetMimeType())) {
484 maybe_image = true;
485 } else {
486 std::string mime;
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),
491 &mime) &&
492 mime_util::IsSupportedImageMimeType(mime)) {
493 maybe_image = true;
497 if (maybe_image) {
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()));
518 item_ = nullptr;
521 void DownloadItemNotification::SetNotificationIcon(int resource_id) {
522 if (image_resource_id_ == resource_id)
523 return;
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())
531 return;
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();
542 return;
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);
576 } else {
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);
587 else
588 actions->push_back(DownloadCommands::RESUME);
589 actions->push_back(DownloadCommands::CANCEL);
590 break;
591 case content::DownloadItem::CANCELLED:
592 case content::DownloadItem::INTERRUPTED:
593 if (item_->CanResume())
594 actions->push_back(DownloadCommands::RESUME);
595 break;
596 case content::DownloadItem::COMPLETE:
597 actions->push_back(DownloadCommands::SHOW_IN_FOLDER);
598 break;
599 case content::DownloadItem::MAX_DOWNLOAD_STATE:
600 NOTREACHED();
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);
613 } else {
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);
625 break;
626 case content::DownloadItem::COMPLETE:
627 title_text = l10n_util::GetStringFUTF16(
628 IDS_DOWNLOAD_STATUS_DOWNLOADED_TITLE, file_name);
629 break;
630 case content::DownloadItem::INTERRUPTED:
631 title_text = l10n_util::GetStringFUTF16(
632 IDS_DOWNLOAD_STATUS_DOWNLOAD_FAILED_TITLE, file_name);
633 break;
634 case content::DownloadItem::CANCELLED:
635 title_text = l10n_util::GetStringFUTF16(
636 IDS_DOWNLOAD_STATUS_DOWNLOAD_FAILED_TITLE, file_name);
637 break;
638 case content::DownloadItem::MAX_DOWNLOAD_STATE:
639 NOTREACHED();
641 return title_text;
644 base::string16 DownloadItemNotification::GetCommandLabel(
645 DownloadCommands::Command command) const {
646 int id = -1;
647 switch (command) {
648 case DownloadCommands::OPEN_WHEN_COMPLETE:
649 if (item_ && !item_->IsDone())
650 id = IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE;
651 else
652 id = IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE;
653 break;
654 case DownloadCommands::PAUSE:
655 // Only for non menu.
656 id = IDS_DOWNLOAD_LINK_PAUSE;
657 break;
658 case DownloadCommands::RESUME:
659 // Only for non menu.
660 id = IDS_DOWNLOAD_LINK_RESUME;
661 break;
662 case DownloadCommands::SHOW_IN_FOLDER:
663 id = IDS_DOWNLOAD_LINK_SHOW;
664 break;
665 case DownloadCommands::DISCARD:
666 id = IDS_DISCARD_DOWNLOAD;
667 break;
668 case DownloadCommands::KEEP:
669 id = IDS_CONFIRM_DOWNLOAD;
670 break;
671 case DownloadCommands::CANCEL:
672 id = IDS_DOWNLOAD_LINK_CANCEL;
673 break;
674 case DownloadCommands::LEARN_MORE_SCANNING:
675 id = IDS_DOWNLOAD_LINK_LEARN_MORE_SCANNING;
676 break;
677 case DownloadCommands::ALWAYS_OPEN_TYPE:
678 case DownloadCommands::PLATFORM_OPEN:
679 case DownloadCommands::LEARN_MORE_INTERRUPTED:
680 // Only for menu.
681 NOTREACHED();
682 return base::string16();
684 CHECK(id != -1);
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);
701 } else {
702 return l10n_util::GetStringFUTF16(IDS_PROMPT_DANGEROUS_DOWNLOAD,
703 elided_filename);
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,
709 elided_filename);
711 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: {
712 return l10n_util::GetStringFUTF16(IDS_PROMPT_UNCOMMON_DOWNLOAD_CONTENT,
713 elided_filename);
715 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: {
716 return l10n_util::GetStringFUTF16(IDS_PROMPT_DOWNLOAD_CHANGES_SETTINGS,
717 elided_filename);
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: {
723 break;
726 NOTREACHED();
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);
738 // "Paused"
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);
767 // "In progress"
768 if (item_->GetReceivedBytes() > 0)
769 return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_IN_PROGRESS_SHORT);
771 // "Starting..."
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();
784 break;
785 case content::DownloadItem::COMPLETE:
786 // If the file has been removed: Removed
787 if (item_->GetFileExternallyRemoved()) {
788 sub_status_text =
789 l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_REMOVED);
791 // Otherwise, no sub status text.
792 break;
793 case content::DownloadItem::CANCELLED:
794 // "Cancelled"
795 sub_status_text =
796 l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED);
797 break;
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);
806 } else {
807 // Same as DownloadItem::CANCELLED.
808 sub_status_text =
809 l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED);
811 break;
813 default:
814 NOTREACHED();
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,
853 profile_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)
859 return true;
861 return false;