summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-17 19:04:40 +0000
committerrsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-17 19:04:40 +0000
commit1218f337b58d7f56e15ae816bb7901dc32e4f160 (patch)
tree4f9dfff301464b4d86d340cbfa8753ac80026ad8
parentfbcbe4960c71806485008ea7de1f3ba04bc44794 (diff)
downloadchromium_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.h19
-rw-r--r--chrome/browser/notifications/notification_ui_manager.cc26
-rw-r--r--chrome/browser/notifications/notification_ui_manager.h3
-rw-r--r--chrome/browser/notifications/notification_ui_manager_impl.cc16
-rw-r--r--chrome/browser/notifications/notification_ui_manager_mac.h96
-rw-r--r--chrome/browser/notifications/notification_ui_manager_mac.mm307
-rw-r--r--chrome/chrome_browser.gypi3
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',