Chromium Code Reviews| Index: chrome/browser/notifications/notification_ui_manager_mac.mm |
| diff --git a/chrome/browser/notifications/notification_ui_manager_mac.mm b/chrome/browser/notifications/notification_ui_manager_mac.mm |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..59130703fa17f9d15d18f225d134716b330634e0 |
| --- /dev/null |
| +++ b/chrome/browser/notifications/notification_ui_manager_mac.mm |
| @@ -0,0 +1,309 @@ |
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "chrome/browser/notifications/notification_ui_manager_mac.h" |
| + |
| +#include "base/mac/cocoa_protocols.h" |
| +#include "base/mac/mac_util.h" |
| +#include "base/sys_string_conversions.h" |
| +#include "chrome/browser/notifications/notification.h" |
| +#include "chrome/browser/notifications/notification_ui_manager_impl.h" |
| + |
| +@class NSUserNotificationCenter; |
| + |
| +// Since NSUserNotification and NSUserNotificationCenter are new classes in |
| +// 10.8, they cannot simply be declared with an @interface. An @implementation |
| +// is needed to link, but providing one would cause a runtime conflict when |
| +// running on 10.8. Instead, provide the interface defined as a protocol and |
| +// use that instead, because sizeof(id<Protocol>) == sizeof(Class*). In order to |
| +// instantiate, use NSClassFromString and simply assign the alloc/init'd result |
| +// to an instance of the proper protocol. This way the compiler, linker, and |
| +// loader are all happy. And the code isn't full of objc_msgSend. |
| +@protocol CrUserNotification <NSObject> |
| +@property(copy) NSString* title; |
| +@property(copy) NSString* subtitle; |
| +@property(copy) NSString* informativeText; |
| +@property(copy) NSString* actionButtonTitle; |
| +@property(copy) NSDictionary* userInfo; |
| +@property(copy) NSDate* deliveryDate; |
| +@property(copy) NSTimeZone* deliveryTimeZone; |
| +@property(copy) NSDateComponents* deliveryRepeatInterval; |
| +@property(readonly) NSDate* actualDeliveryDate; |
| +@property(readonly, getter=isPresented) BOOL presented; |
| +@property(readonly, getter=isRemote) BOOL remote; |
| +@property(copy) NSString* soundName; |
| +@property BOOL hasActionButton; |
| +@end |
| + |
| +@protocol CrUserNotificationCenter |
| ++ (NSUserNotificationCenter*)defaultUserNotificationCenter; |
| +@property(assign) id<NSUserNotificationCenterDelegate> delegate; |
| +@property(copy) NSArray* scheduledNotifications; |
| +- (void)scheduleNotification:(id<CrUserNotification>)notification; |
| +- (void)removeScheduledNotification:(id<CrUserNotification>)notification; |
| +@property(readonly) NSArray* deliveredNotifications; |
| +- (void)deliverNotification:(id<CrUserNotification>)notification; |
| +- (void)removeDeliveredNotification:(id<CrUserNotification>)notification; |
| +- (void)removeAllDeliveredNotifications; |
| +@end |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| + |
| +namespace { |
| + |
| +// A "fun" way of saying: |
| +// +[NSUserNotificationCenter defaultUserNotificationCenter]. |
| +id<CrUserNotificationCenter> GetNotificationCenter() { |
| + return [NSClassFromString(@"NSUserNotificationCenter") |
| + performSelector:@selector(defaultUserNotificationCenter)]; |
| +} |
| + |
| +// The key in NSUserNotification.userInfo that stores the C++ notification_id. |
| +NSString* const kNotificationIDKey = @"notification_id"; |
| + |
| +} // namespace |
| + |
| +// A Cocoa class that can be the delegate of NSUserNotificationCenter that |
| +// forwards commands to C++. |
| +@interface NotificationCenterDelegate : NSObject |
| + <NSUserNotificationCenterDelegate> { |
| + @private |
| + NotificationUIManagerMac* manager_; // Weak, owns self. |
| +} |
| +- (id)initWithManager:(NotificationUIManagerMac*)manager; |
| +@end |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| + |
| +// static |
| +NotificationUIManager* NotificationUIManager::Create( |
| + PrefService* local_state, |
| + BalloonCollection* balloons) { |
| + NotificationUIManager* instance = NULL; |
| + NotificationUIManagerImpl* impl = NULL; |
| + |
| + if (base::mac::IsOSMountainLionOrLater()) { |
| + NotificationUIManagerMac* mac_instance = |
| + new NotificationUIManagerMac(local_state); |
| + instance = mac_instance; |
| + impl = mac_instance->builtin_manager(); |
| + } else { |
| + instance = impl = new NotificationUIManagerImpl(local_state); |
| + } |
| + |
| + impl->Initialize(balloons); |
| + balloons->set_space_change_listener(impl); |
| + |
| + return instance; |
| +} |
| + |
| +NotificationUIManagerMac::NotificationUIManagerMac(PrefService* local_state) |
| + : builtin_manager_(new NotificationUIManagerImpl(local_state)), |
| + delegate_(ALLOW_THIS_IN_INITIALIZER_LIST( |
| + [[NotificationCenterDelegate alloc] initWithManager:this])) { |
| + DCHECK(!GetNotificationCenter().delegate); |
| + GetNotificationCenter().delegate = delegate_.get(); |
| +} |
| + |
| +NotificationUIManagerMac::~NotificationUIManagerMac() { |
| +} |
| + |
| +void NotificationUIManagerMac::Add(const Notification& notification, |
| + Profile* profile) { |
| + if (notification.is_html()) { |
| + builtin_manager_->Add(notification, profile); |
| + } else { |
| + id<CrUserNotification> replacee = FindNotificationWithReplacementId( |
| + notification.replace_id()); |
| + if (replacee) |
| + RemoveNotification(replacee); |
| + |
| + // Owned by notification_map_. |
| + id<CrUserNotification> ns_notification = |
| + [[NSClassFromString(@"NSUserNotification") alloc] init]; |
| + |
| + ns_notification.title = base::SysUTF16ToNSString(notification.title()); |
| + ns_notification.subtitle = |
| + base::SysUTF16ToNSString(notification.display_source()); |
| + ns_notification.informativeText = |
| + base::SysUTF16ToNSString(notification.body()); |
| + ns_notification.userInfo = |
| + [NSDictionary dictionaryWithObject:base::SysUTF8ToNSString( |
| + notification.notification_id()) |
| + forKey:kNotificationIDKey]; |
| + ns_notification.hasActionButton = NO; |
| + |
| + notification_map_.insert( |
| + std::make_pair(ns_notification, new Notification(notification))); |
| + |
| + [GetNotificationCenter() deliverNotification:ns_notification]; |
| + } |
| +} |
| + |
| +bool NotificationUIManagerMac::CancelById(const std::string& notification_id) { |
| + for (NotificationMap::iterator it = notification_map_.begin(); |
| + it != notification_map_.end(); |
| + ++it) { |
| + if (it->second->notification_id() == notification_id) { |
| + return RemoveNotification(it->first); |
| + } |
| + } |
| + |
| + return builtin_manager_->CancelById(notification_id); |
| +} |
| + |
| +bool NotificationUIManagerMac::CancelAllBySourceOrigin( |
| + const GURL& source_origin) { |
| + bool success = builtin_manager_->CancelAllBySourceOrigin(source_origin); |
| + |
| + for (NotificationMap::iterator it = notification_map_.begin(); |
| + it != notification_map_.end(); |
| + ++it) { |
| + if (it->second->origin_url() == source_origin) { |
| + success |= RemoveNotification(it->first); |
| + } |
| + } |
| + |
| + return success; |
| +} |
| + |
| +void NotificationUIManagerMac::CancelAll() { |
| + id<CrUserNotificationCenter> center = GetNotificationCenter(); |
| + |
| + // Calling RemoveNotification would loop many times over, so just replicate |
| + // a small bit of its logic here. |
| + for (NotificationMap::iterator it = notification_map_.begin(); |
| + it != notification_map_.end(); |
| + ++it) { |
| + [center removeDeliveredNotification:it->first]; |
| + [it->first release]; |
| + |
| + it->second->Close(false); |
| + delete it->second; |
| + } |
| + notification_map_.clear(); |
| + |
| + // Clean up any lingering ones in the system tray. |
| + for (id<CrUserNotification> notification in center.deliveredNotifications) { |
| + [center removeDeliveredNotification:notification]; |
| + } |
| + |
| + builtin_manager_->CancelAll(); |
| +} |
| + |
| +BalloonCollection* NotificationUIManagerMac::balloon_collection() { |
| + return builtin_manager_->balloon_collection(); |
| +} |
| + |
| +NotificationPrefsManager* NotificationUIManagerMac::prefs_manager() { |
| + return builtin_manager_.get(); |
| +} |
| + |
| +void NotificationUIManagerMac::GetQueuedNotificationsForTesting( |
| + std::vector<const Notification*>* notifications) { |
| + return builtin_manager_->GetQueuedNotificationsForTesting(notifications); |
| +} |
| + |
| +const Notification* |
| +NotificationUIManagerMac::FindNotificationWithCocoaNotification( |
| + id<CrUserNotification> notification) { |
| + std::string notification_id = base::SysNSStringToUTF8( |
| + [notification.userInfo objectForKey:kNotificationIDKey]); |
| + |
| + for (NotificationMap::iterator it = notification_map_.begin(); |
|
jianli
2012/04/10 00:54:14
nit: use const_iterator
Robert Sesek
2012/04/12 17:48:47
Done.
|
| + it != notification_map_.end(); |
| + ++it) { |
| + if (it->second->notification_id() == notification_id) |
| + return it->second; |
| + } |
| + return NULL; |
| +} |
| + |
| +bool NotificationUIManagerMac::RemoveNotification( |
| + id<CrUserNotification> notification) { |
| + std::string notification_id = base::SysNSStringToUTF8( |
| + [notification.userInfo objectForKey:kNotificationIDKey]); |
| + id<CrUserNotificationCenter> center = GetNotificationCenter(); |
| + |
| + // First remove all Cocoa notifications from the center that match the |
| + // notification. Notifications in the system tray do not share pointer |
| + // equality with the balloons or any other message delievered to the |
| + // delegate, so this loop must be run through every time to clean up stale |
| + // notifications. |
| + NSArray* delivered_notifications = center.deliveredNotifications; |
| + for (id<CrUserNotification> delivered in delivered_notifications) { |
| + if ([delivered isEqual:notification]) { |
| + [center removeDeliveredNotification:delivered]; |
| + } |
| + } |
| + |
| + bool did_remove = false; |
| + |
| + // Then go through and remove any C++ notifications that match the |
| + // notification ID, and release any ObjC notifications to which this still |
| + // owns a reference. |
| + for (NotificationMap::iterator it = notification_map_.begin(); |
| + it != notification_map_.end(); |
| + ++it) { |
| + if (it->second->notification_id() == notification_id) { |
| + it->second->Close(false); |
| + delete it->second; |
| + |
| + [it->first release]; |
| + |
| + notification_map_.erase(it); |
|
jianli
2012/04/10 00:54:14
Is |it| still valid after erasing the element for
Robert Sesek
2012/04/12 17:48:47
Fixed by not erasing in a loop.
|
| + |
| + did_remove = true; |
| + } |
| + } |
| + |
| + return did_remove; |
| +} |
| + |
| +id<CrUserNotification> |
| +NotificationUIManagerMac::FindNotificationWithReplacementId( |
| + const string16& replacement_id) { |
| + for (NotificationMap::iterator it = notification_map_.begin(); |
| + it != notification_map_.end(); |
| + ++it) { |
| + if (it->second->replace_id() == replacement_id) |
| + return it->first; |
| + } |
| + return nil; |
| +} |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| + |
| +@implementation NotificationCenterDelegate |
| + |
| +- (id)initWithManager:(NotificationUIManagerMac*)manager { |
| + if ((self = [super init])) { |
| + CHECK(manager); |
| + manager_ = manager; |
| + } |
| + return self; |
| +} |
| + |
| +- (void)userNotificationCenter:(NSUserNotificationCenter*)center |
| + didDeliverNotification:(id<CrUserNotification>)nsNotification { |
| + const Notification* notification = |
| + manager_->FindNotificationWithCocoaNotification(nsNotification); |
| + if (notification) |
| + notification->Display(); |
| +} |
| + |
| +- (void)userNotificationCenter:(NSUserNotificationCenter*)center |
| + didActivateNotification:(id<CrUserNotification>)nsNotification { |
| + const Notification* notification = |
| + manager_->FindNotificationWithCocoaNotification(nsNotification); |
| + if (notification) |
| + notification->Click(); |
| +} |
| + |
| +- (BOOL)userNotificationCenter:(NSUserNotificationCenter*)center |
| + shouldPresentNotification:(id<CrUserNotification>)nsNotification { |
| + return YES; |
| +} |
| + |
| +@end |