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..8bb8f40387de7b40884f6fda702f9137d7372bb4 |
--- /dev/null |
+++ b/chrome/browser/notifications/notification_ui_manager_mac.mm |
@@ -0,0 +1,303 @@ |
+// 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::ControllerNotification::ControllerNotification( |
+ id<CrUserNotification> a_view, Notification* a_model) |
jianli
2012/04/13 00:03:35
nit: you don't need to use different name for the
Robert Sesek
2012/04/16 14:20:27
Clang does not agree and reports "error: field is
Avi (use Gerrit)
2012/04/16 14:33:45
I was about to lay down some standard smackdown, b
jianli
2012/04/17 17:14:27
The problem might be due to the type of view: id<C
Robert Sesek
2012/04/17 17:31:12
Also produces the warning.
Nico
2012/04/17 23:58:10
FWIW, I can't see this warning locally or on the b
|
+ : view(a_view), |
+ model(a_model) { |
+} |
+ |
+NotificationUIManagerMac::ControllerNotification::~ControllerNotification() { |
+ [view release]; |
+ delete model; |
+} |
+ |
+//////////////////////////////////////////////////////////////////////////////// |
+ |
+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() { |
jianli
2012/04/13 00:03:35
Do we want to clean up notification_map_?
Robert Sesek
2012/04/16 14:20:27
Done.
|
+} |
+ |
+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 ControllerNotification. |
+ 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(notification.notification_id(), |
+ new ControllerNotification(ns_notification, |
+ new Notification(notification)))); |
+ |
+ [GetNotificationCenter() deliverNotification:ns_notification]; |
+ } |
+} |
+ |
+bool NotificationUIManagerMac::CancelById(const std::string& notification_id) { |
+ NotificationMap::iterator it = notification_map_.find(notification_id); |
+ if (it == notification_map_.end()) |
+ return builtin_manager_->CancelById(notification_id); |
+ |
+ return RemoveNotification(it->second->view); |
+} |
+ |
+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->model->origin_url() == source_origin) { |
+ success |= RemoveNotification(it->second->view); |
jianli
2012/04/13 00:03:35
RemoveNotification calls erase, which might invali
Robert Sesek
2012/04/16 14:20:27
Done.
|
+ } |
+ } |
+ |
+ 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) { |
+ it->second->model->Close(false); |
+ delete it->second; |
+ } |
+ notification_map_.clear(); |
+ |
+ // Clean up any lingering ones in the system tray. |
+ [center removeAllDeliveredNotifications]; |
+ |
+ 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) const { |
+ std::string notification_id = base::SysNSStringToUTF8( |
+ [notification.userInfo objectForKey:kNotificationIDKey]); |
+ |
+ NotificationMap::const_iterator it = notification_map_.find(notification_id); |
+ if (it == notification_map_.end()) |
+ return NULL; |
+ |
+ return it->second->model; |
+} |
+ |
+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]; |
+ } |
+ } |
+ |
+ // Then clean up the C++ model side. |
+ NotificationMap::iterator it = notification_map_.find(notification_id); |
+ if (it == notification_map_.end()) |
+ return false; |
+ |
+ it->second->model->Close(false); |
+ delete it->second; |
+ notification_map_.erase(it); |
+ |
+ return true; |
+} |
+ |
+id<CrUserNotification> |
+NotificationUIManagerMac::FindNotificationWithReplacementId( |
+ const string16& replacement_id) const { |
+ for (NotificationMap::const_iterator it = notification_map_.begin(); |
+ it != notification_map_.end(); |
+ ++it) { |
+ if (it->second->model->replace_id() == replacement_id) |
+ return it->second->view; |
+ } |
+ 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 { |
+ // Always display notifications, regardless of whether the app is foreground. |
+ return YES; |
+} |
+ |
+@end |