1// Copyright (c) 2012 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 "ui/message_center/notification_list.h"
6
7#include "base/bind.h"
8#include "base/logging.h"
9#include "base/stl_util.h"
10#include "base/time/time.h"
11#include "base/values.h"
12#include "ui/gfx/image/image.h"
13#include "ui/message_center/message_center_style.h"
14#include "ui/message_center/notification.h"
15#include "ui/message_center/notification_blocker.h"
16#include "ui/message_center/notification_types.h"
17
18namespace message_center {
19
20namespace {
21
22bool ShouldShowNotificationAsPopup(
23    const Notification& notification,
24    const NotificationBlockers& blockers) {
25  for (size_t i = 0; i < blockers.size(); ++i) {
26    if (!blockers[i]->ShouldShowNotificationAsPopup(notification.notifier_id()))
27      return false;
28  }
29  return true;
30}
31
32}  // namespace
33
34bool ComparePriorityTimestampSerial::operator()(Notification* n1,
35                                                Notification* n2) {
36  if (n1->priority() > n2->priority())  // Higher pri go first.
37    return true;
38  if (n1->priority() < n2->priority())
39    return false;
40  return CompareTimestampSerial()(n1, n2);
41}
42
43bool CompareTimestampSerial::operator()(Notification* n1, Notification* n2) {
44  if (n1->timestamp() > n2->timestamp())  // Newer come first.
45    return true;
46  if (n1->timestamp() < n2->timestamp())
47    return false;
48  if (n1->serial_number() > n2->serial_number())  // Newer come first.
49    return true;
50  if (n1->serial_number() < n2->serial_number())
51    return false;
52  return false;
53}
54
55NotificationList::NotificationList()
56    : message_center_visible_(false),
57      quiet_mode_(false) {
58}
59
60NotificationList::~NotificationList() {
61  STLDeleteContainerPointers(notifications_.begin(), notifications_.end());
62}
63
64void NotificationList::SetMessageCenterVisible(
65    bool visible,
66    std::set<std::string>* updated_ids) {
67  if (message_center_visible_ == visible)
68    return;
69
70  message_center_visible_ = visible;
71
72  if (!visible)
73    return;
74
75  for (Notifications::iterator iter = notifications_.begin();
76       iter != notifications_.end(); ++iter) {
77    Notification* notification = *iter;
78    bool was_popup = notification->shown_as_popup();
79    bool was_read = notification->IsRead();
80    if (notification->priority() < SYSTEM_PRIORITY)
81      notification->set_shown_as_popup(true);
82    notification->set_is_read(true);
83    if (updated_ids && !(was_popup && was_read))
84      updated_ids->insert(notification->id());
85  }
86}
87
88void NotificationList::AddNotification(scoped_ptr<Notification> notification) {
89  PushNotification(notification.Pass());
90}
91
92void NotificationList::UpdateNotificationMessage(
93    const std::string& old_id,
94    scoped_ptr<Notification> new_notification) {
95  Notifications::iterator iter = GetNotification(old_id);
96  if (iter == notifications_.end())
97    return;
98
99  new_notification->CopyState(*iter);
100
101  // Handles priority promotion. If the notification is already dismissed but
102  // the updated notification has higher priority, it should re-appear as a
103  // toast. Notifications coming from websites through the Web Notification API
104  // will always re-appear on update.
105  if ((*iter)->priority() < new_notification->priority() ||
106      new_notification->notifier_id().type == NotifierId::WEB_PAGE) {
107    new_notification->set_is_read(false);
108    new_notification->set_shown_as_popup(false);
109  }
110
111  // Do not use EraseNotification and PushNotification, since we don't want to
112  // change unread counts nor to update is_read/shown_as_popup states.
113  Notification* old = *iter;
114  notifications_.erase(iter);
115  delete old;
116
117  // We really don't want duplicate IDs.
118  DCHECK(GetNotification(new_notification->id()) == notifications_.end());
119  notifications_.insert(new_notification.release());
120}
121
122void NotificationList::RemoveNotification(const std::string& id) {
123  EraseNotification(GetNotification(id));
124}
125
126NotificationList::Notifications NotificationList::GetNotificationsByNotifierId(
127        const NotifierId& notifier_id) {
128  Notifications notifications;
129  for (Notifications::iterator iter = notifications_.begin();
130       iter != notifications_.end(); ++iter) {
131    if ((*iter)->notifier_id() == notifier_id)
132      notifications.insert(*iter);
133  }
134  return notifications;
135}
136
137bool NotificationList::SetNotificationIcon(const std::string& notification_id,
138                                           const gfx::Image& image) {
139  Notifications::iterator iter = GetNotification(notification_id);
140  if (iter == notifications_.end())
141    return false;
142  (*iter)->set_icon(image);
143  return true;
144}
145
146bool NotificationList::SetNotificationImage(const std::string& notification_id,
147                                            const gfx::Image& image) {
148  Notifications::iterator iter = GetNotification(notification_id);
149  if (iter == notifications_.end())
150    return false;
151  (*iter)->set_image(image);
152  return true;
153}
154
155bool NotificationList::SetNotificationButtonIcon(
156    const std::string& notification_id, int button_index,
157    const gfx::Image& image) {
158  Notifications::iterator iter = GetNotification(notification_id);
159  if (iter == notifications_.end())
160    return false;
161  (*iter)->SetButtonIcon(button_index, image);
162  return true;
163}
164
165bool NotificationList::HasNotificationOfType(const std::string& id,
166                                             const NotificationType type) {
167  Notifications::iterator iter = GetNotification(id);
168  if (iter == notifications_.end())
169    return false;
170
171  return (*iter)->type() == type;
172}
173
174bool NotificationList::HasPopupNotifications(
175    const NotificationBlockers& blockers) {
176  for (Notifications::iterator iter = notifications_.begin();
177       iter != notifications_.end(); ++iter) {
178    if ((*iter)->priority() < DEFAULT_PRIORITY)
179      break;
180    if (!ShouldShowNotificationAsPopup(**iter, blockers))
181      continue;
182    if (!(*iter)->shown_as_popup())
183      return true;
184  }
185  return false;
186}
187
188NotificationList::PopupNotifications NotificationList::GetPopupNotifications(
189    const NotificationBlockers& blockers,
190    std::list<std::string>* blocked_ids) {
191  PopupNotifications result;
192  size_t default_priority_popup_count = 0;
193
194  // Collect notifications that should be shown as popups. Start from oldest.
195  for (Notifications::const_reverse_iterator iter = notifications_.rbegin();
196       iter != notifications_.rend(); iter++) {
197    if ((*iter)->shown_as_popup())
198      continue;
199
200    // No popups for LOW/MIN priority.
201    if ((*iter)->priority() < DEFAULT_PRIORITY)
202      continue;
203
204    if (!ShouldShowNotificationAsPopup(**iter, blockers)) {
205      if (blocked_ids)
206        blocked_ids->push_back((*iter)->id());
207      continue;
208    }
209
210    // Checking limits. No limits for HIGH/MAX priority. DEFAULT priority
211    // will return at most kMaxVisiblePopupNotifications entries. If the
212    // popup entries are more, older entries are used. see crbug.com/165768
213    if ((*iter)->priority() == DEFAULT_PRIORITY &&
214        default_priority_popup_count++ >= kMaxVisiblePopupNotifications) {
215      continue;
216    }
217
218    result.insert(*iter);
219  }
220  return result;
221}
222
223void NotificationList::MarkSinglePopupAsShown(
224    const std::string& id, bool mark_notification_as_read) {
225  Notifications::iterator iter = GetNotification(id);
226  DCHECK(iter != notifications_.end());
227
228  if ((*iter)->shown_as_popup())
229    return;
230
231  // System notification is marked as shown only when marked as read.
232  if ((*iter)->priority() != SYSTEM_PRIORITY || mark_notification_as_read)
233    (*iter)->set_shown_as_popup(true);
234
235  // The popup notification is already marked as read when it's displayed.
236  // Set the is_read() back to false if necessary.
237  if (!mark_notification_as_read)
238    (*iter)->set_is_read(false);
239}
240
241void NotificationList::MarkSinglePopupAsDisplayed(const std::string& id) {
242  Notifications::iterator iter = GetNotification(id);
243  if (iter == notifications_.end())
244    return;
245
246  if ((*iter)->shown_as_popup())
247    return;
248
249  if (!(*iter)->IsRead())
250    (*iter)->set_is_read(true);
251}
252
253NotificationDelegate* NotificationList::GetNotificationDelegate(
254    const std::string& id) {
255  Notifications::iterator iter = GetNotification(id);
256  if (iter == notifications_.end())
257    return NULL;
258  return (*iter)->delegate();
259}
260
261void NotificationList::SetQuietMode(bool quiet_mode) {
262  quiet_mode_ = quiet_mode;
263  if (quiet_mode_) {
264    for (Notifications::iterator iter = notifications_.begin();
265         iter != notifications_.end();
266         ++iter) {
267      (*iter)->set_shown_as_popup(true);
268    }
269  }
270}
271
272Notification* NotificationList::GetNotificationById(const std::string& id) {
273  Notifications::iterator iter = GetNotification(id);
274  if (iter != notifications_.end())
275    return *iter;
276  return NULL;
277}
278
279NotificationList::Notifications NotificationList::GetVisibleNotifications(
280    const NotificationBlockers& blockers) const {
281  Notifications result;
282  for (Notifications::const_iterator iter = notifications_.begin();
283       iter != notifications_.end(); ++iter) {
284    bool should_show = true;
285    for (size_t i = 0; i < blockers.size(); ++i) {
286      if (!blockers[i]->ShouldShowNotification((*iter)->notifier_id())) {
287        should_show = false;
288        break;
289      }
290    }
291    if (should_show)
292      result.insert(*iter);
293  }
294
295  return result;
296}
297
298size_t NotificationList::NotificationCount(
299    const NotificationBlockers& blockers) const {
300  return GetVisibleNotifications(blockers).size();
301}
302
303size_t NotificationList::UnreadCount(
304    const NotificationBlockers& blockers) const {
305  Notifications notifications = GetVisibleNotifications(blockers);
306  size_t unread_count = 0;
307  for (Notifications::const_iterator iter = notifications.begin();
308       iter != notifications.end(); ++iter) {
309    if (!(*iter)->IsRead())
310      ++unread_count;
311  }
312  return unread_count;
313}
314
315NotificationList::Notifications::iterator NotificationList::GetNotification(
316    const std::string& id) {
317  for (Notifications::iterator iter = notifications_.begin();
318       iter != notifications_.end(); ++iter) {
319    if ((*iter)->id() == id)
320      return iter;
321  }
322  return notifications_.end();
323}
324
325void NotificationList::EraseNotification(Notifications::iterator iter) {
326  delete *iter;
327  notifications_.erase(iter);
328}
329
330void NotificationList::PushNotification(scoped_ptr<Notification> notification) {
331  // Ensure that notification.id is unique by erasing any existing
332  // notification with the same id (shouldn't normally happen).
333  Notifications::iterator iter = GetNotification(notification->id());
334  bool state_inherited = false;
335  if (iter != notifications_.end()) {
336    notification->CopyState(*iter);
337    state_inherited = true;
338    EraseNotification(iter);
339  }
340  // Add the notification to the the list and mark it unread and unshown.
341  if (!state_inherited) {
342    // TODO(mukai): needs to distinguish if a notification is dismissed by
343    // the quiet mode or user operation.
344    notification->set_is_read(false);
345    notification->set_shown_as_popup(message_center_visible_
346                                     || quiet_mode_
347                                     || notification->shown_as_popup());
348  }
349  // Take ownership. The notification can only be removed from the list
350  // in EraseNotification(), which will delete it.
351  notifications_.insert(notification.release());
352}
353
354}  // namespace message_center
355