/// <summary> /// Called just prior to a notification being presented while the app is in the foreground. /// </summary> /// <param name="center"></param> /// <param name="notification"></param> /// <param name="completionHandler"></param> public override async void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action <UNNotificationPresentationOptions> completionHandler) { string identifier = notification?.Request?.Identifier; SensusServiceHelper.Get().Logger.Log("Notification delivered: " + (identifier ?? "[null identifier]"), LoggingLevel.Normal, GetType()); // if the notification is for a callback, service the callback and do not show the notification. we use the local // notification loop to schedule callback events and don't want to display the messages when the app is foregrounded. NSDictionary notificationInfo = notification?.Request?.Content?.UserInfo; iOSCallbackScheduler callbackScheduler = SensusContext.Current.CallbackScheduler as iOSCallbackScheduler; if (callbackScheduler.IsCallback(notificationInfo)) { await callbackScheduler.RaiseCallbackAsync(notificationInfo); completionHandler?.Invoke(UNNotificationPresentationOptions.None); } else if (identifier == Notifier.PENDING_SURVEY_TEXT_NOTIFICATION_ID) { completionHandler?.Invoke(UNNotificationPresentationOptions.Alert | UNNotificationPresentationOptions.Badge | UNNotificationPresentationOptions.Sound); } else if (identifier == Notifier.PENDING_SURVEY_BADGE_NOTIFICATION_ID) { completionHandler?.Invoke(UNNotificationPresentationOptions.Badge); } else { completionHandler?.Invoke(UNNotificationPresentationOptions.Alert | UNNotificationPresentationOptions.Badge | UNNotificationPresentationOptions.Sound); } }
public async Task IssueNotificationAsync(UNNotificationRequest request) { // although we should never, we might be getting in null requests from somewhere. bail if we do. if (request == null) { SensusException.Report("Null notification request."); return; } // don't issue silent notifications from the background, as they will be displayed to the user upon delivery, and this will confuse the user (they're // not designed to be seen). this can happen in a race condition where sensus transitions to the background but has a small amount of time to execute, // and in that time a silent callback (e.g., for local data store) is scheduled. checking for background state below will help mitigate this. bool abort = false; SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() => { iOSCallbackScheduler callbackScheduler = SensusContext.Current.CallbackScheduler as iOSCallbackScheduler; ScheduledCallback callback = callbackScheduler.TryGetCallback(request?.Content?.UserInfo); abort = (callback?.Silent ?? false) && UIApplication.SharedApplication.ApplicationState == UIApplicationState.Background; }); if (abort) { SensusServiceHelper.Get().Logger.Log("Aborting notification: Will not issue silent notification from background.", LoggingLevel.Normal, GetType()); return; } await UNUserNotificationCenter.Current.AddNotificationRequestAsync(request); SensusServiceHelper.Get().Logger.Log("Notification " + request.Identifier + " requested for " + ((request.Trigger as UNCalendarNotificationTrigger)?.NextTriggerDate.ToString() ?? "[time not specified]") + ".", LoggingLevel.Normal, GetType()); }
/// <summary> /// Pre-10.0: Handles notifications received when the app is in the foreground. See <see cref="UNUserNotificationDelegate.WillPresentNotification"/> for /// the corresponding handler for iOS 10.0 and above. /// </summary> /// <param name="application">Application.</param> /// <param name="notification">Notification.</param> public override void ReceivedLocalNotification(UIApplication application, UILocalNotification notification) { // UILocalNotifications were obsoleted in iOS 10.0, and we should not be receiving them via this app delegate // method. we won't have any idea how to service them on iOS 10.0 and above. report the problem and bail. if (UIDevice.CurrentDevice.CheckSystemVersion(10, 0)) { SensusException.Report("Received UILocalNotification in iOS 10 or later."); } else { // we're in iOS < 10.0, which means we should have a notifier and scheduler to handle the notification. // cancel notification (removing it from the tray), since it has served its purpose (SensusContext.Current.Notifier as IUILocalNotificationNotifier)?.CancelNotification(notification); iOSCallbackScheduler callbackScheduler = SensusContext.Current.CallbackScheduler as iOSCallbackScheduler; if (callbackScheduler == null) { SensusException.Report("We don't have an iOSCallbackScheduler."); } else if (notification.UserInfo == null) { SensusException.Report("Null user info passed to ReceivedLocalNotification."); } else { // run asynchronously to release the UI thread System.Threading.Tasks.Task.Run(async() => { // we've tried pulling some of the code below out of the UI thread, but we do not receive/process // the callback notifications when doing so.. await SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(async() => { // service the callback if we've got one (not all notification userinfo bundles are for callbacks) if (callbackScheduler.IsCallback(notification.UserInfo)) { await callbackScheduler.ServiceCallbackAsync(notification.UserInfo); } // check whether the user opened the notification to open sensus, indicated by an application state that is not active. we'll // also get notifications when the app is active, since we use them for timed callback events. if (application.ApplicationState != UIApplicationState.Active) { // if the user opened the notification, display the page associated with the notification, if any. callbackScheduler.OpenDisplayPage(notification.UserInfo); // provide some generic feedback if the user responded to a silent callback notification if (callbackScheduler.TryGetCallback(notification.UserInfo)?.Silent ?? false) { await SensusServiceHelper.Get().FlashNotificationAsync("Study Updated."); } } }); }); } } }
/// <summary> /// Called just prior to a notification being presented while the app is in the foreground. /// </summary> /// <param name="center"></param> /// <param name="notification"></param> /// <param name="completionHandler"></param> public override void WillPresentNotification(UNUserNotificationCenter center, UNNotification notification, Action <UNNotificationPresentationOptions> completionHandler) { SensusServiceHelper.Get().Logger.Log("Notification delivered: " + (notification?.Request?.Identifier ?? "[null identifier]"), LoggingLevel.Normal, GetType()); // long story: app is backgrounded, and multiple non-silent sensus notifications appear in the iOS tray. the user taps one of these, which // dismisses the tapped notification and brings up sensus. upon activation sensus then updates and reissues all notifications. these reissued // notifications will come directly to the app as long as it's in the foreground. the original notifications that were in the iOS notification // tray will still be there, despite the fact that the notifications have been sent to the app via the current method. short story: we need to // cancel each notification as it comes in to remove it from the notification center. SensusContext.Current.Notifier.CancelNotification(notification?.Request?.Identifier); iOSCallbackScheduler callbackScheduler = SensusContext.Current.CallbackScheduler as iOSCallbackScheduler; if (callbackScheduler.IsCallback(notification?.Request?.Content?.UserInfo)) { callbackScheduler.ServiceCallbackAsync(notification?.Request?.Content?.UserInfo); } }
/// <summary> /// Called when the user taps a notification. /// </summary> /// <param name="center"></param> /// <param name="response"></param> /// <param name="completionHandler"></param> public override void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler) { UNNotificationRequest request = response?.Notification?.Request; NSDictionary notificationInfo = request?.Content?.UserInfo; if (notificationInfo != null) { SensusServiceHelper.Get().Logger.Log("Notification received user response: " + (request.Identifier ?? "[null identifier]"), LoggingLevel.Normal, GetType()); // if the notification is associated with a particular UI page to display, show that page now. iOSCallbackScheduler callbackScheduler = SensusContext.Current.CallbackScheduler as iOSCallbackScheduler; callbackScheduler.OpenDisplayPage(notificationInfo); // provide some generic feedback if the user responded to a silent notification. this should only happen in race cases where // a silent notification is issued just before we enter background. if (callbackScheduler.TryGetCallback(notificationInfo)?.Silent ?? false) { SensusServiceHelper.Get().FlashNotificationAsync("Study Updated."); } } completionHandler(); }