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.

1122 lines
51KB

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