Roll src/third_party/WebKit eac3800:0237a66 (svn 202606:202607)
[chromium-blink-merge.git] / chrome / browser / download / notification / download_item_notification.cc
blob540bcafef4c5b0e5a202eadc7958e1e6d1767c12
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"
43 #endif
45 using base::UserMetricsAction;
47 namespace {
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
56 // preview image.
57 const int64 kMaxImagePreviewSize = 10 * 1024 * 1024; // 10 MB
59 std::string ReadNotificationImage(const base::FilePath& file_path) {
60 DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
62 std::string data;
63 bool ret = base::ReadFileToString(file_path, &data);
64 if (!ret)
65 return std::string();
67 DCHECK_LE(data.size(), static_cast<size_t>(kMaxImagePreviewSize));
69 return data;
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();
85 SkRect source_rect;
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,
90 width,
91 original_bitmap.height());
92 } else {
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(),
97 height);
101 SkBitmap container_bitmap;
102 container_bitmap.allocN32Pixels(container_size.width(),
103 container_size.height());
104 SkPaint paint;
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) {
115 switch (command) {
116 case DownloadCommands::SHOW_IN_FOLDER:
117 content::RecordAction(
118 UserMetricsAction("DownloadNotification.Button_ShowInFolder"));
119 break;
120 case DownloadCommands::OPEN_WHEN_COMPLETE:
121 content::RecordAction(
122 UserMetricsAction("DownloadNotification.Button_OpenWhenComplete"));
123 break;
124 case DownloadCommands::ALWAYS_OPEN_TYPE:
125 content::RecordAction(
126 UserMetricsAction("DownloadNotification.Button_AlwaysOpenType"));
127 break;
128 case DownloadCommands::PLATFORM_OPEN:
129 content::RecordAction(
130 UserMetricsAction("DownloadNotification.Button_PlatformOpen"));
131 break;
132 case DownloadCommands::CANCEL:
133 content::RecordAction(
134 UserMetricsAction("DownloadNotification.Button_Cancel"));
135 break;
136 case DownloadCommands::DISCARD:
137 content::RecordAction(
138 UserMetricsAction("DownloadNotification.Button_Discard"));
139 break;
140 case DownloadCommands::KEEP:
141 content::RecordAction(
142 UserMetricsAction("DownloadNotification.Button_Keep"));
143 break;
144 case DownloadCommands::LEARN_MORE_SCANNING:
145 content::RecordAction(
146 UserMetricsAction("DownloadNotification.Button_LearnScanning"));
147 break;
148 case DownloadCommands::LEARN_MORE_INTERRUPTED:
149 content::RecordAction(
150 UserMetricsAction("DownloadNotification.Button_LearnInterrupted"));
151 break;
152 case DownloadCommands::PAUSE:
153 content::RecordAction(
154 UserMetricsAction("DownloadNotification.Button_Pause"));
155 break;
156 case DownloadCommands::RESUME:
157 content::RecordAction(
158 UserMetricsAction("DownloadNotification.Button_Resume"));
159 break;
163 #if !defined(OS_MACOSX)
164 gfx::Image GetVectorIcon(gfx::VectorIconId id, ui::NativeTheme::ColorId color) {
165 SkColor icon_color;
166 ui::CommonThemeGetSystemColor(color, &icon_color);
167 return gfx::Image(gfx::CreateVectorIcon(id, 40, icon_color));
169 #endif
171 } // anonymous namespace
173 DownloadItemNotification::DownloadItemNotification(
174 content::DownloadItem* item,
175 DownloadNotificationManagerForProfile* manager)
176 : item_(item),
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.
205 return false;
207 return true;
210 void DownloadItemNotification::OnNotificationClose() {
211 visible_ = false;
213 if (item_ && item_->IsDangerous() && !item_->IsDone()) {
214 // TODO(yoshiki): Add metrics.
215 item_->Cancel(true /* by_user */);
216 return;
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"));
229 // Do nothing.
230 return;
233 switch (item_->GetState()) {
234 case content::DownloadItem::IN_PROGRESS:
235 content::RecordAction(
236 UserMetricsAction("DownloadNotification.Click_InProgress"));
237 item_->SetOpenWhenComplete(!item_->GetOpenWhenComplete()); // Toggle
238 break;
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();
248 break;
249 case content::DownloadItem::COMPLETE:
250 content::RecordAction(
251 UserMetricsAction("DownloadNotification.Click_Completed"));
252 item_->OpenDownload();
253 CloseNotificationByUser();
254 break;
255 case content::DownloadItem::MAX_DOWNLOAD_STATE:
256 NOTREACHED();
260 void DownloadItemNotification::OnNotificationButtonClick(int button_index) {
261 if (button_index < 0 ||
262 static_cast<size_t>(button_index) >= button_actions_->size()) {
263 // Out of boundary.
264 NOTREACHED();
265 return;
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) {
280 show_next_ = true;
281 Update();
285 // DownloadItem::Observer methods
286 void DownloadItemNotification::OnDownloadUpdated(content::DownloadItem* item) {
287 DCHECK_EQ(item, item_);
289 Update();
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.
306 if (!item_)
307 return;
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,
313 profile_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
324 // is not opened.
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
334 // it'll pop up.
335 bool popup =
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));
342 if (visible_) {
343 UpdateNotificationData(popup ? UPDATE_AND_POPUP : UPDATE);
344 } else {
345 if (show_next_ || popup) {
346 UpdateNotificationData(ADD);
347 visible_ = true;
351 show_next_ = false;
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);
369 // Show icon.
370 if (model.MightBeMalicious()) {
371 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_WARNING_BAD);
372 notification_->set_priority(message_center::DEFAULT_PRIORITY);
373 } else {
374 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_WARNING_UNWANTED);
375 notification_->set_priority(message_center::HIGH_PRIORITY);
377 } else {
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);
386 } else {
387 notification_->set_type(
388 message_center::NOTIFICATION_TYPE_BASE_FORMAT);
389 notification_->set_progress(0);
391 UpdateNotificationIcon();
392 break;
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();
399 break;
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();
415 break;
416 case content::DownloadItem::MAX_DOWNLOAD_STATE: // sentinel
417 NOTREACHED();
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);
435 if (type == ADD) {
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());
465 } else {
466 NOTREACHED();
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)
473 return;
475 DCHECK(notification_->image().IsEmpty());
477 image_decode_status_ = IN_PROGRESS;
479 bool maybe_image = false;
480 if (mime_util::IsSupportedImageMimeType(item_->GetMimeType())) {
481 maybe_image = true;
482 } else {
483 std::string mime;
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),
488 &mime) &&
489 mime_util::IsSupportedImageMimeType(mime)) {
490 maybe_image = true;
494 if (maybe_image) {
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);
514 } else {
515 #if defined(OS_MACOSX)
516 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_DOWNLOADING);
517 #else
518 SetNotificationVectorIcon(gfx::VectorIconId::FILE_DOWNLOAD,
519 ui::NativeTheme::kColorId_GoogleBlue);
520 #endif
522 break;
524 case content::DownloadItem::INTERRUPTED:
525 // TODO(estade): vectorize.
526 SetNotificationIcon(IDR_DOWNLOAD_NOTIFICATION_ERROR);
527 break;
529 case content::DownloadItem::CANCELLED:
530 break;
532 case content::DownloadItem::MAX_DOWNLOAD_STATE:
533 NOTREACHED();
534 break;
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()));
548 item_ = nullptr;
551 void DownloadItemNotification::SetNotificationIcon(int resource_id) {
552 if (image_resource_id_ == resource_id)
553 return;
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))
564 return;
565 vector_icon_params_ = std::make_pair(id, color);
566 image_resource_id_ = 0;
567 notification_->set_icon(GetVectorIcon(id, color));
569 #endif
571 void DownloadItemNotification::OnImageLoaded(const std::string& image_data) {
572 if (image_data.empty())
573 return;
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();
584 return;
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);
618 } else {
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);
629 else
630 actions->push_back(DownloadCommands::RESUME);
631 actions->push_back(DownloadCommands::CANCEL);
632 break;
633 case content::DownloadItem::CANCELLED:
634 case content::DownloadItem::INTERRUPTED:
635 if (item_->CanResume())
636 actions->push_back(DownloadCommands::RESUME);
637 break;
638 case content::DownloadItem::COMPLETE:
639 actions->push_back(DownloadCommands::SHOW_IN_FOLDER);
640 break;
641 case content::DownloadItem::MAX_DOWNLOAD_STATE:
642 NOTREACHED();
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);
655 } else {
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);
667 break;
668 case content::DownloadItem::COMPLETE:
669 title_text = l10n_util::GetStringFUTF16(
670 IDS_DOWNLOAD_STATUS_DOWNLOADED_TITLE, file_name);
671 break;
672 case content::DownloadItem::INTERRUPTED:
673 title_text = l10n_util::GetStringFUTF16(
674 IDS_DOWNLOAD_STATUS_DOWNLOAD_FAILED_TITLE, file_name);
675 break;
676 case content::DownloadItem::CANCELLED:
677 title_text = l10n_util::GetStringFUTF16(
678 IDS_DOWNLOAD_STATUS_DOWNLOAD_FAILED_TITLE, file_name);
679 break;
680 case content::DownloadItem::MAX_DOWNLOAD_STATE:
681 NOTREACHED();
683 return title_text;
686 base::string16 DownloadItemNotification::GetCommandLabel(
687 DownloadCommands::Command command) const {
688 int id = -1;
689 switch (command) {
690 case DownloadCommands::OPEN_WHEN_COMPLETE:
691 if (item_ && !item_->IsDone())
692 id = IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE;
693 else
694 id = IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE;
695 break;
696 case DownloadCommands::PAUSE:
697 // Only for non menu.
698 id = IDS_DOWNLOAD_LINK_PAUSE;
699 break;
700 case DownloadCommands::RESUME:
701 // Only for non menu.
702 id = IDS_DOWNLOAD_LINK_RESUME;
703 break;
704 case DownloadCommands::SHOW_IN_FOLDER:
705 id = IDS_DOWNLOAD_LINK_SHOW;
706 break;
707 case DownloadCommands::DISCARD:
708 id = IDS_DISCARD_DOWNLOAD;
709 break;
710 case DownloadCommands::KEEP:
711 id = IDS_CONFIRM_DOWNLOAD;
712 break;
713 case DownloadCommands::CANCEL:
714 id = IDS_DOWNLOAD_LINK_CANCEL;
715 break;
716 case DownloadCommands::LEARN_MORE_SCANNING:
717 id = IDS_DOWNLOAD_LINK_LEARN_MORE_SCANNING;
718 break;
719 case DownloadCommands::ALWAYS_OPEN_TYPE:
720 case DownloadCommands::PLATFORM_OPEN:
721 case DownloadCommands::LEARN_MORE_INTERRUPTED:
722 // Only for menu.
723 NOTREACHED();
724 return base::string16();
726 CHECK(id != -1);
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);
743 } else {
744 return l10n_util::GetStringFUTF16(IDS_PROMPT_DANGEROUS_DOWNLOAD,
745 elided_filename);
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,
751 elided_filename);
753 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: {
754 return l10n_util::GetStringFUTF16(IDS_PROMPT_UNCOMMON_DOWNLOAD_CONTENT,
755 elided_filename);
757 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: {
758 return l10n_util::GetStringFUTF16(IDS_PROMPT_DOWNLOAD_CHANGES_SETTINGS,
759 elided_filename);
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: {
765 break;
768 NOTREACHED();
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);
780 // "Paused"
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);
809 // "In progress"
810 if (item_->GetReceivedBytes() > 0)
811 return l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_IN_PROGRESS_SHORT);
813 // "Starting..."
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();
826 break;
827 case content::DownloadItem::COMPLETE:
828 // If the file has been removed: Removed
829 if (item_->GetFileExternallyRemoved()) {
830 sub_status_text =
831 l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_REMOVED);
833 // Otherwise, no sub status text.
834 break;
835 case content::DownloadItem::CANCELLED:
836 // "Cancelled"
837 sub_status_text =
838 l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED);
839 break;
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);
848 } else {
849 // Same as DownloadItem::CANCELLED.
850 sub_status_text =
851 l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CANCELLED);
853 break;
855 default:
856 NOTREACHED();
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,
895 profile_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)
901 return true;
903 return false;