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