Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3173)

Unified Diff: chrome/browser/download/notification/download_notification_item.cc

Issue 852043002: Initial Implementation of Download Notification (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Addressed comments & added tests. Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/download/notification/download_notification_item.cc
diff --git a/chrome/browser/download/notification/download_notification_item.cc b/chrome/browser/download/notification/download_notification_item.cc
new file mode 100644
index 0000000000000000000000000000000000000000..358d036eefcdb3af121d17f009b08d2811d0b10d
--- /dev/null
+++ b/chrome/browser/download/notification/download_notification_item.cc
@@ -0,0 +1,429 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/download/notification/download_notification_item.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "chrome/browser/download/download_crx_util.h"
+#include "chrome/browser/download/download_item_model.h"
+#include "chrome/grit/chromium_strings.h"
+#include "chrome/grit/generated_resources.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/download_item.h"
+#include "content/public/browser/web_contents.h"
+#include "grit/theme_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/notification.h"
+#include "ui/message_center/notification_delegate.h"
+
+using message_center::Notification;
+
+namespace {
+
+const char kDownloadNotificationNotifierId[] =
+ "chrome://settings/display/notification/id-notifier";
+const char kDownloadNotificationIdBase[] =
+ "chrome://settings/display/notification/id-";
+
+} // anonymous namespace
+
+DownloadNotificationItem::DownloadNotificationItem(
+ content::DownloadItem* item, Delegate* delegate)
+ : openable_(false),
+ downloading_(false),
+ popup_closing_(false),
+ image_resource_id_(0),
+ item_(item),
+ delegate_(delegate) {
+ item->AddObserver(this);
+
+ message_center_ = message_center::MessageCenter::Get();
+ message_center_->AddObserver(this);
+
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+
+ const base::string16 timeout_message =
+ l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CRX_INSTALL_RUNNING);
+ const base::string16 message =
+ l10n_util::GetStringUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_URL);
+
+ std::string id(kDownloadNotificationIdBase);
+ id += base::UintToString(item_->GetId());
+
+ message_center::RichNotificationData data;
+ notification_.reset(new Notification(
+ message_center::NOTIFICATION_TYPE_PROGRESS,
+ id,
+ message,
+ timeout_message,
+ bundle.GetImageNamed(IDR_DOWNLOAD_NOTIFICATION_DOWNLOADING),
+ base::string16() /* display_source */,
+ message_center::NotifierId(
+ message_center::NotifierId::SYSTEM_COMPONENT,
+ kDownloadNotificationNotifierId),
+ data,
+ this));
+
+ notification_->set_progress(0);
+ notification_->set_never_timeout(false);
+
+ SetNotificationData();
+
+ scoped_ptr<Notification> notification(new Notification(*notification_));
+ message_center_->AddNotification(notification.Pass());
+}
+
+DownloadNotificationItem::~DownloadNotificationItem() {
+ message_center_->RemoveObserver(this);
+}
+
+void DownloadNotificationItem::Close(bool by_user) {
+ popup_closing_ = false;
+
+ const std::string id = notification_->id();
+ message_center::NotificationList::PopupNotifications popups =
+ message_center_->GetPopupNotifications();
+ for (auto it = popups.begin(); it != popups.end(); it++) {
+ if ((*it)->id() == id) {
+ popup_closing_ = true;
+ return;
+ }
+ }
+
+ // OnNotificationRemoved() will be called soon, just after the notification
+ // is removed.
+}
+
+void DownloadNotificationItem::OnNotificationRemoved(
+ const std::string& id, bool by_user) {
+ if (popup_closing_) {
+ notification_->set_is_read(true);
+ notification_->set_shown_as_popup(false);
+
+ // Reshows the notification.
asanka 2015/02/20 23:34:39 Can you explain what this logic does?
yoshiki 2015/02/24 21:30:57 I added some comments. Does it make sense?
+ scoped_ptr<Notification> notification(new Notification(*notification_));
+ message_center_->AddNotification(notification.Pass());
+ message_center_->MarkSinglePopupAsShown(
+ notification_->id(), true);
+
+ popup_closing_ = false;
+ } else {
+ item_->RemoveObserver(this);
+ item_->Cancel(by_user);
+ delegate_->OnDownloadRemoved(this);
asanka 2015/02/20 23:34:40 Is the OnDownloadRemoved call going to destroy |th
yoshiki 2015/02/24 21:30:57 Yes. It removes |this|.
+ }
+}
+
+void DownloadNotificationItem::Click() {
+ if (openable_) {
+ if (item_->IsDone())
+ item_->OpenDownload();
+ else
+ item_->SetOpenWhenComplete(!item_->GetOpenWhenComplete()); // Toggle
+ }
+
+ if (item_->IsDone())
+ message_center_->RemoveNotification(notification_->id(), true);
+}
+
+bool DownloadNotificationItem::HasClickedListener() {
+ return true;
+}
+
+void DownloadNotificationItem::ButtonClick(int button_index) {
+ if (button_index < 0 ||
+ static_cast<size_t>(button_index) >= button_actions_->size()) {
+ // Out of boundary.
+ NOTREACHED();
+ return;
+ }
+
+ DownloadCommands::Command command = button_actions_->at(button_index);
+ DownloadCommands(item_).ExecuteCommand(command, item_->GetWebContents());
asanka 2015/02/20 23:34:39 GetWebContents() can (and often will) return NULL
yoshiki 2015/02/24 21:30:57 I changed it to pass a |Profile| instead of a |Web
+}
+
+// DownloadItem::Observer methods
+void DownloadNotificationItem::OnDownloadUpdated(content::DownloadItem* item) {
+ DCHECK_EQ(item, item_);
+
+ SetNotificationData();
+
+ // Updates notification.
+ scoped_ptr<Notification> notification(new Notification(*notification_));
+ std::string id = notification->id();
+ message_center_->UpdateNotification(
+ id,
+ notification.Pass());
+}
+
+void DownloadNotificationItem::SetNotificationData() {
+ DownloadItemModel* model = new DownloadItemModel(item_);
asanka 2015/02/20 23:34:40 This leaks. In general, DownloadItemModel is a th
yoshiki 2015/02/24 21:30:57 Done.
+ DownloadCommands command(item_);
+
+ if (!downloading_) {
+ if (item_->GetState() == content::DownloadItem::IN_PROGRESS) {
+ delegate_->OnDownloadStarted(this);
+ downloading_ = true;
+ }
+ } else {
+ if (item_->GetState() != content::DownloadItem::IN_PROGRESS) {
+ delegate_->OnDownloadStopped(this);
+ downloading_ = false;
+ }
+ }
+
+ if (item_->IsDangerous()) {
+ notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE);
+ notification_->set_title(GetTitle());
+ notification_->set_message(GetWarningText());
+
+ // Show icon.
+ switch (item_->GetDangerType()) {
+ case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
+ case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
+ // TODO(yoshiki): Shows an appreciate icon.
asanka 2015/02/20 23:34:39 appreciate?
yoshiki 2015/02/24 21:30:57 I changed the code around here and the comment has
+ SetImageToNotification(IDR_DOWNLOAD_NOTIFICATION_MALICIOUS);
+ break;
+ default:
asanka 2015/02/20 23:34:39 Don't use a 'default' label when using an enumerat
yoshiki 2015/02/24 21:30:57 I changed the code around here and it has been rem
+ SetImageToNotification(IDR_DOWNLOAD_NOTIFICATION_MALICIOUS);
+ break;
+ }
+ } else {
+ notification_->set_title(GetTitle());
+ notification_->set_message(model->GetStatusText());
+
+ bool is_off_the_record = item_->GetBrowserContext() &&
+ item_->GetBrowserContext()->IsOffTheRecord();
+
+ switch (item_->GetState()) {
+ case content::DownloadItem::IN_PROGRESS:
+ notification_->set_type(message_center::NOTIFICATION_TYPE_PROGRESS);
+ notification_->set_progress(item_->PercentComplete());
+ if (is_off_the_record) {
+ // TODO(yoshiki): Replace the tentative image.
+ SetImageToNotification(IDR_DOWNLOAD_NOTIFICATION_INCOGNITO);
+ } else {
+ SetImageToNotification(IDR_DOWNLOAD_NOTIFICATION_DOWNLOADING);
+ }
+ break;
+ case content::DownloadItem::COMPLETE:
+ notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE);
+ if (is_off_the_record) {
+ // TODO(yoshiki): Replace the tentative image.
+ SetImageToNotification(IDR_DOWNLOAD_NOTIFICATION_INCOGNITO);
+ } else {
+ SetImageToNotification(IDR_DOWNLOAD_NOTIFICATION_DOWNLOADING);
+ }
+
+ // TODO(yoshiki): Popup a notification again.
+ break;
+ case content::DownloadItem::CANCELLED:
+ notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE);
+ SetImageToNotification(IDR_DOWNLOAD_NOTIFICATION_WARNING);
+ break;
+ case content::DownloadItem::INTERRUPTED:
+ notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE);
+ SetImageToNotification(IDR_DOWNLOAD_NOTIFICATION_WARNING);
+
+ // TODO(yoshiki): Popup a notification again.
+ break;
+ case content::DownloadItem::MAX_DOWNLOAD_STATE: // sentinel
+ NOTREACHED();
+ }
+ }
+
+ std::vector<message_center::ButtonInfo> notification_actions;
+ scoped_ptr<std::vector<DownloadCommands::Command> >
+ actions(GetPossibleActions().Pass());
+
+ openable_ = false;
+ button_actions_.reset(new std::vector<DownloadCommands::Command>);
+ for (auto it = actions->begin(); it != actions->end(); it++) {
+ if (*it == DownloadCommands::OPEN_WHEN_COMPLETE) {
+ openable_ = true;
+ } else {
+ button_actions_->push_back(*it);
+ message_center::ButtonInfo button_info =
+ message_center::ButtonInfo(GetCommandLabel(*it));
+ button_info.icon = command.GetCommandIcon(*it);
+ notification_actions.push_back(button_info);
+ }
+ }
+ notification_->set_buttons(notification_actions);
+
+ if (item_->IsDone()) {
+ // TODO(yoshiki): If the downloaded file is an image, show the thumbnail.
+ }
+}
+
+void DownloadNotificationItem::OnDownloadOpened(content::DownloadItem* item) {
+ DCHECK_EQ(item, item_);
+ // Do nothing.
+}
+
+void DownloadNotificationItem::OnDownloadRemoved(content::DownloadItem* item) {
+ DCHECK_EQ(item, item_);
+
+ // TODO(yoshiki): close the notification if necessary.
+
+ delegate_->OnDownloadRemoved(this);
asanka 2015/02/20 23:34:39 DownloadNOtificationItem being a RefCounted class
yoshiki 2015/02/24 21:30:57 Done.
+}
+
+void DownloadNotificationItem::SetImageToNotification(int resource_id) {
+ if (image_resource_id_ == resource_id)
+ return;
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
+ image_resource_id_ = resource_id;
+ notification_->set_icon(bundle.GetImageNamed(image_resource_id_));
+}
+
+scoped_ptr<std::vector<DownloadCommands::Command> >
+DownloadNotificationItem::GetPossibleActions() const {
+ scoped_ptr<std::vector<DownloadCommands::Command> >
+ actions(new std::vector<DownloadCommands::Command>());
+
+ if (item_->IsDangerous()) {
+ actions->push_back(DownloadCommands::DISCARD);
+ actions->push_back(DownloadCommands::SHOW_IN_FOLDER);
asanka 2015/02/20 23:34:40 YOu don't want to SHOW_IN_FOLDER if it's dangerous
yoshiki 2015/02/24 21:30:57 Done.
+ return actions.Pass();
+ }
+
+ switch (item_->GetState()) {
+ case content::DownloadItem::IN_PROGRESS:
+ actions->push_back(DownloadCommands::OPEN_WHEN_COMPLETE);
+ if (!item_->IsPaused())
+ actions->push_back(DownloadCommands::PAUSE);
+ else
+ actions->push_back(DownloadCommands::RESUME);
+ break;
+ case content::DownloadItem::CANCELLED:
+ case content::DownloadItem::INTERRUPTED:
+ actions->push_back(DownloadCommands::RETRY);
+ break;
+ case content::DownloadItem::COMPLETE:
+ actions->push_back(DownloadCommands::OPEN_WHEN_COMPLETE);
+ actions->push_back(DownloadCommands::SHOW_IN_FOLDER);
+ break;
+ case content::DownloadItem::MAX_DOWNLOAD_STATE:
+ NOTREACHED();
+ }
+ return actions.Pass();
+}
+
+base::string16 DownloadNotificationItem::GetTitle() const {
+ base::string16 title_text;
+ base::string16 file_name =
+ item_->GetFileNameToReportUser().LossyDisplayName();
+ switch (item_->GetState()) {
+ case content::DownloadItem::IN_PROGRESS:
+ title_text = l10n_util::GetStringFUTF16(
+ IDS_DOWNLOAD_STATUS_IN_PROGRESS_TITLE,
+ file_name);
+ break;
+ case content::DownloadItem::COMPLETE:
+ title_text = l10n_util::GetStringFUTF16(
+ IDS_DOWNLOAD_STATUS_DOWNLOADED_TITLE,
+ file_name);
+ case content::DownloadItem::INTERRUPTED:
+ title_text = l10n_util::GetStringFUTF16(
+ IDS_DOWNLOAD_STATUS_DOWNLOADED_TITLE,
+ file_name);
+ break;
+ case content::DownloadItem::CANCELLED:
+ title_text = l10n_util::GetStringFUTF16(
+ IDS_DOWNLOAD_STATUS_DOWNLOAD_FAILED_TITLE,
+ file_name);
+ break;
+ case content::DownloadItem::MAX_DOWNLOAD_STATE:
+ NOTREACHED();
+ }
+ return title_text;
+}
+
+base::string16 DownloadNotificationItem::GetCommandLabel(
+ DownloadCommands::Command command) const {
+ int id = -1;
+ switch (command) {
+ case DownloadCommands::OPEN_WHEN_COMPLETE:
+ if (item_ && !item_->IsDone())
+ id = IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE;
+ else
+ id = IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE;
+ break;
+ case DownloadCommands::PAUSE:
+ // Only for non menu.
+ id = IDS_DOWNLOAD_LINK_PAUSE;
+ break;
+ case DownloadCommands::RESUME:
+ // Only for non menu.
+ id = IDS_DOWNLOAD_LINK_RESUME;
+ break;
+ case DownloadCommands::SHOW_IN_FOLDER:
+ id = IDS_DOWNLOAD_LINK_SHOW;
+ break;
+ case DownloadCommands::RETRY:
+ // Only for non menu.
+ id = IDS_DOWNLOAD_LINK_RETRY;
+ break;
+ case DownloadCommands::DISCARD:
+ id = IDS_DISCARD_DOWNLOAD;
+ break;
+ case DownloadCommands::KEEP:
+ case DownloadCommands::ALWAYS_OPEN_TYPE:
+ case DownloadCommands::PLATFORM_OPEN:
+ case DownloadCommands::CANCEL:
+ case DownloadCommands::LEARN_MORE_SCANNING:
+ case DownloadCommands::LEARN_MORE_INTERRUPTED:
+ // Only for menu.
+ NOTREACHED();
+ return base::string16();
+ }
+ CHECK(id != -1);
+ return l10n_util::GetStringUTF16(id);
+}
+
+base::string16 DownloadNotificationItem::GetWarningText() const {
+ // Should only be called if IsDangerous().
+ DCHECK(item_->IsDangerous());
+ base::string16 elided_filename =
+ item_->GetFileNameToReportUser().LossyDisplayName();
+ switch (item_->GetDangerType()) {
+ case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: {
+ return l10n_util::GetStringUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_URL);
+ }
+ case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: {
+ if (download_crx_util::IsExtensionDownload(*item_)) {
+ return l10n_util::GetStringUTF16(
+ IDS_PROMPT_DANGEROUS_DOWNLOAD_EXTENSION);
+ } else {
+ return l10n_util::GetStringFUTF16(IDS_PROMPT_DANGEROUS_DOWNLOAD,
+ elided_filename);
+ }
+ }
+ case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT:
+ case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: {
+ return l10n_util::GetStringFUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_CONTENT,
+ elided_filename);
+ }
+ case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: {
+ return l10n_util::GetStringFUTF16(IDS_PROMPT_UNCOMMON_DOWNLOAD_CONTENT,
+ elided_filename);
+ }
+ case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: {
+ return l10n_util::GetStringFUTF16(
+ IDS_PROMPT_DOWNLOAD_CHANGES_SETTINGS, elided_filename);
+ }
+ case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS:
+ case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT:
+ case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED:
+ case content::DOWNLOAD_DANGER_TYPE_MAX: {
+ break;
+ }
+ }
+ NOTREACHED();
+ return base::string16();
+}

Powered by Google App Engine
This is Rietveld 408576698