public bool ContainsCallback(ScheduledCallback callback) { // we should never get a null callback id, but it seems that we are from android. if (callback?.Id == null) { SensusException.Report("Attempted to check scheduling status of callback with null id."); return(false); } else { return(_idCallback.ContainsKey(callback.Id)); } }
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); }
private void OpenFile() { lock (_locker) { // it's possible to stop the data store 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); _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); } } }
protected async Task <bool> UpdateFencesAsync(IFenceUpdateRequest updateRequest) { bool success = false; try { // we've seen cases where the update blocks indefinitely (e.g., due to outdated google play services on the // phone). impose a timeout to avoid such blocks. Task <Statuses> updateFencesTask = Awareness.FenceApi.UpdateFencesAsync(_awarenessApiClient, updateRequest); Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(60)); Task finishedTask = await Task.WhenAny(updateFencesTask, timeoutTask); if (finishedTask == updateFencesTask) { Statuses status = await updateFencesTask; if (status.IsSuccess) { SensusServiceHelper.Get().Logger.Log("Updated Google Awareness API fences.", LoggingLevel.Normal, GetType()); success = true; } else if (status.IsCanceled) { SensusServiceHelper.Get().Logger.Log("Google Awareness API fence update canceled.", LoggingLevel.Normal, GetType()); } else if (status.IsInterrupted) { SensusServiceHelper.Get().Logger.Log("Google Awareness API fence update interrupted", LoggingLevel.Normal, GetType()); } else { string message = "Unrecognized fence update status: " + status; SensusServiceHelper.Get().Logger.Log(message, LoggingLevel.Normal, GetType()); SensusException.Report(message); throw new Exception(message); } } else { throw new Exception("Fence update timed out."); } } // catch any errors from calling UpdateFences catch (Exception ex) { // ensure that wait is always set SensusServiceHelper.Get().Logger.Log("Exception while updating fences: " + ex, LoggingLevel.Normal, GetType()); } return(success); }
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))); }
public Window(string windowString) { string[] windowStringParts = windowString.Trim().Split('-'); // format is DD-HH:MM-HH:MM, where the first and last components are optional // check whether the first element is a DOW abbreviation string firstElement = windowStringParts[0].Trim(); List <string> dayOfTheWeekAbbreviations = new List <string>(new string[] { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" }); if (dayOfTheWeekAbbreviations.Contains(firstElement)) { // get enumeration value for abbreviation foreach (string dayOfTheWeek in Enum.GetNames(typeof(DayOfWeek))) { if (dayOfTheWeek.StartsWith(firstElement)) { DayOfTheWeek = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), dayOfTheWeek); break; } } // the string started with a known abbreviation, so we should have obtained the enumeration value. if (DayOfTheWeek == null) { throw SensusException.Report("Unable to obtain DayOfWeek for abbreviation: " + firstElement); } // trim DOW abbreviation from start of array. windowStringParts = windowStringParts.Skip(1).ToArray(); } Start = DateTime.Parse(windowStringParts[0].Trim()).TimeOfDay; // for some reason DateTime.Parse seems to be more forgiving if (windowStringParts.Length == 1) { End = Start; } else if (windowStringParts.Length == 2) { End = DateTime.Parse(windowStringParts[1].Trim()).TimeOfDay; // for some reason DateTime.Parse seems to be more forgiving if (Start > End) { throw new Exception($"Improper trigger window ({windowString})"); } } else { throw new Exception($"Improper trigger window ({windowString})"); } }
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 (INCOMING_SMS != null && intent.Action == "android.provider.Telephony.SMS_RECEIVED") { Bundle bundle = intent.Extras; if (bundle != null) { Java.Lang.Object[] pdus = (Java.Lang.Object[])bundle.Get("pdus"); for (int i = 0; i < pdus.Length; i++) { SmsMessage message; // see the Backwards Compatibility article for more information #if __ANDROID_23__ if (Build.VERSION.SdkInt >= BuildVersionCodes.M) { message = SmsMessage.CreateFromPdu((byte[])pdus[i], intent.GetStringExtra("format")); // API 23 } else #endif { // ignore deprecation warning #pragma warning disable 618 message = SmsMessage.CreateFromPdu((byte[])pdus[i]); #pragma warning restore 618 } Contact contact = SensusServiceHelper.GetContactAsync(message.OriginatingAddress).Result; bool isContact = contact != null; INCOMING_SMS(this, new SmsDatum(DateTimeOffset.FromUnixTimeMilliseconds(message.TimestampMillis), message.OriginatingAddress, null, message.MessageBody, false, isContact, contact?.Name, contact?.Email)); } } } } catch (Exception ex) { SensusException.Report("Exception in SMS broadcast receiver: " + ex.Message, ex); } }
public AndroidPowerConnectionChangeListener() { AndroidPowerConnectionChangeBroadcastReceiver.POWER_CONNECTION_CHANGED += (sender, connected) => { try { PowerConnectionChanged?.Invoke(sender, connected); } catch (Exception ex) { SensusException.Report("Failed to process power connection change.", ex); } }; }
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 { // run asynchronously to release the UI thread System.Threading.Tasks.Task.Run(() => { // the following must be done on the UI thread because we reference UIApplicationState.Active. SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() => { 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 && notification.UserInfo != null) { callbackScheduler.OpenDisplayPage(notification.UserInfo); // provide some generic feedback if the user responded to a silent notification if ((notification.UserInfo.ValueForKey(new NSString(iOSNotifier.SILENT_NOTIFICATION_KEY)) as NSNumber)?.BoolValue ?? false) { SensusServiceHelper.Get().FlashNotificationAsync("Study Updated.", false); } } }); }); } } }
protected override async void OnResume() { Console.Error.WriteLine("--------------------------- Resuming activity ---------------------------"); base.OnResume(); // temporarily hide UI while we bind to service. allowing the user to click around before the service helper is initialized // may result in a crash. (Xamarin.Forms.Application.Current as App).MasterPage.IsVisible = false; (Xamarin.Forms.Application.Current as App).DetailPage.IsVisible = false; // ensure the service is bound any time the activity is resumed BindService(AndroidSensusService.GetServiceIntent(false), _serviceConnection, Bind.AboveClient); // start new task to wait for connection, since we're currently on the UI thread, which the service connection needs in order to complete. await Task.Run(() => { // we've not seen the binding take more than a second or two; however, we want to be very careful not to block indefinitely // here because the UI is currently disabled. if for some strange reason the binding does not work, bail out after 10 seconds // and let the user interact with the UI. most likely, a crash will be coming very soon in this case, as the sensus service // will probably not be running. again, this has not occurred in practice, but allowing the crash to occur will send us information // through the crash analytics service and we'll be able to track it TimeSpan serviceBindTimeout = TimeSpan.FromSeconds(10000); if (_serviceBindWait.WaitOne(serviceBindTimeout)) { SensusServiceHelper.Get().Logger.Log("Activity proceeding following service bind.", LoggingLevel.Normal, GetType()); } else { SensusException.Report("Timed out waiting " + serviceBindTimeout + " for the service to bind."); } SensusServiceHelper.Get().CancelPendingSurveysNotification(); // enable the UI try { SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() => { (Xamarin.Forms.Application.Current as App).MasterPage.IsVisible = true; (Xamarin.Forms.Application.Current as App).DetailPage.IsVisible = true; }); } catch (Exception) { } }); }
public iOSPowerConnectionChangeListener() { UIDevice.Notifications.ObserveBatteryStateDidChange((sender, e) => { try { UIDeviceBatteryState batteryState = UIDevice.CurrentDevice.BatteryState; bool connected = batteryState == UIDeviceBatteryState.Charging || batteryState == UIDeviceBatteryState.Full; PowerConnectionChanged?.Invoke(this, connected); } catch (Exception ex) { SensusException.Report("Failed to process power connection change.", ex); } }); }
/// <summary> /// Gets the Azure format identifier for the native push notification service. /// </summary> /// <returns>The Azure format identifier.</returns> /// <param name="format">Format.</param> public static string GetAzureFormatIdentifier(PushNotificationRequestFormat format) { if (format == PushNotificationRequestFormat.FirebaseCloudMessaging) { return("gcm"); } else if (format == PushNotificationRequestFormat.ApplePushNotificationService) { return("apple"); } else { SensusException.Report("Unrecognized push notification request format: " + format); return(""); } }
private async Task GetCredentialsFromAuthenticationServiceAsync() { if (Protocol.AuthenticationService == null) { SensusException.Report(nameof(GetCredentialsFromAuthenticationServiceAsync) + " called without an authentication service."); } else { // set keys/token for use in the data store AmazonS3Credentials s3Credentials = await Protocol.AuthenticationService.GetCredentialsAsync(); _iamAccessKey = s3Credentials.AccessKeyId; _iamSecretKey = s3Credentials.SecretAccessKey; _sessionToken = s3Credentials.SessionToken; _region = s3Credentials.Region; } }
private void PromoteFiles() { lock (_locker) { foreach (string path in Directory.GetFiles(StorageDirectory)) { try { // promotion applies to files that don't yet have a file extension if (!string.IsNullOrWhiteSpace(System.IO.Path.GetExtension(path))) { continue; } // add the .json file extension, marking the file as complete. string finalPath = path + JSON_FILE_EXTENSION; File.Move(path, finalPath); // add the gzip file extension if we're doing compression if (_compressionLevel != CompressionLevel.NoCompression) { // add the .gz extension to the path string gzipPath = finalPath + GZIP_FILE_EXTENSION; File.Move(finalPath, gzipPath); finalPath = gzipPath; } // encrypt the file if needed if (_encrypt) { string encryptedPath = finalPath + ENCRYPTED_FILE_EXTENSION; Protocol.AsymmetricEncryption.EncryptSymmetrically(File.ReadAllBytes(finalPath), ENCRYPTION_KEY_SIZE_BITS, ENCRYPTION_INITIALIZATION_KEY_SIZE_BITS, encryptedPath); // if everything went through okay, delete the unencrypted file. File.Delete(finalPath); } _filesPromoted++; } catch (Exception ex) { SensusException.Report("Failed to promote file.", ex); } } } }
private Task Put(AmazonS3Client s3, Stream stream, string key, string contentType, CancellationToken cancellationToken) { return(Task.Run(async() => { _putCount++; try { PutObjectRequest putRequest = new PutObjectRequest { BucketName = _bucket, CannedACL = S3CannedACL.BucketOwnerFullControl, // without this, the bucket owner will not have access to the uploaded data InputStream = stream, Key = key, ContentType = contentType }; HttpStatusCode putStatus = (await s3.PutObjectAsync(putRequest, cancellationToken)).HttpStatusCode; if (putStatus == HttpStatusCode.OK) { _successfulPutCount++; } else { throw new Exception("Bad status code: " + putStatus); } } catch (WebException ex) { if (ex.Status == WebExceptionStatus.TrustFailure) { string message = "A trust failure has occurred between Sensus and the AWS S3 endpoint. This is likely the result of a failed match between the server's public key and the pinned public key within the Sensus AWS S3 remote data store."; SensusException.Report(message, ex); } throw ex; } catch (Exception ex) { string message = "Failed to write data stream to Amazon S3 bucket \"" + _bucket + "\": " + ex.Message; SensusServiceHelper.Get().Logger.Log(message + " " + ex.Message, LoggingLevel.Normal, GetType()); throw new Exception(message, ex); } })); }
public async Task <Account> CreateAccountAsync(string participantId) { string deviceType = ""; if (SensusContext.Current.Platform == Platform.Android) { deviceType = "android"; } else if (SensusContext.Current.Platform == Platform.iOS) { deviceType = "ios"; } else { SensusException.Report("Unrecognized platform: " + SensusContext.Current.Platform); } string accountJSON = await new Uri(string.Format(_createAccountURL, SensusServiceHelper.Get().DeviceId, participantId, deviceType)).DownloadStringAsync(); try { Account = accountJSON.DeserializeJson <Account>(); } catch (Exception ex) { SensusException.Report("Exception while deserializing account: " + ex.Message, ex); } // check properties. trim while we're at it. if (string.IsNullOrWhiteSpace(Account.ParticipantId = Account.ParticipantId?.Trim())) { SensusException.Report("Empty " + nameof(Account.ParticipantId) + " returned by authentication service for device " + SensusServiceHelper.Get().DeviceId + " and participant " + (participantId ?? "[null].")); } if (string.IsNullOrWhiteSpace(Account.Password = Account.Password?.Trim())) { SensusException.Report("Empty " + nameof(Account.Password) + " returned by authentication service for device " + SensusServiceHelper.Get().DeviceId + " and participant " + (participantId ?? "[null].")); } // save the app state to hang on to the account await SensusServiceHelper.Get().SaveAsync(); return(Account); }
/// <summary> /// Initializes a new instance of the <see cref="Anonymizable"/> class. /// </summary> /// <param name="propertyDisplayName">Property display name.</param> /// <param name="availableAnonymizerTypes">Available anonymizer types.</param> /// <param name="defaultAnonymizerIndex">Default anonymizer index. Pass -1 for no anonymization.</param> public Anonymizable(string propertyDisplayName, Type[] availableAnonymizerTypes, int defaultAnonymizerIndex) { _propertyDisplayName = propertyDisplayName; if (availableAnonymizerTypes == null) { availableAnonymizerTypes = new Type[0]; } // we're always going to add the value omitting anonymizer at the start of the anonymizers list. if // the default is >= 0 add 1 to produce the result that the caller desires. only do this if the // caller has passed in anonymizer types. if they didn't and they set the default anonymizer to // 0, then they are asking for the value omitting anonymizer by default -- in this case we should // not increment. if (defaultAnonymizerIndex >= 0 && availableAnonymizerTypes.Length > 0) { ++defaultAnonymizerIndex; } // instantiate available anonymizers _availableAnonymizers = new List <Anonymizer>(); _availableAnonymizers.Add(new ValueOmittingAnonymizer()); // omitting the value is always an option foreach (Type availableAnonymizerType in availableAnonymizerTypes) { Anonymizer availableAnonymizer = Activator.CreateInstance(availableAnonymizerType) as Anonymizer; if (availableAnonymizer == null) { throw SensusException.Report("Attempted to create an anonymizer from a type that does not derive from Anonymizer."); } _availableAnonymizers.Add(availableAnonymizer); } if (defaultAnonymizerIndex < -1 || defaultAnonymizerIndex >= _availableAnonymizers.Count) { throw SensusException.Report("Attempted to set default anonymizer for property outside the bounds of available types: " + defaultAnonymizerIndex); } // set default anonymizer if requested if (defaultAnonymizerIndex >= 0) { _defaultAnonymizer = _availableAnonymizers[defaultAnonymizerIndex]; } }
/// <summary> /// Creates a value provider for a member. Only works for PropertyInfo members and will throw an /// exception for all other members. /// </summary> /// <returns>The member value provider.</returns> /// <param name="member">Member.</param> protected override IValueProvider CreateMemberValueProvider(MemberInfo member) { IValueProvider defaultValueProvider = base.CreateMemberValueProvider(member); // only datum objects should be serialized, and these should only contain serialized properties. PropertyInfo propertyInfo = member as PropertyInfo; if (propertyInfo == null) { // this is unexpected. report the issue and return the default serializer. SensusException.Report("Attempted to serialize/anonymize non-property datum member: " + member); return(defaultValueProvider); } else { return(new AnonymizedPropertyValueProvider(propertyInfo, defaultValueProvider, this)); } }
private void OpenFile() { lock (_fileLocker) { // it's possible to stop the data store before entering this lock. if (!Running) { return; } // try a few times to open a new file within the storage directory _currentPath = null; Exception mostRecentException = null; for (int tryNum = 0; _currentPath == null && tryNum < 5; ++tryNum) { try { _currentPath = Path.Combine(StorageDirectory, Guid.NewGuid().ToString()); _currentFile = new BufferedStream(new FileStream(_currentPath, FileMode.CreateNew, FileAccess.Write), _currentFileBufferSizeBytes); _totalDataWrittenToCurrentFile = 0; _totalFilesOpened++; } catch (Exception ex) { mostRecentException = ex; _currentPath = null; } } // we could not open a file to write, so we cannot proceed. report the most recent exception and bail. if (_currentPath == null) { throw SensusException.Report("Failed to open file for local data store.", mostRecentException); } else { // open the JSON array byte[] jsonOpenArrayBytes = Encoding.UTF8.GetBytes("["); _currentFile.Write(jsonOpenArrayBytes, 0, jsonOpenArrayBytes.Length); } } }
public object GetValue(object target) { if (target == null) { throw SensusException.Report("Attempted to process a null object."); } // if the target object is a Datum, consider anonymizing the property value else if (target is Datum) { Datum datum = target as Datum; // if we're processing the Anonymized property, return true so that the output JSON properly reflects the fact that the datum has // been passed through an anonymizer (this regardless of whether an anonymization transformation was actually applied). this also // ensures that, if the JSON is deserialized and then reserialized, we won't attempt to anonymize the JSON again (see checks below). if (_property.DeclaringType == typeof(Datum) && _property.Name == nameof(Datum.Anonymized)) { return(true); } else { // first get the property's value in the default way object propertyValue = _defaultMemberValueProvider.GetValue(datum); Anonymizer anonymizer; if (propertyValue == null || // don't attempt anonymization if the property value is null datum.Anonymized || // don't re-anonymize property values (anonymizer = _contractResolver.GetAnonymizer(datum.GetType().GetProperty(_property.Name))) == null) // don't anonymize when we don't have an anonymizer. we re-get the PropertyInfo from the datum's type so that it matches our dictionary of PropertyInfo objects (the reflected type needs to be the most-derived, which doesn't happen leading up to this point for some reason). { return(propertyValue); } else { return(anonymizer.Apply(propertyValue, _contractResolver.Protocol)); } } } // if the target is not a datum object (e.g., for InputCompletionRecords stored in ScriptDatum objects), simply return the member value in the default way. else { return(_defaultMemberValueProvider.GetValue(target)); } }
public void IssueNotificationAsync(UNNotificationRequest request, Action <NSError> errorCallback = null) { // 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; } 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 virtual bool ValueMatches(object conditionValue, bool conjunctive) { // if either is null, both must be null to be equal if (Value == null || conditionValue == null) { return(Value == null && conditionValue == null); } // if they're of the same type, compare else if (Value.GetType().Equals(conditionValue.GetType())) { return(Value.Equals(conditionValue)); } else { // this should never happen SensusException.Report(new Exception("Called Input.ValueMatches with conditionValue of type " + conditionValue.GetType() + ". Comparing with value of type " + Value.GetType() + ".")); return(false); } }
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 == Intent.ActionNewOutgoingCall) { OUTGOING_CALL?.Invoke(this, intent.GetStringExtra(Intent.ExtraPhoneNumber)); } } catch (Exception ex) { SensusException.Report("Exception in telephony broadcast receiver: " + 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 { if (intent == null) { throw new ArgumentNullException(nameof(intent)); } if (intent.Action == Intent.ActionPowerConnected || intent.Action == Intent.ActionPowerDisconnected) { POWER_CONNECTION_CHANGED?.Invoke(this, intent.Action == Intent.ActionPowerConnected); } } catch (Exception ex) { SensusException.Report("Exception in power connection change broadcast receiver: " + ex.Message, ex); } }
/// <summary> /// Cancels a UILocalNotification. This will succeed in one of two conditions: (1) if the notification to be /// cancelled is scheduled (i.e., not delivered); or (2) if the notification to be cancelled has been delivered /// and if the object passed in is the delivered notification and not the one that was passed to /// ScheduleLocalNotification -- once passed to ScheduleLocalNotification, a copy is made and the objects won't test equal /// for cancellation. /// </summary> /// <param name="notification">Notification to cancel.</param> /// <param name="notificationIdKey">Key for ID in UserInfo of the UILocalNotification.</param> private void CancelLocalNotification(UILocalNotification notification, string notificationIdKey) { // set up action to cancel notification SensusContext.Current.MainThreadSynchronizer.ExecuteThreadSafe(() => { try { string idToCancel = notification.UserInfo.ValueForKey(new NSString(notificationIdKey)).ToString(); Get().Logger.Log("Cancelling local notification \"" + idToCancel + "\".", LoggingLevel.Normal, typeof(iOSSensusServiceHelper)); // a local notification can be scheduled, in which case it hasn't yet been delivered and should reside within the shared // application's list of scheduled notifications. the tricky part here is that these notification objects aren't reference-equal, // so we can't just pass `notification` to CancelLocalNotification. instead, we must search for the notification by id and // cancel the appropriate scheduled notification object. bool notificationCanceled = false; foreach (UILocalNotification scheduledNotification in UIApplication.SharedApplication.ScheduledLocalNotifications) { string scheduledId = scheduledNotification.UserInfo.ValueForKey(new NSString(notificationIdKey))?.ToString(); if (scheduledId == idToCancel) { UIApplication.SharedApplication.CancelLocalNotification(scheduledNotification); notificationCanceled = true; } } // if we didn't cancel the notification above, then it isn't scheduled and should have already been delivered. if it has been // delivered, then our only option for cancelling it is to pass `notification` itself to CancelLocalNotification. this assumes // that `notification` is the actual notification object and not, for example, the one originally passed to ScheduleLocalNotification. if (!notificationCanceled) { UIApplication.SharedApplication.CancelLocalNotification(notification); } } catch (Exception ex) { SensusException.Report("Failed to cancel notification.", ex, false); } }); }
private void PromoteFiles() { lock (_locker) { foreach (string path in Directory.GetFiles(StorageDirectory)) { try { // promotion applies to files that don't yet have a file extension if (!string.IsNullOrWhiteSpace(System.IO.Path.GetExtension(path))) { continue; } string promotedPath = path + JSON_FILE_EXTENSION; if (_compressionLevel != CompressionLevel.NoCompression) { promotedPath += GZIP_FILE_EXTENSION; } if (_encrypt) { promotedPath += ENCRYPTED_FILE_EXTENSION; Protocol.AsymmetricEncryption.EncryptSymmetrically(File.ReadAllBytes(path), ENCRYPTION_KEY_SIZE_BITS, ENCRYPTION_INITIALIZATION_KEY_SIZE_BITS, promotedPath); File.Delete(path); } else { File.Move(path, promotedPath); } _filesPromoted++; } catch (Exception ex) { SensusException.Report("Failed to promote file.", ex); } } } }
public override Task LetDeviceSleepAsync() { if (_wakeLock != null) { lock (_wakeLock) { // ensure the wake lock is held in order to prevent under-locking exceptions if (_wakeLock.IsHeld) { _wakeLock.Release(); Logger.Log("Wake lock released" + (_wakeLock.IsHeld ? "" : " for the final time") + ".", LoggingLevel.Normal, GetType()); // if wake lock is no longer held, then update the amount of time spent holding it. if (!_wakeLock.IsHeld) { if (_wakeLockTimestamp == null) { SensusException.Report("Released wake lock for the final time without a timestamp on the first acquisition."); } else { _wakeLockTime += DateTime.Now - _wakeLockTimestamp.Value; SensusServiceHelper.Get().Logger.Log("Has spent " + _wakeLockTime + " holding the wake lock.", LoggingLevel.Normal, GetType()); _wakeLockTimestamp = null; } } } else { SensusException.Report("Attempted to call " + nameof(LetDeviceSleepAsync) + ", but the wake lock is not currently held."); _wakeLockTimestamp = null; } } } return(Task.CompletedTask); }
protected Probe() { _enabled = false; _state = ProbeState.Stopped; _storeData = true; _startStopTimes = new List <Tuple <bool, DateTime> >(); _successfulHealthTestTimes = new List <DateTime>(); _maxChartDataCount = 10; _chartData = new List <ChartDataPoint>(_maxChartDataCount + 1); _powerConnectionChanged = async(sender, connected) => { if (connected) { // ask the probe to start processing its data try { SensusServiceHelper.Get().Logger.Log("AC power connected. Initiating data processing within probe.", LoggingLevel.Normal, GetType()); _processDataCanceller = new CancellationTokenSource(); await ProcessDataAsync(_processDataCanceller.Token); SensusServiceHelper.Get().Logger.Log("Probe data processing complete.", LoggingLevel.Normal, GetType()); } catch (OperationCanceledException) { // don't report task cancellation exceptions. these are expected whenever the user unplugs the device while processing data. SensusServiceHelper.Get().Logger.Log("Data processing task was cancelled.", LoggingLevel.Normal, GetType()); } catch (Exception ex) { // the data processing actually failed prior to cancellation. this should not happen, so report it. SensusException.Report("Non-cancellation exception while processing probe data: " + ex.Message, ex); } } else { // cancel any previous attempt to process data _processDataCanceller?.Cancel(); } }; }
public ScheduledCallbackState ScheduleCallback(ScheduledCallback callback) { 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.NextExecution = DateTime.Now + callback.Delay; callback.State = ScheduledCallbackState.Scheduled; ScheduleCallbackPlatformSpecific(callback); } else { SensusException.Report("Attempted to schedule duplicate callback for " + callback.Id + "."); } return(callback.State); }