| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/notifications/notification_ui_manager_mac.h" | |
| 6 | |
| 7 #include <utility> | |
| 8 | |
| 9 #include "base/mac/foundation_util.h" | |
| 10 #include "base/mac/mac_util.h" | |
| 11 #include "base/strings/string_number_conversions.h" | |
| 12 #include "base/strings/sys_string_conversions.h" | |
| 13 #include "chrome/browser/browser_process.h" | |
| 14 #include "chrome/browser/notifications/notification.h" | |
| 15 #include "chrome/browser/notifications/notification_display_service_factory.h" | |
| 16 #include "chrome/browser/notifications/persistent_notification_delegate.h" | |
| 17 #include "chrome/browser/notifications/platform_notification_service_impl.h" | |
| 18 #include "chrome/browser/profiles/profile.h" | |
| 19 #include "chrome/browser/profiles/profile_manager.h" | |
| 20 #include "chrome/grit/generated_resources.h" | |
| 21 #include "ui/base/l10n/l10n_util_mac.h" | |
| 22 #include "url/gurl.h" | |
| 23 | |
| 24 @class NSUserNotification; | |
| 25 @class NSUserNotificationCenter; | |
| 26 | |
| 27 // The mapping from web notifications to NsUserNotification works as follows | |
| 28 | |
| 29 // notification#title in NSUserNotification.title | |
| 30 // notification#message in NSUserNotification.subtitle | |
| 31 // notification#context_message in NSUserNotification.informativeText | |
| 32 // notification#tag in NSUserNotification.identifier (10.9) | |
| 33 // notification#icon in NSUserNotification.contentImage (10.9) | |
| 34 // Site settings button is implemented as NSUserNotification's action button | |
| 35 // Not possible to implement: | |
| 36 // -notification.requireInteraction | |
| 37 // -The event associated to the close button | |
| 38 | |
| 39 // TODO(miguelg) implement the following features | |
| 40 // - Sound names can be implemented by setting soundName in NSUserNotification | |
| 41 // NSUserNotificationDefaultSoundName gives you the platform default. | |
| 42 | |
| 43 namespace { | |
| 44 | |
| 45 // Keys in NSUserNotification.userInfo to map chrome notifications to | |
| 46 // native ones. | |
| 47 NSString* const kNotificationOriginKey = @"notification_origin"; | |
| 48 NSString* const kNotificationPersistentIdKey = @"notification_persistent_id"; | |
| 49 | |
| 50 NSString* const kNotificationProfilePersistentIdKey = | |
| 51 @"notification_profile_persistent_id"; | |
| 52 NSString* const kNotificationIncognitoKey = @"notification_incognito"; | |
| 53 | |
| 54 } // namespace | |
| 55 | |
| 56 // static | |
| 57 NotificationPlatformBridge* NotificationPlatformBridge::Create() { | |
| 58 return new NotificationUIManagerMac( | |
| 59 [NSUserNotificationCenter defaultUserNotificationCenter]); | |
| 60 } | |
| 61 | |
| 62 // A Cocoa class that represents the delegate of NSUserNotificationCenter and | |
| 63 // can forward commands to C++. | |
| 64 @interface NotificationCenterDelegate | |
| 65 : NSObject<NSUserNotificationCenterDelegate> { | |
| 66 } | |
| 67 @end | |
| 68 | |
| 69 // ///////////////////////////////////////////////////////////////////////////// | |
| 70 | |
| 71 NotificationUIManagerMac::NotificationUIManagerMac( | |
| 72 NSUserNotificationCenter* notification_center) | |
| 73 : delegate_([NotificationCenterDelegate alloc]), | |
| 74 notification_center_(notification_center) { | |
| 75 [notification_center_ setDelegate:delegate_.get()]; | |
| 76 } | |
| 77 | |
| 78 NotificationUIManagerMac::~NotificationUIManagerMac() { | |
| 79 [notification_center_ setDelegate:nil]; | |
| 80 | |
| 81 // TODO(miguelg) lift this restriction if possible. | |
| 82 [notification_center_ removeAllDeliveredNotifications]; | |
| 83 } | |
| 84 | |
| 85 void NotificationUIManagerMac::Display(const std::string& notification_id, | |
| 86 const std::string& profile_id, | |
| 87 bool incognito, | |
| 88 const Notification& notification) { | |
| 89 base::scoped_nsobject<NSUserNotification> toast( | |
| 90 [[NSUserNotification alloc] init]); | |
| 91 [toast setTitle:base::SysUTF16ToNSString(notification.title())]; | |
| 92 [toast setSubtitle:base::SysUTF16ToNSString(notification.message())]; | |
| 93 | |
| 94 // TODO(miguelg): try to elide the origin perhaps See NSString | |
| 95 // stringWithFormat. It seems that the informativeText font is constant. | |
| 96 NSString* informative_text = | |
| 97 notification.context_message().empty() | |
| 98 ? base::SysUTF8ToNSString(notification.origin_url().spec()) | |
| 99 : base::SysUTF16ToNSString(notification.context_message()); | |
| 100 [toast setInformativeText:informative_text]; | |
| 101 | |
| 102 // Some functionality requires private APIs | |
| 103 // Icon | |
| 104 if ([toast respondsToSelector:@selector(_identityImage)] && | |
| 105 !notification.icon().IsEmpty()) { | |
| 106 [toast setValue:notification.icon().ToNSImage() forKey:@"_identityImage"]; | |
| 107 [toast setValue:@NO forKey:@"_identityImageHasBorder"]; | |
| 108 } | |
| 109 | |
| 110 // Buttons | |
| 111 if ([toast respondsToSelector:@selector(_showsButtons)]) { | |
| 112 [toast setValue:@YES forKey:@"_showsButtons"]; | |
| 113 // A default close button label is provided by the platform but we | |
| 114 // explicitly override it in case the user decides to not | |
| 115 // use the OS language in Chrome. | |
| 116 [toast setOtherButtonTitle:l10n_util::GetNSString( | |
| 117 IDS_NOTIFICATION_BUTTON_CLOSE)]; | |
| 118 | |
| 119 // Display the Settings button as the action button if there either are no | |
| 120 // developer-provided action buttons, or the alternate action menu is not | |
| 121 // available on this Mac version. This avoids needlessly showing the menu. | |
| 122 if (notification.buttons().empty() || | |
| 123 ![toast respondsToSelector:@selector(_alwaysShowAlternateActionMenu)]) { | |
| 124 [toast setActionButtonTitle:l10n_util::GetNSString( | |
| 125 IDS_NOTIFICATION_BUTTON_SETTINGS)]; | |
| 126 } else { | |
| 127 // Otherwise show the alternate menu, then show the developer actions and | |
| 128 // finally the settings one. | |
| 129 DCHECK( | |
| 130 [toast respondsToSelector:@selector(_alwaysShowAlternateActionMenu)]); | |
| 131 DCHECK( | |
| 132 [toast respondsToSelector:@selector(_alternateActionButtonTitles)]); | |
| 133 | |
| 134 [toast setActionButtonTitle:l10n_util::GetNSString( | |
| 135 IDS_NOTIFICATION_BUTTON_OPTIONS)]; | |
| 136 [toast setValue:@YES | |
| 137 forKey:@"_alwaysShowAlternateActionMenu"]; | |
| 138 | |
| 139 NSMutableArray* buttons = [NSMutableArray arrayWithCapacity:3]; | |
| 140 for (const auto& action : notification.buttons()) | |
| 141 [buttons addObject:base::SysUTF16ToNSString(action.title)]; | |
| 142 [buttons | |
| 143 addObject:l10n_util::GetNSString(IDS_NOTIFICATION_BUTTON_SETTINGS)]; | |
| 144 | |
| 145 [toast setValue:buttons forKey:@"_alternateActionButtonTitles"]; | |
| 146 } | |
| 147 } | |
| 148 | |
| 149 // Tag | |
| 150 if ([toast respondsToSelector:@selector(setIdentifier:)] && | |
| 151 !notification.tag().empty()) { | |
| 152 [toast setValue:base::SysUTF8ToNSString(notification.tag()) | |
| 153 forKey:@"identifier"]; | |
| 154 | |
| 155 // If renotify is needed, delete the notification with the same tag | |
| 156 // from the notification center before displaying this one. | |
| 157 if (notification.renotify()) { | |
| 158 NSUserNotificationCenter* notification_center = | |
| 159 [NSUserNotificationCenter defaultUserNotificationCenter]; | |
| 160 for (NSUserNotification* existing_notification in | |
| 161 [notification_center deliveredNotifications]) { | |
| 162 NSString* identifier = | |
| 163 [existing_notification valueForKey:@"identifier"]; | |
| 164 if ([identifier isEqual:base::SysUTF8ToNSString(notification.tag())]) { | |
| 165 [notification_center | |
| 166 removeDeliveredNotification:existing_notification]; | |
| 167 break; | |
| 168 } | |
| 169 } | |
| 170 } | |
| 171 } | |
| 172 | |
| 173 toast.get().userInfo = @{ | |
| 174 kNotificationOriginKey : | |
| 175 base::SysUTF8ToNSString(notification.origin_url().spec()), | |
| 176 kNotificationPersistentIdKey : base::SysUTF8ToNSString(notification_id), | |
| 177 kNotificationProfilePersistentIdKey : base::SysUTF8ToNSString(profile_id), | |
| 178 kNotificationIncognitoKey : [NSNumber numberWithBool:incognito] | |
| 179 }; | |
| 180 | |
| 181 [notification_center_ deliverNotification:toast]; | |
| 182 } | |
| 183 | |
| 184 void NotificationUIManagerMac::Close(const std::string& profile_id, | |
| 185 const std::string& notification_id) { | |
| 186 NSString* candidate_id = base::SysUTF8ToNSString(notification_id); | |
| 187 | |
| 188 NSString* current_profile_id = base::SysUTF8ToNSString(profile_id); | |
| 189 for (NSUserNotification* toast in | |
| 190 [notification_center_ deliveredNotifications]) { | |
| 191 NSString* toast_id = | |
| 192 [toast.userInfo objectForKey:kNotificationPersistentIdKey]; | |
| 193 | |
| 194 NSString* persistent_profile_id = | |
| 195 [toast.userInfo objectForKey:kNotificationProfilePersistentIdKey]; | |
| 196 | |
| 197 if (toast_id == candidate_id && | |
| 198 persistent_profile_id == current_profile_id) { | |
| 199 [notification_center_ removeDeliveredNotification:toast]; | |
| 200 } | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 bool NotificationUIManagerMac::GetDisplayed( | |
| 205 const std::string& profile_id, | |
| 206 bool incognito, | |
| 207 std::set<std::string>* notifications) const { | |
| 208 DCHECK(notifications); | |
| 209 NSString* current_profile_id = base::SysUTF8ToNSString(profile_id); | |
| 210 for (NSUserNotification* toast in | |
| 211 [notification_center_ deliveredNotifications]) { | |
| 212 NSString* toast_profile_id = | |
| 213 [toast.userInfo objectForKey:kNotificationProfilePersistentIdKey]; | |
| 214 if (toast_profile_id == current_profile_id) { | |
| 215 notifications->insert(base::SysNSStringToUTF8( | |
| 216 [toast.userInfo objectForKey:kNotificationPersistentIdKey])); | |
| 217 } | |
| 218 } | |
| 219 return true; | |
| 220 } | |
| 221 | |
| 222 bool NotificationUIManagerMac::SupportsNotificationCenter() const { | |
| 223 return true; | |
| 224 } | |
| 225 | |
| 226 // ///////////////////////////////////////////////////////////////////////////// | |
| 227 | |
| 228 @implementation NotificationCenterDelegate | |
| 229 - (void)userNotificationCenter:(NSUserNotificationCenter*)center | |
| 230 didActivateNotification:(NSUserNotification*)notification { | |
| 231 std::string notificationOrigin = base::SysNSStringToUTF8( | |
| 232 [notification.userInfo objectForKey:kNotificationOriginKey]); | |
| 233 NSNumber* persistentNotificationId = | |
| 234 [notification.userInfo objectForKey:kNotificationPersistentIdKey]; | |
| 235 NSString* persistentProfileId = | |
| 236 [notification.userInfo objectForKey:kNotificationProfilePersistentIdKey]; | |
| 237 NSNumber* isIncognito = | |
| 238 [notification.userInfo objectForKey:kNotificationIncognitoKey]; | |
| 239 | |
| 240 GURL origin(notificationOrigin); | |
| 241 | |
| 242 // Initialize operation and button index for the case where the | |
| 243 // notification itself was clicked. | |
| 244 PlatformNotificationServiceImpl::NotificationOperation operation = | |
| 245 PlatformNotificationServiceImpl::NOTIFICATION_CLICK; | |
| 246 int buttonIndex = -1; | |
| 247 | |
| 248 // Determine whether the user clicked on a button, and if they did, whether it | |
| 249 // was a developer-provided button or the mandatory Settings button. | |
| 250 if (notification.activationType == | |
| 251 NSUserNotificationActivationTypeActionButtonClicked) { | |
| 252 NSArray* alternateButtons = @[]; | |
| 253 if ([notification | |
| 254 respondsToSelector:@selector(_alternateActionButtonTitles)]) { | |
| 255 alternateButtons = | |
| 256 [notification valueForKey:@"_alternateActionButtonTitles"]; | |
| 257 } | |
| 258 | |
| 259 bool multipleButtons = (alternateButtons.count > 0); | |
| 260 | |
| 261 // No developer actions, just the settings button. | |
| 262 if (!multipleButtons) { | |
| 263 operation = PlatformNotificationServiceImpl::NOTIFICATION_SETTINGS; | |
| 264 buttonIndex = -1; | |
| 265 } else { | |
| 266 // 0 based array containing. | |
| 267 // Button 1 | |
| 268 // Button 2 (optional) | |
| 269 // Settings | |
| 270 NSNumber* actionIndex = | |
| 271 [notification valueForKey:@"_alternateActionIndex"]; | |
| 272 operation = (actionIndex.unsignedLongValue == alternateButtons.count - 1) | |
| 273 ? PlatformNotificationServiceImpl::NOTIFICATION_SETTINGS | |
| 274 : PlatformNotificationServiceImpl::NOTIFICATION_CLICK; | |
| 275 buttonIndex = | |
| 276 (actionIndex.unsignedLongValue == alternateButtons.count - 1) | |
| 277 ? -1 | |
| 278 : actionIndex.intValue; | |
| 279 } | |
| 280 } | |
| 281 | |
| 282 PlatformNotificationServiceImpl::GetInstance() | |
| 283 ->ProcessPersistentNotificationOperation( | |
| 284 operation, base::SysNSStringToUTF8(persistentProfileId), | |
| 285 [isIncognito boolValue], origin, | |
| 286 persistentNotificationId.longLongValue, buttonIndex); | |
| 287 } | |
| 288 | |
| 289 - (BOOL)userNotificationCenter:(NSUserNotificationCenter*)center | |
| 290 shouldPresentNotification:(NSUserNotification*)nsNotification { | |
| 291 // Always display notifications, regardless of whether the app is foreground. | |
| 292 return YES; | |
| 293 } | |
| 294 | |
| 295 @end | |
| OLD | NEW |