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.

1000 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 identifier, 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.get());
  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. NSDate* fireDate = n.fireDate;
  239. notif.triggerIntervalSec = [dateNow timeIntervalSinceDate: fireDate];
  240. }
  241. notif.soundToPlay = URL (nsStringToJuce (n.soundName));
  242. notif.badgeNumber = (int) n.applicationIconBadgeNumber;
  243. notif.category = nsStringToJuce (n.category);
  244. notif.properties = nsDictionaryToVar (n.userInfo);
  245. return notif;
  246. }
  247. Action uiUserNotificationActionToAction (UIUserNotificationAction* a)
  248. {
  249. Action action;
  250. action.identifier = nsStringToJuce (a.identifier);
  251. action.title = nsStringToJuce (a.title);
  252. action.style = a.behavior == UIUserNotificationActionBehaviorTextInput
  253. ? Action::text
  254. : Action::button;
  255. action.triggerInBackground = a.activationMode == UIUserNotificationActivationModeBackground;
  256. action.destructive = a.destructive;
  257. action.parameters = nsDictionaryToVar (a.parameters);
  258. return action;
  259. }
  260. Category uiUserNotificationCategoryToCategory (UIUserNotificationCategory* c)
  261. {
  262. Category category;
  263. category.identifier = nsStringToJuce (c.identifier);
  264. for (UIUserNotificationAction* a in [c actionsForContext: UIUserNotificationActionContextDefault])
  265. category.actions.add (uiUserNotificationActionToAction (a));
  266. return category;
  267. }
  268. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  269. Action unNotificationActionToAction (UNNotificationAction* a)
  270. {
  271. Action action;
  272. action.identifier = nsStringToJuce (a.identifier);
  273. action.title = nsStringToJuce (a.title);
  274. action.triggerInBackground = ! (a.options & UNNotificationActionOptionForeground);
  275. action.destructive = a.options & UNNotificationActionOptionDestructive;
  276. if ([a isKindOfClass: [UNTextInputNotificationAction class]])
  277. {
  278. auto* textAction = (UNTextInputNotificationAction*)a;
  279. action.style = Action::text;
  280. action.textInputButtonText = nsStringToJuce (textAction.textInputButtonTitle);
  281. action.textInputPlaceholder = nsStringToJuce (textAction.textInputPlaceholder);
  282. }
  283. else
  284. {
  285. action.style = Action::button;
  286. }
  287. return action;
  288. }
  289. Category unNotificationCategoryToCategory (UNNotificationCategory* c)
  290. {
  291. Category category;
  292. category.identifier = nsStringToJuce (c.identifier);
  293. category.sendDismissAction = c.options & UNNotificationCategoryOptionCustomDismissAction;
  294. for (UNNotificationAction* a in c.actions)
  295. category.actions.add (unNotificationActionToAction (a));
  296. return category;
  297. }
  298. #endif
  299. PushNotifications::Notification nsDictionaryToJuceNotification (NSDictionary* dictionary)
  300. {
  301. const var dictionaryVar = nsDictionaryToVar (dictionary);
  302. const var apsVar = dictionaryVar.getProperty ("aps", {});
  303. if (! apsVar.isObject())
  304. return {};
  305. var alertVar = apsVar.getProperty ("alert", {});
  306. const var titleVar = alertVar.getProperty ("title", {});
  307. const var bodyVar = alertVar.isObject() ? alertVar.getProperty ("body", {}) : alertVar;
  308. const var categoryVar = apsVar.getProperty ("category", {});
  309. const var soundVar = apsVar.getProperty ("sound", {});
  310. const var badgeVar = apsVar.getProperty ("badge", {});
  311. const var threadIdVar = apsVar.getProperty ("thread-id", {});
  312. PushNotifications::Notification notification;
  313. notification.title = titleVar .toString();
  314. notification.body = bodyVar .toString();
  315. notification.groupId = threadIdVar.toString();
  316. notification.category = categoryVar.toString();
  317. notification.soundToPlay = URL (soundVar.toString());
  318. notification.badgeNumber = (int) badgeVar;
  319. notification.properties = getNotificationPropertiesFromDictionaryVar (dictionaryVar);
  320. return notification;
  321. }
  322. }
  323. //==============================================================================
  324. struct PushNotificationsDelegate
  325. {
  326. PushNotificationsDelegate() : delegate ([getClass().createInstance() init])
  327. {
  328. Class::setThis (delegate.get(), this);
  329. id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
  330. SEL selector = NSSelectorFromString (@"setPushNotificationsDelegateToUse:");
  331. if ([appDelegate respondsToSelector: selector])
  332. [appDelegate performSelector: selector withObject: delegate.get()];
  333. }
  334. virtual ~PushNotificationsDelegate() {}
  335. virtual void didRegisterUserNotificationSettings (UIUserNotificationSettings* notificationSettings) = 0;
  336. virtual void registeredForRemoteNotifications (NSData* deviceToken) = 0;
  337. virtual void failedToRegisterForRemoteNotifications (NSError* error) = 0;
  338. virtual void didReceiveRemoteNotification (NSDictionary* userInfo) = 0;
  339. virtual void didReceiveRemoteNotificationFetchCompletionHandler (NSDictionary* userInfo,
  340. void (^completionHandler)(UIBackgroundFetchResult result)) = 0;
  341. virtual void handleActionForRemoteNotificationCompletionHandler (NSString* actionIdentifier,
  342. NSDictionary* userInfo,
  343. NSDictionary* responseInfo,
  344. void (^completionHandler)()) = 0;
  345. virtual void didReceiveLocalNotification (UILocalNotification* notification) = 0;
  346. virtual void handleActionForLocalNotificationCompletionHandler (NSString* actionIdentifier,
  347. UILocalNotification* notification,
  348. void (^completionHandler)()) = 0;
  349. virtual void handleActionForLocalNotificationWithResponseCompletionHandler (NSString* actionIdentifier,
  350. UILocalNotification* notification,
  351. NSDictionary* responseInfo,
  352. void (^completionHandler)()) = 0;
  353. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  354. virtual void willPresentNotificationWithCompletionHandler (UNNotification* notification,
  355. void (^completionHandler)(UNNotificationPresentationOptions options)) = 0;
  356. virtual void didReceiveNotificationResponseWithCompletionHandler (UNNotificationResponse* response,
  357. void (^completionHandler)()) = 0;
  358. #endif
  359. protected:
  360. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  361. std::unique_ptr<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>, NSObjectDeleter> delegate;
  362. #else
  363. std::unique_ptr<NSObject<UIApplicationDelegate>, NSObjectDeleter> delegate;
  364. #endif
  365. private:
  366. //==============================================================================
  367. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  368. struct Class : public ObjCClass<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>>
  369. {
  370. Class() : ObjCClass<NSObject<UIApplicationDelegate, UNUserNotificationCenterDelegate>> ("JucePushNotificationsDelegate_")
  371. #else
  372. struct Class : public ObjCClass<NSObject<UIApplicationDelegate>>
  373. {
  374. Class() : ObjCClass<NSObject<UIApplicationDelegate>> ("JucePushNotificationsDelegate_")
  375. #endif
  376. {
  377. addIvar<PushNotificationsDelegate*> ("self");
  378. addMethod (@selector (application:didRegisterUserNotificationSettings:), didRegisterUserNotificationSettings, "v@:@@");
  379. addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@");
  380. addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@");
  381. addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@");
  382. addMethod (@selector (application:didReceiveRemoteNotification:fetchCompletionHandler:), didReceiveRemoteNotificationFetchCompletionHandler, "v@:@@@");
  383. addMethod (@selector (application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:), handleActionForRemoteNotificationCompletionHandler, "v@:@@@@@");
  384. addMethod (@selector (application:didReceiveLocalNotification:), didReceiveLocalNotification, "v@:@@");
  385. addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:completionHandler:), handleActionForLocalNotificationCompletionHandler, "v@:@@@@");
  386. addMethod (@selector (application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler:), handleActionForLocalNotificationWithResponseCompletionHandler, "v@:@@@@@");
  387. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  388. addMethod (@selector (userNotificationCenter:willPresentNotification:withCompletionHandler:), willPresentNotificationWithCompletionHandler, "v@:@@@");
  389. addMethod (@selector (userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:), didReceiveNotificationResponseWithCompletionHandler, "v@:@@@");
  390. #endif
  391. registerClass();
  392. }
  393. //==============================================================================
  394. static PushNotificationsDelegate& getThis (id self) { return *getIvar<PushNotificationsDelegate*> (self, "self"); }
  395. static void setThis (id self, PushNotificationsDelegate* d) { object_setInstanceVariable (self, "self", d); }
  396. //==============================================================================
  397. static void didRegisterUserNotificationSettings (id self, SEL, UIApplication*,
  398. UIUserNotificationSettings* settings) { getThis (self).didRegisterUserNotificationSettings (settings); }
  399. static void registeredForRemoteNotifications (id self, SEL, UIApplication*,
  400. NSData* deviceToken) { getThis (self).registeredForRemoteNotifications (deviceToken); }
  401. static void failedToRegisterForRemoteNotifications (id self, SEL, UIApplication*,
  402. NSError* error) { getThis (self).failedToRegisterForRemoteNotifications (error); }
  403. static void didReceiveRemoteNotification (id self, SEL, UIApplication*,
  404. NSDictionary* userInfo) { getThis (self).didReceiveRemoteNotification (userInfo); }
  405. static void didReceiveRemoteNotificationFetchCompletionHandler (id self, SEL, UIApplication*,
  406. NSDictionary* userInfo,
  407. void (^completionHandler)(UIBackgroundFetchResult result)) { getThis (self).didReceiveRemoteNotificationFetchCompletionHandler (userInfo, completionHandler); }
  408. static void handleActionForRemoteNotificationCompletionHandler (id self, SEL, UIApplication*,
  409. NSString* actionIdentifier,
  410. NSDictionary* userInfo,
  411. NSDictionary* responseInfo,
  412. void (^completionHandler)()) { getThis (self).handleActionForRemoteNotificationCompletionHandler (actionIdentifier, userInfo, responseInfo, completionHandler); }
  413. static void didReceiveLocalNotification (id self, SEL, UIApplication*,
  414. UILocalNotification* notification) { getThis (self).didReceiveLocalNotification (notification); }
  415. static void handleActionForLocalNotificationCompletionHandler (id self, SEL, UIApplication*,
  416. NSString* actionIdentifier,
  417. UILocalNotification* notification,
  418. void (^completionHandler)()) { getThis (self).handleActionForLocalNotificationCompletionHandler (actionIdentifier, notification, completionHandler); }
  419. static void handleActionForLocalNotificationWithResponseCompletionHandler (id self, SEL, UIApplication*,
  420. NSString* actionIdentifier,
  421. UILocalNotification* notification,
  422. NSDictionary* responseInfo,
  423. void (^completionHandler)()) { getThis (self). handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier, notification, responseInfo, completionHandler); }
  424. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  425. static void willPresentNotificationWithCompletionHandler (id self, SEL, UNUserNotificationCenter*,
  426. UNNotification* notification,
  427. void (^completionHandler)(UNNotificationPresentationOptions options)) { getThis (self).willPresentNotificationWithCompletionHandler (notification, completionHandler); }
  428. static void didReceiveNotificationResponseWithCompletionHandler (id self, SEL, UNUserNotificationCenter*,
  429. UNNotificationResponse* response,
  430. void (^completionHandler)()) { getThis (self).didReceiveNotificationResponseWithCompletionHandler (response, completionHandler); }
  431. #endif
  432. };
  433. //==============================================================================
  434. static Class& getClass()
  435. {
  436. static Class c;
  437. return c;
  438. }
  439. };
  440. //==============================================================================
  441. bool PushNotifications::Notification::isValid() const noexcept
  442. {
  443. const bool iOSEarlierThan10 = std::floor (NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max;
  444. if (iOSEarlierThan10)
  445. return title.isNotEmpty() && body.isNotEmpty() && category.isNotEmpty();
  446. return title.isNotEmpty() && body.isNotEmpty() && identifier.isNotEmpty() && category.isNotEmpty();
  447. }
  448. //==============================================================================
  449. struct PushNotifications::Pimpl : private PushNotificationsDelegate
  450. {
  451. Pimpl (PushNotifications& p)
  452. : owner (p)
  453. {
  454. }
  455. void requestPermissionsWithSettings (const PushNotifications::Settings& settingsToUse)
  456. {
  457. settings = settingsToUse;
  458. auto categories = [NSMutableSet setWithCapacity: (NSUInteger) settings.categories.size()];
  459. if (iOSEarlierThan10)
  460. {
  461. for (const auto& c : settings.categories)
  462. {
  463. auto* category = (UIUserNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c, iOSEarlierThan10);
  464. [categories addObject: category];
  465. }
  466. UIUserNotificationType type = NSUInteger ((bool)settings.allowBadge << 0
  467. | (bool)settings.allowSound << 1
  468. | (bool)settings.allowAlert << 2);
  469. UIUserNotificationSettings* s = [UIUserNotificationSettings settingsForTypes: type categories: categories];
  470. [[UIApplication sharedApplication] registerUserNotificationSettings: s];
  471. }
  472. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  473. else
  474. {
  475. for (const auto& c : settings.categories)
  476. {
  477. auto* category = (UNNotificationCategory*) PushNotificationsDelegateDetails::categoryToNSCategory (c, iOSEarlierThan10);
  478. [categories addObject: category];
  479. }
  480. UNAuthorizationOptions authOptions = NSUInteger ((bool)settings.allowBadge << 0
  481. | (bool)settings.allowSound << 1
  482. | (bool)settings.allowAlert << 2);
  483. [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories: categories];
  484. [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions: authOptions
  485. completionHandler: ^(BOOL /*granted*/, NSError* /*error*/)
  486. {
  487. requestSettingsUsed();
  488. }];
  489. }
  490. #endif
  491. [[UIApplication sharedApplication] registerForRemoteNotifications];
  492. }
  493. void requestSettingsUsed()
  494. {
  495. if (iOSEarlierThan10)
  496. {
  497. UIUserNotificationSettings* s = [UIApplication sharedApplication].currentUserNotificationSettings;
  498. settings.allowBadge = s.types & UIUserNotificationTypeBadge;
  499. settings.allowSound = s.types & UIUserNotificationTypeSound;
  500. settings.allowAlert = s.types & UIUserNotificationTypeAlert;
  501. for (UIUserNotificationCategory *c in s.categories)
  502. settings.categories.add (PushNotificationsDelegateDetails::uiUserNotificationCategoryToCategory (c));
  503. owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); });
  504. }
  505. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  506. else
  507. {
  508. [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:
  509. ^(UNNotificationSettings* s)
  510. {
  511. [[UNUserNotificationCenter currentNotificationCenter] getNotificationCategoriesWithCompletionHandler:
  512. ^(NSSet<UNNotificationCategory*>* categories)
  513. {
  514. settings.allowBadge = s.badgeSetting == UNNotificationSettingEnabled;
  515. settings.allowSound = s.soundSetting == UNNotificationSettingEnabled;
  516. settings.allowAlert = s.alertSetting == UNNotificationSettingEnabled;
  517. for (UNNotificationCategory* c in categories)
  518. settings.categories.add (PushNotificationsDelegateDetails::unNotificationCategoryToCategory (c));
  519. owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); });
  520. }
  521. ];
  522. }];
  523. }
  524. #endif
  525. }
  526. bool areNotificationsEnabled() const { return true; }
  527. void sendLocalNotification (const Notification& n)
  528. {
  529. if (iOSEarlierThan10)
  530. {
  531. auto* notification = PushNotificationsDelegateDetails::juceNotificationToUILocalNotification (n);
  532. [[UIApplication sharedApplication] scheduleLocalNotification: notification];
  533. [notification release];
  534. }
  535. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  536. else
  537. {
  538. UNNotificationRequest* request = PushNotificationsDelegateDetails::juceNotificationToUNNotificationRequest (n);
  539. [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest: request
  540. withCompletionHandler: ^(NSError* error)
  541. {
  542. jassert (error == nil);
  543. if (error != nil)
  544. NSLog (nsStringLiteral ("addNotificationRequest error: %@"), error);
  545. }];
  546. }
  547. #endif
  548. }
  549. void getDeliveredNotifications() const
  550. {
  551. if (iOSEarlierThan10)
  552. {
  553. // Not supported on this platform
  554. jassertfalse;
  555. owner.listeners.call ([] (Listener& l) { l.deliveredNotificationsListReceived ({}); });
  556. }
  557. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  558. else
  559. {
  560. [[UNUserNotificationCenter currentNotificationCenter] getDeliveredNotificationsWithCompletionHandler:
  561. ^(NSArray<UNNotification*>* notifications)
  562. {
  563. Array<PushNotifications::Notification> notifs;
  564. for (UNNotification* n in notifications)
  565. notifs.add (PushNotificationsDelegateDetails::unNotificationToJuceNotification (n));
  566. owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifs); });
  567. }];
  568. }
  569. #endif
  570. }
  571. void removeAllDeliveredNotifications()
  572. {
  573. if (iOSEarlierThan10)
  574. {
  575. // Not supported on this platform
  576. jassertfalse;
  577. }
  578. else
  579. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  580. {
  581. [[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
  582. }
  583. #endif
  584. }
  585. void removeDeliveredNotification (const String& identifier)
  586. {
  587. if (iOSEarlierThan10)
  588. {
  589. ignoreUnused (identifier);
  590. // Not supported on this platform
  591. jassertfalse;
  592. }
  593. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  594. else
  595. {
  596. NSArray<NSString*>* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)];
  597. [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers: identifiers];
  598. }
  599. #endif
  600. }
  601. void setupChannels (const Array<ChannelGroup>& groups, const Array<Channel>& channels)
  602. {
  603. ignoreUnused (groups, channels);
  604. }
  605. void getPendingLocalNotifications() const
  606. {
  607. if (iOSEarlierThan10)
  608. {
  609. Array<PushNotifications::Notification> notifs;
  610. for (UILocalNotification* n in [UIApplication sharedApplication].scheduledLocalNotifications)
  611. notifs.add (PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (n));
  612. owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
  613. }
  614. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  615. else
  616. {
  617. [[UNUserNotificationCenter currentNotificationCenter] getPendingNotificationRequestsWithCompletionHandler:
  618. ^(NSArray<UNNotificationRequest*>* requests)
  619. {
  620. Array<PushNotifications::Notification> notifs;
  621. for (UNNotificationRequest* r : requests)
  622. notifs.add (PushNotificationsDelegateDetails::unNotificationRequestToJuceNotification (r));
  623. owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
  624. }
  625. ];
  626. }
  627. #endif
  628. }
  629. void removePendingLocalNotification (const String& identifier)
  630. {
  631. if (iOSEarlierThan10)
  632. {
  633. // Not supported on this platform
  634. jassertfalse;
  635. }
  636. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  637. else
  638. {
  639. NSArray<NSString*>* identifiers = [NSArray arrayWithObject: juceStringToNS (identifier)];
  640. [[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers: identifiers];
  641. }
  642. #endif
  643. }
  644. void removeAllPendingLocalNotifications()
  645. {
  646. if (iOSEarlierThan10)
  647. {
  648. [[UIApplication sharedApplication] cancelAllLocalNotifications];
  649. }
  650. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  651. else
  652. {
  653. [[UNUserNotificationCenter currentNotificationCenter] removeAllPendingNotificationRequests];
  654. }
  655. #endif
  656. }
  657. String getDeviceToken()
  658. {
  659. // You need to call requestPermissionsWithSettings() first.
  660. jassert (initialised);
  661. return deviceToken;
  662. }
  663. //==============================================================================
  664. //PushNotificationsDelegate
  665. void didRegisterUserNotificationSettings (UIUserNotificationSettings*) override
  666. {
  667. requestSettingsUsed();
  668. }
  669. void registeredForRemoteNotifications (NSData* deviceTokenToUse) override
  670. {
  671. deviceToken = [deviceTokenToUse]() -> String
  672. {
  673. auto length = deviceTokenToUse.length;
  674. if (auto* buffer = (const unsigned char*) deviceTokenToUse.bytes)
  675. {
  676. NSMutableString* hexString = [NSMutableString stringWithCapacity: (length * 2)];
  677. for (NSUInteger i = 0; i < length; ++i)
  678. [hexString appendFormat:@"%02x", buffer[i]];
  679. return nsStringToJuce ([hexString copy]);
  680. }
  681. return {};
  682. }();
  683. initialised = true;
  684. owner.listeners.call ([&] (Listener& l) { l.deviceTokenRefreshed (deviceToken); });
  685. }
  686. void failedToRegisterForRemoteNotifications (NSError* error) override
  687. {
  688. ignoreUnused (error);
  689. deviceToken.clear();
  690. }
  691. void didReceiveRemoteNotification (NSDictionary* userInfo) override
  692. {
  693. auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo);
  694. owner.listeners.call ([&] (Listener& l) { l.handleNotification (false, n); });
  695. }
  696. void didReceiveRemoteNotificationFetchCompletionHandler (NSDictionary* userInfo,
  697. void (^completionHandler)(UIBackgroundFetchResult result)) override
  698. {
  699. didReceiveRemoteNotification (userInfo);
  700. completionHandler (UIBackgroundFetchResultNewData);
  701. }
  702. void handleActionForRemoteNotificationCompletionHandler (NSString* actionIdentifier,
  703. NSDictionary* userInfo,
  704. NSDictionary* responseInfo,
  705. void (^completionHandler)()) override
  706. {
  707. auto n = PushNotificationsDelegateDetails::nsDictionaryToJuceNotification (userInfo);
  708. auto actionString = nsStringToJuce (actionIdentifier);
  709. auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo);
  710. owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (false, n, actionString, response); });
  711. completionHandler();
  712. }
  713. void didReceiveLocalNotification (UILocalNotification* notification) override
  714. {
  715. auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification);
  716. owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, n); });
  717. }
  718. void handleActionForLocalNotificationCompletionHandler (NSString* actionIdentifier,
  719. UILocalNotification* notification,
  720. void (^completionHandler)()) override
  721. {
  722. handleActionForLocalNotificationWithResponseCompletionHandler (actionIdentifier,
  723. notification,
  724. nil,
  725. completionHandler);
  726. }
  727. void handleActionForLocalNotificationWithResponseCompletionHandler (NSString* actionIdentifier,
  728. UILocalNotification* notification,
  729. NSDictionary* responseInfo,
  730. void (^completionHandler)()) override
  731. {
  732. auto n = PushNotificationsDelegateDetails::uiLocalNotificationToJuceNotification (notification);
  733. auto actionString = nsStringToJuce (actionIdentifier);
  734. auto response = PushNotificationsDelegateDetails::getUserResponseFromNSDictionary (responseInfo);
  735. owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (true, n, actionString, response); });
  736. completionHandler();
  737. }
  738. #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  739. void willPresentNotificationWithCompletionHandler (UNNotification* notification,
  740. void (^completionHandler)(UNNotificationPresentationOptions options)) override
  741. {
  742. NSUInteger options = NSUInteger ((int)settings.allowBadge << 0
  743. | (int)settings.allowSound << 1
  744. | (int)settings.allowAlert << 2);
  745. ignoreUnused (notification);
  746. completionHandler (options);
  747. }
  748. void didReceiveNotificationResponseWithCompletionHandler (UNNotificationResponse* response,
  749. void (^completionHandler)()) override
  750. {
  751. const bool remote = [response.notification.request.trigger isKindOfClass: [UNPushNotificationTrigger class]];
  752. auto actionString = nsStringToJuce (response.actionIdentifier);
  753. if (actionString == "com.apple.UNNotificationDefaultActionIdentifier")
  754. actionString.clear();
  755. else if (actionString == "com.apple.UNNotificationDismissActionIdentifier")
  756. actionString = "com.juce.NotificationDeleted";
  757. auto n = PushNotificationsDelegateDetails::unNotificationToJuceNotification (response.notification);
  758. String responseString;
  759. if ([response isKindOfClass: [UNTextInputNotificationResponse class]])
  760. {
  761. UNTextInputNotificationResponse* textResponse = (UNTextInputNotificationResponse*)response;
  762. responseString = nsStringToJuce (textResponse.userText);
  763. }
  764. owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (! remote, n, actionString, responseString); });
  765. completionHandler();
  766. }
  767. #endif
  768. void subscribeToTopic (const String& topic) { ignoreUnused (topic); }
  769. void unsubscribeFromTopic (const String& topic) { ignoreUnused (topic); }
  770. void sendUpstreamMessage (const String& serverSenderId,
  771. const String& collapseKey,
  772. const String& messageId,
  773. const String& messageType,
  774. int timeToLive,
  775. const StringPairArray& additionalData)
  776. {
  777. ignoreUnused (serverSenderId, collapseKey, messageId, messageType);
  778. ignoreUnused (timeToLive, additionalData);
  779. }
  780. private:
  781. PushNotifications& owner;
  782. const bool iOSEarlierThan10 = std::floor (NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max;
  783. bool initialised = false;
  784. String deviceToken;
  785. PushNotifications::Settings settings;
  786. };
  787. } // namespace juce