Chromium Code Reviews| 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, we provide the interface defined as a protocol and | |
|
Mark Mentovai
2012/04/09 16:39:41
we -> (null)
Robert Sesek
2012/04/09 21:36:38
Done.
| |
| 19 // use that instead, because sizeof(id<Protocol>) == sizeof(Class*). In order to | |
| 20 // instantiate, you use NSClassFromString and simply assign the alloc/init'd | |
|
Mark Mentovai
2012/04/09 16:39:41
you -> (null)
Robert Sesek
2012/04/09 21:36:38
Done.
| |
| 21 // result to an instance of the proper protocol. This way the compiler, linker, | |
| 22 // and 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() { | |
|
Mark Mentovai
2012/04/09 16:39:41
Remove any dangling notifications here?
Robert Sesek
2012/04/09 21:36:38
No, clients do that with CancelAll() at shutdown.
| |
| 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> notif = | |
|
Mark Mentovai
2012/04/09 16:39:41
notif again, throughout this file :/
| |
| 124 [[NSClassFromString(@"NSUserNotification") alloc] init]; | |
| 125 | |
| 126 notif.title = base::SysUTF16ToNSString(notification.title()); | |
| 127 notif.subtitle = base::SysUTF16ToNSString(notification.display_source()); | |
| 128 notif.informativeText = base::SysUTF16ToNSString(notification.body()); | |
| 129 notif.userInfo = | |
| 130 [NSDictionary dictionaryWithObject:base::SysUTF8ToNSString( | |
| 131 notification.notification_id()) | |
| 132 forKey:kNotificationIDKey]; | |
| 133 notif.hasActionButton = NO; | |
| 134 | |
| 135 notification_map_.insert( | |
| 136 std::make_pair(notif, new Notification(notification))); | |
| 137 | |
| 138 [GetNotificationCenter() deliverNotification:notif]; | |
| 139 } | |
| 140 } | |
| 141 | |
| 142 bool NotificationUIManagerMac::CancelById(const std::string& notification_id) { | |
| 143 for (NotificationMap::iterator it = notification_map_.begin(); | |
| 144 it != notification_map_.end(); | |
| 145 ++it) { | |
| 146 if (it->second->notification_id() == notification_id) { | |
| 147 return RemoveNotification(it->first); | |
| 148 } | |
| 149 } | |
| 150 | |
| 151 return builtin_manager_->CancelById(notification_id); | |
| 152 } | |
| 153 | |
| 154 bool NotificationUIManagerMac::CancelAllBySourceOrigin( | |
| 155 const GURL& source_origin) { | |
| 156 bool success = builtin_manager_->CancelAllBySourceOrigin(source_origin); | |
| 157 | |
| 158 for (NotificationMap::iterator it = notification_map_.begin(); | |
| 159 it != notification_map_.end(); | |
| 160 ++it) { | |
| 161 if (it->second->origin_url() == source_origin) { | |
| 162 success |= RemoveNotification(it->first); | |
| 163 } | |
| 164 } | |
| 165 | |
| 166 return success; | |
| 167 } | |
| 168 | |
| 169 void NotificationUIManagerMac::CancelAll() { | |
| 170 id<CrUserNotificationCenter> center = GetNotificationCenter(); | |
| 171 | |
| 172 // Calling RemoveNotification would loop many times over, so just replicate | |
| 173 // a small bit of its logic here. | |
| 174 for (NotificationMap::iterator it = notification_map_.begin(); | |
| 175 it != notification_map_.end(); | |
| 176 ++it) { | |
| 177 [center removeDeliveredNotification:it->first]; | |
| 178 [it->first release]; | |
| 179 | |
| 180 it->second->Close(false); | |
| 181 delete it->second; | |
| 182 } | |
| 183 notification_map_.clear(); | |
| 184 | |
| 185 // Clean up any lingering ones in the system tray. | |
| 186 for (id<CrUserNotification> notif in center.deliveredNotifications) { | |
| 187 [center removeDeliveredNotification:notif]; | |
| 188 } | |
| 189 | |
| 190 builtin_manager_->CancelAll(); | |
| 191 } | |
| 192 | |
| 193 BalloonCollection* NotificationUIManagerMac::balloon_collection() { | |
| 194 return builtin_manager_->balloon_collection(); | |
| 195 } | |
| 196 | |
| 197 NotificationPrefsManager* NotificationUIManagerMac::prefs_manager() { | |
| 198 return builtin_manager_.get(); | |
| 199 } | |
| 200 | |
| 201 void NotificationUIManagerMac::GetQueuedNotificationsForTesting( | |
| 202 std::vector<const Notification*>* notifications) { | |
| 203 return builtin_manager_->GetQueuedNotificationsForTesting(notifications); | |
| 204 } | |
| 205 | |
| 206 const Notification* | |
| 207 NotificationUIManagerMac::FindNotificationWithCocoaNotification( | |
| 208 id<CrUserNotification> notif) { | |
| 209 std::string notification_id = base::SysNSStringToUTF8( | |
| 210 [notif.userInfo objectForKey:kNotificationIDKey]); | |
| 211 | |
| 212 for (NotificationMap::iterator it = notification_map_.begin(); | |
| 213 it != notification_map_.end(); | |
| 214 ++it) { | |
| 215 if (it->second->notification_id() == notification_id) | |
| 216 return it->second; | |
| 217 } | |
| 218 return NULL; | |
| 219 } | |
| 220 | |
| 221 bool NotificationUIManagerMac::RemoveNotification( | |
| 222 id<CrUserNotification> notif) { | |
| 223 std::string notification_id = base::SysNSStringToUTF8( | |
| 224 [notif.userInfo objectForKey:kNotificationIDKey]); | |
| 225 id<CrUserNotificationCenter> center = GetNotificationCenter(); | |
| 226 | |
| 227 // First remove all Cocoa notifications from the center that match the | |
| 228 // notification. Notifications in the system tray do not share pointer | |
| 229 // equality with the balloons or any other message delievered to the | |
| 230 // delegate, so this loop must be run through every time to clean up stale | |
| 231 // notifications. | |
| 232 NSArray* delivered_notifications = center.deliveredNotifications; | |
| 233 for (id<CrUserNotification> delivered_notif in delivered_notifications) { | |
| 234 if ([delivered_notif isEqual:notif]) { | |
| 235 [center removeDeliveredNotification:delivered_notif]; | |
| 236 } | |
| 237 } | |
| 238 | |
| 239 bool did_remove = false; | |
| 240 | |
| 241 // Then go through and remove any C++ notifications that match the | |
| 242 // notification ID, and release any ObjC notifications to which this still | |
| 243 // owns a reference. | |
| 244 for (NotificationMap::iterator it = notification_map_.begin(); | |
| 245 it != notification_map_.end(); | |
| 246 ++it) { | |
| 247 if (it->second->notification_id() == notification_id) { | |
| 248 it->second->Close(false); | |
| 249 delete it->second; | |
| 250 | |
| 251 [it->first release]; | |
| 252 | |
| 253 notification_map_.erase(it); | |
| 254 | |
| 255 did_remove = true; | |
| 256 } | |
| 257 } | |
| 258 | |
| 259 return did_remove; | |
| 260 } | |
| 261 | |
| 262 id<CrUserNotification> | |
| 263 NotificationUIManagerMac::FindNotificationWithReplacementId( | |
| 264 const string16& replacement_id) { | |
| 265 for (NotificationMap::iterator it = notification_map_.begin(); | |
| 266 it != notification_map_.end(); | |
| 267 ++it) { | |
| 268 if (it->second->replace_id() == replacement_id) | |
| 269 return it->first; | |
| 270 } | |
| 271 return nil; | |
| 272 } | |
| 273 | |
| 274 //////////////////////////////////////////////////////////////////////////////// | |
| 275 | |
| 276 @implementation NotificationCenterDelegate | |
| 277 | |
| 278 - (id)initWithManager:(NotificationUIManagerMac*)manager { | |
| 279 if ((self = [super init])) { | |
| 280 CHECK(manager); | |
| 281 manager_ = manager; | |
| 282 } | |
| 283 return self; | |
| 284 } | |
| 285 | |
| 286 - (void)userNotificationCenter:(NSUserNotificationCenter*)center | |
| 287 didDeliverNotification:(id<CrUserNotification>)notification { | |
| 288 const Notification* notif = manager_->FindNotificationWithCocoaNotification( | |
| 289 notification); | |
| 290 if (notif) | |
| 291 notif->Display(); | |
| 292 } | |
| 293 | |
| 294 - (void)userNotificationCenter:(NSUserNotificationCenter*)center | |
| 295 didActivateNotification:(id<CrUserNotification>)notification { | |
| 296 const Notification* notif = manager_->FindNotificationWithCocoaNotification( | |
| 297 notification); | |
| 298 if (notif) | |
| 299 notif->Click(); | |
| 300 } | |
| 301 | |
| 302 - (BOOL)userNotificationCenter:(NSUserNotificationCenter*)center | |
| 303 shouldPresentNotification:(id<CrUserNotification>)notification { | |
| 304 return YES; | |
| 305 } | |
| 306 | |
| 307 @end | |
| OLD | NEW |