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/memory/scoped_ptr.h" | |
10 #include "base/strings/sys_string_conversions.h" | |
11 #include "chrome/browser/browser_process.h" | |
12 #include "chrome/browser/notifications/notification.h" | |
13 #include "chrome/browser/notifications/balloon_notification_ui_manager.h" | |
14 #include "chrome/browser/notifications/message_center_notification_manager.h" | |
15 #include "chrome/browser/notifications/message_center_settings_controller.h" | |
16 #include "chrome/browser/profiles/profile.h" | |
17 #include "chrome/browser/profiles/profile_info_cache.h" | |
18 #include "chrome/browser/profiles/profile_manager.h" | |
19 #include "ui/message_center/message_center_util.h" | |
20 | |
21 @class NSUserNotificationCenter; | |
22 | |
23 // Since NSUserNotification and NSUserNotificationCenter are new classes in | |
24 // 10.8, they cannot simply be declared with an @interface. An @implementation | |
25 // is needed to link, but providing one would cause a runtime conflict when | |
26 // running on 10.8. Instead, provide the interface defined as a protocol and | |
27 // use that instead, because sizeof(id<Protocol>) == sizeof(Class*). In order to | |
28 // instantiate, use NSClassFromString and simply assign the alloc/init'd result | |
29 // to an instance of the proper protocol. This way the compiler, linker, and | |
30 // loader are all happy. And the code isn't full of objc_msgSend. | |
31 @protocol CrUserNotification <NSObject> | |
32 @property(copy) NSString* title; | |
33 @property(copy) NSString* subtitle; | |
34 @property(copy) NSString* informativeText; | |
35 @property(copy) NSString* actionButtonTitle; | |
36 @property(copy) NSDictionary* userInfo; | |
37 @property(copy) NSDate* deliveryDate; | |
38 @property(copy) NSTimeZone* deliveryTimeZone; | |
39 @property(copy) NSDateComponents* deliveryRepeatInterval; | |
40 @property(readonly) NSDate* actualDeliveryDate; | |
41 @property(readonly, getter=isPresented) BOOL presented; | |
42 @property(readonly, getter=isRemote) BOOL remote; | |
43 @property(copy) NSString* soundName; | |
44 @property BOOL hasActionButton; | |
45 @end | |
46 | |
47 @protocol CrUserNotificationCenter | |
48 + (NSUserNotificationCenter*)defaultUserNotificationCenter; | |
49 @property(assign) id<NSUserNotificationCenterDelegate> delegate; | |
50 @property(copy) NSArray* scheduledNotifications; | |
51 - (void)scheduleNotification:(id<CrUserNotification>)notification; | |
52 - (void)removeScheduledNotification:(id<CrUserNotification>)notification; | |
53 @property(readonly) NSArray* deliveredNotifications; | |
54 - (void)deliverNotification:(id<CrUserNotification>)notification; | |
55 - (void)removeDeliveredNotification:(id<CrUserNotification>)notification; | |
56 - (void)removeAllDeliveredNotifications; | |
57 @end | |
58 | |
59 //////////////////////////////////////////////////////////////////////////////// | |
60 | |
61 namespace { | |
62 | |
63 // A "fun" way of saying: | |
64 // +[NSUserNotificationCenter defaultUserNotificationCenter]. | |
65 id<CrUserNotificationCenter> GetNotificationCenter() { | |
66 return [NSClassFromString(@"NSUserNotificationCenter") | |
67 performSelector:@selector(defaultUserNotificationCenter)]; | |
68 } | |
69 | |
70 // The key in NSUserNotification.userInfo that stores the C++ notification_id. | |
71 NSString* const kNotificationIDKey = @"notification_id"; | |
72 | |
73 } // namespace | |
74 | |
75 // A Cocoa class that can be the delegate of NSUserNotificationCenter that | |
76 // forwards commands to C++. | |
77 @interface NotificationCenterDelegate : NSObject | |
78 <NSUserNotificationCenterDelegate> { | |
79 @private | |
80 NotificationUIManagerMac* manager_; // Weak, owns self. | |
81 } | |
82 - (id)initWithManager:(NotificationUIManagerMac*)manager; | |
83 @end | |
84 | |
85 //////////////////////////////////////////////////////////////////////////////// | |
86 | |
87 NotificationUIManagerMac::ControllerNotification::ControllerNotification( | |
88 Profile* a_profile, | |
89 id<CrUserNotification> a_view, | |
90 Notification* a_model) | |
91 : profile(a_profile), | |
92 view(a_view), | |
93 model(a_model) { | |
94 } | |
95 | |
96 NotificationUIManagerMac::ControllerNotification::~ControllerNotification() { | |
97 [view release]; | |
98 delete model; | |
99 } | |
100 | |
101 //////////////////////////////////////////////////////////////////////////////// | |
102 | |
103 // static | |
104 NotificationUIManager* NotificationUIManager::Create(PrefService* local_state) { | |
105 // TODO(rsesek): Remove this function and merge it with the one in | |
106 // notification_ui_manager.cc. | |
107 if (DelegatesToMessageCenter()) { | |
108 ProfileInfoCache* profile_info_cache = | |
109 &g_browser_process->profile_manager()->GetProfileInfoCache(); | |
110 scoped_ptr<message_center::NotifierSettingsProvider> settings_provider( | |
111 new MessageCenterSettingsController(profile_info_cache)); | |
112 return new MessageCenterNotificationManager( | |
113 g_browser_process->message_center(), | |
114 local_state, | |
115 settings_provider.Pass()); | |
116 } | |
117 | |
118 BalloonNotificationUIManager* balloon_manager = NULL; | |
119 if (base::mac::IsOSMountainLionOrLater()) | |
120 balloon_manager = new NotificationUIManagerMac(local_state); | |
121 else | |
122 balloon_manager = new BalloonNotificationUIManager(local_state); | |
123 balloon_manager->SetBalloonCollection(BalloonCollection::Create()); | |
124 return balloon_manager; | |
125 } | |
126 | |
127 NotificationUIManagerMac::NotificationUIManagerMac(PrefService* local_state) | |
128 : BalloonNotificationUIManager(local_state), | |
129 delegate_([[NotificationCenterDelegate alloc] initWithManager:this]) { | |
130 DCHECK(!GetNotificationCenter().delegate); | |
131 GetNotificationCenter().delegate = delegate_.get(); | |
132 } | |
133 | |
134 NotificationUIManagerMac::~NotificationUIManagerMac() { | |
135 CancelAll(); | |
136 } | |
137 | |
138 void NotificationUIManagerMac::Add(const Notification& notification, | |
139 Profile* profile) { | |
140 if (notification.is_html()) { | |
141 BalloonNotificationUIManager::Add(notification, profile); | |
142 } else { | |
143 if (!notification.replace_id().empty()) { | |
144 id<CrUserNotification> replacee = FindNotificationWithReplacementId( | |
145 notification.replace_id()); | |
146 if (replacee) | |
147 RemoveNotification(replacee); | |
148 } | |
149 | |
150 // Owned by ControllerNotification. | |
151 id<CrUserNotification> ns_notification = | |
152 [[NSClassFromString(@"NSUserNotification") alloc] init]; | |
153 | |
154 ns_notification.title = base::SysUTF16ToNSString(notification.title()); | |
155 ns_notification.subtitle = | |
156 base::SysUTF16ToNSString(notification.display_source()); | |
157 ns_notification.informativeText = | |
158 base::SysUTF16ToNSString(notification.message()); | |
159 ns_notification.userInfo = | |
160 [NSDictionary dictionaryWithObject:base::SysUTF8ToNSString( | |
161 notification.notification_id()) | |
162 forKey:kNotificationIDKey]; | |
163 ns_notification.hasActionButton = NO; | |
164 | |
165 notification_map_.insert(std::make_pair( | |
166 notification.notification_id(), | |
167 new ControllerNotification(profile, | |
168 ns_notification, | |
169 new Notification(notification)))); | |
170 | |
171 [GetNotificationCenter() deliverNotification:ns_notification]; | |
172 } | |
173 } | |
174 | |
175 std::set<std::string> | |
176 NotificationUIManagerMac::GetAllIdsByProfileAndSourceOrigin( | |
177 Profile* profile, const GURL& source_origin) { | |
178 std::set<std::string> notification_ids = | |
179 BalloonNotificationUIManager::GetAllIdsByProfileAndSourceOrigin( | |
180 profile, source_origin); | |
181 | |
182 for (NotificationMap::iterator it = notification_map_.begin(); | |
183 it != notification_map_.end(); ++it) { | |
184 ControllerNotification* controller_notification = it->second; | |
185 Notification* model = controller_notification->model; | |
186 if (model->origin_url() == source_origin && | |
187 profile->IsSameProfile(controller_notification->profile)) { | |
188 notification_ids.insert(model->notification_id()); | |
189 } | |
190 } | |
191 return notification_ids; | |
192 } | |
193 | |
194 bool NotificationUIManagerMac::CancelById(const std::string& notification_id) { | |
195 NotificationMap::iterator it = notification_map_.find(notification_id); | |
196 if (it == notification_map_.end()) | |
197 return BalloonNotificationUIManager::CancelById(notification_id); | |
198 | |
199 return RemoveNotification(it->second->view); | |
200 } | |
201 | |
202 bool NotificationUIManagerMac::CancelAllBySourceOrigin( | |
203 const GURL& source_origin) { | |
204 bool success = | |
205 BalloonNotificationUIManager::CancelAllBySourceOrigin(source_origin); | |
206 | |
207 for (NotificationMap::iterator it = notification_map_.begin(); | |
208 it != notification_map_.end();) { | |
209 if (it->second->model->origin_url() == source_origin) { | |
210 // RemoveNotification will erase from the map, invalidating iterator | |
211 // references to the removed element. | |
212 success |= RemoveNotification((it++)->second->view); | |
213 } else { | |
214 ++it; | |
215 } | |
216 } | |
217 | |
218 return success; | |
219 } | |
220 | |
221 bool NotificationUIManagerMac::CancelAllByProfile(Profile* profile) { | |
222 bool success = BalloonNotificationUIManager::CancelAllByProfile(profile); | |
223 | |
224 for (NotificationMap::iterator it = notification_map_.begin(); | |
225 it != notification_map_.end();) { | |
226 if (it->second->profile == profile) { | |
227 // RemoveNotification will erase from the map, invalidating iterator | |
228 // references to the removed element. | |
229 success |= RemoveNotification((it++)->second->view); | |
230 } else { | |
231 ++it; | |
232 } | |
233 } | |
234 | |
235 return success; | |
236 } | |
237 | |
238 void NotificationUIManagerMac::CancelAll() { | |
239 id<CrUserNotificationCenter> center = GetNotificationCenter(); | |
240 | |
241 // Calling RemoveNotification would loop many times over, so just replicate | |
242 // a small bit of its logic here. | |
243 for (NotificationMap::iterator it = notification_map_.begin(); | |
244 it != notification_map_.end(); | |
245 ++it) { | |
246 it->second->model->Close(false); | |
247 delete it->second; | |
248 } | |
249 notification_map_.clear(); | |
250 | |
251 // Clean up any lingering ones in the system tray. | |
252 [center removeAllDeliveredNotifications]; | |
253 | |
254 BalloonNotificationUIManager::CancelAll(); | |
255 } | |
256 | |
257 const Notification* | |
258 NotificationUIManagerMac::FindNotificationWithCocoaNotification( | |
259 id<CrUserNotification> notification) const { | |
260 std::string notification_id = base::SysNSStringToUTF8( | |
261 [notification.userInfo objectForKey:kNotificationIDKey]); | |
262 | |
263 NotificationMap::const_iterator it = notification_map_.find(notification_id); | |
264 if (it == notification_map_.end()) | |
265 return NULL; | |
266 | |
267 return it->second->model; | |
268 } | |
269 | |
270 bool NotificationUIManagerMac::RemoveNotification( | |
271 id<CrUserNotification> notification) { | |
272 std::string notification_id = base::SysNSStringToUTF8( | |
273 [notification.userInfo objectForKey:kNotificationIDKey]); | |
274 id<CrUserNotificationCenter> center = GetNotificationCenter(); | |
275 | |
276 // First remove all Cocoa notifications from the center that match the | |
277 // notification. Notifications in the system tray do not share pointer | |
278 // equality with the balloons or any other message delievered to the | |
279 // delegate, so this loop must be run through every time to clean up stale | |
280 // notifications. | |
281 NSArray* delivered_notifications = center.deliveredNotifications; | |
282 for (id<CrUserNotification> delivered in delivered_notifications) { | |
283 if ([delivered isEqual:notification]) { | |
284 [center removeDeliveredNotification:delivered]; | |
285 } | |
286 } | |
287 | |
288 // Then clean up the C++ model side. | |
289 NotificationMap::iterator it = notification_map_.find(notification_id); | |
290 if (it == notification_map_.end()) | |
291 return false; | |
292 | |
293 it->second->model->Close(false); | |
294 delete it->second; | |
295 notification_map_.erase(it); | |
296 | |
297 return true; | |
298 } | |
299 | |
300 id<CrUserNotification> | |
301 NotificationUIManagerMac::FindNotificationWithReplacementId( | |
302 const base::string16& replacement_id) const { | |
303 for (NotificationMap::const_iterator it = notification_map_.begin(); | |
304 it != notification_map_.end(); | |
305 ++it) { | |
306 if (it->second->model->replace_id() == replacement_id) | |
307 return it->second->view; | |
308 } | |
309 return nil; | |
310 } | |
311 | |
312 //////////////////////////////////////////////////////////////////////////////// | |
313 | |
314 @implementation NotificationCenterDelegate | |
315 | |
316 - (id)initWithManager:(NotificationUIManagerMac*)manager { | |
317 if ((self = [super init])) { | |
318 CHECK(manager); | |
319 manager_ = manager; | |
320 } | |
321 return self; | |
322 } | |
323 | |
324 - (void)userNotificationCenter:(NSUserNotificationCenter*)center | |
325 didDeliverNotification:(id<CrUserNotification>)nsNotification { | |
326 const Notification* notification = | |
327 manager_->FindNotificationWithCocoaNotification(nsNotification); | |
328 if (notification) | |
329 notification->Display(); | |
330 } | |
331 | |
332 - (void)userNotificationCenter:(NSUserNotificationCenter*)center | |
333 didActivateNotification:(id<CrUserNotification>)nsNotification { | |
334 const Notification* notification = | |
335 manager_->FindNotificationWithCocoaNotification(nsNotification); | |
336 if (notification) | |
337 notification->Click(); | |
338 } | |
339 | |
340 - (BOOL)userNotificationCenter:(NSUserNotificationCenter*)center | |
341 shouldPresentNotification:(id<CrUserNotification>)nsNotification { | |
342 // Always display notifications, regardless of whether the app is foreground. | |
343 return YES; | |
344 } | |
345 | |
346 @end | |
OLD | NEW |