OLD | NEW |
(Empty) | |
| 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. |
| 4 |
| 5 #include "chrome/browser/download/notification/download_notification_item.h" |
| 6 |
| 7 #include "base/strings/string_number_conversions.h" |
| 8 #include "chrome/browser/download/download_crx_util.h" |
| 9 #include "chrome/browser/download/download_item_model.h" |
| 10 #include "chrome/grit/chromium_strings.h" |
| 11 #include "chrome/grit/generated_resources.h" |
| 12 #include "content/public/browser/browser_context.h" |
| 13 #include "content/public/browser/browser_thread.h" |
| 14 #include "content/public/browser/download_item.h" |
| 15 #include "content/public/browser/web_contents.h" |
| 16 #include "grit/theme_resources.h" |
| 17 #include "ui/base/l10n/l10n_util.h" |
| 18 #include "ui/base/resource/resource_bundle.h" |
| 19 #include "ui/message_center/message_center.h" |
| 20 #include "ui/message_center/notification.h" |
| 21 #include "ui/message_center/notification_delegate.h" |
| 22 |
| 23 using message_center::Notification; |
| 24 |
| 25 namespace { |
| 26 |
| 27 const char kDownloadNotificationNotifierId[] = |
| 28 "chrome://settings/display/notification/id-notifier"; |
| 29 const char kDownloadNotificationIdBase[] = |
| 30 "chrome://settings/display/notification/id-"; |
| 31 |
| 32 } // anonymous namespace |
| 33 |
| 34 DownloadNotificationItem::NotificationWatcher::NotificationWatcher( |
| 35 DownloadNotificationItem* item) |
| 36 : item_(item) { |
| 37 } |
| 38 |
| 39 DownloadNotificationItem::NotificationWatcher::~NotificationWatcher() { |
| 40 } |
| 41 |
| 42 void DownloadNotificationItem::NotificationWatcher::Close(bool by_user) { |
| 43 item_->OnNotificationClose(by_user); |
| 44 } |
| 45 |
| 46 void DownloadNotificationItem::NotificationWatcher::Click() { |
| 47 item_->OnNotificationClick(); |
| 48 } |
| 49 |
| 50 bool DownloadNotificationItem::NotificationWatcher::HasClickedListener() { |
| 51 return true; |
| 52 } |
| 53 |
| 54 void DownloadNotificationItem::NotificationWatcher::ButtonClick( |
| 55 int button_index) { |
| 56 item_->OnNotificationButtonClick(button_index); |
| 57 } |
| 58 |
| 59 void DownloadNotificationItem::NotificationWatcher::OnNotificationRemoved( |
| 60 const std::string& id, |
| 61 bool by_user) { |
| 62 if (id != item_->notification_->id()) |
| 63 return; |
| 64 item_->OnNotificationRemoved(by_user); |
| 65 } |
| 66 |
| 67 DownloadNotificationItem::DownloadNotificationItem(content::DownloadItem* item, |
| 68 Delegate* delegate) |
| 69 : openable_(false), |
| 70 downloading_(false), |
| 71 reshow_after_remove_(false), |
| 72 image_resource_id_(0), |
| 73 watcher_(new NotificationWatcher(this)), |
| 74 item_(item), |
| 75 delegate_(delegate) { |
| 76 item->AddObserver(this); |
| 77 |
| 78 message_center_ = message_center::MessageCenter::Get(); |
| 79 message_center_->AddObserver(watcher_.get()); |
| 80 |
| 81 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); |
| 82 |
| 83 const base::string16 timeout_message = |
| 84 l10n_util::GetStringUTF16(IDS_DOWNLOAD_STATUS_CRX_INSTALL_RUNNING); |
| 85 const base::string16 message = |
| 86 l10n_util::GetStringUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_URL); |
| 87 |
| 88 std::string id(kDownloadNotificationIdBase); |
| 89 id += base::UintToString(item_->GetId()); |
| 90 |
| 91 message_center::RichNotificationData data; |
| 92 notification_.reset(new Notification( |
| 93 message_center::NOTIFICATION_TYPE_PROGRESS, id, message, timeout_message, |
| 94 bundle.GetImageNamed(IDR_DOWNLOAD_NOTIFICATION_DOWNLOADING), |
| 95 base::string16() /* display_source */, |
| 96 message_center::NotifierId(message_center::NotifierId::SYSTEM_COMPONENT, |
| 97 kDownloadNotificationNotifierId), |
| 98 data, watcher_.get())); |
| 99 |
| 100 notification_->set_progress(0); |
| 101 notification_->set_never_timeout(false); |
| 102 |
| 103 UpdateNotificationData(); |
| 104 |
| 105 scoped_ptr<Notification> notification(new Notification(*notification_)); |
| 106 message_center_->AddNotification(notification.Pass()); |
| 107 } |
| 108 |
| 109 DownloadNotificationItem::~DownloadNotificationItem() { |
| 110 if (item_) |
| 111 item_->RemoveObserver(this); |
| 112 message_center_->RemoveObserver(watcher_.get()); |
| 113 } |
| 114 |
| 115 void DownloadNotificationItem::OnNotificationClose(bool by_user) { |
| 116 if (item_->GetState() != content::DownloadItem::IN_PROGRESS) { |
| 117 reshow_after_remove_ = false; |
| 118 } else { |
| 119 bool popup = false; |
| 120 |
| 121 const std::string id = notification_->id(); |
| 122 message_center::NotificationList::PopupNotifications popups = |
| 123 message_center_->GetPopupNotifications(); |
| 124 for (auto it = popups.begin(); it != popups.end(); it++) { |
| 125 if ((*it)->id() == id) { |
| 126 popup = true; |
| 127 break; |
| 128 } |
| 129 } |
| 130 |
| 131 // Reshows the notification in the notification center, if the download is |
| 132 // in progress and the notifitation being closed is a popup. |
| 133 reshow_after_remove_ = popup; |
| 134 } |
| 135 |
| 136 // OnNotificationRemoved() will be called soon, just after the notification |
| 137 // is removed. |
| 138 } |
| 139 |
| 140 void DownloadNotificationItem::OnNotificationRemoved(bool by_user) { |
| 141 if (reshow_after_remove_) { |
| 142 // Sets the notification as read. |
| 143 notification_->set_is_read(true); |
| 144 |
| 145 // Reshows the notification. |
| 146 scoped_ptr<Notification> notification(new Notification(*notification_)); |
| 147 message_center_->AddNotification(notification.Pass()); |
| 148 // Show the reshown notification as a non-popup. |
| 149 message_center_->MarkSinglePopupAsShown(notification_->id(), true); |
| 150 |
| 151 reshow_after_remove_ = false; |
| 152 } else { |
| 153 // Cancels the download. |
| 154 item_->Cancel(by_user); |
| 155 delegate_->OnDownloadRemoved(this); |
| 156 } |
| 157 } |
| 158 |
| 159 void DownloadNotificationItem::OnNotificationClick() { |
| 160 if (openable_) { |
| 161 if (item_->IsDone()) |
| 162 item_->OpenDownload(); |
| 163 else |
| 164 item_->SetOpenWhenComplete(!item_->GetOpenWhenComplete()); // Toggle |
| 165 } |
| 166 |
| 167 if (item_->IsDone()) |
| 168 message_center_->RemoveNotification(notification_->id(), true); |
| 169 } |
| 170 |
| 171 void DownloadNotificationItem::OnNotificationButtonClick(int button_index) { |
| 172 if (button_index < 0 || |
| 173 static_cast<size_t>(button_index) >= button_actions_->size()) { |
| 174 // Out of boundary. |
| 175 NOTREACHED(); |
| 176 return; |
| 177 } |
| 178 |
| 179 DownloadCommands::Command command = button_actions_->at(button_index); |
| 180 DownloadCommands(item_).ExecuteCommand(command); |
| 181 } |
| 182 |
| 183 // DownloadItem::Observer methods |
| 184 void DownloadNotificationItem::OnDownloadUpdated(content::DownloadItem* item) { |
| 185 DCHECK_EQ(item, item_); |
| 186 |
| 187 UpdateNotificationData(); |
| 188 |
| 189 // Updates notification. |
| 190 scoped_ptr<Notification> notification(new Notification(*notification_)); |
| 191 std::string id = notification->id(); |
| 192 message_center_->UpdateNotification(id, notification.Pass()); |
| 193 } |
| 194 |
| 195 void DownloadNotificationItem::UpdateNotificationData() { |
| 196 DownloadItemModel model(item_); |
| 197 DownloadCommands command(item_); |
| 198 |
| 199 if (!downloading_) { |
| 200 if (item_->GetState() == content::DownloadItem::IN_PROGRESS) { |
| 201 delegate_->OnDownloadStarted(this); |
| 202 downloading_ = true; |
| 203 } |
| 204 } else { |
| 205 if (item_->GetState() != content::DownloadItem::IN_PROGRESS) { |
| 206 delegate_->OnDownloadStopped(this); |
| 207 downloading_ = false; |
| 208 } |
| 209 } |
| 210 |
| 211 if (item_->IsDangerous()) { |
| 212 notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE); |
| 213 notification_->set_title(GetTitle()); |
| 214 notification_->set_message(GetWarningText()); |
| 215 |
| 216 // Show icon. |
| 217 SetNotificationImage(IDR_DOWNLOAD_NOTIFICATION_MALICIOUS); |
| 218 } else { |
| 219 notification_->set_title(GetTitle()); |
| 220 notification_->set_message(model.GetStatusText()); |
| 221 |
| 222 bool is_off_the_record = item_->GetBrowserContext() && |
| 223 item_->GetBrowserContext()->IsOffTheRecord(); |
| 224 |
| 225 switch (item_->GetState()) { |
| 226 case content::DownloadItem::IN_PROGRESS: |
| 227 notification_->set_type(message_center::NOTIFICATION_TYPE_PROGRESS); |
| 228 notification_->set_progress(item_->PercentComplete()); |
| 229 if (is_off_the_record) { |
| 230 // TODO(yoshiki): Replace the tentative image. |
| 231 SetNotificationImage(IDR_DOWNLOAD_NOTIFICATION_INCOGNITO); |
| 232 } else { |
| 233 SetNotificationImage(IDR_DOWNLOAD_NOTIFICATION_DOWNLOADING); |
| 234 } |
| 235 break; |
| 236 case content::DownloadItem::COMPLETE: |
| 237 notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE); |
| 238 if (is_off_the_record) { |
| 239 // TODO(yoshiki): Replace the tentative image. |
| 240 SetNotificationImage(IDR_DOWNLOAD_NOTIFICATION_INCOGNITO); |
| 241 } else { |
| 242 SetNotificationImage(IDR_DOWNLOAD_NOTIFICATION_DOWNLOADING); |
| 243 } |
| 244 |
| 245 // TODO(yoshiki): Popup a notification again. |
| 246 break; |
| 247 case content::DownloadItem::CANCELLED: |
| 248 notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE); |
| 249 SetNotificationImage(IDR_DOWNLOAD_NOTIFICATION_WARNING); |
| 250 break; |
| 251 case content::DownloadItem::INTERRUPTED: |
| 252 notification_->set_type(message_center::NOTIFICATION_TYPE_SIMPLE); |
| 253 SetNotificationImage(IDR_DOWNLOAD_NOTIFICATION_WARNING); |
| 254 |
| 255 // TODO(yoshiki): Popup a notification again. |
| 256 break; |
| 257 case content::DownloadItem::MAX_DOWNLOAD_STATE: // sentinel |
| 258 NOTREACHED(); |
| 259 } |
| 260 } |
| 261 |
| 262 std::vector<message_center::ButtonInfo> notification_actions; |
| 263 scoped_ptr<std::vector<DownloadCommands::Command>> actions( |
| 264 GetPossibleActions().Pass()); |
| 265 |
| 266 openable_ = false; |
| 267 button_actions_.reset(new std::vector<DownloadCommands::Command>); |
| 268 for (auto it = actions->begin(); it != actions->end(); it++) { |
| 269 if (*it == DownloadCommands::OPEN_WHEN_COMPLETE) { |
| 270 openable_ = true; |
| 271 } else { |
| 272 button_actions_->push_back(*it); |
| 273 message_center::ButtonInfo button_info = |
| 274 message_center::ButtonInfo(GetCommandLabel(*it)); |
| 275 button_info.icon = command.GetCommandIcon(*it); |
| 276 notification_actions.push_back(button_info); |
| 277 } |
| 278 } |
| 279 notification_->set_buttons(notification_actions); |
| 280 |
| 281 if (item_->IsDone()) { |
| 282 // TODO(yoshiki): If the downloaded file is an image, show the thumbnail. |
| 283 } |
| 284 } |
| 285 |
| 286 void DownloadNotificationItem::OnDownloadOpened(content::DownloadItem* item) { |
| 287 DCHECK_EQ(item, item_); |
| 288 // Do nothing. |
| 289 } |
| 290 |
| 291 void DownloadNotificationItem::OnDownloadRemoved(content::DownloadItem* item) { |
| 292 DCHECK_EQ(item, item_); |
| 293 |
| 294 // Removing the notification causes calling both |OnNotificationClose()| and |
| 295 // |OnNotificationRemoved()|. |
| 296 message_center_->RemoveNotification(notification_->id(), false); |
| 297 } |
| 298 |
| 299 void DownloadNotificationItem::OnDownloadDestroyed( |
| 300 content::DownloadItem* item) { |
| 301 DCHECK_EQ(item, item_); |
| 302 |
| 303 item_ = nullptr; |
| 304 } |
| 305 |
| 306 void DownloadNotificationItem::SetNotificationImage(int resource_id) { |
| 307 if (image_resource_id_ == resource_id) |
| 308 return; |
| 309 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); |
| 310 image_resource_id_ = resource_id; |
| 311 notification_->set_icon(bundle.GetImageNamed(image_resource_id_)); |
| 312 } |
| 313 |
| 314 scoped_ptr<std::vector<DownloadCommands::Command>> |
| 315 DownloadNotificationItem::GetPossibleActions() const { |
| 316 scoped_ptr<std::vector<DownloadCommands::Command>> actions( |
| 317 new std::vector<DownloadCommands::Command>()); |
| 318 |
| 319 if (item_->IsDangerous()) { |
| 320 actions->push_back(DownloadCommands::DISCARD); |
| 321 actions->push_back(DownloadCommands::KEEP); |
| 322 return actions.Pass(); |
| 323 } |
| 324 |
| 325 switch (item_->GetState()) { |
| 326 case content::DownloadItem::IN_PROGRESS: |
| 327 actions->push_back(DownloadCommands::OPEN_WHEN_COMPLETE); |
| 328 if (!item_->IsPaused()) |
| 329 actions->push_back(DownloadCommands::PAUSE); |
| 330 else |
| 331 actions->push_back(DownloadCommands::RESUME); |
| 332 break; |
| 333 case content::DownloadItem::CANCELLED: |
| 334 case content::DownloadItem::INTERRUPTED: |
| 335 actions->push_back(DownloadCommands::RETRY); |
| 336 break; |
| 337 case content::DownloadItem::COMPLETE: |
| 338 actions->push_back(DownloadCommands::OPEN_WHEN_COMPLETE); |
| 339 actions->push_back(DownloadCommands::SHOW_IN_FOLDER); |
| 340 break; |
| 341 case content::DownloadItem::MAX_DOWNLOAD_STATE: |
| 342 NOTREACHED(); |
| 343 } |
| 344 return actions.Pass(); |
| 345 } |
| 346 |
| 347 base::string16 DownloadNotificationItem::GetTitle() const { |
| 348 base::string16 title_text; |
| 349 base::string16 file_name = |
| 350 item_->GetFileNameToReportUser().LossyDisplayName(); |
| 351 switch (item_->GetState()) { |
| 352 case content::DownloadItem::IN_PROGRESS: |
| 353 title_text = l10n_util::GetStringFUTF16( |
| 354 IDS_DOWNLOAD_STATUS_IN_PROGRESS_TITLE, file_name); |
| 355 break; |
| 356 case content::DownloadItem::COMPLETE: |
| 357 title_text = l10n_util::GetStringFUTF16( |
| 358 IDS_DOWNLOAD_STATUS_DOWNLOADED_TITLE, file_name); |
| 359 case content::DownloadItem::INTERRUPTED: |
| 360 title_text = l10n_util::GetStringFUTF16( |
| 361 IDS_DOWNLOAD_STATUS_DOWNLOADED_TITLE, file_name); |
| 362 break; |
| 363 case content::DownloadItem::CANCELLED: |
| 364 title_text = l10n_util::GetStringFUTF16( |
| 365 IDS_DOWNLOAD_STATUS_DOWNLOAD_FAILED_TITLE, file_name); |
| 366 break; |
| 367 case content::DownloadItem::MAX_DOWNLOAD_STATE: |
| 368 NOTREACHED(); |
| 369 } |
| 370 return title_text; |
| 371 } |
| 372 |
| 373 base::string16 DownloadNotificationItem::GetCommandLabel( |
| 374 DownloadCommands::Command command) const { |
| 375 int id = -1; |
| 376 switch (command) { |
| 377 case DownloadCommands::OPEN_WHEN_COMPLETE: |
| 378 if (item_ && !item_->IsDone()) |
| 379 id = IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE; |
| 380 else |
| 381 id = IDS_DOWNLOAD_STATUS_OPEN_WHEN_COMPLETE; |
| 382 break; |
| 383 case DownloadCommands::PAUSE: |
| 384 // Only for non menu. |
| 385 id = IDS_DOWNLOAD_LINK_PAUSE; |
| 386 break; |
| 387 case DownloadCommands::RESUME: |
| 388 // Only for non menu. |
| 389 id = IDS_DOWNLOAD_LINK_RESUME; |
| 390 break; |
| 391 case DownloadCommands::SHOW_IN_FOLDER: |
| 392 id = IDS_DOWNLOAD_LINK_SHOW; |
| 393 break; |
| 394 case DownloadCommands::RETRY: |
| 395 // Only for non menu. |
| 396 id = IDS_DOWNLOAD_LINK_RETRY; |
| 397 break; |
| 398 case DownloadCommands::DISCARD: |
| 399 id = IDS_DISCARD_DOWNLOAD; |
| 400 break; |
| 401 case DownloadCommands::KEEP: |
| 402 id = IDS_CONFIRM_DOWNLOAD; |
| 403 break; |
| 404 case DownloadCommands::ALWAYS_OPEN_TYPE: |
| 405 case DownloadCommands::PLATFORM_OPEN: |
| 406 case DownloadCommands::CANCEL: |
| 407 case DownloadCommands::LEARN_MORE_SCANNING: |
| 408 case DownloadCommands::LEARN_MORE_INTERRUPTED: |
| 409 // Only for menu. |
| 410 NOTREACHED(); |
| 411 return base::string16(); |
| 412 } |
| 413 CHECK(id != -1); |
| 414 return l10n_util::GetStringUTF16(id); |
| 415 } |
| 416 |
| 417 base::string16 DownloadNotificationItem::GetWarningText() const { |
| 418 // Should only be called if IsDangerous(). |
| 419 DCHECK(item_->IsDangerous()); |
| 420 base::string16 elided_filename = |
| 421 item_->GetFileNameToReportUser().LossyDisplayName(); |
| 422 switch (item_->GetDangerType()) { |
| 423 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL: { |
| 424 return l10n_util::GetStringUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_URL); |
| 425 } |
| 426 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE: { |
| 427 if (download_crx_util::IsExtensionDownload(*item_)) { |
| 428 return l10n_util::GetStringUTF16( |
| 429 IDS_PROMPT_DANGEROUS_DOWNLOAD_EXTENSION); |
| 430 } else { |
| 431 return l10n_util::GetStringFUTF16(IDS_PROMPT_DANGEROUS_DOWNLOAD, |
| 432 elided_filename); |
| 433 } |
| 434 } |
| 435 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT: |
| 436 case content::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST: { |
| 437 return l10n_util::GetStringFUTF16(IDS_PROMPT_MALICIOUS_DOWNLOAD_CONTENT, |
| 438 elided_filename); |
| 439 } |
| 440 case content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT: { |
| 441 return l10n_util::GetStringFUTF16(IDS_PROMPT_UNCOMMON_DOWNLOAD_CONTENT, |
| 442 elided_filename); |
| 443 } |
| 444 case content::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED: { |
| 445 return l10n_util::GetStringFUTF16(IDS_PROMPT_DOWNLOAD_CHANGES_SETTINGS, |
| 446 elided_filename); |
| 447 } |
| 448 case content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS: |
| 449 case content::DOWNLOAD_DANGER_TYPE_MAYBE_DANGEROUS_CONTENT: |
| 450 case content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED: |
| 451 case content::DOWNLOAD_DANGER_TYPE_MAX: { |
| 452 break; |
| 453 } |
| 454 } |
| 455 NOTREACHED(); |
| 456 return base::string16(); |
| 457 } |
OLD | NEW |