The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

992 lines
46KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  22. template <> struct ContainerDeletePolicy<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>> { static void destroy (NSObject* o) { [o release]; } };
  23. #endif
  24. namespace PushNotificationsDelegateDetails
  25. {
  26. //==============================================================================
  27. using Action = PushNotifications::Settings::Action;
  28. using Category = PushNotifications::Settings::Category;
  29. void* actionToNSAction (const Action& a, bool iOSEarlierThan10)
  30. {
  31. if (iOSEarlierThan10)
  32. {
  33. auto* action = [[UIMutableUserNotificationAction alloc] init];
  34. action.identifier = juceStringToNS (a.identifier);
  35. action.title = juceStringToNS (a.title);
  36. action.behavior = a.style == Action::text ? UIUserNotificationActionBehaviorTextInput
  37. : UIUserNotificationActionBehaviorDefault;
  38. action.parameters = varObjectToNSDictionary (a.parameters);
  39. action.activationMode = a.triggerInBackground ? UIUserNotificationActivationModeBackground
  40. : UIUserNotificationActivationModeForeground;
  41. action.destructive = (bool) a.destructive;
  42. [action autorelease];
  43. return action;
  44. }
  45. else
  46. {
  47. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  48. if (a.style == Action::text)
  49. {
  50. return [UNTextInputNotificationAction actionWithIdentifier: juceStringToNS (a.identifier)
  51. title: juceStringToNS (a.title)
  52. options: NSUInteger (a.destructive << 1 | (! a.triggerInBackground) << 2)
  53. textInputButtonTitle: juceStringToNS (a.textInputButtonText)
  54. textInputPlaceholder: juceStringToNS (a.textInputPlaceholder)];
  55. }
  56. return [UNNotificationAction actionWithIdentifier: juceStringToNS (a.identifier)
  57. title: juceStringToNS (a.title)
  58. options: NSUInteger (a.destructive << 1 | (! a.triggerInBackground) << 2)];
  59. #else
  60. return nullptr;
  61. #endif
  62. }
  63. }
  64. void* categoryToNSCategory (const Category& c, bool iOSEarlierThan10)
  65. {
  66. if (iOSEarlierThan10)
  67. {
  68. auto* category = [[UIMutableUserNotificationCategory alloc] init];
  69. category.identifier = juceStringToNS (c.identifier);
  70. auto* actions = [NSMutableArray arrayWithCapacity: (NSUInteger) c.actions.size()];
  71. for (const auto& a : c.actions)
  72. {
  73. auto* action = (UIUserNotificationAction*) actionToNSAction (a, iOSEarlierThan10);
  74. [actions addObject: action];
  75. }
  76. [category setActions: actions forContext: UIUserNotificationActionContextDefault];
  77. [category setActions: actions forContext: UIUserNotificationActionContextMinimal];
  78. [category autorelease];
  79. return category;
  80. }
  81. else
  82. {
  83. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  84. auto* actions = [NSMutableArray arrayWithCapacity: (NSUInteger) c.actions.size()];
  85. for (const auto& a : c.actions)
  86. {
  87. auto* action = (UNNotificationAction*) actionToNSAction (a, iOSEarlierThan10);
  88. [actions addObject: action];
  89. }
  90. return [UNNotificationCategory categoryWithIdentifier: juceStringToNS (c.identifier)
  91. actions: actions
  92. intentIdentifiers: @[]
  93. options: c.sendDismissAction ? UNNotificationCategoryOptionCustomDismissAction : 0];
  94. #else
  95. return nullptr;
  96. #endif
  97. }
  98. }
  99. //==============================================================================
  100. UILocalNotification* juceNotificationToUILocalNotification (const PushNotifications::Notification& n)
  101. {
  102. auto* notification = [[UILocalNotification alloc] init];
  103. notification.alertTitle = juceStringToNS (n.title);
  104. notification.alertBody = juceStringToNS (n.body);
  105. notification.category = juceStringToNS (n.category);
  106. notification.applicationIconBadgeNumber = n.badgeNumber;
  107. auto triggerTime = Time::getCurrentTime() + RelativeTime (n.triggerIntervalSec);
  108. notification.fireDate = [NSDate dateWithTimeIntervalSince1970: triggerTime.toMilliseconds() / 1000.];
  109. notification.userInfo = varObjectToNSDictionary (n.properties);
  110. auto soundToPlayString = n.soundToPlay.toString (true);
  111. if (soundToPlayString == "default_os_sound")
  112. notification.soundName = UILocalNotificationDefaultSoundName;
  113. else if (soundToPlayString.isNotEmpty())
  114. notification.soundName = juceStringToNS (soundToPlayString);
  115. return notification;
  116. }
  117. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  118. UNNotificationRequest* juceNotificationToUNNotificationRequest (const PushNotifications::Notification& n)
  119. {
  120. // content
  121. auto* content = [[UNMutableNotificationContent alloc] init];
  122. content.title = juceStringToNS (n.title);
  123. content.subtitle = juceStringToNS (n.subtitle);
  124. content.threadIdentifier = juceStringToNS (n.groupId);
  125. content.body = juceStringToNS (n.body);
  126. content.categoryIdentifier = juceStringToNS (n.category);
  127. content.badge = [NSNumber numberWithInt: n.badgeNumber];
  128. auto soundToPlayString = n.soundToPlay.toString (true);
  129. if (soundToPlayString == "default_os_sound")
  130. content.sound = [UNNotificationSound defaultSound];
  131. else if (soundToPlayString.isNotEmpty())
  132. content.sound = [UNNotificationSound soundNamed: juceStringToNS (soundToPlayString)];
  133. auto* propsDict = (NSMutableDictionary*) varObjectToNSDictionary (n.properties);
  134. [propsDict setObject: juceStringToNS (soundToPlayString) forKey: nsStringLiteral ("com.juce.soundName")];
  135. content.userInfo = propsDict;
  136. // trigger
  137. UNTimeIntervalNotificationTrigger* trigger = nil;
  138. if (std::abs (n.triggerIntervalSec) >= 0.001)
  139. {
  140. BOOL shouldRepeat = n.repeat && n.triggerIntervalSec >= 60;
  141. trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval: n.triggerIntervalSec repeats: shouldRepeat];
  142. }
  143. // request
  144. // each notification on iOS 10 needs to have an identifer, otherwise it will not show up
  145. jassert (n.identifier.isNotEmpty());
  146. UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier: juceStringToNS (n.identifier)
  147. content: content
  148. trigger: trigger];
  149. [content autorelease];
  150. return request;
  151. }
  152. #endif
  153. String getUserResponseFromNSDictionary (NSDictionary* dictionary)
  154. {
  155. if (dictionary == nil || dictionary.count == 0)
  156. return {};
  157. jassert (dictionary.count == 1);
  158. for (NSString* key in dictionary)
  159. {
  160. const auto keyString = nsStringToJuce (key);
  161. id value = dictionary[key];
  162. if ([value isKindOfClass: [NSString class]])
  163. return nsStringToJuce ((NSString*) value);
  164. }
  165. jassertfalse;
  166. return {};
  167. }
  168. //==============================================================================
  169. var getNotificationPropertiesFromDictionaryVar (const var& dictionaryVar)
  170. {
  171. DynamicObject* dictionaryVarObject = dictionaryVar.getDynamicObject();
  172. if (dictionaryVarObject == nullptr)
  173. return {};
  174. const auto& properties = dictionaryVarObject->getProperties();
  175. DynamicObject::Ptr propsVarObject = new DynamicObject();
  176. for (int i = 0; i < properties.size(); ++i)
  177. {
  178. auto propertyName = properties.getName (i).toString();
  179. if (propertyName == "aps")
  180. continue;
  181. propsVarObject->setProperty (propertyName, properties.getValueAt (i));
  182. }
  183. return var (propsVarObject);
  184. }
  185. //==============================================================================
  186. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  187. double getIntervalSecFromUNNotificationTrigger (UNNotificationTrigger* t)
  188. {
  189. if (t != nil)
  190. {
  191. if ([t isKindOfClass: [UNTimeIntervalNotificationTrigger class]])
  192. {
  193. auto* trigger = (UNTimeIntervalNotificationTrigger*) t;
  194. return trigger.timeInterval;
  195. }
  196. else if ([t isKindOfClass: [UNCalendarNotificationTrigger class]])
  197. {
  198. auto* trigger = (UNCalendarNotificationTrigger*) t;
  199. NSDate* date = [trigger.dateComponents date];
  200. NSDate* dateNow = [NSDate date];
  201. return [dateNow timeIntervalSinceDate: date];
  202. }
  203. }
  204. return 0.;
  205. }
  206. PushNotifications::Notification unNotificationRequestToJuceNotification (UNNotificationRequest* r)
  207. {
  208. PushNotifications::Notification n;
  209. n.identifier = nsStringToJuce (r.identifier);
  210. n.title = nsStringToJuce (r.content.title);
  211. n.subtitle = nsStringToJuce (r.content.subtitle);
  212. n.body = nsStringToJuce (r.content.body);
  213. n.groupId = nsStringToJuce (r.content.threadIdentifier);
  214. n.category = nsStringToJuce (r.content.categoryIdentifier);
  215. n.badgeNumber = r.content.badge.intValue;
  216. auto userInfoVar = nsDictionaryToVar (r.content.userInfo);
  217. if (auto* object = userInfoVar.getDynamicObject())
  218. {
  219. static const Identifier soundName ("com.juce.soundName");
  220. n.soundToPlay = URL (object->getProperty (soundName).toString());
  221. object->removeProperty (soundName);
  222. }
  223. n.properties = userInfoVar;
  224. n.triggerIntervalSec = getIntervalSecFromUNNotificationTrigger (r.trigger);
  225. n.repeat = r.trigger != nil && r.trigger.repeats;
  226. return n;
  227. }
  228. PushNotifications::Notification unNotificationToJuceNotification (UNNotification* n)
  229. {
  230. return unNotificationRequestToJuceNotification (n.request);
  231. }
  232. #endif
  233. PushNotifications::Notification uiLocalNotificationToJuceNotification (UILocalNotification* n)
  234. {
  235. PushNotifications::Notification notif;
  236. notif.title = nsStringToJuce (n.alertTitle);
  237. notif.body = nsStringToJuce (n.alertBody);
  238. if (n.fireDate != nil)
  239. {
  240. NSDate* dateNow = [NSDate date];
  241. notif.triggerIntervalSec = [dateNow timeIntervalSinceDate: n.fireDate];
  242. }
  243. notif.soundToPlay = URL (nsStringToJuce (n.soundName));
  244. notif.badgeNumber = (int) n.applicationIconBadgeNumber;
  245. notif.category = nsStringToJuce (n.category);
  246. notif.properties = nsDictionaryToVar (n.userInfo);
  247. return notif;
  248. }
  249. Action uiUserNotificationActionToAction (UIUserNotificationAction* a)
  250. {
  251. Action action;
  252. action.identifier = nsStringToJuce (a.identifier);
  253. action.title = nsStringToJuce (a.title);
  254. action.style = a.behavior == UIUserNotificationActionBehaviorTextInput
  255. ? Action::text
  256. : Action::button;
  257. action.triggerInBackground = a.activationMode == UIUserNotificationActivationModeBackground;
  258. action.destructive = a.destructive;
  259. action.parameters = nsDictionaryToVar (a.parameters);
  260. return action;
  261. }
  262. Category uiUserNotificationCategoryToCategory (UIUserNotificationCategory* c)
  263. {
  264. Category category;
  265. category.identifier = nsStringToJuce (c.identifier);
  266. for (UIUserNotificationAction* a in [c actionsForContext: UIUserNotificationActionContextDefault])
  267. category.actions.add (uiUserNotificationActionToAction (a));
  268. return category;
  269. }
  270. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  271. Action unNotificationActionToAction (UNNotificationAction* a)
  272. {
  273. Action action;
  274. action.identifier = nsStringToJuce (a.identifier);
  275. action.title = nsStringToJuce (a.title);
  276. action.triggerInBackground = ! (a.options & UNNotificationActionOptionForeground);
  277. action.destructive = a.options & UNNotificationActionOptionDestructive;
  278. if ([a isKindOfClass: [UNTextInputNotificationAction class]])
  279. {
  280. auto* textAction = (UNTextInputNotificationAction*)a;
  281. action.style = Action::text;
  282. action.textInputButtonText = nsStringToJuce (textAction.textInputButtonTitle);
  283. action.textInputPlaceholder = nsStringToJuce (textAction.textInputPlaceholder);
  284. }
  285. else
  286. {
  287. action.style = Action::button;
  288. }
  289. return action;
  290. }
  291. Category unNotificationCategoryToCategory (UNNotificationCategory* c)
  292. {
  293. Category category;
  294. category.identifier = nsStringToJuce (c.identifier);
  295. category.sendDismissAction = c.options & UNNotificationCategoryOptionCustomDismissAction;
  296. for (UNNotificationAction* a in c.actions)
  297. category.actions.add (unNotificationActionToAction (a));
  298. return category;
  299. }
  300. #endif
  301. PushNotifications::Notification nsDictionaryToJuceNotification (NSDictionary* dictionary)
  302. {
  303. const var dictionaryVar = nsDictionaryToVar (dictionary);
  304. const var apsVar = dictionaryVar.getProperty ("aps", {});
  305. if (! apsVar.isObject())
  306. return {};
  307. var alertVar = apsVar.getProperty ("alert", {});
  308. const var titleVar = alertVar.getProperty ("title", {});
  309. const var bodyVar = alertVar.isObject() ? alertVar.getProperty ("body", {}) : alertVar;
  310. const var categoryVar = apsVar.getProperty ("category", {});
  311. const var soundVar = apsVar.getProperty ("sound", {});
  312. const var badgeVar = apsVar.getProperty ("badge", {});
  313. const var threadIdVar = apsVar.getProperty ("thread-id", {});
  314. PushNotifications::Notification notification;
  315. notification.title = titleVar .toString();
  316. notification.body = bodyVar .toString();
  317. notification.groupId = threadIdVar.toString();
  318. notification.category = categoryVar.toString();
  319. notification.soundToPlay = URL (soundVar.toString());
  320. notification.badgeNumber = (int) badgeVar;
  321. notification.properties = getNotificationPropertiesFromDictionaryVar (dictionaryVar);
  322. return notification;
  323. }
  324. }
  325. //==============================================================================
  326. struct PushNotificationsDelegate
  327. {
  328. PushNotificationsDelegate() : delegate ([getClass().createInstance() init])
  329. {
  330. Class::setThis (delegate, this);
  331. id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
  332. SEL selector = NSSelectorFromString (@"setPushNotificationsDelegateToUse:");
  333. if ([appDelegate respondsToSelector: selector])
  334. [appDelegate performSelector: selector withObject: delegate];
  335. }
  336. virtual ~PushNotificationsDelegate() {}
  337. virtual void didRegisterUserNotificationSettings (UIUserNotificationSettings* notificationSettings) = 0;
  338. virtual void registeredForRemoteNotifications (NSData* deviceToken) = 0;
  339. virtual void failedToRegisterForRemoteNotifications (NSError* error) = 0;
  340. virtual void didReceiveRemoteNotification (NSDictionary* userInfo) = 0;
  341. virtual void didReceiveRemoteNotificationFetchCompletionHandler (NSDictionary* userInfo,
  342. void (^completionHandler)(UIBackgroundFetchResult result)) = 0;
  343. virtual void handleActionForRemoteNotificationCompletionHandler (NSString* actionIdentifier,
  344. NSDictionary* userInfo,
  345. NSDictionary* responseInfo,
  346. void (^completionHandler)()) = 0;
  347. virtual void didReceiveLocalNotification (UILocalNotification* notification) = 0;
  348. virtual void handleActionForLocalNotificationCompletionHandler (NSString* actionIdentifier,
  349. UILocalNotification* notification,
  350. void (^completionHandler)()) = 0;
  351. virtual void handleActionForLocalNotificationWithResponseCompletionHandler (NSString* actionIdentifier,
  352. UILocalNotification* notification,
  353. NSDictionary* responseInfo,
  354. void (^completionHandler)()) = 0;
  355. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  356. virtual void willPresentNotificationWithCompletionHandler (UNNotification* notification,
  357. void (^completionHandler)(UNNotificationPresentationOptions options)) = 0;
  358. virtual void didReceiveNotificationResponseWithCompletionHandler (UNNotificationResponse* response,
  359. void (^completionHandler)()) = 0;
  360. #endif
  361. protected:
  362. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  363. ScopedPointer<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>> delegate;
  364. #else
  365. ScopedPointer<NSObject<UIApplicationDelegate>> delegate;
  366. #endif
  367. private:
  368. //==============================================================================
  369. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  370. struct Class : public ObjCClass<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>>
  371. {
  372. Class() : ObjCClass<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>> ("JucePushNotificationsDelegate_")
  373. #else
  374. struct Class : public ObjCClass<NSObject<UIApplicationDelegate>>
  375. {
  376. Class() : ObjCClass<NSObject<UIApplicationDelegate>> ("JucePushNotificationsDelegate_")
  377. #endif
  378. {
  379. addIvar<PushNotificationsDelegate*> ("self");
  380. addMethod (@selector (application:didRegisterUserNotificationSettings:), didRegisterUserNotificationSettings, "v@:@@");
  381. addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@");
  382. addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@");
  383. addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@");
  384. addMethod (@selector (application:didReceiveRemoteNotification:fetchCompletionHandler:), didReceiveRemoteNotificationFetchCompletionHandler, "v@:@@@");
  385. addMethod (@selector (application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:), handleActionForRemoteNotificationCompletionHandler, "v@:@@@@@");
  386. addMethod (@selector (application:didReceiveLocalNotification:), didReceiveLocalNotification, "v@:@@");
  387. addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:completionHandler:), handleActionForLocalNotificationCompletionHandler, "v@:@@@@");
  388. addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler:), handleActionForLocalNotificationWithResponseCompletionHandler, "v@:@@@@@");
  389. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  390. addMethod (@selector (userNotificationCenter:willPresentNotification:withCompletionHandler:), willPresentNotificationWithCompletionHandler, "v@:@@@");
  391. addMethod (@selector (userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:), didReceiveNotificationResponseWithCompletionHandler, "v@:@@@");
  392. #endif
  393. registerClass();
  394. }
  395. //==============================================================================
  396. static PushNotificationsDelegate& getThis (id self) { return *getIvar<PushNotificationsDelegate*> (self, "self"); }
  397. static void setThis (id self, PushNotificationsDelegate* d) { object_setInstanceVariable (self, "self", d); }
  398. //==============================================================================
  399. static void didRegisterUserNotificationSettings (id self, SEL, UIApplication*,
  400. UIUserNotificationSettings* settings) { getThis (self).didRegisterUserNotificationSettings (settings); }
  401. static void registeredForRemoteNotifications (id self, SEL, UIApplication*,
  402. NSData* deviceToken) { getThis (self).registeredForRemoteNotifications (deviceToken); }
  403. static void failedToRegisterForRemoteNotifications (id self, SEL, UIApplication*,
  404. NSError* error) { getThis (self).failedToRegisterForRemoteNotifications (error); }
  405. static void didReceiveRemoteNotification (id self, SEL, UIApplication*,
  406. NSDictionary* userInfo) { getThis (self).didReceiveRemoteNotification (userInfo); }
  407. static void didReceiveRemoteNotificationFetchCompletionHandler (id self, SEL, UIApplication*,
  408. NSDictionary* userInfo,
  409. void (^completionHandler)(UIBackgroundFetchResult result)) { getThis (self).didReceiveRemoteNotificationFetchCompletionHandler (userInfo, completionHandler); }
  410. static void handleActionForRemoteNotificationCompletionHandler (id self, SEL, UIApplication*,
  411. NSString* actionIdentifier,
  412. NSDictionary* userInfo,
  413. NSDictionary* responseInfo,
  414. void (^completionHandler)()) { getThis (self).handleActionForRemoteNotificationCompletionHandler (actionIdentifier, userInfo, responseInfo, completionHandler); }
  415. static void didReceiveLocalNotification (id self, SEL, UIApplication*,
  416. UILocalNotification* notification) { getThis (self).didReceiveLocalNotification (notification); }
  417. static void handleActionForLocalNotificationCompletionHandler (id self, SEL, UIApplication*,
  418. NSString* actionIdentifier,
  419. UILocalNotification* notification,
  420. void (^completionHandler)()) { getThis (self).handleActionForLocalNotificationCompletionHandler (actionIdentifier, notification, completionHandler); }
  421. static void handleActionForLocalNotificationWithResponseCompletionHandler (id self, SEL, UIApplication*,
  422. NSString* actionIdentifier,
  423. UILocalNotification* notification,
  424. NSDictionary* responseInfo,
  425. void (^completionHandler)()) { getThis (self). handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier, notification, responseInfo, completionHandler); }
  426. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  427. static void willPresentNotificationWithCompletionHandler (id self, SEL, UNUserNotificationCenter*,
  428. UNNotification* notification,
  429. void (^completionHandler)(UNNotificationPresentationOptions options)) { getThis (self).willPresentNotificationWithCompletionHandler (notification, completionHandler); }
  430. static void didReceiveNotificationResponseWithCompletionHandler (id self, SEL, UNUserNotificationCenter*,
  431. UNNotificationResponse* response,
  432. void (^completionHandler)()) { getThis (self).didReceiveNotificationResponseWithCompletionHandler (response, completionHandler); }
  433. #endif
  434. };
  435. //==============================================================================
  436. static Class& getClass()
  437. {
  438. static Class c;
  439. return c;
  440. }
  441. };
  442. //==============================================================================
  443. bool PushNotifications::Notification::isValid() const noexcept
  444. {
  445. const bool iOSEarlierThan10 = std::floor (NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max;
  446. if (iOSEarlierThan10)
  447. return title.isNotEmpty() && body.isNotEmpty() && category.isNotEmpty();
  448. return title.isNotEmpty() && body.isNotEmpty() && identifier.isNotEmpty() && category.isNotEmpty();
  449. }
  450. //==============================================================================
  451. struct PushNotifications::Pimpl : private PushNotificationsDelegate
  452. {
  453. Pimpl (PushNotifications& p)
  454. : owner (p)
  455. {
  456. }
  457. void requestPermissionsWithSettings (const PushNotifications::Settings& settingsToUse)
  458. {
  459. settings = settingsToUse;
  460. auto* categories = [NSMutableSet setWithCapacity: (NSUInteger) settings.categories.size()];
  461. if (iOSEarlierThan10)
  462. {
  463. for (const auto& c : settings.categories)
  464. {
  465. auto* category = (UIUserNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c, iOSEarlierThan10);
  466. [categories addObject: category];
  467. }
  468. UIUserNotificationType type = NSUInteger ((bool)settings.allowBadge << 0
  469. | (bool)settings.allowSound << 1
  470. | (bool)settings.allowAlert << 2);
  471. UIUserNotificationSettings* s = [UIUserNotificationSettings settingsForTypes: type categories: categories];
  472. [[UIApplication sharedApplication] registerUserNotificationSettings: s];
  473. }
  474. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  475. else
  476. {
  477. for (const auto& c : settings.categories)
  478. {
  479. auto* category = (UNNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c, iOSEarlierThan10);
  480. [categories addObject: category];
  481. }
  482. UNAuthorizationOptions authOptions = NSUInteger ((bool)settings.allowBadge << 0
  483. | (bool)settings.allowSound << 1
  484. | (bool)settings.allowAlert << 2);
  485. [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories: categories];
  486. [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions: authOptions
  487. completionHandler: ^(BOOL /*granted*/, NSError* /*error*/)
  488. {
  489. requestSettingsUsed();
  490. }];
  491. }
  492. #endif
  493. [[UIApplication sharedApplication] registerForRemoteNotifications];
  494. }
  495. void requestSettingsUsed()
  496. {
  497. if (iOSEarlierThan10)
  498. {
  499. UIUserNotificationSettings* s = [UIApplication sharedApplication].currentUserNotificationSettings;
  500. settings.allowBadge = s.types & UIUserNotificationTypeBadge;
  501. settings.allowSound = s.types & UIUserNotificationTypeSound;
  502. settings.allowAlert = s.types & UIUserNotificationTypeAlert;
  503. for (UIUserNotificationCategory *c in s.categories)
  504. settings.categories.add (PushNotificationsDelegateDetails::uiUserNotificationCategoryToCategory (c));
  505. owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); });
  506. }
  507. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  508. else
  509. {
  510. [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:
  511. ^(UNNotificationSettings* s)
  512. {
  513. [[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler:
  514. ^(NSSet<UNNotificationCategory*>* categories)
  515. {
  516. settings.allowBadge = s.badgeSetting == UNNotificationSettingEnabled;
  517. settings.allowSound = s.soundSetting == UNNotificationSettingEnabled;
  518. settings.allowAlert = s.alertSetting == UNNotificationSettingEnabled;
  519. for (UNNotificationCategory* c in categories)
  520. settings.categories.add (PushNotificationsDelegateDetails::unNotificationCategoryToCategory (c));
  521. owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); });
  522. }
  523. ];
  524. }];
  525. }
  526. #endif
  527. }
  528. bool areNotificationsEnabled() const { return true; }
  529. void sendLocalNotification (const Notification& n)
  530. {
  531. if (iOSEarlierThan10)
  532. {
  533. auto* notification = PushNotificationsDelegateDetails::juceNotificationToUILocalNotification (n);
  534. [[UIApplication sharedApplication] scheduleLocalNotification: notification];
  535. [notification release];
  536. }
  537. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  538. else
  539. {
  540. UNNotificationRequest* request = PushNotificationsDelegateDetails::juceNotificationToUNNotificationRequest (n);
  541. [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest: request
  542. withCompletionHandler: ^(NSError* error)
  543. {
  544. jassert (error == nil);
  545. if (error != nil)
  546. NSLog (nsStringLiteral ("addNotificationRequest error: %@"), error);
  547. }];
  548. }
  549. #endif
  550. }
  551. void getDeliveredNotifications() const
  552. {
  553. if (iOSEarlierThan10)
  554. {
  555. // Not supported on this platform
  556. jassertfalse;
  557. owner.listeners.call ([] (Listener& l) { l.deliveredNotificationsListReceived ({}); });
  558. }
  559. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  560. else
  561. {
  562. [[UNUserNotificationCenter currentNotificationCenter] getDeliveredNotificationsWithCompletionHandler:
  563. ^(NSArray<UNNotification*>* notifications)
  564. {
  565. Array<PushNotifications::Notification> notifs;
  566. for (UNNotification* n in notifications)
  567. notifs.add (PushNotificationsDelegateDetails::unNotificationToJuceNotification (n));
  568. owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifs); });
  569. }];
  570. }
  571. #endif
  572. }
  573. void removeAllDeliveredNotifications()
  574. {
  575. if (iOSEarlierThan10)
  576. {
  577. // Not supported on this platform
  578. jassertfalse;
  579. }
  580. else
  581. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  582. {
  583. [[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
  584. }
  585. #endif
  586. }
  587. void removeDeliveredNotification (const String& identifier)
  588. {
  589. if (iOSEarlierThan10)
  590. {
  591. ignoreUnused (identifier);
  592. // Not supported on this platform
  593. jassertfalse;
  594. }
  595. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  596. else
  597. {
  598. NSArray<NSString*>* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)];
  599. [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers: identifiers];
  600. }
  601. #endif
  602. }
  603. void setupChannels (const Array<ChannelGroup>& groups, const Array<Channel>& channels)
  604. {
  605. ignoreUnused (groups, channels);
  606. }
  607. void getPendingLocalNotifications() const
  608. {
  609. if (iOSEarlierThan10)
  610. {
  611. Array<PushNotifications::Notification> notifs;
  612. for (UILocalNotification* n in [UIApplication sharedApplication].scheduledLocalNotifications)
  613. notifs.add (PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (n));
  614. owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
  615. }
  616. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  617. else
  618. {
  619. [[UNUserNotificationCenter currentNotificationCenter] getPendingNotificationRequestsWithCompletionHandler:
  620. ^(NSArray<UNNotificationRequest*>* requests)
  621. {
  622. Array<PushNotifications::Notification> notifs;
  623. for (UNNotificationRequest* r : requests)
  624. notifs.add (PushNotificationsDelegateDetails::unNotificationRequestToJuceNotification (r));
  625. owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
  626. }
  627. ];
  628. }
  629. #endif
  630. }
  631. void removePendingLocalNotification (const String& identifier)
  632. {
  633. if (iOSEarlierThan10)
  634. {
  635. // Not supported on this platform
  636. jassertfalse;
  637. }
  638. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  639. else
  640. {
  641. NSArray<NSString*>* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)];
  642. [[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers: identifiers];
  643. }
  644. #endif
  645. }
  646. void removeAllPendingLocalNotifications()
  647. {
  648. if (iOSEarlierThan10)
  649. {
  650. [[UIApplication sharedApplication] cancelAllLocalNotifications];
  651. }
  652. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  653. else
  654. {
  655. [[UNUserNotificationCenter currentNotificationCenter] removeAllPendingNotificationRequests];
  656. }
  657. #endif
  658. }
  659. String getDeviceToken()
  660. {
  661. // You need to call requestPermissionsWithSettings() first.
  662. jassert (initialised);
  663. return deviceToken;
  664. }
  665. //==============================================================================
  666. //PushNotificationsDelegate
  667. void didRegisterUserNotificationSettings (UIUserNotificationSettings*) override
  668. {
  669. requestSettingsUsed();
  670. }
  671. void registeredForRemoteNotifications (NSData* deviceTokenToUse) override
  672. {
  673. NSString* deviceTokenString = [[[[deviceTokenToUse description]
  674. stringByReplacingOccurrencesOfString: nsStringLiteral ("<") withString: nsStringLiteral ("")]
  675. stringByReplacingOccurrencesOfString: nsStringLiteral (">") withString: nsStringLiteral ("")]
  676. stringByReplacingOccurrencesOfString: nsStringLiteral (" ") withString: nsStringLiteral ("")];
  677. deviceToken = nsStringToJuce (deviceTokenString);
  678. initialised = true;
  679. owner.listeners.call ([&] (Listener& l) { l.deviceTokenRefreshed (deviceToken); });
  680. }
  681. void failedToRegisterForRemoteNotifications (NSError* error) override
  682. {
  683. ignoreUnused (error);
  684. deviceToken.clear();
  685. }
  686. void didReceiveRemoteNotification (NSDictionary* userInfo) override
  687. {
  688. auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo);
  689. owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, n); });
  690. }
  691. void didReceiveRemoteNotificationFetchCompletionHandler (NSDictionary* userInfo,
  692. void (^completionHandler)(UIBackgroundFetchResult result)) override
  693. {
  694. didReceiveRemoteNotification (userInfo);
  695. completionHandler (UIBackgroundFetchResultNewData);
  696. }
  697. void handleActionForRemoteNotificationCompletionHandler (NSString* actionIdentifier,
  698. NSDictionary* userInfo,
  699. NSDictionary* responseInfo,
  700. void (^completionHandler)()) override
  701. {
  702. auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo);
  703. auto actionString = nsStringToJuce (actionIdentifier);
  704. auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo);
  705. owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (false, n, actionString, response); });
  706. completionHandler();
  707. }
  708. void didReceiveLocalNotification (UILocalNotification* notification) override
  709. {
  710. auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification);
  711. owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, n); });
  712. }
  713. void handleActionForLocalNotificationCompletionHandler (NSString* actionIdentifier,
  714. UILocalNotification* notification,
  715. void (^completionHandler)()) override
  716. {
  717. handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier,
  718. notification,
  719. nil,
  720. completionHandler);
  721. }
  722. void handleActionForLocalNotificationWithResponseCompletionHandler (NSString* actionIdentifier,
  723. UILocalNotification* notification,
  724. NSDictionary* responseInfo,
  725. void (^completionHandler)()) override
  726. {
  727. auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification);
  728. auto actionString = nsStringToJuce (actionIdentifier);
  729. auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo);
  730. owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, n, actionString, response); });
  731. completionHandler();
  732. }
  733. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  734. void willPresentNotificationWithCompletionHandler (UNNotification* notification,
  735. void (^completionHandler)(UNNotificationPresentationOptions options)) override
  736. {
  737. NSUInteger options = NSUInteger ((int)settings.allowBadge << 0
  738. | (int)settings.allowSound << 1
  739. | (int)settings.allowAlert << 2);
  740. ignoreUnused (notification);
  741. completionHandler (options);
  742. }
  743. void didReceiveNotificationResponseWithCompletionHandler (UNNotificationResponse* response,
  744. void (^completionHandler)()) override
  745. {
  746. const bool remote = [response.notification.request.trigger isKindOfClass: [UNPushNotificationTrigger class]];
  747. auto actionString = nsStringToJuce (response.actionIdentifier);
  748. if (actionString == "com.apple.UNNotificationDefaultActionIdentifier")
  749. actionString.clear();
  750. else if (actionString == "com.apple.UNNotificationDismissActionIdentifier")
  751. actionString = "com.juce.NotificationDeleted";
  752. auto n = PushNotificationsDelegateDetails::unNotificationToJuceNotification (response.notification);
  753. String responseString;
  754. if ([response isKindOfClass: [UNTextInputNotificationResponse class]])
  755. {
  756. UNTextInputNotificationResponse* textResponse = (UNTextInputNotificationResponse*)response;
  757. responseString = nsStringToJuce (textResponse.userText);
  758. }
  759. owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (! remote, n, actionString, responseString); });
  760. completionHandler();
  761. }
  762. #endif
  763. void subscribeToTopic (const String& topic) { ignoreUnused (topic); }
  764. void unsubscribeFromTopic (const String& topic) { ignoreUnused (topic); }
  765. void sendUpstreamMessage (const String& serverSenderId,
  766. const String& collapseKey,
  767. const String& messageId,
  768. const String& messageType,
  769. int timeToLive,
  770. const StringPairArray& additionalData)
  771. {
  772. ignoreUnused (serverSenderId, collapseKey, messageId, messageType);
  773. ignoreUnused (timeToLive, additionalData);
  774. }
  775. private:
  776. PushNotifications& owner;
  777. const bool iOSEarlierThan10 = std::floor (NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max;
  778. bool initialised = false;
  779. String deviceToken;
  780. PushNotifications::Settings settings;
  781. };
  782. } // namespace juce