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