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