public override void OnWindowFocusChanged(bool hasFocus) { base.OnWindowFocusChanged(hasFocus); // the service helper is responsible for running actions that depend on the main activity. if the main activity // is not showing, the service helper starts the main activity and then runs requested actions. there is a race // condition between actions that wish to show a dialog (e.g., starting speech recognition) and the display of // the activity. in order to ensure that the activity is showing before any actions are run, we override this // focus changed event and let the service helper know when the activity is focused and when it is not. this // way, any actions that the service helper runs will certainly be run after the main activity is running // and focused. AndroidSensusServiceHelper serviceHelper = SensusServiceHelper.Get() as AndroidSensusServiceHelper; if (serviceHelper != null) { if (hasFocus) { serviceHelper.SetFocusedMainActivity(this); } else { serviceHelper.SetFocusedMainActivity(null); } } }
public override void OnCreate() { base.OnCreate(); SensusContext.Current = new AndroidSensusContext { Platform = Platform.Android, MainThreadSynchronizer = new MainConcurrent(), SymmetricEncryption = new SymmetricEncryption(SensusServiceHelper.ENCRYPTION_KEY), CallbackScheduler = new AndroidCallbackScheduler(this), Notifier = new AndroidNotifier(this) }; SensusServiceHelper.Initialize(() => new AndroidSensusServiceHelper()); AndroidSensusServiceHelper serviceHelper = SensusServiceHelper.Get() as AndroidSensusServiceHelper; // we might have failed to create the service helper. it's also happened that the service is created after the // service helper is disposed: https://insights.xamarin.com/app/Sensus-Production/issues/46 if (serviceHelper == null) { Stop(); } else { serviceHelper.SetService(this); } }
public override void OnCreate() { base.OnCreate(); // insights should be initialized first to maximize coverage of exception reporting InsightsInitialization.Initialize(new AndroidInsightsInitializer(Settings.Secure.GetString(ContentResolver, Settings.Secure.AndroidId)), SensusServiceHelper.XAMARIN_INSIGHTS_APP_KEY); SensusContext.Current = new AndroidSensusContext { Platform = Platform.Android, MainThreadSynchronizer = new MainConcurrent(), SymmetricEncryption = new SymmetricEncryption(SensusServiceHelper.ENCRYPTION_KEY), CallbackScheduler = new AndroidCallbackScheduler(this), Notifier = new AndroidNotifier(this) }; SensusServiceHelper.Initialize(() => new AndroidSensusServiceHelper()); AndroidSensusServiceHelper serviceHelper = SensusServiceHelper.Get() as AndroidSensusServiceHelper; // we might have failed to create the service helper. it's also happened that the service is created after the // service helper is disposed: https://insights.xamarin.com/app/Sensus-Production/issues/46 if (serviceHelper == null) { Stop(); } else { serviceHelper.SetService(this); } }
public override void OnCreate() { base.OnCreate(); _serviceHelper = SensusServiceHelper.Get() as AndroidSensusServiceHelper; _serviceHelper.SetService(this); _serviceHelper.UpdateApplicationStatus("0 protocols are running"); }
public override void OnDestroy() { base.OnDestroy(); if (_serviceHelper != null) { _serviceHelper.Logger.Log("Destroying service.", LoggingLevel.Normal, GetType()); _serviceHelper.Dispose(); _serviceHelper = null; } }
public override void OnCreate() { base.OnCreate(); // initialize the current activity plugin here as well as in the main activity // since this service may be created by iteself without a main activity (e.g., // on boot or on OS restart of the service). we want the plugin to have be // initialized regardless of how the app comes to be created. CrossCurrentActivity.Current.Init(Application); SensusContext.Current = new AndroidSensusContext { Platform = Platform.Android, MainThreadSynchronizer = new MainConcurrent(), SymmetricEncryption = new SymmetricEncryption(SensusServiceHelper.ENCRYPTION_KEY), CallbackScheduler = new AndroidCallbackScheduler(this), Notifier = new AndroidNotifier(), PowerConnectionChangeListener = new AndroidPowerConnectionChangeListener() }; // promote this service to a foreground service as soon as possible. we use a foreground service 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, etc. (SensusContext.Current.Notifier as AndroidNotifier).UpdateForegroundServiceNotificationBuilder(); StartForeground(AndroidNotifier.FOREGROUND_SERVICE_NOTIFICATION_ID, (SensusContext.Current.Notifier as AndroidNotifier).BuildForegroundServiceNotification()); // https://developer.android.com/reference/android/content/Intent#ACTION_POWER_CONNECTED // This is intended for applications that wish to register specifically to this notification. Unlike ACTION_BATTERY_CHANGED, // applications will be woken for this and so do not have to stay active to receive this notification. This action can be // used to implement actions that wait until power is available to trigger. // // We use the same receiver for both the connected and disconnected intents. _powerBroadcastReceiver = new AndroidPowerConnectionChangeBroadcastReceiver(); IntentFilter powerConnectFilter = new IntentFilter(); powerConnectFilter.AddAction(Intent.ActionPowerConnected); powerConnectFilter.AddAction(Intent.ActionPowerDisconnected); powerConnectFilter.AddCategory(Intent.CategoryDefault); RegisterReceiver(_powerBroadcastReceiver, powerConnectFilter); // must come after context initialization. also, it is here -- below StartForeground -- because it can // take a while to complete and we don't want to run afoul of the short timing requirements on calling // StartForeground. SensusServiceHelper.Initialize(() => new AndroidSensusServiceHelper()); AndroidSensusServiceHelper serviceHelper = SensusServiceHelper.Get() as AndroidSensusServiceHelper; // we might have failed to create the service helper. it's also happened that the service is created // after the service helper is disposed. if (serviceHelper == null) { Stop(); } }
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 void OnCreate() { base.OnCreate(); SensusServiceHelper.Initialize(() => new AndroidSensusServiceHelper()); _serviceHelper = SensusServiceHelper.Get() as AndroidSensusServiceHelper; // it's happened that the service is created after the service helper is disposed: https://insights.xamarin.com/app/Sensus-Production/issues/46 if (_serviceHelper == null) { StopSelf(); return; } _serviceHelper.SetService(this); _serviceHelper.UpdateApplicationStatus("0 protocols are running"); }
public override void OnCreate() { base.OnCreate(); InsightsInitialization.Initialize(new AndroidInsightsInitializer(Settings.Secure.GetString(ContentResolver, Settings.Secure.AndroidId)), SensusServiceHelper.XAMARIN_INSIGHTS_APP_KEY); SensusContext.Current = new AndroidSensusContext(SensusServiceHelper.ENCRYPTION_KEY); SensusServiceHelper.Initialize(() => new AndroidSensusServiceHelper()); AndroidSensusServiceHelper serviceHelper = SensusServiceHelper.Get() as AndroidSensusServiceHelper; // we might have failed to create the service helper. it's also happened that the service is created after the // service helper is disposed: https://insights.xamarin.com/app/Sensus-Production/issues/46 if (serviceHelper == null) { Stop(); } else { serviceHelper.SetService(this); } }
public override void OnDestroy() { Console.Error.WriteLine("--------------------------- Destroying Service ---------------------------"); base.OnDestroy(); AndroidSensusServiceHelper serviceHelper = SensusServiceHelper.Get() as AndroidSensusServiceHelper; // the service helper will be null if we failed to create it within OnCreate, so first check that. also, // OnDestroy can be called either when the user stops Sensus (in Android) and when the system reclaims // the service under memory pressure. in the former case, we'll already have done the notification and // stopping of protocols; however, we have no way to know how we reached OnDestroy, so to cover the latter // case we're going to do the notification and stopping again. this will be duplicative in the case where // the user has stopped sensus. in sum, anything we do below must be safe to run repeatedly. if (serviceHelper != null) { serviceHelper.Logger.Log("Destroying service.", LoggingLevel.Normal, GetType()); NotifyBindingsOfStop(); serviceHelper.StopProtocols(); serviceHelper.SetService(null); } }
private void UpdateForegroundServiceNotificationBuilder() { AndroidSensusServiceHelper serviceHelper = SensusServiceHelper.Get() as AndroidSensusServiceHelper; int numRunningStudies = serviceHelper.RunningProtocolIds.Count; _foregroundServiceNotificationBuilder.SetContentTitle("You are enrolled in " + numRunningStudies + " " + (numRunningStudies == 1 ? "study" : "studies") + "."); string contentText = ""; // although the number of studies might be greater than 0, the protocols might not yet be started (e.g., after starting sensus). List <Protocol> runningProtocols = serviceHelper.GetRunningProtocols(); if (runningProtocols.Count > 0) { double avgParticipation = runningProtocols.Average(protocol => protocol.Participation) * 100; contentText += "Your overall participation level is " + Math.Round(avgParticipation, 0) + "%. "; } contentText += "Tap to open Sensus."; _foregroundServiceNotificationBuilder.SetContentText(contentText); }
public override void OnCreate() { base.OnCreate(); _notificationManager = GetSystemService(Context.NotificationService) as NotificationManager; UpdateNotification("Sensus", ""); _sensusServiceHelper = SensusServiceHelper.Load <AndroidSensusServiceHelper>(new Geolocator(this)) as AndroidSensusServiceHelper; if (_sensusServiceHelper == null) { _sensusServiceHelper = new AndroidSensusServiceHelper(); _sensusServiceHelper.Initialize(new Geolocator(this)); _sensusServiceHelper.Save(); } _sensusServiceHelper.SetService(this); _sensusServiceHelper.Stopped += (o, e) => { _notificationManager.Cancel(SERVICE_NOTIFICATION_ID); StopSelf(); }; }
public AndroidSensusServiceBinder(AndroidSensusServiceHelper sensusServiceHelper) { _sensusServiceHelper = sensusServiceHelper; }
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); }
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()); // update the foreground service notification with information about loaded/running studies. (SensusContext.Current.Notifier as AndroidNotifier).ReissueForegroundServiceNotification(); // if the service started but there are no protocols that should be running, then stop the app now. there is no // reason for the app to be running in this situation, and the user will likely be annoyed at the presence of the // foreground service notification. if (intent != null && intent.GetBooleanExtra(KEY_STOP_SERVICE_IF_NO_PROTOCOLS_SHOULD_RUN, false) && serviceHelper.RunningProtocolIds.Count == 0) { serviceHelper.Logger.Log("Started service without running protocols. Stopping service now.", LoggingLevel.Normal, GetType()); Stop(); return(StartCommandResult.NotSticky); } // acquire wake lock before this method returns to ensure that the device does not sleep prematurely, interrupting the execution of a callback. serviceHelper.KeepDeviceAwakeAsync().Wait(); Task.Run(async() => { try { // 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) { AndroidCallbackScheduler callbackScheduler = SensusContext.Current.CallbackScheduler as AndroidCallbackScheduler; if (callbackScheduler.IsCallback(intent)) { await callbackScheduler.RaiseCallbackAsync(intent); } } } catch (Exception ex) { serviceHelper.Logger.Log("Exception while responding to on-start command: " + ex.Message, LoggingLevel.Normal, GetType()); } finally { serviceHelper.LetDeviceSleepAsync().Wait(); } }); } // 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 (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); }