diff options
author | rsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-17 19:04:40 +0000 |
---|---|---|
committer | rsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-17 19:04:40 +0000 |
commit | 1218f337b58d7f56e15ae816bb7901dc32e4f160 (patch) | |
tree | 4f9dfff301464b4d86d340cbfa8753ac80026ad8 | |
parent | fbcbe4960c71806485008ea7de1f3ba04bc44794 (diff) | |
download | chromium_src-1218f337b58d7f56e15ae816bb7901dc32e4f160.zip chromium_src-1218f337b58d7f56e15ae816bb7901dc32e4f160.tar.gz chromium_src-1218f337b58d7f56e15ae816bb7901dc32e4f160.tar.bz2 |
[Mac] Implement a NotificationUIManager that uses Notification Center on 10.8 for text notifications.
BUG=120677
TEST=On 10.8, enable Gmail desktop notifications and chat with somebody. You see notifications and clicking them closes them, brings Chromium to the foreground, and removes them from the tray.
TEST=On 10.8, try using a HTML-based notification and it gets displayed in the old style.
Review URL: http://codereview.chromium.org/10021026
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@132611 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | base/mac/cocoa_protocols.h | 19 | ||||
-rw-r--r-- | chrome/browser/notifications/notification_ui_manager.cc | 26 | ||||
-rw-r--r-- | chrome/browser/notifications/notification_ui_manager.h | 3 | ||||
-rw-r--r-- | chrome/browser/notifications/notification_ui_manager_impl.cc | 16 | ||||
-rw-r--r-- | chrome/browser/notifications/notification_ui_manager_mac.h | 96 | ||||
-rw-r--r-- | chrome/browser/notifications/notification_ui_manager_mac.mm | 307 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 3 |
7 files changed, 448 insertions, 22 deletions
diff --git a/base/mac/cocoa_protocols.h b/base/mac/cocoa_protocols.h index 88b07b4..d6cb614 100644 --- a/base/mac/cocoa_protocols.h +++ b/base/mac/cocoa_protocols.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. @@ -19,13 +19,13 @@ // define these protocols at all, this file will provide empty protocol // definitions when used with earlier SDK versions. -#if !defined(MAC_OS_X_VERSION_10_6) || \ - MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 - #define DEFINE_EMPTY_PROTOCOL(p) \ @protocol p \ @end +#if !defined(MAC_OS_X_VERSION_10_6) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 + DEFINE_EMPTY_PROTOCOL(NSAlertDelegate) DEFINE_EMPTY_PROTOCOL(NSApplicationDelegate) DEFINE_EMPTY_PROTOCOL(NSControlTextEditingDelegate) @@ -42,8 +42,15 @@ DEFINE_EMPTY_PROTOCOL(NSTextFieldDelegate) DEFINE_EMPTY_PROTOCOL(NSTextViewDelegate) DEFINE_EMPTY_PROTOCOL(NSWindowDelegate) -#undef DEFINE_EMPTY_PROTOCOL +#endif // MAC_OS_X_VERSION_10_6 + +#if !defined(MAC_OS_X_VERSION_10_8) || \ + MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_8 + +DEFINE_EMPTY_PROTOCOL(NSUserNotificationCenterDelegate) -#endif +#endif // MAC_OS_X_VERSION_10_6 + +#undef DEFINE_EMPTY_PROTOCOL #endif // BASE_COCOA_PROTOCOLS_MAC_H_ diff --git a/chrome/browser/notifications/notification_ui_manager.cc b/chrome/browser/notifications/notification_ui_manager.cc new file mode 100644 index 0000000..af6dc6a --- /dev/null +++ b/chrome/browser/notifications/notification_ui_manager.cc @@ -0,0 +1,26 @@ +// 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.h" + +#include "chrome/browser/notifications/balloon_collection.h" +#include "chrome/browser/notifications/notification_ui_manager_impl.h" + +// static +NotificationUIManager* NotificationUIManager::Create(PrefService* local_state) { + return Create(local_state, BalloonCollection::Create()); +} + +#if !defined(OS_MACOSX) +// static +NotificationUIManager* NotificationUIManager::Create( + PrefService* local_state, + BalloonCollection* balloons) { + NotificationUIManagerImpl* instance = + new NotificationUIManagerImpl(local_state); + instance->Initialize(balloons); + balloons->set_space_change_listener(instance); + return instance; +} +#endif // !OS_MACOSX diff --git a/chrome/browser/notifications/notification_ui_manager.h b/chrome/browser/notifications/notification_ui_manager.h index 9242477..41a29b1 100644 --- a/chrome/browser/notifications/notification_ui_manager.h +++ b/chrome/browser/notifications/notification_ui_manager.h @@ -9,11 +9,14 @@ #include <string> #include <vector> +#include "base/basictypes.h" + class BalloonCollection; class GURL; class Notification; class NotificationPrefsManager; class PrefService; +class Profile; // This virtual interface is used to manage the UI surfaces for desktop // notifications. There is one instance per profile. diff --git a/chrome/browser/notifications/notification_ui_manager_impl.cc b/chrome/browser/notifications/notification_ui_manager_impl.cc index cdecd85..dfe12fa 100644 --- a/chrome/browser/notifications/notification_ui_manager_impl.cc +++ b/chrome/browser/notifications/notification_ui_manager_impl.cc @@ -64,22 +64,6 @@ NotificationUIManagerImpl::~NotificationUIManagerImpl() { #endif } -// static -NotificationUIManager* NotificationUIManager::Create(PrefService* local_state) { - return Create(local_state, BalloonCollection::Create()); -} - -// static -NotificationUIManager* NotificationUIManager::Create( - PrefService* local_state, - BalloonCollection* balloons) { - NotificationUIManagerImpl* instance = - new NotificationUIManagerImpl(local_state); - instance->Initialize(balloons); - balloons->set_space_change_listener(instance); - return instance; -} - void NotificationUIManagerImpl::Initialize( BalloonCollection* balloon_collection) { DCHECK(!balloon_collection_.get()); diff --git a/chrome/browser/notifications/notification_ui_manager_mac.h b/chrome/browser/notifications/notification_ui_manager_mac.h new file mode 100644 index 0000000..0b48518 --- /dev/null +++ b/chrome/browser/notifications/notification_ui_manager_mac.h @@ -0,0 +1,96 @@ +// 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. + +#ifndef CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_UI_MANAGER_MAC_H_ +#define CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_UI_MANAGER_MAC_H_ +#pragma once + +#import <AppKit/AppKit.h> + +#include <map> + +#include "base/basictypes.h" +#include "base/memory/scoped_nsobject.h" +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "chrome/browser/notifications/notification_ui_manager.h" + +@protocol CrUserNotification; +class Notification; +@class NotificationCenterDelegate; +class NotificationUIManagerImpl; +class PrefService; +class Profile; + +// This class is an implementation of NotificationUIManager that will send +// text-only (non-HTML) notifications to Notification Center on 10.8. This +// class is only instantiated on 10.8 and above. For HTML notifications, +// this class uses an instance of NotificationUIManagerImpl to present +// notifications in the typical way. +class NotificationUIManagerMac : public NotificationUIManager { + public: + explicit NotificationUIManagerMac(PrefService* local_state); + virtual ~NotificationUIManagerMac(); + + // NotificationUIManager: + virtual void Add(const Notification& notification, + Profile* profile) OVERRIDE; + virtual bool CancelById(const std::string& notification_id) OVERRIDE; + virtual bool CancelAllBySourceOrigin(const GURL& source_origin) OVERRIDE; + virtual void CancelAll() OVERRIDE; + virtual BalloonCollection* balloon_collection() OVERRIDE; + virtual NotificationPrefsManager* prefs_manager() OVERRIDE; + virtual void GetQueuedNotificationsForTesting( + std::vector<const Notification*>* notifications) OVERRIDE; + + // Returns the corresponding C++ object for the Cocoa notification object, + // or NULL if it could not be found. + const Notification* FindNotificationWithCocoaNotification( + id<CrUserNotification> notification) const; + + NotificationUIManagerImpl* builtin_manager() const { + return builtin_manager_.get(); + } + + private: + // A ControllerNotification links the Cocoa (view) notification to the C++ + // (model) notification. This assumes ownership (i.e. does not retain a_view) + // of both objects and will delete them on destruction. + struct ControllerNotification { + ControllerNotification(id<CrUserNotification> a_view, + Notification* a_model); + ~ControllerNotification(); + + id<CrUserNotification> view; + Notification* model; + + private: + DISALLOW_COPY_AND_ASSIGN(ControllerNotification); + }; + typedef std::map<std::string, ControllerNotification*> NotificationMap; + + // Erases a Cocoa notification from both the application and Notification + // Center, and deletes the corresponding C++ object. + bool RemoveNotification(id<CrUserNotification> notification); + + // Finds a notification with a given replacement id. + id<CrUserNotification> FindNotificationWithReplacementId( + const string16& replacement_id) const; + + // The class used to present HTML notifications that can't be sent to + // Notification Center. + scoped_ptr<NotificationUIManagerImpl> builtin_manager_; + + // Cocoa class that receives callbacks from the NSUserNotificationCenter. + scoped_nsobject<NotificationCenterDelegate> delegate_; + + // Maps notification_ids to ControllerNotifications. The map owns the value, + // so it must be deleted when remvoed from the map. This is typically handled + // in RemoveNotification. + NotificationMap notification_map_; + + DISALLOW_COPY_AND_ASSIGN(NotificationUIManagerMac); +}; + +#endif // CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_UI_MANAGER_MAC_H_ 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 0000000..ba74927 --- /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, 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) + : 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() { + CancelAll(); +} + +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();) { + if (it->second->model->origin_url() == source_origin) { + // RemoveNotification will erase from the map, invalidating iterator + // references to the removed element. + success |= RemoveNotification((it++)->second->view); + } else { + ++it; + } + } + + 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 diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 099db2c..102e880 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1411,9 +1411,12 @@ 'browser/notifications/notification_options_menu_model.h', 'browser/notifications/notification_prefs_manager.cc', 'browser/notifications/notification_prefs_manager.h', + 'browser/notifications/notification_ui_manager.cc', 'browser/notifications/notification_ui_manager.h', 'browser/notifications/notification_ui_manager_impl.cc', 'browser/notifications/notification_ui_manager_impl.h', + 'browser/notifications/notification_ui_manager_mac.mm', + 'browser/notifications/notification_ui_manager_mac.h', 'browser/ntp_background_util.cc', 'browser/ntp_background_util.h', 'browser/omnibox_search_hint.cc', |