// Copyright (c) 2012 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 "ui/message_center/notification_list.h" #include "base/bind.h" #include "base/logging.h" #include "base/stl_util.h" #include "base/time/time.h" #include "base/values.h" #include "ui/gfx/image/image.h" #include "ui/message_center/message_center_style.h" #include "ui/message_center/notification.h" #include "ui/message_center/notification_blocker.h" #include "ui/message_center/notification_types.h" namespace message_center { namespace { bool ShouldShowNotificationAsPopup( const Notification& notification, const NotificationBlockers& blockers) { for (size_t i = 0; i < blockers.size(); ++i) { if (!blockers[i]->ShouldShowNotificationAsPopup(notification.notifier_id())) return false; } return true; } } // namespace bool ComparePriorityTimestampSerial::operator()(Notification* n1, Notification* n2) { if (n1->priority() > n2->priority()) // Higher pri go first. return true; if (n1->priority() < n2->priority()) return false; return CompareTimestampSerial()(n1, n2); } bool CompareTimestampSerial::operator()(Notification* n1, Notification* n2) { if (n1->timestamp() > n2->timestamp()) // Newer come first. return true; if (n1->timestamp() < n2->timestamp()) return false; if (n1->serial_number() > n2->serial_number()) // Newer come first. return true; if (n1->serial_number() < n2->serial_number()) return false; return false; } NotificationList::NotificationList() : message_center_visible_(false), quiet_mode_(false) { } NotificationList::~NotificationList() { STLDeleteContainerPointers(notifications_.begin(), notifications_.end()); } void NotificationList::SetMessageCenterVisible( bool visible, std::set* updated_ids) { if (message_center_visible_ == visible) return; message_center_visible_ = visible; if (!visible) return; for (Notifications::iterator iter = notifications_.begin(); iter != notifications_.end(); ++iter) { Notification* notification = *iter; bool was_popup = notification->shown_as_popup(); bool was_read = notification->IsRead(); if (notification->priority() < SYSTEM_PRIORITY) notification->set_shown_as_popup(true); notification->set_is_read(true); if (updated_ids && !(was_popup && was_read)) updated_ids->insert(notification->id()); } } void NotificationList::AddNotification(scoped_ptr notification) { PushNotification(notification.Pass()); } void NotificationList::UpdateNotificationMessage( const std::string& old_id, scoped_ptr new_notification) { Notifications::iterator iter = GetNotification(old_id); if (iter == notifications_.end()) return; new_notification->CopyState(*iter); // Handles priority promotion. If the notification is already dismissed but // the updated notification has higher priority, it should re-appear as a // toast. Notifications coming from websites through the Web Notification API // will always re-appear on update. if ((*iter)->priority() < new_notification->priority() || new_notification->notifier_id().type == NotifierId::WEB_PAGE) { new_notification->set_is_read(false); new_notification->set_shown_as_popup(false); } // Do not use EraseNotification and PushNotification, since we don't want to // change unread counts nor to update is_read/shown_as_popup states. Notification* old = *iter; notifications_.erase(iter); delete old; // We really don't want duplicate IDs. DCHECK(GetNotification(new_notification->id()) == notifications_.end()); notifications_.insert(new_notification.release()); } void NotificationList::RemoveNotification(const std::string& id) { EraseNotification(GetNotification(id)); } NotificationList::Notifications NotificationList::GetNotificationsByNotifierId( const NotifierId& notifier_id) { Notifications notifications; for (Notifications::iterator iter = notifications_.begin(); iter != notifications_.end(); ++iter) { if ((*iter)->notifier_id() == notifier_id) notifications.insert(*iter); } return notifications; } bool NotificationList::SetNotificationIcon(const std::string& notification_id, const gfx::Image& image) { Notifications::iterator iter = GetNotification(notification_id); if (iter == notifications_.end()) return false; (*iter)->set_icon(image); return true; } bool NotificationList::SetNotificationImage(const std::string& notification_id, const gfx::Image& image) { Notifications::iterator iter = GetNotification(notification_id); if (iter == notifications_.end()) return false; (*iter)->set_image(image); return true; } bool NotificationList::SetNotificationButtonIcon( const std::string& notification_id, int button_index, const gfx::Image& image) { Notifications::iterator iter = GetNotification(notification_id); if (iter == notifications_.end()) return false; (*iter)->SetButtonIcon(button_index, image); return true; } bool NotificationList::HasNotificationOfType(const std::string& id, const NotificationType type) { Notifications::iterator iter = GetNotification(id); if (iter == notifications_.end()) return false; return (*iter)->type() == type; } bool NotificationList::HasPopupNotifications( const NotificationBlockers& blockers) { for (Notifications::iterator iter = notifications_.begin(); iter != notifications_.end(); ++iter) { if ((*iter)->priority() < DEFAULT_PRIORITY) break; if (!ShouldShowNotificationAsPopup(**iter, blockers)) continue; if (!(*iter)->shown_as_popup()) return true; } return false; } NotificationList::PopupNotifications NotificationList::GetPopupNotifications( const NotificationBlockers& blockers, std::list* blocked_ids) { PopupNotifications result; size_t default_priority_popup_count = 0; // Collect notifications that should be shown as popups. Start from oldest. for (Notifications::const_reverse_iterator iter = notifications_.rbegin(); iter != notifications_.rend(); iter++) { if ((*iter)->shown_as_popup()) continue; // No popups for LOW/MIN priority. if ((*iter)->priority() < DEFAULT_PRIORITY) continue; if (!ShouldShowNotificationAsPopup(**iter, blockers)) { if (blocked_ids) blocked_ids->push_back((*iter)->id()); continue; } // Checking limits. No limits for HIGH/MAX priority. DEFAULT priority // will return at most kMaxVisiblePopupNotifications entries. If the // popup entries are more, older entries are used. see crbug.com/165768 if ((*iter)->priority() == DEFAULT_PRIORITY && default_priority_popup_count++ >= kMaxVisiblePopupNotifications) { continue; } result.insert(*iter); } return result; } void NotificationList::MarkSinglePopupAsShown( const std::string& id, bool mark_notification_as_read) { Notifications::iterator iter = GetNotification(id); DCHECK(iter != notifications_.end()); if ((*iter)->shown_as_popup()) return; // System notification is marked as shown only when marked as read. if ((*iter)->priority() != SYSTEM_PRIORITY || mark_notification_as_read) (*iter)->set_shown_as_popup(true); // The popup notification is already marked as read when it's displayed. // Set the is_read() back to false if necessary. if (!mark_notification_as_read) (*iter)->set_is_read(false); } void NotificationList::MarkSinglePopupAsDisplayed(const std::string& id) { Notifications::iterator iter = GetNotification(id); if (iter == notifications_.end()) return; if ((*iter)->shown_as_popup()) return; if (!(*iter)->IsRead()) (*iter)->set_is_read(true); } NotificationDelegate* NotificationList::GetNotificationDelegate( const std::string& id) { Notifications::iterator iter = GetNotification(id); if (iter == notifications_.end()) return NULL; return (*iter)->delegate(); } void NotificationList::SetQuietMode(bool quiet_mode) { quiet_mode_ = quiet_mode; if (quiet_mode_) { for (Notifications::iterator iter = notifications_.begin(); iter != notifications_.end(); ++iter) { (*iter)->set_shown_as_popup(true); } } } Notification* NotificationList::GetNotificationById(const std::string& id) { Notifications::iterator iter = GetNotification(id); if (iter != notifications_.end()) return *iter; return NULL; } NotificationList::Notifications NotificationList::GetVisibleNotifications( const NotificationBlockers& blockers) const { Notifications result; for (Notifications::const_iterator iter = notifications_.begin(); iter != notifications_.end(); ++iter) { bool should_show = true; for (size_t i = 0; i < blockers.size(); ++i) { if (!blockers[i]->ShouldShowNotification((*iter)->notifier_id())) { should_show = false; break; } } if (should_show) result.insert(*iter); } return result; } size_t NotificationList::NotificationCount( const NotificationBlockers& blockers) const { return GetVisibleNotifications(blockers).size(); } size_t NotificationList::UnreadCount( const NotificationBlockers& blockers) const { Notifications notifications = GetVisibleNotifications(blockers); size_t unread_count = 0; for (Notifications::const_iterator iter = notifications.begin(); iter != notifications.end(); ++iter) { if (!(*iter)->IsRead()) ++unread_count; } return unread_count; } NotificationList::Notifications::iterator NotificationList::GetNotification( const std::string& id) { for (Notifications::iterator iter = notifications_.begin(); iter != notifications_.end(); ++iter) { if ((*iter)->id() == id) return iter; } return notifications_.end(); } void NotificationList::EraseNotification(Notifications::iterator iter) { delete *iter; notifications_.erase(iter); } void NotificationList::PushNotification(scoped_ptr notification) { // Ensure that notification.id is unique by erasing any existing // notification with the same id (shouldn't normally happen). Notifications::iterator iter = GetNotification(notification->id()); bool state_inherited = false; if (iter != notifications_.end()) { notification->CopyState(*iter); state_inherited = true; EraseNotification(iter); } // Add the notification to the the list and mark it unread and unshown. if (!state_inherited) { // TODO(mukai): needs to distinguish if a notification is dismissed by // the quiet mode or user operation. notification->set_is_read(false); notification->set_shown_as_popup(message_center_visible_ || quiet_mode_ || notification->shown_as_popup()); } // Take ownership. The notification can only be removed from the list // in EraseNotification(), which will delete it. notifications_.insert(notification.release()); } } // namespace message_center