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.

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