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.
104  if ((*iter)->priority() < new_notification->priority()) {
105    new_notification->set_is_read(false);
106    new_notification->set_shown_as_popup(false);
107  }
108
109  // Do not use EraseNotification and PushNotification, since we don't want to
110  // change unread counts nor to update is_read/shown_as_popup states.
111  Notification* old = *iter;
112  notifications_.erase(iter);
113  delete old;
114
115  // We really don't want duplicate IDs.
116  DCHECK(GetNotification(new_notification->id()) == notifications_.end());
117  notifications_.insert(new_notification.release());
118}
119
120void NotificationList::RemoveNotification(const std::string& id) {
121  EraseNotification(GetNotification(id));
122}
123
124NotificationList::Notifications NotificationList::GetNotificationsByNotifierId(
125        const NotifierId& notifier_id) {
126  Notifications notifications;
127  for (Notifications::iterator iter = notifications_.begin();
128       iter != notifications_.end(); ++iter) {
129    if ((*iter)->notifier_id() == notifier_id)
130      notifications.insert(*iter);
131  }
132  return notifications;
133}
134
135bool NotificationList::SetNotificationIcon(const std::string& notification_id,
136                                           const gfx::Image& image) {
137  Notifications::iterator iter = GetNotification(notification_id);
138  if (iter == notifications_.end())
139    return false;
140  (*iter)->set_icon(image);
141  return true;
142}
143
144bool NotificationList::SetNotificationImage(const std::string& notification_id,
145                                            const gfx::Image& image) {
146  Notifications::iterator iter = GetNotification(notification_id);
147  if (iter == notifications_.end())
148    return false;
149  (*iter)->set_image(image);
150  return true;
151}
152
153bool NotificationList::SetNotificationButtonIcon(
154    const std::string& notification_id, int button_index,
155    const gfx::Image& image) {
156  Notifications::iterator iter = GetNotification(notification_id);
157  if (iter == notifications_.end())
158    return false;
159  (*iter)->SetButtonIcon(button_index, image);
160  return true;
161}
162
163bool NotificationList::HasNotificationOfType(const std::string& id,
164                                             const NotificationType type) {
165  Notifications::iterator iter = GetNotification(id);
166  if (iter == notifications_.end())
167    return false;
168
169  return (*iter)->type() == type;
170}
171
172bool NotificationList::HasPopupNotifications(
173    const NotificationBlockers& blockers) {
174  for (Notifications::iterator iter = notifications_.begin();
175       iter != notifications_.end(); ++iter) {
176    if ((*iter)->priority() < DEFAULT_PRIORITY)
177      break;
178    if (!ShouldShowNotificationAsPopup(**iter, blockers))
179      continue;
180    if (!(*iter)->shown_as_popup())
181      return true;
182  }
183  return false;
184}
185
186NotificationList::PopupNotifications NotificationList::GetPopupNotifications(
187    const NotificationBlockers& blockers,
188    std::list<std::string>* blocked_ids) {
189  PopupNotifications result;
190  size_t default_priority_popup_count = 0;
191
192  // Collect notifications that should be shown as popups. Start from oldest.
193  for (Notifications::const_reverse_iterator iter = notifications_.rbegin();
194       iter != notifications_.rend(); iter++) {
195    if ((*iter)->shown_as_popup())
196      continue;
197
198    // No popups for LOW/MIN priority.
199    if ((*iter)->priority() < DEFAULT_PRIORITY)
200      continue;
201
202    if (!ShouldShowNotificationAsPopup(**iter, blockers)) {
203      if (blocked_ids)
204        blocked_ids->push_back((*iter)->id());
205      continue;
206    }
207
208    // Checking limits. No limits for HIGH/MAX priority. DEFAULT priority
209    // will return at most kMaxVisiblePopupNotifications entries. If the
210    // popup entries are more, older entries are used. see crbug.com/165768
211    if ((*iter)->priority() == DEFAULT_PRIORITY &&
212        default_priority_popup_count++ >= kMaxVisiblePopupNotifications) {
213      continue;
214    }
215
216    result.insert(*iter);
217  }
218  return result;
219}
220
221void NotificationList::MarkSinglePopupAsShown(
222    const std::string& id, bool mark_notification_as_read) {
223  Notifications::iterator iter = GetNotification(id);
224  DCHECK(iter != notifications_.end());
225
226  if ((*iter)->shown_as_popup())
227    return;
228
229  // System notification is marked as shown only when marked as read.
230  if ((*iter)->priority() != SYSTEM_PRIORITY || mark_notification_as_read)
231    (*iter)->set_shown_as_popup(true);
232
233  // The popup notification is already marked as read when it's displayed.
234  // Set the is_read() back to false if necessary.
235  if (!mark_notification_as_read)
236    (*iter)->set_is_read(false);
237}
238
239void NotificationList::MarkSinglePopupAsDisplayed(const std::string& id) {
240  Notifications::iterator iter = GetNotification(id);
241  if (iter == notifications_.end())
242    return;
243
244  if ((*iter)->shown_as_popup())
245    return;
246
247  if (!(*iter)->IsRead())
248    (*iter)->set_is_read(true);
249}
250
251NotificationDelegate* NotificationList::GetNotificationDelegate(
252    const std::string& id) {
253  Notifications::iterator iter = GetNotification(id);
254  if (iter == notifications_.end())
255    return NULL;
256  return (*iter)->delegate();
257}
258
259void NotificationList::SetQuietMode(bool quiet_mode) {
260  quiet_mode_ = quiet_mode;
261  if (quiet_mode_) {
262    for (Notifications::iterator iter = notifications_.begin();
263         iter != notifications_.end();
264         ++iter) {
265      (*iter)->set_shown_as_popup(true);
266    }
267  }
268}
269
270Notification* NotificationList::GetNotificationById(const std::string& id) {
271  Notifications::iterator iter = GetNotification(id);
272  if (iter != notifications_.end())
273    return *iter;
274  return NULL;
275}
276
277NotificationList::Notifications NotificationList::GetVisibleNotifications(
278    const NotificationBlockers& blockers) const {
279  Notifications result;
280  for (Notifications::const_iterator iter = notifications_.begin();
281       iter != notifications_.end(); ++iter) {
282    bool should_show = true;
283    for (size_t i = 0; i < blockers.size(); ++i) {
284      if (!blockers[i]->ShouldShowNotification((*iter)->notifier_id())) {
285        should_show = false;
286        break;
287      }
288    }
289    if (should_show)
290      result.insert(*iter);
291  }
292
293  return result;
294}
295
296size_t NotificationList::NotificationCount(
297    const NotificationBlockers& blockers) const {
298  return GetVisibleNotifications(blockers).size();
299}
300
301size_t NotificationList::UnreadCount(
302    const NotificationBlockers& blockers) const {
303  Notifications notifications = GetVisibleNotifications(blockers);
304  size_t unread_count = 0;
305  for (Notifications::const_iterator iter = notifications.begin();
306       iter != notifications.end(); ++iter) {
307    if (!(*iter)->IsRead())
308      ++unread_count;
309  }
310  return unread_count;
311}
312
313NotificationList::Notifications::iterator NotificationList::GetNotification(
314    const std::string& id) {
315  for (Notifications::iterator iter = notifications_.begin();
316       iter != notifications_.end(); ++iter) {
317    if ((*iter)->id() == id)
318      return iter;
319  }
320  return notifications_.end();
321}
322
323void NotificationList::EraseNotification(Notifications::iterator iter) {
324  delete *iter;
325  notifications_.erase(iter);
326}
327
328void NotificationList::PushNotification(scoped_ptr<Notification> notification) {
329  // Ensure that notification.id is unique by erasing any existing
330  // notification with the same id (shouldn't normally happen).
331  Notifications::iterator iter = GetNotification(notification->id());
332  bool state_inherited = false;
333  if (iter != notifications_.end()) {
334    notification->CopyState(*iter);
335    state_inherited = true;
336    EraseNotification(iter);
337  }
338  // Add the notification to the the list and mark it unread and unshown.
339  if (!state_inherited) {
340    // TODO(mukai): needs to distinguish if a notification is dismissed by
341    // the quiet mode or user operation.
342    notification->set_is_read(false);
343    notification->set_shown_as_popup(message_center_visible_
344                                     || quiet_mode_
345                                     || notification->shown_as_popup());
346  }
347  // Take ownership. The notification can only be removed from the list
348  // in EraseNotification(), which will delete it.
349  notifications_.insert(notification.release());
350}
351
352}  // namespace message_center
353