OLD | NEW |
---|---|
(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::ControllerNotification::ControllerNotification( | |
102 id<CrUserNotification> a_view, Notification* a_model) | |
jianli
2012/04/13 00:03:35
nit: you don't need to use different name for the
Robert Sesek
2012/04/16 14:20:27
Clang does not agree and reports "error: field is
Avi (use Gerrit)
2012/04/16 14:33:45
I was about to lay down some standard smackdown, b
jianli
2012/04/17 17:14:27
The problem might be due to the type of view: id<C
Robert Sesek
2012/04/17 17:31:12
Also produces the warning.
Nico
2012/04/17 23:58:10
FWIW, I can't see this warning locally or on the b
| |
103 : view(a_view), | |
104 model(a_model) { | |
105 } | |
106 | |
107 NotificationUIManagerMac::ControllerNotification::~ControllerNotification() { | |
108 [view release]; | |
109 delete model; | |
110 } | |
111 | |
112 //////////////////////////////////////////////////////////////////////////////// | |
113 | |
114 NotificationUIManagerMac::NotificationUIManagerMac(PrefService* local_state) | |
115 : builtin_manager_(new NotificationUIManagerImpl(local_state)), | |
116 delegate_(ALLOW_THIS_IN_INITIALIZER_LIST( | |
117 [[NotificationCenterDelegate alloc] initWithManager:this])) { | |
118 DCHECK(!GetNotificationCenter().delegate); | |
119 GetNotificationCenter().delegate = delegate_.get(); | |
120 } | |
121 | |
122 NotificationUIManagerMac::~NotificationUIManagerMac() { | |
jianli
2012/04/13 00:03:35
Do we want to clean up notification_map_?
Robert Sesek
2012/04/16 14:20:27
Done.
| |
123 } | |
124 | |
125 void NotificationUIManagerMac::Add(const Notification& notification, | |
126 Profile* profile) { | |
127 if (notification.is_html()) { | |
128 builtin_manager_->Add(notification, profile); | |
129 } else { | |
130 id<CrUserNotification> replacee = FindNotificationWithReplacementId( | |
131 notification.replace_id()); | |
132 if (replacee) | |
133 RemoveNotification(replacee); | |
134 | |
135 // Owned by ControllerNotification. | |
136 id<CrUserNotification> ns_notification = | |
137 [[NSClassFromString(@"NSUserNotification") alloc] init]; | |
138 | |
139 ns_notification.title = base::SysUTF16ToNSString(notification.title()); | |
140 ns_notification.subtitle = | |
141 base::SysUTF16ToNSString(notification.display_source()); | |
142 ns_notification.informativeText = | |
143 base::SysUTF16ToNSString(notification.body()); | |
144 ns_notification.userInfo = | |
145 [NSDictionary dictionaryWithObject:base::SysUTF8ToNSString( | |
146 notification.notification_id()) | |
147 forKey:kNotificationIDKey]; | |
148 ns_notification.hasActionButton = NO; | |
149 | |
150 notification_map_.insert( | |
151 std::make_pair(notification.notification_id(), | |
152 new ControllerNotification(ns_notification, | |
153 new Notification(notification)))); | |
154 | |
155 [GetNotificationCenter() deliverNotification:ns_notification]; | |
156 } | |
157 } | |
158 | |
159 bool NotificationUIManagerMac::CancelById(const std::string& notification_id) { | |
160 NotificationMap::iterator it = notification_map_.find(notification_id); | |
161 if (it == notification_map_.end()) | |
162 return builtin_manager_->CancelById(notification_id); | |
163 | |
164 return RemoveNotification(it->second->view); | |
165 } | |
166 | |
167 bool NotificationUIManagerMac::CancelAllBySourceOrigin( | |
168 const GURL& source_origin) { | |
169 bool success = builtin_manager_->CancelAllBySourceOrigin(source_origin); | |
170 | |
171 for (NotificationMap::iterator it = notification_map_.begin(); | |
172 it != notification_map_.end(); | |
173 ++it) { | |
174 if (it->second->model->origin_url() == source_origin) { | |
175 success |= RemoveNotification(it->second->view); | |
jianli
2012/04/13 00:03:35
RemoveNotification calls erase, which might invali
Robert Sesek
2012/04/16 14:20:27
Done.
| |
176 } | |
177 } | |
178 | |
179 return success; | |
180 } | |
181 | |
182 void NotificationUIManagerMac::CancelAll() { | |
183 id<CrUserNotificationCenter> center = GetNotificationCenter(); | |
184 | |
185 // Calling RemoveNotification would loop many times over, so just replicate | |
186 // a small bit of its logic here. | |
187 for (NotificationMap::iterator it = notification_map_.begin(); | |
188 it != notification_map_.end(); | |
189 ++it) { | |
190 it->second->model->Close(false); | |
191 delete it->second; | |
192 } | |
193 notification_map_.clear(); | |
194 | |
195 // Clean up any lingering ones in the system tray. | |
196 [center removeAllDeliveredNotifications]; | |
197 | |
198 builtin_manager_->CancelAll(); | |
199 } | |
200 | |
201 BalloonCollection* NotificationUIManagerMac::balloon_collection() { | |
202 return builtin_manager_->balloon_collection(); | |
203 } | |
204 | |
205 NotificationPrefsManager* NotificationUIManagerMac::prefs_manager() { | |
206 return builtin_manager_.get(); | |
207 } | |
208 | |
209 void NotificationUIManagerMac::GetQueuedNotificationsForTesting( | |
210 std::vector<const Notification*>* notifications) { | |
211 return builtin_manager_->GetQueuedNotificationsForTesting(notifications); | |
212 } | |
213 | |
214 const Notification* | |
215 NotificationUIManagerMac::FindNotificationWithCocoaNotification( | |
216 id<CrUserNotification> notification) const { | |
217 std::string notification_id = base::SysNSStringToUTF8( | |
218 [notification.userInfo objectForKey:kNotificationIDKey]); | |
219 | |
220 NotificationMap::const_iterator it = notification_map_.find(notification_id); | |
221 if (it == notification_map_.end()) | |
222 return NULL; | |
223 | |
224 return it->second->model; | |
225 } | |
226 | |
227 bool NotificationUIManagerMac::RemoveNotification( | |
228 id<CrUserNotification> notification) { | |
229 std::string notification_id = base::SysNSStringToUTF8( | |
230 [notification.userInfo objectForKey:kNotificationIDKey]); | |
231 id<CrUserNotificationCenter> center = GetNotificationCenter(); | |
232 | |
233 // First remove all Cocoa notifications from the center that match the | |
234 // notification. Notifications in the system tray do not share pointer | |
235 // equality with the balloons or any other message delievered to the | |
236 // delegate, so this loop must be run through every time to clean up stale | |
237 // notifications. | |
238 NSArray* delivered_notifications = center.deliveredNotifications; | |
239 for (id<CrUserNotification> delivered in delivered_notifications) { | |
240 if ([delivered isEqual:notification]) { | |
241 [center removeDeliveredNotification:delivered]; | |
242 } | |
243 } | |
244 | |
245 // Then clean up the C++ model side. | |
246 NotificationMap::iterator it = notification_map_.find(notification_id); | |
247 if (it == notification_map_.end()) | |
248 return false; | |
249 | |
250 it->second->model->Close(false); | |
251 delete it->second; | |
252 notification_map_.erase(it); | |
253 | |
254 return true; | |
255 } | |
256 | |
257 id<CrUserNotification> | |
258 NotificationUIManagerMac::FindNotificationWithReplacementId( | |
259 const string16& replacement_id) const { | |
260 for (NotificationMap::const_iterator it = notification_map_.begin(); | |
261 it != notification_map_.end(); | |
262 ++it) { | |
263 if (it->second->model->replace_id() == replacement_id) | |
264 return it->second->view; | |
265 } | |
266 return nil; | |
267 } | |
268 | |
269 //////////////////////////////////////////////////////////////////////////////// | |
270 | |
271 @implementation NotificationCenterDelegate | |
272 | |
273 - (id)initWithManager:(NotificationUIManagerMac*)manager { | |
274 if ((self = [super init])) { | |
275 CHECK(manager); | |
276 manager_ = manager; | |
277 } | |
278 return self; | |
279 } | |
280 | |
281 - (void)userNotificationCenter:(NSUserNotificationCenter*)center | |
282 didDeliverNotification:(id<CrUserNotification>)nsNotification { | |
283 const Notification* notification = | |
284 manager_->FindNotificationWithCocoaNotification(nsNotification); | |
285 if (notification) | |
286 notification->Display(); | |
287 } | |
288 | |
289 - (void)userNotificationCenter:(NSUserNotificationCenter*)center | |
290 didActivateNotification:(id<CrUserNotification>)nsNotification { | |
291 const Notification* notification = | |
292 manager_->FindNotificationWithCocoaNotification(nsNotification); | |
293 if (notification) | |
294 notification->Click(); | |
295 } | |
296 | |
297 - (BOOL)userNotificationCenter:(NSUserNotificationCenter*)center | |
298 shouldPresentNotification:(id<CrUserNotification>)nsNotification { | |
299 // Always display notifications, regardless of whether the app is foreground. | |
300 return YES; | |
301 } | |
302 | |
303 @end | |
OLD | NEW |