/// <summary> /// Initializes a new instance of the <see cref="T:Sensus.Notifications.PushNotificationRequest"/> class. The /// intent of this instance will be to deliver a <see cref="PushNotificationUpdate"/> to the app. This update /// does not necessarily have any user-targeted content. /// </summary> /// <param name="id">Identifier of the request. To update a previously issued request, submit a new request /// with the same identifier.</param> /// <param name="deviceId">Identifier of target device.</param> /// <param name="protocol">Target protocol.</param> /// <param name="update">Update.</param> /// <param name="format">Format.</param> /// <param name="notificationTime">Time to deliver the notification.</param> /// <param name="backendKey">Backend storage key. It is reasonable to supply a new <see cref="Guid"/> for each /// <see cref="PushNotificationRequest"/> instance.</param> public PushNotificationRequest(string id, string deviceId, Protocol protocol, PushNotificationUpdate update, PushNotificationRequestFormat format, DateTimeOffset notificationTime, Guid backendKey) : this(id, deviceId, protocol, null, null, null, format, notificationTime, backendKey) { _update = update; }
private async Task ApplyPendingUpdatesAsync(CancellationToken cancellationToken) { // apply pending updates until we're cancelled (e.g., due to ios background timeout). List <Tuple <PushNotificationUpdate, Protocol> > pendingUpdateProtocols; lock (_pendingUpdateProtocols) { pendingUpdateProtocols = _pendingUpdateProtocols.ToList(); } SensusServiceHelper.Get().Logger.Log("Attempting to apply " + pendingUpdateProtocols.Count + " pending update(s).", LoggingLevel.Normal, GetType()); foreach (Tuple <PushNotificationUpdate, Protocol> pendingUpdateProtocol in pendingUpdateProtocols) { try { if (cancellationToken.IsCancellationRequested) { SensusServiceHelper.Get().Logger.Log("Cancellation token cancelled while applying pending update(s). ", LoggingLevel.Normal, GetType()); break; } PushNotificationUpdate pendingUpdate = pendingUpdateProtocol.Item1; Protocol protocol = pendingUpdateProtocol.Item2; #region callback if (pendingUpdate.Type == PushNotificationUpdateType.Callback) { string callbackId = pendingUpdate.Content.Value <string>("callback-id"); string invocationId = pendingUpdate.Content.Value <string>("invocation-id"); #if __IOS__ // cancel any previously delivered local notifications for the callback. we do not need to cancel // any pending notifications, as they will either (a) be canceled if the callback is non-repeating // or (b) be replaced if the callback is repeating and gets rescheduled. furthermore, there is a // race condition on app activation in which the callback is updated, run, and rescheduled, after // which the push notification is delivered and is processed. cancelling the newly rescheduled // pending local push notification at this point will terminate the local invocation loop, and the // callback command at this point will contain an invalid invocation ID causing it to not be // rescheduled). thus, both local and remote invocation will terminate and the probe will halt. UserNotifications.UNUserNotificationCenter.Current.RemoveDeliveredNotifications(new[] { callbackId }); #endif await SensusContext.Current.CallbackScheduler.RaiseCallbackAsync(callbackId, invocationId); } #endregion #region clear pnr backlog else if (pendingUpdate.Type == PushNotificationUpdateType.ClearPushNotificationRequestBacklog) { await ClearPushNotificationRequestBacklogAsync(cancellationToken); } #endregion #region protocol else if (pendingUpdate.Type == PushNotificationUpdateType.Protocol) { List <ProtocolSetting> settings = pendingUpdate.Content.Value <JArray>("settings") .Select(setting => setting.ToObject <ProtocolSetting>()) .ToList(); if (await protocol.ApplySettingsAsync(settings, cancellationToken)) { JObject userNotificationObject = pendingUpdate.Content.Value <JObject>("user-notification"); if (userNotificationObject != null) { string message = userNotificationObject.Value <string>("message"); message = "Your study has been updated" + (string.IsNullOrWhiteSpace(message) ? "." : ": " + message.Trim()); await IssueNotificationAsync("Study Updated", message, pendingUpdate.Id, true, protocol, null, NotificationUserResponseAction.None, message); } } } #endregion #region survey agent policy else if (pendingUpdate.Type == PushNotificationUpdateType.SurveyAgentPolicy) { await protocol.UpdateScriptAgentPolicyAsync(pendingUpdate.Content); } #endregion #region sensing agent policy else if (pendingUpdate.Type == PushNotificationUpdateType.SensingAgentPolicy) { await protocol.UpdateSensingAgentPolicyAsync(pendingUpdate.Content); } #endregion } catch (Exception ex) { SensusServiceHelper.Get().Logger.Log("Exception while applying update: " + ex.Message, LoggingLevel.Normal, GetType()); } finally { // if we haven't been cancelled, then consider the pending update to be complete. this // leaves open the race condition in which the update was actually applied just prior // to the cancellation. in this case, the update will be retained and will be run again // the next time we receive a push notification. we don't see much harm in this for the // operations listed above. if (!cancellationToken.IsCancellationRequested) { lock (_pendingUpdateProtocols) { _pendingUpdateProtocols.Remove(pendingUpdateProtocol); } } } } lock (_pendingUpdateProtocols) { SensusServiceHelper.Get().Logger.Log(_pendingUpdateProtocols.Count + " pending update(s) left.", LoggingLevel.Normal, GetType()); } }