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..58e1ad41fc80496495568dac3cbcb3124fac70c9 |
| --- /dev/null |
| +++ b/chrome/browser/notifications/notification_ui_manager_mac.mm |
| @@ -0,0 +1,307 @@ |
| +// 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, 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.
|
| +// use that instead, because sizeof(id<Protocol>) == sizeof(Class*). In order to |
| +// 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.
|
| +// 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() { |
|
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.
|
| +} |
| + |
| +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> notif = |
|
Mark Mentovai
2012/04/09 16:39:41
notif again, throughout this file :/
|
| + [[NSClassFromString(@"NSUserNotification") alloc] init]; |
| + |
| + notif.title = base::SysUTF16ToNSString(notification.title()); |
| + notif.subtitle = base::SysUTF16ToNSString(notification.display_source()); |
| + notif.informativeText = base::SysUTF16ToNSString(notification.body()); |
| + notif.userInfo = |
| + [NSDictionary dictionaryWithObject:base::SysUTF8ToNSString( |
| + notification.notification_id()) |
| + forKey:kNotificationIDKey]; |
| + notif.hasActionButton = NO; |
| + |
| + notification_map_.insert( |
| + std::make_pair(notif, new Notification(notification))); |
| + |
| + [GetNotificationCenter() deliverNotification:notif]; |
| + } |
| +} |
| + |
| +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> notif in center.deliveredNotifications) { |
| + [center removeDeliveredNotification:notif]; |
| + } |
| + |
| + 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> notif) { |
| + std::string notification_id = base::SysNSStringToUTF8( |
| + [notif.userInfo objectForKey:kNotificationIDKey]); |
| + |
| + for (NotificationMap::iterator it = notification_map_.begin(); |
| + it != notification_map_.end(); |
| + ++it) { |
| + if (it->second->notification_id() == notification_id) |
| + return it->second; |
| + } |
| + return NULL; |
| +} |
| + |
| +bool NotificationUIManagerMac::RemoveNotification( |
| + id<CrUserNotification> notif) { |
| + std::string notification_id = base::SysNSStringToUTF8( |
| + [notif.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_notif in delivered_notifications) { |
| + if ([delivered_notif isEqual:notif]) { |
| + [center removeDeliveredNotification:delivered_notif]; |
| + } |
| + } |
| + |
| + 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); |
| + |
| + 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>)notification { |
| + const Notification* notif = manager_->FindNotificationWithCocoaNotification( |
| + notification); |
| + if (notif) |
| + notif->Display(); |
| +} |
| + |
| +- (void)userNotificationCenter:(NSUserNotificationCenter*)center |
| + didActivateNotification:(id<CrUserNotification>)notification { |
| + const Notification* notif = manager_->FindNotificationWithCocoaNotification( |
| + notification); |
| + if (notif) |
| + notif->Click(); |
| +} |
| + |
| +- (BOOL)userNotificationCenter:(NSUserNotificationCenter*)center |
| + shouldPresentNotification:(id<CrUserNotification>)notification { |
| + return YES; |
| +} |
| + |
| +@end |