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) |
| 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() { |
| 123 CancelAll(); |
| 124 } |
| 125 |
| 126 void NotificationUIManagerMac::Add(const Notification& notification, |
| 127 Profile* profile) { |
| 128 if (notification.is_html()) { |
| 129 builtin_manager_->Add(notification, profile); |
| 130 } else { |
| 131 id<CrUserNotification> replacee = FindNotificationWithReplacementId( |
| 132 notification.replace_id()); |
| 133 if (replacee) |
| 134 RemoveNotification(replacee); |
| 135 |
| 136 // Owned by ControllerNotification. |
| 137 id<CrUserNotification> ns_notification = |
| 138 [[NSClassFromString(@"NSUserNotification") alloc] init]; |
| 139 |
| 140 ns_notification.title = base::SysUTF16ToNSString(notification.title()); |
| 141 ns_notification.subtitle = |
| 142 base::SysUTF16ToNSString(notification.display_source()); |
| 143 ns_notification.informativeText = |
| 144 base::SysUTF16ToNSString(notification.body()); |
| 145 ns_notification.userInfo = |
| 146 [NSDictionary dictionaryWithObject:base::SysUTF8ToNSString( |
| 147 notification.notification_id()) |
| 148 forKey:kNotificationIDKey]; |
| 149 ns_notification.hasActionButton = NO; |
| 150 |
| 151 notification_map_.insert( |
| 152 std::make_pair(notification.notification_id(), |
| 153 new ControllerNotification(ns_notification, |
| 154 new Notification(notification)))); |
| 155 |
| 156 [GetNotificationCenter() deliverNotification:ns_notification]; |
| 157 } |
| 158 } |
| 159 |
| 160 bool NotificationUIManagerMac::CancelById(const std::string& notification_id) { |
| 161 NotificationMap::iterator it = notification_map_.find(notification_id); |
| 162 if (it == notification_map_.end()) |
| 163 return builtin_manager_->CancelById(notification_id); |
| 164 |
| 165 return RemoveNotification(it->second->view); |
| 166 } |
| 167 |
| 168 bool NotificationUIManagerMac::CancelAllBySourceOrigin( |
| 169 const GURL& source_origin) { |
| 170 bool success = builtin_manager_->CancelAllBySourceOrigin(source_origin); |
| 171 |
| 172 for (NotificationMap::iterator it = notification_map_.begin(); |
| 173 it != notification_map_.end();) { |
| 174 if (it->second->model->origin_url() == source_origin) { |
| 175 // RemoveNotification will erase from the map, invalidating iterator |
| 176 // references to the removed element. |
| 177 success |= RemoveNotification((it++)->second->view); |
| 178 } else { |
| 179 ++it; |
| 180 } |
| 181 } |
| 182 |
| 183 return success; |
| 184 } |
| 185 |
| 186 void NotificationUIManagerMac::CancelAll() { |
| 187 id<CrUserNotificationCenter> center = GetNotificationCenter(); |
| 188 |
| 189 // Calling RemoveNotification would loop many times over, so just replicate |
| 190 // a small bit of its logic here. |
| 191 for (NotificationMap::iterator it = notification_map_.begin(); |
| 192 it != notification_map_.end(); |
| 193 ++it) { |
| 194 it->second->model->Close(false); |
| 195 delete it->second; |
| 196 } |
| 197 notification_map_.clear(); |
| 198 |
| 199 // Clean up any lingering ones in the system tray. |
| 200 [center removeAllDeliveredNotifications]; |
| 201 |
| 202 builtin_manager_->CancelAll(); |
| 203 } |
| 204 |
| 205 BalloonCollection* NotificationUIManagerMac::balloon_collection() { |
| 206 return builtin_manager_->balloon_collection(); |
| 207 } |
| 208 |
| 209 NotificationPrefsManager* NotificationUIManagerMac::prefs_manager() { |
| 210 return builtin_manager_.get(); |
| 211 } |
| 212 |
| 213 void NotificationUIManagerMac::GetQueuedNotificationsForTesting( |
| 214 std::vector<const Notification*>* notifications) { |
| 215 return builtin_manager_->GetQueuedNotificationsForTesting(notifications); |
| 216 } |
| 217 |
| 218 const Notification* |
| 219 NotificationUIManagerMac::FindNotificationWithCocoaNotification( |
| 220 id<CrUserNotification> notification) const { |
| 221 std::string notification_id = base::SysNSStringToUTF8( |
| 222 [notification.userInfo objectForKey:kNotificationIDKey]); |
| 223 |
| 224 NotificationMap::const_iterator it = notification_map_.find(notification_id); |
| 225 if (it == notification_map_.end()) |
| 226 return NULL; |
| 227 |
| 228 return it->second->model; |
| 229 } |
| 230 |
| 231 bool NotificationUIManagerMac::RemoveNotification( |
| 232 id<CrUserNotification> notification) { |
| 233 std::string notification_id = base::SysNSStringToUTF8( |
| 234 [notification.userInfo objectForKey:kNotificationIDKey]); |
| 235 id<CrUserNotificationCenter> center = GetNotificationCenter(); |
| 236 |
| 237 // First remove all Cocoa notifications from the center that match the |
| 238 // notification. Notifications in the system tray do not share pointer |
| 239 // equality with the balloons or any other message delievered to the |
| 240 // delegate, so this loop must be run through every time to clean up stale |
| 241 // notifications. |
| 242 NSArray* delivered_notifications = center.deliveredNotifications; |
| 243 for (id<CrUserNotification> delivered in delivered_notifications) { |
| 244 if ([delivered isEqual:notification]) { |
| 245 [center removeDeliveredNotification:delivered]; |
| 246 } |
| 247 } |
| 248 |
| 249 // Then clean up the C++ model side. |
| 250 NotificationMap::iterator it = notification_map_.find(notification_id); |
| 251 if (it == notification_map_.end()) |
| 252 return false; |
| 253 |
| 254 it->second->model->Close(false); |
| 255 delete it->second; |
| 256 notification_map_.erase(it); |
| 257 |
| 258 return true; |
| 259 } |
| 260 |
| 261 id<CrUserNotification> |
| 262 NotificationUIManagerMac::FindNotificationWithReplacementId( |
| 263 const string16& replacement_id) const { |
| 264 for (NotificationMap::const_iterator it = notification_map_.begin(); |
| 265 it != notification_map_.end(); |
| 266 ++it) { |
| 267 if (it->second->model->replace_id() == replacement_id) |
| 268 return it->second->view; |
| 269 } |
| 270 return nil; |
| 271 } |
| 272 |
| 273 //////////////////////////////////////////////////////////////////////////////// |
| 274 |
| 275 @implementation NotificationCenterDelegate |
| 276 |
| 277 - (id)initWithManager:(NotificationUIManagerMac*)manager { |
| 278 if ((self = [super init])) { |
| 279 CHECK(manager); |
| 280 manager_ = manager; |
| 281 } |
| 282 return self; |
| 283 } |
| 284 |
| 285 - (void)userNotificationCenter:(NSUserNotificationCenter*)center |
| 286 didDeliverNotification:(id<CrUserNotification>)nsNotification { |
| 287 const Notification* notification = |
| 288 manager_->FindNotificationWithCocoaNotification(nsNotification); |
| 289 if (notification) |
| 290 notification->Display(); |
| 291 } |
| 292 |
| 293 - (void)userNotificationCenter:(NSUserNotificationCenter*)center |
| 294 didActivateNotification:(id<CrUserNotification>)nsNotification { |
| 295 const Notification* notification = |
| 296 manager_->FindNotificationWithCocoaNotification(nsNotification); |
| 297 if (notification) |
| 298 notification->Click(); |
| 299 } |
| 300 |
| 301 - (BOOL)userNotificationCenter:(NSUserNotificationCenter*)center |
| 302 shouldPresentNotification:(id<CrUserNotification>)nsNotification { |
| 303 // Always display notifications, regardless of whether the app is foreground. |
| 304 return YES; |
| 305 } |
| 306 |
| 307 @end |
OLD | NEW |