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

Side by Side Diff: chrome/browser/notifications/notification_ui_manager_mac.mm

Issue 10021026: [Mac] Implement a NotificationUIManager that uses Notification Center on 10.8 for text notifications (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address comments Created 8 years, 8 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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 "chrome/browser/notifications/notification_ui_manager_mac.h"
6
7 #include "base/mac/cocoa_protocols.h"
8 #include "base/mac/mac_util.h"
9 #include "base/sys_string_conversions.h"
10 #include "chrome/browser/notifications/notification.h"
11 #include "chrome/browser/notifications/notification_ui_manager_impl.h"
12
13 @class NSUserNotificationCenter;
14
15 // Since NSUserNotification and NSUserNotificationCenter are new classes in
16 // 10.8, they cannot simply be declared with an @interface. An @implementation
17 // is needed to link, but providing one would cause a runtime conflict when
18 // running on 10.8. Instead, provide the interface defined as a protocol and
19 // use that instead, because sizeof(id<Protocol>) == sizeof(Class*). In order to
20 // instantiate, use NSClassFromString and simply assign the alloc/init'd result
21 // to an instance of the proper protocol. This way the compiler, linker, and
22 // loader are all happy. And the code isn't full of objc_msgSend.
23 @protocol CrUserNotification <NSObject>
24 @property(copy) NSString* title;
25 @property(copy) NSString* subtitle;
26 @property(copy) NSString* informativeText;
27 @property(copy) NSString* actionButtonTitle;
28 @property(copy) NSDictionary* userInfo;
29 @property(copy) NSDate* deliveryDate;
30 @property(copy) NSTimeZone* deliveryTimeZone;
31 @property(copy) NSDateComponents* deliveryRepeatInterval;
32 @property(readonly) NSDate* actualDeliveryDate;
33 @property(readonly, getter=isPresented) BOOL presented;
34 @property(readonly, getter=isRemote) BOOL remote;
35 @property(copy) NSString* soundName;
36 @property BOOL hasActionButton;
37 @end
38
39 @protocol CrUserNotificationCenter
40 + (NSUserNotificationCenter*)defaultUserNotificationCenter;
41 @property(assign) id<NSUserNotificationCenterDelegate> delegate;
42 @property(copy) NSArray* scheduledNotifications;
43 - (void)scheduleNotification:(id<CrUserNotification>)notification;
44 - (void)removeScheduledNotification:(id<CrUserNotification>)notification;
45 @property(readonly) NSArray* deliveredNotifications;
46 - (void)deliverNotification:(id<CrUserNotification>)notification;
47 - (void)removeDeliveredNotification:(id<CrUserNotification>)notification;
48 - (void)removeAllDeliveredNotifications;
49 @end
50
51 ////////////////////////////////////////////////////////////////////////////////
52
53 namespace {
54
55 // A "fun" way of saying:
56 // +[NSUserNotificationCenter defaultUserNotificationCenter].
57 id<CrUserNotificationCenter> GetNotificationCenter() {
58 return [NSClassFromString(@"NSUserNotificationCenter")
59 performSelector:@selector(defaultUserNotificationCenter)];
60 }
61
62 // The key in NSUserNotification.userInfo that stores the C++ notification_id.
63 NSString* const kNotificationIDKey = @"notification_id";
64
65 } // namespace
66
67 // A Cocoa class that can be the delegate of NSUserNotificationCenter that
68 // forwards commands to C++.
69 @interface NotificationCenterDelegate : NSObject
70 <NSUserNotificationCenterDelegate> {
71 @private
72 NotificationUIManagerMac* manager_; // Weak, owns self.
73 }
74 - (id)initWithManager:(NotificationUIManagerMac*)manager;
75 @end
76
77 ////////////////////////////////////////////////////////////////////////////////
78
79 // static
80 NotificationUIManager* NotificationUIManager::Create(
81 PrefService* local_state,
82 BalloonCollection* balloons) {
83 NotificationUIManager* instance = NULL;
84 NotificationUIManagerImpl* impl = NULL;
85
86 if (base::mac::IsOSMountainLionOrLater()) {
87 NotificationUIManagerMac* mac_instance =
88 new NotificationUIManagerMac(local_state);
89 instance = mac_instance;
90 impl = mac_instance->builtin_manager();
91 } else {
92 instance = impl = new NotificationUIManagerImpl(local_state);
93 }
94
95 impl->Initialize(balloons);
96 balloons->set_space_change_listener(impl);
97
98 return instance;
99 }
100
101 NotificationUIManagerMac::NotificationUIManagerMac(PrefService* local_state)
102 : builtin_manager_(new NotificationUIManagerImpl(local_state)),
103 delegate_(ALLOW_THIS_IN_INITIALIZER_LIST(
104 [[NotificationCenterDelegate alloc] initWithManager:this])) {
105 DCHECK(!GetNotificationCenter().delegate);
106 GetNotificationCenter().delegate = delegate_.get();
107 }
108
109 NotificationUIManagerMac::~NotificationUIManagerMac() {
110 }
111
112 void NotificationUIManagerMac::Add(const Notification& notification,
113 Profile* profile) {
114 if (notification.is_html()) {
115 builtin_manager_->Add(notification, profile);
116 } else {
117 id<CrUserNotification> replacee = FindNotificationWithReplacementId(
118 notification.replace_id());
119 if (replacee)
120 RemoveNotification(replacee);
121
122 // Owned by notification_map_.
123 id<CrUserNotification> ns_notification =
124 [[NSClassFromString(@"NSUserNotification") alloc] init];
125
126 ns_notification.title = base::SysUTF16ToNSString(notification.title());
127 ns_notification.subtitle =
128 base::SysUTF16ToNSString(notification.display_source());
129 ns_notification.informativeText =
130 base::SysUTF16ToNSString(notification.body());
131 ns_notification.userInfo =
132 [NSDictionary dictionaryWithObject:base::SysUTF8ToNSString(
133 notification.notification_id())
134 forKey:kNotificationIDKey];
135 ns_notification.hasActionButton = NO;
136
137 notification_map_.insert(
138 std::make_pair(ns_notification, new Notification(notification)));
139
140 [GetNotificationCenter() deliverNotification:ns_notification];
141 }
142 }
143
144 bool NotificationUIManagerMac::CancelById(const std::string& notification_id) {
145 for (NotificationMap::iterator it = notification_map_.begin();
146 it != notification_map_.end();
147 ++it) {
148 if (it->second->notification_id() == notification_id) {
149 return RemoveNotification(it->first);
150 }
151 }
152
153 return builtin_manager_->CancelById(notification_id);
154 }
155
156 bool NotificationUIManagerMac::CancelAllBySourceOrigin(
157 const GURL& source_origin) {
158 bool success = builtin_manager_->CancelAllBySourceOrigin(source_origin);
159
160 for (NotificationMap::iterator it = notification_map_.begin();
161 it != notification_map_.end();
162 ++it) {
163 if (it->second->origin_url() == source_origin) {
164 success |= RemoveNotification(it->first);
165 }
166 }
167
168 return success;
169 }
170
171 void NotificationUIManagerMac::CancelAll() {
172 id<CrUserNotificationCenter> center = GetNotificationCenter();
173
174 // Calling RemoveNotification would loop many times over, so just replicate
175 // a small bit of its logic here.
176 for (NotificationMap::iterator it = notification_map_.begin();
177 it != notification_map_.end();
178 ++it) {
179 [center removeDeliveredNotification:it->first];
180 [it->first release];
181
182 it->second->Close(false);
183 delete it->second;
184 }
185 notification_map_.clear();
186
187 // Clean up any lingering ones in the system tray.
188 for (id<CrUserNotification> notification in center.deliveredNotifications) {
189 [center removeDeliveredNotification:notification];
190 }
191
192 builtin_manager_->CancelAll();
193 }
194
195 BalloonCollection* NotificationUIManagerMac::balloon_collection() {
196 return builtin_manager_->balloon_collection();
197 }
198
199 NotificationPrefsManager* NotificationUIManagerMac::prefs_manager() {
200 return builtin_manager_.get();
201 }
202
203 void NotificationUIManagerMac::GetQueuedNotificationsForTesting(
204 std::vector<const Notification*>* notifications) {
205 return builtin_manager_->GetQueuedNotificationsForTesting(notifications);
206 }
207
208 const Notification*
209 NotificationUIManagerMac::FindNotificationWithCocoaNotification(
210 id<CrUserNotification> notification) {
211 std::string notification_id = base::SysNSStringToUTF8(
212 [notification.userInfo objectForKey:kNotificationIDKey]);
213
214 for (NotificationMap::iterator it = notification_map_.begin();
jianli 2012/04/10 00:54:14 nit: use const_iterator
Robert Sesek 2012/04/12 17:48:47 Done.
215 it != notification_map_.end();
216 ++it) {
217 if (it->second->notification_id() == notification_id)
218 return it->second;
219 }
220 return NULL;
221 }
222
223 bool NotificationUIManagerMac::RemoveNotification(
224 id<CrUserNotification> notification) {
225 std::string notification_id = base::SysNSStringToUTF8(
226 [notification.userInfo objectForKey:kNotificationIDKey]);
227 id<CrUserNotificationCenter> center = GetNotificationCenter();
228
229 // First remove all Cocoa notifications from the center that match the
230 // notification. Notifications in the system tray do not share pointer
231 // equality with the balloons or any other message delievered to the
232 // delegate, so this loop must be run through every time to clean up stale
233 // notifications.
234 NSArray* delivered_notifications = center.deliveredNotifications;
235 for (id<CrUserNotification> delivered in delivered_notifications) {
236 if ([delivered isEqual:notification]) {
237 [center removeDeliveredNotification:delivered];
238 }
239 }
240
241 bool did_remove = false;
242
243 // Then go through and remove any C++ notifications that match the
244 // notification ID, and release any ObjC notifications to which this still
245 // owns a reference.
246 for (NotificationMap::iterator it = notification_map_.begin();
247 it != notification_map_.end();
248 ++it) {
249 if (it->second->notification_id() == notification_id) {
250 it->second->Close(false);
251 delete it->second;
252
253 [it->first release];
254
255 notification_map_.erase(it);
jianli 2012/04/10 00:54:14 Is |it| still valid after erasing the element for
Robert Sesek 2012/04/12 17:48:47 Fixed by not erasing in a loop.
256
257 did_remove = true;
258 }
259 }
260
261 return did_remove;
262 }
263
264 id<CrUserNotification>
265 NotificationUIManagerMac::FindNotificationWithReplacementId(
266 const string16& replacement_id) {
267 for (NotificationMap::iterator it = notification_map_.begin();
268 it != notification_map_.end();
269 ++it) {
270 if (it->second->replace_id() == replacement_id)
271 return it->first;
272 }
273 return nil;
274 }
275
276 ////////////////////////////////////////////////////////////////////////////////
277
278 @implementation NotificationCenterDelegate
279
280 - (id)initWithManager:(NotificationUIManagerMac*)manager {
281 if ((self = [super init])) {
282 CHECK(manager);
283 manager_ = manager;
284 }
285 return self;
286 }
287
288 - (void)userNotificationCenter:(NSUserNotificationCenter*)center
289 didDeliverNotification:(id<CrUserNotification>)nsNotification {
290 const Notification* notification =
291 manager_->FindNotificationWithCocoaNotification(nsNotification);
292 if (notification)
293 notification->Display();
294 }
295
296 - (void)userNotificationCenter:(NSUserNotificationCenter*)center
297 didActivateNotification:(id<CrUserNotification>)nsNotification {
298 const Notification* notification =
299 manager_->FindNotificationWithCocoaNotification(nsNotification);
300 if (notification)
301 notification->Click();
302 }
303
304 - (BOOL)userNotificationCenter:(NSUserNotificationCenter*)center
305 shouldPresentNotification:(id<CrUserNotification>)nsNotification {
306 return YES;
307 }
308
309 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698