public async Task DeletePushNotificationRequestAsync(Guid backendKey, Protocol protocol, CancellationToken cancellationToken) { if (protocol == null) { SensusException.Report("Received null PNR protocol."); return; } try { await protocol.RemoteDataStore.DeletePushNotificationRequestAsync(backendKey, cancellationToken); RemovePushNotificationRequestToDelete(backendKey); } catch (Exception deleteException) { SensusServiceHelper.Get().Logger.Log("Exception while deleting push notification request: " + deleteException.Message, LoggingLevel.Normal, GetType()); // hang on to the push notification for deleting in the future, e.g., when internet is restored. AddPushNotificationRequestToDelete(backendKey, protocol.Id); } finally { // we just attempted to delete the push notification request, so it does not make sense for the // request to be pending sending. remove any pending push notification sendings. RemovePushNotificationRequestToSend(backendKey); } }
public void IssueNotificationAsync(UNNotificationRequest request, Action <NSError> errorCallback = null) { // don't issue silent notifications from the background, as they will be delivered and will confuse the user (they're // not designed to be seen). bool abort = false; SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() => { abort = IsSilent(request?.Content?.UserInfo) && UIApplication.SharedApplication.ApplicationState != UIApplicationState.Active; }); if (abort) { SensusServiceHelper.Get().Logger.Log("Aborting notification: Will not issue silent notification from background.", LoggingLevel.Normal, GetType()); return; } UNUserNotificationCenter.Current.AddNotificationRequest(request, error => { if (error == null) { SensusServiceHelper.Get().Logger.Log("Notification " + request.Identifier + " requested for " + ((request.Trigger as UNCalendarNotificationTrigger)?.NextTriggerDate.ToString() ?? "[time not specified]") + ".", LoggingLevel.Normal, GetType()); } else { SensusServiceHelper.Get().Logger.Log("Failed to add notification request: " + error.Description, LoggingLevel.Normal, GetType()); SensusException.Report("Failed to add notification request: " + error.Description); } errorCallback?.Invoke(error); }); }
public override async void OnReceive(global::Android.Content.Context context, Intent intent) { // this method is usually called on the UI thread and can crash the app if it throws an exception try { if (intent == null) { throw new ArgumentNullException(nameof(intent)); } SensusServiceHelper serviceHelper = SensusServiceHelper.Get(); // service helper will be null for a time when the app is starting up if (serviceHelper != null) { if (intent.Action == AndroidNotifier.FOREGROUND_SERVICE_NOTIFICATION_ACTION_PAUSE) { IEnumerable <Protocol> pausableProtocols = serviceHelper.RegisteredProtocols.Where(protocol => protocol.AllowPause); await(Application.Current as App).DetailPage.Navigation.PushAsync(new SnoozeProtocolsPage(pausableProtocols)); } else if (intent.Action == AndroidNotifier.FOREGROUND_SERVICE_NOTIFICATION_ACTION_RESUME) { foreach (Protocol protocol in serviceHelper.RegisteredProtocols) { await protocol.ResumeAsync(); } } } } catch (Exception ex) { SensusException.Report("Exception while handling notification action: " + ex.Message, ex); } }
private string CloseFile() { string path = _currentPath; lock (_fileLocker) { if (_currentFile != null) { try { // close the JSON array and close the file byte[] jsonCloseArrayBytes = Encoding.UTF8.GetBytes(Environment.NewLine + "]"); _currentFile.Write(jsonCloseArrayBytes, 0, jsonCloseArrayBytes.Length); _currentFile.Flush(); _currentFile.Close(); _currentFile.Dispose(); _currentFile = null; _currentPath = null; _totalFilesClosed++; _totalDataWrittenToCurrentFile = 0; } catch (Exception ex) { SensusException.Report("Exception while closing file: " + ex.Message, ex); } } } return(path); }
public override void OnReceive(global::Android.Content.Context context, Intent intent) { // this method is usually called on the UI thread and can crash the app if it throws an exception try { if (intent == null) { throw new ArgumentNullException(nameof(intent)); } if (intent.Action == BluetoothDevice.ActionFound && DEVICE_FOUND != null) { BluetoothDevice device = intent.GetParcelableExtra(BluetoothDevice.ExtraDevice) as BluetoothDevice; DEVICE_FOUND(this, device); } else if (intent.Action == BluetoothAdapter.ActionStateChanged && STATE_CHANGED != null) { int stateInt = intent.GetIntExtra(BluetoothAdapter.ExtraState, -1); if (stateInt != -1) { STATE_CHANGED(this, (State)stateInt); } } } catch (Exception ex) { SensusException.Report("Exception in BLE broadcast receiver: " + ex.Message, ex); } }
public virtual async Task <ScheduledCallbackState> ScheduleCallbackAsync(ScheduledCallback callback) { // the next execution time is computed from the time the current method is called, as the // caller may hang on to the ScheduledCallback for some time before calling the current method. // we set the time here, before adding it to the collection below, so that any callback // in the collection will certainly have a next execution time. callback.NextExecution = DateTime.Now + callback.Delay; if (callback.State != ScheduledCallbackState.Created) { SensusException.Report("Attemped to schedule callback " + callback.Id + ", which is in the " + callback.State + " state and not the " + ScheduledCallbackState.Created + " state."); callback.State = ScheduledCallbackState.Unknown; } else if (_idCallback.TryAdd(callback.Id, callback)) { callback.InvocationId = Guid.NewGuid().ToString(); BatchNextExecutionWithToleratedDelay(callback); // the state needs to be updated after batching is performed, so that other callbacks don't // attempt to batch with it, but before the invocations are requested, so that if the invocation // comes back immediately (e.g., being scheduled in the past) the callback is scheduled and // ready to run. callback.State = ScheduledCallbackState.Scheduled; await RequestLocalInvocationAsync(callback); } else { SensusServiceHelper.Get().Logger.Log("Attempted to schedule duplicate callback for " + callback.Id + ".", LoggingLevel.Normal, GetType()); } return(callback.State); }
public override void OnReceive(global::Android.Content.Context context, Intent intent) { try { if (intent == null) { throw new ArgumentNullException(nameof(intent)); } if (intent.Action == BluetoothDevice.ActionFound) { AndroidBluetoothDevice device = new AndroidBluetoothDevice { Device = intent.GetParcelableExtra(BluetoothDevice.ExtraDevice) as BluetoothDevice, Rssi = intent.GetShortExtra(BluetoothDevice.ExtraRssi, short.MinValue), Timestamp = DateTimeOffset.UtcNow }; lock (_devices) { _devices.Add(device); } } } catch (Exception ex) { SensusException.Report("Exception in BLE broadcast receiver: " + ex.Message, ex); } }
protected PollingProbe() { _pollingSleepDurationMS = DefaultPollingSleepDurationMS; _pollingTimeoutMinutes = 5; _isPolling = false; _pollTimes = new List <DateTime>(); #if __IOS__ _significantChangePoll = false; _significantChangePollOverridesScheduledPolls = false; _locationManager = new CLLocationManager(); _locationManager.LocationsUpdated += async(sender, e) => { await Task.Run(async() => { try { CancellationTokenSource canceller = new CancellationTokenSource(); // if the callback specified a timeout, request cancellation at the specified time. if (_pollCallback.CallbackTimeout.HasValue) { canceller.CancelAfter(_pollCallback.CallbackTimeout.Value); } await _pollCallback.Action(_pollCallback.Id, canceller.Token, () => { }); } catch (Exception ex) { SensusException.Report("Failed significant change poll.", ex); } }); }; #endif }
public void OpenDisplayPage(DisplayPage displayPage) { if (displayPage == DisplayPage.None) { return; } SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(async() => { Page desiredTopPage = null; if (displayPage == DisplayPage.PendingSurveys) { desiredTopPage = new PendingScriptsPage(); } else { SensusException.Report("Unrecognized display page: " + displayPage); return; } Page currentTopPage = Application.Current.MainPage.Navigation.NavigationStack.LastOrDefault(); if (currentTopPage == null || desiredTopPage.GetType() != currentTopPage.GetType()) { await Application.Current.MainPage.Navigation.PushAsync(desiredTopPage); } }); }
public DataStore Copy() { JsonSerializerSettings settings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.None, ReferenceLoopHandling = ReferenceLoopHandling.Ignore, TypeNameHandling = TypeNameHandling.All, ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor }; try { SensusServiceHelper.Get().FlashNotificationsEnabled = false; return(JsonConvert.DeserializeObject <DataStore>(JsonConvert.SerializeObject(this, settings), settings)); } catch (Exception ex) { string message = $"Failed to copy data store: {ex.Message}"; SensusServiceHelper.Get().Logger.Log(message, LoggingLevel.Normal, GetType()); SensusException.Report(message, ex); return(null); } finally { SensusServiceHelper.Get().FlashNotificationsEnabled = true; } }
public override Task KeepDeviceAwakeAsync() { if (_wakeLock != null) { lock (_wakeLock) { bool firstAcquisition = !_wakeLock.IsHeld; _wakeLock.Acquire(); Logger.Log("Wake lock acquired" + (firstAcquisition ? " for the first time" : "") + ".", LoggingLevel.Normal, GetType()); // if this is the first successful acquisition, then mark the time. if (firstAcquisition && _wakeLock.IsHeld) { if (_wakeLockTimestamp != null) { SensusException.Report("Acquired wake lock for the first time but with an existing timestamp."); } _wakeLockTimestamp = DateTime.Now; } } } return(Task.CompletedTask); }
private void WriteToNewPath() { lock (_storageDirectoryLocker) { _path = null; int pathNumber = 0; while (pathNumber++ < int.MaxValue && _path == null) { try { _path = Path.Combine(StorageDirectory, pathNumber.ToString()); } catch (Exception ex) { throw SensusException.Report("Failed to get path to local file: " + ex.Message, ex); } // create an empty file at the path if one does not exist if (File.Exists(_path)) { _path = null; } else { File.Create(_path).Dispose(); } } if (_path == null) { throw SensusException.Report("Failed to find new path."); } } }
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()); }
public override bool ValueMatches(object conditionValue, bool conjunctive) { // if a list is passed, compare values if (conditionValue is List <object> ) { List <object> selectedValueList = Value as List <object>; List <object> conditionValueList = conditionValue as List <object>; // if the matching condition is conjunctive, then the two lists must be identical. if (conjunctive) { return(selectedValueList.OrderBy(o => o).SequenceEqual(conditionValueList.OrderBy(o => o))); } // if the matching condiction is disjunctive, then any of the condition values may be selected. else { return(conditionValueList.Any(o => selectedValueList.Contains(o))); } } else { SensusException.Report("Called ItemPickerPageInput.ValueMatches with conditionValue that is not a List<object>."); return(false); } }
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 to Insights 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); IiOSCallbackScheduler callbackScheduler = SensusContext.Current.CallbackScheduler as IiOSCallbackScheduler; if (callbackScheduler == null) { SensusException.Report("Invalid callback scheduler."); } else { // service the callback. 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 the user opened the notification, // display the page associated with the notification (if there is one). if (application.ApplicationState != UIApplicationState.Active) { callbackScheduler.OpenDisplayPage(notification.UserInfo); } } } }
public void GetActivityResultAsync(Intent intent, AndroidActivityResultRequestCode requestCode, Action <Tuple <Result, Intent> > callback) { Task.Run(() => { lock (_locker) { _activityResultRequestCode = requestCode; _activityResult = null; _activityResultWait.Reset(); try { StartActivityForResult(intent, (int)requestCode); } catch (Exception ex) { SensusException.Report(ex); _activityResultWait.Set(); } _activityResultWait.WaitOne(); callback(_activityResult); } }); }
public override void OnActivated(UIApplication uiApplication) { System.Threading.Tasks.Task.Run(async() => { try { // ensure service helper is running await SensusServiceHelper.Get().StartAsync(); // update/run all callbacks await(SensusContext.Current.CallbackScheduler as IiOSCallbackScheduler).UpdateCallbacksAsync(); #if UI_TESTING // load and run the UI testing protocol string filePath = NSBundle.MainBundle.PathForResource("UiTestingProtocol", "json"); using (Stream file = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { await Protocol.RunUiTestingProtocolAsync(file); } #endif } catch (Exception ex) { SensusException.Report("Failed in OnActivated.", ex); } }); base.OnActivated(uiApplication); }
public AndroidPowerConnectionChangeListener() { AndroidPowerConnectionChangeBroadcastReceiver.POWER_CONNECTION_CHANGED += async(sender, connected) => { try { bool listenOnACPower = SensusServiceHelper.Get().GetRunningProtocols().SelectMany(x => x.Probes.OfType <ListeningProbe>()).Any(x => x.KeepDeviceAwakeOnAcPower); if (connected && listenOnACPower) { await SensusServiceHelper.Get().KeepDeviceAwakeAsync(); } else { await SensusServiceHelper.Get().LetDeviceSleepAsync(); } PowerConnectionChanged?.Invoke(sender, connected); } catch (Exception ex) { SensusException.Report("Failed to process power connection change.", ex); } }; }
private async Task StartNewFileAsync(CancellationToken cancellationToken) { // start a new file within a lock to ensure that anyone with the lock will have a valid file string unpreparedPath = null; lock (_fileLocker) { try { unpreparedPath = CloseFile(); } catch (Exception closeException) { SensusException.Report("Exception while closing file: " + closeException.Message, closeException); } if (Running) { try { OpenFile(); } catch (Exception openException) { SensusException.Report("Exception while opening file: " + openException.Message, openException); } } } await PreparePathForRemoteAsync(unpreparedPath, cancellationToken); }
// This method should be used to release shared resources and it should store the application state. // If your application supports background exection this method is called instead of WillTerminate // when the user quits. public async override void DidEnterBackground(UIApplication uiApplication) { nint enterBackgroundTaskId = uiApplication.BeginBackgroundTask(() => { // not much to do if we run out of time. just report it. string message = "Ran out of background time while entering background."; SensusServiceHelper.Get().Logger.Log(message, LoggingLevel.Normal, GetType()); SensusException.Report(message); }); iOSSensusServiceHelper serviceHelper = SensusServiceHelper.Get() as iOSSensusServiceHelper; // if the callback scheduler is timer-based and gps is not running then we need to request remote notifications if (SensusContext.Current.CallbackScheduler is iOSTimerCallbackScheduler scheduler) { bool gpsIsRunning = SensusServiceHelper.Get().GetRunningProtocols().SelectMany(x => x.Probes).OfType <ListeningLocationProbe>().Any(x => x.Enabled); await scheduler.RequestNotificationsAsync(gpsIsRunning); } // save app state await serviceHelper.SaveAsync(); uiApplication.EndBackgroundTask(enterBackgroundTaskId); }
public async override void OnMessageReceived(RemoteMessage message) { AndroidSensusServiceHelper serviceHelper = null; try { // based on log messages, it looks like the os might destroy the service component of the application // but leave the rest of the application (e.g., the service helper) intact and resident in memory. // if this happens then the serivce helper will be present, but the service itself will be destroyed. // this may also mean that the protocols are stopped. regardless, we desire for the service to always // be running, as this ensures that the app will continue as a foreground service. so, ask the os to // start the service any time a push notification is received. this should be a no-op if the service // is already running. don't ask for the service to be stopped in case no protocols are running, as // it could just be the case that a push notification arrives late after the user has stopped protocols. AndroidSensusService.Start(false); serviceHelper = SensusServiceHelper.Get() as AndroidSensusServiceHelper; // if we just started the service above, then it's likely that the service helper will not yet be // initialized (it must be deserialized, which is slow). in this case, just bail out and wait for // the next push notification to arrive, at which time the service helper will hopefully be ready. if (serviceHelper == null) { SensusServiceHelper.Get().Logger.Log("Service helper not initialized following receipt of push notification and service start.", LoggingLevel.Normal, GetType()); return; } // acquire wake lock before this method returns to ensure that the device does not sleep prematurely, // interrupting any execution requested by the push notification. the service serviceHelper.KeepDeviceAwakeAsync().Wait(); PushNotification pushNotification = new PushNotification { Id = message.Data["id"], ProtocolId = message.Data["protocol"], Update = bool.Parse(message.Data["update"]), Title = message.Data["title"], Body = message.Data["body"], Sound = message.Data["sound"] }; // backend key might be blank string backendKeyString = message.Data["backend-key"]; if (!string.IsNullOrWhiteSpace(backendKeyString)) { pushNotification.BackendKey = new Guid(backendKeyString); } await SensusContext.Current.Notifier.ProcessReceivedPushNotificationAsync(pushNotification, CancellationToken.None); } catch (Exception ex) { SensusException.Report("Exception while processing remote notification: " + ex.Message, ex); } finally { serviceHelper?.LetDeviceSleepAsync().Wait(); } }
/// <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."); } } }); }); } } }
public override void OnChange(bool selfChange) { try { OnChange(selfChange, global::Android.Net.Uri.Parse("content://sms")); } catch (Exception ex) { SensusException.Report("Exception in OnChange: " + ex.Message, ex); } }
public override void OnReceive(global::Android.Content.Context context, Intent intent) { // this method is usually called on the UI thread and can crash the app if it throws an exception try { AndroidSensusService.Start(true); } catch (Exception ex) { SensusException.Report("Exception in boot-start broadcast receiver: " + ex.Message, ex); } }
public FacebookPermission(string name, string edge, string field) : base() { _name = name; _edge = edge; _field = field; if (_edge == null && _field == null) { throw SensusException.Report("Facebook permission edge and field are both null for permission \"" + _name + "\"."); } }
private void OpenFile() { lock (_locker) { // it's possible to stop the datastore before entering this lock. if (!Running) { return; } // open new file _path = null; Exception mostRecentException = null; for (int i = 0; _path == null && i < 5; ++i) { try { _path = System.IO.Path.Combine(StorageDirectory, Guid.NewGuid().ToString()); Stream file = new FileStream(_path, FileMode.CreateNew, FileAccess.Write); // add gzip stream if doing compression if (_compressionLevel != CompressionLevel.NoCompression) { file = new GZipStream(file, _compressionLevel, false); } // use buffering for compression and runtime performance _file = new BufferedStream(file, _bufferSizeBytes); _bytesWrittenToCurrentFile = 0; _dataWrittenToCurrentFile = 0; _filesOpened++; } catch (Exception ex) { mostRecentException = ex; _path = null; } } // we could not open a file to write, so we cannot proceed. report the most recent exception and bail. if (_path == null) { throw SensusException.Report("Failed to open file for local data store.", mostRecentException); } else { // open the JSON array byte[] jsonBeginArrayBytes = Encoding.UTF8.GetBytes("["); _file.Write(jsonBeginArrayBytes, 0, jsonBeginArrayBytes.Length); } } }
public SensusMasterDetailPage() { _masterPage = new SensusMasterPage(); _masterPage.MasterPageItemsListView.ItemSelected += (sender, e) => { try { SensusDetailPageItem selectedDetailPageItem = e.SelectedItem as SensusDetailPageItem; if (selectedDetailPageItem != null) { if (selectedDetailPageItem.TargetType == null) { selectedDetailPageItem.Action?.Invoke(); } else { Detail = new NavigationPage((Page)Activator.CreateInstance(selectedDetailPageItem.TargetType)); IsPresented = false; } _masterPage.MasterPageItemsListView.SelectedItem = null; } } catch (Exception ex) { SensusException.Report("Exception while handling master-detail menu selection: " + ex.Message, ex); } }; Master = _masterPage; // the SensusServiceHelper is not yet loaded when this page is constructed. as a result, we cannot assign the // ProtocolsPage to the Detail property. instead, just assign a blank content page and show the user the master // detail list. by the time the user selects from the list, the service helper will be available and the protocols // page will be ready to go. Detail = new NavigationPage(new ContentPage { Content = new Label { Text = "Welcome to Sensus." + Environment.NewLine + "Please select from the menu above.", FontSize = 30, HorizontalOptions = LayoutOptions.CenterAndExpand, VerticalOptions = LayoutOptions.CenterAndExpand, VerticalTextAlignment = TextAlignment.Center, HorizontalTextAlignment = TextAlignment.Center } }); IsPresented = true; }
public override void OnAccessibilityEvent(AccessibilityEvent e) { try { if (e.Text.Count > 0) { AccessibilityBroadcast?.Invoke(this, new KeystrokeDatum(DateTimeOffset.UtcNow, e.Text[0].ToString(), e.PackageName)); } } catch (Exception ex) { SensusException.Report("Exception in Kesystroke service: " + ex.Message, ex); } }
public bool CallbackIsScheduled(string callbackId) { // we should never get a null callback id, but it seems that we are from android. if (callbackId == null) { SensusException.Report("Received null callback id."); return(false); } else { return(_idCallback.ContainsKey(callbackId)); } }
protected ScheduledCallback TryGetCallback(string id) { ScheduledCallback callback; _idCallback.TryGetValue(id, out callback); if (callback == null) { SensusException.Report("Failed to retrieve callback " + id + "."); } return(callback); }