| 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 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 |