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.

juce_ios_PushNotifications.cpp 46KB

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