Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / notifications / notification_ui_manager_mac.mm
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