protected override void ScheduleCallbackPlatformSpecific(ScheduledCallback callback)
        {
            // get the callback information. this can be null if we don't have all required information. don't schedule the notification if this happens.
            NSMutableDictionary callbackInfo = GetCallbackInfo(callback);

            if (callbackInfo == null)
            {
                return;
            }

            Action <UILocalNotification> notificationCreated = notification =>
            {
                lock (_callbackIdNotification)
                {
                    _callbackIdNotification.Add(callback.Id, notification);
                }
            };

            IUILocalNotificationNotifier notifier = SensusContext.Current.Notifier as IUILocalNotificationNotifier;

            if (callback.Silent)
            {
                notifier.IssueSilentNotificationAsync(callback.Id, callback.NextExecution.Value, callbackInfo, notificationCreated);
            }
            else
            {
                notifier.IssueNotificationAsync(callback.Protocol?.Name ?? "Alert", callback.UserNotificationMessage, callback.Id, callback.Protocol, true, callback.DisplayPage, callback.NextExecution.Value, callbackInfo, notificationCreated);
            }
        }
示例#2
0
        public DataFlowQueue([NotNull] ProducerConsumerQueueOptions <T> options, CancellationToken token = default(CancellationToken))
            : base(options, token)
        {
            _workCompletedEventSlim = new ManualResetEventSlim(false);

            // Define the mesh.
            _queue = new BufferBlock <T>(new DataflowBlockOptions
            {
                CancellationToken = Token
            });
            _processor = new ActionBlock <T>(e =>
            {
                ScheduledCallback?.Invoke(e);
                Run(e);
            }, new ExecutionDataflowBlockOptions
            {
                MaxDegreeOfParallelism = Threads,
                CancellationToken      = Token
            });
            _link = _queue.LinkTo(_processor, new DataflowLinkOptions
            {
                PropagateCompletion = true
            });
            Task.Run(Consume).ConfigureAwait();
        }
示例#3
0
        protected override async Task RequestLocalInvocationAsync(ScheduledCallback callback)
        {
            // get the callback information. this can be null if we don't have all required information. don't schedule the notification if this happens.
            NSMutableDictionary callbackInfo = GetCallbackInfo(callback);

            if (callbackInfo == null)
            {
                return;
            }

            Action <UNNotificationRequest> requestCreated = request =>
            {
                lock (_callbackIdRequest)
                {
                    _callbackIdRequest[callback.Id] = request;
                }
            };

            UNUserNotificationNotifier notifier = SensusContext.Current.Notifier as UNUserNotificationNotifier;

            if (callback.Silent)
            {
                await notifier.IssueSilentNotificationAsync(callback.Id, callback.NextExecution.Value, callbackInfo, requestCreated);
            }
            else
            {
                await notifier.IssueNotificationAsync(callback.Protocol?.Name ?? "Alert", callback.UserNotificationMessage, callback.Id, true, callback.Protocol, null, callback.NotificationUserResponseAction, callback.NotificationUserResponseMessage, callback.NextExecution.Value, callbackInfo, requestCreated);
            }
        }
示例#4
0
        public Task ServiceCallbackAsync(ScheduledCallback callback)
        {
            return(Task.Run(async() =>
            {
                if (callback == null)
                {
                    SensusServiceHelper.Get().Logger.Log("Attempted to service null callback.", LoggingLevel.Normal, GetType());
                    return;
                }

                SensusServiceHelper.Get().Logger.Log("Servicing callback " + callback.Id + ".", LoggingLevel.Normal, GetType());

                // start background task for callback
                nint callbackTaskId = -1;
                SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() =>
                {
                    callbackTaskId = UIApplication.SharedApplication.BeginBackgroundTask(() =>
                    {
                        // if we're out of time running in the background, cancel the callback.
                        CancelRaisedCallback(callback);
                    });
                });

                // raise callback but don't notify user since we would have already done so when the notification was delivered to the notification tray.
                // we don't need to specify how repeats will be scheduled, since the class that extends this one will take care of it. furthermore, there's
                // nothing to do if the callback thinks we can sleep, since ios does not provide wake-locks like android.
                await RaiseCallbackAsync(callback, false, null, null);

                // end the background task
                SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() =>
                {
                    UIApplication.SharedApplication.EndBackgroundTask(callbackTaskId);
                });
            }));
        }
示例#5
0
        private void ScheduleScriptRun(ScriptTriggerTime triggerTime)
        {
            // don't bother with the script if it's coming too soon.
            if (triggerTime.ReferenceTillTrigger.TotalMinutes <= 1)
            {
                return;
            }

            ScheduledCallback callback = CreateScriptRunCallback(triggerTime);

            // there is a race condition, so far only seen in ios, in which multiple script runner notifications
            // accumulate and are executed concurrently when the user opens the app. when these script runners
            // execute they add their scripts to the pending scripts collection and concurrently attempt to
            // schedule all future scripts. because two such attempts are made concurrently, they may race to
            // schedule the same future script. each script callback id is functional in the sense that it is
            // a string denoting the script to run and the time window to run within. thus, the callback ids can
            // duplicate. the callback scheduler checks for such duplicate ids and will return unscheduled on the next
            // line when a duplicate is detected. in the case of a duplicate we can simply abort scheduling the
            // script run since it was already scheduled. this issue is much less common in android because all
            // scripts are run immediately in the background, producing little opportunity for the race condition.
            if (SensusContext.Current.CallbackScheduler.ScheduleCallback(callback) == ScheduledCallbackState.Scheduled)
            {
                lock (_scriptRunCallbacks)
                {
                    _scriptRunCallbacks.Add(callback);
                }

                SensusServiceHelper.Get().Logger.Log($"Scheduled for {triggerTime.Trigger} ({callback.Id})", LoggingLevel.Normal, GetType());

                _maxScheduledDate = _maxScheduledDate.Max(triggerTime.Trigger);
            }
        }
        protected override async Task RequestLocalInvocationAsync(ScheduledCallback callback)
        {
            if (callback.NextExecution != null)
            {
                if (_timers.TryGetValue(callback.Id, out NSTimer existingTimer) == false || existingTimer.TimeInterval != callback.RepeatDelay?.TotalSeconds)
                {
                    CancelLocalInvocation(callback);

                    SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() =>
                    {
                        NSTimer timer = new NSTimer((NSDate)callback.NextExecution.Value, callback.RepeatDelay ?? TimeSpan.Zero, async t =>
                        {
                            await RaiseCallbackAsync(callback, callback.InvocationId);
                        }, callback.RepeatDelay.HasValue);

                        /*{
                         *      Tolerance = callback.RepeatDelay.Value.TotalMilliseconds * .10
                         * };*/

                        NSRunLoop.Main.AddTimer(timer, NSRunLoopMode.Default);

                        lock (_timers)
                        {
                            _timers[callback.Id] = timer;
                        }
                    });
                }
            }

            await Task.CompletedTask;
        }
示例#7
0
        public AwsHttpClient(Utils.Executor executor, ISocketFactory socketFactory, string endpoint, int port, bool verifyCertificate, bool secure, ulong minConnections = 1, ulong maxConnections = 6, ulong connectTimeout = 10000, ulong requestTimeout = 10000, bool single_use_sockets = false)
        {
            int poll_delay = 3000;

            this.executor_          = executor;
            this.socketFactory_     = socketFactory;
            this.endpoint           = endpoint;
            this.port               = port;
            this.verifyCertificate  = verifyCertificate;
            this.secure             = secure;
            this.minConnections     = minConnections;
            this.maxConnections     = maxConnections;
            this.connectTimeout     = connectTimeout;
            this.requestTimeout     = requestTimeout;
            this.scheduled_callback = executor_.Schedule
                                      (
                () =>
            {
                run_tasks();
                lock (mutex_)
                    scheduled_callback.reschedule(poll_delay);
            },
                poll_delay
                                      );
        }
示例#8
0
        public override void Reset()
        {
            base.Reset();

            _mostRecentSuccessfulWriteTime = null;
            _writeCallback = null;
        }
示例#9
0
        public async Task RaiseCallbackAsync(Intent intent)
        {
            ScheduledCallback callback = TryGetCallback(intent.Action);

            if (callback == null)
            {
                return;
            }

            SensusServiceHelper serviceHelper = SensusServiceHelper.Get();

            // if the user removes the main activity from the switcher, the service's process will be killed and restarted without notice, and
            // we'll have no opportunity to unschedule repeating callbacks. when the service is restarted we'll reinitialize the service
            // helper, restart the repeating callbacks, and we'll then have duplicate repeating callbacks. handle the invalid callbacks below.
            // if the callback is present, it's fine. if it's not, then unschedule it.
            if (ContainsCallback(callback))
            {
                string invocationId = intent.GetStringExtra(SENSUS_CALLBACK_INVOCATION_ID_KEY);

                await RaiseCallbackAsync(callback, invocationId);
            }
            else
            {
                await UnscheduleCallbackAsync(callback);
            }
        }
示例#10
0
        protected override void StartListening()
        {
            // we expect this probe to start successfully, but an exception may occur if no bands are paired with the device of if the
            // connection with a paired band fails. so schedule a static repeating callback to check on all band probes and restart them
            // if needed/possible. this is better than a non-static callback for each band probe because there are many band probes and
            // the callbacks would be redundant, frequent, and power-hungry.
            lock (HEALTH_TEST_LOCKER)
            {
                // only schedule the callback if we haven't done so already. the callback should be global across all band probes.
                if (HEALTH_TEST_CALLBACK == null)
                {
                    // the band health test is static, so it has no domain other than sensus.
                    HEALTH_TEST_CALLBACK = new ScheduledCallback(TestBandClientAsync, "BAND-HEALTH-TEST", null, null, HEALTH_TEST_TIMEOUT);
                    SensusContext.Current.CallbackScheduler.ScheduleRepeatingCallback(HEALTH_TEST_CALLBACK, HEALTH_TEST_DELAY, HEALTH_TEST_DELAY, false);
                }
            }

            // hook up the contact event for non-contact probes. need to do this before the calls below because they might throw
            // a band connect exception. such an exception will leave the probe in a running state in anticipation that the user
            // might pair a band later. the band health test will start readings later for all band probes, but it will not use
            // Start to do so (it will simply start the readings). so this is our only chance to hook up the contact event.
            if (!(this is MicrosoftBandContactProbe))
            {
                MicrosoftBandContactProbe contactProbe = Protocol.Probes.Single(probe => probe is MicrosoftBandContactProbe) as MicrosoftBandContactProbe;
                contactProbe.ContactStateChanged += ContactStateChanged;
            }

            ConnectClient();
            Configure(BandClient);
            StartReadings();
        }
示例#11
0
        /// <summary>
        /// Updates the callbacks when the app is activated. This services any callbacks that should have already been
        /// serviced or will be serviced in the near future. This also reissues all silent notifications, which would
        /// have been canceled when the app went into the background.
        /// </summary>
        /// <returns>Async task.</returns>
        public async Task UpdateCallbacksOnActivationAsync()
        {
            foreach (string callbackId in CallbackIds)
            {
                ScheduledCallback callback = TryGetCallback(callbackId);

                if (callback == null)
                {
                    continue;
                }

                // service any callback that should have already been serviced or will soon be serviced
                if (callback.NextExecution.Value < DateTime.Now + CALLBACK_NOTIFICATION_HORIZON_THRESHOLD)
                {
                    iOSNotifier notifier = SensusContext.Current.Notifier as iOSNotifier;
                    notifier.CancelNotification(callback.Id);
                    await RaiseCallbackAsync(callback, callback.InvocationId);
                }
                // all silent notifications (e.g., those for health tests) were cancelled when the app entered background. reissue them now.
                // if the notification has already been issued, it will simply be replaced with itself (no change).
                else if (callback.Silent)
                {
                    await ReissueSilentNotificationAsync(callback.Id);
                }
                else
                {
                    SensusServiceHelper.Get().Logger.Log("Non-silent callback notification " + callback.Id + " has upcoming trigger time of " + callback.NextExecution, LoggingLevel.Normal, GetType());
                }
            }
        }
        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());
        }
示例#13
0
        public async Task RaiseCallbackAsync(NSDictionary callbackInfo)
        {
            ScheduledCallback callback     = TryGetCallback(callbackInfo);
            string            invocationId = callbackInfo?.ValueForKey(new NSString(SENSUS_CALLBACK_INVOCATION_ID_KEY)) as NSString;

            await RaiseCallbackAsync(callback, invocationId);
        }
示例#14
0
        public override async Task RaiseCallbackAsync(ScheduledCallback callback, string invocationId)
        {
            // start a background task for raising the callback
            nint raiseCallbackTaskId = -1;

            SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() =>
            {
                SensusServiceHelper.Get().Logger.Log("Starting background task for callback.", LoggingLevel.Normal, GetType());

                raiseCallbackTaskId = UIApplication.SharedApplication.BeginBackgroundTask(() =>
                {
                    // if we're out of time running in the background, cancel the callback.
                    CancelRaisedCallback(callback);
                });
            });

            await base.RaiseCallbackAsync(callback, invocationId);

            // end the background task for raising the callback
            SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() =>
            {
                SensusServiceHelper.Get().Logger.Log("Ending background task for callback.", LoggingLevel.Normal, GetType());

                UIApplication.SharedApplication.EndBackgroundTask(raiseCallbackTaskId);
            });
        }
示例#15
0
        private void ScheduleCallbackAlarm(ScheduledCallback callback, PendingIntent callbackPendingIntent)
        {
            AlarmManager alarmManager = _service.GetSystemService(global::Android.Content.Context.AlarmService) as AlarmManager;

            // cancel the current alarm for this callback id. this deals with the situations where (1) sensus schedules callbacks
            // and is subsequently restarted, or (2) a probe is restarted after scheduling callbacks (e.g., script runner). in
            // these situations, the originally scheduled callbacks will remain in the alarm manager, and we'll begin piling up
            // additional, duplicate alarms. we need to be careful to avoid duplicate alarms, and this is how we manage it.
            alarmManager.Cancel(callbackPendingIntent);

            long callbackTimeMS = callback.NextExecution.Value.ToJavaCurrentTimeMillis();

            // see the Backwards Compatibility article for more information
#if __ANDROID_23__
            if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
            {
                alarmManager.SetExactAndAllowWhileIdle(AlarmType.RtcWakeup, callbackTimeMS, callbackPendingIntent);  // API level 23 added "while idle" option, making things even tighter.
            }
            else if (Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat)
            {
                alarmManager.SetExact(AlarmType.RtcWakeup, callbackTimeMS, callbackPendingIntent);  // API level 19 differentiated Set (loose) from SetExact (tight)
            }
            else
#endif
            {
                alarmManager.Set(AlarmType.RtcWakeup, callbackTimeMS, callbackPendingIntent);  // API 1-18 treats Set as a tight alarm
            }

            SensusServiceHelper.Get().Logger.Log("Alarm scheduled for callback " + callback.Id + " at " + callback.NextExecution.Value + ".", LoggingLevel.Normal, GetType());
        }
        public override Task RaiseCallbackAsync(ScheduledCallback callback, bool notifyUser, Action scheduleRepeatCallback, Action letDeviceSleepCallback)
        {
            return(Task.Run(async() =>
            {
                // see corresponding comments in the UILocalNotificationCallbackScheduler

                UNNotificationRequest request;
                lock (_callbackIdRequest)
                {
                    _callbackIdRequest.TryGetValue(callback.Id, out request);
                    _callbackIdRequest.Remove(callback.Id);
                }

                await base.RaiseCallbackAsync(callback, notifyUser,

                                              () =>
                {
                    (SensusContext.Current.Notifier as IUNUserNotificationNotifier).IssueNotificationAsync(request.Identifier, request.Content, callback.NextExecution.Value, newRequest =>
                    {
                        lock (_callbackIdRequest)
                        {
                            _callbackIdRequest.Add(newRequest.Identifier, newRequest);
                        }
                    });
                },

                                              letDeviceSleepCallback
                                              );
            }));
        }
示例#17
0
        protected override void ScheduleCallbackPlatformSpecific(ScheduledCallback callback)
        {
            Intent        callbackIntent        = CreateCallbackIntent(callback);
            PendingIntent callbackPendingIntent = CreateCallbackPendingIntent(callbackIntent);

            ScheduleCallbackAlarm(callback, callbackPendingIntent);
            SensusServiceHelper.Get().Logger.Log("Callback " + callback.Id + " scheduled for " + callback.NextExecution + " " + (callback.RepeatDelay.HasValue ? "(repeating)" : "(one-time)") + ".", LoggingLevel.Normal, GetType());
        }
示例#18
0
        protected override Task RequestLocalInvocationAsync(ScheduledCallback callback)
        {
            Intent        callbackIntent        = CreateCallbackIntent(callback);
            PendingIntent callbackPendingIntent = CreateCallbackPendingIntent(callbackIntent);

            ScheduleCallbackAlarm(callback, callbackPendingIntent);
            SensusServiceHelper.Get().Logger.Log("Callback " + callback.Id + " scheduled for " + callback.NextExecution + " " + (callback.RepeatDelay.HasValue ? "(repeating)" : "(one-time)") + ".", LoggingLevel.Normal, GetType());
            return(Task.CompletedTask);
        }
示例#19
0
        private Intent CreateCallbackIntent(ScheduledCallback callback)
        {
            Intent callbackIntent = new Intent(_service, typeof(AndroidSensusService));

            callbackIntent.SetAction(callback.Id);
            callbackIntent.PutExtra(SENSUS_CALLBACK_KEY, true);
            callbackIntent.PutExtra(SENSUS_CALLBACK_INVOCATION_ID_KEY, callback.InvocationId);
            return(callbackIntent);
        }
示例#20
0
        private Intent CreateCallbackIntent(ScheduledCallback callback)
        {
            Intent callbackIntent = new Intent(_service, typeof(AndroidSensusService));

            callbackIntent.SetAction(callback.Id);
            callbackIntent.PutExtra(Notifier.DISPLAY_PAGE_KEY, callback.DisplayPage.ToString());
            callbackIntent.PutExtra(SENSUS_CALLBACK_KEY, true);
            return(callbackIntent);
        }
示例#21
0
        private void Consume()
        {
            _workStartedEvent.Set();
            if (IsDisposed)
            {
                return;
            }

            try
            {
                T item;

                while (!IsDisposed && !Token.IsCancellationRequested && !CompleteMarked)
                {
                    while (!IsDisposed && !Token.IsCancellationRequested && !CompleteMarked && _queue.IsEmpty)
                    {
                        _queueEvent.WaitOne(TimeSpanHelper.FAST, Token);
                    }

                    if (IsDisposed || Token.IsCancellationRequested)
                    {
                        return;
                    }
                    if (CompleteMarked)
                    {
                        break;
                    }

                    lock (SyncRoot)
                    {
                        if (_queue.IsEmpty || !_queue.TryDequeue(out item))
                        {
                            continue;
                        }
                    }

                    ScheduledCallback?.Invoke(item);
                    Run(item);
                }

                while (!IsDisposed && !Token.IsCancellationRequested && !_queue.IsEmpty)
                {
                    // no lock here
                    if (!_queue.TryDequeue(out item))
                    {
                        continue;
                    }
                    ScheduledCallback?.Invoke(item);
                    Run(item);
                }
            }
            finally
            {
                SignalAndCheck();
            }
        }
示例#22
0
        private ScheduledCallback CreateScriptRunCallback(ScriptTriggerTime triggerTime)
        {
            Script scriptToRun = Script.Copy(true);

            scriptToRun.ExpirationDate   = triggerTime.Expiration;
            scriptToRun.ScheduledRunTime = triggerTime.Trigger;

            ScheduledCallback callback = new ScheduledCallback((callbackId, cancellationToken, letDeviceSleepCallback) =>
            {
                return(Task.Run(() =>
                {
                    SensusServiceHelper.Get().Logger.Log($"Running script on callback ({callbackId})", LoggingLevel.Normal, GetType());

                    if (!Probe.Running || !_enabled)
                    {
                        return;
                    }

                    Run(scriptToRun);

                    lock (_scriptRunCallbacks)
                    {
                        _scriptRunCallbacks.RemoveAll(c => c.Id == callbackId);
                    }

                    // on android, the callback alarm has fired and the script has been run. on ios, the notification has been
                    // delivered (1) either to the app in the foreground or (2) to the notification tray where the user has opened
                    // it -- either way on ios the app is in the foreground and the script has been run. now is a good time to update
                    // the scheduled callbacks to run this script.
                    ScheduleScriptRuns();
                }, cancellationToken));

                // Be careful to use Script.Id rather than script.Id for the callback domain. Using the former means that callbacks are tied to the script runner and not the script copies (the latter) that we will be running. The latter would always be unique.
            }, triggerTime.ReferenceTillTrigger, GetType().FullName + "-" + ((long)(triggerTime.Trigger - DateTime.MinValue).TotalDays) + "-" + triggerTime.Window, Script.Id, Probe.Protocol);

#if __IOS__
            // all scheduled scripts with an expiration should show an expiration date to the user. on iOS this will be the only notification for
            // scheduled surveys, since we don't have a way to update the "you have X pending surveys" notification (generated by triggered
            // surveys) without executing code in the background.
            if (scriptToRun.ExpirationDate.HasValue)
            {
                callback.UserNotificationMessage = "Survey expires on " + scriptToRun.ExpirationDate.Value.ToShortDateString() + " at " + scriptToRun.ExpirationDate.Value.ToShortTimeString() + ".";
            }
            // on iOS, even if we don't have an expiration date we should show some additional notification, again because we don't have a way
            // to update the "you have X pending surveys" notification from the background.
            else
            {
                callback.UserNotificationMessage = "Please open to take survey.";
            }

            callback.DisplayPage = DisplayPage.PendingSurveys;
#endif

            return(callback);
        }
示例#23
0
        public Task ServiceCallbackAsync(Intent intent)
        {
            return(Task.Run(async() =>
            {
                ScheduledCallback callback = TryGetCallback(intent.Action);

                if (callback == null)
                {
                    return;
                }

                SensusServiceHelper serviceHelper = SensusServiceHelper.Get();

                serviceHelper.Logger.Log("Servicing callback " + callback.Id + ".", LoggingLevel.Normal, GetType());

                // if the user removes the main activity from the switcher, the service's process will be killed and restarted without notice, and
                // we'll have no opportunity to unschedule repeating callbacks. when the service is restarted we'll reinitialize the service
                // helper, restart the repeating callbacks, and we'll then have duplicate repeating callbacks. handle the invalid callbacks below.
                // if the callback is present, it's fine. if it's not, then unschedule it.
                if (ContainsCallback(callback))
                {
                    bool wakeLockReleased = false;

                    // raise callback and notify the user if there is a message. we wouldn't have presented the user with the message yet.
                    await RaiseCallbackAsync(callback, true,

                                             // schedule a new alarm for the same callback at the desired time.
                                             () =>
                    {
                        ScheduleCallbackAlarm(callback, CreateCallbackPendingIntent(intent));
                    },

                                             // if the callback indicates that it's okay for the device to sleep, release the wake lock now.
                                             () =>
                    {
                        wakeLockReleased = true;
                        serviceHelper.LetDeviceSleep();
                        serviceHelper.Logger.Log("Wake lock released preemptively for scheduled callback action.", LoggingLevel.Normal, GetType());
                    }
                                             );

                    // release wake lock now if we didn't while the callback action was executing.
                    if (!wakeLockReleased)
                    {
                        serviceHelper.LetDeviceSleep();
                        serviceHelper.Logger.Log("Wake lock released after scheduled callback action completed.", LoggingLevel.Normal, GetType());
                    }
                }
                else
                {
                    UnscheduleCallback(callback);
                    serviceHelper.LetDeviceSleep();
                }
            }));
        }
示例#24
0
 private static void CancelHealthTest()
 {
     lock (HEALTH_TEST_LOCKER)
     {
         if (HEALTH_TEST_CALLBACK != null)
         {
             SensusContext.Current.CallbackScheduler.UnscheduleCallback(HEALTH_TEST_CALLBACK);
             HEALTH_TEST_CALLBACK = null;
         }
     }
 }
示例#25
0
 private static void CancelHealthTest()
 {
     lock (HEALTH_TEST_LOCKER)
     {
         if (HEALTH_TEST_CALLBACK != null)
         {
             SensusServiceHelper.Get().Logger.Log("Canceling health test.", LoggingLevel.Verbose, typeof(MicrosoftBandProbeBase));
             SensusContext.Current.CallbackScheduler.UnscheduleCallback(HEALTH_TEST_CALLBACK.Id);
             HEALTH_TEST_CALLBACK = null;
         }
     }
 }
示例#26
0
        public override void Reset()
        {
            base.Reset();

            _isPolling    = false;
            _pollCallback = null;

            lock (_pollTimes)
            {
                _pollTimes.Clear();
            }
        }
 protected override void UnscheduleCallbackPlatformSpecific(ScheduledCallback callback)
 {
     lock (_callbackIdNotification)
     {
         // there are race conditions on this collection, and the key might be removed elsewhere
         UILocalNotification notification;
         if (_callbackIdNotification.TryGetValue(callback.Id, out notification))
         {
             (SensusContext.Current.Notifier as IUILocalNotificationNotifier)?.CancelNotification(notification);
             _callbackIdNotification.Remove(callback.Id);
         }
     }
 }
 protected override void UnscheduleCallbackPlatformSpecific(ScheduledCallback callback)
 {
     lock (_callbackIdRequest)
     {
         // there are race conditions on this collection, and the key might be removed elsewhere
         UNNotificationRequest request;
         if (_callbackIdRequest.TryGetValue(callback.Id, out request))
         {
             (SensusContext.Current.Notifier as IUNUserNotificationNotifier)?.CancelNotification(request);
             _callbackIdRequest.Remove(callback.Id);
         }
     }
 }
示例#29
0
        /// <summary>
        /// Creates the script run callback.
        /// </summary>
        /// <returns>The script run callback.</returns>
        /// <param name="triggerTime">Trigger time.</param>
        /// <param name="scriptId">Script identifier. If null, then a random identifier will be generated for the script that will be run.</param>
        private ScheduledCallback CreateScriptRunCallback(ScriptTriggerTime triggerTime, string scriptId = null)
        {
            Script scriptToRun = Script.Copy(true);

            scriptToRun.ExpirationDate   = triggerTime.Expiration;
            scriptToRun.ScheduledRunTime = triggerTime.Trigger;

            // if we're passed a run ID, then override the random one that was generated above in the call to Script.Copy.
            if (scriptId != null)
            {
                scriptToRun.Id = scriptId;
            }

            ScheduledCallback callback = new ScheduledCallback(async cancellationToken =>
            {
                SensusServiceHelper.Get().Logger.Log("Running script \"" + Name + "\".", LoggingLevel.Normal, GetType());

                if (Probe.State != ProbeState.Running || !_enabled)
                {
                    return;
                }

                await RunAsync(scriptToRun);

                // on android, the callback alarm has fired and the script has been run. on ios, the notification has been
                // delivered (1) to the app in the foreground, (2) to the notification tray where the user has opened
                // it, or (3) via push notification in the background. in any case, the script has been run. now is a good
                // time to update the scheduled callbacks to run this script.
                await ScheduleScriptRunsAsync();
            }, triggerTime.TimeTillTrigger, Script.Id + "." + GetType().FullName + "." + (triggerTime.Trigger - DateTime.MinValue).Days + "." + triggerTime.Window, Probe.Protocol.Id, Probe.Protocol, null, TimeSpan.FromMilliseconds(DelayToleranceBeforeMS), TimeSpan.FromMilliseconds(DelayToleranceAfterMS));  // use Script.Id rather than script.Id for the callback identifier. using the former means that callbacks are unique to the script runner and not the script copies (the latter) that we will be running. the latter would always be unique.

#if __IOS__
            // all scheduled scripts with an expiration should show an expiration date to the user. on iOS this will be the only notification for
            // scheduled surveys, since we don't have a way to update the "you have X pending surveys" notification (generated by triggered
            // surveys) without executing code in the background.
            if (scriptToRun.ExpirationDate.HasValue)
            {
                callback.UserNotificationMessage = "Survey expires on " + scriptToRun.ExpirationDate.Value.ToShortDateString() + " at " + scriptToRun.ExpirationDate.Value.ToShortTimeString() + ".";
            }
            // on iOS, even if we don't have an expiration date we should show some additional notification, again because we don't have a way
            // to update the "you have X pending surveys" notification from the background.
            else
            {
                callback.UserNotificationMessage = "Please open to take survey.";
            }

            callback.NotificationUserResponseAction = NotificationUserResponseAction.DisplayPendingSurveys;
#endif

            return(callback);
        }
示例#30
0
        public NSMutableDictionary GetCallbackInfo(ScheduledCallback callback)
        {
            // we've seen cases where the UserInfo dictionary cannot be serialized because one of its values is null. if this happens, the
            // callback won't be serviced, and things won't return to normal until Sensus is activated by the user and the callbacks are
            // refreshed. don't create the UserInfo dictionary if we've got null values.
            if (callback.Id == null)
            {
                SensusException.Report("Failed to get callback information bundle due to null callback ID.");
                return(null);
            }

            return(new NSMutableDictionary(new NSDictionary(SENSUS_CALLBACK_KEY, true,
                                                            iOSNotifier.NOTIFICATION_ID_KEY, callback.Id)));
        }
示例#31
0
 public string ScheduleRepeatingCallback(ScheduledCallback callback, int initialDelayMS, int repeatDelayMS, bool repeatLag)
 {
     lock (_idCallback)
     {
         string callbackId = AddCallback(callback);
         ScheduleRepeatingCallback(callbackId, initialDelayMS, repeatDelayMS, repeatLag);
         return callbackId;
     }
 }
示例#32
0
 private string AddCallback(ScheduledCallback callback)
 {
     lock (_idCallback)
     {
         // treat the callback as if it were brand new, even if it might have been previously used (e.g., if it's being reschedueld). set a
         // new ID and cancellation token.
         callback.Id = Guid.NewGuid().ToString();
         callback.Canceller = new CancellationTokenSource();
         _idCallback.Add(callback.Id, callback);
         return callback.Id;
     }
 }
示例#33
0
        /// <summary>
        /// Starts the commit thread. This should always be called last within child-class overrides.
        /// </summary>
        public virtual void Start()
        {
            if (!_running)
            {
                _running = true;
                SensusServiceHelper.Get().Logger.Log("Starting.", LoggingLevel.Normal, GetType());
                _mostRecentSuccessfulCommitTime = DateTime.Now;
                string userNotificationMessage = null;

                // we can't wake up the app on ios. this is problematic since data need to be stored locally and remotely
                // in something of a reliable schedule; otherwise, we risk data loss (e.g., from device restarts, app kills, etc.).
                // so, do the best possible thing and bug the user with a notification indicating that data need to be stored.
                // only do this for the remote data store to that we don't get duplicate notifications.
                #if __IOS__
                if (this is RemoteDataStore)
                    userNotificationMessage = "Sensus needs to submit your data for the \"" + _protocol.Name + "\" study. Please open this notification.";
                #endif

                ScheduledCallback callback = new ScheduledCallback(CommitAsync, GetType().FullName + " Commit", TimeSpan.FromMinutes(_commitTimeoutMinutes), userNotificationMessage);
                _commitCallbackId = SensusServiceHelper.Get().ScheduleRepeatingCallback(callback, _commitDelayMS, _commitDelayMS, COMMIT_CALLBACK_LAG);
            }
        }
示例#34
0
        private void StartRandomTriggerCallbacks()
        {
            lock (_locker)
            {
                if (_randomTriggerWindows.Count == 0)
                    return;

                StopRandomTriggerCallbacks();

                SensusServiceHelper.Get().Logger.Log("Starting random script trigger callbacks.", LoggingLevel.Normal, GetType());

                #if __IOS__
                string userNotificationMessage = "Please open to provide input.";
                #elif __ANDROID__
                string userNotificationMessage = null;
                #elif WINDOWS_PHONE
                string userNotificationMessage = null; // TODO:  Should we use a message?
                #else
                #error "Unrecognized platform."
                #endif

                // find next future trigger window, ignoring month, day, and year of windows. the windows are already sorted.
                DateTime triggerWindowStart = default(DateTime);
                DateTime triggerWindowEnd = default(DateTime);
                DateTime now = DateTime.Now;
                bool foundTriggerWindow = false;
                foreach (Tuple<DateTime, DateTime> randomTriggerWindow in _randomTriggerWindows)
                {
                    if (randomTriggerWindow.Item1.Hour > now.Hour || (randomTriggerWindow.Item1.Hour == now.Hour && randomTriggerWindow.Item1.Minute > now.Minute))
                    {
                        triggerWindowStart = new DateTime(now.Year, now.Month, now.Day, randomTriggerWindow.Item1.Hour, randomTriggerWindow.Item1.Minute, 0);
                        triggerWindowEnd = new DateTime(now.Year, now.Month, now.Day, randomTriggerWindow.Item2.Hour, randomTriggerWindow.Item2.Minute, 0);
                        foundTriggerWindow = true;
                        break;
                    }
                }

                // if there were no future trigger windows, skip to the next day and use the first trigger window
                if (!foundTriggerWindow)
                {
                    Tuple<DateTime, DateTime> firstRandomTriggerWindow = _randomTriggerWindows.First();
                    triggerWindowStart = new DateTime(now.Year, now.Month, now.Day, firstRandomTriggerWindow.Item1.Hour, firstRandomTriggerWindow.Item1.Minute, 0).AddDays(1);
                    triggerWindowEnd = new DateTime(now.Year, now.Month, now.Day, firstRandomTriggerWindow.Item2.Hour, firstRandomTriggerWindow.Item2.Minute, 0).AddDays(1);
                }

                // schedule callback for random offset into trigger window
                DateTime triggerTime = triggerWindowStart.AddSeconds(_random.NextDouble() * (triggerWindowEnd - triggerWindowStart).TotalSeconds);
                int triggerDelayMS = (int)(triggerTime - now).TotalMilliseconds;

                ScheduledCallback callback = new ScheduledCallback((callbackId, cancellationToken, letDeviceSleepCallback) =>
                    {
                        return Task.Run(() =>
                            {
                                // if the probe is still running and the runner is enabled, run a copy of the script so that we can retain a pristine version of the original.
                                // also, when the script prompts display let the caller know that it's okay for the device to sleep.
                                if (_probe.Running && _enabled)
                                {
                                    Run(_script.Copy(), postDisplayCallback: letDeviceSleepCallback);

                                    // establish the next random trigger callback
                                    StartRandomTriggerCallbacks();
                                }
                            });

                    }, "Trigger Randomly", null, userNotificationMessage);

                _randomTriggerCallbackId = SensusServiceHelper.Get().ScheduleOneTimeCallback(callback, triggerDelayMS);
            }
        }
示例#35
0
        private void StartRerunCallbacksAsync()
        {
            new Thread(() =>
                {
                    lock (_invalidScripts)
                    {
                        StopRerunCallbacks();

                        SensusServiceHelper.Get().Logger.Log("Starting rerun callbacks.", LoggingLevel.Normal, GetType());

                        ScheduledCallback callback = new ScheduledCallback((callbackId, cancellationToken, letDeviceSleepCallback) =>
                            {
                                return Task.Run(() =>
                                    {
                                        if (_probe.Running && _enabled && _rerunInvalidScripts)
                                        {
                                            Script scriptToRerun = null;
                                            lock (_invalidScripts)
                                                while (scriptToRerun == null && _invalidScripts.Count > 0)
                                                {
                                                    scriptToRerun = _invalidScripts.Dequeue();
                                                    if (scriptToRerun.Age.TotalMinutes > _maximumAgeMinutes)
                                                    {
                                                        SensusServiceHelper.Get().Logger.Log("Script \"" + _name + "\" has aged out.", LoggingLevel.Normal, GetType());
                                                        scriptToRerun = null;
                                                        ++_numScriptsAgedOut;
                                                    }
                                                }

                                            // we don't need to copy the script, since we're already working with a copy of the original. also, when the script prompts
                                            // are displayed let the caller know that it's okay to let the device sleep.
                                            if (scriptToRerun != null)
                                                Run(scriptToRerun, postDisplayCallback: letDeviceSleepCallback);
                                        }
                                    });

                            }, "Rerun Script", null); // no user notification message, since there might not be any scripts to rerun

                        _rerunCallbackId = SensusServiceHelper.Get().ScheduleRepeatingCallback(callback, _rerunDelayMS, _rerunDelayMS, RERUN_CALLBACK_LAG);
                    }

                }).Start();
        }
示例#36
0
        public void AddRunningProtocolId(string id)
        {
            lock (_runningProtocolIds)
            {
                if (!_runningProtocolIds.Contains(id))
                    _runningProtocolIds.Add(id);

                if (_healthTestCallbackId == null)
                {
                    ScheduledCallback callback = new ScheduledCallback(TestHealthAsync, "Test Health", TimeSpan.FromMinutes(1)); 
                    _healthTestCallbackId = ScheduleRepeatingCallback(callback, HEALTH_TEST_DELAY_MS, HEALTH_TEST_DELAY_MS, HEALTH_TEST_REPEAT_LAG);
                }
            }
        }
示例#37
0
 public string ScheduleOneTimeCallback(ScheduledCallback callback, int delayMS)
 {
     lock (_idCallback)
     {
         string callbackId = AddCallback(callback);
         ScheduleOneTimeCallback(callbackId, delayMS);
         return callbackId;
     }
 }
示例#38
0
        protected override void InternalStart()
        {
            lock (_locker)
            {
                base.InternalStart();

                #if __IOS__
                string userNotificationMessage = DisplayName + " data requested.";
                #elif __ANDROID__
                string userNotificationMessage = null;
                #elif WINDOWS_PHONE
                string userNotificationMessage = null; // TODO:  Should we use a message?
                #else
                #error "Unrecognized platform."
                #endif

                ScheduledCallback callback = new ScheduledCallback((callbackId, cancellationToken, letDeviceSleepCallback) =>
                    {
                        return Task.Run(() =>
                            {
                                if (Running)
                                {
                                    _isPolling = true;

                                    IEnumerable<Datum> data = null;
                                    try
                                    {
                                        SensusServiceHelper.Get().Logger.Log("Polling.", LoggingLevel.Normal, GetType());
                                        data = Poll(cancellationToken);

                                        lock (_pollTimes)
                                        {
                                            _pollTimes.Add(DateTime.Now);
                                            _pollTimes.RemoveAll(pollTime => pollTime < Protocol.ParticipationHorizon);
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        SensusServiceHelper.Get().Logger.Log("Failed to poll:  " + ex.Message, LoggingLevel.Normal, GetType());
                                    }

                                    if (data != null)
                                        foreach (Datum datum in data)
                                        {
                                            if (cancellationToken.IsCancellationRequested)
                                                break;

                                            try
                                            {
                                                StoreDatum(datum);
                                            }
                                            catch (Exception ex)
                                            {
                                                SensusServiceHelper.Get().Logger.Log("Failed to store datum:  " + ex.Message, LoggingLevel.Normal, GetType());
                                            }
                                        }

                                    _isPolling = false;
                                }
                            });

                    }, GetType().FullName + " Poll", TimeSpan.FromMinutes(_pollingTimeoutMinutes), userNotificationMessage);

                _pollCallbackId = SensusServiceHelper.Get().ScheduleRepeatingCallback(callback, 0, _pollingSleepDurationMS, POLL_CALLBACK_LAG);
            }
        }