public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) { AndroidSensusServiceHelper serviceHelper = SensusServiceHelper.Get() as AndroidSensusServiceHelper; // there might be a race condition between the calling of this method and the stopping/disposal of the service helper. // if the service helper is stopped/disposed before the service is stopped but after this method is called, the service // helper will be null. if (serviceHelper != null) { serviceHelper.Logger.Log("Sensus service received start command (startId=" + startId + ", flags=" + flags + ").", LoggingLevel.Normal, GetType()); // acquire wake lock before this method returns to ensure that the device does not sleep prematurely, interrupting the execution of a callback. serviceHelper.KeepDeviceAwake(); // the service can be stopped without destroying the service object. in such cases, // subsequent calls to start the service will not call OnCreate. therefore, it's // important that any code called here is okay to call multiple times, even if the // service is running. calling this when the service is running can happen because // sensus receives a signal on device boot and for any callback alarms that are // requested. furthermore, all calls here should be nonblocking / async so we don't // tie up the UI thread. serviceHelper.StartAsync(() => { if (intent == null) { serviceHelper.LetDeviceSleep(); } else { DisplayPage displayPage; // is this a callback intent? if (intent.GetBooleanExtra(CallbackScheduler.SENSUS_CALLBACK_KEY, false)) { (SensusContext.Current.CallbackScheduler as AndroidCallbackScheduler).ServiceCallbackAsync(intent); } // should we display a page? else if (Enum.TryParse(intent.GetStringExtra(Notifier.DISPLAY_PAGE_KEY), out displayPage)) { serviceHelper.BringToForeground(); SensusContext.Current.Notifier.OpenDisplayPage(displayPage); serviceHelper.LetDeviceSleep(); } else { serviceHelper.LetDeviceSleep(); } } }); } return(StartCommandResult.Sticky); }
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) { AndroidSensusServiceHelper serviceHelper = SensusServiceHelper.Get() as AndroidSensusServiceHelper; // there might be a race condition between the calling of this method and the stopping/disposal of the service helper. // if the service helper is stopped/disposed before the service is stopped but after this method is called (e.g., by // an alarm callback), the service helper will be null. if (serviceHelper != null) { serviceHelper.Logger.Log("Sensus service received start command (startId=" + startId + ", flags=" + flags + ").", LoggingLevel.Normal, GetType()); // promote this service to a foreground, for several reasons: it's honest and transparent. it lets us work effectively with the // android 8.0 restrictions on background services (we can run forever without being killed, we receive background location // updates). it's okay to call this multiple times. doing so will simply update the notification. if (_foregroundServiceNotificationBuilder == null) { PendingIntent mainActivityPendingIntent = PendingIntent.GetActivity(this, 0, new Intent(this, typeof(AndroidMainActivity)), 0); _foregroundServiceNotificationBuilder = (SensusContext.Current.Notifier as AndroidNotifier).CreateNotificationBuilder(this, AndroidNotifier.SensusNotificationChannel.ForegroundService) .SetSmallIcon(Resource.Drawable.ic_launcher) .SetContentIntent(mainActivityPendingIntent) .SetOngoing(true); UpdateForegroundServiceNotificationBuilder(); StartForeground(FOREGROUND_SERVICE_NOTIFICATION_ID, _foregroundServiceNotificationBuilder.Build()); } else { ReissueForegroundServiceNotification(); } // acquire wake lock before this method returns to ensure that the device does not sleep prematurely, interrupting the execution of a callback. serviceHelper.KeepDeviceAwake(); Task.Run(async() => { // the service can be stopped without destroying the service object. in such cases, // subsequent calls to start the service will not call OnCreate. therefore, it's // important that any code called here is okay to call multiple times, even if the // service is running. calling this when the service is running can happen because // sensus receives a signal on device boot and for any callback alarms that are // requested. furthermore, all calls here should be nonblocking / async so we don't // tie up the UI thread. await serviceHelper.StartAsync(); if (intent == null) { serviceHelper.LetDeviceSleep(); } else { DisplayPage displayPage; // is this a callback intent? if (intent.GetBooleanExtra(CallbackScheduler.SENSUS_CALLBACK_KEY, false)) { // service the callback -- the matching LetDeviceSleep will be called therein await(SensusContext.Current.CallbackScheduler as AndroidCallbackScheduler).ServiceCallbackAsync(intent); } // should we display a page? else if (Enum.TryParse(intent.GetStringExtra(Notifier.DISPLAY_PAGE_KEY), out displayPage)) { serviceHelper.BringToForeground(); SensusContext.Current.Notifier.OpenDisplayPage(displayPage); serviceHelper.LetDeviceSleep(); } else { serviceHelper.LetDeviceSleep(); } } }); } // if the service is killed by the system (e.g., due to resource constraints), ask the system to restart // the service when possible. return(StartCommandResult.Sticky); }
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) { AndroidSensusServiceHelper serviceHelper = SensusServiceHelper.Get() as AndroidSensusServiceHelper; // there might be a race condition between the calling of this method and the stopping/disposal of the service helper. // if the service helper is stopped/disposed before the service is stopped but after this method is called, the service // helper will be null. if (serviceHelper != null) { serviceHelper.Logger.Log("Sensus service received start command (startId=" + startId + ", flags=" + flags + ").", LoggingLevel.Normal, GetType()); // acquire wake lock before this method returns to ensure that the device does not sleep prematurely, interrupting the execution of a callback. serviceHelper.KeepDeviceAwake(); // the service can be stopped without destroying the service object. in such cases, // subsequent calls to start the service will not call OnCreate. therefore, it's // important that any code called here is okay to call multiple times, even if the // service is running. calling this when the service is running can happen because // sensus receives a signal on device boot and for any callback alarms that are // requested. furthermore, all calls here should be nonblocking / async so we don't // tie up the UI thread. serviceHelper.StartAsync(() => { if (intent != null) { // is this a callback intent? if (intent.GetBooleanExtra(SensusServiceHelper.SENSUS_CALLBACK_KEY, false)) { string callbackId = intent.GetStringExtra(SensusServiceHelper.SENSUS_CALLBACK_ID_KEY); // 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 scheduled, it's fine. if it's not, then unschedule it. if (serviceHelper.CallbackIsScheduled(callbackId)) { bool repeating = intent.GetBooleanExtra(SensusServiceHelper.SENSUS_CALLBACK_REPEATING_KEY, false); int repeatDelayMS = intent.GetIntExtra(SensusServiceHelper.SENSUS_CALLBACK_REPEAT_DELAY_KEY, -1); bool repeatLag = intent.GetBooleanExtra(SensusServiceHelper.SENSUS_CALLBACK_REPEAT_LAG_KEY, false); 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. serviceHelper.RaiseCallbackAsync(callbackId, repeating, repeatDelayMS, repeatLag, true, // schedule a new callback at the given time. repeatCallbackTime => { serviceHelper.ScheduleCallbackAlarm(serviceHelper.CreateCallbackPendingIntent(intent), callbackId, repeatCallbackTime); }, // 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 { serviceHelper.UnscheduleCallback(callbackId); serviceHelper.LetDeviceSleep(); } } else if (intent.GetStringExtra(SensusServiceHelper.NOTIFICATION_ID_KEY) == SensusServiceHelper.PENDING_SURVEY_NOTIFICATION_ID) { serviceHelper.BringToForeground(); // display the pending scripts page if it is not already on the top of the navigation stack SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(async() => { IReadOnlyList <Page> navigationStack = Xamarin.Forms.Application.Current.MainPage.Navigation.NavigationStack; Page topPage = navigationStack.Count == 0 ? null : navigationStack.Last(); if (!(topPage is PendingScriptsPage)) { await Xamarin.Forms.Application.Current.MainPage.Navigation.PushAsync(new PendingScriptsPage()); } serviceHelper.LetDeviceSleep(); }); } else { serviceHelper.LetDeviceSleep(); } } else { serviceHelper.LetDeviceSleep(); } }); } return(StartCommandResult.Sticky); }